Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Creating a Google Map with ExpressionEngine

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

As Richard Tape has begun to show in his part 1 and part 2 articles on Becoming an ExpressionEngine Superstar, EE is a flexible and easy to customize CMS. Now that everyone has some understanding of how EE works, I thought I would take this opportunity to show a relatively real world example of creating a dynamic Google Map powered by EE.

Final Product

For this example, let’s assume that our company has various locations throughout the US, and we are tasked with creating a Google Map that shows all the locations and is easy to maintain. Take a look at the demo to see what we are trying to accomplish.

Setting Up the Weblog and Custom Field Group

First, remember that a weblog is nothing more than just a container of data. Actually, in EE 2.0, they are changing the term weblog to channel. So we are going to create a weblog called Locations and a custom field group called Locations. It’s definitely not a requirement to have them be named the same, but it just makes it easier to understand the relationship.

Defining the Custom Field Group

I actually like to create the field group first, so let’s do that by going to Admin > Weblog Administration > Custom Weblog Fields. Then click on the big green button that says Create a New Weblog Field Group.

New Field Group

Field Groups

Enter Locations as the Field Group Name and click submit.

New Field Group

Naming the new field group

After you click submit, you will see that EE tells you that you can’t use it until you assign it to a weblog. But, we will assign it to a weblog when we create the weblog.

New Field Group Created

The Locations field group has been created

Now that our field group is created, we need to think about the actual fields that we want to have in it. The following are the fields that I think fit, and their properties in EE:

  1. Field Label: Longitude
    • Field Name: locations_longitude
    • Field Type: Text Input
    • Maxlength: 50
    • Default Text Formatting: None
    • Hide Formatting Menu
    • Required Field? Yes
  2. Field Label: Latitude
    • Field Name: locations_latitude
    • Field Type: Text Input
    • Maxlength: 50
    • Default Text Formatting: None
    • Hide Formatting Menu
    • Required Field? Yes
  3. Field Label: Address
    • Field Name: locations_address
    • Field Type: Text Input
    • Maxlength: 200
    • Default Text Formatting: None
    • Hide Formatting Menu
    • Required Field? Yes
  4. Field Label: Photo
    • Field Name: locations_photo
    • Field Type: Text Input
    • Maxlength: 50
    • Default Text Formatting: None
    • Hide Formatting Menu
    • Required Field? No
  5. Field Label: Photo Width
    • Field Name: locations_photo_width
    • Field Type: Text Input
    • Maxlength: 4
    • Default Text Formatting: None
    • Hide Formatting Menu
    • Required Field? No
  6. Field Label: Photo Height
    • Field Name: locations_photo_height
    • Field Type: Text Input
    • Maxlength: 4
    • Default Text Formatting: None
    • Hide Formatting Menu
    • Required Field? No
  7. Field Label: Description
    • Field Name: locations_description
    • Field Type: Textarea
    • Textarea Rows: 6
    • Default Text Formatting: XHTML
    • Display Formatting Menu
    • Required Field? No

Now that we have mapped out our fields, we need to create the fields in EE. So first, click on Add/Edit Custom Fields in the Locations record. Then, click on the big green button that says Create a New Custom Field.

Custom Fields

Click on Create a New Custom Field

Now here is where we create the fields in EE that we defined previously. First, let’s start with the Longitude field:

New Custom Field

Defining the Longitude field

Once we have selected all of the appropriate properties, click submit. Now, the Longitude field is created.

Custom Field Created

The Longitude field is created

Now that you have seen how to create one field, I’m just going to go ahead and create the rest.

Custom Fields Created

All custom fields created

Create the Locations Weblog

Now that our field group has been defined, we need to create our weblog and assign the Locations custom field group to it. First, click on the Weblog Administration bread crumb. Then click on Weblog Management. Finally, click the big green button that says Create a New Weblog.

Weblog Management

Weblog Management

Enter Locations as the Full Weblog Name and locations as the Short Name. Select Yes for Edit Group Preferences, and a new section will show up. This is where we select Locations as the Field Group.

