Chapter 11. Request

There is perhaps no bigger revolution that propelled JavaScript to where it is today than Ajax. This paradigm shift that started in the early 2000s brought JavaScript back to the center of Web development and restored the luster it had lost during its early years. And this whole revolution happened because of the rediscovery of a very interesting object called the XMLHttpRequest.

In this chapter, we'll learn about the HTTP request and response process, and how XMLHttpRequest fits into the equation. We'll also talk about the MooTools Request class, a simple abstraction of the native XMLHttpRequest API, and how it makes working with requests easier and more MooToolsian.

Requests and Responses

At the most basic level, browsers are tools for displaying resources on the Web, resources that are stored in special places called servers. At the heart of the Web is the communication between browsers and servers, and it is this communication that underlies everything we do online.

The communication between browsers and servers is a very complex topic, one that touches subjects we don't really need in our discussion. However, a certain part of browser-server communication—the request-response flow—is integral to understanding web applications, and we need a basic understanding of this process in order to fully grasp the things we'll see later.

When a browser loads a resource, it first sends a request to the server. A request is a set of directives containing information about what exactly we're requesting, as well as other criteria for the resource we're trying to fetch. Let's say we're loading http://foo.com/index.html in our browser. The browser first connects to the server for foo.com and then sends a request that looks like this:

GET /index.html HTTP/1.1
User-Agent: BrowserX/1.1
Host: foo.com
Accept: text/html

This request has several parts. The first line starts with the method, which is a verb that describes the action we want to perform on the particular resource. In this example, we use the GET method, which tells the server we would like to fetch the particular resource. Other supported verbs include POST and PUT for sending user data to a particular resource, DELETE for removing a particular resource, and HEAD for retrieving the headers for a resource.

Right after the method comes the resource URI, which describes the path of the resource we want to access. In our example, we told the server for foo.com that we want to access the resource called /index.html. Finally, the first line ends with an HTTP version identifier, which tells the server which version of the HTTP protocol we'd like to use.

The first line, which is called the request line, is the most important part of the request because it contains the essential information about the request. The next lines, called headers, contain other information about the request. A header is a key-value pair that takes the form <Header Name>: <Header Value>. Each header appears on a separate line, and each describes a specific criterion or property of the request.

Our example has three headers. The first is User-Agent, which identifies the current browser to the server. In our example, the browser is called BrowserX and its version identifier is 1.1. Normally, the value of the User-Agent header is exactly the same string as the value of the navigator.userAgent property we saw in Chapter 7. Next is an Accept header, which tells the server what kinds of files we'd like to receive. In our example, we tell the server to give us files that have the mimetype text/html, a common mimetype for HTML files. Finally, we have the Host header, a required header in HTTP/1.1.

After sending this request to the server, the browser waits for a response. A response is a set of directives and properties the server sends back as an answer to a particular request. Our hypothetical foo.com server, for example, could send us back this response in answer to our request:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 87

<html>
<head>
    <title>Hello World!</title>
</head>
<body>
    Hello World!
</body>
</html>

The first line of the response begins with the HTTP version identifier, which tells the browser which version of the HTTP protocol the server used to answer the request. This is followed by the status code, a numeric value that determines the nature of the response. A 200 status code, for example, means that the request was correctly answered by the server and there were no errors in putting together a response. A 404 status code, on the other hand, means that the server couldn't find the particular resource asked for by the request. The status code is followed by the status text, which is a textual representation of the status code, in our case OK.

Because the first line of the response contains information about the status of the response, it's also called the status line.

Just as with requests, the headers come after the first line. The response headers, of course, describe the nature of the response, and they also give the browser some instructions regarding how to interpret the particular response. In our example, we have two headers: Content-Type, which describes the mimetype of the response data, and Content-Length, which is the length of the response data.

Right after the final header comes a mandatory blank line, followed by the most important part of the response: the body. In most cases, the body contains the actual file data for the particular resource, like in our example where we received the HTML source of the page as a body. This part of the response is the actual content, and is therefore the part we're most interested in.

With the response in hand, the browser can start parsing the HTML source and display the page. When a new resource is needed, the whole process is repeated. This request-response cycle is the lifeblood of browser-server interaction, and it affects the way we develop web applications in a massive way.

The basic request-response cycle fits the model of the web as a collection of hyperlinked documents. A browser first sends a request for a document to a server, and the server then sends a response back to the browser with the requested document. When the user clicks on a hyperlink, the process is repeated. The cycle of requesting a resource and then parsing the response for the resource is suitable for this model of the web because we're working with documents with a very limited interaction framework: open a resource, click links to open more resources, repeat.

Many web applications actually implement this same document-style interaction, and it works for the most part. First a page is shown to the user, then the user performs an action, such as filling in a form and submitting it, which triggers the browser to send a request to the server containing the data provided by the user. The server processes the data and sends a response containing the updated page, which is then displayed to the user by the browser. This process is repeated when the user performs another action.

While this works for simple applications, building more complex web applications that somehow mimic the desktop application model is hard to fit in this style. Desktop applications, which most users are familiar with, are often very dynamic: an action is immediately reflected in the interface, no loading or reloading involved. Simple web applications, on the other hand, feel a lot less like applications and more like web sites because they are too constrained by the hyperlink-style request-response cycle: if a part of the interface needs to be updated, the entire page has to be reloaded.

The problem isn't about developers not being smart enough to create desktop-style applications. Rather, it's about the lack of an API for issuing HTTP requests using JavaScript. Requests happen at the browser level: a default event or piece of JavaScript code triggers the browser to load a page, and the browser does the appropriate HTTP requests to load or reload a page. In the past, there was no infrastructure in place that let developers request data from servers directly using JavaScript.

The landscape changed, however, in the early years of the 21st century, with the rediscovery of a then obscure API. This API, originally developed for loading XML documents into JavaScript, became an instant hit because it enabled developers to make direct HTTP requests using JavaScript. Suddenly, JavaScript gained the necessary ingredient to create powerful web applications.

Using this API, web applications could load or post new content from the server without having to refresh the whole page. This helped developers create rich, complex applications that can rival desktop applications. Users no longer have to wait for a page to reload in order to see their changes, because applications can now make those changes in the background and then update the interface to reflect the changes.

JavaScript-based HTTP requests made web applications less like web sites and more like desktop apps, and the browser became the platform that enabled these rich, dynamic applications to run. JavaScript, as the language of the browser, became the old new cool thing, and the language regained popularity and regard within the developer community. And all of this is because of an obscure object called the XMLHttpRequest.

