Friday, January 06, 2006

XML and the Dynamic Script Tag: Easy, XML Web Services for JavaScript

JSON has caught my eye recently, it has apparently been around since 2002 but with its recent association with AJAX it has come to the fore again. Also there have been a number of high profile sightings of it on the web recently.

Yahoo's Jason talks JSON...

Jason Levitt has recently published some very interesting articles concerning AJAX and JSON on XML.com. I have mild concerns about what in retrospect appears to be a very biased perspective and as a result I feel these articles suffer a little by only telling half the story.

In the first article, Fixing AJAX: XMLHttpRequest Considered Harmful, he rightly pointed out some of the shortcomings of the AJAX approach, the need for proxies to load remote XML sources and he proposes a number of solutions including something called On-Demand Javascript.

In his more recent article, JSON and the Dynamic Script Tag: Easy, XML-less Web Services for JavaScript, Jason talks about using JSON as an alternative approach to XML in AJAX style applications. As an example Jason uses Yahoo's Geocode web service. He also suggests that this is achieved with JSON and via something he now calls the dynamic script tag approach. In his XML.com profile Jason is described as a Technical Evangelist for the Yahoo Developer Network. So it is only right and proper that he should be promoting Yahoo's technical agenda.

What I personally found slightly misleading is the second article, it implied that JSON was easier than XML and I'm not convinced about that (I'm not alone: JSON not so great..., E4X and JSON). Okay, I'm being facetious but why was this published on XML.com instead of on JSON.com?! ;). Another issue I had with the second article was that JSON was being touted as part of the magic recipe underlying the dynamic script tag approach. One point that was omitted was that the JSON as a web service output format is currently pretty much only available via the Yahoo web services family (which thanks to Yahoo's recent acquisition also includes the del.icio.us API).