Create New Weblog

Create Locations Weblog

Once all the settings have been select, click submit, and the Locations weblog is created.

Weblog Created

Locations Weblog Created

Entering Locations

Now that our Locations weblog is created, we can start to enter the locations. Hover over the Publish tab and click on Locations.

Locations Entry Form

Locations Entry Form

The Title, Address, and Description fields are pretty self explanatory, but the URL Title, Longitude, Latitude, and Photo fields may need a little explanation.

URL Title

The URL Title field is auto-populated when you enter the Title. The URL Title replaces spaces with underscores and makes the text lowercase. For this certain situation, we are not going to use it. There is a way to hide it, but I’m not going to go through that right now.

Longitude and Latitude

I’m sure everyone knows what longitude and latitude are, but the question is: how do you determine it from an address? By using a third party site like Map Builder. This site allows you to enter an address, and it returns the longitude and latitude.

Map Builder

Using Map Builder to get longitude and latitude

Photo

If we had a photo of the location, we can include it in when the plot point is clicked. Uploading images in EE can be a little confusing, so let’s walk through it.

First, click on the Upload File link which is located under the submit button. A pop-up window will come up.

File Upload

File Upload

Browse for your file on your computer, and click Submit.

You can resize the photo after uploading it by clicking the Resize Image button, but I sized my image before uploading, so I won’t demo that part. We want to select URL Only for the File Type and Photo for the Image Location.

File Uploaded

File Uploaded

Once our options have been selected, click on Place Image and Close Window. This will now populate your Photo field with the location of your image.

Photo Width and Height

If a user uploads a photo of the location, we want them to input the image height and width. If they do not, the image will break out of the information bubble because of how Google Maps calculates the size of the bubble. It is simple enough to add in the width and height to solve this problem, so we added that in.

Description

If we want to enter a brief description to display in the bubble when the plot point is clicked, this is where you would enter it. This field supports HTML formatting by default.

Now that we have entered all of our pieces of data, we can submit the form and the location will be published.

File Uploaded

Our Completed Entry

Then, just repeat this process for the additional entries.

Creating the Map

First, we need to create a new template group for our locations map by clicking on the Templates tab. Then, click on the big green button that says Create a New Template Group.

Templates

Templates

Then, enter locations into the Template Group name field, and click submit. Our template group has now been created.

Template Group Created

Template Group Created

While we are here, let’s make another template group called scripts.

Next, select the locations template group from the left hand column, and click on index. Now we can start to build our template. You will presented with what is just an empty box, this is where you will add all of your code.

Edit Template

Editing Templates

Now all we need to do is enter our code and click update, and our template is updated.

The Code

In order to use the Google Maps API, you need to sign up for an API key for your site. Once you sign up, you will be provided with the API key, which you use when including the JavaScript on the page. Let’s get started with a simple page and include our Google Maps code:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Locations</title>
<script src="http://maps.google.com/maps?file=api&v=2&sensor=false&key=ABQIAAAAGbpRl2XCyCtoHtEtVLA9mhT9xvUTfY2sa86RDF1pWLQtRVPGPxQD1aEASfi1xtt39RqVCDd8ib1hGw" type="text/javascript"></script>
</head>

<body>

</body>
</html>

Note: ABQIAAAAGbpRl2XCyCtoHtEtVLA9mhT9xvUTfY2sa86RDF1pWLQtRVPGPxQD1aEASfi1xtt39RqVCDd8ib1hGw is the value of the Google Maps API key for my site. You will need to update this with your own key.

Once this code has been entered into to textarea, just click update to save the template. Then, if you click the big green button that says View Rendered Template, we will see what the page will look like.

Next, we need to add a div that will actually contain the map.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Locations</title>
<script src="http://maps.google.com/maps?file=api&v=2&sensor=false&key=ABQIAAAAGbpRl2XCyCtoHtEtVLA9mhT9xvUTfY2sa86RDF1pWLQtRVPGPxQD1aEASfi1xtt39RqVCDd8ib1hGw" type="text/javascript"></script>
</head>

