Recently
In my book I have in fact described how to add multiple markers from JSON and how to attach unique InfoWindows to each of them. What I haven’t described is how to get the JSON-data to actually show up in the InfoWindows. This tutorial aims to describe how to do just that.
The JSON
In this example I’m going to use the capitals of the Scandinavian countries: Stockholm, Oslo and Copenhagen. Each capital will be displayed on the map with a marker. When you click a marker an InfoWindow will popup with some brief information about the city.
The JSON data looks like this:
var json = [
{
"title": "Stockholm",
"lat": 59.3,
"lng": 18.1,
"description": "Stockholm is the capital and the largest city of Sweden and constitutes the most populated urban area in Scandinavia with a population of 2.1 million in the metropolitan area (2010)"
},
{
"title": "Oslo",
"lat": 59.9,
"lng": 10.8,
"description": "Oslo is a municipality, and the capital and most populous city of Norway with a metropolitan population of 1,442,318 (as of 2010)."
},
{
"title": "Copenhagen",
"lat": 55.7,
"lng": 12.6,
"description": "Copenhagen is the capital of Denmark and its most populous city, with a metropolitan population of 1,931,467 (as of 1 January 2012)."
}
]
Note that the JSON data above contains the following information:
- A Title in plain text
We will use this information for a tooltip on the marker - A Latitude
For the latitude portion of the position - A Longitude
For the longitude portion of the position - A Description
The content that will be displayed in the InfoWindows. I’ve included some HTML in it to illustrate that you’re not restricted to just plain text.
Note: In this example I have the JSON data in the same js file as the code, but in a real-life scenario you would probably grab it from an external source such as a back-end server or a web service.
Creating the map
First of all we need to create a map. The map will be centered over Scandinavia so that both Stockholm, Oslo and Copenhagen are visible.
// Creating a new map
var map = new google.maps.Map(document.getElementById("map"), {
center: new google.maps.LatLng(57.9, 14.6),
zoom: 6,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
If you feel unsure how this all works I recommend that you read Google Maps API 3 β The basics. It describes how to create a simple map.
Adding Markers
To create markers from the JSON-data we need to loop through it and extract the information for each marker.
for (var i = 0, length = json.length; i < length; i++) {
var data = json[i],
latLng = new google.maps.LatLng(data.lat, data.lng);
// Creating a marker and putting it on the map
var marker = new google.maps.Marker({
position: latLng,
map: map,
title: data.title
});
}
This will add markers to the map and they will all have their own unique title which will show up as a tooltip when you hover over them with the mouse.
Adding InfoWindows
Ok, so now we've created a map and added some markers from JSON data. So far it hasn't been that hard, the way of doing this is pretty straight-forward. But to attach the InfoWindows properly is a different story. It's now that it starts to get tricky.
First of all We're going to create a global InfoWindow
that we will reuse for all markers. This needs to be created outside of the loop. So just above the loop in the code we insert:
var infoWindow = new google.maps.InfoWindow();
This gives us an empty InfoWindow object that we can use on the map.
The next step is to attach a click event to each marker. In the code that executes we will fill the InfoWindow
with the correct information and open it att the right location, pointing at the clicked marker.
At first glance it seems that you can do this the same way that you add markers:
// Attaching a click event to the current marker
google.maps.event.addListener(marker, "click", function(e) {
infoWindow.setContent(data.description);
infoWindow.open(map, marker);
});
The problem with this approach is that when you're trying to add more than one InfoWindow
, they will all have the same content as the one in the last iteration. To prevent this from happening we need to use something called a closure.
Closures
What a closure does is to persist the data in each iteration of the loop so that it's bound to that particular marker.
One way of doing this is by encapsulating the code inside an anonymous function that is instantly executed. You need to make sure to pass the information from the current iteration to the anonymous function. In our case we're going to pass the current marker and the current data.
This is kind of a confusing concept and a bit hard to explain, so lets instead look at some code.
Implementing the closure
// Creating a closure to retain the correct data
//Note how I pass the current data in the loop into the closure (marker, data)
(function(marker, data) {
// Attaching a click event to the current marker
google.maps.event.addListener(marker, "click", function(e) {
infoWindow.setContent(data.description);
infoWindow.open(map, marker);
});
})(marker, data);
By passing the data to the anonymous function we make sure that it will stay there. We're essentially creating 3 self contained objects (closures) from our 3 locations.
Now if we're try the code, it will work perfectly and each InfoWindow
will display the right information.
Tip: Nicholas Johnson explains how closures work in 5 easy bullet points in his short post, What is it with Closure?
Live Demo
Conclusion
I hope that this example will clear out some confusion on this topic. Closures are a pretty confusing concept but once you understand how it works, it's something you we'll easily be able to apply in your own code. Anyway, you should be able to grab the code from this example and use it as a boilerplate to tweak your own needs.
Happy Coding!
My Google Maps book
If you found this article useful you might be interested in my book Beginning Google Maps API 3. It covers everything you need to know to create awesome maps on your web sites. Buy it on Amazon.com
March 29, 2012 at 11:58 pm
The closure does not work for me. I see three markers but only Copenhagen displays an infoWindow. Can you please post the entire code as one page?
March 30, 2012 at 6:56 am
You can always check out the code in the Live Demo. Here’s a direct link to the js file.
Cheers!
March 30, 2012 at 12:12 pm
Thank you! I got it to work, I think I had the order mixed up.
April 18, 2012 at 7:35 am
I’m copy your knowledge.
thanks for info.
this very helping for me.
thanks a lot.
π
April 20, 2012 at 9:20 am
Hi – I’m working through your book (which is great btw), and trying to use the above example. However my base code is ColdFusion. I have a query from my database which I can convert to a JSON object with serializeJSON. However my format is very different from your example and I can’t see how to loop over the json object I have.
Is there a quick way you can see that I can read it or is there another way I should go.
My json looks like this:
{“COLUMNS”:[“BUSINESS_NAME”,”LATITUDE”,”LONGITUDE”],”DATA”:[[“sdfdsf Books”,-36.890408,121.154751],[“dsdsdsd Booksellers”,-14.921735,18.602189],[“fff ints”,-17.8451823,195.0541819]]}
Any help gratefully accepted
April 27, 2012 at 12:34 am
Thanks for the elaboration/clarification. I bought your book last year and highly recommend it!
April 27, 2012 at 8:41 am
That’s nice to hear. Thanks for the recommendation!
May 2, 2012 at 8:24 pm
Thank you! The closure portion was all that I was missing for my feature to work. π
May 3, 2012 at 4:46 pm
I bought your book. I want to know how to implement this same feature to read a json file instead of what is currently there. Regards.
May 5, 2012 at 12:51 pm
that’s the SOLUTION !!!
thanks
May 5, 2012 at 12:58 pm
but what if I want one info window open on page load?
I have tried to make a infoWindow on page load and it works, but when I click on the markers in the json data:
a) infoWindow at page load stays open
b) once closed it will not display again (that’ maybe because it only display on pageload)
how can I solve this?
May 24, 2012 at 9:59 am
I have used code above and ‘infowindow’ is not working with Stockholm and Oslo… I can only see an infowindow after clicking Copenhagen. Could you please let me know that what could be wrong at my end.
May 30, 2012 at 5:22 pm
Thanks for posting this article. It’s been very helpful. I’ve managed to get things to work. Now, I’m trying to understand why it worked. I’m a bit unclear about the encapsulated code in the anonymous function:
1 (function(marker, data) {
2
3 // Attaching a click event to the current marker
4 google.maps.event.addListener(marker, “click”, function(e) {
5 infoWindow.setContent(data.description);
6 infoWindow.open(map, marker);
7 });
8
9 })(marker, data);
I’d been thinking that the first “(marker, data)” (ie. at line 1) was the passing in of the parameters, but I’m guessing that the parameters are actually being passed in at line 9. Is that correct? If so, then could you explain the purpose of specifying (marker, data) after the function keyword in line 1?
Cheers!
June 8, 2012 at 1:54 pm
wow… I was struggling with this for like hours. Thank you for this post.
July 2, 2012 at 4:22 am
awesome
July 13, 2012 at 10:06 am
The page with the JS cannot be found, please can you show me the source as only the one marker displays the infobox.
Thank you for a good article
August 6, 2012 at 8:16 am
Sorry about that, now it’s up again. (I’ve been on vacation for the last 3 weeks, hence my late reply)
September 6, 2012 at 1:45 pm
Hi, with your article (and of course your book) I had been able to implement more markers on a map.
What about adding differnt icons to different markers in a Json array?
October 5, 2012 at 4:59 pm
You saved my life π
I’ve been struggling for a week trying to integrate JSON data with OverlappingMarkerSpiderfyer and closure was the only way I could get it to work..
thanks again (and heres my code incase anyone else is struggling)
(function() {
window.onload = function() {
// Creating a new map var map = new google.maps.Map(document.getElementById(“map_canvas”), { center: new google.maps.LatLng(54.6, -3.0), zoom: 6, mapTypeId: google.maps.MapTypeId.ROADMAP });
//load data in via json var json = (function () { var json = null; $.ajax({ ‘async’: false, ‘global’: false, ‘url’: “yourjson.json”, ‘dataType’: “json”, ‘success’: function (data) { json = data; } }); return json;})();
// Creating a global infoWindow object that will be reused by all markers var infoWindow = new google.maps.InfoWindow(); var oms = new OverlappingMarkerSpiderfier(map,{markersWontMove: true, markersWontHide: true}); var usualColor = ‘eebb22’; var spiderfiedColor = ‘ffee22’; var iconWithColor = function(color) { return ‘http://chart.googleapis.com/chart?chst=d_map_xpin_letter&chld=pin|+|’ + color + ‘|000000|ffff00’; } var shadow = new google.maps.MarkerImage( ‘https://www.google.com/intl/en_ALL/mapfiles/shadow50.png’, new google.maps.Size(37, 34), // size – for sprite clipping new google.maps.Point(0, 0), // origin – ditto new google.maps.Point(10, 34) // anchor – where to meet map location );
// Looping through the JSON data var bounds = new google.maps.LatLngBounds(); for (var i = 0, length = json.length; i < length; i++) { var data = json[i], latLng = new google.maps.LatLng(data.latitude, data.longitude); bounds.extend(latLng); // Creating a marker and putting it on the map var marker = new google.maps.Marker({ position: latLng, map: map, icon: iconWithColor(usualColor), shadow: shadow, title: data.fullname }); marker.desc = '
'+data.comment+'';
// Creating a closure to retain the correct data, notice how I pass the current data in the loop into the closure (marker, data) (function(marker, data) { oms.addListener('click', function(marker) { infoWindow.setContent(marker.desc); infoWindow.open(map, marker); }); oms.addListener('spiderfy', function(markers) { for(var i = 0; i < markers.length; i ++) { markers[i].setIcon(iconWithColor(spiderfiedColor)); markers[i].setShadow(null); } infoWindow.close(); }); oms.addListener('unspiderfy', function(markers) { for(var i = 0; i < markers.length; i ++) { markers[i].setIcon(iconWithColor(usualColor)); markers[i].setShadow(shadow); } }); oms.addMarker(marker);
// Attaching a click event to the current marker /*google.maps.event.addListener(marker, "click", function(e) { infoWindow.setContent(data.comment); infoWindow.open(map, marker); });*/
})(marker, data);map.fitBounds(bounds);
}
}
})();
October 13, 2012 at 1:02 pm
plz povide dynamically it should be take not statically………
January 16, 2013 at 10:47 am
Thank u so much , it is a nice awesome tutorial
January 25, 2013 at 10:27 am
is it possible to add the json data that google maps customize app provides?
January 30, 2013 at 8:18 pm
Thank You!
I was stuck on the whole “closure” thing for two hours!
February 5, 2013 at 4:52 am
THANK YOU! Your instructions fixed my problem so quickly!
March 14, 2013 at 4:17 pm
very very happy
April 29, 2013 at 12:03 am
How can I do this with infobox instead of info windows
June 17, 2013 at 12:28 am
Hi, great post!
I am trying to do something similar, but want to load the data from the JSON file, and than use it to display as a marker on the map. Could you please guide what I should be doing?
This is the code i am using below to read from json file:
$(document).ready(function() {
$.getJSON(“markers.json”, function(json1) {
$.each(json1, function(key, markers) {
var latLng = new google.maps.LatLng(markers.lat, markers.lng);
// Creating a marker and putting it on the map
var marker = new google.maps.Marker({
position: latLng,
title: markers.title
});
marker.setMap(map);
});
});
});
My json file looks like:
var markers = [
{“lat”:-33.890542, “lng”:151.274856, “title”: “hello”},{“lat”:-33.923036, “lng”:151.259052, “title”: “hi”}];
Could you please help me with this? I will really appreciate it. Thanks. Hoping to hear back from you.
October 6, 2015 at 12:02 am
Hello,
For jshint, when you’re imp implementing the closure (marker,data), an error comes up “Don’t make functions within a loop”.
Do you have a fix for that by chance?