In addition to all of the other useful classes Prototype gives to
developers, it also provides a number of classes and methods for
development of Ajax-enabled web applications.
Ajax with Prototype
Introduction
In addition to all of the other useful classes Prototype gives to
developers, it also provides a number of classes and methods for
development of Ajax-enabled web applications. That is, it allows
developers to easily perform HTTP sub-requests using XmlHttpRequest and to handle the response accordingly.
In this article I will show you how Prototype makes Ajax development
for developers by covering the functionality it provides. Additionally,
I will show you how to easily transfer data between your JavaScript
code and hosting server using JSON data.
There are many Prototype concepts and functions used in this example
that have been covered in earlier articles in this series, such as
selecting and updating elements, and event handling.
In the final article in this series, I will cover an extensive
example of programming with Prototype, which will include using Ajax to
communicate with the web server.
Request Types
There are three different classes Prototype provides to perform Ajax requests. These are Ajax.Request, Ajax.Updater and Ajax.PeriodicalUpdater.
Ajax.Request
This is the primary class that is used for Ajax, which the other two classes mentioned above use also. Ajax.Request provides a cross-browser solution for performing Ajax requests without requiring that you know how to specifically use XmlHttpRequest.
This class is simple to use – just instantiate it and pass to it the
URL you want to send a HTTP request to as the first argument. You can
optionally pass a second argument to the constructor which you can use
to specify a number of different options. It's pretty rare that you
would ever use this class without specifying the second argument, since
most of the time you will want to use the response in some way.
Listing 1 shows an example of performing an Ajax request. For now,
the options are an empty JavaScript object, but in the next section I
will cover the different options that are available for you to specify.
These also apply to the Ajax.Updater and Ajax.PeriodicalUpdater classes we will look at shortly.
Note: If you try out this example,
your sub-request will likely result in a 404 file not found error since
the requested file does not exist. Try changing the URL to a file you
know exists on your server.
Listing 1 A basic Ajax sub-request (listing-1.html)
<html>
<head>
<title>A basic Ajax sub-request</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<script type="text/javascript">
var options = {
method : 'post'
}
new Ajax.Request('/path/to/file', options);
</script>
</body>
</html>
Note: Ajax.Request is a class, not a function, so you must remember to instantiate the class by including the new keyword.
In the above example, the options object is used to
hold the various request options. This may include get or post data to
include in the request or instructions on what to do on successful
completion (or failure) of the request. In this example, I have set the
request method to be post, simply by specifying the method option.
In this example, once the script is requested nothing will happen
since we haven't defined any callbacks for handling the response. I
will cover this in the next section.
Ajax.Updater
The Ajax.Updater class is an extension of the normal Ajax.Request
class, specifically used to update a DOM element with whatever content
is returned from the HTTP sub-request. The alternative would be to use Ajax.Request yourself directly, then manually handle the response and update the required element accordingly.
Listings 2 and 3 show an example of using Ajax.Updater. In Listing 2 is some basic HTML content that we are going to load into the page shown in Listing 3.
Listing 2 Sample content to load using Ajax.Updater (listing-2.html)
<p>
Here is some HTML content, with any element you would normally
use, such as <strong>bold</strong> and <a href="http://www.example.com">a link</a>.
</p>
Listing 3 Populating an element using Ajax.Updater (listing-3.html)
<html>
<head>
<title>Populating an element using Ajax.Updater</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<input type="button" value="Click Me!" id="myButton" />
</div>
<script type="text/javascript">
function onButtonClick(e)
{
var button = Event.element(e);
new Ajax.Updater(button.up(), 'listing-02.html');
}
$('myButton').observe('click', onButtonClick);
</script>
</body>
</html>
When you load this code in your browser you will see a button that
says "Click Me!". When you click the button, an Ajax request is
triggered to request the listing-02.html file. This occurs in the event handler onButtonClick.
The first argument to Ajax.Updater is the element to
populate with the results from the Ajax request. The second argument is
the URL to fetch. As mentioned previously, we could also specify
options to pass as the final arguments. Because this example is
somewhat trivial, I have not done so, but you will see how to do this
later in this article.
If you're unsure how the event handling code works in this example, please refer to the fourth article in this series.
Ajax.PeriodicalUpdater
The Ajax.PeriodicalUpdater class is somewhat similar to the Ajax.Updater
class, except instead of performing a single sub-request and updating
an element it will continue to perform sub-requests after a specified
period, thereby continually updating the element's content.
Because this class and Ajax.Updater are extensions to the base Ajax.Request class, the remainder of this article will focus specifically on using Ajax.Request.
Request Options
There are a number of different options you can pass to Ajax.Request
that allow you to control specifically how the sub-request should be
performed, or how its response should be handled. Although there are
quite a number of different options.
In order to use these options, you typically create a JavaScript object
in which you specify whichever options are required. My own preference
is to call this variable options, but it doesn't really matter. You then pass this variable as the second argument to Ajax.Request (the first argument is the URL to request).
Listing 4 shows an example of how to specify the request options to pass to Ajax.Request. My own personal preference is to create the options separately from the call to Ajax.Request, since it makes the code slightly easier to read.
Listing 4 How to specify options to pass to Ajax.Request (listing-4.js)
var url = '/url/to/request';
var options = {
};
new Ajax.Request(url, options);
new Ajax.Request('/url/to/request', {
});
Now that you know how to create the options, I'll cover the most important options available.
-
method: This specifies the type of HTTP request to perform. The typical values for this are post or get. If you don't specify this option, post
is used. As always, if you are sending data back to the server that may
affect the state of the application (e.g. updating data in the
database) you should use post.
-
parameters: This option specifies any form data you would
like to include in your request. This value can be a string or you can
pass an object, which Prototype will automatically serialize and escape
for you. This is demonstrated below.
-
postBody: If you're performing a request with a method of post, then you can use this option to hold the post data. My own preference is just to use the parameters option, since it will be used for a post request if postBody is left empty.
Listing 5 shows an example of specifying these options for Ajax.Request. Take note of how the parameters
option is specified. The second method is preferred since it not only
looks cleaner but ensures your values are encoded properly.
Listing 5 Some basic examples of specifying Ajax.Request options (listing-5.js)
var url = '/url/to/request';
var options = {
method : 'post',
parameters : 'name=Quentin&country=Australia'
};
new Ajax.Request(url, options);
var url = '/url/to/request';
var options = {
method : 'post',
parameters : {
name : 'Quentin',
country : 'Australia'
}
};
new Ajax.Request(url, options);
Event Callbacks
The next part of specifying the request options is to specify the
functions that should be called when certain events occur. These are
included in the options array just as above, except each value should
be either a function or the name of a function to call. In the next
section I'll show you how to create these functions.
The following list shows the most common callbacks that you will
use. There are others available, but not all are available on all
browsers.
- onSuccess: This callback is triggered when a request
is completed and the HTTP response code is in the 200s. In the past I
have seen a lot of code utilizing XmlHttpRequest that checks specifically for a response code of 200, whereas this isn't necessarily the response code that will be returned. With onSuccess, this is not a problem.
- onFailure: This callback is triggered when a request
completes but the HTTP response code is not in the 200s. For instance,
if a 404 File Not Found error occurs then this callback would be used.
- onComplete: Regardless of whether a request is deemed
to have succeeded or failed, this callback is triggered upon completion
of a request. This is the final callback to be triggered. In other
words, if you specify onSuccess or onFailure, then onComplete will be used after that.
Listing 6 shows an example of specifying these callbacks in the
request options. Each callback accepts the response object (an instance
of Ajax.Response) as the first argument. I will cover how to specifically use this response in the next section.
Listing 6 Specifying callbacks for success, failure and request completion (listing-6.js)
function onRequestSuccess(transport)
{
}
function onRequestFailure(transport)
{
}
function onRequestComplete(transport)
{
}
var url = '/url/to/request';
var options = {
method : 'post',
parameters : {
name : 'Quentin',
country : 'Australia'
},
onSuccess : onRequestSuccess,
onFailure : onRequestFailure,
onComplete : onRequestComplete
};
new Ajax.Request(url, options);
As an alternative to using onSuccess and onFailure,
you can handle specific HTTP response codes. For instance, if you
wanted a different handler for a 404 error to a 403 error, you could
achieve this easily by specifying the on404 callback and on403 callbacks. You can use any HTTP code, in the format of onXYZ (where XYZ is the response code).
Note however that if you do this, the onFailure or onSuccess
callback will not be used for that response code. As an alternative,
you may wish to manually check for the response code and act
accordingly from within either the onFailure or onSuccess callback.
Listings 7 and 8 show example of two different ways of handling response codes. In the first example, I have specified the on403 and on404 callbacks, while in the second I check the transport.status value to determine the code.
Listing 7 Specifying a separate callback for different status codes (listing-7.js)
var url = '/url/to/request';
var options = {
on403 : function(transport) { ... },
on404 : function(transport) { ... }
};
new Ajax.Request(url, options);
Listing 8 Using a single callback and checking the status code (listing-8.js)
function onRequestFailure(transport)
{
switch (transport.status) {
case 403:
break;
case 404:
}
}
var url = '/url/to/request';
var options = {
onFailure : onRequestFailure
};
Handling the Ajax Response
Now that you know how to specify the callbacks for particular events
that occur when performing an Ajax request, I will show you write to
write the callback handler.
All of these callbacks are passed an instance of Ajax.Response
as the first argument. This object contains information about the
request, such as the HTTP status code that resulted from the request,
and any response data sent back.
My own preference is to call this parameter transport.
Whatever you call it, be consistent. Functions should be
self-documenting, so you should be easily able to determine what the
function is used for by its name and the naming of its arguments.
In writing a handler we are typically concerned with the response
data. Typically we want to use this data somehow on the current page,
whether it's text, HTML, JSON or XML. Because of this, when writing a
response handler we are really only concerned with the onSuccess handler, since onFailure occurs only if something went wrong with the request.
Even if the Ajax request resulted in some error condition in your
application, if the Ajax request succeeded you then need to handle your
application error in the onSuccess handler.
Note: An Ajax request doesn't have to return any data.
Some operations involve you simply sending data to the server for it to
process, without requiring any response. You don't really need any
success or failure handlers if you don't care about the response.
The most commonly used values that are available in the transport variable are as follows:
-
status: The HTTP response code. An example of reading this value is shown in listing 8.
-
statusText: The text that corresponds to the response
code, as sent by the server. Since different servers may send a
slightly different string for certain response codes, you shouldn't
rely on this value.
-
responseText: This is any data that is sent back from the request as a string.
-
responseXml: If data was sent from the server in XML format (with the correct content type header of text/xml), this variable contains the response as a document object.
-
responseJSON: If the data was sent back from the server in
JSON format, you can read the data from this array. In the next section
I will show you how to do this. Note that the response from the server
must use the application/json content type header.
You can read any of these properties from the first argument that is
sent to the request handlers. In the previous section I called this
variable transport.
Listing 9 shows identical functionality to the Ajax.Updater example in Listing 3, however, in this example we read the responseText variable and update the element accordingly. This example uses the same example HTML, shown in Listing 2.
Listing 9 Populating an element using the responseText property (listing-9.html)
<html>
<head>
<title>Populating an element using the responseText property</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<input type="button" value="Click Me!" id="myButton" />
</div>
<script type="text/javascript">
function onAjaxSuccess(transport)
{
$('myButton').up().update(transport.responseText);
}
function onButtonClick(e)
{
var options = {
method : 'get',
onSuccess : onAjaxSuccess
};
new Ajax.Request('listing-02.html', options);
}
$('myButton').observe('click', onButtonClick);
</script>
</body>
</html>
new Ajax.Request(url, options);
Typically you will only ever need to use the onSuccess and onFailure handlers to do something with an Ajax response (although if you're lazy you may not even bother with the onFailure). Typically you won't really need to use the onComplete or other callbacks.
In the next section I will show how to actually do something useful with the callback handlers.
Using JSON Data
I've mentioned JSON a few times in this article so far, but what
exactly is it? Short for JavaScript Object Notation, it is a way to
send complex data structures between the client and server.
Essentially, it is just JavaScript code. However, it is really only
the code that would be used to create a new array or object in
JavaScript. Therefore, when you read the JSON data you can bind it to a
JavaScript variable and access the response just as you would from any
other array or object in JavaScript.
For example, if you wanted to create an array of data in PHP, then
make that data easily accessible in JavaScript (let's say you wanted to
send this PHP array back in an Ajax response), then you would use JSON.
Listing 10 shows an example of some data you are representing in PHP
that you want available in JavaScript. Listing 11 shows the JSON
representation of this data.
Listing 10 Some sample data in PHP that we want to use in JavaScript (listing-10.php)
<?php
$person = array(
'name' => 'Quentin',
'country' => 'Australia'
);
?>
Listing 11 The $person array represented in JSON (listing-11.js)
{
name : 'Quentin',
country : 'Australia'
}
This data is in the same format as JavaScript uses, meaning you can
easily use it in your JavaScript code. Listing 12 shows how you might
access this data using the responseJSON variable in your onSuccess handler for Ajax.Request. This assumes that the $person array was the only data sent in the response. In this example I assigned the JSON data to a variable called json, purely so the code is easier to read when the data is accessed.
Listing 12 Reading the JSON response (listing-12.js)
function onSuccess(transport)
{
var json = transport.responseJSON;
alert(json.name + ' is from ' + json.country);
}
PHP provides the json_encode() function to convert a PHP variable into its equivalent JSON format.
Handling Ajax Requests on the Server Side Using PHP
Now that you have some understanding of how JSON data works, I'll
give you a concrete example of performing an Ajax request which returns
some JSON data. Additionally, I'll now show you how to handle Ajax
responses in PHP. The principles here apply to other languages also,
but the server-side language used is PHP.
When writing scripts in PHP to handle Ajax requests, you typically
write your scripts as you would for normal requests. The key difference
is that you will typically want to set the content type of the response
data. By default, PHP uses a content type of text/html, so if you're just sending HTML data back (as we did in listings 3 and 9), you don't need to set the content type.
As I mentioned in the previous section, PHP provides a function called json_encode() which you can use to convert a PHP variable (such as an array) into JSON format. You can use header() to send the content type, then echo the output from json_encode().
Listing 13 Sending a PHP array as JSON data (listing-13.php)
<?php
$person = array(
'name' => 'Quentin',
'country' => 'Australia'
);
header('Content-type: application/json');
echo json_encode($person);
?>
We can now build on the code from Listing 12 to actually request
this data and display it accordingly. Listing 14 shows the complete
example of reading JSON from the server. When you click the button that
is displayed, the Ajax request will be initiated. The response will
then be displayed. Note however that there is no error handling in this
example and it assumes that specific data will be returned.
Listing 14 Reading JSON data from the server (listing-14.html)
<html>
<head>
<title>Reading JSON data from the server</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<input type="button" value="Click Me!" id="myButton" />
</div>
<script type="text/javascript">
function onSuccess(transport)
{
var json = transport.responseJSON;
alert(json.name + ' is from ' + json.country);
}
function onButtonClick(e)
{
var options = {
method : 'get',
onSuccess : onSuccess
};
new Ajax.Request('listing-15.php', options);
}
$('myButton').observe('click', onButtonClick);
</script>
</body>
</html>
When handling an Ajax request on the server-side, you may want to
ensure that the request did in fact come via Ajax. Whenever a request
is performed using Prototype's Ajax.Request, the header X-Requested-With header is sent, with a value of XMLHttpRequest.
While this value can be manually set by a clever user, ultimately it
doesn't matter too much and simply allows you perform different
functionality if a random user happened to stumble across your Ajax
request handler.
Listing 15 shows an example of checking for this header. This is a
modification of Listing 13, in which we send a different content type
based on how the request is performed. Try viewing this script in your
browser. Realistically you may prefer to redirect to another page
rather than what is done here.
Listing 15 Checking the request method for the current script (listing-15.php)
<?php
$person = array(
'name' => 'Quentin',
'country' => 'Australia'
);
$isXmlHttpRequest = isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
if ($isXmlHttpRequest)
header('Content-type: application/json');
else
header('Content-type: text/plain');
echo json_encode($person);
?>
In PHP, you read the header from $_SERVER. PHP modifies the request header names by prepending HTTP_, capitalizing the name and replacing hyphens with underscores. In other words, X-Requested-With becomes HTTP_X_REQUESTED_WITH. Additionally, I like to use strtolower() on this value so there is no confusion between, say, XMLHttpRequest and XmlHttpRequest.
Summary
In this article I showed you how to perform Ajax requests using the Ajax.Request provided by Prototype. This class is a wrapper to the XMLHttpRequest
object that modern browsers have. I showed you how to specify options
when performing a request, including how to specify the callback
handlers to deal with the response from the HTTP sub-request.
Additionally, I introduced you to JSON data and how to send JSON
data with PHP, and receive it in the Ajax response. While the examples
given were mostly conceptual, hopefully they gave you a good idea of
how you can use Ajax in your own applications. In the eighth article of
this series a larger, more concrete example will be given when I bring
together all of the Prototype functionality covered in this series.
In the next article I will show you how to create JavaScript classes
in Prototype. If you prefer an object-oriented approach to your
programming, you will find this article invaluable, since knowing how
to effectively create classes will give your code a much better overall
structure.
|