Merchants that sell both online and from physical stores will no doubt want to list physical locations on their websites.
In this tutorial series, I will demonstrate how to create a store locator using version 3 of the Google Maps JavaScript API, PHP, lots of JavaScript, and a MySQL database.
In Part 1, I wrote the HTML and CSS for my locator form; created a MySQL database; populated that database with geocoded store locations; connected to the database from a PHP script; and wrote an SQL query to return a list of locations based on their respective distance from the location that the user submitted.

In this post, I am going to format the query results as XML, which can be passed asynchronously to my JavaScript (think Ajax).
As the subject of my application, I have been using Dutch Bros., an Idaho-based chain of coffee houses that also sells merchandise online.
Step No. 6: Create the XML
In Step No. 5 (in Part 1), I created a query that uses the Haversine formula to calculate great-circle distances (thank you Pamela Fox). The results of that query were captured in a variable $results.
$query = "SELECT `store_name`,`street_number`,`city`,`state`,`zip`,`latitude`,`longitude`, ( 3959 * acos( cos( radians($user_lat) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians($user_long) ) + sin( radians($user_lat) ) * sin( radians( latitude ) ) ) ) AS distance FROM dutch_bros_locations ORDER BY distance";
$result = mysqli_query($dbc, $query)
or die('Error query the database');
The results that the database returns are in rows, with data for each field. Below is a screen capture from phpMyAdmin, which serves to illustrate how my results are organized.

