Wednesday, January 17, 2007

XMLHTTP

Using XMLHttp

After you have created an XMLHttp object, you are ready to start making HTTP requests from JavaScript. The first step is to call the open() method, which initializes the object. This method accepts the following three arguments:

  • Request Type: A string indicating the request type to be made-typically, GET or POST (these are the only ones currently supported by all browsers).
  • URL: A string indicating the URL to send the request to.
  • Async: A Boolean value indicating whether the request should be made asynchronously.

The last argument, async, is very important because it controls how JavaScript executes the request. When set to true, the request is sent asynchronously, and JavaScript code execution continues without waiting for the response; you must use an event handler to watch for the response to the request. If async is set to false, the request is sent synchronously, and JavaScript waits for a response from the server before continuing code execution. That means if the response takes a long time, the user cannot interact with the browser until the response has completed. For this reason, best practices around the development of Ajax applications favor the use of asynchronous requests for routine data retrieval, with synchronous requests reserved for short messages sent to and from the server.

To make an asynchronous GET request to info.txt, you would start by doing this:

var oXmlHttp = zXmlHttp.createRequest();
oXmlHttp.open("get", "info.txt", true);

Note that the case of the first argument, the request type, is irrelevant even though technically request types are defined as all uppercase.

Next, you need to define an onreadystatechange event handler. The XMLHttp object has a property called readyState that changes as the request goes through and the response is received. There are five possible values for readyState:

  • 0 (Uninitialized): The object has been created but the open() method hasn't been called.
  • 1 (Loading): The open() method has been called but the request hasn't been sent.
  • 2 (Loaded): The request has been sent.
  • 3 (Interactive): A partial response has been received.
  • 4 (Complete): All data has been received and the connection has been closed.

Every time the readyState property changes from one value to another, the readystatechange event fires and the onreadystatechange event handler is called. Because of differences in browser implementations, the only reliable readyState values for cross-browser development are 0, 1, and 4. In most cases, however, you will check only for 4 to see when the request has returned:

var oXmlHttp = zXmlHttp.createRequest();
oXmlHttp.open("get", "info.txt", true);
oXmlHttp.onreadystatechange = function () {
if (oXmlHttp.readyState == 4) {
alert("Got response.");
}
};

The last step is to call the send() method, which actually sends the request. This method accepts a single argument, which is a string for the request body. If the request doesn't require a body (remember, a GET request doesn't), you must pass in null:

var oXmlHttp = zXmlHttp.createRequest();
oXmlHttp.open("get", "info.txt", true);
oXmlHttp.onreadystatechange = function () {
if (oXmlHttp.readyState == 4) {
alert("Got response.");
}
};
oXmlHttp.send(null);

That's it! The request has been sent and when the response is received, an alert will be displayed. But just showing a message that the request has been received isn't very useful. The true power of XMLHttp is that you have access to the returned data, the response status, and the response headers.

To retrieve the data returned from the request, you can use the responseText or responseXML properties. The responseText property returns a string containing the response body, whereas the responseXML property is an XML document object used only if the data returned has a content type of text/xml. (XML documents are discussed in Chapter 4.) So, to get the text contained in info.txt, the call would be as follows:

var sData = oXmlHttp.responseText;

Note that this will return the text in info.txt only if the file was found and no errors occurred. If, for example, info.txt didn't exist, then the responseText would contain the server's 404 message. Fortunately, there is a way to determine if any errors occurred.

The status property contains the HTTP status code sent in the response, and statusText contains the text description of the status (such as "OK" or "Not Found"). Using these two properties, you can make sure the data you've received is actually the data you want or tell the user why the data wasn't retrieved:

if (oXmlHttp.status == 200) {
alert("Data returned is: " + oXmlHttp.responseText;
} else {
alert("An error occurred: " + oXmlHttp.statusText;
}

Generally, you should always ensure that the status of a response is 200, indicating that the request was completely successful. The readyState property is set to 4 even if a server error occurred, so just checking that is not enough. In this example, the responseText property is shown only if the status is 200; otherwise, the error message is displayed.

The statusText property isn't implemented in Opera and sometimes returns an inaccurate description in other browsers. You should never rely on statusText alone to determine if an error occurred.

As mentioned previously, it's also possible to access the response headers. You can retrieve a specific header value using the getResponseHeader() method and passing in the name of the header that you want to retrieve. One of the most useful response headers is Content-Type, which tells you the type of data being sent:

var sContentType = oXmlHttp.getResponseHeader("Content-Type");
if (sContentType == "text/xml") {
alert("XML content received.");
} else if (sContentType == "text/plain") {
alert("Plain text content received.");
} else {
alert("Unexpected content received.");
}

This code snippet checks the content type of the response and displays an alert indicating the type of data returned. Typically, you will receive only XML data (content type of text/xml) or plain text (content type of text/plain) from the server, because these content types are the easiest to work with using JavaScript.

If you'd prefer to see all headers returned from the server, you can use the getAllResponseHeaders() method, which simply returns a string containing all of the headers. Each heading in the string is separated by either a new line character (\n in JavaScript) or a combination of the carriage return and new line (\r\n in JavaScript), so you can deal with individual headers as follows:

var sHeaders = oXmlHttp.getAllResponseHeaders();
var aHeaders = sHeaders.split(/\r?\n/);

for (var i=0; i < aHeaders.length; i++) {
alert(aHeaders[i]);
}

This example splits the header string into an array of headers by using the JavaScript split() method for strings and passing in a regular expression (which matches either a carriage return/new line couple or just a new line). Now you can iterate through the headers and do with them as you please. Keep in mind that each string in aHeaders is in the format headername: headervalue.

It's also possible to set headers on the request before it's sent out. You may want to indicate the content type of data that you'll be sending, or you may just want to send along some extra data that the server may need to deal with the request. To do so, use the setRequestHeader() method before calling send():

var oXmlHttp = zXmlHttp.createRequest();
oXmlHttp.open("get", "info.txt", true);
oXmlHttp.onreadystatechange = function () {
if (oXmlHttp.readyState == 4) {
alert("Got response.");
}
};
oXmlHttp.setRequestHeader("myheader", "myvalue");
oXmlHttp.send(null);

In this code, a header named myheader is added to the request before it's sent out. The header will be added to the default headers as myheader: myvalue.

Up to this point, you've been dealing with asynchronous requests, which are preferable in most situations. Sending synchronous requests means that you don't need to assign theonreadystatechange event handler because the response will have been received by the time the send() method returns. This makes it possible to do something like this:

var oXmlHttp = zXmlHttp.createRequest();
oXmlHttp.open("get", "info.txt", false);
oXmlHttp.send(null);

if (oXmlHttp.status == 200) {
alert("Data returned is: " + oXmlHttp.responseText;
} else {
alert("An error occurred: " + oXmlHttp.statusText;
}

Sending the request synchronously (setting the third argument of open() to false) enables you to start evaluating the response immediately after the call to send(). This can be useful if you want the user interaction to wait for a response or if you're expecting to receive only a very small amount of data (for example, less than 1K). In the case of average or larger amounts of data, it's best to use an asynchronous call.

No comments: