XMLHTTP and XMLHttpRequest  Hot PDF Print E-mail
Tag it:
Delicious
Furl it!
Digg
NewsVine
Reddit
YahooMyWeb
Technorati
Articles Reviews Ajax
Written by Adi Bach   
Saturday, 14 October 2006


{mos_sb_discuss:53}

Even the methods now referred to as " AJAX" are not new. Microsoft's version of the interface, XMLHTTP, has been available as an ActiveX object in various incarnations since a preview release of IE 5.0 in late 1998 (the final release of IE 5.0 was in March 1999). Companies have been capitalizing on this development model in IE-only environments for years. Beginning slightly before the release of version 1.0 of Mozilla's browser product, a native object, modeled after Microsoft's interface and named XMLHttpRequest, has been available. Recently, Safari (since version 1.2), Opera (since version 8), and Konqueror have included compliant interfaces, also named XMLHttpRequest. 


In the last few months, the use of XMLHTTP and its variants has increased dramatically. The adoption of the technology by Google in their Google Suggest and Google GMail product offerings has been primarily responsible for introducing the development method to the masses.

Additionally, an article by Adaptive Path coining the term " AJAX" allowed developers to refer to the collection of technologies with one unifying name, which has facilitated communication and awareness of the subject.

The Interfaces

The following are the methods of the XMLHTTP and XMLHttpRequest interfaces that are relevant and, for the most part, supported in all browsers:

  •           abort(): Aborts a request.

  •           getResponseHeader(key): Returns the value of the HTTP response header named key.

  •           open(method, uri, [async, [username, [password]]]): Sets the HTTP request method to method and the request URI to uri. Optionally, it sets the flag indicating whether or not to process the request asynchronously to async, and sets a username and password to use for authentication, if necessary. The method attribute is typically either get or post. Using post is appropriate if you are pushing data in the request (see the send() method). In almost all cases, async should be left to its default value of true.

  •             overrideMimeType(type): Forces the interpretation of the response as if the server sent the   mime type of the named type. This method is not available in IE. This can be useful if it is        impractical to alter the server to send a given mime type. This must be called prior to send(). It can also be used to avoid a Mozilla bug that sometimes causes the browser to hang when processing a non-XML response.

  •             send(requestContent): Sends the HTTP request. The parameter requestContent is used for  requests with a method of post (see the open() method), and represents the request body. If the request is asynchronous, this method returns immediately. Early versions of Mozilla had difficulty sending a string as request content. requestContent should be null for a get request. For post requests, it must be null, a string, or a DOM Document object. If it is the latter, the object is serialized. The content of this parameter is included in the request body and is therefore accessible to the server.
 
  •             setRequestHeader(key, value): Assigns the value of value to the HTTP request header     named key. This method must be called after calling open() and before calling send().

The open() and send() methods handle the primary functionality of the interface. The open() method  sets various parameters of the request, such as the HTTP request method (get or post) and the request  URI.

The request URI will typically be the relative location of the script on the server to call. You may  use a fully qualified URI if you would like, but due to security restrictions, the domain of the URI must match that of the page containing the JavaScript issuing the request.

The implication of this is that if you would like to fetch data from third-party data sources located on other domains (such as, for instance, weather data feeds), you must proxy such requests through a server-side script residing on your server.

The optional async parameter of the open() method determines the behavior of the send() method.

If set to true (or left to its default), the send() method will issue the HTTP request and immediately  return. This means that lines of your JavaScript code subsequent to the call to send() will execute  regardless of the response to the issued request.

