It’s time to switch gears and look at code for Ajax web applications. The most important part of an Ajax application is the connection between the client and the server. If this code is not solid and optimized, your application could suffer sluggish (or simply broken) behavior as a result.
You code the connection between the client and the server using JavaScript, and usually build the data format used to exchange information in XML. I say usually because a new format is on the rise and is fast becoming the new choice for web developers. This new format is JavaScript Object Notation (JSON).
In this chapter, we will explore how to use XML and JSON to transmit data. We will also discuss how the client and the server can parse or otherwise manipulate these formats. Of course, a discussion of this nature would be incomplete without some points on the differences among browser versions, and how to make cross-browser-compatible code.
We will start with XML, as it is part of the original meaning of
Ajax. This section will cover the basics of how Ajax works and what to
do with the XML that is sent back and forth between the client and the
server. First, driving the Ajax component of an Ajax web application
is the XMLHttpRequest
object. This
object allows for asynchronous communication between the client and
the server. In other words, the client can start communicating with
the server, and instead of the client freezing up and becoming
unusable until that communication is complete, the client can continue
to function like normal.
Unfortunately for the developer, how an XMLHttpRequest
object is implemented is
different from one browser to the next. For Safari, Mozilla, Opera,
and other like-minded browsers, you create the object like
this:
var request = new XMLHttpRequest( );
For browsers that use ActiveX controls, you simply pass the name of the object to the ActiveX control:
var request = new ActiveXObject('Microsoft.XMLHTTP'),
Once the object has been instantiated, whether you are using the
XMLHttpRequest
object or the
ActiveX version, the object has the same basic methods and properties
associated with it, as shown in Table 4-1 and Table 4-2.
Table 4-1. The XMLHttpRequest object’s properties
Property | Description |
---|---|
| The function assigned
to this property, which is an event listener, is called
whenever the |
| This property represents the current state that the object is in. It is an integer that takes one of the following:
|
| A version of the returned data in a plain-text format. |
| A version of the
returned data that has been instantiated into a Document
Object Model (DOM) |
| The response status code that the server returned, such as 200 (OK) or 404 (Not Found). |
| The text message associated with the response status code the server returned. |
Table 4-2. The XMLHttpRequest object’s methods
Property | Description |
---|---|
| Cancels the object’s current request. |
| Returns all of the response headers; headers and values as a formatted string. |
| Returns the value of the passed header as a string. |
| Prepares the request by assigning:
|
| Sends the request with the optional contents, either a postable string or a DOM object’s data. |
| Sets the request header
with the value, but the |
But first things first; before we delve into the properties and
methods of the XMLHttpRequest
object, we must create the object. Example 4-1 shows a
cross-browser-compatible way to create the XMLHttpRequest
object.
Example 4-1. Creating the XMLHttpRequest object
/* * Example 4-1, Creating the XMLHttpRequest object. */ /** * This function, createXMLHttpRequest, checks to see what objects the * browser supports in order to create the right kind of XMLHttpRequest * type object to return. * * @return Returns an XMLHttpRequest type object or false. * @type Object | Boolean */ function createXMLHttpRequest( ) { var request = false; /* Does this browser support the XMLHttpRequest object? */ if (window.XMLHttpRequest) { if (typeof XMLHttpRequest != 'undefined') /* Try to create a new XMLHttpRequest object */ try { request = new XMLHttpRequest( ); } catch (e) { request = false; } /* Does this browser support ActiveX objects? */ } else if (window.ActiveXObject) { /* Try to create a new ActiveX XMLHTTP object */ try { request = new ActiveXObject('Msxml2.XMLHTTP'), } catch(e) { try { request = new ActiveXObject('Microsoft.XMLHTTP'), } catch (e) { request = false; } } } return request; } var request = createXMLHttpRequest( );
The createXMLHttpRequest( )
function returns an abstract object that functions out of the user’s
view. The request
object has the
methods and properties listed in Table 4-1 and Table 4-2. Once you have your
XMLHttpRequest
object instantiated,
you can start to build requests and trap responses.
So, we have our XMLHttpRequest
object, and now we need to
do something with it. This object will control all of the requests
that will be communicated to the server, as well as all of the
responses sent back to the client. Two methods and one property are
typically used when building a request for the server: open( ), send( )
, and onreadystatechange
. For example:
if (request) {
request.open('GET', URL
, true);
request.onreadystatechange = parseResponse;
request.send(''),
}
This is the bare-bones request that can be made to the server.
It is not entirely useful, however, until you pass data to the
server for it to act on. We need to build a function that accepts as
input an XMLHttpRequest
object, a
URL to send to, parameters to pass to the server, and a function to
fire when the readyState
of the
object changes, as shown in Example 4-2.
Example 4-2. Creating a request function
/* * Example 4-2, Creating a request function. */ /** * This function, requestData, takes the passed /p_request/ object and * sends the passed /p_data/ to the passed /p_URL/. The /p_request/ * object calls the /p_func/ function on /onreadystatechange/. * * @param {Object} p_request The XMLHttpRequest object to use. * @param {String} p_URL The URL to send the data request to. * @param {String} p_data The data that is to be sent to the server through * the request. * @param {Object} p_func The function to call on * /onreadystatechange/. */ function requestData(p_request, p_URL, p_data, p_func) { /* Does the XMLHttpRequest object exist? */ if (p_request) { p_request.open('GET', p_URL, true); p_request.onreadystatechange = p_func; p_request.send(p_data); } }
As the developer, it is up to you whether you send your request with a GET method or a POST method, unless you wish to send the server some XML. When this is the case, a POST method is required. So, we would want to modify our function to also receive as a parameter the method of the request. The new declaration line would look like this:
function requestData(request, url, data, func, method) {
The data that is sent can be in the form of passed parameters, or XML. With both a POST and a GET, the data passed would look like this:
param1=data1¶m2=data2¶m3=data3
This same data could be passed as an XML document as:
<parameters> <param id="1">data1</param> <param id="2">data2</param> <param id="3">data3</param> </parameters>
If the data you are passing is simple in nature, I recommend sticking with the passed parameter string instead of the XML. Less data is passed to the server, which could lead to a faster response time.
When the server receives the request, the corresponding script is executed to generate a response. You should build these scripts so that the least possible amount of data is returned. Remember, the idea behind Ajax and Ajax web applications is speed: speed in requests, speed in responses, and speed in displaying the response to the client. Example 4-3 shows how to program a typical script to create a response for the client.
Example 4-3. A typical script for creating a server response
<?php /** * Example 4-3, A typical script for creating a server response. */ /** * The Zend framework Db.php library is required for this example. */ require_once('Zend/Db.php'), /** * The generic db.inc library, containing database connection information such as * username, password, server, etc., is required for this example. */ require_once('db.inc'), /* Output the XML Prolog so the client can recognize this as XML */ $xml = <<< PROLOG <?xml version="1.0" encoding="iso-8859-1"?> PROLOG; /* Set up the parameters to connect to the database */ $params = array ('host' => $host, 'username' => $username, 'password' => $password, 'dbname' => $db); try { /* Connect to the database */ $conn = Zend_Db::factory('PDO_MYSQL', $params); /* Get the parameter values from the query string */ $value1 = $conn->quote(($_GET['param1']) ? $_GET['param1'] : ''), $value2 = $conn->quote(($_GET['param2']) ? $_GET['param2'] : ''), $value3 = $conn->quote(($_GET['param3']) ? $_GET['param3'] : ''), /* * Create a SQL string and use the values that are protected from SQL injections */ $sql = 'SELECT * FROM table1 WHERE condition1 = $value1 AND condition2 = $value2' .' AND condition3 = $value3'; /* Get the results of the query */ $result = $conn->query($sql); /* Are there results? */ if ($rows = $result->fetchAll( )) { /* Create the response XML string */ $xml .= '<results>'; foreach($rows in $row) { $xml .= "<result>"; $xml .= "<column1>{$row['column1']}</column1>"; $xml .= "<column2>{$row['column2']}</column2>"; $xml .= "</result>"; } $xml .= '</results>'; } } catch (Exception $e) { $xml .= '<error>There was an error retrieving the data.</error>'; } /* * Change the header to text/xml so that the client can use the return string as XML */ header("Content-Type: text/xml"); echo $xml; ?>
This script does what most simple scripts do. It gets the passed parameters, inserts those values into the SQL query, formats the response as XML, and outputs the results. How data is sent to the server is up to the developer, and probably depends on the server-side scripting language being used. For PHP, for example, it is relatively easy to parse XML coming from the client, just as it is easy to parse a query string, as shown in Example 4-4.
Example 4-4. Dealing with an XML data request
<?php /** * Example 4-4, Dealing with an XML data request. */ /** * The Zend framework Db.php library is required for this example. */ require_once('Zend/Db.php'), /** * The generic db.inc library, containing database connection information such as * username, password, server, etc., is required for this example. */ require_once('db.inc'), /* Get the passed XML */ $raw_xml = file_get_contents("php://input"); $data = simplexml_load_string($raw_xml); /* Parse the XML and create the parameters */ foreach ($data->param as $param) switch ($param['id']) { case 1: $value1 = $param; break; case 2: $value2 = $param; break; case 3: $value3 = $param; break; } /* Output the XML Prolog so the client can recognize this as XML */ $xml = <<< PROLOG <?xml version="1.0" encoding="iso-8859-1"?> PROLOG; /* Set up the parameters to connect to the database */ $params = array ('host' => $host, 'username' => $username, 'password' => $password, 'dbname' => $db); try { /* Connect to the database */ $conn = Zend_Db::factory('PDO_MYSQL', $params); $value1 = $conn->quote($value1); $value2 = $conn->quote($value2); $value3 = $conn->quote($value3); /* * Create a SQL string and use the values that are protected from SQL injections */ $sql = 'SELECT * FROM table1 WHERE condition1 = $value1 AND condition2 = $value2' .' AND condition3 = $value3'; /* Get the results of the query */ $result = $conn->query($sql); /* Are there results? */ if ($rows = $result->fetchAll( )) { /* Create the response XML string */ $xml .= '<results>'; foreach($rows in $row) { $xml .= "<result>"; $xml .= "<column1>{$row['column1']}</column1>"; $xml .= "<column2>{$row['column2']}</column2>"; $xml .= "</result>"; } $xml .= '</results>'; } } catch (Exception $e) { $xml .= '<error>There was an error retrieving the data.</error>'; } /* * Change the header to text/xml so that the client can use the return string as XML */ header("Content-Type: text/xml"); echo $xml; ?>
The server has created a response, and now the client must
gather that response for whatever parsing needs to be done. For
handling the server response, you use the XMLHttpRequest
object’s readyState, status, responseText
or
responseXML
, and statusText
. In Example 4-5, we will build our
function that was set with the onreadystatechange
property during the
request.
Example 4-5. Handling the server’s response
/* * Example 4-5, Handling the server's response. */ /** * This function, parseResponse, waits until the /readyState/ and /status/ * are in the state needed for parsing (4 and 200 respectively), and uses * the /responseText/ from the request. */ function parseResponse( ) { /* Is the /readyState/ 4? */ if (request.readyState == 4) { /* Is the /status/ 200? */ if (request.status == 200) { /* Grab the /responseText/ from the request (XMLHttpRequest) */ var response = request.responseText; alert(response); // here is where the parsing would begin. } else alert('There was a problem retrieving the data: ' + request.statusText); request = null; } }
In this function, if the readyState
isn’t equal to 4
(complete), we’re not interested in
proceeding. Likewise, if the status
returned isn’t 200
(OK), we need to tell the user there
was an error. The responseText
property is set with a string version of whatever content the server
sent. If the server returns XML, the responseXML
property is automatically
created as a DOM XML Document
object that can be parsed like the rest of the DOM.
That is all fine and dandy for the server side, but what if
you need to send XML to the server as part of your request because
the data is not so simple? Often, for example, the data you need to
send is not part of a form. In these cases, you POST the XML string
to the server. Remember the requestData(
)
function? Here is a quick alteration of that
function:
/** * This function, requestData, takes the passed /p_request/ object and * sends the passed /p_data/ to the passed /p_URL/. The /p_request/ * object calls the /p_func/ function on /onreadystatechange/. * * @param {Object} p_request The XMLHttpRequest object to use. * @param {String} p_URL The URL to send the data request to. * @param {String} p_data The data that is to be sent to the server through * the request. * @param {String} p_func The string name of the function to call on * /onreadystatechange/. * @param {String} p_method The method that the request should use to pass * parameters. */ function requestData(p_request, p_URL, p_data, p_func, p_method) { /* Does the XMLHttpRequest object exist? */ if (p_request) { /* Is the posting method 'GET'? */ if (p_method == 'GET') p_request.open('GET', p_URL + '?' + p_data, true); else p_request.open('POST', p_URL, true) p_request.onreadystatechange = p_func; /* Is the posting method 'GET'? */ if (p_method == 'GET') p_request.send(null); else p_request.send(p_data); } }
The data
that you pass to
this function can be an XML string, but in these cases, the method
must be 'POST'
.
Requests and responses using XML are as simple as that. The most important thing a developer must be aware of is how the data is being returned from the server.
Once you have received a responseText
or responseXML
, you need to be able to parse
that response so that it is useful to the application. Many DOM
methods are available in JavaScript, but for now we will concentrate
on just a couple of them. Chapter 5
will detail the rest of the methods to complete our discussion of
XML manipulation within the DOM. The methods we will focus on now
are getElementById( )
and
getElementsByTagName(
)
.
The basic syntax for the getElementById( )
method is:
var node = document.getElementById(elementId
);
Just as basic, the syntax for the getElementsByTagName
method is:
var nodeList = xmlObject.getElementsByTagName(tagName
);
Developers most often use the getElementById( )
and getElementsByTagName( )
methods to
retrieve elements based on the World Wide Web Consortium (W3C) DOM.
Befriend these methods; they make dynamic programming in JavaScript
what it is, and every developer of an Ajax web application needs to
know exactly what she gets back from each method.
By using the XML from this chapter’s earlier “XML Requests and Responses” section as our response from the server:
<parameters> <param id="1">data1</param> <param id="2">data2</param> <param id="3">data3</param> </parameters>
we can access our data using the responseXML
property from the XMLHttpRequest
object, as shown in Example 4-6.
Example 4-6. Parsing data sent from the server
/* * Example 4-6, Parsing data sent from the server. */ /** * This function, parseResponse, takes the XML response from the server * and pulls out the elements it needs to dynamically alter the contents * of a page. */ function parseResponse( ) { /* Is the /readyState/ 4? */ if (request.readyState == 4) { /* Is the /status/ 200? */ if (request.status == 200) { var response = request.responseXML; var paramList = response.getElementsByTagName('param'), /* This will be the XHTML string to use */ var out = '<ul>'; for (i = 0, il = paramList.length; i < il;) out += '<li>' + paramList[i++].firstChild.nodeValue + '</li>'; out += '</ul>'; document.getElementById('list').innerHTML = out; } else alert('There was a problem retrieving the data: ' + request.statusText); request = null; } }
Here, we get a node list of all the elements with a tag name of param
with getElementsByTagName( )
, and after looping
through the nodes and creating some quick and dirty XHTML, we use
getElementById( )
to specify
where we want to put our formatted string.
The choice of using this to get to the value of the text node:
paramList[i].firstChild.nodeValue
instead of this:
paramList.item(i).firstChild.nodeValue
is really a matter of developer taste. I chose the former because it requires fewer keystrokes, and less is almost always more.
Sometimes the XML you want to dynamically pull comes from an
XML file or an XML string. In these cases, you will want to load the
file into a DOM Document
object
so that you can then parse the XML. To load a file you use the
load( )
method, which is
implemented in all browsers. To load an XML string, however, there
is no universal method. Internet Explorer has a method that is part
of the Document
object, called
loadXML( )
. Unfortunately, most
other browsers do not implement such a method. In these cases, the
developer will need to create his own loadXML( )
for cross-browser
compatibility, as shown in Example 4-7.
Example 4-7. Adding a loadXML method to the Document object
/* * Example 4-7, Adding a loadXML method to the Document object. */ /* Is this a DOM-compliant browser? */ if (!window.ActiveXObject) { /** * This method, loadXML, is a cross-browser method for DOM-compliant * browsers that do not have this method natively. It loads an XML * string into the DOM document for proper XML DOM parsing. */ Document.prototype.loadXML = function (xml_string) { /* Parse the string to a new doc */ var doc = (new DOMParser( )).parseFromString(xml_string, 'text/xml'), /* Remove all initial children */ while (this.hasChildNodes( )) this.removeChild(this.lastChild); /* Insert and import nodes */ for (i = 0, il = doc.childNodes.length; i < il;) this.appendChild(this.importNode(doc.childNodes[i++], true)); }; }
First, let’s look at the code required to load an XML file into the DOM, as shown in Example 4-8. We want to make sure this code is cross-browser-compliant; otherwise, it is useless to us.
Example 4-8. Cross-browser code to load an XML file into the DOM
/*
* Example 4-8, Cross-browser code to load an XML file into the DOM.
*/
/**
* This function, loadXMLFromFile, takes the passed /p_file/ string file name
* and loads the contents into the DOM document.
*
* @param {String} p_file The string file name to load from.
*/
function loadXMLFromFile(p_file) {
/* Does this browser support ActiveX? (Internet Explorer) */
if (window.ActiveXObject) {
xmlDoc = new ActiveXObject('Microsoft.XMLDOM'),
xmlDoc.async = false;
xmlDoc.load(p_file);
parseXML( );
} else if (document.implementation && document.implementation.createDocument) {
xmlDoc = document.implementation.createDocument('', '', null);
xmlDoc.load(p_file);
xmlDoc.onload = parseXML( );
}
}
var xmlDoc = null;
loadXMLFromFile('dummy.xml
'),
With this example, the file dummy.xml is loaded as a DOM Document
object before the function
parseXML( )
is called to parse
the global xmlDoc
object. When
xmlDoc
is created using document.implementation.createDocument('', '',
null)
, the load method is a synchronous call. The client
halts everything else until the XML file is loaded. The ActiveX
object, however, is not automatically a synchronous call. The
async
property must be set to
false
to achieve the same
functionality as its counterpart.
If you want the ActiveX object to behave asynchronously, you
first must set the async
property
to true
. Second, you must set the
onreadystatechange
property to a
function call. The function that is called on every readyState
change must then check the
state of the document’s loading. The same readyState
codes in Table 4-1 that apply to the
XMLHttpRequest
object also apply
to the xmlDoc
object. Example 4-9 gives an example of
this.
Example 4-9. Asynchronously loading an XML file
/* * Example 4-9, Asynchronously loading an XML file. */ /** * This function, loadXMLAsyncFromFile, takes the passed /p_file/ string file name * and loads the contents asynchronously into the DOM document. * * @param {String} p_file The string filename to load from. * @see #verify */ function loadXMLAsyncFromFile(p_file) { xmlDoc = new ActiveXObject('Microsoft.XMLDOM'), xmlDoc.async = true; xmlDoc.onreadystatechange = verify; xmlDoc.load(p_file); } /** * This function, verify, checks to see if the file is ready to be parsed * before attempting to use it. * * @see #loadXMLAsyncFromFile */ function verify( ) { /* Is the /readyState/ 4? */ if (xmlDoc.readyState == 4) parseXML( ); else return false; } var xmlDoc = null; loadXMLAsyncFromFile('dummy.xml'),
So, we can load a file now, but sometimes you’ll want to
create a DOM Document
object from
a string, too. Why? Imagine that you are getting your dynamic data
from a third-party application. In this scenario, you have no
control over the code because it is not open source. This
application also sends the client XML data, but does not send the
Content-Type of the HTTP header as text/xml. In this case, the responseXML
property is set to null and
the data is only in the responseText
property as a string. This is
where the loadXML( )
method comes
in handy. Example 4-10 shows
how to use this method to load an XML string.
Example 4-10. Loading an XML string into a DOM Document object
/* * Example 4-10, Loading an XML string into a DOM Document object. */ /** * This function, parseResponse, takes the XML response from the server * and pulls out the elements it needs to dynamically alter the contents of a page. */ function parseResponse( ) { /* Is the /readyState/ 4? */ if (request.readyState == 4) { /* Is the /status/ 200? */ if (request.status == 200) { var xmlString = request.responseText; var response = null; /* Does this browser support ActiveX? (Internet Explorer) */ if (window.ActiveXObject) { response = new ActiveXObject('Microsoft.XMLDOM'), response.async = false; } else if (document.implementation && document.implementation.createDocument) response = document.implementation.createDocument('', '', null); response.loadXML(xmlString); var paramList = response.getElementsByTagName('param'), /* This will be the XML string to use */ var out = '<ul>'; /* Loop through the list taken from the XML response */ for (i = 0, il = paramList.length; i < il;) { out += '<li>' + paramList[i++].firstChild.nodeValue + '</li>'; } out += '</ul>'; document.getElementById('list').innerHTML = out; } else alert('There was a problem retrieving the data: ' + request.statusText); request = null; } }
Once the XML is loaded into a DOM Document
object, you can parse it in the
same way you would with a responseXML
object.
The ability to quickly navigate the DOM to the elements you need is an essential part of Ajax web development. This is where the W3C standard, XPath, comes into play. XPath is the syntax a developer can use to define parts of an XML document using path expressions to navigate through elements and attributes in the document. More important, it is an integral part of Extensible Stylesheet Language Transformation (XSLT), which we’ll cover in the next section.
Now the bad news: DOM Level 3 XPath is fully implemented in Mozilla, but not in Internet Explorer. Are you as sick of writing cross-browser-compatible code as I am? To jump the gun a little bit, what we need is a client framework that can do all of this cross-browser-compatible code for us so that we can concentrate on other things. So, although I cover this topic in more depth later in this chapter (in the section “A Quick Introduction to Client Frameworks”), in this section I want to introduce you to Sarissa (http://sarissa.sourceforge.net/).
Sarissa provides a cross-browser solution, not only to XPath
but also to XSLT. Jumping right in, first we need to create a DOM
Document
object using
Sarissa:
var domDoc = Sarissa.getDomDocument( );
Now we need to load the XML document into the newly created
DOM Document
object:
domDoc.async = false;
domDoc.load('my.xml
'),
Here we set the DOM Document
object to load synchronously, and
then executed the file load. Now comes the XPath part. For this, we
use two methods: selectNodes( )
and selectSingleNode( )
.
Here is the Internet Explorer gotcha. Before we can use either
method, we must call the setProperty(
)
method. If we didn’t take this step, Internet Explorer
would give an error. To make XPath available to the DOM Document
object in Internet Explorer, you
do the following:
domDoc.setProperty('SelectionLanguage', 'XPath'),
And if you want Internet Explorer to resolve namespace prefixes, you do the following:
domDoc.setProperty('SelectionNamespaces', 'xmlns:xhtml='http://www.w3.org/1999/xhtml''),
The same method called with different parameters sets the
different things the DOM Document
object needs. This method can also enable the object to resolve
multiple namespace prefixes using a space-delimited list:
domDoc.setproperty('SelectionNamespaces', 'xmlns:xhtml='http://www.w3.org/1999/xhtml' xmlns:xsl='http://www.w3.org/1999/XSL/Transform''),
To use these methods, you must include the sarissa_ieemu_xpath.js file on your page. Mozilla does not need this method and will ignore it if it is called.
Finally, we are ready to use the XPath methods. Example 4-11 gives an example of using
both the selectNodes( )
and
selectSingleNode( )
methods. It
assumes that the file being loaded contains the following:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/TR/xhtml1/strict"> <xsl:strip-space elements="chapter section"/> <xsl:output method="xml" indent="yes" encoding="iso-8859-1"/> <xsl:template match="book"> <h1><xsl:value-of select="title"/></h1> <div><xsl:value-of select="author"/></div> </xsl:template> <xsl:template match="*"></xsl:template> <xsl:template match="@*"></xsl:template> </xsl:stylesheet>
This does not really do much, but it serves our example.
Example 4-11. XPath in action with Sarissa
/* * Example 4-11, XPath in action with Sarissa. */ /* Create a new Sarissa DOM document to hold the XSL */ var domDoc = Sarissa.getDomDocument( ); /* Load the XSL from the file */ domDoc.async = false; domDoc.load('my.xsl'), /* Set the properties of the XSL document to use XPath */ domDoc.setProperty('SelectionLanguage', 'XPath'), domDoc.setProperty('SelectionNamespaces', xmlns:xsl='http://www.w3.org/1999/XSL/Transform''), var nodeList = null; var element = null; /* Use XPath to get elements from the document */ nodeList = domDoc.selectNodes('//xsl:template'), element = domDoc.documentElement.selectNode('//xsl:template'),
The example finds nodes that match the string xsl:template
anywhere within the
document’s DOM tree. For better information on XPath and how to use
expressions to search through the DOM tree, John E. Simpson’s
XPath and XPointer (O’Reilly) is a good
reference.
As I stated earlier, XSLT relies on XPath in a big way, using it to search the document to extract parts of the DOM tree during a transformation, forming conditional expressions, building sequences, and so forth. XSLT makes good sense in Ajax web development, as it can transform XML data sent from the server into something the client can recognize. Again, an easy solution for this task is using Sarissa.
The simplest way to use Sarissa is to load the XSL file,
create an XLSTProcessor
object,
and transform the XML in question using the transformToDocument( )
method. Example 4-12 builds off of Example 4-10 where the XML to
transform is received from an Ajax call to the server. The XSL
document is loaded from a file residing on the server.
Example 4-12. Sarissa in action for XSLT
/* * Example 4-12, Sarissa in action for XSLT. */ /** * This function, parseResponse, checks the /request/ object for its /readyState/ * and /status/ to see if the response is complete, and then takes the XML string * returned and does an XSLT transformation using a provided XSL file. It then * sets the transformed XML to the /innerHTML/ of the 'list' element. */ function parseResponse( ) { /* Is the /readyState/ for the /request/ a 4 (complete)? */ if (request.readyState == 4) { /* Is the /status/ from the server 200? */ if (request.status == 200) { var xmlString = request.responseText; /* Create a new Sarissa DOM document to hold the XML */ var xmlDoc = Sarissa.getDomDocument( ); /* Create a new Sarissa DOM document to hold the XSL */ var xslDoc = Sarissa.getDomDocument( ); /* Parse the /responseText/ into the /xmlDoc/ */ xmlDoc = (new DOMParser( )).parseFromString(xmlString, 'text/xml'), /* Load the XSL document into the /xslDoc/ */ xslDoc.async = false; xslDoc.load('my.xsl'), xslDoc.setProperty('SelectionLanguage', 'XPath'), xslDoc.setproperty('SelectionNamespaces', xmlns:xsl='http://www.w3.org/1999/XSL/Transform''), /* Create a new /XSLTProcessor/ object to do the transformation */ var processor = new XSLTProcessor( ); processor.importStyleSheet(xslDoc); /* Transform the document and set it to the /innerHTML/ of the list */ var newDoc = processor.transformToDocument(xmlDoc); document.getElementById('list').innerHTML = Sarissa.serialize(newDoc); } else alert('There was a problem retrieving the data: ' + request.statusText); request = null; } }
I might have oversimplified the process of XSLT transformation
using Sarissa. So, I’ll demystify it a little bit. First, we receive
the responseText
from the server,
which we have seen before. The difference from Example 4-10 is that we use
Sarissa’s getDomDocument( )
method to create our document and then import the string into XML
using the line:
xmlDoc = (new DOMParser( )).parseFromString(xmlString, 'text/xml'),
Next, we loaded the XSL file using Sarissa’s methods for doing
so. After that, we created the XSLTProcessor
object, as well as the
stylesheet for transforming our XML (the my.xsl file, in this example), using the
importStyleSheet( )
method.
Finally, we executed the transformToDocument( )
method on the XML,
and a transformed XML document was created. We completed the example
by serializing the XML document using Sarissa’s serialize( )
method so that the document
could be inserted into the XHTML document.
In Example 4-12, we
instantiated both of the XML documents being used—the response
from the server and the XSL file—using Sarissa’s getDomDocument( )
method. This was by
design, and not just to show how to load an XML string into a DOM
Document
using Sarissa. If you
were to create the XSL using document.implementation.createDocument(
)
or ActiveXObject('Microsoft.XMLDOM')
, you
would not be able to manipulate that object using Sarissa’s
classes and methods. You must use Sarissa to create both DOM
objects.
JSON is a data exchange format that is a subset of the object
literal notation in JavaScript. It has been gaining a lot of attention
lately as a lightweight alternative to XML, especially in Ajax
applications. Why is this? Because of the ability in JavaScript to
parse information quickly using the eval(
)
function. JSON does not require JavaScript, however, and
you can use it as a simple exchange format for any scripting
language.
Here is an example of what JSON looks like:
{'details': { 'id': 1, 'type': 'book', 'author': 'Anthony T. Holdener III', 'title': 'Ajax: The Definitive Guide', 'detail': { 'pages': 960, 'extra': 20, 'isbn': 0596528388, 'price': { 'us': 49.99, 'ca': 49.99 } } }}
This is the equivalent in XML:
<details id="1" type="book"> <author>Anthony T. Holdener III</author> <title>Ajax: The Definitive Guide</title> <detail> <pages extra="20">960</pages> <isbn>0596528388</isbn> <price us="49.99" ca="49.99" /> </detail> </details>
Some developers think JSON is more elegant at describing data. Others like its simplicity. Still others argue that it is more lightweight (we’ll get into that in a bit). Looking at the two preceding examples, you can see that they’re almost identical in size. In fact, the size difference is a mere eight bytes. I won’t tell you which is smaller; keep reading and you’ll find out. I will tell you that you can find more on JSON at http://www.json.org/.
Requests to the server using Ajax and JSON are the same as with XML. We are again looking at this function:
function requestData(request, url, data, func, method) { if (request) { if (method == 'GET') request.open('GET', url + '?' + data, true); else request.open('POST', url, true); request.onreadystatechange = func; if (method == 'GET') request.send(''), else request.send(data); } }
As with the XML string, your data
is the JSON string and the method
again must be a 'POST'
. That part is simple enough, but
what about the server side of things? If JSON is just a notation for
JavaScript, how will other languages interpret it? Luckily, JSON has
been ported to pretty much every scripting language there is. For a
full list, you should refer to the JSON site. Because our examples
are in PHP, we have many choices for porting JSON. I will be using
JSON-PHP in these examples.
The data we are sending to the server will look like this:
{'parameters': { 'param': [ {'id': 1, 'value': 'data1'}, {'id': 2, 'value': 'data2'}, {'id': 3, 'value': 'data3'} ] } }
This is the JSON version of the XML from the “XML Requests and Responses” section, earlier in this chapter. Example 4-13 shows how to handle this request with PHP.
Example 4-13. PHP handling a JSON request from the client
<?php /** * Example 4-13, PHP handling a JSON request from the client. */ /** * The Zend Framework Db.php library is required for this example. */ require_once('Zend/Db.php'), /** * The generic db.inc library, containing database connection information * such as username, password, server, etc., is required for this example. */ require_once('db.inc'), /** * The JSON library required for this example. */ require_once('JSON.php'), /* Create a new JSON service */ $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); /* Get the parameter values from the post the client sent */ $raw_json = file_get_contents("php://input"); $data = $json->decode($raw_json); /* Find all of the parameter values */ for ($i = 0, $il = count($data['parameters']['param']); $i < $il;) { $d = $data['parameters']['param'][$i++]; switch ($d['id']) { case 1: $value1 = $d['value']; break; case 2: $value2 = $d['value']; break; case 3: $value3 = $d['value']; } } /* Set up the parameters to connect to the database */ $params = array ('host' => $host, 'username' => $username, 'password' => $password, 'dbname' => $db); try { /* Connect to the database */ $conn = Zend_Db::factory('PDO_MYSQL', $params); $value1 = $conn->quote($value1); $value2 = $conn->quote($value2); $value3 = $conn->quote($value3); /* * Create a SQL string and use the values that are protected from SQL injections */ $sql = 'SELECT * FROM table1 WHERE condition1 = $value1 AND condition2 = $value2' .' AND condition3 = $value3'; /* Get the results of the query */ $result = $conn->query($sql); /* Are there results? */ if ($rows = $result->fetchAll( )) { /* Create a JSON result string */ $value = array( ); $value['results'] = array( ); $value['results']['result'] = array( ); /* Loop through the results */ foreach($rows in $row) $value['results']['result'][$i] = array('column1' => $row['column1'], 'column2' => $row['column2']); $output = $json->encode($value); } } catch (Exception $ex) { $output = "{error: 'There was an error retrieving the data.'}"; } echo $output; ?>
In this example, the JSON string that is passed to the server
is read into the variable $raw_data
. The string is then decoded
using the decode( )
method from
the json
class. This decoded
object looks like this:
Array ( [parameters] => Array ( [param] => Array ( [0] => Array ( [id] => 1 [value] => data1 ) [1] => Array ( [id] => 2 [value] => data2 ) [2] => Array ( [id] => 3 [value] => data3 ) ) ) )
From here, it is just a matter of looking through the array
and pulling out the values of each index. After that, an array is
created with the response data. This array is encoded into a JSON
string with the encode( )
method,
and then it is sent back to the client. The response to the client
looks like this:
{"results":{"result":[{"column1":12,"column2":13},{"column1":3,"column2":5}]}}
It is then up to the client to parse this string.
When instantiating the Services_JSON
class, the parameter that
was passed, SERVICES_JSON_LOOSE_TYPE
, forced the
decode( )
method to create
associative arrays. If this value was not passed, the decode( )
method would have returned
objects. This value can be passed with the Boolean OR (|
) and the value SERVICES_JSON_SUPPRESS_ERRORS
which, you
guessed it, suppresses any errors when decoding or
encoding.
Back on the client, after the server has done what it needs to
do, the response is set in the responseText
property of the XMLHttpRequest
object. Once the readyState
and status
are set to 4
and 200
, respectively, the JSON string can be
saved and eval( )
‘d, as in Example 4-14.
Example 4-14. Getting a JSON string ready to parse
/* * Example 4-14, Getting a JSON string ready to parse. */ /** * This function, parseResponse, checks the /request/ object for its /readyState/ * and /status/ to see if the response is complete, and then parses the * /responseText/ (the JSON string) to get the results from the server. */ function parseResponse( ) { /* Is the /readyState/ for the /request/ a 4 (complete)? */ if (request.readyState == 4) { /* Is the /status/ from the server 200? */ if (request.status == 200) { var jsonString = request.responseText; var response = eval('(' + jsonString + ')'), // here is where the parsing would begin. } else alert('There was a problem retrieving the data: ' + request.statusText); request = null; } }
The response
is now a
JavaScript object, and the object can be walked, searched, or
manipulated just like any other DOM object. Example 4-15 shows some ways to get
at the data from the JavaScript object created with a JSON
string.
Example 4-15. Parsing the JSON response object
/* * Example 4-15, Parsing the JSON response object. */ /** * This function, parseResponse, checks the /request/ object for its /readyState/ * and /status/ to see if the response is complete, and then parses the * /responseText/ (the JSON string) to get the results from the server. */ function parseResponse( ) { /* Is the /readyState/ for the /request/ a 4 (complete)? */ if (request.readyState == 4) { /* Is the /status/ from the server 200? */ if (request.status == 200) { var jsonString = request.responseText; var response = eval('(' + jsonString + ')'), var out = '<div>'; /* Loop through the object and create the checkboxes*/ for (i = 0, il = response.Parameters.param.length; i < il; i++) { var resp = response.Parameters.param[i]; out += '<input type="checkbox" name="choice_' + resp.id + '" value="' + resp.value + '" /><br />'; } out += '</div>'; document.getElementById('choices').innerHTML = out; } else alert('There was a problem retrieving the data: ' + request.statusText); request = null; } }
Looking at this example, you probably see just how easy it is to get to the data you need. That is part of the beauty of JSON.
I have shown you how to make Ajax calls between the client and the server with both XML and JSON. So which one should you use? I could tell you that you should use JSON because it is lightweight and easy to use on the client. Or, I could tell you that you should use XML because it is better able to describe data when complicated data sets are moved back and forth between the client and the server. I could tell you these things, but I am not going to. The fact is that it really is up to the developer and the situation that she is in.
That’s not to say that you cannot make an informed opinion once I show you the facts about both XML and JSON.
One of the arguments for JSON is that it is lightweight in nature. Earlier I said I would tell you whether the JSON example or the XML example was smaller in byte size: the JSON example contains 248 bytes (count them yourself if you like), whereas the XML example contains 240 bytes. So much for JSON being lightweight compared to XML. In reality, the complexity and size of the data being exchanged determines which format is smaller in size.
Another argument for JSON is that it is easier to read by both
humans and machines. It is true that it takes less time to parse
through JSON than XML; thus, JSON is easier for the machine to “read.”
It can actually take longer to eval(
)
a JSON string than to create a DOM Document
object depending on the size of the
data. Based on this, you could say that for machines, it is a wash.
But what about humans? I think that is a matter of developer opinion.
Beauty is in the eye of the beholder, after all.
Here are some arguments for XML. XML works as a good data exchange format for moving data between similar applications. XML is designed to have a structure that describes its data, enabling it to provide richer information. XML data is self-describing. XML supports internationalization of its data. XML is widely adopted by the technology industry.
You can counter all of these arguments with one simple statement: the same is true for JSON. JSON can provide the same solid data exchange between like systems. JSON is also built on structures (those structures are objects and arrays). JSON is just as self-describing as XML. JSON supports Unicode, so internationalization is not a problem. To be fair, JSON is pretty new, and the industry is already adopting it. Only time will tell which has more widespread adoption. Those arguments could be rebuffed easily. Now, let’s take a look at some other arguments.
XML has a simple standard, and therefore you can process it more easily. XML is object-oriented. XML has a lot of reusable software available to developers to read its data. For the first argument, it is true that XML has a simple standard, but JSON actually has a simpler structure and is processed more easily. Let the record show that XML is not object-oriented, but instead is document-oriented. In that same line of thinking, JSON is actually data-oriented, making it easier to map to object-oriented systems. As for software, XML needs to have its structured data put into a document structure, and it can be complicated with elements that can be nested, attributes that cannot be nested, and an endless number of metastructures that can be used to describe the data. JSON is based entirely on arrays and objects, making it simple and requiring less software to translate.
I could do this all day. However, I hope you now understand that there is no right answer. I cannot tell you which data exchange format is better any more than I could tell you which server-side frameworks to use. Each developer should decide, after asking the following questions:
What are my client and server platforms (what languages will I use)?
How large are the data sets I will be transferring?
Am I more comfortable with JavaScript or XML/XSLT?
Will I be using outside web services? If so, what format do they prefer?
How complex are the data sets being used?
Do I completely control the server that the client will be getting responses from?
Regarding question 1, you can decide which format to use simply from the languages you will be using. If you aren’t going to be using JavaScript on the client side, JSON doesn’t make sense. Likewise, the support for XML or JSON on the server side can be a major factor.
As for question 2 regarding the size of the data sets that will be transferred, JSON may be a better solution than XML if transferred byte size is a concern. Remember, JSON is also faster for parsing data—larger data sets should be processed faster with JSON than with XML. If you are not passing a large amount of data, XML may be the better alternative. A small, already formatted XHTML data set passed to the client can very quickly be utilized; JSON would have to be formatted.
There isn’t much I need to say about question 3. I think it is self-explanatory.
Question 4 is good to consider. If you will be using outside web services in your applications, your hands may be tied regarding the format to use to request data, and certainly, your choices will be limited for the data sent back from the web service in its response.
Question 5 is pretty easy to answer. JSON works great when the
data being described is just that—data. XML is much better suited for
handling data such as sounds, images, and some other large binary
structures because it has the handy <[CDATA[]]>
feature. I am not saying
it is a good idea to send this type of data using Ajax. All I am
saying is that it is possible with XML and not with JSON.
As for question 6, as I just explained, if you do not have
complete control of both sides of the data exchange, it could be
dangerous to use JSON as the format. This is because JSON requires the
eval( )
method to parse its data.
The way around this is to use a JSON parser. With a parser, only the
JSON text is parsed, making it much safer. The only downside to the
JSON parser is that it slows down response object creation.
Deciding on a data exchange format is hard and often leads to second-guessing or, worse, rewriting code after switching formats. My advice is to choose a format and stick with it, but remember this: always use the right tool for the right job.
Earlier in the chapter, I used the Sarissa library to aid in XSLT and XPath development. Sarissa is one of many frameworks available for Ajax and JavaScript. It would not be practical to highlight all of them, but in this section I will cover a few of the most popular.
The Dojo Toolkit, which you can find at http://www.dojotoolkit.org/, is a component-based open source JavaScript toolkit that is designed to speed up application development on multiple platforms. It is currently dual-licensed under the terms of the BSD License and the Academic Free License. Dojo is a bootstrapping system, whereby you can add individual toolkit components once you’ve loaded the base component. Dojo’s components, known as packages, can be single or multiple files, and may be interdependent.
Some of the toolkit’s notable features are:
A robust event system that allows for code to execute not only on DOM events, but also on function calls and other arbitrary events
A widget system that allows for the creation of reusable components, and includes a number of prebuilt widgets: a calendar-based date picker, inline editing, a rich-text editor, charting, tool tips, menus and trees, and more
An animation library that allows for the creation of reusable effects, and includes a number of predefined effects, including fades, wipes, slides, drag and drop, and more
A wrapper around the XMLHttpRequest
object, allowing for
easier cross-browser Ajax development
A library of utilities for DOM manipulation
More recent Dojo developments include the announcement of official support by both Sun Microsystems[1] and IBM[2] (including code contributions), and the Dojo Foundation’s involvement with the OpenAJAX Alliance (http://www.openajax.org/).
As of this writing, the current version of the Dojo Toolkit is 1.3.2.
The Prototype Framework, which you can find at http://www.prototypejs.org/, is a JavaScript framework that is used to develop foundation code and to build new functionality on top of it. Sam Stephenson developed and maintains it. Prototype is a standalone framework, though it is part of Ruby on Rails and is found in Rails’ source tree. According to the September 2006 Ajaxian survey, Prototype is the most popular of all the Ajax frameworks.
Prototype is a set of foundation classes and utilities, and so it does not provide any of the flashy Web 2.0 components found in other JavaScript frameworks. Instead, it provides functions and classes you can use to develop JavaScript applications. Some of the most notable functions and classes are:
The dollar sign functions—$( ),
$F( ), $A( ), $$( )
, and so on
The Ajax
object
The Element
object
A number of JavaScript libraries and frameworks are built on top of Prototype, most notably script.aculo.us and moo.fx.
In this book, I am using Prototype version 1.5.1.1, though the latest version as of this writing is 1.6.
script.aculo.us, which you can find at http://script.aculo.us/, is a JavaScript library that provides developers with an easy-to-use, cross-browser user interface to make web sites and web applications fly. Thomas Fuchs, a partner at wollzelle, created script.aculo.us, and open source contributors extend and improve it. script.aculo.us is released under the MIT License, and like Prototype, it is also included with Ruby on Rails and extends the Prototype Framework by adding visual effects, user interface controls, and utilities.
script.aculo.us features include:
Visual effects, including opacity, scaling, moving, and highlighting, among others
Dragging and dropping, plus draggable sorting
Autocompletion and inline editing
Testing
As of this writing, the current version of script.aculo.us is 1.8.2.
moo.fx, which you can find at http://moofx.mad4milk.net/, is different from the other frameworks that build on Prototype in that it uses a stripped-down version of the Prototype library: Prototype Lite. Valerio Proietti created moo.fx and it is released under the MIT License. moo.fx is said to be a super-lightweight JavaScript effects library. Some of the classes that it has implemented include simple effects on elements (changing height, width, etc.), more complex effects (such as accordion, scrolling, cookie memory, and so on), and an Ajax class.
moo.fx is not a replacement for script.aculo.us, and instead creates its own effects for Ajax web applications.
As of this writing, the current version of moo.fx is 2.
DWR, which you can find at http://directwebremoting.org/dwr/index.html, is a Java open source library that allows developers to write Ajax web sites by permitting code in a browser to use Java functions running on a web server just as though it were in the browser. DWR works by dynamically generating JavaScript based on Java classes. The code then does some “Ajax magic” to make it feel like the execution is happening on the browser, but in reality the server is executing the code and then DWR is shoveling the data back and forth.
DWR consists of two main parts:
A Java servlet running on the server that processes requests and sends responses back to the browser
JavaScript running in the browser that sends requests and can dynamically update the web page
DWR acts differently than other frameworks and libraries because the pushing of data back and forth gives its users a feel much like conventional RPC mechanisms such as RMI and SOAP, with the added benefit that it runs over the Web without requiring web browser plug-ins. DWR is available under the Apache Software License v2.0.
As of this writing, the current version of DWR is 2.0.
jQuery, which you can find at http://jquery.com/, is a new type of JavaScript library that is not a huge, bloated framework promising the best in Ajax, nor just a set of needlessly complex enhancements to the language. jQuery is designed to change the way you write JavaScript code by how the DOM is accessed. John Resig wrote and maintains it, and the developer community contributes to it. jQuery is available under the MIT License.
jQuery achieves its goal of new JavaScript scripting by stripping all the unnecessary markup from common, repetitive tasks. This leaves them short, smart, and understandable. The goal of jQuery, as stated on its web site, is to make it fun to write JavaScript code.
As of this writing, the current version of jQuery is 1.3.2.
As I explained earlier in the chapter, Sarissa (http://sarissa.sourceforge.net/) is a library that encapsulates XML functionality. It is good for XSLT- and XPath-related problems. It has good DOM manipulation functions, as well as XML serialization. Its major benefit is that it provides cross-browser functionality without the developer having to take care of everything else, and it is small in size. It is an ideal library when a developer needs nothing more complicated than some XML DOM manipulation.
Sarissa is distributed under the GNU GPL version 2 and later, the GNU LGPL version 2.1 and later, and the Apache Software License v2.0. Having three licenses to choose from makes Sarissa a flexible library as well.
As of this writing, the latest release of Sarissa is 0.9.9.4.
Of course, you can use many other frameworks to develop Ajax web applications. Frameworks such as Rico (http://openrico.org/), Yahoo! UI (http://developer.yahoo.com/yui/), and Ajax.NET (formerly Atlas; http://ajax.asp.net/) are also popular depending on the development environment, though their use is more in the four to five percent range. The examples in the rest of this book will use many of the frameworks I’ve highlighted here.
You can find an exhaustive list of frameworks for Ajax in Appendix A of Ajax Design Patterns by Michael Mahemoff (O’Reilly). His list highlights each framework and explains its licensing terms.
In general, frameworks are meant to ease the grunt work developers usually have to perform when building a foundation before beginning to code. Frameworks allow developers to jump right into the important functional parts of the application they are working on. Beyond that, good foundation frameworks such as Prototype also speed up the time it takes to program through the classes and functions they offer. In this section, we will explore some of the ways these foundations help with Ajax application programming, and how they will crop up throughout the rest of this book.
As I said before, Prototype is most famous for the dollar sign
function, $( )
. Other frameworks
have been duplicating Prototype’s functionality since it was
introduced. So, what does it do? $(
)
is a helper function that provides references to any
element based on the id
s passed
to it—that’s right, the plural of id
. For example:
var navigation = $('img1'),
In this example, the navigation
variable is set to the element
with id='img1'
. Here is an
example of multiple id
s being
passed:
var imageArray = $('img1', 'img2', 'img3'),
Now, $( )
returns an array
of elements with any id
s that
match what was passed in.
This is just the tip of the iceberg when it comes to what
Prototype can help with. We’ll take a look at three other helper
functions Prototype provides before we talk about how Prototype
helps with Ajax. These helper functions are $F( ), document.getElementsByClassName( )
,
and, as of Prototype version 1.5.0_rc0, $$(
)
.
$F( )
returns the value of
any form field that is identified by the id
passed to the function. For example,
with the following in a form:
<select name="food_choice" id="food_choice"> <option selected="selected" value="filet">Filet Mignon</option> <option value="poulet">Chicken Cordon Bleu</option> <option value="fishay">Poached Salmon</option> </select>
it is possible to get the value of food_choice
like this:
var food_choice = $F('food_choice'),
The variable food_choice
would be set with filet
.
Prototype extended the document
object with document.getElementsByClassName( )
, a
method that can be very handy. For example, to get all of the
elements that have class='borderless'
, you simply need to do
the following:
var imageArray = document.getElementsByClassName('borderless'),
This method is even more powerful. Consider the following:
var imageArray = document.getElementsByClassName('borderless', $('helpWindow'));
In this case, the array would return all elements with
class='borderless'
that are
inside the element with id='helpWindow'
.
$$( )
is a powerful
function that was added to the Prototype library only recently. With
this function, using the standard CSS selector syntax allows you to
select corresponding elements. For example:
var menuItemArray = $$('#menuitem div'),
Here, all div
elements
inside 'menuitem'
are returned.
Or:
var linkArray = $$('a.menuPath'),
This code returns an array of links that have the class name
menuPath
. You can probably see
how powerful $$( )
is.
Prototype has other helper functions as well, such as $H( ), $R( )
, and $A( )
. The best documentation for all of
Prototype’s functions and classes is on its official site at http://www.prototypejs.org/.
Prototype has three objects for use with Ajax functionality:
Ajax.Request, Ajax.Updater
, and
Ajax.PeriodicalUpdater
. Our main
focus will be with Ajax.Request
,
though we will briefly discuss the other two as well. Here is a
basic Ajax request using Ajax.Request
:
new Ajax.Request(URL
, {
method: 'get',
parameters: 'param1=data1',
onSuccess: parseResponse,
onFailure: handleError
});
The constructor takes a URL and options in the form of an
object. In our example, we are sending parameter param1
to URL
via the GET method. If the request is successful, it will call
parseResponse
with an XMLHttpRequest
object. If the request were
to fail, the function handleError
would be called. You can see a list of all available options in
Table 4-3.
Table 4-3. Optional arguments to pass to the constructor
Example 4-16 shows a more complete example of how to call an Ajax request using Prototype.
Example 4-16. Prototype in action for an Ajax request
/* * Example 4-16, Prototype in action for an Ajax request. */ /* Create an Ajax call to the server */ new Ajax.Request(URL, { method: 'post', parameters: 'param1=data1¶m2=data2¶m3=data3', onSuccess: parseResponse, onFailure: function(xhrResponse) { alert('There was a problem retrieving the data: ' + xhrResponse.statusText); } }); /** * This function, parseResponse, takes the /xhrResponse/ object that is * the response from the server and parses its /responseXML/ to create a * list from the results. * * @param {Object} xhrResponse The response from the server. */ var parseResponse = function(xhrResponse) { var response = xhrResponse.responseXML; var paramList = response.getElementsByTagName('param'), var out = '<ul>'; /* Loop through the /param/ elements in the response to create the list items */ for (i = 0, il = paramList.length; i < il;) { out += '<li>' + paramList[i++].firstChild.nodeValue + '</li>'; } out += '</ul>'; $('list').innerHTML = out; }
This example has the same functionality as Example 4-6 does; however, the
developer has much less to code. This makes his job easier, and he
can concentrate instead on the best way to parse the XML, how to
display it, and so on. We set the request to a POST, and then
created our URL-encoded parameter string. onSuccess
called the function parseResponse
, while onError
was assigned an inline function
definition. The biggest change was in the parseResponse
function itself. Notice how
we did not have to check the XMLHttpRequest
object’s readyState
or status
. This was already done for us, or
we wouldn’t be in the function.
All that was left was to parse through the response; no new
code here. The last thing to notice is that I used $( )
to get the element with id='list'
.
Something you may not realize unless you have traced through
the Ajax.Request
code is that
in the setRequestHeaders( )
method, the object sets certain headers that are set on every HTTP
request. They are:
X-Requested-With: XMLHttpRequest X-Prototype-Version: 1.5.1.1
The server could check these headers to detect whether the request was an Ajax call and not a regular call.
Now we know how Ajax.Request
works, but what about
Ajax.Updater
? The syntax for
Ajax.Updater
is:
new Ajax.Updater('myDiv', URL,
{ method: 'get', parameters: 'param1=data1' });
Here is the difference. The first parameter passed is the
container that will hold the response from the server. It can be an
element’s id
, the element itself,
or an object with two properties:
object.success
Element (or id
) that
will be used when the request succeeds
object.failure
Element (or id
) that
will be used otherwise
Also, Ajax.Updater
has
options that the normal Ajax.Request
does not, as shown in Table 4-4.
Table 4-4. Ajax.Updater-specific options
Option | Description |
---|---|
| Class telling the object how the content will be inserted. It is one of:
|
| Tells the object whether a script block will be evaluated when the response arrives. |
Ajax.Updater
works by
extending the functionality of Ajax.Request
to actually make the request
to the server. It then takes the response to insert it into the
container.
Finally, the syntax for the Ajax.PeriodicUpdater
object is:
new Ajax.PeriodicUpdater('myDiv', URL
, {
method: 'get',
parameters: 'param1=data1',
frequency: 20 });
Like the Ajax.Updater
class, the first parameter passed is the container that will hold
the response from the server. It can be an element’s id
, the element itself, or an object with
two properties:
object.success
Element (or id
) that
will be used when the request succeeds
object.failure
Element (or id
) that
will be used otherwise
Also like Ajax.Updater,
Ajax.PeriodicUpdater
has options that the normal Ajax.Request
does not, as shown in Table 4-5.
Table 4-5. Ajax.PeriodicUpdater-specific options
Option | Description |
---|---|
| Tells the object what
the progressive slowdown for the object’s refresh rate will
be when the response received is the same as the last one.
For example, with Leave this option
undefined, or set |
| Tells the object the interval in seconds that it should wait between refreshes. |
Ajax.PeriodicUpdater
works
by calling Ajax.Updater
internally on its onTimerEvent( )
method, and does not extend Ajax.Updater
like it extends Ajax.Request
.
In this section, I presented a brief tutorial on how you can
use a client-side framework such as Prototype to greatly increase
development speed by producing a robust foundation library. This can
help with more than just making requests to the server, as I showed
here. As you progress through the book, you will find many
situations in which a framework made things easier. These frameworks
will not necessarily be Prototype, either. Now that you have this
background, it is time to manipulate the DOM Document
object that an Ajax call, or the
DOM object itself, may return to you.
[1] * You can find Sun Microsystems’ article at http://www.sun.com/smi/Press/sunflash/2006-06/sunflash.20060616.1.xml
[2] †
44.200.94.150