The XMLHttpRequest Object

That obscure object called the XMLHttpRequest, or XHR, is at the heart of the new model of internal requests. First introduced by Microsoft in 1999, XHR is a special object that can be used to make HTTP requests—as well as receive appropriate HTTP responses—from within JavaScript. This object gives us a very simple API that can be used to build a request and send it to a server, as well as the appropriate means to handle the responses the server sends back. As its name implies, an XHR was originally used for retrieving XML documents, but it is flexible enough to be used for any kind of data.

You create an XHR object using the XMLHttpRequest constructor:

var xhr = new XMLHttpRequest();

This snippet creates a new instance of XMLHttpRequest, which we can now use to send requests to servers. Unlike most other constructors, the XMLHttpRequest constructor takes no arguments, but rather depends on methods to set the appropriate values for the request.

Note

Older versions of Microsoft Internet Explorer do not have an XMLHttpRequest constructor. Instead, XHRs are created by instantiating ActiveX objects that implement the XMLHttpRequest API. Because this topic has already been covered in numerous books and articles, we won't talk about cross-browser XHR instantiation here.

The first of these methods is open, which is used to initialize a request. It takes two required arguments: method, which is an uppercase string indicating the HTTP method of the request, and url, which is a string that points to the location of the resource we're trying to access. A third argument, async, is used to determine the mode of the request. We'll look at how this third argument affects requests later, but for now we'll pass false as the value of this argument.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);

In this snippet, we created a new XHR object called xhr, then called its open method to initialize it. We passed three arguments to open: 'GET', which corresponds to the HTTP method GET, 'http://foo.com/index.html', which is the resource we're trying to load, and a false value, the use of which we'll find out later.

There are several important things you need to remember about these arguments. First, the method argument should always be in uppercase, so it's 'GET' and 'POST', not 'get' or 'Post'. Second and more important, the value for the URI should be in the same domain as the current domain running the script. In our example, we assume that the script is running in the foo.com domain, not www.foo.com or dev.foo.com.

Note

That last point is important. One of the limitations of XHRs is imposed by the same-origin policy. This is a security concept that limits XHRs to requesting resources only from the same domain in which they are running. This security "feature" was put into place to prevent malicious scripts from sending or loading data from other malicious sites.

You'll notice that these arguments correspond to the request line of the HTTP request message:

GET /index.html HTTP/1.1

The HTTP version identifier, which is the last part of the request line, isn't included in the formal parameters of the open method, because the browser itself sets which version of the HTTP protocol to use.

Another method of XHRs is setRequestHeader, which is used to add appropriate headers to the request. It takes two arguments: header, which is a string name of the header we're adding, and value, which is the value of the header.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),

Here we added a single request header called 'Accept', which tells our server the kind of file we want to receive. Our request message now looks like this:

GET /index.html HTTP/1.1
Accept: text/html

Note that some common or required headers, such as Host or User-Agent, are added automatically by the browser, so we no longer need to add them explicitly.

At this point, our request is ready to be sent to the server. We do this using the send method:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

Here we send the original example above by calling the send method of our XHR object. This tells the browser to send an HTTP GET request to foo.com to retrieve the /index.html resource.

Sometimes you'll need to send data with your request, especially for requests that use the POST and PUT methods. In that case, you'll need to pass the optional body argument to the send method. This argument should be a string that contains the data you want to send.

var data = 'name=Mark&age=23';

var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://foo.com/data/', false);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'),
xhr.setRequestHeader('Content-Length', data.length);
xhr.send(data);

In this snippet, we need to send the value of the data variable to http://foo.com/data. First we create a new XHR object, then we initialize it using the open method, specifying the POST method and the URL endpoint. We then set two necessary headers, Content-Type and Content-Length, which are used by the server in parsing the data. Finally, we send the request using the send method, passing in the data variable that becomes the body of our request. Our request message will look like this:

POST /data HTTP/1.1
Host: foo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 16

name=Mark&age=23

Now that we've sent the request, it's time to parse the response. When the data is received back from the server after sending the request, the browser will update the XHR object and put the data from the response into properties. Let's say that after sending our GET example above, the server responds with the following:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 87

<html>
<head>
    <title>Hello World!</title>
</head>
<body>
    Hello World!
</body>
</html>

We can access the parts of this response using the properties of the XHR object itself. The first property is status, a numeric value that corresponds to the status code of the response:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

console.log(xhr.status); // 200

Here we sent the original GET example, which returns the response we saw above. We then retrieve the status code of the response by accessing xhr.status, whose value in this example is 200, just like our response.

A related property, statusText, gives us the status code plus the status text of the response as a string:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

console.log(xhr.statusText); // 'OK'

Response headers can be accessed using the getResponseHeader method. This method takes a single argument, headerName, and returns the value for that particular header:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

console.log(xhr.getResponseHeader('Content-Type')); // 'text/html'
console.log(xhr.getResponseHeader('Content-Length')); // '87'

console.log(xhr.getResponseHeader('Fake-Header')); // null

The getResponseHeader method always returns a string containing the value of the header if the header is present, and null if the header doesn't exist in the response. In this example, we used the getResponseHeader method to retrieve the values of the Content-Type and Content-Length headers of the response. Because these two headers exist in the response, getResponseHeader returns their values as strings. However, when we tried retrieving the value of Fake-Header using the same method, we get null because the response doesn't have this header.

You can retrieve all response headers using the getAllResponseHeaders method. This method returns a string containing all the headers of the request, one header per line.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

console.log(xhr.getAllResponseHeaders());

This snippet will log the following string:

'Content-Type: text/html
Content-Length: 87'

Finally, you can access the body of the response itself using responseText, which is a string containing the raw response body:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

console.log(xhr.responseText);

This will give us the following console output:

'<html>
<head>
    <title>Hello World!</title>
</head>
<body>
    Hello World!
</body>
</html>'

The responseText property contains the actual body of the response and is not parsed by the browser. This allows us to retrieve any kind of resource from the server for use in our applications.

One thing to note is that the value of responseText can be null or an empty string in some cases. If the server returned a response with no body, or if there was a server-based error, the responseText property will be empty. However, if there was an error in the request itself or an error from the server side that resulted in a wrong response, the value of the property will be null.

An easy way to guard against errors is to check the status code of the response. Usually, a response with a status code greater than or equal to 200 but less than 300 is considered a "successful" response from the HTTP protocol point of view. Therefore, we can add a simple if statement to our code to check for success:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

