Using AJAX To Update A Non-Map DIV Via Google Maps API’s GDownload() And GMarker OnClick Event

Recently asked on Yahoo! Answers:

Is it possible to have a google map invoke an Ajax request?

I basically need a google map to invoke a different Ajax request to load a part of a page outside the map when an item on the map is clicked…. is this possible? I know that the map itself uses Ajax when you scroll too far, to call in new imagery, but can I also modify the map to load/swap a different part of the page? I’m using ASP.NET pages too, btw.

The answer is “Yes,” thanks in large part to the Google Maps API’s built-in GDownload() class and the GMarker’s onclick event.

All we need to do is add an event listener that waits for our marker to be clicked, then call a function that will accept some unique ID for the marker, and cull the relevant information from some helper script. For this example, I’ll use PHP to pull a record from a MySQL database that gives details about the thing the marker is pointing out.

This discussion assumes you have basic familiarity with the Google Maps API, or that you can at least follow the documentation overview.

As always, you can download the source code for this example at the end of the discussion.

Step 1: The HTML And CSS

To begin, we need two DIVs: one for the map and one for the details we’ll pull from the database. I have decided to ID them, strangely enough, as “map” and “details.”

<div id="map"></div>
<div id="details">Clicking on any of the icons above will reveal details about the marked item in this area.</div>

We also need to add onload and onunload events to the body tag. The load event will trigger a JavaScript function that actually places the map on our page; the unload event will trigger a different function that cleans up all the resources used by the Maps API once we close the page.

I have found that cleaning up the page on close is vital; the Maps API can really chew up memory, especially if you have lots of points plotted on your map and double-especially for Internet Explorer, triple-especially version 6 or earlier.

<body onload="pageLoad()" onunload="pageUnload()">

Finally, we need to style the DIVs. I am making them 800px wide, one atop another; you can, of course, lay them out however you want.

div#map, div#details {
	border: solid 1px #000;
	margin: 10px;
}

div#map {
	width: 800px;
	height: 570px;
}

div#details {
	width: 780px;
	padding: 10px;
}

Step 2: The Initial JavaScript

We need to load the API, so we do that with a simple JavaScript call to the API library, again using our API key:

<script type="text/javascript" src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAynoIQZ5YX-BdZ9UvBsREmBTdnN6G66CbOAd5ryXk14zWHUvlaxQuno-HUh7x2lF8jtwWTCV-u8SS5w"></script>

Next, we’re going to declare a few variables in the global scope: The map itself; the variable that will refer to the details DIV; the default center point of the map; and the default zoom level of the map.

We’re declaring the map and details DIV in global scope because it’s going to be much easier to manage our script by having them there, rather than constantly passing them as arguments back and forth between functions.

Technically, I don’t need to declare the center point or zoom level with global scope; however, I find that when I work with maps that users can zoom or pan (move), it’s helpful to retain those original settings, so the user can revert to the default settings whenever he needs.

var map;
var details;
var dcenter = new GLatLng(44.30753551540104, -69.77983474731445);
var dzoom = 17;

Step 3: The pageLoad() Function

We’re ready to create the map. We’ll use the standard GMap2 constructor for this.

Next, I want to show this as a hybrid map / satellite photo that can’t be moved or zoomed, so I’ll call the proper methods to have that happen.

Finally, I’ll declare the value for the details variable here, because JavaScript requires me to associate such declarations with a function that is invoked by some event, and not as part of the global scope.

