advertisement

Print

Building Mashup-Friendly Sites in Rails
Pages: 1, 2, 3

Using Dynamic Script Tags

The solution is to dynamically add a script tag to the DOM that has the URL of the service as the 'src' of the tag. The only thing I need to change is the 'load' code and that's shown in Listing 10.

Listing 10. Using a script tag instead of Ajax
 function load() {
 if (GBrowserIsCompatible()) {
 map = new  GMap2(document.getElementById("map"));

    var elScript =  document.createElement('script');
   elScript.src =  'http://0.0.0.0:3000/photos/jscallback';
   document.body.appendChild( elScript );
   }
   }

Here is the trick, Ajax can only access servers located in the same domain. But 'script' tags can point anywhere. So I can now put this code on any page in any domain and it will still work (given that the URL is something it can get to and not 0.0.0.0:3000).

But I don't really want my users having to know all of this script tag creation stuff. So I am going to, like Google, hide all of the code in a library to make it easier for people to use the code. This simple library is shown in Listing 11.

Listing 11. The photo library
 var g_photos_cb = [];

function  photos_callback( photos ) {
   while( g_photos_cb.length > 0 ) {
   var cb = g_photos_cb.pop();
   cb( photos );
   }
   }
   function photos_get( cb  ) {
   g_photos_cb.push( cb );
   var elScript =  document.createElement('script');
   elScript.src =  'http://0.0.0.0:3000/photos/jscallback';
   document.body.appendChild( elScript );
   }

The user of the library calls 'photos_get' and gives it a function that will take the photos data and do something with it. The method is pretty simple; it just takes the callback and adds it to an array of callbacks, then does the same script tag creation as I did before. The callback then invokes the different user-defined callbacks with the photos data.

To use it, I will change the load method one last time and remove the original photos_callback from the code. This updated code is shown in Listing 12.

Listing 12. The photo library in use
 function load() {
 if (GBrowserIsCompatible()) {
 map = new GMap2(document.getElementById("map"));
 
 photos_get( function( photos ) {
 for( var b = 0; b < photos.length; b++  ) {

       if ( b == 0 )
   map.setCenter(new GLatLng(  photos[b].attributes.latitude,
   photos[b].attributes.longitude ),  13);

       map.addOverlay( buildMarker(  photos[b].attributes.title, 
   photos[b].attributes.url,  photos[b].attributes.description,
   photos[b].attributes.latitude,  photos[b].attributes.longitude ) );
   }
   } );
   }
   }

Now there is one problem with this library. Internet Explorer has a nasty habit of blowing up on pages where you add tags to the body tag before the page has been completely loaded. To avoid that I'll change the code a little bit to harden it up for IE. This final version of the mashup friendly photos library is shown in Listing 13.

Listing 13. A hardened photos library
 var g_photos_cb = [];
 var g_photos_loaded =  false;

window.addEventListener(  'load', photos_onload );

function photos_onload()  {
   photos_process_requests();
   g_photos_loaded = true;
   }

function  photos_process_requests() {
   if ( g_photos_cb.length > 0 )
   {
   var elScript =  document.createElement('script');
   elScript.src =  'http://0.0.0.0:3000/photos/jscallback?d='+(new Date().valueOf());
   document.body.appendChild( elScript );
   }
   }

function  photos_callback( photos ) {
   while( g_photos_cb.length > 0 ) {
   var cb = g_photos_cb.pop();
   cb( photos );
   }
   }

function photos_get( cb  ) {
   g_photos_cb.push( cb );
   if ( g_photos_loaded ) photos_process_requests();
   }

The code latches on to the 'onload' message on the window. Then it adds script tags only once the onload message has been sent. If the onload message has already been sent, then it just sends the request right on to the server.

This completed photos library is a very mashup-friendly web services interface. Anyone with Notepad and a browser can now write JavaScript code that can access data on the Rails server from anywhere in the world.

Some Tips and Suggestion

I've written a couple of these interfaces, so I have a few things I'd like to recommend to save you a little pain. The first is to write your interface to basic Dynamic HTML without using any libraries like Prototype, Dojo, Yahoo! UI, or any of the others. Those libraries can be incompatible with each other and you don't want to exclude anyone from using your interface.

The other important thing is to figure out whether or not you even need this type of interface. This type of API is ideal when your target customers are bloggers, HTML or JavaScript coders, or folks who may not control their server environment. It's not a good general purpose API to replace conventional SOAP, XML/RPC, REST, or JSON APIs. Those APIs are far better when your customers are writing server code.

Another thing to keep in mind is that this interface is good for creating read-only APIs. It's not particularly easy to write writeable APIs as 'script' tags only GET, they don't POST. Nor is it particularly straightforward to authenticate using 'script' tags, though it is possible.

Conclusions

Hopefully the example I've walked you through here has shown you how to build an API that works well for getting data to page-level coders for mashups. If it inspires you to build a mashup-friendly API, let me know by posting to the comments section at the end of the article. I'd love to see what you come up with, and you may just get a few more people using your API.

Jack Herrington is an engineer, author and presenter who lives and works in the Bay Area. His mission is to expose his fellow engineers to new technologies. That covers a broad spectrum, from demonstrating programs that write other programs in the book Code Generation in Action. Providing techniques for building customer centered web sites in PHP Hacks. All the way writing a how-to on audio blogging called Podcasting Hacks.


Return to the O'Reilly Ruby.