Wrox Home  
Search
Professional Ajax, 2nd Edition
by Nicholas C. Zakas, Jeremy McPeak, Joe Fawcett
March 2007, Paperback


Excerpted from Professional Ajax 2nd Edition

Ajax in Prototype

by Nicholas C. Zakas

With the popularity of Ajax applications exploding in 2005, developers and companies began looking for ways to streamline the process. As with many common programming practices, Ajax involves a lot of repetitive procedures that can be identified and simplified for common use. It wasn't long before JavaScript developers started introducing libraries to ease the redundant and sometimes quirky behavior of Ajax communication techniques. These libraries sought to break outside of the hidden frame and XHR modalities of communication and introduce their own methods (which typically are just wrappers for already accepted forms of Ajax communication). Remember, the goals of such libraries are to free the developer from worrying about cross-browser Ajax issues by hiding the details.

Prototype

One JavaScript library that has gained considerable popularity with the emergence of Ajax is Prototype, available at http://prototype.conio.net. Prototype is not simply an Ajax library; it is actually a complete JavaScript framework designed to ease the development of all types of JavaScript solutions.

It is beyond the scope of this article to fully explore all of Prototype's features, so the focus here is on its Ajax capabilities.

The Ajax.Request Object

Most of Prototype's low-level Ajax features are contained on the aptly named Ajax object. The Ajax object has several properties containing methods and constructors for useful objects. The simplest object, and the most similar to XHR, is the Ajax.Request object, which has the following constructor:

request = new Ajax.Request(url, options);

