Geocoding Forms on Submit with Javascript
The Google Maps API offers an exceptional geocoding service. Its accurate, reliable and well documented. However, on shared hosting environments (such as Google's own AppEngine), rate-limiting can become an issue quickly. Fortunately, using the Javascript API (and jQuery), we can offload this task to the client where the number of requests from the current IP address is unlikely to become an issue. When implemented in conjunction with server-side geocoding, we can deliver a reasonably robust solution that's transparent to the user and gracefully degrades when Javascript is unavailable.
Let's start with a simple example. Let's assume we need to capture our user's names and addresses with the intention of plotting them on a map. Here's a simple form:
<form method="post">
<label>Name:</label> <input type="text" name="name">
<label>Street Address:</label> <input type="text" name="street_address">
<label>City:</label> <input type="text" name="state">
<label>State:</label> <input type="text" name="state">
<label>Postal Code:</label> <input type="text" name="postal_code">
<label>Country:</label> <input type="text" name="country">
<input type="submit" value="Submit">
</form>
For the sake of argument, let's also assume that when submitted, we've already implemented the logic necessary to geocode this address on the server before the data is saved. Now all is well and good until we discover that relying on server-side geocoding is not an option (and it wouldn't it be really nice if our form could supply the latitude and longitude values?)
This is where the Javascript API can help. What if, when the user submits this form, their address was quietly geocoded behind the scenes and the result was sent along with the form? If the server sees that the address has already been geocoded it can happily save the values without having to attempt the request itself. If not, the server-side geocoding machinery can step in and attempt to complete the process. First things first. Let's give our form a CSS class to identify it as a target for geocoding.
<form class="geocode" method="post">
<label>Name:</label> <input type="text" name="name">
<label>Street Address:</label> <input type="text" name="street_address">
<label>City:</label> <input type="text" name="city">
<label>State:</label> <input type="text" name="state">
<label>Postal Code:</label> <input type="text" name="postal_code">
<label>Country:</label> <input type="text" name="country">
<input type="submit" value="Submit">
</form>
Next we need to include jQuery and the Google Maps API Javascript library.
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="http://maps.google.com/maps/api/js?sensor=false"></script>
Now we can write our script.
<script>
var geocoder;
$(document).ready(function() {
geocoder = new google.maps.Geocoder();
$('form.geocode').submit(function(e) {
var that = this;
var addr;
var addrArray = [];
var addrFields = ['street_address', 'city', 'state', 'postal_code', 'country'];
$(addrFields).each(function(idx, name) {
var val = $(this).find('input[name="' + name + '"]').val();
if (val.length) {
addrArray.push(val);
}
});
if (addrArray.length) {
e.preventDefault();
$(that).unbind('submit');
var onSuccess = function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
result = results[0].geometry.location;
var point = result.lat() + ', ' + result.lng();
$(that).prepend('<input type="hidden" name="point" value="' + point + '">');
}
$(that).trigger('submit');
}
addr = addrArray.join(', ');
geocoder.geocode({'address': addr}, onSuccess);
}
});
});
</script>
Let's take a look at the important bits. First we declare a variable to hold our Geocoder object.
var geocoder;
After that, using jQuery, we assign a function to the submit event for all forms with the CSS class “Geocode”.
$('form.geocode').submit(function(e) {
...
In the next line, we save the current object (this) to the local variable (that) so that we can access it from within our callback function later via a closure.
var that = this;
The next few lines just build an array of address values (from our form) that we can assemble into a single string that the Geocoder will accept.
var addrArray = [];
var addrFields = ['street_address', 'city', 'state', 'postal_code', 'country'];
$(addrFields).each(function(idx, name) {
var val = $(this).find('input[name="' + name + '"]').val();
if (val.length) {
addrArray.push(val);
}
});
With that out of the way we first make sure we have something to geocode, and if so, call preventDefault to stop the form from being submitted.
e.preventDefault();
Next we remove the event handler from the form so that once geocoding is complete we can submit the form successfully without triggering our code again.
$(that).unbind('submit');
This next bit is the callback function we're going to pass to the Geocoder object that will be executed once our request is returned. This function first checks for a successful response, and if found, prepends a hidden field to our form containing the latitude and longitude information we received. Once the request has been returned, regardless of the result, we need to submit the form data by triggering the “submit” event on our form.
var onSuccess = function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
result = results[0].geometry.location;
var point = result.lat() + ', ' + result.lng();
$(that).prepend('<input type="hidden" name="point" value="' + point + '">');
}
$(that).trigger('submit');
}
Lastly, we assemble the address values into a single, comma separated string and pass this value and our callback function to the geocode method of our geocoder object.
addr = addrArray.join(', ');
geocoder.geocode({'address': addr}, onSuccess);
Now we can stop relying on the server to do all our geocoding. Of course, if our request fails (or the user has Javascript disabled), the server can still pick up the slack in most cases. If you have any questions, comments or corrections you can find me on Twitter.