Adding an EventListener to a Google Maps Marker can be a little trickier than it seems at first glance. This is because of variable scope and closures in JavaScript. The below example is a simple map that receives four locations from a JavaScript array. I add each location with JavaScript as well as it’s corresponding content and InfoWindow. When you hover over the map marker, the InfoWindow shows above the marker with some helpful information about the location.
The Code: Create Google Maps Markers with EventListeners
This time, I’ll start with fully functioning code and then explain why it’s working. This is a simple example so all the HTML/CSS/JS is in one file. You can copy this into an HTML file and open it your browser. Don’t forget to replace “YOUR_API_KEY” with your Google Maps API key.
<!DOCTYPE html>
<html>
<head>
<title>Simple Map</title>
<meta name="viewport" content="initial-scale=1.0">
<meta charset="utf-8">
<style>
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
function initMap() {
console.log("initializing map...");
// Array that stores the locations and the content about each location
var locations = [
{lat: 36.15181, lng: -95.9612437, content: "<p>Tulsa is the best city.</p>"},
{lat: 39.129770, lng: -94.619938, content: "<p>Kansas City is the best city.</p>"},
{lat: 39.73733, lng: -104.9610077, content: "<p>Denver is the best city.</p>"},
{lat: 30.292798, lng: -97.6979297, content: "<p>Austin is the best city.</p>"},
]
// Use the first location as the center
var mapOptions = {
center: new google.maps.LatLng(locations[0]['lat'],locations[0]['lng']),
zoom: 5,
zoomControl: true,
}
var mapElement = document.getElementById('map');
var map = new google.maps.Map(mapElement, mapOptions);
for (i = 0; i < locations.length; i++) {
console.log("Adding marker at lat="+ locations[i]['lat'] + ", long=" + locations[i]['lng']);
var marker = new google.maps.Marker({
position: new google.maps.LatLng(locations[i]['lat'], locations[i]['lng']),
map: map,
});
var infowindow = new google.maps.InfoWindow({
content: locations[i]['content']
});
// Now we are inside the closure or scope of this for loop,
// but we're calling a function that was defined in the global scope.
addMarkerListener(marker, infowindow);
}
}
// We have to define this function outside the scope of the above for loop,
// so we define is here in the global scope. If your application is large,
// you probably don't want to put this in the global scope - to preserve
// the global namespace.
function addMarkerListener(marker, infowindow) {
marker.addListener('mouseover', function(e) {
infowindow.open(map,marker);
});
marker.addListener('mouseout', function() {
infowindow.close();
});
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
async defer></script>
</body>
</html>
How It Works
I got most of this code from the Google Maps documentation on
getting started,
markers, and
InfoWindows. The trick here is to put it together and apply it to multiple markers on a map whose InfowWindows rely on an EventListener. The most logical way to add multiple locations to the map it to use a loop. When you use a loop, you need to reference each location with a number variable. In this example, “i” is the variable used to access each index of the locations array.
The Mistake That’s Easy to Make
On your first pass at writing this code, you might want to skip creating a separate function for adding the InfoWindow and mouseover EventListener to each marker. That would look something like this:
/*...*/
var mapElement = document.getElementById('map');
var map = new google.maps.Map(mapElement, mapOptions);
for (i = 0; i < locations.length; i++) {
console.log("Adding marker at lat="+ locations[0]['lat'] + ", long=" + locations[0]['lng']);
var marker = new google.maps.Marker({
position: new google.maps.LatLng(locations[i]['lat'], locations[i]['lng']),
map: map,
});
var infowindow = new google.maps.InfoWindow({
content: locations[i]['content']
});
marker.addListener('mouseover', function(e) {
infowindow.open(map,marker);
});
marker.addListener('mouseout', function() {
infowindow.close();
});
}
/*...*/
If you try this, you can see that it’s as if you hovered over the last marker that was added when you mouseover each marker. Each marker’s mouseover event triggers the InfoWindow of the last marker added to the map. Why? This happens because the “marker” variable we create each iteration of the for loop overrides the previous value.
To fix this, we need to declare a function outside the scope of the for loop that adds the Google Maps Markers (this is what we did in the initial example). When we call the addMarkerListener function from inside the for loop, we pass in the marker object and the InfoWindow as parameters. Since JavaScript function parameters are
passed by value, we get the actual value of the marker object and the InfoWindow object instead of references to where they’re stored in memory. In hindsight, it’s obvious. However, not knowing this simple fact can lead to some serious frustration.
Conclusion
I hope this not only helps you fix your Google Maps problem but also gives you a better understanding of JavaScript scope and closures.