Ask your WordPress questions! Pay money and get answers fast! Comodo Trusted Site Seal
Official PayPal Seal

javascript variable scope WordPress

  • SOLVED

In my test code, I want the value of an array to be used, but it's always empty. I assume it has something to do with variable scope. But I just can't get it to work.

Here is some code:

var places = [];

var addresses = ["123 Main Street", "789 Broad Street"];


for (var i = 0; i <= addresses.length -1; i++) {

getCoordinates(addresses[i]);

geocoder.geocode(geocoderRequest, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
places.push(results[0].geometry.location);
}

<strong>alert (places.length); // array length is shown in each loop, so it does work here</strong>
});

}
<strong>
alert (places.length); // ARRAY LENGTH = 0</strong>


I want to use the places array down here, but it's empty! (sorry the payment offer is low because I think this is a very basic solution)


Answers (5)

2011-09-08

John Cotton answers:

It's not scope, it's timing.

The alert at the end of the for loop gets executed immediately after the loop has exited.

The alert(s) within the loop has to wait for the callback to happen which, depending on network conditions, could be any time but almost certainly after the loop has exited.

So whilst it might work sometimes (for example on a slow PC with a very fast internet connection, it's unreliable.

If you need something to happen at the end of loop AND all values returned, then trigger an event or call a function within the in-line function (you can use i to determine if the loop has completed).


John Buchmann comments:

I think you may be onto something, but I don't understand what to do next.

But check this out... i think it will shed more light on the problem. The alert at the bottom of the code returns "0":



var places = [];

var addresses = ["233 Ferndale Lane, Downingtown, pa", "304 Heritage Place, Downingtown, PA"];


for (var i = 0; i <= addresses.length -1; i++) {

geocoder.geocode(geocoderRequest, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
places.push(results[0].geometry.location);
}
});
alert (places.length); //returns 0
}





But if I take out some of the Google Map code, then the alert at the bottom returns "2" (which is good)



var places = [];

var addresses = ["233 Ferndale Lane, Downingtown, pa", "304 Heritage Place, Downingtown, PA"];


for (var i = 0; i <= addresses.length -1; i++) {

//geocoder.geocode(geocoderRequest, function(results, status) {
//if (status == google.maps.GeocoderStatus.OK) {
places.push("some text");
//}
//});
alert (places.length); //returns 2
}



So do you know any way to make it work while keeping the Google Map code? (I'm going to change the reward to $15 since it's more complex than I thought)





John Cotton comments:

I'm definitely onto something....but then I have been doing this for nearly 20 years :)

It's all to do with asynchronous calls - or what you might call AJAX. Put simply, AJAX works in the background and doesn't prevent the foreground code from executing. That is mostly a good thing, but in your case, it's a bad thing!

In you last example above, you commented out the geocode request and - importantly - the function that gets call on a response from the Google server - that's why "it works".

As I said before, what you need to do is structure your code differently - to react to the final response from the server.

What is below is crude, but will work:


var places = [];
var addresses = ["233 Ferndale Lane, Downingtown, pa", "304 Heritage Place, Downingtown, PA"];

for (var i = 0; i <= addresses.length -1; i++) {
geocoder.geocode(geocoderRequest, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
places.push("some text");
}

if(i == (addresses.length - 1)) the_wait_is_over();
});
}

function the_wait_is_over() {
alert (places.length); // Now we're talking!
}


Clearly, you can play with the "if i" line to do other things if you wish.


John Buchmann comments:

I think we're close!... but the the_wait_is_over() function is never getting fired.


John Cotton comments:

Apologies, it might not be - for exactly the same reason! (It's late and I need to go to bed!!)

Try this:


var places = [];
var addresses = ["233 Ferndale Lane, Downingtown, pa", "304 Heritage Place, Downingtown, PA"];
var geocoder = new google.maps.Geocoder();
var response_count = 0;
for (var i = 0; i <= addresses.length -1; i++) {

geocoder.geocode({'address': addresses[i]}, function(results, status) {
response_count++;

if (status == google.maps.GeocoderStatus.OK) {
places.push("some text");
}

the_wait_is_over();
});

}

function the_wait_is_over() {
if( response_count == addresses.length ) {
// We're finished
alert ('the wait really is over! - '+places.length);
// Remember that places.length might not be the same as addresses if Google returns an error

}
}


John Buchmann comments:

That does seem to work! But this means that the array can only be used inside the "the_wait_is_over()" function, right? Is there no way to have the array accessible in other places?


John Cotton comments:

<blockquote>But this means that the array can only be used inside the "the_wait_is_over()" function, right?</blockquote>

No! It means you just have to wait for the geo-coding to finish.

You need to structure your code so either that it is more event driven, or you have tests before executing code. The former is much more desirable, you just have to think a little more.

Whatever you want to do once the geo-coding is done needs to go in a function (the_wait_is_over or another more specialised one) that gets called from the_wait_is_over. Simple!

The point is that your code cannot go on executing straight after the for loop because the geocoding hasn't finished. But the new function you have now knows when it is and so you code can react then.


John Buchmann comments:

Thanks John. I've awarded you the full prize money. :)

2011-09-08

Julio Potier answers:

Hello

First you wrote "addresses[0]" in place of "addresses[i]" !?

and try

places[] = results[0].geometry.location;

See you


John Buchmann comments:

Changing it to that line causes the Google Map to not show AND no alert showing the count of the array.

2011-09-08

Jurre Hanema answers:

Can you provide a more complete test case? Because in this code I see absolutely no reason why it shouldn't work.

2011-09-08

Josh Eaton answers:

Modify your code like this:


getCoordinates(addresses[i]);



geocoder.geocode(geocoderRequest, function(results, status) {

if (status == google.maps.GeocoderStatus.OK) {

places.push(results[0].geometry.location);

}



alert (places.length); // array length is shown in each loop, so it does work here

});


John Buchmann comments:

That won't make a difference because it has to be results[0], not results[i]. I need the first result in the results array in each loop.


Josh Eaton comments:

Yes, I had edited my answer to change that back.

Did you try changing the line:


getCoordinates(addresses[i]);


After you make that change, add this above your places.push line:


alert(results[0].geometry.location);


Make sure that your results array actually has something in it to add to the places array. If nothing is in the results array, then your places array will be empty.


John Buchmann comments:

Josh, the array does get populated. But the contents seem to go empty once I access it later at the bottom of the code.

2011-09-08

Reland Pigte answers:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
var geocoder = new google.maps.Geocoder();
var places = [];
var addresses = ["123 Main Street", "789 Broad Street"];
for (var i = 0; i < addresses.length; i++) {
//getCoordinates(addresses[i]);
geocoder.geocode( { 'address': addresses[i]}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
places.push(results[0].geometry.location);
}
alert (places.length); // array length is shown in each loop, so it does work here
});
}
function test(){
alert (places.length + ' - length'); // ARRAY LENGTH = 0
}
</script>
</head>
<body>
<div>
<input id="address" type="textbox" value="Sydney, NSW">
<input type="button" value="Geocode" onclick="test()">
</div>
</body>
</html>


if you run that locally, you can see the length of the places by hitting the button geocode

what your code did above is to alert the length of the places but the for loop is not process yet so the value would be zero because no data was pushed.