views:

85

answers:

5

I have this code inside the success function of a jQuery ajax call success: function(d)

for (var n in d.items)
{        
    google.maps.event.addListener(markers[d.items[n].id], 'mouseover', function() {
        focusMarker(d.items[n].id);
    });
}

Unfortunately, the function always evaluated d.items[n].id as the last item in d.items collection.

I tried making this modification:

for (var n in d.items)
{        
    var id = d.items[n].id;        
    google.maps.event.addListener(markers[d.items[n].id], 'mouseover', function() {
        focusMarker(id);
    });
}

but my function always returned the same thing.

Is this a scope problem, or is there something wrong with my function definition?

+5  A: 

There are several ways to solve this problem, the most common is to use a function to preserve the looping values:

for (var n in d.items) {
  (function(id) {
    google.maps.event.addListener(markers[id], 'mouseover', function() {
        focusMarker(id);
    });
  })(d.items[n].id);
}

By the way, if d.items is an array, I would recommend you to use a sequential loop e.g.:

for (var n = 0; n < d.items.length; n++) {
  //..
}

The for-in statement is meant to be used to enumerate over object properties.

If you use it on arrays or array like objects, it can give you several problems, first, inherited properties are also enumerated, meaning that if someone augments the Array.prototype object, those properties will be also enumerated in your loop.

Also the order of iteration is not guaranteed by the specification, the properties may not be visited in the numeric order.

CMS
Thanks, that worked perfectly!
Zahymaka
+1  A: 

You can make a closure:

for (var n in d.items)
{        
    (function(id) {        
      google.maps.event.addListener(markers[d.items[n].id], 'mouseover', function() {
          focusMarker(id);
      });
    })(d.items[n].id)
}
meder
+1  A: 

This is a scope problem. What you want to do is use a closure like this:

for (var n in d.items)
{        
    (function(id){
        google.maps.event.addListener(markers[d.items[n].id], 'mouseover', function() {
            focusMarker(id);
        });
    })(d.items[n].id);
}
Darko Z
+1  A: 

Yes, it's a scope problem, and a very common one.

Variables enclosed in a closure share the same single environment, so by the time the mouseover callback is called, the for in loop will have run its course, and the n variable will be left pointing to the last value it was assigned.

You can solve this problem with even more closures, using a function factory:

function makeOnHoverHandler(id) {  
  return function () {  
    focusMarker(id);
  };  
}

// ...

for (var n in d.items) {        
  google.maps.event.addListener(markers[d.items[n].id], 
                                'mouseover', 
                                makeOnHoverHandler(d.items[n].id));
}

This can be quite a tricky topic, if you are not familiar with how closures work. You may want to check out the following Mozilla article for a brief introduction:

You could also inline the above. This is actually a more common approach, but is practically the same as the above:

for (var n in d.items) {        
  google.maps.event.addListener(markers[d.items[n].id], 'mouseover', (function (id) {
    focusMarker(id);
  })(d.items[n].id));
}

Any yet another solution is to enclose each iteration in its own scope, by using self invoking anonymous functions:

for (var n in d.items) {
  (function (id) {
    google.maps.event.addListener(markers[d.items[n].id], 'mouseover', function () { 
      focusMarker(id);
    });
  })(d.items[n].id);
}
Daniel Vassallo
+1  A: 

Similar to other responses, but I think defining things inline is neater, more concise style:

for (var n in d.items)
{        
    google.maps.event.addListener(markers[d.items[n].id], 'mouseover', new function() {
        return function() {
            focusMarker(d.items[n].id);
        }
    });
}

You might wonder what new function() does. That declares and executes a function all at once. It's basically shorthand for:

for (var n in d.items)
{        
    google.maps.event.addListener(markers[d.items[n].id], 'mouseover', function() {
        return function() {
            focusMarker(d.items[n].id);
        }
    }());
}

Although I think that second example may not work, because if you're not assigning the result to a variable you need extra parenthesis. For example:

function() { alert("hi"); }();

Is a syntax error. Has to be:

(function() { alert("hi"); })();

Does inline as a function argument count as assignment? I don't know. Better stick with my first example.

darkporter