1

In an effort to create infowindows for markers inside of a Google Map, I have used an array to create "disposable objects" inside of my for loop. However, my method does not seem to be working. Clicking on the markers does nothing, and when I check the console, I am getting this error message:

Uncaught TypeError: Cannot call method 'open' of undefined 

When I don't assign the object to an array index, clicking on any of the created markers only opens the last info window (meaning that when the object is overwritten, it updates all of the references to the previous objects).

How can I circumvent this?

markers = []
infowindows = []
counter = 0
for location in exports.response.locations
    myLatlng = new google.maps.LatLng(location.latitude, location.longitude);
    markers[counter] = new google.maps.Marker(
        position: myLatlng
        map: map
        title: location.name
    )
    contentString = '<div id="info_content_' + location.id + '">' + '<h3>' + location.name + '</h3>' + '<ul>' + '<li>' + location.address + ', ' + location.city + '</li>' + '</ul>'
    infowindows[counter] = new google.maps.InfoWindow(content: contentString)

    google.maps.event.addListener markers[counter], "click", ->
        infowindows[counter].open(map, markers[counter])

    counter++

note

the problem area is the 3rd to last line in the code above. (infowindows[counter].open(map, markers[counter]))

answer

Virtually every reply to this question helped me find a fix, but - for the record (and anyone readying this later) I solved it with a foreach:

markers = []
infowindows = []
exports.response.locations.forEach (location) ->
    myLatlng = new google.maps.LatLng(location.latitude, location.longitude);
    markers[location.id] = new google.maps.Marker(
        position: myLatlng
        map: map
        title: location.name
    )
    contentString = '<div id="info_content_' + location.id + '">' + '<h3>' + location.name + '</h3>' + '<ul>' + '<li>' + location.address + ', ' + location.city + '</li>' + '</ul>'
    infowindows[location.id] = new google.maps.InfoWindow(content: contentString)

    google.maps.event.addListener markers[location.id], "click", ->
        infowindows[location.id].open(map, markers[location.id])
drewwyatt
  • 5,989
  • 15
  • 60
  • 106
  • 3
    Hint: what value do you think _counter_ has when the event handler runs? – nnnnnn Oct 08 '13 at 03:58
  • possible duplicate of [Assign click handlers in for loop](http://stackoverflow.com/questions/4091765/assign-click-handlers-in-for-loop) – Thilo Oct 08 '13 at 04:00
  • possible duplicate of [Closure Scope not captured? — Coffeescript](http://stackoverflow.com/questions/11996218/closure-scope-not-captured-coffeescript) – mu is too short Oct 08 '13 at 04:44

2 Answers2

2

I think your problem is that the counter will not have a valid index anymore:

for location in exports.response.locations

    google.maps.event.addListener markers[counter], "click", ->
        infowindows[counter].open(map, markers[counter])

    counter++

counter is captured in the onClick handler closure.

It will be incremented beyond bounds before the onClick handler runs.

All of those handlers will end up using the same value for counter.

Thilo
  • 257,207
  • 101
  • 511
  • 656
  • You could wrap it into another closure, passing the counter in as a parameter. See the linked (Javascript) question. – Thilo Oct 08 '13 at 04:11
  • Another option is to make use of the `this` and parameters that will be available in the callback to "figure out where you are". – Thilo Oct 08 '13 at 04:15
  • 2
    @anwyatt: You probably want to use `do` in the loop, see this mostly-duplicate for a similar situation in CoffeeScript: http://stackoverflow.com/questions/11996218/closure-scope-not-captured-coffeescript – mu is too short Oct 08 '13 at 04:47
  • @muistooshort Your comment helped me find the fix. – drewwyatt Oct 08 '13 at 04:57
1

addListener triggers code asynchronously. Because of that, the value of counter is not the same when the listener's callback function is called than when the function was declared. To freeze the variables to the values they had when you declared the function, you have to have them in a closure. Full proof option below. Declaring currentCounter within the loop should be enough but it might help to make the use of closure explicit.

markers = []
infowindows = []
counter = 0
for location in exports.response.locations
    myLatlng = new google.maps.LatLng(location.latitude, location.longitude);
    markers[counter] = new google.maps.Marker(
        position: myLatlng
        map: map
        title: location.name
    )
    contentString = '<div id="info_content_' + location.id + '">' + '<h3>' + location.name + '</h3>' + '<ul>' + '<li>' + location.address + ', ' + location.city + '</li>' + '</ul>'

    infowindows[counter] = new google.maps.InfoWindow(content: contentString)
    google.maps.event.addListener markers[counter], "click", (function(infowindows, markers, currentcounter) { 
        return function() {  infowindows[currentCounter].open(map, markers[currentCounter]) }
    })(infowindows, markers, counter)
    counter++    

See JavaScript closure inside loops – simple practical example for more practical examples

Community
  • 1
  • 1
widged
  • 2,749
  • 21
  • 25