Instead, response handling is triggered by the firing of  an event, handled by an event handler. This feature is the first A in AJAX, and it allows you to develop  applications that behave much like desktop applications. The specifics of this event handling are set via  properties of the interface:

  •             onreadystatechange: The function called when the ReadyStateChange event is fired (that is,  when the readyState property changes). The value of this property should be a JavaScript function. Make sure not to reference the function with parentheses, or the function will be called  and the value of this property will end up being the return value of your function rather than the function itself.
 
  •             readyState: Read-only. This is an integer representing the current state of the request. Possible values of this property range from 0 to 4 and indicate increasing progress. A value of 4 indicates that the request has been completed and the response is fully processed. Headers and status are  available with a readyState of at least 2.

  •             responseText: Read-only. This is the response body as text. It is non-null with a successful     request starting with a readyState of 3. The responseText is fully populated at a readyState of 4.


  •             responseXML: Read-only. This is the response body parsed as text/xml content and packaged  as a DOM Document object. It is non-null with a successful request at a readyState of 4.

  •             status: Read-only. This is the integer HTTP response status code. 200 indicates a successful   response. For other possible status codes, including numerous error codes, see section 6.1.1 of           the HTTP/1.1 RFC (http://www.rfc-editor.org/rfc/rfc2616.txt)

With the exception of responseText and responseXML, these properties are mainly involved in determining if and when a response has been received. This is necessary because your requests are asynchronous, meaning you cannot rely on the availability of the response at any given line of code based on execution order or time. Rather, asynchronous communication relies on an event-driven model for processing.

The readyState property contains a value that indicates the progress of the request. Every time this value changes, a ReadyStateChange event is fired. If defined, the onreadystatechange handler is then called to handle the ReadyStateChange event. Note that for a given request, the readyStateChange event will fire at least four times.

In most cases, you will not be interested in any of these events except for the final one, when the readyState property changes from 3 (loading) to 4 (complete). However, a readyState of 4 does not mean that the request was successful. It indicates only that all operations have been completed.

You must still check the status property to gain insight to the success of the request. The server could have encountered an error during the processing of the request, for example.

If the status property has the integer value of 200 (OK), then the request was successful and any expected response should be avail able in responseXML or responseText.

If this seems a bit confusing, that's okay: you'll be diving into some code next and it should all come together.
 

Working with the Interfaces

Because the interface goes by different names and is instantiated one way for Internet Explorer (because the interface is an ActiveX object) and another way in Mozilla, Safari, Konqueror, and Opera, creating a request object is a little less straightforward than you might expect. The code shown here is a very basic method of working around these browser differences. If you have looked into an implementation of AJAX, you have likely seen code that looks very similar to this.

                  <script type="text/javascript">
                  function getXMLHTTP() {
                      var req = false;
                      // first, try to instantiate the native object
                     if(window.XMLHttpRequest) {
                           try {
                              req = new XMLHttpRequest();
                           }
                          catch(e) {}
                      }
                      // otherwise, try instantiating the ActiveX
                      // object for IE.
                      else if(window.ActiveXObject) {
                           // just because creation of ActiveX objects
                           // is supported, doesn't mean the XMLHTTP
                           // object is available. So, let's try it.
                           try {
                              // try instantiating the newer version
                              req = new ActiveXObject("Msxml2.XMLHTTP");
                           }
                          catch(e) {
                              try {
                                 // otherwise, try the older version
                                  req = new ActiveXObject("Microsoft.XMLHTTP");
                              }
                             catch(e) {}
                           }
                      }
                      // req will either be the reference to the
                      // interface or false on failure.
                      return req;
                  }
                  </script>

This code hides browser differences in instantiation of the XMLHTTP and XMLHttpRequest interfaces  and returns whichever version is available.

As previously described, the receipt of the response to an XMLHTTP request is handled with the firing  of events and the triggering of appropriate event handlers. The next set of code offers the typical way of  handling the ReadyStateChange event firing and acting on only the event in which you are interested:

                  <script type="text/javascript">
                  function handleReadyStateChange() {
                      if (!request) return;
                      // ignore unless complete readyState
                      if (request.readyState == 4) {
                          // Ignore unless successful.
                          // You might choose to handle errors
                          // rather than ignore them.
                          if (request.status == 200) {
                               // act on the response
                               var xmlbody = request.responseXML;
                              request = false;
                              processResponse(xmlbody);
                          }
                        }
                  }
                  </script>

This function is assigned as the onreadystatechange handler and is called when the ReadyStateChange  event is fired. It ignores all events where the readyState is not 4 (complete) and the HTTP status code is  not 200 (OK).

The XML body of complete and successful responses is extracted and passed to another function for processing. Note that after the XML body is retrieved, the request object is set to false. This is to avoid a browser bug in Opera where multiple ReadyStateChange events are sometimes fired when the readyState changes to 4.

This could result in acting upon a single response multiple times.
 

Handling the Response

The code in the previous sections is relatively standard. The complexity in AJAX applications is often in  interpreting the response and taking appropriate actions based on its content. Often, the appropriate  actions are manipulations of the DOM via JavaScript.

For example, if you issue a request to the server to  check if a username is available, and the server responds with the XML response shown here, then you  might wish to display a message to the user indicating that he or she needs to select a different user-name.

You may also wish to offer suggestions based on the alternatives contained in the XML response:
 

              <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
             <usernameresult>
                  <username value="jessica" available="false" />
                 <usernamealts>
                     <username value="jess" available="true" />
                  <username value="jessica2005" available="true" />
                  <username value="jessie" available="true" />
               </usernamealts>
           </usernameresult>

In this XML response, the server has indicated the unavailability of a username. Additionally, the server
has suggested variations of the username that are available.

One way you might accomplish conveying this information to the user is by opening a dialog box withJavaScript's alert() function. However, a more user-friendly method might be adding a DIV or UL element in a certain part of the page using DOM manipulations.

This offers a less intrusive form of messaging and would allow the user to continue with whatever interaction he is having with your application and tend to the message at his leisure.

The following code offers an example of how the XML tree can be traversed and acted upon appropriately:

                <script type="text/javascript">
                function processResponse(oXML) {
                    // exit if xml is undefined or
                    // doesn't have a documentElement
                    if (!oXML) return;
                    if (!oXML.documentElement) return;
                    var doc = oXML.documentElement;
                    // return a nodeList of all elements
                    // named username
                    var unames = doc.getElementsByTagName(`username');
                    var msgs = new Array();
                    // iterate through the username nodeList
                    for (var i=0; i<unames.length; i++) {
                       var u = unames.item(i);
                       var username = u.getAttribute(`value');
                       var availability = u.getAttribute(`available');
                       // make the available attribute
                       // more user-friendly
                       if (availability == `true') {
                          availability = `available';
                       }
                       else {
                          availability = `not available';
                       }
                      msgs[msgs.length] = `Username `+ username
                           +' is `+ availability;
                    }
                    // create an unordered list element
                    ul = document.createElement(`ul');
                    ul.id = `msg';
                    // for each message, create a list item
                    // element and a text node containing
                    // the message. The text node is a child
                  // node of the list item element, and the
                    // the list item is a child node of the
                    // unordered list element.
                    for (var k=0; k<msgs.length; k++) {
                       var li = document.createElement(`li');
                          
var txt = document.createTextNode(msgs[k]);
                          li.appendChild(txt);
                          ul.appendChild(li);
                       }
                       // obtain a reference to the maindiv element
                       // and insert our new unordered list just before it
                       var maindiv = document.getElementById(`maindiv');
                      maindiv.parentNode.insertBefore(ul,maindiv);
                   }
                   </script>

This function processes the XML from the request's responseXML property and adds a UL element to  the DOM.

As you can see, DOM manipulations can be quite complex. The nice thing about this particular response  is that you collect username elements regardless of their position in the XML response.

Often, you cannot do this directly and you must have an idea of the expected structure of the XML response, or at least  where various elements correspond to other elements.

This can cause problems if the XML format is ever  amended. Attaching response handlers at the XML element level, as opposed to the response level, shows some promise in abstracting the details of the whole XML document, allowing the client-side script to focus on the local structure relevant for that particular DOM manipulation.

For example, you might wish to show alternative usernames in a separate DOM element, in which case  you would need to differentiate between username elements that do or do not have a parent element of usernamealts.

However, you would not need to know how the rest of the XML document is structured and where in the document tree these username elements are located. Implementing this idea would require a mapping between XML element name and handler function.

The details of such an implementation are beyond the scope of this chapter, but there are articles available that delve deeper into the subject.


User reviews

There are no user reviews for this item.

Add new review




Powered by jReviews

Last Updated ( Friday, 08 June 2007 )
 
< Prev   Next >