Build Your Own AJAX Web Applications - Part 2
By Matthew Eernisse, SitePoint.com
Chapter 2. Basic XMLHttpRequest
I can't wait to share this new wonder, The people will all see its light, Let them all make their own music, The priests praise my name on this night.
-- Rush, Discovery
It's XMLHttpRequest that gives AJAX its true power: the ability to make asynchronous HTTP requests from the browser and pull down content in small chunks.
Web developers have been using tricks and hacks to achieve this for a long time, while suffering annoying limitations: the invisible iframe hack forced us to pass data back and forth between the parent document and the document in the iframe, and even the "remote scripting" method was limited to making GET requests to pages that contained JavaScript.
Modern AJAX techniques, which use XMLHttpRequest, provide a huge improvement over these kludgy methods, allowing your app to make both GET and POST requests without ever completely reloading the page.
In this chapter, we'll jump right in and build a simple AJAX web application -- a simple site-monitoring application that pings a page on a web server to a timed schedule. But before we start making the asynchronous HTTP requests to poll the server, we'll need to simplify the use of the XMLHttpRequest class by taking care of all of the little browser incompatibilities, such as the different ways XMLHttpRequest objects are instantiated, inside a single, reusable library of code.
A Simple AJAX Library
One approach to simplifying the use of the XMLHttpRequest class would be to use an existing library of code. Thanks to the increasing popularity of AJAX development, there are literally dozens of libraries, toolkits, and frameworks available that make XMLHttpRequest easier to use.
But, as the code for creating an instance of the XMLHttpRequest class is fairly simple, and the API for using it is easy to understand, we'll just write a very simple JavaScript library that takes care of the basic stuff we need.
Stepping through the process of creating your own library will ensure you know how the XMLHttpRequest class works, and will help you get more out of those other toolkits or libraries when you do decide to use them.
Starting our Ajax Class
We'll start by creating a basic class, called Ajax, in which we'll wrap the functionality of the XMLHttpRequest class.
I've Never done Object Oriented Programming in JavaScript -- Help!
In this section, we'll start to create classes and objects in JavaScript. If you've never done this before, don't worry -- it's quite simple as long as you know the basics of object oriented programming.
In JavaScript, we don't declare classes with complex syntax like we would in Java [27], C++ or one of the .NET [28] languages; we simply write a constructor function to create an instance of the class. All we need to do is:
- provide a constructor function -- the name of this function is the name of your class
- add properties to the object that's being constructed using the keyword this, followed by a period and the name of the property
- add methods to the object in the same way we'd add properties, using JavaScript's special function constructor syntax
Here's the code that creates a simple class called HelloWorld:
function HelloWorld() {
this.message = 'Hello, world!';
this.sayMessage = function() {
window.alert(this.message);
};
}
JavaScript's framework for object oriented programming is very lightweight, but functions surprisingly well once you get the hang of it. More advanced object oriented features, such as inheritance and polymorphism, aren't available in JavaScript, but these features are rarely needed on the client side in an AJAX application. The complex business logic for which these features are useful should always be on the web server, and accessed using the XMLHttpRequest class.
In this example, we create a class called HelloWorld with one property (message) and one method (sayMessage). To use this class, we simply call the constructor function, as shown below:
var hw = new HelloWorld();
hw.sayMessage();
hw.message = 'Goodbye';
hw.sayMessage();
Here, we create an instance of HelloWorld (called hw), then use this object to display two messages. The first time we call sayMessage, the default "Hello, world!" message is displayed. Then, after changing our object's message property to "Goodbye," we call sayMessage and "Goodbye" is displayed.
Don't worry if this doesn't make too much sense at the moment. As we progress through the building of our Ajax class, it will become clearer.
Here are the beginnings of our Ajax class's constructor function:
Example 2.1. ajax.js (excerpt)
function Ajax() {
this.req = null;
this.url = null;
this.method = 'GET';
this.async = true;
this.status = null;
this.statusText = '';
this.postData = null;
this.readyState = null;
this.responseText = null;
this.responseXML = null;
this.handleResp = null;
this.responseFormat = 'text', // 'text', 'xml', or 'object'
this.mimeType = null;
}
This code just defines the properties we'll need in our Ajax class in order to work with XMLHttpRequest objects. Now, let's add some methods to our object. We need some functions that will set up an XMLHttpRequest object and tell it how to make requests for us.
Creating an XMLHttpRequest Object
First, we'll add an init method, which will create an XMLHttpRequest object for us. Unfortunately, XMLHttpRequest is implemented slightly differently in Firefox (in this book, whenever I explain how something works in Firefox, I'm referring to all Mozilla-based browsers, including Firefox, Mozilla, Camino, and SeaMonkey), Safari, and Opera[29] than it was in Internet Explorer's original implementation (interestingly, Internet Explorer version 7 now supports the same interface as Firefox, which promises to simplify AJAX development in the future), so you'll have to try instantiating the object in a number of different ways if you're not targeting a specific browser. Firefox and Safari create XMLHttpRequest objects using a class called XMLHttpRequest, while Internet Explorer versions 6 and earlier use a special class called ActiveXObject that's built into Microsoft's scripting engine. Although these classes have different constructors, they behave in the same way.
Cross-browser Code
Fortunately, most modern browsers (Internet Explorer 6, Firefox 1.0, Safari 1.2, and Opera 8, or later versions of any of these browsers) adhere to web standards fairly well overall, so you won't have to do lots of browser-specific branching in your AJAX code.
This usually makes a browser-based AJAX application faster to develop and deploy cross-platform than a desktop application. As the power and capabilities available to AJAX applications increase, desktop applications offer fewer advantages from a user-interface perspective.
The init method looks like this:
Example 2.2. ajax.js (excerpt)
this.init = function() {
if (!this.req) {
try {
// Try to create object for Firefox, Safari, IE7 [30], etc.
this.req = new XMLHttpRequest();
}
catch (e) {
try {
// Try to create object for later versions of IE.
this.req = new ActiveXObject('MSXML2.XMLHTTP');
}
catch (e) {
try {
// Try to create object for early versions of IE.
this.req = new ActiveXObject('Microsoft.XMLHTTP');
}
catch (e) {
// Could not create an XMLHttpRequest object.
return false;
}
}
}
}
return this.req;
};
The init method goes through each possible way of creating an XMLHttpRequest object until it creates one successfully. This object is then returned to the calling function.
Degrading Gracefully
Maintaining compatibility with older browsers (by "older" I mean anything older than the "modern browsers" I mentioned in the previous note) requires a lot of extra code work, so it's vital to define which browsers your application should support.
If you know your application will receive significant traffic via older browsers that don't support the XMLHtmlRequest class (e.g., Internet Explorer 4 and earlier, Netscape 4 and earlier), you will need either to leave it out completely, or write your code so that it degrades gracefully. That means that instead of allowing your functionality simply to disappear in less-capable browsers, you code to ensure that users of those browsers receive something that's functionally equivalent, though perhaps in a less interactive or easy-to-use format.
It's also possible that your web site will attract users who browse with JavaScript disabled. If you want to cater to these users, you should provide an alternative, old-school interface by default, which you can then modify on-the-fly -- using JavaScript -- for modern browsers.
Sending a Request
We now have a method that creates an XMLHttpRequest. So let's write a function that uses it to make a request. We start the doReq method like this:
Example 2.3. ajax.js (excerpt)
this.doReq = function() {
if (!this.init()) {
alert('Could not create XMLHttpRequest object.');
return;
}
};
This first part of doReq calls init to create an instance of the XMLHttpRequest class, and displays a quick alert if it's not successful.
Setting Up the Request
Next, our code calls the open method on this.req -- our new instance of the XMLHttpRequest class -- to begin setting up the HTTP request:
Example 2.4. ajax.js (excerpt)
this.doReq = function() {
if (!this.init()) {
alert('Could not create XMLHttpRequest object.');
return;
}
this.req.open(this.method, this.url, this.async);
};
The open method takes three parameters:
1. Method - This parameter identifies the type of HTTP request method we'll use. The most commonly used methods are GET and POST.
Methods are Case-sensitive
According to the HTTP specification (RFC 2616), the names of these request methods are case-sensitive. And since the methods described in the spec are defined as being all uppercase, you should always make sure you type the method in all uppercase letters.
2. URL - This parameter identifies the page being requested (or posted to if the method is POST).
Crossing Domains
Normal browser security settings will not allow you to send HTTP requests to another domain. For example, a page served from ajax.net would not be able to send a request to remotescripting.com unless the user had allowed such requests.
3. Asynchronous Flag - If this parameter is set to true, your JavaScript will continue to execute normally while waiting for a response to the request. As the state of the request changes, events are fired so that you can deal with the changing state of the request.
If you set the parameter to false, JavaScript execution will stop until the response comes back from the server. This approach has the advantage of being a little simpler than using a callback function, as you can start dealing with the response straight after you send the request in your code, but the big disadvantage is that your code pauses while the request is sent and processed on the server, and the response is received. As the ability to communicate with the server asynchronously is the whole point of an AJAX application, this should be set to true.
In our Ajax class, the method and async properties are initialized to reasonable defaults (GET and true), but you'll always have to set the target URL, of course.
Setting Up the onreadystatechange Event Handler
As the HTTP request is processed on the server, its progress is indicated by changes to the readyState property. This property is an integer that represents one of the following states, listed in order from the start of the request to its finish:
0: uninitialized - open has not been called yet. 1: loading - send has not been called yet. 2: loaded - send has been called, but the response is not yet available. 3: interactive - The response is being downloaded, and the responseText property holds partial data. 4: completed - The response has been loaded and the request is completed.
An XMLHttpRequest object tells you about each change in state by firing a readystatechange event. In the handler for this event, check the readyState of the request, and when the request completes (i.e., when the readyState changes to 4), you can handle the server's response.
A basic outline for our Ajax code would look like this:
Example 2.5. ajax.js (excerpt)
this.doReq = function() {
if (!this.init()) {
alert('Could not create XMLHttpRequest object.');
return;
}
this.req.open(this.method, this.url, this.async);
var self = this; // Fix loss-of-scope in inner function
this.req.onreadystatechange = function() {
if (self.req.readyState == 4) {
// Do stuff to handle response
}
};
};
We'll discuss how to "do stuff to handle response" in just a bit. For now, just keep in mind that you need to set up this event handler before the request is sent.
Sending the Request
Use the send method of the XMLHttpRequest class to start the HTTP request, like so: