I am using a getJSON method to post the data I have in a database, through a for loop and into an HTML page. But I would like to the function to call different tables in my database depending on the integer the for loop is currently on, something like this:
for (var r = 0; r < 8; r++){
$.getJSON("PHP-PAGE.php?jsoncallback=?", function(table+r) {
//function stuff here
});
}
But when I try to do this, the "table+r" is flagging a syntax error. What am I doing wrong?
You are defining a function, not calling it. Between ( and ) you have to put identifiers (variable names) not expressions.
To pass data here, you need to use variables from a wider scope than the function. Since the variable is going to change (and the function is called asynchronously) you have to use a closure to do this.
function mkCallback(table) {
var foo = "table" + table;
return function () {
// function stuff that uses foo here
// foo from the time mkCallback was called to make this function
// will still be in scope
};
}
for (var r = 0; r < 8; r++){
$.getJSON("PHP-PAGE.php?jsoncallback=?", mkCallback(table+r));
}
function(table+r) { tries to create a function with table+r as a parameter, but + is not valid in a variable name. I think you instead want something like this:
for (var r = 0; r < 8; r++){
$.getJSON("PHP-PAGE.php?jsoncallback=?",
(function(currentR){
return function() {
var someVariable=table+currentR; // No idea where table came from...
//function stuff here
}
})(r));
}
As #Quentin mentioned by the time the callback is called, r will have reached its final value, hence the interesting closure.
I think what you probably want is
for (var r = 0; r < 8; r++){ //outer loop
function(tablenum){ //closure function
tablename = table+tablenum // saved reference to "table+r"
$.getJSON("PHP-PAGE.php?jsoncallback=?", function() {
//function stuff here, using tablename as the param
});
}(r)
}
This creates a closure to maintain the value of the iterated value. You can reference tablename in the callback function, and that will refer to a value equivalent to table+r
The issues with your original example
You were putting table+r as a parameter to a function you were defining, rather than an argument to one you were calling
You were trying to get the callback to reference r. But the callback won't run until after the loop has executed, so r will be 8 for all callback functions.
If you were trying to reference "table1", "table2" then you want to have "table"+r. Otherwise I assume you're referencing a table variable outside the scope of the code you showed us.
You can directly reference the variable r in your callback. Not sure what table is - the return data from the JSON call? Try the following:
for (var r = 0; r < 8; r++){
$.getJSON("PHP-PAGE.php?jsoncallback=?", function(jsonReturnData) {
//function stuff here
alert(r);
});
}
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
}
};
In the function "Encaisser", the value of "i" is OK in the for, but if i call 'i' in a function in my function, "i" return "Undefined.
function Encaisser()
{
for(var i=1; i <= Nombre_ligne_en_caisse; i++)
{
db.transaction(function(t,i){ t.executeSql('SELECT En_cour FROM Sequence WHERE Nom="Ticket_ID"', [], function(tx,rs,i){
var row = rs.rows.item(0);
var Tick_ID = row['En_Cour'];
var Noma = window['Produit_en_caisse_' + i] ;
alert(i); //Undefined
alert(Noma); //Undefined
}, [])});
alert(i); //If i put the alert here, its OK
}
}
Do you know why?
Thank You,
The problem is that your inner function defines a parameter named i here:
db.transaction(function(t,i){ ...
If you intend for i to be the value from the outer function, I recommend you simply remove this parameter. It doesn't appear that db.transaction is actually providing a value for this parameter anyway. You'll probably also want to close the value of i at each iteration in a separate variable, and use that inside your function, like this:
var index = i;
db.transaction(function(t){ ...
var Noma = window['Produit_en_caisse_' + index ];
alert(index);
You redefine i inside both your db.transaction callback and your t.executeSql callback. Inside your t.executeSql callback, i must be undefined.
If you want to access the value of i from the for loop, you'll need to rename those parameters in your callbacks.
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,
...
});
}
});
My code:
for (var i = 0; i < mapInfos.length; i++) {
var x = function () { doStuff(i); };
google.maps.event.addListenerOnce(mapInfos[i].map, 'tilesloaded', x);
}
The doStuff method simply alerts the value of i. mapInfos has two entries, so you'd expect it to alert 0 and 1, but instead it alerts 2 and 2. I can appreciate vaguely why it is doing this (although var i should keep it local to the scope of the loop?) but how can I make it work as intended?
edit — note that when first posted, the original question included a link to a jsfiddle that seemed to be a relevant example of what the current question is trying to achieve, only it appears to work ...
The code in the jsfiddle works because there's only one "i" in that code. The "i" used in the second loop (where the functions are actually called) is the same "i" as used in the first loop. Thus, you get the right answer because that second loop is running "i" through all the values from zero through four again. If you added:
i = 100;
functions[0]();
you'd get 100 printed out.
The only way to introduce a new scope in JavaScript is a function. One approach is to write a separate "function maker" function:
function makeCallback(param) {
return function() {
doStuff(param);
};
}
Then in your loop:
for (var i = 0; i < mapInfos.length; i++) {
var x = makeCallback(i);
google.maps.event.addListenerOnce(mapInfos[i].map, 'titlesloaded', x);
}
That'll work because the call to the "makeCallback" function isolates a copy of the value of "i" into a new, unique instance of "param" in the closure returned.
Create a new scope for it.
Functions create scope.
function doStuffFactory(i) {
return function () { doStuff(i); };
}
for (var i = 0; i < mapInfos.length; i++) {
var x = doStuffFactory(i);
google.maps.event.addListenerOnce(mapInfos[i].map, 'tilesloaded', x);
}
Change it to
var x = function (param) { doStuff(param); };
Obviously what is going on is that you are alerting a variable that is changing. With the above change it copies it so even if i changes it will still alert the right value.
Javascript doesn't have block scope, so you don't get an x that's local to the loop. Yea!
It has function scope, though.
Yep, weird isn't it!Pointy has an explanation
I have no idea why your first example worked (I wasn't expecting it to) Pointy has an explanation of why your first example worked - The reason why your second one doesn't is because i is scoped to the function containing the for loop, not to the scope defined by the for loop. In fact the only things that have scope in JavaScript are functions. This means that by the time your function gets executed i is 2.
What you need to do is create a scope, for example:
for (var i = 0; i < mapInfos.length; i++) {
var x = (function() {
return function () { doStuff(i); };
})(i);
google.maps.event.addListenerOnce(mapInfos[i].map, 'tilesloaded', x);
}
See JavaScript closures in for-loops for more.
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.