if (xhr.status >= 200 && xhr.status < 300){
    console.log(xhr.responseText);
} else {
    console.log('Unsuccessful Request.'),
}

Our if statement in this example checks the status code of the XHR object to see whether its value is greater than or equal to 200 but less than 300. If it is, it logs the responseText property of the XHR. Otherwise, it will log 'Unsuccessful Request.'. Because the response status for this example is 200, we'll get a proper console output like the previous one.

Because the XMLHttpRequest API was originally created with XML documents in mind, there's another property called responseXML, which is a document object. If the content-type of the response body is an XML document, the browser will automatically try to parse the responseText value into a DOM Tree object, and set it as the value of responseXML. However, if the response is not an XML document—as in our case—the responseXML property is set to null.

There are times you'll need to cancel a request, and you can do that using the abort method:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();
xhr.abort();

There are many reasons you might want to abort a request, such as if the user decides to cancel the action. Or you may want to set some sort of timeout for the request. The XMLHttpRequest object doesn't natively support timeouts, which means it will wait for a response from the server as long as possible. You can use abort together with the setTimeout function to automatically cancel a request after a specific time:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html'),
xhr.setRequestHeader('Accept', 'text/html'),

// timeout
setTimeout(function(){
    xhr.abort();
}, 5000);

xhr.send();

Here we combined the abort method with setTimeout to cancel the request after 5 seconds. If the request hasn't received a response after 5 seconds, the timeout function will automatically cancel the request, making it possible to create some sort of timeout function that can be used to cancel long-running requests.

Going Async

The requests we made above using XHR objects are synchronous, which is a fancy term for blocking: after sending the request, the browser halts all processes in the current window until it receives a response. Only after getting a response back from the server will the browser continue execution.

While that's fine in some cases, synchronous requests are problematic in general because loading resources takes time. Since the browser blocks all processes during synchronous requests, your application will remain unresponsive during this time and the user won't be able to interact with it. If you're loading a large file, for instance, it might take a few seconds for the browser to finish loading the response from the server—a few seconds that might be enough reason for your bored user to stop using your application. Factor in the slow speed of some Internet connections plus the latency between the user's physical location and the server, and you get a user-experience nightmare.

Thankfully, we have an alternative: asynchronous requests. An asynchronous (or async) request happens in the background and is therefore non-blocking. Using an async request, we can send a request to the server and continue without waiting for a response. Because it doesn't block the browser from performing other processes, our application remains interactive while the request is being processed, and users won't notice anything happening until we get the response back and update the interface.

To use the async mode of XHRs, we must initialize our objects with the third argument for the open method. This third parameter, called async, takes a Boolean value: if the value is true, the request will be asynchronous. In the previous examples, we passed false to this argument, which made our requests synchronous. To make our request asynchronous, we simply have to change that value to true:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', true);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

In this snippet, we changed the synchronous example from the last section into an asynchronous version by passing a true value as the async argument to open. When we send our request using the send method, it will now be done in the background, and the lines after the send invocation will immediately be interpreted. This behavior is different from the synchronous example, where the script waits for a response from the server before executing the next lines.

The use of async requests, though, requires a different approach. Until the previous example, all of our code snippets were written with the synchronous calls in mind. This enabled us to access the values of the XHR object directly after our send invocation:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', false);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

console.log(xhr.responseText.length); // 87

Since synchronous requests block further execution until the response arrives, we're able to get the response values like this. By the time our first console.log line is evaluated, the response has already been received, so we can read the data immediately.

But what about async requests?

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', true);
xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

console.log(xhr.responseText); // undefined

Here we modified the example to use an async request instead of a synchronous one. Even if both examples point to the same URL, this example will always log undefined for the responseText property. This is because, unlike in our previous example, the JavaScript interpreter will not wait until the response is received before interpreting the line after the send call. Instead, the interpreter triggers the browser to perform a non-blocking background request and then goes on to interpret the rest of the program.

Because the request is asynchronous, we won't be able to access the properties right after we invoke send, because we don't know for sure whether the response has arrived from the server by the time the interpreter evaluates our access code—and often, the response arrives much later.

To work with asynchronous requests, then, we need a new plan of attack. Instead of immediately accessing the properties of the XHR after sending it, we need to defer this access until we actually get a response. In other words, we need to wait for the response to actually arrive before processing it. And the technique we'll use to do that should already be familiar to us, since we just discussed it in the previous chapter: events.

An XHR object supports one main event: the readystatechange event. This event is dispatched by the XHR every time its ready state changes, which we'll discuss in a bit. In order for us to effectively use async requests, we must therefore attach an appropriate event handler for this event.

Attaching an event handler to an XHR, however, is a bit tricky. In browsers that support the standard model, we can use the addEventListener method for this purpose. Internet Explorer, on the other hand, does not implement event methods such as attachEvent on XHR objects. We must therefore use an older style of event attachment that's supported by all browsers—event handler properties:

var xhr = new XMLHttpRequest();

// attach handler
xhr.onreadystatechange = function(){
    console.log('Ready State Change.'),
};

Instead of using a method to attach an event handler, we attached the event handler directly to the object through a property name that's composed of the "on" prefix plus the name of the event. In this case, we attached a readystatechange event handler by assigning a function to the onreadystatechange property of the XHR object. This method of attaching the event handler is part of the legacy event model, which comes from the DOM Level 0 days of the browser but is still supported by all major browsers.

Of course, attaching the event handler is just part of the equation. Take a look at the following example:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', true);

// attach handler
xhr.onreadystatechange = function(){
    console.log('Ready State Change.'),
};

xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

If we try running this in the browser, we'll get the following console output:

'Ready State Change'
'Ready State Change'
'Ready State Change'
'Ready State Change'

We got four log outputs, which means that our event handler was dispatched four times. Instead of just dispatching the event when it receives a response from the server, the XHR dispatches the readystatechange event for every phase of the request-response cycle.

If we look back at the request-response cycle, we can see that there are four phases. The first phase is connecting to the server where the HTTP request message will be sent. The second phase is sending the actual request message to the server. The third phase is downloading the response data. The fourth phase is ending the download phase and parsing the data from the response. So the four phases are connect, send, download, and complete.

These four phases are connected to the "ready state" of the XHR. An XHR object has a special property, readyState, that contains a numeric value that tells us the current phase the XHR object is in:

  • 0 - the XHR has just been created, but its open method hasn't been called yet so it remains uninitialized. This XHR phase does not correspond to a request-response flow phase.

  • 1 - the XHR has been initialized, but the request hasn't yet been sent to the server using send.

  • 2 - the XHR's request has been sent, and the preliminary data—headers and status codes—are available.

  • 3 - the XHR's response is being downloaded and the responseText property already has partial data.

  • 4 - the XHR's response has been downloaded completely and we can now process the data.