The first argument is the URL to send the request to. The second argument is an object containing any number of options for the request. As soon as the creation of the Ajax.Request object is complete, the request is sent (think of it as combining XHR's open() and send() methods in one call). For this reason, the options object is very important.

The Options Object

The options object, as used in the Ajax.Request() constructor, contains all of the information about the request except for the URL. In its simplest form, the options object contains the following properties:

  • method: Either "get" or "post."
  • parameters: The data to be sent to the URL. Typically, a URL-encoded string of name-value pairs, but can also be other data formats when method is "post."
  • onSuccess: Function to call when the response has been successfully received. Similar to the success() method in the Yahoo! Connection Manager, it fires when the status of a response is between 200 and 300.
  • onFailure: Function to call when the response has failed. Similar to the failure() method in the Yahoo! Connection Manager, it fires when the status of a response is not between 200 and 300.

Also like the Connection Manager callback object, the Ajax.Request options object is defined with a simple object literal, such as:

var oOptions = {
    method: "get",
    parameters: "first%20name=Nicholas&last%20name=Zakas",
    onSuccess: function (oXHR, oJson) {
        //your code here
    },
    onFailure: function (oXHR, oJson) {
        //your code here
    }
}

The onSuccess() and onFailure() methods are functions that are passed two arguments: the XHR object used to make the request and an optional JSON object with additional information about the request. The second argument is used mostly in conjunction with a feature in Ruby on Rails (www.rubyonrails.org), so it won't be covered here.

When using Ajax.Request, the URL should be specified without a query string and the parameters property should be used:

var oOptions = {
    method: "get",
    parameters: "name=Nicholas",
    onSuccess: function (oXHR, oJson) {
        alert("Response received successfully.");
    },
    onFailure: function (oXHR, oJson) {
        alert("Request was unsuccessful.");
    }
};
var oRequest = new Ajax.Request("test.php", oOptions);

The combining of the URL and the parameters property is handled by Prototype behind the scenes. This same methodology can be used for POST requests by just changing the method property:

var oOptions = {
    method: "post",
    parameters: "name=Nicholas",
    onSuccess: function (oXHR, oJson) {
        alert("Response received successfully.");
    },
    onFailure: function (oXHR, oJson) {
        alert("Request was unsuccessful.");
    }
};
var oRequest = new Ajax.Request("test.php", oOptions);

In this way, Prototype simplifies Ajax requests so that the switch between a GET and a POST request is simply a one-step change. Prototype also handles setting the default POST content type on the XHR request, further simplifying things for the developer.

The requestHeaders Property

To add headers to the outgoing request, specify the requestHeaders property in the options object. This must be an array with an even number of items so that the first item is the name of a header, the second item is the value of that header, and so on. For example:

var oOptions = {
    method: "get",
    parameters: "name=Nicholas",
    requestHeaders: [ "header1", "header1 value", "header2", "header2 value"],
    onSuccess: function (oXHR, oJson) {
        alert("Response received successfully.");
    },
    onFailure: function (oXHR, oJson) {
        alert("Request was unsuccessful.");
    }
};

This code adds two headers to the request: "header1", which has a value of "header1 value", and "header2", which has a value of "header2 value". This is the same as calling setRequestHeader() on an XHR object for each header.

The asynchronous Property

By default, all requests initiated using Ajax.Request are sent asynchronously, meaning that the JavaScript doesn't wait for a response. If, for some reason, you need to send a request synchronously, which locks the JavaScript code execution and the user interface, it can be accomplished by setting the asynchronous property to false:

var oOptions = {
    method: "get",
    parameters: "name=Nicholas",
    asynchronous: false
};

In this case, there is no need for onSuccess() or onFailure(), because the next line of code after the request is sent can handle all conditions. After the call has been completed, the XHR object can be accessed directly via Ajax.Request.transport.

Remember, synchronous requests should be used sparingly and only for small amounts of data, since they lock the user interface while the request is being processed.

Other Events

The Ajax.Request object supports several custom events outside of the success/failure realm. Each of these events can be handled with developer-defined functions through the options object. The complete list of event handlers is as follows:

  • onException(): Called when an error occurred in the JavaScript code while trying to execute the request or during a callback function call.
  • onLoaded(): Called when the response has been received but not evaluated. The same as XHR ready state 2. Not recommended for use due to cross-browser differences.
  • onLoading(): Called repeatedly while a request is waiting for or receiving a response.
  • onInteractive(): Called when the response has been received and parsed; some information is available. The same as XHR ready state 3. Not recommended for use due to cross-browser ­ differences.
  • onComplete(): Called when the response has been completely received and parsed. The same as XHR ready state 4.

Each of these event handlers are passed the same two arguments as onSuccess() and onFailure(): the XHR object used to make the request and an optional second object containing response information.

In general, onException() is probably the most useful of these event handlers, since onSuccess() and onFailure() handle most of the important cases.

GET Example

In Chapter 2, "Ajax Basics," of the book, Professional Ajax 2nd Edition (Wrox, 2007, ISBN: 978-0-470-10949-6), we create a GET request with XMLHttp Requests (XHR). Here we show how the code for the Prototype version is fairly straightforward:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Prototype GET Example</title>
    <script type="text/javascript"src="prototype.js"></script>
    <script type="text/javascript">
        //<![CDATA[
        function requestCustomerInfo() {
            var sId = document.getElementById("txtCustomerId").value;
            var oOptions = {
                method: "get",
                parameters: "id=" + sId,
                onSuccess: function (oXHR, oJson) {
                    displayCustomerInfo(oXHR.responseText);
                },
                onFailure: function (oXHR, oJson) {
                    displayCustomerInfo("An error occurred: " + 
                                                       oXHR.statusText);
                }
            };
            var oRequest = new Ajax.Request("GetCustomerData.php", oOptions);
        }
        
        function displayCustomerInfo(sText) {
            var divCustomerInfo = document.getElementById("divCustomerInfo");
            divCustomerInfo.innerHTML = sText;
        }
        //]]>
    </script>
</head>
<body>
    <p>Enter customer ID number to retrieve information:</p>
    <p>Customer ID: <input type="text" id="txtCustomerId" value="" /></p>
    <p><input type="button" value="Get Customer Info" 
              onclick="requestCustomerInfo()" /></p>
    <div id="divCustomerInfo"></div>
</body>
</html>

The important thing to note here is that the query string is not appended directly to the URL, as in the previous examples.

POST Example

The XHR POST example can also be changed to use Prototype. Unfortunately, since Prototype doesn't include a method to encode the data in a form, you'll still need all the code that was in the original example. The only thing that changes is the sendRequest() function:

function sendRequest() {
    var oForm = document.forms[0];
    var sBody = getRequestBody(oForm);
            
    var oOptions = {
        method: "post",
        parameters: sBody,
        onSuccess: function (oXHR, oJson) {
            saveResult(oXHR.responseText);
        },
        onFailure: function (oXHR, oJson) {
            saveResult("An error occurred: " + oXHR.statusText);
        }
    };
            
    var oRequest = new Ajax.Request("SaveCustomer.php", oOptions);      
}

Note that the data to POST is still passed in using the parameters property of the options object.

The Ajax.Updater Object

Each of the two XHR examples had something in common: they outputted a status message to an element on the page once the response had been received. This is actually a fairly common use case of Ajax calls, and Prototype has made it easy to handle this automatically using the Ajax.Updater object.

The Ajax.Updater object is created using a constructor similar to that of Ajax.Request:

request = new Ajax.Updater(element_id, url, options);

Behind the scenes, Ajax.Updater uses Ajax.Request to initiate a request, so it should come as no surprise that the arguments to the constructor include the ones needed for Ajax.Request. The only difference is the insertion of an additional argument at the beginning of the list: an HTML element's ID. When a response is received, Ajax.Updater takes the responseText from the XHR object and puts it into the HTML element with the given ID using the innerHTML property.

When you are using Ajax.Updater, it's not necessary to assign the onSuccess() or onFailure(), methods because the responseText is output to the HTML element either way. For example, consider how the GET example can change using Ajax.Updater:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Prototype Updater Example</title>
    <script type="text/javascript"src="prototype.js"></script>
    <script type="text/javascript">
        //<![CDATA[
        function requestCustomerInfo() {
            var sId = document.getElementById("txtCustomerId").value;
            var oOptions = {
                method: "get",
                parameters: "id=" + sId
            };
            var oRequest = new Ajax.Updater("divCustomerInfo", 
                                    "GetCustomerData.php", oOptions);
        }
        //]]>
    </script>
</head>
<body>
    <p>Enter customer ID number to retrieve information:</p>
    <p>Customer ID: <input type="text" id="txtCustomerId" value="" /></p>
    <p><input type="button" value="Get Customer Info" 
              onclick="requestCustomerInfo()" /></p>
    <div id="divCustomerInfo"></div>
</body>
</html>

In this code, the displayCustomerInfo() function has been completely removed since its only purpose was to display text in divCustomerInfo. Note that the ID is passed in as the first argument of the Ajax.Updater() constructor as well. That's all that is necessary to maintain the functionality of the example.

Of course, there is the possibility that a 404 or other error status may occur, and it may bring with it some ugly HTML. To handle this case, there is an alternate constructor for Ajax.Updater where the first argument is an object that can direct the output to one HTML element for a successful response and another for a failure, such as:

var oRequest = new Ajax.Updater({ 
    success: "success_element_id",
    failure: "failure_element_id"
}, url, options);

Realistically, however, you probably want the output to be displayed only when the request was successful. In that case, just assign the success element ID and add an onFailure() method to the options object, such as:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Prototype Updater Failure Example</title>
    <script type="text/javascript"src="prototype.js"></script>
    <script type="text/javascript">
        //<![CDATA[
        function requestCustomerInfo() {
            var sId = document.getElementById("txtCustomerId").value;
            var oOptions = {
                method: "get",
                parameters: "id=" + sId,
                onFailure: function (oXHR, oJson) {
                    alert("An error occurred: " + oXHR.status);
                }
            };
            var oRequest = new Ajax.Updater({ 
                success: "divCustomerInfo"
            }, "GetCustomerData.php", oOptions);
        }
        //]]>
    </script>
</head>
<body>
    <p>Enter customer ID number to retrieve information:</p>
    <p>Customer ID: <input type="text" id="txtCustomerId" value="" /></p>
    <p><input type="button" value="Get Customer Info" onclick="requestCustomerInfo()" /></p>
    <div id="divCustomerInfo"></div>
</body>
</html>

In this revision of the previous example, data is displayed on the page only if the request was successful because only the success property is provided in the first argument. If a request fails, then an alert is displayed using the onFailure() method of the options object.

The Ajax.Responders Object

Suppose that you want the same action to take place each time an Ajax request takes place. Why would you want to do this? Think in terms of a generic loading message that should be displayed every time there is a request in progress (to ensure the user interface is consistent). Using other libraries or XHR directly, you'd be forced to manually call a specific function each time. Prototype makes this easy using the Ajax.Responders object.

To set up event handlers for all Ajax requests, define an options object containing onCreate() and/or onComplete() methods, such as:

var oGlobalOptions = {
    onCreate: function (oXHR, oJson) {
        alert("Sending request...");
    },
    onComplete: function (oXHR, oJson) {
        alert("Response received.");
    }
};

This options object can then be passed to the register() method:

Ajax.Responders.register(oGlobalOptions);

Adding this code, means there is no need to make any changes to the previously existing example JavaScript code. All that is required is the addition of an area to display request status:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Prototype Responders Example</title>
    <script type="text/javascript"src="prototype.js"></script>
    <script type="text/javascript">
        //<![CDATA[

        var oGlobalOptions = {
            onCreate : function (oXHR, oJson) {
                document.getElementById("divStatus").innerHTML = 
                                                        "Contacting the server...";
            },
            onComplete : function (oXHR, oJson) {
                document.getElementById("divStatus").innerHTML = 
                                                              "Response received.";
            }
        };
        Ajax.Responders.register(oGlobalOptions);
        function requestCustomerInfo() {
            var sId = document.getElementById("txtCustomerId").value;
            var oOptions = {
                method: "get",
                parameters: "id=" + sId,
                onFailure: function (oXHR, oJson) {
                    alert("An error occurred: " + oXHR.status);
                }
            };
            var oRequest = new Ajax.Updater({ 
                success: "divCustomerInfo"
            }, "GetCustomerData.php", oOptions);
        }
        //]]>
    </script>
</head>
<body>
    <p>Enter customer ID number to retrieve information:</p>
    <p>Customer ID: <input type="text" id="txtCustomerId" value="" /></p>
    <p><input type="button" value="Get Customer Info" 
              onclick="requestCustomerInfo()" /></p>
    <div id="divStatus" style="color: blue"></div>
    <div id="divCustomerInfo"></div>    
</body>
</html>

When the button is clicked to retrieve customer information in this example, the divStatus element is filled with status information about the request. Specifically, when the request is first sent, the status changes to "Contacting the server..." and when the response is received, the status is set to "Response received." As you can see, the Ajax.Responders object allows seamless interaction with all Ajax requests without the need to change the code that already exists.

Advantages and Disadvantages

Prototype offers a fairly straightforward approach to Ajax communication that allows both synchronous and asynchronous communication (unlike the Yahoo! Connection Manager, which supports only asynchronous requests). The Ajax.Updater object offers a clean interface for updating HTML elements, and the Ajax.Responders object allows developers to respond to all requests and responses with ease. Clearly, Prototype has some major advantages as far as ease of use and practicality.

That being said, some things are noticeably missing from the library.

  • Unlike Yahoo! Connection Manager, Prototype lacks the ability to encode all of the values in a form, necessitating developers to write their own function to do so.
  • Further, Prototype lacks support for non-XHR types of communication, making it impossible to upload files.
  • And of course, Prototype is not simply an Ajax communication library, so loading the file automatically brings in many other functions, objects, etc., that you may not use. However, this is the same for all JavaScript libraries, and ultimately, it is up to your individual requirements as to whether or not Prototype is a right fit.

This article is excerpted from Chapter 4, "Ajax Libraries," of Professional Ajax 2nd Edition, by Nicholas C. Zakas, Jeremy McPeak, Joe Fawcett. Nicholas C. Zakas has a BS in Computer Science from Merrimack College and an MBA from Endicott College. He is the author of Professional JavaScript for Web Developers (Wrox 2005, ISBN: 978-0-7645-7908-0). Nicholas works for Yahoo! as a front-end engineer and has worked in Web development for more than 6 years, during which time he has helped develop Web solutions in use at some of the largest companies in the world. Nicholas can be reached through his Web site at www.nczonline.net. Some of his other recent Wrox.com articles include Ajax Debugging with Microsoft Fiddler, JavaScript DOM Ranges, JavaScript XSLT Support in Firefox, Creating an Ajax Search Widget, XPath Support in Browsers, Introduction to the Google Maps API, XMLHttp Requests for Ajax, Ajax and the Yahoo! Connection Manager, and Ajax Submission Throttling.