Cover by Sam Ruby, Leonard Richardson

Safari, the world’s most comprehensive technology and business learning platform.

Find the exact information you need to solve a problem on the fly, or go deeper to master the technologies and skills you need to succeed

Start Free Trial

No credit card required

O'Reilly logo

Cross-Browser Issues and Ajax Libraries

As always when web browsers are involved, different clients have different levels of support for XMLHttpRequest. And as always seems to happen, Internet Explorer is the major outlier. This isn’t quite fair, because XMLHttpRequest was a Microsoft invention and Internet Explorer was the first browser to support Ajax at all. But until the release of Internet Explorer 7, Ajax was implemented as Windows-specific technology: an ActiveX control called XMLHttp.

The cross-platform Mozilla project adopted the API of the XMLHttp control, but implemented it as a class you could instantiate directly from JavaScript. Other browsers followed this lead, and all current browsers now use the XMLHttpRequest name (including the new Internet Explorer). But old versions of Internet Explorer still make up a big portion of the user base, so cross-browser issues are still a problem.

Example 11-7 is a JavaScript function that always creates an object that acts like an XMLHttpRequest, even though under the covers it may be an ActiveX control. It was written by Bret Taylor and comes from his site at http://ajaxcookbook.org/.

Example 11-7. A cross-browser wrapper for XMLHttpRequest

function createXMLHttpRequest() { 
  if (typeof XMLHttpRequest != "undefined") { 
    return new XMLHttpRequest();
  } else if (typeof ActiveXObject != "undefined") { 
    return new ActiveXObject("Microsoft.XMLHTTP"); 
  } else { 
    throw new Error("XMLHttpRequest not supported");
  }
}

This function is a drop-in replacement for the XMLHttpRequest constructor in Example 11-3, instead of this:

request = new XMLHttpRequest();

you might write this:

request = createXMLHttpRequest();

I know of two other major cross-browser issues. First, the Safari browser doesn’t support the PUT and DELETE methods. If you want your service to be accessible from Safari, you’ll need to allow your clients to simulate PUT and DELETE requests with overloaded POST. Second, Microsoft Internet Explorer caches successful responses indefinitely. This makes it look to the user like your resources haven’t changed, even when they have. The best way to get around this is to send proper ETag response headers with your representations, or to disable caching altogether with Cache-Control. You can use the XMLHttpRequest test suite to find out about more minor cross-browser quirks.

Because Ajax is a very important niche for JavaScript applications, some JavaScript libraries include wrappers for hiding the differences between browsers. I’m not going to cover these frameworks in detail, because they act more as standard libraries for JavaScript than tools for building web service clients. I will show how to make simple HTTP requests with two popular libraries, Prototype and Dojo. Another popular library, script.aculo.us, is based on Prototype.

Prototype

Prototype introduces three classes for making HTTP requests:

  • Ajax.Request: a wrapper around XMLHttpRequest that takes care of cross-browser issues and can call different JavaScript functions on the request’s success or failure. The actual XMLHttpRequest object is available as the transport member of the Request object, so responseXML will be through request.transport.responseXML.

  • Ajax.Updater: a subclass of Request that makes an HTTP request and inserts the response document into a specified element of the DOM.

  • Ajax.PeriodicalUpdater, which makes the same HTTP request at intervals, refreshing a DOM element each time.

I’ve implemented the del.icio.us Ajax client in Prototype, and it was mostly the same as the client I showed you starting in Example 11-1. The code snippet below mostly replaces the code in Example 11-3 where the XMLHttpRequest constructor used to be. Note the new script tag, the use of request.transport instead of request, and the use of Prototype’s onFailure hook to signal a failure (such as an authorization failure) to the user.

Example 11-8. A portion of ajax-delicious-prototype.html

...
<script src="prototype.js"></script> 
<script type="text/javascript">
  ... 
  var request = new Ajax.Request("https://api.del.icio.us/v1/posts/recent", 
                                 {method: 'get', onSuccess: populateLinkList, 
                                 onFailure: reportFailure});
  function reportFailure() { 
    setMessage("An error occured: " + request.transport.status);
  }

  // Called when the HTTP request has completed. 
  function populateLinkList() { 
    setMessage("Request complete."); 
    if (netscape.security.PrivilegeManager.enablePrivilege) {
      netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
    }

    posts = request.transport.responseXML.getElementsByTagName("post");
    ...

In its quest to simplify XMLHttpRequest, Prototype hides some of the features. You can’t set request headers, or specify a username and password for basic HTTP auth. So even if you’re using Prototype, you might want to keep around a snippet of code like the one in Example 11-7. On the other hand, the Prototype implementation of the del.icio.us client doesn’t need the username and password text fields at all: it just needs a button. The end user’s browser will prompt her anyway for her del.icio.us username and password.

Dojo

The Dojo library provides a uniform API that not only hides the differences between browsers when it comes to XMLHttpRequest, it hides the difference between XMLHttpRequest and other ways of getting the browser to send an HTTP request. These “transports” include tricks that use HTML tags, such as JoD. All the variants on XMLHttpRequest are kept in the dojo.io.XMLHttp transport class. For all transports, the bind method is the one that makes the HTTP request.

As with Prototype, I’ve implemented the del.icio.us Ajax client with Dojo, and it’s mostly the same as the original, except for the section in Example 11-3 where the XMLHttpRequest constructor used to be. Example 11-9 shows the relevant portions of ajax-delicious-dojo.html.

Example 11-9. Some portions of ajax-delicious-dojo.html

...
  <script src="dojo/dojo.js"></script>
  <script type="text/javascript">
    ...
    dojo.require("dojo.io.*");
    dojo.io.bind({ url: "https://api.del.icio.us/v1/posts/recent", load:
                   populateLinkList, error: reportFailure });

    function reportFailure(type, error) { 
      setMessage("An error occured: " + error.message); 
    }

    // Called when the HTTP request has completed. 
    function populateLinkList(type, data, request) { 
      setMessage("Request complete.");
      if (netscape.security.PrivilegeManager.enablePrivilege) {
        netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
      }

      posts = request.responseXML.getElementsByTagName("post");
    ...

The error-handling function is passed a dojo.io.Error object with members called number and message. You can ignore the first argument: it’s always “error.” You can also ignore the first argument to the success-handling function (it’s always “load”). The second argument, called data above, is an interface to use Dojo’s DOM manipulation interface. If you want to use the XMLHttpRequest interface instead, you can ignore that argument too.

Find the exact information you need to solve a problem on the fly, or go deeper to master the technologies and skills you need to succeed

Start Free Trial

No credit card required