XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Very Dynamic Web Interfaces

February 09, 2005

One of the classic drawbacks to building a web application interface is that once a page has been downloaded to the client, the connection to the server is severed. Any attempt at a dynamic interface involves a full roundtrip of the whole page back to the server for a rebuild--a process which tends to make your web app feel inelegant and unresponsive. In this article, I'll be exploring how this problem can be tackled with the use of JavaScript and the XMLHttpRequest object.

I'm sure you're familiar with the traditional interface model for a web application. The user requests a page from the server, which is built and delivered to the browser. This page includes an HTML form element for capturing data from the user. Once the user posts their input back to the server, the next page can be built and served based on the input, and so the process continues. This is largely dictated by the nature of HTTP and differs from the traditional desktop application model of an interface which is inherently connected to the application layer.

Take the simple example of filling out a serial number box to register a desktop app on a platform like Microsoft Windows. According to convention, once you've finished typing that tiresome string of alphanumeric into the little boxes, a big green 'tick' icon appears to indicate you've entered a valid code. This happens instantly as a result of the interface being sewn to the application; as soon as you finish typing the number, the application is able to check its validity and respond.

Contrast this to the standard behavior of the same task represented through a web interface. Sure, all the boxes for keying in the serial number will look identical, but on completing input, the user would need to submit the page back to the server for the input to be validated. A new page would then load with a message to indicate success or failure, and on failure, the user would need to go back and try again ad infinitum.

So whilst it's not terribly common that a user would be asked to enter a serial number into a web application, there are countless other examples of user actions that can benefit from very fast reactions from the interface, and when the business logic is all the way back at the server, this can be difficult to achieve in a traditional web app.

Enter JavaScript

Through the use of JavaScript, a reasonable amount of logic can be added to an HTML page in order to give timely feedback to user interactions. This has some major drawbacks, however. The first problem is that, as the JavaScript has been delivered to the browser along with the page, that logic has been opened up to interrogation. This might be fine for checking the format of an email address but would be no good for something like our serial number example, as the exposure of the method of verifying that input would compromise the integrity of the serial number mechanism.

The second problem with including any serious logic within the page is that the interface layer is simply not the place for serious logic. This belongs in the application layer, which is way back at the server. The problem is compounded by the fact that JavaScript cannot usually be relied upon to be available at the client. Whilst the majority of users are able and willing to run JavaScript in their browser, a considerable number prefer not to, or browse with a device where JavaScript is either unavailable or makes no sense. Therefore, any logic operations performed with JavaScript at the client must be verified at the server in case the operation never occurred.

The XMLHttpRequest Object

A solution to these problem presents itself in the form of the XMLHttpRequest object. This object, first implemented by Microsoft as an ActiveX object but now also available as a native object within both Mozilla and Apple's Safari browser, enables JavaScript to make HTTP requests to a remote server without the need to reload the page. In essence, HTTP requests can be made and responses received, completely in the background and without the user experiencing any visual interruptions.

This is a tremendous boon, as it takes the developer a long way towards achieving the goals of both a responsive user interface and keeping all the important logic in the application layer. By using JavaScript to ferry input back to the server in real time, the logic can be performed on the server and the response returned for near-instant feedback.

The Basics

Due to its history, and not yet being embodied in any public standard (although something similar is in the works for the proposed W3C DOM Level 3 Load and Save spec), there are two distinct methods for instantiating an XMLHttpRequest object. For Internet Explorer, an ActiveX object is used:

var req = new ActiveXObject("Microsoft.XMLHTTP");

For Mozilla and Safari, it's just a native object:

var req = new XMLHttpRequest();

Clearly, as a result of this inconsistency, it's necessary to fork your code based on support for the appropriate object. Whilst there are a number of methods for doing this (including inelegant browser hacks and conditional comment mechanisms), I believe it's best to simply test for support of either object. A good example of this can be found in Apple's developer documentation on the subject. Let's take their example:

var req;

function loadXMLDoc(url) 
{
    // branch for native XMLHttpRequest object
    if (window.XMLHttpRequest) {
        req = new XMLHttpRequest();
        req.onreadystatechange = processReqChange;
        req.open("GET", url, true);
        req.send(null);
    // branch for IE/Windows ActiveX version
    } else if (window.ActiveXObject) {
        req = new ActiveXObject("Microsoft.XMLHTTP");
        if (req) {
            req.onreadystatechange = processReqChange;
            req.open("GET", url, true);
            req.send();
        }
    }
}

A particularly important property to note is the onreadystatechange property. Note how it is assigned to a function processReqChange. This property is an event handler which is triggered whenever the state of the request changes. The states run from zero (uninitialized) through to four (complete). This is important because our script isn't going to wait for the response before continuing. The HTTP shenanigans are initiated, but then they carry on out of process whilst the rest of the script runs. Due to this, it's not as simple as having loadXMLDoc return the result of the request at the end of the function, because we don't know if we'll have a response by then or not. By having the function processReqChange check for the state changing, we can tell when the process has finished and carry on only if it has been successful.

With this in mind, a skeleton processReqChange function needs to check for two things. The first is the state changing to a value of 4, indicating the process complete. The second is to check the HTTP status code. You'll be familiar with common status codes like 404 (file not found) and 500 (internal server error), but the status code we're looking for is good old 200 (ok), which means everything went well. If we get both a state of 4 and an HTTP status code of 200, we can go right ahead and start processing the response. Optionally, of course, we can attempt to handle any errors at this point, if, for example, the HTTP status code was something other than 200.

function processReqChange() 
{
    // only if req shows "complete"
    if (req.readyState == 4) {
        // only if "OK"
        if (req.status == 200) {
            // ...processing statements go here...
        } else {
            alert("There was a problem retrieving 
               the XML data:\n" + req.statusText);
        }
    }
}

In Practice

I'm going to work up a practical example so we can get this going. Most web applications have some method of signing up users, and it's common to ask the user to pick a username to use for the site. Often, these need to be unique, and so a check is made against the database to see if any other user already has the username a new recruit is trying to sign up with. If you've ever signed up for a web mail account, you'll know how infuriating it is cycling around the process trying to find a username that isn't already taken. It would be really helpful if that check could be made without the user leaving the page.

The solution will involve four key elements: an XHTML form, a JavaScript function for handling the specifics of this case, our pair of generic functions (as above) for dealing with HTTP, and finally, a script on the server to search the database.

The Form

Here's the easy bit--a simple form field to collect the user's chosen username. An onblur event handler is used to fire the script. In order to display a friendly message to the user if the name is taken, I've embedded it in the form and hidden it with CSS. This should prove a little less violent than a standard JavaScript alert box.

<input id="username" name="username" type="text" 
  onblur="checkName(this.value,'')" />
<span class="hidden" id="nameCheckFailed">
  This name is in use, please try another. 
</span>

The CSS defines a class for hidden and also one for showing the error. Call that one error.

span.hidden{
  display: none;
}

span.error{
  display: inline;
  color: black;
  background-color: pink;  
}

Handling the Input

The checkName function is used to handle the input from our form. Its job is to collect the input, decide which script on the server to present it to, invoke the HTTP functions to do the dirty work on its behalf, and then deal with the response. As such, this function has to operate in two modes. One mode receives input from the form, the other the response from the HTTP request. I'll explain the reason for this in the next section.

function checkName(input, response)
{
  if (response != ''){ 
    // Response mode
    message   = document.getElementById('nameCheckFailed');
    if (response == '1'){
      message.className = 'error';
    }else{
      message.className = 'hidden';
    } 
  }else{
    // Input mode
    url  = 
      'http://localhost/xml/checkUserName.php?q=' + input;
    loadXMLDoc(url);
  }

}

Our response is going to be easy to deal with--it'll be a string of either 1 or 0, with 1 indicating that the name is in use. Therefore, the function changes the class name of the error message so it gets displayed or hidden, depending. As you can see, the dirty work at the server is being done by a script called checkUserName.php.

HTTP Heavy Lifting

As we saw earlier, the HTTP work is being done by two functions, loadXMLDoc and processReqChange. The former can remain totally as-is for the purposes of this example, with the only modifications needed to the latter being a quick bit of DOM work.

You'll recall that by the time a successful response has been passed to processReqChange, we're no long in a position to pass any sort of return value back up the chain. Because of this, it's going to be necessary to make an explicit function call to another bit of code in order to do anything useful with the response. This is why our checkName function has to run in two modes. Therefore, the main job of processReqChange is to parse the XML coming back from the server and pass the raw values back to checkName.

However, it is important that we keep these functions generic (we may have multiple items on the page that need to make use of XMLHttpRequest), and so hard-coding a reference to checkName at this point would be foolhardy. Instead, a better design is to have the server indicate the handling function as part of its response.

<?xml version="1.0" encoding="UTF-8" 
  standalone="yes"?>
<response>
  <method>checkName</method>
  <result>1</result>
</response>

Parsing such a simple response should be no problem at all.

function processReqChange() 
{
    // only if req shows "complete"
    if (req.readyState == 4) {
        // only if "OK"
        if (req.status == 200) {
            // ...processing statements go here...
      response  = req.responseXML.documentElement;

      method    =
response.getElementsByTagName('method')[0].firstChild.data;

      result    = 
response.getElementsByTagName('result')[0].firstChild.data;

      eval(method + '(\'\', result)');
        } else {
            alert("There was a problem retrieving the XML 
                data:\n" + req.statusText);
        }
    }
}

By using the responseXML property of the XMLHttpRequest object, we have a ready-made XML object we can traverse with the DOM. By grabbing content of the method element, we know which local function to execute along with the result. Once you've finished testing, it's probably a good idea to dump the else clause from the above code, enabling the function to fail silently.

The Server Script

The final piece in our jigsaw is the script on the server to accept the request, process it, and return an XML document in response. For the purposes of our example, this script looks up usernames in a database table to determine whether a name is already in use. For brevity, my example PHP script below just checks against two hard-coded names, 'Drew' and 'Fred'.

<?php
header('Content-Type: text/xml');

function nameInUse($q)
{  
  if (isset($q)){
    switch(strtolower($q))
    {
      case  'drew' :
          return '1';
          break;
      case  'fred' :
          return '1';
          break;
      default:
          return '0';
    }
  }else{
    return '0';
  }
  
}
?>
<?php echo '<?xml version="1.0" encoding="UTF-8"
  standalone="yes"?>'; ?>
<response>
  <method>checkName</method>
  <result><?php 
    echo nameInUse($_GET['q']) ?>
  </result>
</response>

Of course, the logic used to verify the availability of the username in this script can be reused after the form is submitted to recheck that the name is available. This is an important step, since if JavaScript was not available at the client, this check would not have yet taken place. Additionally, on a busy site, a username which checked out OK at the time the user was filling the form in may have been taken by the time the form is submitted.

Perhaps as a next step, if you're interested in playing with this some more, you could add the ability for the server to return a list of suggested alternative usernames if the suggested name is taken.

In Conclusion

This small example really only scratches the surface of the things achievable with XMLHttpRequest. Some other examples would include Google Suggest, which uses XMLHttpRequest to provide suggested search terms, and the Ta-da Lists application, which commits user data to the server in the background to provide a really fast list managing interface. The real challenge here is not figuring out how to make the code work but thinking of interesting ways in which it can be utilized.



1 to 65 of 65
  1. Thanks
    2010-08-08 19:23:16 Robber86
  2. Awesome Work
    2010-08-02 04:48:55 RickyWhore
  3. commenst
    2010-08-02 04:45:27 goticketsnow
  4. steroids
    2010-08-01 04:40:00 dianabol

  5. 2010-08-01 04:38:12 dianabol
  6. Re hello
    2010-07-25 17:49:01 fullbet
  7. poker online
    2010-07-25 17:47:01 fullbet
  8. O
    2010-07-22 20:28:34 bloghd86

  9. 2010-07-22 04:45:51 Clonk
  10. Nice
    2010-07-18 17:18:53 fta satellite
  11. Thanks
    2010-07-18 17:11:45 fta satellite
    • Thanks
      2010-07-18 17:20:23 fta satellite

  12. 2010-07-08 11:56:37 xenaxon
  13. iPhone App script problem, please help!
    2010-07-05 09:17:58 iPhoneGuy

  14. 2010-07-02 17:46:19 shinsweera

  15. 2010-07-01 03:13:39 shinsweera

  16. 2010-06-30 07:12:49 shinsweera

  17. 2010-06-30 07:05:05 shinsweera

  18. 2010-05-20 14:07:20 ryan57
  19. dizi mekanimiz
    2008-04-26 16:49:38 oyun oyna
  20. chip oyun
    2008-04-24 11:27:48 oyun oyna
  21. Learn AJAX
    2007-11-06 07:06:25 Training-Connection
  22. Short and simple way of handling multiple XMLHttpRequests
    2007-05-30 05:19:35 FST777
  23. Header Output
    2007-02-13 00:20:37 tame_one
  24. Thanks
    2006-12-19 22:00:10 venkatramanan
  25. xmlHttpRequest could not be made
    2006-11-21 00:39:14 memen
  26. Fantastic but it won't work for me with MYSQL?
    2006-07-15 18:39:36 rodent88
  27. Loading and Parsing XML using AJAX
    2006-06-11 18:33:58 LeProgrammeur
  28. IE very slow processing
    2006-03-22 20:58:00 alexamies
  29. My tribute - thankfully
    2006-01-05 13:25:10 _roro
  30. Simple remote scripting library
    2006-01-02 20:45:40 joppinkaru
  31. Thank You
    2005-12-16 09:14:43 Stuart@xDox
  32. url problem
    2005-10-26 04:38:49 ramsee
  33. Probelm...
    2005-10-15 06:34:03 fretoune
  34. XMLHTTP and java
    2005-10-07 13:17:44 vivekst
  35. XMLHTTP and java
    2005-10-07 12:19:56 vivekst
  36. Thanks for the very good introduction, however, ...
    2005-09-12 20:32:01 peterhf
  37. Nice analysis
    2005-09-05 07:50:29 Auious
  38. I need help =(
    2005-08-22 22:29:43 chris99
  39. DWR
    2005-07-18 16:03:54 dmeany1
  40. Microsoft.XMLHTTP vs. XMLHttpRequest
    2005-07-04 06:16:42 chrisward1
  41. char encoding problem
    2005-06-27 05:24:29 saidka
  42. responseXML lost across windows?
    2005-06-09 10:42:10 JJG
  43. non-active x implementation for IE?
    2005-06-09 01:27:13 akrinsky
  44. A threadsafe implementation for XMLHTTPRequest
    2005-05-27 12:48:37 brockweaver
  45. Why does the method take input and response?
    2005-05-17 11:58:36 tieTYT
  46. Great introduction, now what about refreshing lists?
    2005-05-11 08:10:09 acidbox
  47. Knowledge Base Application
    2005-05-06 08:07:25 Julian Turner
  48. Chess GUI
    2005-04-24 04:22:46 JOlsen
  49. Data the other way round
    2005-04-21 11:35:17 nGear
  50. Concerns
    2005-04-03 07:00:05 sunnyO5
  51. Problem
    2005-03-24 14:35:49 Goonie
    • Problem
      2005-04-07 23:21:31 rkhlin
    • Problem
      2005-04-06 23:00:56 mdchaney
  52. I realize that I'm on XML.com, but....
    2005-03-23 07:09:49 mdchaney
  53. Working Example
    2005-03-14 03:28:27 Julian Turner
  54. The correct code
    2005-03-02 12:29:03 xmL
  55. Re: Example
    2005-02-24 14:16:03 rk9728
  56. Good Article!
    2005-02-19 18:08:08 DanielBThurman
  57. XMLHTTP is Good but Needs WS-Security
    2005-02-18 13:40:55 ErikJ
  58. micro applet can work as well
    2005-02-16 09:59:42 jseller
  59. DOM 3 & simplification
    2005-02-14 05:45:48 JimDabell
  60. XMLHTTPRequest and Javascript to create web applications with very dynamic, smooth interfaces.
    2005-02-14 05:44:52 anand123
  61. Coldfusion Server Script
    2005-02-11 08:05:06 makalu
  62. XSL
    2005-02-11 02:13:32 redben
  63. Very nice article
    2005-02-10 18:47:27 robhudson
  64. Don't forget www in the URL
    2005-02-10 09:29:16 ideawire_bb
  65. Nice, but one small error
    2005-02-09 23:53:32 dzac
1 to 65 of 65