Make the XMLHttpRequest Object Work Cross-Browser

Make the XMLHttpRequest Object Work Cross-Browser

he World Wide Web has traditionally used a book or slideshow metaphor as the basis for navigation between hyperlinks. Clicking on a link is like flicking from one page to another in a book or magazine, or like operating a slideshow projector?click a button or link, and a new page or slide appears. But those metaphors don’t work well for all applications. This article describes a modern way to proceed when you want to expose information without changing pages. The XMLHttpRequest object?now widely available?is the answer.

The Web wasn’t originally intended for application delivery, but that’s what many HTML-based Web sites provide. Such applications attempt to present a user interface that is less like a book and more like a control console. Examples of real-world consoles are sound engineer’s decks and air traffic control systems. Users expect to be able to operate the console controls without each operation replacing the entire console with a fresh copy. In other words, they want the console to stay where it is while they work.

Early Web applications achieved this effect with the “hidden frame” technique, using a tag with its height (or width) attribute set to 0%. Using this method, the visible frame (the console) appeared to change dynamically, while the other, hidden frame was free to shuffle data between the browser and the server as often as necessary using form submissions. The visible frame obtained fresh data from the hidden frame whenever it was updated. That approach was definitely a hack, though. It required hand crafting a simple protocol on top of HTTP, HTML, and JavaScript. Another hack used a form submission that relied on an HTTP 204 No Content response from the server, using piggybacked cookies to carry the precious return data.

A better solution is to throw out the page metaphor entirely and craft a way to submit form data directly from the current page, but without changing the current page view. That’s what the XMLHttpRequest object does for you, and in such a natural way that its future popularity is assured.

What You Need
Internet Explorer, or nearly any other modern Web browser, such as Firefox, Camino, Epiphany, Mozilla Application Suite, Netscape Navigator 6+, K-Meleon, etc.

Editor’s Note: The editorial staff of DevX would like to add our condolences to the voices of so many around the Web. Our longtime author and friend, Nigel McFarlane, passed away in June 2005. This article is one of two that Nigel wrote for us before his death; they are published with the permission of his family. Nigel McFarlane was the author of two books and a frequent contributor to Mozilla and the open source movement. ?Lori Piquet

Introducing XMLHttpRequest
The XMLHttpRequest object is a JavaScript (ECMAScript) host object, meaning that it exposes features of the JavaScript interpreter’s host (the Web browser) to scripts. It’s similar to the Image and Option objects, in that you create instances of it using the JavaScript new operator. It’s different in that it’s not ever contained in any HTML or XML document; instead, it’s a bit of the browser’s own internal plumbing that’s exposed to the Web page’s scripting environment. You can think of it as similar to the document.cookie property, which provides an access point to the browser’s internal database of cookies.

Here’s a bit of code that shows how to use this object?I’ll cover what’s portable and what’s not in the remainder of this article. All browsers except Microsoft’s Internet Explorer follow the Mozilla implementation (which nearly adheres to the original IE implementation), so I’ll use the Mozilla syntax as a starting point, and show you IE-specific code later. To whet your appetite, here’s a basic JavaScript form submission:

   var form    = document.form[0];   var params  = encodeURI(      "fieldA=" + form.fieldA.value +      ";fieldB=" + form.fieldB.value);      var request = new XMLHttpRequest();   var len;"POST", "post_handler.cgi");   request.send(params);      if ( request.status == 200 )   {     len = request.getResponseHeader("Content-Length");     form.fieldA.value = len;     form.fieldB.value = "Form data sent OK!";   }

The preceding code sends the values of two form fields (name=”fieldA” and name=”fieldB”) to the server. First it collects the data from the HTML form. Next, it packs the data into the correct form-encoded string. The encodeURI() method is the fancy modern equivalent of the trusty escape() method, so the code uses that.

   var params  = encodeURI(      "fieldA=" + form.fieldA.value +      ";fieldB=" + form.fieldB.value);   

The browser performs that encoding automatically in a non-scripted (standard) form submit process, but for a scripted version, you have to remember to escape the data yourself.

So far, the code is all plain JavaScript and pretty simple. But then, it assigns a special XMLHttpRequest instance object to the request variable.

   var request = new XMLHttpRequest();

That’s new. When the script calls the open() method, the object composes the HTTP header for the request, so all that’s left to do is send off the data, using send()."POST", "post_handler.cgi");   request.send(params);

When the send method returns, the request object is filled with whatever data and headers the server sent back in the HTTP response. Simple! In this case the script checks that the response code indicated success (but all 200-series return codes technically indicate success, so we could be more thorough), then digs out a header, and displays it for the user by writing it into one of the form fields already visible on the page.

   if ( request.status == 200 )   {     len = request.getResponseHeader("Content-Length");     form.fieldA.value = len;     form.fieldB.value = "Form data sent OK!";   }

Now for the gory bits. Technically, this request object is a network proxy object, and follows the Half Sync/Half-Async design pattern (if you believe in such things). You can read the theoretical details here,, but the practical explanation is that the object acts as a network endpoint. If you put in true as a third argument to open(), the request will proceed (roughly) as though it were sent with this line:


