Sunday, March 20, 2011

FIX: jQuery JSONP request to the Google Maps API Geo Service

A lot of you guys (including me) had a problem with the Maps Geocoding API. Google seems to have dropped the JSONP feature of their library. Well they still support it (otherwise their own client library would not work) and here's a fix.

Note: this applies to v2 only.

Table of Contents



Introduction


Hi

Remember, some of us had a bug with Google's Geo API and JSONP? Well the main reason (according to me) is that they don't want you to use the service directly nor using it with a third party library. They want you to use it via their JavaScript API. But sometimes people must call the service in a RESTful way (With jQuery plugins for example). Talking about the popular jQuery, the "bug" is especially present in this library:

Reproducing the problem


Here's the scenario to reproduce the bug (click on the button, you should receive a status code 610):

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
    // <![CDATA[
    $('#buttonTest1').click(function () {
        $.ajax({
            params: { q: 'montreal', output: 'json', v: 2, sensor: false, key: 'ABQIAAAAzAkrOIsv60fbtV-5UlnnJxRUrcFJRfPs1DYZSl5DdA7w5qtvARRv6HblVCIt_AgTSnG3PdPypHTBEQ' },
            url: 'http://maps.google.com/maps/geo',
            dataType: 'jsonp',
            cache: false,
            success: function (data) {
                alert('Status code: ' + data.Status.code);
            }
        });
    });
    // ]]>
</script>



What's wrong? Clicking on the button will produce this request: http://maps.google.com/maps/geo?callback=jQuery15102910673765909235_1300669342297&q=montreal&output=json&v=2&sensor=false&key=ABQIAAAAzAkrOIsv60fbtV-5UlnnJxRUrcFJRfPs1DYZSl5DdA7w5qtvARRv6HblVCIt_AgTSnG3PdPypHTBEQ&_=1300669368614

A quick fix


Now I'll show you a fix. Let's see if you're able to spot the difference (again, click on the button):

<script type="text/javascript">
    // <![CDATA[
    $('#buttonTest2').click(function () {
        $.ajax({
            url: 'http://maps.google.com/maps/geo?q=montreal&output=json&v=2&sensor=false&key=ABQIAAAAzAkrOIsv60fbtV-5UlnnJxRUrcFJRfPs1DYZSl5DdA7w5qtvARRv6HblVCIt_AgTSnG3PdPypHTBEQ',
            dataType: 'jsonp',
            cache: false,
            success: function (data) {
                alert('Status code: ' + data.Status.code);
            }
        });
    });
    // ]]>
</script>



Again, this will produce the following request: http://maps.google.com/maps/geo?q=montreal&output=json&v=2&sensor=false&key=ABQIAAAAzAkrOIsv60fbtV-5UlnnJxRUrcFJRfPs1DYZSl5DdA7w5qtvARRv6HblVCIt_AgTSnG3PdPypHTBEQ&callback=jQuery15108036324891551678_1300669681039&_=1300669709521

The main difference is that the callback parameter is almost at the end of the query. It seems that the callback parameter must be after some other key parameters?

So you have a fix. You must serialize yourself the parameters at the end of the url. jQuery will then append its own parameters after yours. It seems to do the trick. But thats not handy: sometimes plugins are only accepting URLs to do their queries (they let jQuery handle the parameters). So your stuck again :(

A more permanent and transparent fix


If you want a more transparent fix, I hacked the jQuery 1.5.1 library. Just replace what's in bold here (in the minified version):

[...]a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange[...]

With this JavaScript snippet (sorry for the mess):

[...]a.scriptCharset),d.src=/*fix*/a.url.match(/\?callback=[a-zA-Z0-9_-]+&/) == null ? a.url : a.url.replace(a.url.match(/\?callback=[a-zA-Z0-9_-]+&/), '?') + '&' + a.url.match(/\?callback=[a-zA-Z0-9_-]+&/)[0].substring(1, a.url.match(/\?callback=[a-zA-Z0-9_-]+&/)[0].length - 1)/*fix*/,d.onload=d.onreadystatechange[...]

What is does? Just before sending any ajax request, the callback parameter will be pushed at the end of the URL (if there's a callback parameter). You can then use jQuery as usual with the Geocoding Maps API in a more transparent way.

Conclusion


You must realize that you must read the terms and the agreements of the Google Maps API libraries prior to using it. My blog post was for demonstration purposes only!

I'm putting time and effort on this blog and having you here is my reward. Make me feel better and better everyday by spreading the love with the buttons below! Also, don't hesitate to leave a comment. Thanks for reading!

Related Articles



See ya

3 comments:

Anonymous Avatar Anonymous said...

What's interesting about this is that v3 seems to work fine with json and jsonp, with jquery, for the geocodes. I am getting an HTTP 200 code from the Fiddler HTTP Proxy; however, I am still not able to do anything with the JSON/JSONP data in the "success" portion. I see that the Google GeoCode API returned the JSON/JSONP object successfully; status=OK and I can visually see the object iself from the Fiddler Proxy. So, I know it works. But why on Earth won't my code allow me to process the JSON/JSONP data? I believe I understand the concept of cross-domain ajax, same site policy, but if that were the case, that json is not working because of cross-domain access, then why would my HTTP Proxy return an HTTP code 200 and Status OK with the complete JSON/JSONP object? I mean, I can visually inspect every bit of data in the object from within the HTTP Proxy, just not in my code. I am using FireFox 4, in this case, as well.

Here is how I am calling it:

$.getJSON("http://maps.googleapis.com/maps/api/geocode/json", { address: "90210", sensor: false }, function(data) {
alert("JSON Data: " + data);
});

I would appreciate any help. Thank you.

Hi Mike,

Need help problem Google map json response

var url='http://maps.googleapis.com/maps/api/geocode/json?latlng=13.049082,80.233496&output=json&v=2&sensor=false&key=ABQIAAAAAFtdHQuUtN66POQgIG9AghQVWYqFFJ5lOubwFLAo8VUMXJKaZhTNUYtH9p1k3JD5X5eUWXGKkWrylg';
$('#button').click(function ()
{
$.ajax({
url: url,
dataType: 'jsonp',
cache: false,
success: function (data)
{
alert('Status: ' + data);
}
});
});

hari said...

Hi Mike,
I am unable to display all the results requested from the Google Places Api..i used pagenation and getting 60 results but only can display 20..and 40 using settimeoutfuntion()...getting the following error: Uncaught TypeError: Cannot read property 'length' of null
Any clue?

©2009-2011 Mike Gleason jr Couturier All Rights Reserved