advertisement

Print

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

Accessing the Server Using Ajax and XML

The first way I'm going to access the server is using Ajax calls to the XML view. To start off, I'll request the XML data from the server on the command line using curl. This is shown in Listing 4.

Listing 4. The XML Response
% curl  "http://0.0.0.0:3000/photos/xml"
<?xml  version="1.0" encoding="UTF-8"?>
  <photos>
    <photo>
      <description>Megan after winning 2nd  place in the pumpkin race.</description>
      <id  type="integer">1</id>
      <latitude  type="float">37.591753</latitude>
      <longitude  type="float">-122.048358</longitude>
      <title>Megan At The  Races</title>
      <url>http://0.0.0.0:3000/megan1.jpg</url>
     </photo>
 ...

The output of 'to_xml' is remarkably well formatted. There is a 'photos' tag that contains a set of 'photo' tags, each of which has sub-tags for the different attributes. Perfect.

To make use of that, I'll mash the data up with a Google Map. To start with I'll go to the Google Maps API page and request an API key. Then I'll write the view code shown in Listing 5.

Listing 5. The Ajax/XML map implementation
 <html>
 <head>
 <title>Map Ajax Example</title>
 <%= javascript_include_tag 'prototype'  %>
 <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=<Maps  Key>"
 type="text/javascript"></script>
 <script  type="text/javascript">
 function buildMarker(  title, url, description, latitude, longitude ) {
 var marker = new  GMarker(new GLatLng(latitude, longitude),
 { title: title,  clickable: true });

GEvent.addListener(marker,  "click", function() {
   var html =  '<strong>'+title+'</strong><br/><img  height="200" src="'+url+'" />';
   marker.openInfoWindowHtml( html );
   });

return marker;
   }

function load() {
   if (GBrowserIsCompatible()) {
   var map = new  GMap2(document.getElementById("map"));

    new Ajax.Request( '/photos/xml', {
   method: 'get',
   onSuccess: function(  transport ) {
   var photoTags =  transport.responseXML.getElementsByTagName( 'photo' );

for( var b = 0; b <  photoTags.length; b++ ) {
   var title =  photoTags[b].getElementsByTagName('title')[0].firstChild.nodeValue;
   var url =  photoTags[b].getElementsByTagName('url')[0].firstChild.nodeValue;
   var description = photoTags[b].getElementsByTagName('description')[0].firstChild.nodeValue;
   var latitude = parseFloat(  photoTags[b].getElementsByTagName('latitude')[0].firstChild.nodeValue );
   var longitude = parseFloat(  photoTags[b].getElementsByTagName('longitude')[0].firstChild.nodeValue );

 if ( b == 0 )
   map.setCenter(new GLatLng(latitude,  longitude), 13);
   map.addOverlay( buildMarker( title, url,  description, latitude, longitude ) );
   } } } );
   }
   }
   </script>
   </head>
   <body onload="load()"  onunload="GUnload()">
   <div id="map"  style="width: 95%; height: 95%"></div>
   </body>
   </html>

The first couple of lines at the top are pretty important. The 'javascript_include_tag' invocation includes the Prototype library into the page. I'll use the Ajax.Request function from the Prototype library to get the XML from the server. The next script tag is the Google Maps script tag, which brings in the map objects.

The two functions that are specified after this are 'buildMarker' and 'load'. The 'load' function creates the map and starts the Ajax request. The 'buildMarker' function is used by the Ajax request handler to add markers to the map for each of the images. These markers will then pop up an information window with the image when you click on them.

The completed page is shown in Figure 4.

figure 4
Figure 4. The map mashup

Here I have clicked on the picture of my daughter that I took at her first race. She came in second place! A natural born runner.

Now, back to the code for a second, if you take a look at the code in the function within Ajax.Request you can see where I break up the XML and get out the various fields for each marker. It's a pain to do that sort of stuff. It would be far easier if the server just returned some JSON data that I could convert into an array quickly.

The JSON Version of the Mapper

Thankfully, we have a controller method that will return JSON. I'll test it once again using curl on the command line. The output is in Listing 6.

Listing 6. The JSON output
 % curl  "http://0.0.0.0:3000/photos/json"
 [{attributes: {latitude:  "37.591753", title: "Megan At The Races", url:  "http://0.0.0.0:3000/megan1.jpg", id: "1", description:  "Megan after winning 2nd place in the pumpkin race.", longitude:  "-122.048358"}}, ...

Very cool. Now I can change just the load function from the previous example to use JSON instead of XML. This change is shown in Listing 7.

Listing 7. The JSON loader
 function load() {
 if (GBrowserIsCompatible()) {
 var map = new  GMap2(document.getElementById("map"));

    new Ajax.Request( '/photos/json', {
   method: 'get',
   onSuccess: function(  transport ) {
   var photos = eval(  transport.responseText );

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 )  );
   } } } );
   }
   }

All right, now this is much easier. Instead of doing all of the XML stuff I just 'eval' the responseText and that gives me back an array of photos which I can then iterate to create markers.

Another way to do this is to have the returned JavaScript call a function directly. I do that by using the 'jscallback' method on the controller. You can see the output of that in Listing 8.

Listing 8. The JavaScript callback output
 % curl  "http://0.0.0.0:3000/photos/jscallback"
 photos_callback([{attributes:  {latitude: "37.591753", title: "Megan At The Races", ...

Now my 'load' function becomes really tiny because the bulk of the photo handling code moves into a new function called 'photos_callback'. This new version of the code is shown in Listing 9.

Listing 9. The Photos Callback
 var map = null;

function  photos_callback( 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 ) );
   }
   }

function load() {
   if (GBrowserIsCompatible()) {
   map = new  GMap2(document.getElementById("map"));

    new Ajax.Request( '/photos/jscallback', {
   method: 'get',
   onSuccess: function( transport ) {  eval( transport.responseText ); }
   } );
   }
   }

Now comes the fun part. What if I didn't want to use the Ajax.Request method at all? What if I couldn't use the Ajax.Request because the data that I wanted is on a server in a different domain? Well, it turns out there is a solution for that. Something that pre-dates Ajax altogether

Pages: 1, 2, 3

Next Pagearrow