As the XHR moves from one phase to the next, it dispatches the readystatechange event to inform us of the change of state. We can then use the readyState property to check which phase it is in.

Let's modify the previous example:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', true);

// attach handler
xhr.onreadystatechange = function(){
    switch(xhr.readyState){
        case 1: console.log('1: Connect'), break;
        case 2: console.log('2: Send'), break;
        case 3: console.log('3: Download'), break;
        case 4: console.log('4: Complete'), break;
    }
};

xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

In our readystatechange event handler, we used a switch statement to check for the value of the readyState property. Here's the corresponding console output from our snippet when run on the browser:

'1: Connect'
'2: Send'
'3: Download'
'4: Complete'

The first line in our output, '1: Connect', is logged after we call the open method. The open method changes the state of the XHR from 0 to 1, which dispatches the readystatechange event. The second line is logged after we call send, which changes the state from 1 to 2, triggering another dispatch of the readystatechange event. As the browser starts receiving data from the server, it changes the state of the XHR to 3, dispatching a third readystatechange event that logs our third line. Finally, the last readystatechange event is dispatched when the browser finishes downloading the whole response from the server, thereby logging our fourth and last line.

The last phase is perhaps the most important of these four phases, because it's only when we have all of the response data that we can start parsing it for our purposes. This means that until the readyState of our XHR object is 4, we can't access the complete data from our response. With this in mind, we can now create a proper event handler for our async request:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', true);

// attach handler
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        console.log(xhr.responseText);
    }
};

xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

Here we changed our readystatechange handler by adding an if statement that checks whether the readyState property is equal to 4. If the value of this property is anything other than 4, the data isn't ready yet and we won't be able to read the full response body. But if the value is equal to 4, our response has been completely downloaded, and we can begin using it or parsing it for our needs.

One thing we also have to factor in is the simple guard we included to check for failed requests. Remember that we checked the value of the response's status code in the previous section to determine whether the response was successful or not. We should also add that into our new event handler:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', true);

// attach handler
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if (xhr.status >= 200 && xhr.status < 300){
            console.log(xhr.responseText);
        } else {
            console.log('Unsuccessful Request.'),
        }
    }
};

xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

Finally, we can also add a timeout that will automatically cancel our request if it takes too long:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://foo.com/index.html', true);

// attach handler
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if (xhr.status >= 200 && xhr.status < 300){
            console.log(xhr.responseText);
        } else {
            console.log('Unsuccessful Request.'),
        }
    }
};

xhr.setRequestHeader('Accept', 'text/html'),
xhr.send();

// timeout
setTimeout(function(){
    xhr.abort();
}, 5000);

And with that, we now have our complete asynchronous XHR code.

The MooTools Request Class

Now that we've looked at the native XMLHttpRequest implementation, you might have noticed a few things:

  • The initialization method open has to be called separately from the constructor function, which doesn't fit with the normal JavaScript (or MooTools) style.

  • Native XHRs dispatch only a single event type in most browsers, which means that we have to cram all our code into a single event handler for both successful and failed requests.

  • Timeouts aren't natively implemented, so we need to add separate code to handle this for us.

  • We don't have the flexibility to handle different response types, and we have to parse the responses ourselves.

Because XHRs are used a lot these days, you'll probably encounter these issues in most applications you build. Therefore, we need a somewhat better API for working with XHRs to streamline the process for us.

Thankfully, MooTools provides us with the API we need: the Request class. This special class is an abstraction of the native XHR class, and can be used as a replacement for native XHR code. Like the Event type, the Request class is a wrapper: it does not override the native XMLHttpRequest type, but instead wraps it in order to add functionality. Unlike most of the constructors we've discussed so far, Request is implemented as a class rather than as a type object in order to enable subclassing—which, as we'll see later in this chapter, makes Request a really powerful feature of MooTools.

In the sections to come, we'll translate the following native XHR code to a version that uses the Request class:

window.addEvent('domready', function(){
    var notify = $('notify'),
        data = 'name=Mark&age=23';

    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'http://foo.com/comment/', true);

    xhr.onreadystatechange = function(){
        if (xhr.readyState == 4){
            if (xhr.status >= 200 && xhr.status < 300){
                notify.set('html', xhr.responseText);
            } else {
                notify.set('html', '<strong>Request failed, please try again.</strong>'),
            }
        }
    };

    xhr.setRequestHeader('Accept', 'text/html'),
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'),
    xhr.setRequestHeader('Content-Length', data.length);

    xhr.send(data);
    notify.set('html', '<strong>Request sent, please wait.</strong>'),
// timeout
    setTimeout(function(){
        xhr.abort();
        notify.set('html', '<strong>Request timeout, please try again.</strong>'),
    }, 5000);
});

In this snippet, we send a POST request to http://foo.com/comment/ in order to send the data to the server. We then wait for a response from the server to tell us whether the operation was successful, and this response is composed of an HTML string that we'll insert into the notify element.

Creating New Requests

The first thing we need to do is to create a new request object using the Request constructor. This constructor takes a single argument, options, which is an object containing options for our request object.

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/',
    method: 'post',
    async: true
});

Unlike the native XMLHttpRequest API, the Request API doesn't have a separate open method to initialize the request. Instead, we pass the values that we'd normally pass to open as values in our options argument. In this snippet, for example, we passed three options: url, which is the URL of the location we're requesting; method, which is the HTTP method to use for the request; and async, which tells the Request class that we want to use the asynchronous mode for this request.

You'll notice that the method option isn't in uppercase, and that's okay. The Request class allows any case for the method option: we could have used 'POST', 'Post' and even 'PoST'. It's common, however, to use the lowercase style with Request class instances.

Another thing we need to know is that most of the options for Request have default values. For instance, the method option is 'POST' by default, and the async option is true by default. If the default values of these options are already set to what we need, we can simply omit them from our options object:

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/'
});

This snippet is the same as the previous one, even if we didn't include values for the method and async options.

When we create a new instance of Request, the constructor automatically creates a native XHR object that will be used for the request. As I mentioned, the Request class is a wrapper object, like the Event type: it doesn't create a new object type of its own but only creates an abstraction for the native object. We can access this wrapped XHR object by accessing the xhr property of our request object:

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/'
});

