O'Reilly logo

Ajax: The Definitive Guide by Anthony T. Holdener III

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Foundations: Scripting XML and JSON

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.

XML

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

onreadystatechange

The function assigned to this property, which is an event listener, is called whenever the readyState of the object changes.

readyState

This property represents the current state that the object is in. It is an integer that takes one of the following:

  • 0 = uninitialized (The open( ) method of the object has not been called yet.)

  • 1 = loading (The send( ) method of the object has not been called yet.)

  • 2 = loaded (The send( ) method has been called, and header and status information is available.)

  • 3 = interactive (The responseText property of the object holds some partial data.)

  • 4 = complete (The communication between the client and server is finished.)

responseText

A version of the returned data in a plain-text format.

responseXML

A version of the returned data that has been instantiated into a Document Object Model (DOM) Document object.

status

The response status code that the server returned, such as 200 (OK) or 404 (Not Found).

statusText

The text message associated with the response status code the server returned.

Table 4-2. The XMLHttpRequest object’s methods

Property

Description

abort( )

Cancels the object’s current request.

getAllResponseHeaders( )

Returns all of the response headers; headers and values as a formatted string.

getResponseHeader(header)

Returns the value of the passed header as a string.

open(method, URL[, asynchronous flag[, username[, password]]])

Prepares the request by assigning:

method

The method the request will use, either GET or POST.

URL

The destination of the request.

asynchronous flag

Optional Boolean value determining whether to send the request asynchronously or synchronously.

username

Optional username to pass to the URL.

password

Optional password to pass to the URL.

send([contents])

Sends the request with the optional contents, either a postable string or a DOM object’s data.

setRequestHeader(header, value)

Sets the request header with the value, but the open( ) method must be called first.

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.

XML Requests and 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&param2=data2&param3=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: \n' +
                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.

Parsing

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: \n' +
                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.

XML in a String

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: \n' +
                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.

XPath

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.

XSLT

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: \n' +
                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.

Warning

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

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/.

JSON Requests and Responses

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.

Tip

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.

Parsing

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: \n' +
                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: \n' +
                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.

Choosing a Data Exchange Format

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:

  1. What are my client and server platforms (what languages will I use)?

  2. How large are the data sets I will be transferring?

  3. Am I more comfortable with JavaScript or XML/XSLT?

  4. Will I be using outside web services? If so, what format do they prefer?

  5. How complex are the data sets being used?

  6. 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.

A Quick Introduction to Client Frameworks

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

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.

Prototype

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

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

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

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

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.

Sarissa

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.

Others

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.

Tip

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.

Simplifying Development

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.

Prototype Helper Functions

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 ids 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 ids being passed:

var imageArray = $('img1', 'img2', 'img3');

Now, $( ) returns an array of elements with any ids 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 and Ajax

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

Option

Description

parameters

A URL-encoded string to be sent with the request in the URL.

method

The type of request for the call. Is post or get, with the default being post.

asynchronous

Tells the object whether it should make the call asynchronously. Is true or false, with the default being true.

requestHeaders

An array of request headers to be sent with the call. They should be in the form:

['header1', 'value1', 'header2', 'value2']

postBody

Contents that are passed with the body of the request. This applies to a post only.

onInteractive, onLoaded, onComplete

Assigns a function to call when the XMLHttpRequest object triggers one of these events. The function is passed the XMLHttpRequest object.

on404, onXXX

Assigns a function to call when the server returns one of these response codes. The function is passed the XMLHttpRequest object.

onSuccess

Assigns a function to call when the request is completed successfully. The function is passed the XMLHttpRequest object and the returned JSON object (if any).

onFailure

Assigns a function to call when the server returns a fail code. The function is passed the XMLHttpRequest object.

onException

Assigns a function to call when there is a client-side error. The function is passed the XMLHttpRequest object.

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&param2=data2&param3=data3',
    onSuccess: parseResponse,
    onFailure: function(xhrResponse) {
        alert('There was a problem retrieving the data: \n' +
           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'.

Tip

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

insertion

Class telling the object how the content will be inserted. It is one of:

  • Insertion.After

  • Insertion.Before

  • Insertion.Bottom

  • Insertion.Top

evalScripts

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

decay

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 decay: 2, when a decay is to occur, the object will wait twice as long before refreshing. If a decay occurs again, the object will wait four times as long before refreshing, and so on.

Leave this option undefined, or set decay: 1 to avoid decay.

frequency

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]

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required