In other words, the third argument therefore determines whether JavaScript execution halts while runs. If it does, that’s a delay (potentially a long one) for the user. If it doesn’t, the UI won’t freeze up, but you must install an event handler on the request object so that you can find out when the request completes. You assign such a handler to the request object before calling send(), using code such as this:

   // create a response handler   function response_handler()   {     // Start any clean-up processing here     if ( request.status == 200 ) {       ... as before ...     }   }…);   request.onload = response_handler;   request.send(…);

The script assigns the response_handler() function to the onload event of the request (XmlHttpRequest) object; therefore the response_handler() function runs when the object receives its data from the server in response to the request. This sample code doesn’t do much with the return data, but that data could be a script, an XML fragment, plain text (character data), or anything else. The data exists as a property of the request object until you do something with it, such as storing it in a JavaScript variable or inserting it into the DOM for the currently loaded page. The important point to remember is that it’s ultimately just data; it’s not “special” in any way.

You can construct either a GET or POST request, and you can send that request synchronously (the default) or asynchronously, by passing true as the third parameter to the open() method.

So, now that you’ve seen the basics of how the XMLHttpRequest object works, you need to remain aware that the implementations differ between Internet Explorer and other modern browsers.

What’s Portable
The good news is that most of the logic required for synchronous requests is highly portable. Once you have an XMLHttpRequest object variable, code such as this works well across all modern browsers.

   // the request variable holds an XMLHttpRequest    // object instance   request.setRequestHeader("X-Foo-Header","Bar");"GET", "post_handler.cgi");   request.send("");      if ( request.status == 200 )   {     len = request.getResponseHeader("Content-Length");     len = request.getAllResponseHeaders().length;     alert(request.status);     alert(request.responseText);     alert(request.responseXML);   }

This code manipulates HTTP headers both before and after the request runs. It can also inspect the data sent back using portable object properties such as status. For simple cases, that may be all you need. For more complex cases (which includes early versions of Opera), you need a scripting library from Andrew Gregory to put these features in place before you can assume support for all version 5.x onwards browsers. Those old Opera versions won’t be around for long, though.

What’s Not Portable
Alas, there’s a bit of custom coding to do both before and after using the XMLHttpRequest object.

First, creating the request object differs depending on the browser version. As Microsoft’s implementation is an ActiveX control, as compared to the JavaScript implementations of the later imitators, you must treat IE differently. Here’s some portable code that instantiates an XMLHttpRequest object based on the browser type and version.

   var ua = navigator.userAgent.toLowerCase();   if (!window.ActiveXObject)     request = new XMLHttpRequest();   else if (ua.indexOf('msie 5') == -1)     request = new ActiveXObject("Msxml2.XMLHTTP");   else     request = new ActiveXObject("Microsoft.XMLHTTP");

The preceding code shows that for all browsers except IE, you can use the Mozilla semantics. For IE, the script grabs the userAgent string and determines the IE browser version based on the version number contained in that string. For IE versions other than 5x, it uses the most up-to-date ActiveX implementation of XMLHttpRequest. For ancient 5.x versions it creates an older ActiveX implementation instance. Both ActiveX objects are marked as “script safe” and “web safe,” so users don’t need to lower their security settings for you to use these objects.

Second, asynchronous requests need a bit of portability legwork. The process to install a handler is different for IE and Mozilla.

If you try to use Mozilla’s onload object property to register the handler for all browsers (as shown earlier in this article) then you’re in for trouble. That approach allows your handler function to be very simple, but it’s not portable. Here’s a portable handler registration that has the same effect as an onload handler target, but uses the portable onreadystatechange event instead.

   function progress_handler()   {     if (request.readyState != 4 )  // not yet finished       return;        // finished. Continue as before     if (request.status == 200 )     { … }   }      request.onreadystatehandler = progress_handler;   request.send(…);

The Google “Suggest” Code Squeeze
One popular user of XMLHttpRequest is the Google Suggest Web page. This page is the same as the standard search window, but adds a drop-down list of remembered terms to the text box. It’s like the drop-down list in the location bar of the browser, except the list of terms comes from Google’s own server. The page retrieves the terms from the server after you’ve displayed the page by repeatedly submitting your typed partial search terms to the Google server in real time.

If you view the source of Google’s page, you’ll find a tag that includes a file called ac.js. Load that rather cryptic file and you'll see buried in it a very short cross-platform wrapper for the XMLHttpRequest object. Although you'll have to decode the meaningless variable names, this is a good example on which to base your cross-platform JavaScript XMLHttpRequest wrapper code.

So, the next time you begin planning a Web application, ask yourself if you're building a book or a console-type application. If the latter, replacing the current page needlessly can be like flashing a sign in the user's face. Take a step back and ask yourself: Is refreshing the entire page really necessary? If not, the XMLHttpRequest object is an increasingly viable alternative for sophisticated browser-based applications?and now that you know the required cross-platform scripting contortions aren't that painful, if you experiment with this useful object you may find that you're hooked for good!


Share the Post: