Meteor: oncreated vs onrendered - javascript

The dilemma:
oncreated: the template is not yet rendered (fires only one
time for each template).
onrendered: the template is rendered (fires multiple times).
Is it possible to fire a function only once the template is rendered fully?
I have a list of messages, that look similar to this
<template name="messages">
<div id="messages">
<span class="message">{{this.message}}</span>
</div>
</template>
Each time a new message is inserted into the DOM, I want to know if the text of the message contains the username.
The following code snippet runs multiple times, of which it should only run a single time.
Template.messages.rendered = function() {
var username = Meteor.user().services.twitter.screenName;
$("#messages").bind("DOMSubtreeModified", function() {
var lastmessage = $('.message').last().text();
if (lastmessage.indexOf(username) > -1) {
//Do something
}
}
}
Interchanging rendered by created & changing the template to contain a single message, makes the function run one time for each new message. This means it takes the second to last value for the lastmessage variable:
Template.message.created = function() {
var username = Meteor.user().services.twitter.screenName;
var lastmessage = $('.message').last().text();//this is not the last message
if (lastmessage.indexOf(username) > -1) {
//Do something
}
}

Honestly couldn't you do something like this in "rendered". Or you could put in some callback after you render your extra stuff into the DOM.
Additionally you could use self.autorun(() => { if(self.alreadyRun) return; ... }) and self.alreadyRun = new ReactiveVar(false).
Just guessing!
var self = this;
self.alreadyRun = false;
if(self.alreadyRun){
self.alreadyRun = true;
// run once code here
}

Fixed this by wrapping the code in a setTimeout function.

Related

How to detect an account change in MetaMask using Meteor and web3?

I am creating a DApp using meteor in which a templateVar displays an account address.
Using a web3 function in JavaScript I defined the updateInterface() method as seen below as updateInterface()
However, since the method is a loop I can't seem to trigger the event inside Template.foo.events. The method works as I am able to alert(res) alert the result onto the screen.
Could somebody point me in the right direction? cheers
EDIT: added the template.oncreated function and also the function updateInterface() is placed outside the oncreated, helper and events methods.
Template.foo.onCreated(function fooOnCreated() {
this.account= new ReactiveVar(0);
});
Template.foo.helpers({
account() {
var template = Template.instance();
myContract = web3.eth.contract(ABIArray).at(contractAddress);
var result = web3.eth.getCoinbase(function(err, res) {
TemplateVar.set(template, "person", res);
});
},
});
Template.foo.events({
// not sure what to place here
});
var account = web3.eth.accounts[0];
var accountInterval = setInterval(function() {
if (web3.eth.accounts[0] !== account) {
account = web3.eth.accounts[0];
updateInterface();
}
}, 100);
function updateInterface() {
var template = Template.instance();
var result = web3.eth.getCoinbase(function(err, res) {
alert(res);
TemplateVar.set(template, "account", res);
});
}
The problem with events is that it requires an event, whereas the metamask detection function detects a change in account every 100 milliseconds, I am not sure where to place the updateInterface() method.
TemplateVar points here.
<head>
<body>
<div id="section">
{{>foo}}
</div>
</body>
</head>
<template name="foo">
<div id="location">
<p>Currently logged in as: {{account}} {{TemplateVar.get
"account"}} </p>
</div>
</template>
I think you should put the interval loop inside of the onCreated and instead of checking account !== web3.eth.accounts[0] you should check self.account.get() = web3.eth.accounts[0]
like this
Template.foo.onCreated(function fooOnCreated() {
this.account= new ReactiveVar(0);
this.account.set(web3.eth.accounts[0]);
var self = this;
var accountInterval = setInterval(function() {
if (web3.eth.accounts[0] !== self.account.get()) {
self.account.set(web3.eth.accounts[0]);
}
}, 500);
});
and because it is a ReactiveVar you don't have to call the update function. The template should update automatically as the value is used in it.
I have not tested it but I think it should work like this

JQuery $.post callback firing a function that never finishes

Here's the problem. I'm making a callback to the server that receives an MVC partial page. It's been working great, it calls the success function and all that. However, I'm calling a function after which iterates through specific elements:
$(".tool-fields.in div.collapse, .common-fields div.collapse").each(...)
Inside this, I'm checking for a specific attribute (custom one using data-) which is also working great; however; the iterator never finishes. No error messages are given, the program doesn't hold up. It just quits.
Here's the function with the iterator
function HideShow() {
$(".tool-fields.in div.collapse, .common-fields div.collapse").each(function () {
if (IsDataYesNoHide(this)) {
$(this).collapse("show");
}
else
$(this).collapse("hide");
});
alert("test");
}
Here's the function called in that, "IsDataYesNoHide":
function IsDataYesNoHide(element) {
var $element = $(element);
var datayesnohide = $element.attr("data-yes-no-hide");
if (datayesnohide !== undefined) {
var array = datayesnohide.split(";");
var returnAnswer = true;
for (var i in array) {
var answer = array[i].split("=")[1];
returnAnswer = returnAnswer && (answer.toLowerCase() === "true");
}
return returnAnswer;
}
else {
return false;
}
}
This is the way the attribute appears
data-yes-no-hide="pKanban_Val=true;pTwoBoxSystem_Val=true;"
EDIT: Per request, here is the jquery $.post
$.post(path + conPath + '/GrabDetails', $.param({ data: dataArr }, true), function (data) {
ToggleLoader(false); //Page load finished so the spinner should stop
if (data !== "") { //if we got anything back of if there wasn't a ghost record
$container.find(".container").first().append(data); //add the content
var $changes = $("#Changes"); //grab the changes
var $details = $("#details"); //grab the current
SplitPage($container, $details, $changes); //Just CSS changes
MoveApproveReject($changes); //Moves buttons to the left of the screen
MarkAsDifferent($changes, $details) //Adds the data- attribute and colors differences
}
else {
$(".Details .modal-content").removeClass("extra-wide"); //Normal page
$(".Details input[type=radio]").each(function () {
CheckOptionalFields(this);
});
}
HideShow(); //Hide or show fields by business logic
});
For a while, I thought the jquery collapse was breaking, but putting the simple alert('test') showed me what was happening. It just was never finishing.
Are there specific lengths of time a callback function can be called from a jquery postback? I'm loading everything in modal views which would indicate "oh maybe jquery is included twice", but I've already had that problem for other things and have made sure that it only ever includes once. As in the include is only once in the entire app and the layout is only applied to the main page.
I'm open to any possibilities.
Thanks!
~Brandon
Found the problem. I had a variable that was sometimes being set as undefined cause it to silently crash. I have no idea why there was no error message.

How to show the result of this jQuery function without the need of clicking the button?

I have this function below, however I want to make it work on windows load and show the result without clicking the button.
This is the code I use https://raw.githubusercontent.com/SuyashMShepHertz/indexedDB_sample/master/index.html
How to do this?
$("#getBtn").click(function(){
var type = 'permanent';
var request = db.transaction(["hashes"],"readwrite").objectStore("hashes").get(type);
request.onsuccess = function(event){
$("#result").html("Name : "+request.result.name);
};
});
just put your code in
$( window ).load(function() {
//Code Here
});
If you need it both on click and initially when the page loads, make it a reusable function:
function doTheThing() {
var type = 'permanent';
var request = db.transaction(["hashes"], "readwrite").objectStore("hashes").get(type);
request.onsuccess = function(event) {
$("#result").html("Name : " + request.result.name);
};
}
Then call it from both places you need it:
On page load
On click
To call it on page load, just make sure your script is at the end of the HTML (just before the closing </body> tag; this is best practice unless you have a good reason for doing something else) and call it:
doTheThing();
If you can't put the script at the end of the HTML, you can use jQuery's ready callback instead:
// Concise, but easy to misunderstand:
$(doTheThing);
// Or more verbose but also more clear:
$(document).ready(doTheThing);
(See note below about doing it directly or indirectly.)
To call it on click, hook it up, either directly or indirectly:
// Directly
$("#getBtn").click(doTheThing);
// Or indirectly
$("#getBtn").click(function() {
doTheThing();
});
The only reason for hooking it up indirectly would be to avoid having it receive the event object jQuery will pass it automatically, and to avoid having its return value examined by jQuery to see if it should stop propagation and prevent the default event action.
To avoid creating globals, I'd make sure the entire thing is in a scoping function:
(function() {
function doTheThing() {
var type = 'permanent';
var request = db.transaction(["hashes"], "readwrite").objectStore("hashes").get(type);
request.onsuccess = function(event) {
$("#result").html("Name : " + request.result.name);
};
}
doTheThing();
$("#getBtn").click(doTheThing);
})();
just put it in $(document).ready, like this
$(document).ready(function(){
var type = 'permanent';
var request = db.transaction(["hashes"],"readwrite").objectStore("hashes").get(type);
request.onsuccess = function(event){
$("#result").html("Name : "+request.result.name);
};
});

addEventListener only firing once

I'm adding a click event to all links that match a particular selector as part of a JS module I'm creating. It looks something like this.
var Lightbox = (function () {
var showLightbox = function () {
// this does stuff
};
var init = function () {
var links = document.querySelectorAll(options.selector);
for(var i = 0; i < links.length; i++) {
links[i].addEventListener('click', function() {
showLightbox();
}, false);
}
};
return {
init: init
};
})();
Lightbox.init();
On first load the any links on the page that match the selector work. There is also a closeLightbox() method that works fine. However when clicking the links for a second time nothing happens. I get no console errors – nuffin.
Is there something I'm doing wrong when adding the event listener?
EDIT: I've updated the code to remove some redundant methods and have pasted the full code here: http://pastebin.com/mC8pSAV2
You are reassigning innerHTML of the whole document:
document.body.innerHTML += response;
on the link click. That wipes out all existing DOM elements with their events and creates new DOM structure with no clicks assigned.

Click all anchor tags on page with given class, but cancel prior to navigation

Trying to automate some testing for some analytics tracking code, and I'm running into issues when I try passing links into the each() method.
I copied a lot of this from stackoverflow - how to follow all links in casperjs, but I don't need return the href of the link; I need to return the link itself (so I can click it). I keep getting this error: each() only works with arrays. Am I not returning an array?
UPDATE:
For each anchor tag that has .myClass, click it, then return requested parameters from casper.options.onResourceReceived e.g. event category, event action, etc. I may or may not have to cancel the navigation the happens after the click; I simply only need to review the request, and do not need the follow page to load.
Testing steps:
click link that has .myClass
look at request parameters
cancel the click to prevent it from going to the next page.
I'm new to javascript and casper.js, so I apologize if I'm misinterpreting.
ANOTHER UPDATE:
I've updated the code to instead return an array of classes. There are a few sketchy bits of code in this though (see comments inline).
However, I'm now having issues canceling the navigation after the click. .Clear() canceled all js. Anyway to prevent default action happening after click? Like e.preventDefault();?
var casper = require('casper').create({
verbose: true,
logLevel: 'debug'
});
casper.options.onResourceReceived = function(arg1, response) {
if (response.url.indexOf('t=event') > -1) {
var query = decodeURI(response.url);
var data = query.split('&');
var result = {};
for (var i = 0; i < data.length; i++) {
var item = data[i].split('=');
result[item[0]] = item[1];
}
console.log('EVENT CATEGORY = ' + result.ec + '\n' +
'EVENT ACTION = ' + result.ea + '\n' +
'EVENT LABEL = ' + decodeURIComponent(result.el) + '\n' +
'REQUEST STATUS = ' + response.status
);
}
};
var links;
//var myClass = '.myClass';
casper.start('http://www.leupold.com', function getLinks() {
links = this.evaluate(function() {
var links = document.querySelectorAll('.myClass');
// having issues when I attempted to pass in myClass var.
links = Array.prototype.map.call(links, function(link) {
// seems like a sketchy way to get a class. what happens if there are multiple classes?
return link.getAttribute('class');
});
return links;
});
});
casper.waitForSelector('.myClass', function() {
this.echo('selector is here');
//this.echo(this.getCurrentUrl());
//this.echo(JSON.stringify(links));
this.each(links, function(self, link) {
self.echo('this is a class : ' + link);
// again this is horrible
self.click('.' + link);
});
});
casper.run(function() {
this.exit();
});
There are two problems that you're dealing with.
1. Select elements based on class
Usually a class is used multiple times. So when you first select elements based on this class, you will get elements that have that class, but it is not guaranteed that this will be unique. See for example this selection of element that you may select by .myClass:
myClass
myClass myClass2
myClass myClass3
myClass
myClass myClass3
When you later iterate over those class names, you've got a problem, because 4 and 5 can never be clicked using casper.click("." + links[i].replace(" ", ".")) (you need to additionally replace spaces with dots). casper.click only clicks the first occurrence of the specific selector. That is why I used createXPathFromElement taken from stijn de ryck to find the unique XPath expression for every element inside the page context.
You can then click the correct element via the unique XPath like this
casper.click(x(xpathFromPageContext[i]));
2. Cancelling navigation
This may depend on what your page actually is.
Note: I use the casper.test property which is the Tester module. You get access to it by invoking casper like this: casperjs test script.js.
Note: There is also the casper.waitForResource function. Have a look at it.
2.1 Web 1.0
When a click means a new page will be loaded, you may add an event handler to the page.resource.requested event. You can then abort() the request without resetting the page back to the startURL.
var resourceAborted = false;
casper.on('page.resource.requested', function(requestData, request){
if (requestData.url.match(/someURLMatching/)) {
// you can also check requestData.headers which is an array of objects:
// [{name: "header name", value: "some value"}]
casper.test.pass("resource passed");
} else {
casper.test.fail("resource failed");
}
if (requestData.url != startURL) {
request.abort();
}
resourceAborted = true;
});
and in the test flow:
casper.each(links, function(self, link){
self.thenClick(x(link));
self.waitFor(function check(){
return resourceAborted;
});
self.then(function(){
resourceAborted = false; // reset state
});
});
2.2 Single page application
There may be so many event handlers attached, that it is quite hard to prevent them all. An easier way (at least for me) is to
get all the unique element paths,
iterate over the list and do every time the following:
Open the original page again (basically a reset for every link)
do the click on the current XPath
This is basically what I do in this answer.
Since single page apps don't load pages. The navigation.requested and page.resource.requested will not be triggered. You need the resource.requested event if you want to check some API call:
var clickPassed = -1;
casper.on('resource.requested', function(requestData, request){
if (requestData.url.match(/someURLMatching/)) {
// you can also check requestData.headers which is an array of objects:
// [{name: "header name", value: "some value"}]
clickPassed = true;
} else {
clickPassed = false;
}
});
and in the test flow:
casper.each(links, function(self, link){
self.thenOpen(startURL);
self.thenClick(x(link));
self.waitFor(function check(){
return clickPassed !== -1;
}, function then(){
casper.test.assert(clickPassed);
clickPassed = -1;
}, function onTimeout(){
casper.test.fail("Resource timeout");
});
});

Categories