StructureCMS

April 20, 2010

Google Maps – Create your own store locator

Filed under: AJAX, Web Development — joel.cass @ 12:43 pm

Recently I have had to create a store locator for a website I have been working on. On the outset, it sounds easy. Simply add a number of points to the map and calculate the distance between a point of reference and the points you have added.

Then the question comes up: How do you calculate distance? As the crow flies, or by road directions?

To test this out, I created a Google Maps Distance Calculator.

Road directions work, and are not too difficult to get from the API. However, they are dead slow and require callbacks. Sometimes a request may take up to 30 seconds, which means that it could take ages to render the distance between 10 or so points. Furthermore, sometimes the distance cannot be calculated at all.

So the more efficent and reliable way is to draw a line and measure it. That’s much easier and does not require a callback method:

var polyline = new GPolyline([
		point1.getLatLng(), // starting point
		point2.getLatLng() // finishing point
	],
	"#0000FF", // color
	1 // opacity
);
numDistance = polyline.getLength(); // in metres

I could go on about the intricacies of storing and sorting an array of points, but I won’t. Instead I have created a script that can do this for you. First off, you will need to get a google maps key. This is easy, you can do it here. Then, you need to call the google maps V2 API as follows:

<head>
...
<script type="text/javascript" src="http://www.google.com/jsapi?key=__________________KEY_________________"></script>
<script type="text/javascript">
	// initialise google maps
	google.load("maps", "2");
</script>
</head>
....

You will need to add some divs for containing the map and listing areas. By default, the map element is assumed to be a <div> tag and the listing container is a <table> tag.

<body>
...
	<div id="map"></div>
	<table id="table"></table>
...
</body>

(We will get into how you can change the listing container later)

Then, you will need to include the pointSorter.js script as follows:

<head>
...
</script>
<script type="text/javascript" src="pointSorter.js"></script>
<script type="text/javascript">
	// set target divs
	pointSorter.setDiv("map");
	pointSorter.setListingContainer("table");

	// load maps, point sorter etc
	google.setOnLoadCallback(function () {
		// initialise point sorter
		pointSorter.init();

		// add points
		...
	});
</script>
...
</head>

You may have noticed that I have not entered anything below “// add points” in the block above. There are two ways to add points:

// add point by coordinate
pointSorter.addPoint(
	new GLatLng(37.423156,-122.084917),
	"Google Inc.","<strong>Google Inc. Head Office</strong>"
);

// add point by address
pointSorter.addPointByAddress(
	"701 First Avenue, Sunnyvale, California",
	"<strong>Yahoo! Head Office</strong>"
);

Both methods take similar arguments. The first takes a GLatLng (point) object + a description, whilst the second takes an address and a description. The first method is much faster than the second as no request has to be made to the server to look up an address.