<body>

<div id="map">You must have JavaScript Enabled to view this map.</div>

</body>
</html>

Note: I just added the message about needing JavaScript for the users who do not have JavaScript enabled, but you could certainly output the list of locations instead of this message.

We need to define the height and width of the div that is going to contain the map, so for this example, I am just going to use some CSS on the page.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Locations</title>
<script src="http://maps.google.com/maps?file=api&v=2&sensor=false&key=ABQIAAAAGbpRl2XCyCtoHtEtVLA9mhT9xvUTfY2sa86RDF1pWLQtRVPGPxQD1aEASfi1xtt39RqVCDd8ib1hGw" type="text/javascript"></script>
<style type="text/css">
#map { height: 600px; width: 800px; }
</style>
</head>

<body>

<div id="map">You must have JavaScript Enabled to view this map.</div>

</body>
</html>

That is all of the HTML we need to get our map to show up, so once the code is entered, click Update and Finished, and we are taken back to the main templates section. Next, we are going to code our JavaScript to add the points to the map.

Select scripts from the left hand column and click on New Template. Enter map as the template name, and select JavaScript from the Template Type dropdown, and then click Submit.

Our new template named map has now been created. So click on map, and let’t edit the template.

The cool thing about EE is that we can enter EE code into the JS and CSS templates and it will process that code. In order to do this, we do have to add a hidden config variable to our config.php file that is located in the system folder. So open up your config.php file and enter the following: $conf['protect_javascript'] = 'n';. Then upload it, and EE tags will be processed in JS code.

Note: I am going to use plain old JavaScript for this example, but you could easily modify some code to use your framework of choice.

First, we are going to get started creating a function that will add the plot points to the map:

function populateMap() {
	var map = new GMap2(document.getElementById("map"));
	map.setCenter(new GLatLng(32.02670629333614, -95.009765625), 4);
	map.setUIToDefault();
}

The first line is specifying to use the element with an id of map to create the map. The next line sets the center of the map to the specified latitude and longitude, then sets the zoom level. These are just arbitrary values for this example, so you will most likely need to adjust. The final line sets up the controls on the map, i.e. zoom, move, map type, etc.

Now, we want to access the data that we entered in EE by using the exp:weblog:entries tag:

function populateMap() {
	var map = new GMap2(document.getElementById("map"));
	map.setCenter(new GLatLng(32.02670629333614, -95.009765625), 4);
	map.setUIToDefault();
	
	{exp:weblog:entries weblog="locations" disable="categories|category_fields|member_data|pagination|trackbacks"}
		{if count == 1}
			var points = new Array({total_results});
		{/if}

	{/exp:weblog:entries}
}

So this code is saying to “query” the locations weblog, and disable additional parameters that we will not be using. This is a good habit to get into because it can improve performance.

There are some EE variables that we are using that I will need to explain: count and total_results. count is exactly what it sounds like: it just indicates the number of the current record that we are on. total_results indicates how many total records will be returned.

So if count equals 1, this the first time through the loop, so we initialize our array that will have a size of the total number of records returned.

Next, we want to actually grab the data and output it in the loop. Note: I am going to add line breaks to this code just for readability, but in production use, the line breaks need to be removed.