console.log(typeOf(request.xhr)); // 'object'

Adding Request Headers

Now that we have our request object, we need to add the headers, and we do this using the setHeader method. This method takes two arguments, name and value, which correspond to the header name and value:

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/'
});

request.setHeader('Accept', 'text/html'),
request.setHeader('Content-Type', 'application/x-www-form-urlencoded'),
request.setHeader('Content-Length', data.length);

Here we set three headers for our request: Accept, Content-Type, and Content-Length. You'll notice that the setHeader method is very similar to the setRequestHeader method from the native API, and they actually are somewhat similar in style.

One nice feature, though, is that we can actually pass these headers to the Request options. This saves us three function invocations:

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/',
    headers: {
        'Accept': 'text/html',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': data.length
    }
});

Instead of calling setHeader separately, we just pass a headers option to the Request constructor. This option should have an object value, with the keys of the object corresponding to the name of the header, and the value corresponding to the header value. This cleans up our code considerably, and makes the Request declaration more expressive.

All request objects have a default Accept header with the value 'text/javascript, text/html, application/xml, text/xml, */*'. This means we can remove the Accept header declaration in our code:

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/',
    headers: {
'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': data.length
    }
});

Another feature of MooTools is the special urlEncoded option, which automatically encodes our data into the application/x-www-form-urlencoded type. It also automatically adds a Content-Type and Content-Length header to our request if the method used is POST. We can therefore remove those two headers and use the urlEncoded option instead:

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/',
    urlEncoded: true
});

By default, the urlEncoded option of a request object is set to true, which means we don't even need to explicitly add it. So, we can go back to our original request code once more:

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/'
});

Sending Data

At this point, we've replaced about half of our original native code with this simple code block. Now we need to consider the data to be sent. In the native model, we passed the data to the send method of the XHR object. In MooTools, we can do the same using its send method:

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/'
});

request.send(data);

Like the native send method, the Request send method can be invoked with an argument to send data to the server. Here we send the value of the data variable to the server by passing it the send method.

However, we don't need to use the send method to pass data to the request; we can also declare the data to be sent using the Request options:

var data = 'name=Mark&age=23';

var request = new Request({
    url: 'http://foo.com/comment/',
    data: data
});

Here we added a new option called data to the options object. This option is used to declare the data that will be sent to the server, and in our case, we declared the value of this option to be the string value of our data variable.

We aren't limited to using strings as data values in the Request class though. MooTools allows us to send other values, such as objects:

var request = new Request({
    url: 'http://foo.com/comment/',
    data: {
        'name': 'Mark',
        'age': 23
    }
});

In this example, we declared the value of the data option to be an object with two properties. When we send this request, MooTools automatically turns the object into a string that's readable by the server. The Request class is able to process different kinds of objects like this: plain objects, arrays, and even form elements. This gives us flexibility in our code and enables us to transparently send complex JavaScript objects to the server.

Attaching Event Handlers

The next thing we have to deal with is events. With native XHRs, we needed to attach a readystatechange event handler and check the readyState property, and then put our processing code inside the handler. For our example, we had a readystatechange event handler that looked like this:

xhr.onreadystatechange = function(){
        if (xhr.readyState == 4){
            if (xhr.status >= 200 && xhr.status < 300){
                notify.set('html', xhr.responseText);
            } else {
                notify.set('html', '<strong>Request failed, please try again.</strong>'),
            }
        }
    };

The MooTools Request class, on the other hand, lessens the complexity of the readystatechange event by providing not one but five main events:

  • The request event is dispatched right after the request is sent.

  • The complete event is dispatched when the request has finished.

  • The success event is dispatched for a successful request.

  • The failure event is dispatched for an unsuccessful request.

  • The cancel event is dispatched when a running request is stopped.

You can manage event handlers for these events to your request object using the MooTools event methods such as addEvent or removeEvent.

The request Event

The first event, request, is dispatched right after the request is sent. It is useful for displaying notification messages or loading images in your interface to tell the user that something is happening. In our native example, we logged a message right after the request was sent. This is a good candidate for use with our request event:

var notify = $('notify'),

var request = new Request({
    url: 'http://foo.com/comment/',
    data: {
        'name': 'Mark',
        'age': 23
    }
});

request.addEvents({

    'request': function(){
        notify.set('html', '<strong>Request sent, please wait.</strong>'),
    }

});

When our request object is sent in this example, the request event will be dispatched, which in turn invokes the event handler we attached. The HTML source of our notify element will then be updated, informing our users that an action is taking place.

The complete Event

The next event, complete, is dispatched when the request has been completed. This event, however, does not tell us whether the request was successful or not—it simply tells us that the request has been done. Therefore, this event shouldn't be used for data processing event handlers. Instead, it should be used for "cleanup" purposes, such as removing elements you've added during the request event.

var spinner = new Element('img', {src: 'spinner.gif'});

var request = new Request({
    method: 'get',
    url: 'http://foo.com/index.html'
});

request.addEvents({

    'request': function(){
        spinner.inject(document.body, 'top'),
    },

    'complete': function(){
        spinner.destroy();
    }

});

In this separate example, we attached event handlers for the request and complete events. For the request event, we displayed a spinner image in our interface to tell the user that we're loading something. We then removed this spinner during the complete event to signify that we finished loading the data. Since we're not doing anything like this in our original native example, we didn't attach a complete handler in the earlier example.

The isSuccess Function

The next two events, success and failure, are the two most important events when it comes to requests, since these events are dispatched right after the complete event to inform us whether our request was successful or not. To determine whether an event was successful, the Request class uses a function named isSuccess. When the request is completed, the request object will invoke this function to check whether the request was successful. If the function returns true, then the request is successful and the request object will dispatch the success event. If the function returns false, the request is considered unsuccessful and the request object will dispatch the failure event.

The default isSuccess function looks like this:

isSuccess: function(){
    var status = this.status;
    return (status >= 200 && status < 300);
}

As you can see, the criteria used in the isSuccess method are the same as those we used in the native example: if the status of the response is greater than or equal to 200 and is less than 300, the request was successful.

There are times, though, when the default isSuccess criteria doesn't suffice for your applications. Thankfully, the Request class allows you to define your own isSuccess function by passing it using the options object:

var request = new Request({
    method: 'get',
    url: 'http://foo.com/index.html',
    isSuccess: function(){
        return this.status == 200;
    }
});

Here we define a different isSuccess method by passing it as an option in our Request declaration. The criterion used by our isSuccess function in this case is stricter than the default one: only responses with the status code of 200 will be considered successful.