By now (after some style tweaking), your code may be looking something like this:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>
	<title>Maps Test</title>
	<style type="text/css">
		body { font-family:sans-serif; font-size:0.8em; }
		/* ID Selectors */
		#map {
			width:70%;
			height:500px;
			float:left;
		}
		#table {
			margin-top:1em;
			width:30%;
			height:500px;
			overflow-y:auto;
			float:right;
		}
		/* classes */
		.info { color:#008000; }
	</style>
	<script type="text/javascript" src="http://www.google.com/jsapi?key=_____________KEY____________"></script>
	<script type="text/javascript" src="pointSorter.js"></script>
	<script type="text/javascript">
	// initialise google maps
	google.load("maps", "2");

	// set target divs
	pointSorter.setDiv("map");
	pointSorter.setListingContainer("table");

	// load maps, point sorter etc
	google.setOnLoadCallback(function () {
		// initialise point sorter
		pointSorter.init();

		// add point by coordinate
		pointSorter.addPoint(
			new GLatLng(37.423156,-122.084917),
			"Google Inc.","<strong>Google Inc. Head Office</strong>"
		);

		// add point by address
		pointSorter.addPointByAddress(
			"701 First Avenue, Sunnyvale, California",
			"<strong>Yahoo! Head Office</strong>"
		);

	});
	</script>
</head>

<body>

<div id="map"></div>
<table id="table"></table>

</body>
</html>

If you run this in your browser, you should see a map appear with two locations shown. Clicking on either will reveal the name of the location.

Now the only thing left to do is allow users to enter in an address. This is as simple as adding the method and form below. When the form is “submitted”, a search is made for the address.

<script type="text/javascript">
	...
	function setLocation (location) {
		// set point of reference
		pointSorter.setPointOfReference(
			location,
			"<strong>Reference Location</strong><p>"+location+"</p>"
		);
	}
	</script>
</head>

<body>

<form action="" onsubmit="setLocation(this.address.value);return false;">
	<input type="text" name="address" />
	<input type="submit" value="Set Reference Location" />
</form>
...
</html>

When the form is submitted, a new point is added to the map and the distances are shown.

Here are some other tips about the script.

You can set icons for the map pointers by using the following methods:

// set icon for reference location
pointSorter.setHomeIcon("http://www.google.com/mapfiles/dd-start.png", 20, 34);
// set icon for surrounding point locations
pointSorter.setPointerIcon("http://www.google.com/mapfiles/dd-end.png", 20, 34);

If you have a lot of locations, you can limit the display to only list locations within a certain distance from the reference point, e.g. 100km:

// distance in kilometres
pointSorter.setDistanceThreshold(100)

if you are not going to use a table, you need to set the item template for listing items. The example below allows your items to be listed inside an unordered list (<ul>) tag:

pointSorter.setItemTemplate(
	"
  • " + " {description} ({distance_direct}km)" + "
  • " );

    February 2, 2010

    Enable AJAX on almost any site using jQuery

    Filed under: AJAX — joel.cass @ 11:47 am

    A website I was working on a while back had a requirement for an animated background to be running whilst users were on the website. The main problem was, that every time a user would click on a new page, the background would restart, interrupting the user experience and slowing things down in general. So what do we do? Inline frames? Create an AJAX service that returns the site content?

    No, the answer is even simpler. Enter AJAX using jQuery. All you need is to have two div elements with identifiers surrounding the main content of your page, for example:

    Example page structure

    Example page structure

    If you have the above structure and have the jQuery libraries loaded, then all you need to do is implement the following function:

    function loadPage(URL) {
        $("#content-outer").load(URL + " #content", null, ajax_loaded);
        return false;
    }

    JQuery uses css selectors to identify elements, e.g. <div id=”content”> = #content, so what this function does is it looks up the element #content-outer, executes the AJAX request, and then looks up the #content element from the next page and replaces the contents of the #content-outer element with the new element, as demonstrated in the below diagram.

    How requests are made using jQuery

    How requests are made using jQuery

    I have also added the following routine that will be called on startup. It effectively adds the loadPage(URL) method to each link’s onclick method. Because loadPage(URL) returns false, when the link is clicked, the AJAX call will be initialised and the user’s action will be cancelled.

    function check_on_load() {
        // update links
        updateLinks("A");
    }
    
    $(document).ready(check_on_load);
    
    function updateLinks(DOMLocation) {
        var links = $(DOMLocation);
        var baseURL = location.protocol + "//" + location.host + "/";
        for (var i = 0; i < links.length; i++) {
            var el = links[i]
            /* exclusions to the rule
             * - onclick must be null,
             * - url must contain the site's base url (http://site_domain)
             * - url cannot already have an anchor
             * - target must be empty
             * - classname must not contain 'no-ajax'
            */
            if (el.onclick == null &&
                    el.href.indexOf(baseURL) == 0 &&
                    el.href.indexOf("#") < 0 &&
                    el.target == "" &&
                    el.className.toLowerCase().indexOf("no-ajax") < 0) {
                el.onclick = Function("return loadPage(this.href);");
            }
        }
    }

    Furthermore, I have also added a callback to the method which will update the page title and replace any links within the new page content.

    function ajax_loaded(responseText, textStatus, XMLHttpRequest) {
        if (textStatus == "error") {
            prompt("URL Failed: ", XMLHttpRequest);
        } else {
            // set title
            document.title = titleFromHTML(responseText);
            // update links
            updateLinks("#content-outer A");
        }
    }
    
    function titleFromHTML(HTML) {
        var regex = new RegExp("");
        var matches = regex.exec(HTML);
        if (matches.length > 1) {
            return matches[1];
        } else {
            return "";
        }
    }

    ...And the best part? By simply including the script within your page along with jQuery (and making sure that your structure is correct), you will instantly enable AJAX-based loading on your page. Even better, if your browser does not fully support AJAX, javascript, or jQuery, all links will remain untouched and function just as they normally do.

    Download jQuery here: http://docs.jquery.com/Downloading_jQuery
    Download the script here: ajax-plugin_no_history.js
    Demo: demo_no_history.htm

    The only thing I have left out of this is history management. By using the script above, users lose the ability to switch back and forth between pages. By using the jQuery history plugin, you can give users the ability to switch back and forth between pages, and also the ability to bookmark AJAX'd pages.

    Download jQuery history plugin here: http://www.mikage.to/jquery/jquery_history.html
    Download the history enabled script here: ajax-plugin_with_history.js
    Demo: demo_with_history.htm