function pageLoad() {
	map = new GMap2(document.getElementById('map'));
	map.setMapType(G_HYBRID_MAP);
	details = document.getElementById('details');

	map.setCenter(dcenter);
	map.setZoom(dzoom);
	map.addControl(new GSmallMapControl());
	map.addControl(new GMapTypeControl());
	map.disableDragging();
	map.disableScrollWheelZoom();
	map.disableDoubleClickZoom();

As the last part of the pageLoad() function, I’m going to call a function named addPoint(), which I haven’t yet described. That function will take four arguments: A unique ID which corresponds to a database entry; the name of the place, as a string; and finally, the latitude and longitude of the point, respectively.

I’ll call this function for every point I intend to plot.

addPoint(1, 'Capitol Park', 44.30616121527788, -69.77895498275757);
addPoint(2, 'State House', 44.307113247837854, -69.7816264629364);
addPoint(3, 'Burton M. Cross State Office Building', 44.30720537920164, -69.78294610977173);
addPoint(4, 'Blaine House (Governor\'s Mansion)', 44.308211137183676, -69.78119730949402);
addPoint(5, 'Cultural Building', 44.305754293425046, -69.78273153305054);
addPoint(6, 'Vietnam Veterans War Memorial', 44.30728215522767, -69.77898716926575);
addPoint(7, 'Department Of Labor Building', 44.30738196391136, -69.77783918380737);

Step 4: The addPoint() Function

Most of the work we’re going to do is handled by the addPoint() function. It not only places the markers on the map, it also assigns to them three events: onmouseover, onmouseout and onclick.

The onmouseover event will open GInfoWindow object, which gives the name of the place being shown and instructs the user to click the icon for more info. The onmouseout event will close the GInfoWindow we just opened.

The onclick event will display our database content in the details DIV. More on that momentarily. For now, we begin by creating a marker object, then adding it to the map as an overlay:

function addPoint(pid, name, lat, lng) {
	var point = new GLatLng(lat, lng);
	var marker = new GMarker(point);
	map.addOverlay(marker);

To make the GInfoWindows work, we first create a string that gives us the text we want to display in the window; that string will use the name parameter from our function. Then, we assign the mouseover and mouseout events to open, and close, the GInfoWindow object for each point, respectively.

We add these events using the addListener() method, which is part of the GMarker and GMap2 classes.

You’ll note that we open the GInfoWindow by invoking the marker object, but close it with the map object. That’s because the way the Maps API works, which GInfoWindow gets placed on the map is a function of which marker is selected; but once the GInfoWindow is added to the map, it’s part of the map, and thus must be closed by the map.

var info = '<strong>' + name + '</strong><br />';
info += 'For more information, click the icon.';

GEvent.addListener(marker, 'mouseover', function() {
	marker.openInfoWindow(info);
});

GEvent.addListener(marker, 'mouseout', function() {
	map.closeInfoWindow();
});

Finally, we add an onclick event to each marker that will populate the details DIV. For that, I simply pass the unique identifier for each marker on to another not-yet-described function, this one named showDivInfo().

And, as a help to the user, we’re going to change the details DIV’s HTML to display a generic loading message, so the user knows his click was received and is being handled.

GEvent.addListener(marker, 'click', function() {
	details.innerHTML = '<em>Loading information ...</em>';
	showDivInfo(pid);
});

Step 5: The showDivInfo Function

Our AJAX request is going to be handled by GDownload(), a class built into the Maps API that handles HTTP requests. We could, in theory, load our own HTTP request object and use that; but since we already have all the overhead of the Maps API loaded, why not use Google’s request object?

First, we create a URL that will call our PHP helper page, passing to it the unique ID we assigned to the marker that was clicked. Once we get a response back from the object, we then simply set the details DIV’s innerHTML property to be the text our helper script returns.

function showDivInfo(pid) {
	var url = 'info.php?pid=' + pid;
	GDownloadUrl(url, function(data, responseCode) {
		details.innerHTML = data;
	});
}

Step 6: The pageUnload() Function

Our last bit of JavaScript is the pageUnload() function, that simply calls the GUnload() class to clear out all the Maps API objects once the window is closed.

function pageUnload() {
	GUnload();
}

Step 7: The MySQL Table

Our MySQL table contains five fields:

  • A primary key, which corresponds to one of the unique IDs our AJAX is going to request;
  • A varchar field that contains the name of the place;
  • An integer field that contains the year the building was erected;
  • A text field which contains the description of the place;
  • A varchar field that holds the image name / URL for the entry, if applicable. When there isn’t an image, the field will be NULL.
DROP TABLE IF EXISTS `mapstable`;
CREATE TABLE IF NOT EXISTS `mapstable` (
  `record_id` int(10) unsigned NOT NULL auto_increment,
  `item_title` varchar(50) NOT NULL,
  `item_year` int(10) unsigned NOT NULL,
  `item_description` text NOT NULL,
  `item_image` varchar(100) default NULL,
  PRIMARY KEY  (`record_id`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;

I won’t include here the code to insert the records, but it will be part of the downloadable source code.

Step 8: The info.php Page

Our PHP page begins with checking that we have a GET variable that is in range. If not, we kill the script. Next, we try to connect to the database server and select our database; if we cannot do those steps, the script dies.

if(!preg_match('/^[0-7]{1,1}$/', $_GET['pid'])) {
	die('Input values out of range');
}
else {
	$host = "localhost";
	$user = "username";
	$pass = "password";
	$db = "databasename";

	mysql_connect($host, $user, $pass) or die('Cannot connect to database server');
	mysql_select_db($db) or die('Cannot select database');

We can now query our database for the requested record. If we cannot successfully query the server, our script dies.

If the query is successful, we check for a record match by seeing if the results set has any rows. If it doesn’t, we report that we could not find the record.

$sql = "SELECT * FROM mapstable WHERE record_id = $_GET[pid]";
$rs = mysql_query($sql) or die('Cannot parse query');

if(mysql_num_rows($rs) == 0) {
	echo "<p><em>No matching record found.</em></p>n";
}

If we do have a record, then we go ahead and assign that row to an associative array and start outputting HTML for display in our DIV. We also check to see if the image field is NULL. If not, then we output an image tag that uses the image field’s value as the URL for the image.

else {
	$row = mysql_fetch_array($rs);
	if(!is_null($row['item_image'])) {
		echo "<img src="$row[item_image]" alt="$row[item_title]" style="float: right; margin-left: 5px;" />n";
	}
	echo "<h1>$row[item_title]</h1>n";
	echo "<p><strong>Year constructed:</strong> $row[item_year]</p>n";
	echo str_replace("n", "<br />", $row['item_description']);
}

And that’s all there is to it; we can make a request, we can get what we want from the database, and when all goes according to plan, we can update a non-Google Maps DIV with new HTML, based on click events that take place inside the Maps DIV.

You can see a working demo here:

http://www.dougv.com/demo/ajax_google_maps_detaildiv/

You can download the source code for this project here: Using AJAX To Update A Non-Map DIV Via Google Maps API’s GDownload() And GMarker OnClick Event demo code

I distribute code under the GNU GPL. See Copyright & Attribution for details.

Important note: The source code will not run on your site as is! You need your own Google Maps API key to run this code. Replace my API key with your key.

Special note of thanks: I would be remiss not to thank Anthony Douin of the Maine State Archives and Earle Shettleworth of the Maine Historic Preservation Commission for their assistance in the construction dates used in this application.

One thought on “Using AJAX To Update A Non-Map DIV Via Google Maps API’s GDownload() And GMarker OnClick Event

  1. Pingback: dougv.com | The Web home of Doug Vanderweide » Blog Archive » Getting Plain Text From An ASP.NET 2.0 Page For Use As An AJAX Data Source

Leave a Reply