The success Event

After consulting the isSuccess function, the request will fire one of two events. The first event, success, is dispatched when the request is successful:

var notify = $('notify'),

var request = new Request({
    url: 'http://foo.com/comment/',
    data: {
        'name': 'Mark',
        'age': 23
}
});

request.addEvents({

    'request': function(){
        notify.set('html', '<strong>Request sent, please wait.</strong>'),
    },

    'success': function(text, xml){
        notify.set('html', text);
    }

});

The event handler for a request's success event receives two arguments when invoked: text and xml. The second argument, xml, is simply the value of the responseXML property of the wrapped native XHR object. The text argument, on the other hand, is a stripped version of the responseText property value: all scripts tags are removed from the original responseText value. For example, say we received the following response data:

<div>Hello World</div>
<script>
    alert('Hello World'),
</script>

The request object will parse this response data and strip out the script tag. The text argument that our success event handler receives will therefore look like this:

'<div>Hello World</div>
'

The script tag was removed, leaving us with only the HTML source for the div.

MooTools strips out scripts for security reasons to prevent malicious scripts from being injected directly into the page. However, there are instances when you'd want to use those scripts and evaluate them in your application. The Request class, therefore, provides a special option called evalScripts. If you pass this option with the value of true, the Request object will automatically evaluate your scripts after stripping them.

Note

The evalScripts option, however, only works for <script> tags with bodies, such as <script>alert('Hello World'),</script>. Tags that are implemented with the src attribute, such as <script src="hello.js"></script>, will not be automatically loaded or evaluated, but will still be stripped.

Another option related to evalScripts is evalResponse. If you set this option to true in your request declaration, MooTools will automatically evaluate the whole body of the response as a script. MooTools will also automatically evaluate the value of the response body if the Content-Type of your response contains the words ecmascript or javascript.

Of course, there are cases where the automatic script stripping will not be what you want to do. In such cases, you can access the raw responseText and responseXML values using the response object property of your request object, which stores the unprocessed response data from the server:

var notify = $('notify'),

var request = new Request({
    url: 'http://foo.com/comment/',
    data: {
        'name': 'Mark',
        'age': 23
    }
});

request.addEvents({

    'request': function(){
        notify.set('html', '<strong>Request sent, please wait.</strong>'),
    },

    'success': function(){
        notify.set('html', this.response.text);
    }

});

In this snippet, we removed the formal parameters for our success event handler, and instead accessed the raw response body using the response object property of the request.

The failure Event

In contrast to the success event, the failure event's handler functions receive only one argument: the wrapped native XHR object. MooTools passes the native XHR object so we can handle the failure ourselves. Take note, though, that even if we're not passed the response text and response xml values, we can still access them using the response property object in our failure event handler.

var notify = $('notify'),

var request = new Request({
    url: 'http://foo.com/comment/',
    data: {
        'name': 'Mark',
        'age': 23
    }
});

request.addEvents({

    'request': function(){
        notify.set('html', '<strong>Request sent, please wait.</strong>'),
    },

    'success': function(){
        notify.set('html', this.response.text);
    },

    'failure': function(){
notify.set('html', '<strong>Request failed, please try again.</strong>'),
    }

});

We didn't need any fancy error handling in our original native example, so we just attached a basic failure event handler.

Timeouts

The next thing we need to do is to add a timeout. In our native example, we used the abort method of the XHR object together with a setTimeout call to cancel the request after a specific amount of time. The Request class has a method called cancel that is equivalent to abort, and we can use that here:

var notify = $('notify'),

var request = new Request({
url: 'http://foo.com/comment/',
    data: {
        'name': 'Mark',
        'age': 23
    }
});

request.addEvents({

    'request': function(){
        notify.set('html', '<strong>Request sent, please wait.</strong>'),
    },

    'success': function(){
        notify.set('html', this.response.text);
    },

    'failure': function(){
        notify.set('html', '<strong>Request failed, please try again.</strong>'),
    }

});

setTimeout(function(){
    request.cancel();
    notify.set('html', '<strong>Request timeout, please try again.</strong>'),
}, 5000);

You'll notice that after we canceled our request, we updated our notify element to show that our request has timed out. Instead of putting this directly in our setTimeout function, we can also implement this as an event handler.

var notify = $('notify'),

var request = new Request({
    url: 'http://foo.com/comment/',
    data: {
        'name': 'Mark',
        'age': 23
    }
});

request.addEvents({

    'request': function(){
        notify.set('html', '<strong>Request sent, please wait.</strong>'),
    },

    'success': function(){
        notify.set('html', this.response.text);
    },

    'failure': function(){
        notify.set('html', '<strong>Request failed, please try again.</strong>'),
    },

    'cancel': function(){
        notify.set('html', '<strong>Request timeout, please try again.</strong>'),
    }

});

setTimeout(function(){
    request.cancel();
}, 5000);

Request objects dispatch the cancel event every time a running request is cancelled. In this example, our cancel event handler will be dispatched when our request times out after 5 seconds.

While our snippet looks good right now, we can actually make it cleaner. The MooTools Request class actually adds support for timeouts using the timeout option. So instead of writing our request code as we did above, we can modify it to look like this:

var notify = $('notify'),

var request = new Request({
    url: 'http://foo.com/comment/',
    data: {
        'name': 'Mark',
        'age': 23
    },
    timeout: 5000
});

request.addEvents({

    'request': function(){
        notify.set('html', '<strong>Request sent, please wait.</strong>'),
},

    'success': function(){
        notify.set('html', this.response.text);
    },

    'failure': function(){
        notify.set('html', '<strong>Request failed, please try again.</strong>'),
    },

    'timeout': function(){
        notify.set('html', '<strong>Request timeout, please try again.</strong>'),
    }

});

Instead of using the cancel method with setTimeout, we simply added a timeout option to our Request declaration. The request class will automatically handle the timeout for us, and dispatch a timeout event when the request times out. You'll notice that we also changed our cancel event handler to a timeout event handler since we're no longer handling the cancel event.

Event Handler Declarations

At this point our code is almost complete, and we can now add the send invocation to finish it. Before we do that, however, we need to check out one more feature of the request declaration. Instead of attaching event handlers using addEvent or addEvents, we can actually include them in the request declaration by using the "on" prefix form:

var notify = $('notify'),