I'm not saying JSON is bad, just that I can't quite see the reason why it is easier than XML (but then I enjoy XSL programming so I would say that!). If you think that JSON is much easier than XML then I say go right ahead and use it. The article fails to mention that for security reasons you should really use the JSON parser in your JSON script to avoid cross scripting attack issues. Adding the JSON parser has the added disadvantage of making the JSON approach somewhat slower (one of the arguments I've seen for using JSON over XML is speed). AJAX XMLHttpRequest based methods also usually include some request exception handling (for 404 errors and such) which Yahoo's jsr_class.js does not include. The ability to use XPath is another benefit of the XML format approach, I'm not entirely sure how I would traverse a JSON object of un-predetermined structure. So I would conclude that JSON is only useful if you have a very good idea of the nature of the object you are getting returned.

To my eye, the dynamic script tag approach is a variant of the On-Demand Javascript approach. Clever though the dynamic script tag approach is, it doesn't really need to include JSON at all. Indeed the javascript JSONscriptRequest class can be reused quite easily and comfortably with other data formats (including XML, shock, horror).

Dynamic Script Tag Approach using XML

The following three examples will illustrate how you could use the the dynamic script tag approach in other ways; using the browser's built in XML parser (cross browser compatible), using a third party javascript XML parsing class library (XML for <Script>) (cross browser compatible) and finally using E4X (standard compliant browser compatible ;)). At least armed with a more complete picture you will be able to make a more informed comparison of the techniques so you can decide which is easiest for yourself.

Firstly I slightly modified the JSONscriptRequest class javascript to remove the noCacheIE related code. For these examples I am not actually implementing the callback mechanism that Yahoo uses, in fact for these purposes each file in the following example use a static file so I don't need the noCacheIE functionality code inside the library function. In fact including the noCacheIE stops the scripts working but since the Yahoo code has no request exception handling capability it is not immediately obviously why the scripts are not working unless you dig a little deeper, take my word for it, for these examples you don't need the noCacheIE stuff.

For the first two examples our "remote web service" URL is a static file containing (or click here):


getGeo(
"<ResultSet>" +
"<Result precision=\"zip\">" +
"<Latitude>37.7668</Latitude>" +
"<Longitude>-122.3959</Longitude>" +
"<Address></Address> "+
"<City>SAN FRANCISCO</City> "+
"<State>CA</State>" +
"<Zip>94107</Zip>" +
"<Country>US</Country>" +
"</Result>" +
"</ResultSet>");

Example 1 - Using the built in XML parsing capabilities of the browser (cross browser).

There are cross browser issues for this but they are no different to the issues which face users of Google Maps API. Indeed this will probably be familiar to people who have experimented with Google Maps API. The Javascript object being returned via the callback function in this example is an XML string.


<html>
<body>
<!-- Include the JSONscriptRequest class -->
<script type="text/javascript" src="jsr_class.js"> </script>
<script type="text/javascript">

// Define the callback function
function getGeo(xmlString) {

if (document.implementation.createDocument){
// Mozilla, create a new DOMParser
var parser = new DOMParser();
myDocument = parser.parseFromString(xmlString, "text/xml");
} else if (window.ActiveXObject){
// Internet Explorer, create a new XML document using ActiveX
// and use loadXML as a DOM parser.
myDocument = new ActiveXObject("Microsoft.XMLDOM")
myDocument.async="false";
myDocument.loadXML(xmlString);
}

var docRoot = myDocument.documentElement;

//get the first "Latitude" element
var latitude = docRoot.getElementsByTagName("Latitude")[0].firstChild.data;

//get the first "Logitude" element
var longitude = docRoot.getElementsByTagName("Longitude")[0].firstChild.data;

alert('Latitude = ' + latitude + ' Longitude = ' + longitude);

bObj.removeScriptTag();
}

// The web service call
var req = 'noScriptObject.js';

// Create a new request object
bObj = new JSONscriptRequest(req);
// Build the dynamic script tag
bObj.buildScriptTag();
// Add the script tag to the page
bObj.addScriptTag();

</script>
</body>
</html>

See Example 1 in action here.

With all the browser specific code going on this example is a bit yuck, but I would contend that it is hardly horrific.

Example 2 - Using a third party XML parsing javascript library [XML for <Script>] (cross browser).

Again the JavaScript object returned via the callback method is an XML string. Although this time we are using the same DOM parsing code for both browsers with no apparent browser specific code (at least on the surface of things).


<html>
<body>

<!-- Include the XML Script javascript classes -->
<script type="text/javascript" src="tinyxmlw3cdom.js"> </script>
<script type="text/javascript" src="tinyxmlsax.js"> </script>

<!-- Include the JSONscriptRequest class -->
<script type="text/javascript" src="jsr_class.js"> </script>
<script type="text/javascript">

// Define the callback function
function getGeo(xml) {

//instantiate the W3C DOM Parser
var parser = new DOMImplementation();

//load the XML into the parser and get the DOMDocument
var domDoc = parser.loadXML(xml);

//get the root node (in this case, it is ResultSet)
var docRoot = domDoc.getDocumentElement();

//get the first "Latitude" element
var latitude = docRoot.getElementsByTagName("Latitude").item(0);

//get the first "Logitude" element
var longitude = docRoot.getElementsByTagName("Longitude").item(0);

alert('Latitude = ' + latitude.getFirstChild().getNodeValue() + ' Longitude = ' + longitude.getFirstChild().getNodeValue());
bObj.removeScriptTag();
}


// The web service call
var req = 'xmlScriptObject.js';

// Create a new request object
bObj = new JSONscriptRequest(req);
// Build the dynamic script tag
bObj.buildScriptTag();
// Add the script tag to the page
bObj.addScriptTag();

</script>
</body>
</html>

See Example 2 in action here.

Example 3 - Using E4X

This example will only work in E4X supporting browsers (at the time of writing Firefox 1.5 and Mozilla 1.8). This time we pass a new type of JavaScript object in the callback method, namely the XML type introduced by E4X (see here).


getGeo(new XML(
"<ResultSet>" +
"<Result precision=\"zip\">" +
"<Latitude>37.7668</Latitude>" +
"<Longitude>-122.3959</Longitude>" +
"<City>SAN FRANCISCO</City> "+
"<State>CA</State>" +
"<Zip>94107</Zip>" +
"<Country>US</Country>" +
"</Result>" +
"</ResultSet>"));

Although this is not currently cross browser compatible, it could be argued that this approach is even simpler than the JSON approach illustrated in the Jason's JSON article.


<html>
<body>
<!-- Include the JSONscriptRequest class -->
<script type="text/javascript;e4x=1" src="jsr_class.js"> </script>
<script type="text/javascript;e4x=1">

// Define the callback function
function getGeo(ResultSet) {
alert('Latitude = ' + ResultSet.Result[0].Latitude + ' Longitude = ' + ResultSet.Result[0].Longitude);
bObj.removeScriptTag();
}


// The web service call
var req = 'e4xObject.js';
// Create a new request object
bObj = new JSONscriptRequest(req);
// Build the dynamic script tag
bObj.buildScriptTag();
// Add the script tag to the page
bObj.addScriptTag();

</script>
</body>
</html>

See Example 3 in action here.

So now that you have a more complete picture you can hopefully make a more informed decision about what is easiest method.

1 comments:

Mark McLaren said...

One of the reasons why JSON is becoming an attractive alternative is because it "eval"s to live script, not just data. This opens up a lot of possibilities for fleixibility, bringing in only the script that is needed when it is needed, and bringing back the code to render the UI right along with the data for that UI.
Note: Comment imported. Original by Peter Bromberg website: http://petesbloggerama.blogspot.com at 2006-05-08 18:13