whenever i try to run something like the following, firebug tells me that "markers is undefined" at the line "for (var i=0 ..."
but i declared markers as a global variable at the top right...?
var markers;
function load() {
$.get("phpsqlajax_genxml.php", function(data) {
markers = data.documentElement.getElementsByTagName("marker");
});
for (var i = 0; i < markers.length; i++) {
var name = markers[i].getAttribute("name")
//do more stuff
}
}
but when i do this, it works.
var markers;
function load() {
$.get("phpsqlajax_genxml.php", function(data) {
markers = data.documentElement.getElementsByTagName("marker");
makeMarkersWithXMLinfo();
});
function makeMarkersWithXMLinfo() {
for (var i = 0; i < markers.length; i++) {
var name = markers[i].getAttribute("name")
//do more stuff
}
}
}
i'm not even passing "markers" as an argument to my makeMarkersWithXMLinfo() function. but yet it works. what's going on? thnx
The problem you're having is that get starts an asynchronous operation. So your code immediately following the call to get happens before the success callback on the get is run. E.g. (see the comments):
var markers;
function load() {
// ===> This happens FIRST
$.get("phpsqlajax_genxml.php", function(data) {
// ===> This happens THIRD, some time after `load` returns
markers = data.documentElement.getElementsByTagName("marker");
});
// ===> This happens SECOND
for (var i = 0; i < markers.length; i++) {
var name = markers[i].getAttribute("name")
//do more stuff
}
}
Your second example is the correct way to code it (although I'd recommend avoiding a global entirely), because you're using markers only after the GET has completed.
$.get is asynchronous, that means that if you call something immediatly after $.get, it's callback function wouldn't be invoked yet, and your global would still be undefined.
Related
I'm trying to use the following code in Javascript. I'd like to pass a function rulefunc() a number of times into the onChange() function iteratively. I want to be able to access i from within the function when it is called. How can I do this?
var gui = new DAT.GUI();
for for (var i=0; i<5; i++) {
// want to associate ruleFunc with i
gui.add(lsys, 'grammarString').onChange(ruleFunc);
}
function ruleFunc(newVal) {
...
// access i here
}
At the event side:
Here since for loop is synchronous an IIFE is used so that right value of i is passed
IIFE and the onchange event makes a closure which makes the right value of i to be passed
at the argument
At the event callback side
Closure is used so that the function that is returned can access the value of the argument
var gui = new DAT.GUI();
for (var i=0; i<5; i++) {
// want to associate ruleFunc with i
(function(a){ //making an IIFE to make sure right value of i is passed to the function
f1.add(lsys, 'grammarString').onChange(ruleFunc(a));
})(i);
}
function ruleFunc(newVal) {
return function(){
//making a closure which will have access to the argument passed to the outer function
console.log(newVal);
}
}
You can write a function that returns a function like
function ruleFunc(i) {
return function(newVal){
// ... here use your newVal and i
}
}
And use like
f1.add(lsys, 'grammarString').onChange(ruleFunc(i));
You call the function that gets the i from the outer scope and then the returned function gets the newVal of the 'onChange' event. Note that I am calling the function ruleFunc and pass a parameter i. Inside the function you can now use your i variable and newVal.
Example of how it works. Here I add functions to the array with approprite i values. After that when I execute each function, it properly knows what i was used when it have been created. It is called closure.
var functions = [];
function ruleFunc(newVal) {
return function (){
console.log(newVal);
};
}
for(var i = 0; i < 5; i++) {
functions.push(ruleFunc(i));
}
for(var i = 0; i < functions.length; i++) {
functions[i]();
}
I am running into strange issue. This is regarding google Place search of type "textsearch". I am searching map for the results with a keyword and have a callback function to create result ("li" in html).
The problem is that google Place search api only gives 20 results for text search. To retrieve more results, we have to call pagination.nextPage(). This calls the same callBack function and gives more result.
So,
My code is
var request = {query: 'pizza in newyork'};
var service = new google.maps.places.PlacesService(map);
service.textSearch(request, callBack1);
function callBack1(results, status,pagination) {
for (var i = 0; i < results.length; i++) {
var place = results[i];
//add place as li
}
if (pagination.hasNextPage) {
pagination.nextPage();
}
doOtherOperation();
}
function doOtherOperation() {
//do manipulations on "li" which are created from callBack1
}
The problem is that doOtherOperation() starts executing before callBack1() completes execution.
Anybody can help? how to make sure that callBack1 will executed fully (including recursive calls by pagination.nextPage())?
Not an expert, but looks like a small logic flaw, so I will do it this way:
if (pagination.hasNextPage) {
pagination.nextPage();
}
else
{
doOtherOperation();
}
Otherwise doOtherOperation(); gets called each time regardless of the fact that you have to go trough nextPage again.
Hope it helps
try to wrap doOtherOperation() with setTimeout(function() { /* */}, 0)
UPDATE... how about that?
var flag = false;
function callBack1(results, status,pagination) {
/* since callback1 is the same callback for textSearch() and nextPage() -
you need to call dootherOperation at the beginning of statement */
if (flag) {
doOtherOperation();
}
for (var i = 0; i < results.length; i++) {
var place = results[i];
//add place as li
}
if (pagination.hasNextPage) {
flag = true;
pagination.nextPage();
}
else {
flag = false;
}
}
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,
...
});
}
});
I have the following code that adds an onmouseover event to a bullet onload
for (var i = 0; i <= 3; i++) {
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
}
This is the function that it is calling. It is supposed to add a css class to the menu item as the mouse goes over it.
function addBarOnHover(node) {
document.getElementById('menu').getElementsByTagName('li')[node].className = "current_page_item"; }
When the function is called, I keep getting the error:
"document.getElementById("menu").getElementsByTagName("li")[node] is
undefined"
The thing that is stumping me is I added an alert(node) statement to the addBarOnHover function to see what the value of the parameter was. The alert said the value of the parameter being passed was 4. I'm not sure how this could happen with the loop I have set up.
Any help would be much appreciated.
This is a common problem when you close over an iteration variable. Wrap the for body in an extra method to capture the value of the iteration variable:
for (var i = 0; i <= 3; i++) {
(function(i){ //here
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
})(i); //here
}
an anonymous function is created each time the loop is entered, and it is passed the current value of the iteration variable. i inside the anonymous function refers to the argument of this function, rather than the i in the outer scope.
You could also rename the inner variable for clarity:
for(var i=0; i<=3; i++){
(function(ii){
//use ii as i
})(i)
}
Without capturing the iteration variable, the value of i when it is finally used in the anonymous handler has been already changed to 4. There's only one i in the outer scope, shared between all instances of the handler. If you capture the value by an anonymous function, then the argument to that function is used instead.
i is being passed as a reference (not by value), so once the onmouseover callback is called, the value of i has already become 4.
You'll have to create your callback function using another function:
var menu = document.getElementById('menu');
var items = menu.getElementsByTagName('li');
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = (function(i) {
return function() {
addBarOnHover(i);
};
})(i);
}
You could make it a little more readable by making a helper function:
var createCallback = function(i) {
return function() {
addBarOnHover(i);
};
};
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = createCallback(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.