var request = new Request({

    url: 'http://foo.com/comment/',

    data: {
        'name': 'Mark',
        'age': 23
    },

    timeout: 5000,

    onRequest: function(){
        notify.set('html', '<strong>Request sent, please wait.</strong>'),
    },

    onSuccess: function(){
        notify.set('html', this.response.text);
    },

    onFailure: function(){
        notify.set('html', '<strong>Request failed, please try again.</strong>'),
    },
onTimeout: function(){
        notify.set('html', '<strong>Request timeout, please try again.</strong>'),
    }

});

We moved the event handlers from a separate addEvent call to the actual Request declaration by capitalizing their names then attaching an "on" prefix to them. This declaration form is similar to the one above, but it's cleaner and tighter and therefore used more often in development.

Sending the Request

This brings us finally to the part where we send the request. To complete our code, we simply have to send the request by invoking the send method:

var notify = $('notify'),

var request = new Request({

    url: 'http://foo.com/comment/',

    data: {
        'name': 'Mark',
        'age': 23
    },

    timeout: 5000,

    onRequest: function(){
        notify.set('html', '<strong>Request sent, please wait.</strong>'),
    },

    onSuccess: function(){
        notify.set('html', this.response.text);
    },

    onFailure: function(){
        notify.set('html', '<strong>Request failed, please try again.</strong>'),
    },

    onTimeout: function(){
        notify.set('html', '<strong>Request timeout, please try again.</strong>'),
    }

});

request.send();

You already saw the send method a few sections back when we discussed how to factor in the data being sent to the request. The MooTools send method looks very similar to the native send method for XHR objects: we can pass the string data to be sent by making it an argument to the send method.

However, the send method will only use the argument as the data for the request if the argument is a string or an element. If you pass an object to the send method, for example, it won't be sent to the server. Thus, something like request.send({name: 'Mark'}) won't work.

This is because the send method's argument isn't actually called data—it's called options. This options object is similar to the options object you pass to the Request constructor, but it understands only three options: url, method, and data.

When you pass a string or element argument to send, the method actually transforms it into the data property of an options object. Thus, send('name=Mark') is the same as doing send({data: 'Mark'}). If we want to send an actual object using the send method, we have to use a real options object, like send({data: {name: 'Mark'}}).

Passing an options object to send makes it possible to create reusable request objects.

var request = new Request({
    link: 'chain',
    onSuccess: function(){
        console.log(this.response.text);
    }
});

request.send({url: '/index.html', method: 'get'});
request.send({url: '/comments', method: 'post', data: {name: 'Mark'}});

Here we created a single request object, then used options objects with the send method so that we can send different requests using a single request object. Note that passing in different values for the options to send does not change the actual option values of the request object. This means that even if we used a different url value in our send options, the actual url of the request object won't be changed—it will still be a blank string, which is the default value.

Request Sending Modes

You'll notice from our last example that we added a new option to the request declaration called link. This option is used to set the behavior of the request object if a send call is issued while a request is still running.

By default, the value of this option is 'ignore'. In this mode, the request object will ignore additional calls to send while it is running.

var request = new Request({
    link: 'ignore',
    onRequest: function(){
        console.log(this.response.text);
    }
});

request.send({url: '/index.html', method: 'get'});
request.send({url: '/comments', method: 'post', data: {name: 'Mark'}});

In this snippet, only the first request—the GET request to /index.html—will be honored. Because we called send immediately after sending the first request, the second request will be ignored. This is because the second time we invoked send, our original request was still running. By default, all request objects use the ignore mode.

The second possible value for link is 'cancel'. In this mode, subsequent calls to the send method will cancel the current running request.

var request = new Request({
    link: 'cancel',
    onRequest: function(){
        console.log(this.response.text);
}
});

request.send({url: '/index.html', method: 'get'});
request.send({url: '/comments', method: 'post', data: {name: 'Mark'}});

In this example, the second send call will cancel the previous one. Therefore, the POST request to /comments will be the one honored by the request object and the first GET request will be canceled.

The last possible value for link is 'chain'. In this mode, requests will be "chained": if the send method of the request object is called while it is running, the request will wait for the currently running request to finish before sending the new one.

var request = new Request({
    link: 'chain',
    onRequest: function(){
        console.log(this.response.text);
    }
});

request.send({url: '/index.html', method: 'get'});
request.send({url: '/comments', method: 'post', data: {name: 'Mark'}});

Both requests will be sent in this example. First, the GET request will be sent, and the second POST request will be added to the request chain. When the GET request is finished, the request object will automatically send the POST request.

Our Final Code

This brings us finally to the part where we send the request. To complete our code, we simply have to send the request itself by invoking the send method:

window.addEvent('domready', function(){

    var notify = $('notify'),

    new Request({

        url: 'http://foo.com/comment/',

        data: {
            'name': 'Mark',
            'age': 23
        },

        timeout: 5000,

        onRequest: function(){
            notify.set('html', '<strong>Request sent, please wait.</strong>'),
        },

        onSuccess: function(){
            notify.set('html', this.response.text);
        },
onFailure: function(){
            notify.set('html', '<strong>Request failed, please try again.</strong>'),
        },

        onTimeout: function(){
            notify.set('html', '<strong>Request timeout, please try again.</strong>'),
        }

    }).send();

});

You'll notice that the original request variable assignment was removed from this snippet. Since we don't need to store the request, we can simply do away with the assignment and send the new instance directly. We also wrapped the whole snippet in a domready event handler, as in our original example.

Let's take another look at the original native request code:

window.addEvent('domready', function(){

    var notify = $('notify'),
        data = "name=Mark&age=23";

    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'http://foo.com/comment/', true);

    xhr.onreadystatechange = function(){
        if (xhr.readyState == 4){
            if (xhr.status >= 200 && xhr.status < 300){
                notify.set('html', xhr.responseText);
            } else {
                notify.set('html', '<strong>Request failed, please try again.</strong>'),
            }
        }
    };

    xhr.setRequestHeader('Accept', 'text/html'),
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'),
    xhr.setRequestHeader('Content-Length', data.length);

    xhr.send(data);
    notify.set('html', '<strong>Request sent, please wait.</strong>'),

    // timeout
    setTimeout(function(){
        xhr.abort();
        notify.set('html', '<strong>Request timeout, please try again.</strong>'),
    }, 5000);

});

I think we will all agree about which code is better. Our Request-based code is cleaner, easier to read, and more modular, and it fits the MooTools style perfectly.

Subclassing Request

A great thing about Request is that it's implemented as a class rather than a type object. This gives us the opportunity to create subclasses that extend the functionality of Request.

Request Internals

