2009
jquery, expression engine and google maps
Based on a comment i made on a few blog posts on the always interesting viget, i was asked to relate how i might populate a google map with information from expression engine using jquery.
Expression Engine is great in its ability to dynamically generate xml the same way it generates any other page - you call for it, and it generates it. When you create a new template the template type dropdown gives you the option of xml. The code for generating that page uses the same syntax as generating any other page:
<?xml version="1.0" encoding="utf-8"?>
<network>
{exp:weblog:entries weblog="my_weblog" sort="asc"}
{if place_lat && place_long }
<place>
<home>{is_home}</home>
<latitude>{place_lat}</latitude>
<longitude>{place_long}</longitude>
<place_loc>{place_city}</place_loc>
<place_name>{place_title}</place_name>
</place>
{/if}
{/exp:weblog:entries}
</network>
Basically, what this is doing is making sure that the latitude and longitude are set (we have entries for the coordinates in the form field with the location of a good address geocoder in the caption, so the client does not need to wonder how to get those), then filling in all the other information as it’s needed using the regular EE syntax.
We could be using the GClientGeocoder option baked into google maps, but it’s a little fussy sometimes and we’re looking for the best solution.
So now that EE is giving us a dynamically generated XML file, we’re moving on to our google maps with jquery. I’m assuming we all know how to call the google maps api and jquery into our page, so i’ll skip over that.
Just as a warning, i’m a big fan of namespacing my scripts. This (obviously) doesn’t need to be done, but it helps me keep my functions distinct and use variables throughout that might interfere with other scripts. I haven’t detailed how i set it up, but Dustin Diaz has a great tutorial if you really want to know.
The first part is the easy part - setting up the maps API.
BH.maps.load = function () {
if (GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(0,0),0);
var bounds = new GLatLngBounds( );
This is the obvious stuff, i would think. the setting a center just allows the map to work. Don’t worry, we’ll recenter soon (hence the GLatLngBounds)
Now we can go ahead and create different icons for different uses. If you look back, the first item in
var greenIcon = new GIcon(G_DEFAULT_ICON);
greenIcon.image = "/EE_Global/img/icons/green_tack.png";
greenIcon.iconSize = new GSize(17,46);
greenIcon.shadow = "/EE_Global/img/icons/tack_shadow.png";
greenIcon.shadowSize = new GSize(0,0);
greenIcon.iconAnchor = new GPoint(3,42);
var redIcon = new GIcon(G_DEFAULT_ICON);
redIcon.image = "/EE_Global/img/icons/red_tack.png";
redIcon.iconSize = new GSize(27,44);
redIcon.shadow = "/EE_Global/img/icons/tack_shadow.png";
redIcon.shadowSize = new GSize(0,0);
redIcon.iconAnchor = new GPoint(4,38);
Now here’s where the jquery comes in. Getting through this is a breeze. We use a get function, then look through the XML which we happened to define in a variable for reuse of this function in other pages using different XML, but which could be called explicitly here.
$.get(mapXML, {}, function(xml){
$('place', xml).each(function(){
if ($(this).find("home").text() == "true") {
markerOptions = { icon:redIcon };
} else {
markerOptions = { icon:greenIcon };
}
var newLat = parseFloat($(this).find("latitude").text());
var newLong = parseFloat($(this).find("longitude").text());
var new_point = new GLatLng(newLat,newLong);
var theHTMLa = $(this).find("place_name").text();
var theHTMLb = $(this).find("place_loc").text();
var theHTMLall = theHTMLa + "<br>" + theHTMLb;
var marker = BH.maps.createMarker(new_point,markerOptions,theHTMLall);
map.addOverlay(marker);
bounds.extend(new_point);
map.setCenter(bounds.getCenter());
map.setZoom(map.getBoundsZoomLevel(bounds));
});
});
So we’re ripping through the XML, finding the tags we need to populate different variables, then popping those into our createMarker function. going through it a few lines at a time:
$.get(mapXML, {}, function(xml){
$('place', xml).each(function(){
We get the xml, and each “place” tag gets a function.
if ($(this).find("home").text() == "true") {
markerOptions = { icon:redIcon };
} else {
markerOptions = { icon:greenIcon };
}
We find our boolean to determine which marker we’re using. Again, this could be done with something other than a boolean, if you wanted to use 3 or more markers.
var newLat = parseFloat($(this).find("latitude").text());
var newLong = parseFloat($(this).find("longitude").text());
var new_point = new GLatLng(newLat,newLong);
We read our latitude and longitude, then put them into a GLatLng point.
var theHTMLa = $(this).find("place_name").text();
var theHTMLb = $(this).find("place_loc").text();
var theHTMLall = theHTMLa + "<br>" + theHTMLb;
Read a few pieces of info and concatenate them into one string. This could be done in the xml generator, but we chose to do it in the JS.
var marker = BH.maps.createMarker(new_point,markerOptions,theHTMLall);
Then we go to our marker creation function and hand it the GLatLng point, the color of the marker it should be using, and the html to put in the info bubble.
bounds.extend(new_point);
map.setCenter(bounds.getCenter());
map.setZoom(map.getBoundsZoomLevel(bounds));
Changing the bounds by adding the new point, then setting the zoom level and center based on that new addition. Yes, it does go through each point and make the change, but A) it happens so quickly you’re not seeing it redraw each time, and B) we found this way gives us less hassle in different browsers than trying to add them one at a time and then redrawing when all points are added.
The createMarker function is pretty bog standard:
BH.maps.createMarker = function (point,markerOptions,code) {
var marker = new GMarker(point,markerOptions);
GEvent.addListener(marker, "click", function() {
marker.openInfoWindowHtml(code);
});
gmarkers.push(marker);
return marker;
};
We’re not pushing the marker to any separate arrays for different marker icons, because we don’t need it. But you totally could.
So the code, all in one chunk:
BH.maps.load = function () {
if (GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(0,0),0);
var bounds = new GLatLngBounds( );
var greenIcon = new GIcon(G_DEFAULT_ICON);
greenIcon.image = "/EE_Global/img/icons/green_tack.png";
greenIcon.iconSize = new GSize(17,46);
greenIcon.shadow = "/EE_Global/img/icons/tack_shadow.png";
greenIcon.shadowSize = new GSize(0,0);
greenIcon.iconAnchor = new GPoint(3,42);
var redIcon = new GIcon(G_DEFAULT_ICON);
redIcon.image = "/EE_Global/img/icons/red_tack.png";
redIcon.iconSize = new GSize(27,44);
redIcon.shadow = "/EE_Global/img/icons/tack_shadow.png";
redIcon.shadowSize = new GSize(0,0);
redIcon.iconAnchor = new GPoint(4,38);
$.get(mapXML, {}, function(xml){
$('school', xml).each(function(i){
if ($(this).find("home").text() == "true") {
markerOptions = { icon:redIcon };
} else {
markerOptions = { icon:greenIcon };
}
var newLat = parseFloat($(this).find("latitude").text());
var newLong = parseFloat($(this).find("longitude").text());
var new_point = new GLatLng(newLat,newLong);
var theHTMLa = $(this).find("place_name").text();
var theHTMLb = $(this).find("place_loc").text();
var theHTMLall = theHTMLa + "<br>" + theHTMLb;
var marker = BH.maps.createMarker(new_point,markerOptions,theHTMLall);
map.addOverlay(marker);
bounds.extend(new_point);
map.setCenter(bounds.getCenter());
map.setZoom(map.getBoundsZoomLevel(bounds));});
});
}
};
BH.maps.createMarker = function (point,markerOptions,code) {
var marker = new GMarker(point,markerOptions);
GEvent.addListener(marker, "click", function() {
marker.openInfoWindowHtml(code);
});
gmarkers.push(marker);
return marker;
};
I may edit this as time goes by to respond to any questions (i’m a coder, not a writer), but there it is in a nutshell. As soon as the site we did this work on is up, we can post a link!

Thanks for posting this. I couldn’t get your example working in the few minutes I spent on it, but if you get it up online I’d really like to check it out. Thanks!
August 19th, 2009 at 4:07 pmkeith -
first question, and i apologize if it’s a duh question, but did you initialize it with a $().load(function() { BH.maps.load }); ??
if you did, and you want to send me your code i can poke through it and see if there was something i missed in my description. it’s the first of my first and my whole last at the domain name for my email.
- bob
August 20th, 2009 at 7:48 amGreat tutorial.
I’m trying to make a little web app that will allow users to add places on the map. Do you know if there is a way to let users populate a weblog? I’m looking for something like the comments plugin but that will let me direct data from user-side form fields to weblog fields without making people login as a user or something.
It would be way easier to use EE than straight up php and mysql.
Great tutorial. :) Is this map live someplace?
February 8th, 2010 at 4:44 amhey ryan -
sorry about the delayed response.
i think it all depends on what you’re looking to do. is this just a temporary thing where you’d let a user add places to a map for just them to view during that viewing, or are you looking for them to be able to add for the whole site?
if it’s just during that particular session, you could run it all through jquery - take an address and run it through a long/lat parser (or just make them enter long/lat on their own), then add a map spot dynamically using $(thisIsMyInputId).val() and taking the values and using them as arguments in a new function that basically mirrors the xml.each() function. of course you’d have to make some variables more global (e.g. bounds), but this way you can add as many points as you want, give them choices of markers, etc. and it wouldn’t take that long to do at all.
if it’s more of an app where each user is adding to a community-created array and that array keeps building, i’m not sure you’d want to do that without making them log in anyway, for security reasons. but yes, you could use EE and field values with a function to add them to a collection which then would get parsed in the next XML grab.
hope that helps!
- bob
ps - this code is up and running at http://www.bostonteacherresidency.org/profiles/#second (click on any one of the schools, though right now they only have one pin per map)
February 23rd, 2010 at 5:19 pm