I want to convert each row into an XML node and each associated field into an attribute of that node. For example, in the image above the first row will become an XML node called "store." The storename field and streetnumber field will each be attributes of the "store" node. Each row represents a new node.
To begin, I create the XML header, placing the following line of code before the statement that closes my database.
header("Content-type:text/xml");
$dom = new DOMDocument("1.0");
$node = $dom->createElement("stores");
$parnode = $dom->appendChild($node);
Next, I create a while loop that will store each row's information in a variable called $row. This loop will go through each line of the results, carry out the described actions, and quit when it runs out of rows.
The built-in mysqlifetchassoc() will retrieve the results for me. Notice that I need to pass the $results variable.
while ($row = @mysqli_fetch_assoc($result)){
//actions go here
}
As each row is processed, I will create a node called "store," adding the code to my while loop. It is helpful that PHP and JavaScript are very similar in their approach to creating elements, which is great for someone like me that is more comfortable on the client side.
while ($row = @mysqli_fetch_assoc($result)){
$node = $dom->createElement("store");
$newnode = $parnode->appendChild($node);
}
For each field of each row, I need to create an attribute.
while ($row = @mysqli_fetch_assoc($result)){
$node = $dom->createElement("store");
$newnode = $parnode->appendChild($node);
$newnode->setAttribute("store_name", $row['store_name']);
$newnode->setAttribute("street", $row['street_number']);
$newnode->setAttribute("city", $row['city']);
$newnode->setAttribute("state", $row['state']);
$newnode->setAttribute("zip", $row['zip']);
$newnode->setAttribute("latitude", $row['latitude']);
$newnode->setAttribute("longitude", $row['longitude']);
$newnode->setAttribute("distance", $row['distance']);
}
You should notice that for each setAttribute(), I passed the name I wanted to assign to the attribute and the field name as $row['street'], for example.
Generally, I labeled my attributes to match the field name, but this is not required. Note that "street" and "street_number" differ.
I also want to point out that "distance" is not a "regular" field in the database. Rather, my query actually produced it with the SQL statement "AS distance."
Now, it is time to echo the XML.
echo $dom->saveXML();
Here is the complete PHP script, which I have saved in a file called, dutch-bros-form.php.
<?php
include 'connect.php';
$user_lat = mysqli_real_escape_string($dbc, $_GET['latitude']);
$user_long = mysqli_real_escape_string($dbc, $_GET['longitude']);
If(empty($user_lat) || empty($user_long)){
$user_lat = 43.644257;
$user_long = -116.271225;
}
$query = "SELECT `store_name`,`street_number`,`city`,`state`,`zip`,`latitude`,`longitude`, ( 3959 * acos( cos( radians($user_lat) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians($user_long) ) + sin( radians($user_lat) ) * sin( radians( latitude ) ) ) ) AS distance FROM dutch_bros_locations ORDER BY distance";
$result = mysqli_query($dbc, $query)
or die('Error query the database');
header("Content-type:text/xml");
while ($row = @mysqli_fetch_assoc($result)){
$node = $dom->createElement("store");
$newnode = $parnode->appendChild($node);
$newnode->setAttribute("store_name", $row['store_name']);
$newnode->setAttribute("street", $row['street_number']);
$newnode->setAttribute("city", $row['city']);
$newnode->setAttribute("state", $row['state']);
$newnode->setAttribute("zip", $row['zip']);
$newnode->setAttribute("latitude", $row['latitude']);
$newnode->setAttribute("longitude", $row['longitude']);
$newnode->setAttribute("distance", $row['distance']);
}
echo $dom->saveXML();
mysqli_close($dbc);
?>
Step No. 7: Add a JavaScript Event Listener
With the PHP portion of my store locator complete, I turn my attention to the JavaScript, which is really the heart of the application.
At the moment, when a user fills out the "Where Are You" form and clicks "Go Dutch," the page simply reloads.
To change this I am going to create a JavaScript file and add an event listener. To match the script declaration that I used in my HTML, I am naming this file js.js.
document.onclick = manageClick;
function manageClick(e){
if(!e) var e = window.event;
var target = e.target || e.srcElement;
switch(target.id){
case "go-dutch" :
e.precentDefault();
nearStepOne();
break;
}
}
This event listener starts by watching, generally, for click events. In my example, there is only one clickable item, so I could have assigned the listener directly to it. But as a best practice, I always use event delegation. That is, I create one listener and then handle all possible outcomes with a function. In this case that function is manageClick.
document.onclick = manageClick;
Next, my click handling function must compensate for differences between Microsoft's Internet Explorer (IE) and all other browsers.
if(!e) var e = window.event; var target = e.target || e.srcElement;
The function receives the click event from the listener, and stores it as e. For most browsers this is enough. But IE has different syntax, so my conditional checks to learn if the browser recognizes the event. If not, it sets the variable to support IE's syntax, window.event.
The variable target also works across browsers.
The switch allows me to describe different actions depending on the id of the event target—the element that was clicked. Nicholas C. Zakas, who is a world-class JavaScript expert, describes this technique in some detail on this blog.
switch(target.id){
case "go-dutch" :
e.preventDefault();
nearStepOne();
break;
}
If the click originated on an element with the id of "go-dutch," the JavaScript will take three actions.
First, it will use the method, preventDefault. This will stop the click from working in its normal way, which would reload the page. Since I am going to be using Ajax to display and manipulate the resulting list of locations and the map, I don't want the page to reload. (I am going to modify this in a later step,sometimes change is good. Just watch for it.)
Next, the JavaScript will execute a function called nearStepOne(). This function doesn't exist yet (I will create it in Part 3). So running this code right now would result in an error.
Finally, the JavaScript will stop or break.
I need to make one minor change to my HTML. In my JavaScript, I am capturing the target by its id. At the moment, my form's submission button, doesn't have an id. So I will add it.
<button type="submit" id="go-dutch">Go Dutch</button>
Summing Up This Installment
In this tutorial, I (1) described in detail how to take the results of a SQL query and output them as XML, and (2) added an event listener to my JavaScript file.
In Part 3, I will capture the user-submitted address, initialize the Google Maps API, and geocode the user-generated location so that it can be sent to my PHP script.
Related Articles
- Develop a Store Finder with the Google Maps API, Part 1
- Working with Google's New Font API
- Good APIs Make or Break Plugin Opportunities
