- 2.1 Not a Mockup
- 2.2 A Technique Without a Name
- 2.3 What Is Ajax?
- 2.4 An Ajax Encounter of the First Kind
- 2.5 An Ajax Encounter of the Second Kind
- 2.6 An Ajax Encounter of the Third Kind
- 2.7 The Shape of Things to Come
- 2.8 Summary
2.6 An Ajax Encounter of the Third Kind
The fifth part of Ajax, an optional part, isn't for the faint of heart. It transcends the "mad scientist stuff" into the realm of the magical, and it is called eXtensible Stylesheet Language for Transformations, or XSLT. In other words, if Ajax really was mad science and it was taught in school, this would be a 400-level course. Why? The reason is that the technology is both relatively new and very, very browser dependent. However, when it works, this method provides an incredible experience for the user.
2.6.1 XSLT
XSLT is an XML-based language that is used to transform XML into other forms. XSLT applies a style sheet (XSLT) as input for an XML document and produces output—in most cases, XHTML or some other form of XML. This XHTML is then displayed on the browser, literally in the "wink of an eye."
One of the interesting things about XSLT is that, other than the XML being well formed, it really doesn't make any difference where the XML came from. This leads to some interesting possible sources of XML. For example, as you are probably aware, a database query can return XML. But did you know that an Excel spreadsheet can be saved as XML? XSLT can be used to transform any XML-derived language, regardless of the source.
Listing 2-9 shows a simple Internet Explorer–only web page along the same lines as the earlier examples. By using XSLT and the XMLHttpRequest object to retrieve both the XML and XSLT shown in Listing 2-10, it is extremely flexible. This is because after the initial page is loaded, any conceivable page can be generated simply by changing the XML and/or the XSLT. Sounds pretty powerful, doesn't it?
Listing 2-9. A Simple IE-Only Web Page
<html> <head> <title>AJAX Internet Explorer Flavor</title> <script language="javascript"> var dom = new ActiveXObject('MSXML2.FreeThreadedDOMDocument.3.0'); var xslt = new ActiveXObject('MSXML2.FreeThreadedDOMDocument.3.0'); var objXMLHTTP; /* Obtain the initial XML document from the web server. */ function initialize() { doPOST(true); } /* Use the XMLHttpRequest to communicate with a web service. */ function doPOST(blnState) { var strURL = 'http://localhost/AJAX/msas.asmx'; objXMLHTTP = new ActiveXObject('Microsoft.XMLHTTP'); objXMLHTTP.open('POST',strURL,true); if(blnState) objXMLHTTP.setRequestHeader('SOAPAction','http:// tempuri.org/getState'); else objXMLHTTP.setRequestHeader('SOAPAction','http://tempuri.org/getXML'); objXMLHTTP.setRequestHeader('Content-Type','text/xml'); objXMLHTTP.onreadystatechange = stateChangeHandler; try { objXMLHTTP.send(buildSOAP(blnState)); } catch(e) { alert(e.description); } } /* Construct a SOAP envelope. */ function buildSOAP(blnState) { var strSOAP = '<?xml version="1.0" encoding="UTF-8"?>'; strSOAP += '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'; strSOAP += '<soap:Body>'; if(blnState) { strSOAP += '<getState xmlns="http://tempuri.org/">'; strSOAP += '<state_abbreviation/>'; strSOAP += '</getState>'; } else { strSOAP += '<getXML xmlns="http://tempuri.org/">'; strSOAP += '<name>xsl/state.xsl</name>'; strSOAP += '</getXML>'; } strSOAP += '</soap:Body>'; strSOAP += '</soap:Envelope>'; return(strSOAP); } /* Handle server response to XMLHTTP requests. */ function stateChangeHandler() { if(objXMLHTTP.readyState == 4) try { var work = new ActiveXObject('MSXML2.FreeThreadedDOMDocument.3.0'); work.loadXML(objXMLHTTP.responseText); switch(true) { case(work.selectNodes('//getStateResponse').length != 0): dom.loadXML(objXMLHTTP.responseText); doPOST(false); break; case(work.selectNodes('//getXMLResponse').length != 0): var objXSLTemplate = new ActiveXObject('MSXML2.XSLTemplate.3.0'); xslt.loadXML(work.selectSingleNode('//getXMLResult').firstChild.xml); objXSLTemplate.stylesheet = xslt; var objXSLTProcessor = objXSLTemplate.createProcessor; objXSLTProcessor.input = dom; objXSLTProcessor.transform(); document.getElementById('select').innerHTML = objXSLTProcessor.output; break; default: alert('error'); break; } } catch(e) { } } </script> </head> <body onload="initialize()"> <div id="select"></div> </html>
Listing 2-10. The XML and XSLT Part
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:template match="/"> <xsl:element name="select"> <xsl:attribute name="id">state</xsl:attribute> <xsl:attribute name="name">selState</xsl:attribute> <xsl:apply-templates select="//Table[country_id = 1]"/> </xsl:element> </xsl:template> <xsl:template match="Table"> <xsl:element name="option"> <xsl:attribute name="value"><xsl:value-of select="state_abbreviation"/></xsl:attribute> <xsl:value-of select="state_name"/> </xsl:element> </xsl:template> </xsl:stylesheet>
2.6.2 Variations on a Theme
At first glance, the JavaScript in the previous example appears to be very similar to that shown in Listing 2-7; however, nothing could be further from the truth. The first of these differences is due to two calls being made to a web service and the use of XSLT to generate the HTML to be displayed in the browser. Let's look at this in a little more detail.
First, the only thing that the initialize function does is call another function, doPOST, passing a true. Examining doPOST reveals that the purpose of the true is to indicate what the SOAPAction in the request header is, http://tempuri.org/getState to get information pertaining to states and provinces from the web service, or http://tempuri.org/getXML to get XML/XSLT from the web service. The first time through, however, we're getting the XML.
The second difference, also in doPOST, is the addition of a call to buildSOAP right smack in the middle of the XMLHttpRequest object's send. This is how arguments are passed to a web service, in the form of text—a SOAP request, in this instance. Checking out buildSOAP, you'll notice that Boolean from doPOST is passed to indicate what the body of the SOAP request should be. Basically, this is what information is needed from the web service, states or XSLT.
You'll remember the stateChangeHandler from the earlier set of examples, and although it is similar, there are a few differences. The first thing that jumps out is the addition of a "work" XML document that is loaded and then used to test for specific nodes; getStateResponse and getXMLResponse. The first indicates that the SOAP response is from a request made to the web service's getState method, and the second indicates a response from the getXML method. Also notice the doPOST with an argument of false in the part of the function that handles getState responses; its purpose is to get the XSLT for the XSL transformation.
Speaking of a transformation, that is the purpose of the code that you might not recognize in the getXML portion of the stateChangeHandler function. Allow me to point out the selectSingleNode method used, the purpose of which is to remove the SOAP from the XSLT. The reason for this is that the XSLT simply won't work when wrapped in a SOAP response. The final lines of JavaScript perform the transformation and insert the result into the page's HTML.
The use of XSLT to generate the HTML "on the fly" offers some interesting possibilities that the other two methods of implementing Ajax do not. For instance, where in the earlier example the look of the page was dictated by the hard-coded HTML, this doesn't have to be the case when using XSLT. Consider for a moment the possibility of a page using multiple style sheets to change the look and feel of a page. Also, with the speed of XSLT, this change would occur at Windows application speeds instead of the usual crawl that web applications proceed at.