To understand how Request subclassing works, we must first get a feel for the internals of the MooTools Request class. Request is a very simple class. It uses all of the three mixin classes we discussed in Chapter 5: Options, Events, and Chain. We use the Options mixin for the initialize method, which is how we're able to pass an options object when we create a new object. We use the Events mixin to enable the request object to use event handlers and dispatch events. And we use the Chain mixin to power the "chain" mode of request sending.

When a new Request instance is created, the initialize method does two things. First, it creates the internal XHR object that will be used to send the requests and stores it in the xhr property of the instance. Second, it takes the options object argument and merges it with the default options using the setOptions method from the Options class. Remember that this method enables us to define event handlers using the onEventName format, which is how we're able to combine the event handler declaration with the other options in our request object instantiation.

At this point, the Request instance contains a non-initialized native XHR object. Request doesn't actually call the open method of the XHR until later in the process. Rather, all processes at this point are "buffered" internally. For example, when we add new request headers using the setHeader method, the Request instance doesn't actually add them immediately to the XHR object using setRequestHeader. Instead, it stores the headers first in the internal headers property. This makes the class flexible enough so that changes can be easily made without having to reset the XHR instance.

The send Method

The bulk of the Request processes happens in the send method. When called, it first sets the current request as "running," so that subsequent calls to it will be controlled. The method then prepares the internal options: it combines the options object passed to it (if there is one) to the option values defined during the creation of the request object.

The send method then prepares the data for sending. If our data is a simple string, it does no further parsing. If our data is an element, it will first call the toQueryString method of Element to turn form elements into a query string value. And if our data is an object, it'll use the Object.toQueryString generic to turn the object into a proper query string. Thus, no matter what kind of data we pass to Request, it always turns it into a string.

The next step the method takes is to initialize the native XHR object by calling its open method. It uses the prepared values from the options to perform this task: an uppercase version of options.method for the method argument, options.url for the url, and options.async for the async argument. It then attaches the readystatechange handler for the XHR object: a method called onStateChange, which we'll discuss in a second. Next, it adds the appropriate headers to the XHR object by looping through the internal header property and adding them using setRequestHeader. Finally, it dispatches the request event before sending the native XHR object.

The onStateChange Method

The send method, however, is only half of the puzzle. The other half is the readystatechange event handler method, onStateChange. After the send method sends the request, control of the request goes over to the onStateChange method, which waits for the wrapper XHR object to reach the ready state of 4. When this happens, onStateChange prepares the response object property of the request, setting the raw responseText value for response.text and raw responseXML value for response.xml.

The onStateChange method then calls the isSuccess function to check whether the request was successful. If the request was successful, the method calls the success method, which parses and prepares the response.text value to strip out script tags. This success method then passes this new formatted value to the onSuccess method, which dispatches the complete and success events.

Unsuccessful requests, in contrast, will make the onStateChange method invoke the failure method, whose main job is to invoke the onFailure method that dispatches the complete and failure events.

These four methods—success, onSuccess, failure, and onFailure—are the usual points for subclassing. Most Request subclasses will override these four methods in order to create a specialized version of the class.

Success Overriding

The most basic kinds of Request subclasses add additional options and use a different parsing method for the responseText value. Because it is the job of the success method to parse the response data before passing it to success event handlers, most subclasses override only this method for their purposes.

For example, let's take one of the Request subclasses included in MooTools Core: Request.JSON. This is a specialized request class used for JSON requests that automatically turns the response data into a JavaScript object. Normally, if we want to include a JSON request using the Request class, we do this:

var request = new Request({
    url: 'myfile.json',
    method: 'get',
    headers: {
        'Accept': 'application/json'
    },
    onSuccess: function(text){
        var obj = JSON.decode(text);
        if (obj){
            console.log(obj.name);
        } else {
            console.log('Improper JSON response!'),
        }
    }
}).send();

Here we requested a JSON file from the server. We made sure that the server will only send us JSON back by attaching an Accept header that has the value of the JSON mimetype. In our success event handler, we then parse the text response from the server using JSON.decode to turn it into a JSON object. If the parsing is successful, we'll get an object result, the name property of which we output to the console. If the parsing fails, we log an error message.

Request.JSON automates this process, and handles the parsing process using JSON.decode internally.

var request = new Request.JSON({
    url: 'myfile.json',
    method: 'get',
    onSuccess: function(obj){
        console.log(obj.name);
    },
    onFailure: function(){
        console.log('Improper JSON response!'),
}
}).send();

Instead of passing a string to the success event handler, Request.JSON passes an object that is the parsed value of the JSON response. This saves us from having to call JSON.decode ourselves. Request.JSON also automatically dispatches the failure event in the case of the response failing the JSON.decode parsing.

The source itself of Request.JSON is very simple:

Request.JSON = new Class({

    Extends: Request,

    options: {
        secure: true
    },

    initialize: function(options){
        this.parent(options);
        Object.append(this.headers, {
            'Accept': 'application/json',
            'X-Request': 'JSON'
        });
    },

    success: function(text){
        var secure = this.options.secure;
        var json = this.response.json = Function.attempt(function(){
            return JSON.decode(text, secure);
        });

        if (json == null) this.onFailure();
        else this.onSuccess(json, text);
    }

});

The Request.JSON class is a direct subclass of Request. It overrides the original initialize method in order to add the appropriate headers to the request instance, but it also retains the original initialize process from Request using this.parent(). You'll see that the success method is the only real method that's overridden. The Request.JSON version of the success method first tries to turn the response data into a proper JSON object using JSON.decode. If the process fails, it calls the onFailure method, which dispatches the failure event. If the process succeeds, it calls the onSuccess method, passing in the newly parsed object. The onSuccess method will then dispatch the event handlers for the success event, passing the parsed object.

As this example shows, subclassing the Request object is a very simple affair. The flexibility of the original Request class itself makes the process really simple, and gives Request the ability to be subclassed into new and more useful classes.

The Wrap-Up

In this chapter we learned all about the HTTP request-response cycle and how it affects the design of our applications. We also found out about asynchronous requests, and how the native XMLHttpRequest object enables us to issue requests from inside our code to create more powerful and dynamic applications. Finally, we talked about the MooTools Request class, and how it provides a nice abstraction of the native XHR API.

In the next chapter, we'll learn about another fancy technique for improving our interfaces: animation. We'll talk about how animation is done in JavaScript, as well as how the Fx classes give us a very powerful framework for complex animations.

So if you're ready, follow my lead and jump into the magic of animation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset