Welcome!

Java Authors: Jeremy Geelan, Liz McMillan, Hari Gottipati, Tad Anderson, Yakov Fain

Related Topics: Java, AJAX & REA

Java: Article

The Year of the JavaScript-Only Web Services

"Google's Search API...couldn't be easier to use."

I am a developer rather than a writer or great thinker or futurist. But I think it's obvious that JavaScript APIs will give access to more and more data. I was critical of some of Google’s decisions in their AJAX Search API, but despite my nitpicking they are exposing their entire database and it couldn’t be easier to use. Obviously, they couldn’t send you their entire database, instead they send you a small amount of JS to get you started. Then when you ask for more, they happily send what you ask for.

But Google wants to make sure you aren’t greedy. By making the API JS it helps them protect their data. It seems counterintuitive at first. A typical REST or SOAP api is very easy to harvest, because once you have the client built you can just write a cheap little script and suck down all the data into a database to use and repurpose as you see nessessary. When the API is in JavaScript it is a little more tedious. When you have a web service you don’t usually mind giving away all the content, unless you want to serve ads with your content, like google does (and who can really blame them).

This is going to become more and more common among companies who value sharing data, but survive on maintaining and displaingy that data. In five minutes of searching, I found 10+ examples of this technique (primarily in Google and Yahoo APIs). The ease of use, the general usefulness and pseudo protection of data all makes this technique a bit of a no-brainer. But how easy is it for them to implement? Extremely! Keep reading to see my solution to this problem.

I run a site called DCRails. It is a Google Maps mashup with the Washington DC Metrorail stops plotted out. One thing I frequently find myself needing is, which stop is the closest and the distance to that stop from a given location. I am happy to make this data freely available to the public, even though I don’t know how useful it will be any one else. Here is how it works.

Include this in your head tag

<script src="http://dcrails.com/api/closest_metro_js" 
    type="text/javascript" charset="utf-8"></script>

That is the API, to use it, it is a one liner.

new DCRails.MetroSearcher(lat,lng,callback);

Obviously, the lat is the latitude, the lng is the longitude and the callback is the function you want called when the MetroSearcher gathers the data.

That is all fine and dandy, but how does it work?

Well in my implementation it all comes down to tight coupling between the client-side and the server-side. Usually this is considered a no-no, but in this case you control both so if a better solution presents itself in the future you are free to change it. I will show you the entire API in two parts, and explain how it works.

if(!JsOnDemand) {
    var JsOnDemand = {};
    JsOnDemand.callbacks = {};
    JsOnDemand.num = 0;
    JsOnDemand.Get = function(url, callback) {
        this.callback = callback;
        var id = JsOnDemand.num++;
        JsOnDemand.callbacks[id] = this;
        var script = document.createElement('script');
        script.type = 'text/javascript';
        var t = new Date();
        var cacheDefeater = '' + t.getYear() + 
            t.getMonth() + 
            t.getDay() + 
            t.getHours() + 
            t.getMinutes() + 
            t.getSeconds() + 
            id;
        script.src = 'http://dcrails.com' + 
            url + "&JsOnDemandNum=" + id + 
            '&__no_cache=' + cacheDefeater;
        document.getElementsByTagName('head')[0].appendChild(script);
    }
}

That is the first object, it essentially makes a dynamic script tag of the url and stores the callback function.

if(!DCRails) var DCRails = {};
DCRails.MetroSearcher = function(lat, lng, callback) {
    new JsOnDemand.Get("/map/closest_stop?lat=" + 
        lat + "&lng=" + lng, callback)
}

That is the definition of the DCRails.MetroSearcher object. It provides the user a nice callable function that takes in the exact parameters it requires and hopefully the user has. You can see it calls JsOnDemand.Get from the first part.

The trick to making this work is what the server-side returns. Remember that since we are requesting a script tag, whatever gets returned needs to be run-able JavaScript. It also needs to call the function we specified. My solution isn’t the most elegant, but it works flawlessly. Here is what the server returns.

eval(JsOnDemand.callbacks[0].callback(
        {"closest_metro_stop":{
            "name":"McPherson Square",
            "id":16},
        "closest_metro_exit":{
            "latitude":38.9005,
            "longitude":-77.0322,
            "name":"Franklin Square, 14th st.",
            "id":24},
        "distance":0.189})); 
delete JsOnDemand.callbacks[0];

It looks in the JsOnDemand.callbacks hash for the first (0) callback because this is the first request on this web page. It knows to do this because the JsOnDemand object adds a query string parameter called JsOnDemandNum. The server must look for the parameter and uses it to construct the js. You end up with the afore mentioned response. The last thing the response does is clean up the reference to the callback that was used.

I think this is a valuable technique to understand if you are a client-side developer. If you understand how it works, you can get a feel for the limitations of using this approach.

I have tested this on several browsers (IE 6, IE 7, Firefox 2, Safari 2, and Opera 9) with no problems or limitations.

As a quick aside to any RoR users out there. Watch out for my plugin that makes this all work magically. Expected release date sooner than Feb. 5 2007. I need to understand the new REST stuff in rails and try to integrate where I see fit.

More Stories By Nicholas Schlueter

Nicholas Schlueter runs DCRails (www.dcrails.com) and blogs at www.simpltry.com about JavaScript, Prototype, and why you shouldn't fear the coming revolution.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.