views:

123

answers:

2

I'm using the Google Maps API to plot several points on a map. However, in the click event function below, i is always set to 4, i.e. its value after iterating the loop:

// note these are actual addresses in the real page
var addresses = new Array( "addr 1", "addr 2", "addr 3", "addr 4" );

for (var i = 0; i < addresses.length; i++) {
    geocoder.getLatLng(addresses[i], function(point) {
        if (point) {
            var marker = new GMarker(point);
            map.addOverlay(marker);
            map.setCenter(point, 13);

            GEvent.addListener(marker, "click", function() {
                // here, i=4
                marker.openInfoWindowHtml("Address: <b>" + addresses[i] + "</b>");
            });
        }
    });
}

So when the marker displays it's using addresses[4] which is undefined. How do I pass the correct value of i to the function?

A: 

i should not reach 4 in that loop. The loop runs so long as i < addresses.length (which is 4), so the loop should run from i=0 to i=3.

darren
`i` would have to increase to 4 so that it can fail the `i < addresses.length` test.
Andy E
thanks, yeah I noticed that after i posted this. Out of votes to close for today. Admins, feel free to delete this answer.
darren
No, `i` will reach 4. Try it. The loop stops when `i` reaches 4 but `i` does reach 4. And since `i` is captured by a closure, it doesn't matter if the loop body is not evaluated after `i` have reached 4, `i` would still be assigned the value of 4.
slebetman
*Inside* the loop, i runs from 0 to 3, but when the loop exits Javascript will have incremented it to 4. Because geocoding and marker clicking are asynchronous, they actually occur after the loop has completed, even though the code might lok like it's inside the loop.
Mike Williams
+1  A: 

You need to generate an anonymous function during the current iteration, the following should fix it:

// note these are actual addresses in the real page 
var addresses = new Array( "addr 1", "addr 2", "addr 3", "addr 4" ); 

for (var i = 0; i < addresses.length; i++) { 
    geocoder.getLatLng(addresses[i], function (current) { 
        return function(point) { 
            if (point) { 
                var marker = new GMarker(point); 
                map.addOverlay(marker); 
                map.setCenter(point, 13); 

                GEvent.addListener(marker, "click", function() { 
                    // here, i=4 
                    marker.openInfoWindowHtml("Address: <b>" + addresses[current] + "</b>"); 
                }); 
            }
        } 
    }(i)); 
} 

B// note these are actual addresses in the real page var addresses = new Array( "addr 1", "addr 2", "addr 3", "addr 4" );

for (var i = 0; i < addresses.length; i++) { geocoder.getLatLng(addresses[i], function(point) { if (point) { var marker = new GMarker(point); map.addOverlay(marker); map.setCenter(point, 13);

        GEvent.addListener(marker, "click", function() { 
            // here, i=4 
            marker.openInfoWindowHtml("Address: <b>" + addresses[i] + "</b>"); 
        }); 
    } 
}); 

}

Further Clarification
The getLatLng method provided by Google uses an ajax call to get the lat and long for a specific address. Since this is an asynchronous call and is not part of the current thread, a callback function is required which is called on completion of the ajax request. This is the anonymous function you specify as the second parameter of the function.
Now, whilst the ajax requests are being made, your code continues to run, increasing the value of i each time the loop iterates over the array. By the time your first ajax call returns, the loop has already increased to the length of the addresses array (4), so when the your callback function runs you're retrieving the in-scope variable i after it has been increased by the loop.

With the fix I wrote, you're creating an anonymous function that takes a single argument - current - and returns the previous anonymous function with the i variable replaced with the current variable. This function is invoked right away, before the next iteration of the loop, with the i variable as it's first parameter. This creates a closure for which the current value of i is stored in the current variable at the time the function is called. When we refer to the current variable later, we're getting the stored value of i.

I'm not really very good at explaining these sort of things, probably because my understanding of it isn't quite as good as the js Gods. Better to read some more info on javascript closures.

Andy E
Thanks, this worked great! But I don't completely understand how it works. How does the Google Maps callback function on `getLatLng` now pass `i` instead of the point it did originally?
DisgruntledGoat
I've tried to explain it as best as I can (I'm not very good at explaining closures lol), I hope it helps you to understand it. I also posted a link to the best site I could find from a search for javascript closures.
Andy E