function populateMap() {
	var map = new GMap2(document.getElementById("map"));
	map.setCenter(new GLatLng(32.02670629333614, -95.009765625), 4);
	map.setUIToDefault();
	
	{exp:weblog:entries weblog="locations" disable="categories|category_fields|member_data|pagination|trackbacks"}
		{if count == 1}
			var points = new Array({total_results});
		{/if}
		
		points[{count}] = [{locations_latitude},{locations_longitude},
			'<div class="infoContainer">
				<h2>{title}</h2>
				{if locations_photo && locations_photo_width && locations_photo_height}
					<img src="{locations_photo}" alt="" height="{locations_photo_height}" width="{locations_photo_width}" />
				{/if}
				<p class="address">{locations_address}</p>
				{if locations_description}
					{locations_description}
				{/if}
			</div>'];

	{/exp:weblog:entries}
}

That may look like a lot, so let’s go through it piece by piece.

points[{count}] = [{locations_latitude},{locations_longitude},

Here, we are adding an array to our points array. In this array, we are going to be adding the latitude, longitude, and information to display in the information bubble. Notice how locations_latitude and locations_longitude match up with the values we added for the Field Names in our custom field group.

'<div class="infoContainer">
	<h2>{title}</h2>
</div>'];

In this snippet, we are just adding a div and the title that was entered into EE.

{if locations_photo && locations_photo_width && locations_photo_height}
	<img src="{locations_photo}" alt="" height="{locations_photo_height}" width="{locations_photo_width}" />
{/if}

In this piece, we are checking to see if there was a value entered into the photo, photo width, and photo height fields. If there was, we add the photo to our information bubble.

<p class="address">{locations_address}</p>
{if locations_description}
	{locations_description}
{/if}

Finally, we add the address to the information bubble, and if there was a description entered, we add that too. Here is that whole line without any line breaks:

points[{count}] = [{locations_latitude},{locations_longitude},'<div class="infoContainer"><h2>{title}</h2>{if locations_photo && locations_photo_width && locations_photo_height}<img src="{locations_photo}" alt="" height="{locations_photo_height}" width="{locations_photo_width}" />{/if}<p class="address">{locations_address}</p>{if locations_description}{locations_description}{/if}</div>'];

To finish off the function, we just need to loop through the array and add the plot points and event listeners to the map:

function populateMap() {
	var map = new GMap2(document.getElementById("map"));
	map.setCenter(new GLatLng(32.02670629333614, -95.009765625), 4);
	map.setUIToDefault();
	
	{exp:weblog:entries weblog="locations" disable="categories|category_fields|member_data|pagination|trackbacks"}
		{if count == 1}
			var points = new Array({total_results});
		{/if}
		
		points[{count}] = [{locations_latitude},{locations_longitude},'<div class="infoContainer"><h2>{title}</h2>{if locations_photo && locations_photo_width && locations_photo_height}<img src="{locations_photo}" alt="" height="{locations_photo_height}" width="{locations_photo_width}" />{/if}<p class="address">{locations_address}</p>{if locations_description}{locations_description}{/if}</div>'];

	{/exp:weblog:entries}
	
	for(var i=1; i < points.length; i++) {
		var point = new GLatLng(points[i][0],points[i][1]);
		var windowInfo = points[i][2];
		var marker = createMarker(point,windowInfo);
		map.addOverlay(marker);
	}
}

So we are creating our point and passing in the latitude and longitude. Then, we are creating a variable that contains all of the information for the information bubble. Next, we are calling a custom function that I will define in a minute and passing in the point and information bubble text. Finally, we are adding the marker to the map.

Here is our custom function that is creating the marker:

function createMarker(point, overlayText) {
	var marker = new GMarker(point);
	GEvent.addListener(marker, "click", function() {marker.openInfoWindowHtml(overlayText);});
	return marker;
}

So we are creating a marker based on our point on the map, then adding an event listener that will open the information bubble with the appropriate text when it is clicked.

So that’s pretty much it. Although, I will add one more function that will tell the JS function to run once the dom has been loaded:

function addLoadEvent(func) {
	var oldonload = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = func;
	} else {
		window.onload = function() {
			if (oldonload) {
				oldonload();
			}
			func();
		}
	}
}
addLoadEvent(populateMap);

I first read about this function from Simon Willison. You could again replace this with code for whatever JS library you are using.

Here is the full JS for that template:

function populateMap() {
	var map = new GMap2(document.getElementById("map"));
	map.setCenter(new GLatLng(32.02670629333614, -95.009765625), 4);
	map.setUIToDefault();
	
	{exp:weblog:entries weblog="locations" disable="categories|category_fields|member_data|pagination|trackbacks"}
		{if count == 1}
			var points = new Array({total_results});
		{/if}
		
		points[{count}] = [{locations_latitude},{locations_longitude},'<div class="infoContainer"><h2>{title}</h2>{if locations_photo && locations_photo_width && locations_photo_height}<img src="{locations_photo}" alt="" height="{locations_photo_height}" width="{locations_photo_width}" />{/if}<p class="address">{locations_address}</p>{if locations_description}{locations_description}{/if}</div>'];

	{/exp:weblog:entries}

	for(var i=1; i < points.length; i++) {
		var point = new GLatLng(points[i][0],points[i][1]);
		var windowInfo = points[i][2];
		var marker = createMarker(point,windowInfo);
		map.addOverlay(marker);
	}
}

function createMarker(point, overlayText) {
	var marker = new GMarker(point);
	GEvent.addListener(marker, "click", function() {marker.openInfoWindowHtml(overlayText);});
	return marker;
}

function addLoadEvent(func) {
	var oldonload = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = func;
	} else {
		window.onload = function() {
			if (oldonload) {
				oldonload();
			}
			func();
		}
	}
}
addLoadEvent(populateMap);

