I have the following code:
for (var i = 0; i < numFiles; i++) {
$.post('/Secure/File/PhysicalFileHandler.ashx?custId=whatever&idx=' + i, function (data) {
ids += data + '|';
if (i == numFiles-1) {
ids = ids.substring(0, ids.length - 1);
}
});
}
How could I execute a chunk of code after the last post has finished? The method I was trying above does not work (i is == to numFiles already when it gets there). Suggestions?
This is a somewhat common problem - the "i" you are referencing is handled by the for function, and commonly the for function has finished executing by the time the callback is triggered (in which case, the "i" is equal to numFiles already). There are a couple solutions to this: one would be to change the callback to this:
(function (i) {
return function (data) {
ids += data + '|';
if (i == numFiles-1) {
ids = ids.substring(0, ids.length - 1);
}
})(i)
What this does is create a new function for each execution of the loop, and each time the function has it's own reference to "i" - instead of looking back to the for loop's definition/value of i, it has it's own local parameter to refer to. This has to be done by returning another function since you need the code to execute during the first pass of the for loop, ensuring the right value is preserved when the callback is triggered.
One other alternative is to wrap the entire post statement in a function with it's own copy of i - I don't think there's too much difference between the two. Personally, I favor the approach in the above snippet more since it minimizes the effect of the overall flow of the code, but opinions will vary on this.
The posts may not finish in the order they were started. You must count each one as it finishes to know that all have finished.
var finishedCount = 0; // track number of calls finished
for (var i = 0; i < numFiles; i++) {
(function(i) {
$.post('/Secure/File/PhysicalFileHandler.ashx?custId=whatever&idx=' + i, function (data) {
ids += data + '|';
finishedCount = finishedCount + 1; // increment number of calls finished
if (finishedCount == numFiles) { // run only when all calls have finished
ids = ids.substring(0, ids.length - 1);
}
});
})(i); // capture value of i in closure
}
Edit: Capturing value of i in the closure is no longer necessary since i is not used in the post callback function in this version.
Related
This question has been flagged as already answered with a link provided above. However, I already read that answer and it only answered how to use setInterval in a for loop. There were no functions being called with parameters passed to them in that solution, and that is my situation, so I couldn't use it to fix my situation.
I'm fairly new to programming, so I'll try to describe as best as I can. In setInterval, I am passing a parameter to the function toggleClusters which setInterval calls. The debugger shows the parameter as being correct. It is a reference to an array position that holds an object literal that contains map marker objects. I seem to be misunderstanding something about what values stay around and what do not when using setInterval, because the debugger shows the correct object literal being passed as an arg, but when the function is called, the debugger shows the obj that is supposed to be passed as undefined. Is it that this passed value no longer exists when the function is called?
function setClusterAnimations() {
for (var i = 0; i < clusters.length; i++) {
//intervalNames stores handle references for stopping any setInterval instances created
intervalNames.push(setInterval(function () {
//clusters[i] will hold an object literal containing marker objects
toggleClusters(clusters[i]);
}, 1000));
}
}
//cObj is coming back as undefined in debugger and bombing
function toggleClusters(cObj) {
var propCount = Object.keys(cObj).length;
for (var prop in cObj){
if (prop.getZIndex() < 200 || prop.getZIndex() == 200 + propCount) {
prop.setZIndex(200);
}
else {
prop.setZindex(prop.getZIndex() + 1)
}
}
}
This is typically the issue with such asynchronous calls as with setInterval(). You can solve this in different ways, one of which is using bind():
for (var i = 0; i < clusters.length; i++) {
//intervalNames stores handle references for stopping any setInterval instances created
intervalNames.push(setInterval(function (i) {
//clusters[i] will hold an object literal containing marker objects
toggleClusters(clusters[i]);
}.bind(null, i), 1000));
}
The toggleClusters(clusters[i]) statement will only be executed when your loop has finished, at which time i will be beyond the correct range (it will be clusters.length). With bind(), and mostly with the function parameter i, you create a separate variable in the scope of the call back function, which gets its value defined at the moment you execute bind(). That i is independent from the original i, and retains the value you have given it via bind().
that is because your "i" variable is not captured in the function passed as an argument to setInverval.
Therefore , when this function is invoked, i is always equal to clusters.length.
consider the differences between the two following pieces of code:
var arr = [1, 2, 3];
var broken = function() {
for(var i = 0; i < arr.length; ++i) {
setInterval(function() {
console.log("broken: " + arr[i]);
}, 1000);
// logs broken: undefined
}
};
var fixed = function() {
for(var i = 0; i < arr.length; ++i) {
setInterval((function(k) {
return function() {
console.log("fixed: " + arr[k]);
}
}(i)), 1000); // i is captured here
}
};
I'm working on quite a unique project and have a variable that calls my function, is it possible to have it where after it makes 1 function call, it stops working and becomes useless.
var limiter = function (limit, cb) {
var counter = limit;
return function () {
if (counter > 0) {
cb(counter);
counter--;
}
};
};
var counting = limiter(3, function (data) {
console.log('This function can be used ' + data + ' more times.');
});
for (var i = 0; i < 5; i++) {
counting();
};
Calling limiter with the first paramater as 1 and the second parameter as the function definition will allow you to run the function only one time. In this example, I have created the function 'counting' that will log how many more calls it has until it is useless (It only run three times, despite the for loop calling it five times). The for loop at the bottom just shows that it works. You can also create multiple functions using limiter, without the counters overlapping, as they will each have their own unique scope.
Fiddle: http://jsfiddle.net/Kajdav/acLxxywt/
for (var i = 0; i < json.length; i++) {
$.Mustache.load('/mustaches.php', function(i) {
//Do Something
});
}
How do I pass the var i to the function in this case?
EDIT: Sorry I don't actually want to make the Mustache.load call too many times. Only once. How can I do that?
This is a little more complicated than you might think, as you must ensure you pass the right value of i, so that the callback doesn't use the value of end of loop.
for (var i = 0; i < json.length; i++) {
(function(i){
$.Mustache.load('/mustaches.php', function() {
// use i. Call a function if necessary
//Do Something
});
})(i);
}
About the callback term : it refers to a function you pass as argument so that the function you call can call it back.
To understand the code I wrote, you must
understand that the callback is called later, when the loop has finished and so when i in the loop has the value of end of loop
that the scope of a non global variable is the function call in which it is defined. That's why there's this intermediate function : to define another variable i which is called with the value of the loop
An elegant way to solve your question would be using the bind method.
for (var i = 0; i < json.length; i++) {
$.Mustache.load('/mustaches.php', function(i) {
//Do Something
}.bind(this, i));
}
the bind method returns a new function with a new context (in this case this) and applies one (or more) argument(s) to your function (i in this particular case). You can find more about bind and currying here.
EDIT. You can optimise your loop by loading the template only once. In fact, $.Mustache.load fetches /mustache.php on each cycle of the loop. Also, because the function asynchronously fetches the template with AJAX, you might get not consistent ordering in your template (one response may take longer than others). The fix is pretty straightforward: we load the template and then we iterate through the loop.
$.get('/mustache.php').done(function(template){
$.Mustache.add('my-template', template);
for (var i = 0, len = json.length; i < len; ++i) {
var rendered_template = $.Mustache.render('my-template', {
i: i,
...
});
}
});
Sorry if the question title is a little confusing, but I'm not sure exactly how to word my problem.
I have the following synchronous ajax call (the purpose of which is to get json file contents).
$.ajax({
url: "/JsonControl/Events.json",
dataType: 'json',
async: false,
success: function(jsonObj)
{
for (var i = 0; i < jsonObj.events.length; ++i)
{
if(day == jsonObj.events[i].dateNumber && (navDate.getMonth() + monthAdjust) == (jsonObj.events[i].dateMonth -1) && navDate.getFullYear() == jsonObj.events[i].dateYear)
{
document.getElementById("cGrid" + gridMod).className="eventDay";
document.getElementById("cGrid" + gridMod).onmousedown = function(){document.getElementById("eventBox").src="/Event htms/Event.htm"; document.getElementById("eventBox").document.getElementById("title").innerHTML = (jsonObj.events[i].title);}
document.getElementById("cGrid" + gridMod).style.backgroundColor = "#336633";
isAnEvent = true;
}
}
}
});
The problem I am having is with the following line (line 12, if '$.ajax({' is line 1):
document.getElementById("cGrid" + gridMod).onmousedown = function(){document.getElementById("eventBox").src="/Event htms/Event.htm"; document.getElementById("eventBox").document.getElementById("title").innerHTML = (jsonObj.events[i].title);}
I have been told that I need closure, but I can't make heads or tails of any examples I have seen, as I have never seen syntax set up in such a way, and examples I have tried don't work (I will give details on what "don't work" means further down).
This is what I attempted (I replaced the concerning line mentioned above with this one, shown below).
(function(index) {
document.getElementById("cGrid" + gridMod).onmousedown = function(){document.getElementById("eventBox").src="/Event htms/Event.htm"; document.getElementById("eventBox").document.getElementById("title").innerHTML = jsonObj.events[index].title;}
})(i);
The error I get is this:
Uncaught TypeError: Cannot call method 'getElementById' of undefined
I get this error with the original problem line, as well as, with the replacement line, just shown above.
I find this self-executing function stuff pretty new, so I'm not sure how to proceed.
Any advice on how to plot what the actual value of jsonObj.events[i].title at the time it is accessed instead of literally plotting 'jsonObj.events[i].title' would be greatly appreciated.
I also tried (guessing, as I was) the valueOf method, but quick research has shown that simply returns the value of a boolean value.
Combination of wrapping the in-loop code (as suggested by Justin) with moving to .contentDocument for <iframe> contents, along with a couple other changes.
See comments in code or ask below to understand what is happening. (I'm assuming #eventBox is an <iframe>)
function (jsonObj) {
for (var i = 0; i < jsonObj.events.length; ++i) (function (i) { // `i` inside function is protected from outside changes
var cGridMod; // holder to reduce lookups
if (day == jsonObj.events[i].dateNumber
&& (navDate.getMonth() + monthAdjust) == (jsonObj.events[i].dateMonth -1)
&& navDate.getFullYear() == jsonObj.events[i].dateYear) {
cGridMod = document.getElementById("cGrid" + gridMod);
cGridMod.className="eventDay";
cGridMod.onmousedown = function () {
var eventBox = document.getElementById("eventBox"); // holder to reduce lookups
eventBox.onload = function () { // set an onload listener so #document exists at time of execution
eventBox.onload = null; // unset it so it only fires once
eventBox.contentDocument.getElementById("title").innerHTML = (jsonObj.events[i].title);
// using `.contentDocument` to get <iframe> document
};
eventBox.src="/Event htms/Event.htm";
// now set location (done after so onload will fire when loaded)
};
cGridMod.style.backgroundColor = "#336633";
isAnEvent = true;
}
}(i)); // invoke the function for this iteration of the loop; `i`
}
The problem is because the mousedown event is always going to use the very last index in the for loop, you need to wrap the for loop using a function to make sure it's value is maintained properly.
for (var i = 0; i < jsonObj.events.length; ++i) (function(i){
....stuff here
})(i)
I have a code similar to this:
$.ajax({
success: function(data) {
text = '';
for (var i = 0; i< data.length; i++) {
text = text + '' + data[i].Name + "<br />";
}
$("#SomeId").html(text);
for (var i = 0; i< data.length; i++) {
$("#Data_"+i).click(function() {
alert(data[i]);
RunFunction(data[i]);
return false;
});
}
}
});
This gets an array of some data in json format, then iterates through this array generating a link for each entry. Now I want to add a function for each link that will run a function that does something with this data. The problem is that the data seems to be unavailable after the ajax success function is called (although I thought that they behave like closures). What is the best way to use the queried json data later on? (I think setting it as a global variable would do the job, but I want to avoid that, mainly because this ajax request might be called multiple times)
Thanks.
Your problem is that the i variable is shared by the callbacks.
Therefore, all of the callbacks run on the last item.
The simplest solution is to use $.each:
$.each(data, function(i) {
$("#Data_" + i).click(function() {
alert(data[i]);
RunFunction(data[i]);
return false;
});
});
This will make a separate function call for each iteration, so there will be a separate i variable (or, in this case, parameter) for each iteration.
You can use .bind() directly and passing the data:
for (var i = 0; i< data.length; i++) {
$("#Data_"+i).bind('click', {data: data[i]}, function() {
alert(event.data.data);
RunFunction(event.data.data);
return false;
});
}
I think you made a classical mistake, trying to generate functions in a loop. The variable i will have the same value for all functions but it is not even a valid array index anymore at the end of the loop.
See also JavaScript Closures for Dummies (no offense), example 5.
SLaks answer is a good one, but he failed to explain why it wasn't working.
The problem is due to scoping. Try this out:
var logger = function(x){
console.log(x);
};
for(var j = 0; j < 10; j++){
window.setTimeout(function(){
logger(j);
}, 1000);
}
That nice little function prints out nothing but...9s! That's because the reference to j is kept by the timeout, so by the time the timeout runs, j is already set to 9.
Contrast that with:
var logger = function(x){
console.log(x);
};
for(var j = 0; j < 10; j++){
// we're wrapping our call in an anonymous function
// don't do this in production code...make the function external instead
// so you don't create 10 functions
(function(k){
window.setTimeout(function(){
logger(k);
}, 1000);
})(j);
}
This version wraps the inner call in an anonymous function that takes as an argument the index. Since the scope of k is now limited to that function, the logger works as you'd expect.