Once all that code is added, click Update and Finished to be returned to the main templates page. Next, we need to go back to our locations template group and edit the index template.

We need to include the JS template that we just created in the head of the document:

<script type="text/javascript" src="{path="scripts/map"}"></script>

I am using the path variable to link to the JS template.

So now, when we go back and view our locations template in the browser, you may see a JS error. If a location description was entered, it may add some new lines to the code, so this will break the string in the JS. So the solution is to somehow remove those new lines.

Installing the Plugin

Luckily, there is a plugin for EE called Find and Replace that will let us replace all \n with nothing.

So browse to Admin > Utilities > Plugin Manager, and find the plugin in the right hand column. When you find it, click on install, and that’s it.

Using the Plugin

Now, we can use the plugin in our code. So go back and edit the map template in our scripts template group, and replace this:

{if locations_description}
	{locations_description}
{/if}

With this:

{if locations_description}
	{exp:replace find="\n" multiple="yes" regex="yes"}
		{locations_description}
	{/exp:replace}
{/if}

This function is basically just saying to replace all new lines with nothing. Of course we want to remove all of the line breaks from the code, so here is the updated full JS:

function populateMap() {
	var map = new GMap2(document.getElementById("map"));
	map.setCenter(new GLatLng(32.02670629333614, -95.009765625), 4);
	map.setUIToDefault();
	
	{exp:weblog:entries weblog="locations" disable="categories|category_fields|member_data|pagination|trackbacks"}
		{if count == 1}
			var points = new Array({total_results});
		{/if}
		
		points[{count}] = [{locations_latitude},{locations_longitude},'<div class="infoContainer"><h2>{title}</h2>{if locations_photo && locations_photo_width && locations_photo_height}<img src="{locations_photo}" alt="" height="{locations_photo_height}" width="{locations_photo_width}" />{/if}<p class="address">{locations_address}</p>{if locations_description}{exp:replace find="\n" multiple="yes" regex="yes"}{locations_description}{/exp:replace}{/if}</div>'];

	{/exp:weblog:entries}

	for(var i=1; i < points.length; i++) {
		var point = new GLatLng(points[i][0],points[i][1]);
		var windowInfo = points[i][2];
		var marker = createMarker(point,windowInfo);
		map.addOverlay(marker);
	}
}

function createMarker(point, overlayText) {
	var marker = new GMarker(point);
	GEvent.addListener(marker, "click", function() {marker.openInfoWindowHtml(overlayText);});
	return marker;
}

function addLoadEvent(func) {
	var oldonload = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = func;
	} else {
		window.onload = function() {
			if (oldonload) {
				oldonload();
			}
			func();
		}
	}
}
addLoadEvent(populateMap);

Conclusion

That’s it! We can now view our template in the browser and see our EE powered Google Map. You can also view my demo.

Google Map



Advertisement