Menu

Remote Scripting with AJAX, Part 2

August 22, 2005

Cameron Adams

Editor's note: In part one of this two-part series, Cameron Adams created an example application that shows how to use remote scripting to implement the AJAX XMLHttpRequest protocol. Now, in part two, he shows how to create a usable interface for the example app.

The content for this article was excerpted from Cameron's originally published article, which was posted on SitePoint's website last month.

To begin, download the code archive, which contains all of the files you'll need to create the working examples presented here and for part one of this series.

Create a Usable Remote Scripting Interface

The remote scripting model is quite different from the standard page-based interaction that permeates most of the Web, and with that difference comes new usability pitfalls that can too-easily be introduced into your projects. These pitfalls typically arise either from the dynamic manipulation of the interface while the user is accessing it, or from the need to access data that's external to the web page.

Example 1 used remote scripting to validate the receipt number, and to automatically insert data that was retrieved from the database; however, none of this information was used particularly well, nor was it obvious to the user what was going on. Example 2 aims to correct this and other deficiencies in the first example, and make the experience a whole lot quicker, easier, and more understandable for the user. The five tips below explain some of the changes that can be used to turn a bad experience into a good one.

Tip #1: Tell Users Why They're Waiting

Remote scripting is not instantaneous. Regardless of the speed of your web connection, communication time with an external source will vary. So, while communication with a server occurs, it's imperative that you tell the user why they're waiting. (The example PHP scripts use sleep() calls to highlight the waiting periods that can be caused by network traffic or other factors.)

Because remote scripting applications do not make calls using the normal browser interface, the status bar--which normally notifies the user of transfer status and activity--does not function as it normally does. Thus, we have to provide feedback to the user ourselves.

In Example 2, while the receipt number is being verified, a label displays next to the receipt number field to explain the wait.

Figure 1
Figure 1.

The label changes to indicate completion once the XMLHttpRequest connection has finished.

Figure 2
Figure 2.

The status message is initialized just before the XMLHttpRequest connection, when the onchange event for the receipt number field is triggered:

receipt.onchange = onchangeReceipt; 

      

  function onchangeReceipt() 

  { 

 message(this, "loadingMessage", "Verifying receipt number"); 

  

 /* Check for running connections */ 

 if (requester != null && requester.readyState != 0 && requester.readyState

!= 4) 

 { 

   requester.abort(); 

 } 

  

  ...

Once the remote scripting operation has finished, the message is updated to tell the user whether the receipt number was valid or not:

function writeDetails() 

  { 

 if (requester.responseText.charAt(0) == "<") 

 { 

   message(receipt, "statusMessage", "Your receipt details were retrieved"); 

  ... 

  

 else 

 { 

   message(receipt, "errorMessage", "Please enter a valid receipt number"); 

  ...

Updating the message to indicate completion is important, as it provides closure for the user. If the loading message simply disappeared, users could not be certain that it had been successful.

In the two code samples above, the message function is a custom function that dynamically creates a status label for a form element, and positions it visually adjacent to the related element. It also accepts a class for the status label, which allows CSS styles to be applied differently for loading, error, and completion messages:

function message(element, classString, errorMessage) 

  { 

 var messageDiv = document.createElement("div"); 

  

 element.parentNode.insertBefore(messageDiv, element); 

 messageDiv.className = classString; 

 messageDiv.appendChild(document.createTextNode(errorMessage)); 

  

 return true; 

  }

While the XMLHttpRequest process is running, the label animates to indicate that the action is ongoing and still alive. In Example 2, this is performed via CSS styling with an animated GIF, but it could also be effected using JavaScript animation.

The same feature is applied to the form submission button. Again, this alerts the user that some action is being undertaken, and also lets them know that they did click the button, which will help to discourage users from pressing the button more than once:

Figure 3
Figure 3.

To achieve this, simply change the value and the CSS class of the submit button:

submit.className = "submit loading"; 

  submit.value = "Contacting server";



Tip #2: Don't Interfere with the User's Interaction

Users become frustrated with interfaces that interfere with the completion of their tasks. In Example 1, such interference might occur after users have entered a receipt number: if they begin to fill in their names and email addresses before the receipt number has been verified, those details will be overwritten once their user data is received from the server.

To rectify this, Example 2 checks whether a user has changed the values of the text fields before the script enters any data into them. The default values of the text fields can be detected when the page loads, and recorded using custom DOM properties:

email.defaultValue = email.value;

The default value of a field can then be checked against its current contents before the script attempts to write any data into it:

if (email.value == email.defaultValue) 

  { 

 email.value = newValue; 

  }

This makes sure that the user--who probably knows his or her own name better than we do--doesn't have any entries overwritten by overzealous automation.

Some other common cases of interference that you should avoid include moving the cursor to a field while the user is filling out another field, and locking the user out of the interface (which is why XMLHttpRequest should be used asynchronously).

Tip #3: Catch Errors Early, But Not Too Early

It's best to catch errors as soon as they occur. Many forms that currently appear on the Web rely upon the user to submit the form before any form errors will be shown, either using server-side scripts or inelegant JavaScript alerts (as per Example 1). These methods have several disadvantages for the user:

  • The process of submitting the form takes up the user's time.
  • JavaScript alerts do not permanently mark all of the fields that require correction.
  • Indicating errors well after they have been committed requires the user to mentally recollect what the erroneous field asked of them.
  • Even if users know which form elements to correct, they will have to re-submit the form to find out if those elements have been corrected properly.

For those reasons, it is much better to inform users of an error as soon as they have made it. In Example 2, if a user enters an invalid email address, the application tells them straight away. The notification is placed right next to the email field, using the message() function from Tip #1:

Figure 4
Figure 4.

However, you shouldn't check for validity as soon as a user starts typing, as it is distracting--not to mention annoying--to be told that you've made an error before you've even finished entering the data. Field checking should only be done once a user has finalized the entry; i.e., when they move away from the input. For text fields, this type of action is best captured using the onchange event:

email.onchange = onchangeEmail;

The function that is triggered by the event can then check the field and ensure that the data it contains is valid for that data type:

function onchangeEmail() 

  { 

 if (!this.value.match(/^[\w\.\-]+@([\w\-]+\.)+[a-zA-Z]+$/)) 

 { 

   field.valid = false; 

   message(field, "errorMessage", "Please enter a valid e-mail address"); 

   field.className = "text error"; 

 } 

  

 return true; 

  }

Tip #4: Let the User Know When an Error Has Been Fixed

Once a field has been found to be incorrect, and the user has been alerted to the error, it's equally important to let the user know when he or she has changed it to be correct; otherwise, the user will become trapped in the form submission cycle once again.

In these circumstances, it's not good enough to wait for the browser's onchange event to fire, as that usually occurs only when the user defocuses a form element. Therefore, it is best to use the onkeyup event to check the correctness of a field that was known to be incorrect:

email.onkeyup = onkeyupEmail;

The onkeyupEmail() function checks whether the email field has an error message displayed alongside it before moving on to check whether the field is correct. Thus, as soon as a user makes appropriate corrections to the field, the error message will disappear; however, if the user is typing into the field for the first time, no message will appear:

function onkeyupEmail() 

  { 

 /* If an error message is displayed */ 

 if (this.message != null && this.message.className == "errorMessage") 

 { 

   if (this.value.match(/^[\w\.\-]+@([\w\-]+\.)+[a-zA-Z]+$/)) 

   { 

     this.valid = true; 

  

     /* Remove error message */ 

  message(this); 

  

  /* Remove error CSS class */ 

     this.className = "text"; 

   } 

  ...

These scenarios don't capture the case in which mandatory fields have been skipped, so it's a good idea to allow the user to submit an incomplete form, as this allows the program to highlight exactly what needs to be completed, instead of searching for details that have not yet been filled out.

Tip #5: Provide Interface Feedback

Creating a seamless web application can let you explore new functionality that hasn't already been seen in a browser, but in doing so, we must still remember the foundations of usable interface design. One such foundation is the provision of interface feedback: letting the user know what they can do, and what they have done.

In Example 1, it's not entirely clear that users can click on the thumbnails of ecard graphics. This is easily counteracted if we give a grey outline to the image over which the cursor is positioned at any given time.

Figure 5
Figure 5.

The :hover pseudo-class will be familiar to anyone who has used CSS. It allows an object to change its an appearance when the cursor is moved over that object. Although mouseover effects can theoretically be achieved through CSS alone, current versions of Internet Explorer do not allow :hover effects on any element except an anchor tag. To achieve a hover effect on the image elements, Example 2 attaches onmouseover and onmouseout event handlers:

var cards = document.getElementById("ecardSet"). 

  getElementsByTagName("img"); 

  

  for (var i = 0; i < cards.length; i++) 

  { 

 cards[i].onmouseover = onmouseoverCard; 

 cards[i].onmouseout = onmouseoutCard; 

  }

Those event handlers can then change the class of each image and allow us to provide visual feedback using CSS:

function onmouseoverCard() 

  { 

 this.className = "hover"; 

  

 return true; 

  } 

  

  function onmouseoutCard() 

  { 

 this.className = ""; 

  

 return true; 

  }

Changing the cursor to indicate its "clickability" can also help to provide feedback to the user. This can be done using a simple rule in CSS:

img.hover 

  { 

 cursor: pointer; 

  }

Conclusion

After making all of these changes to Example 1, Example 2 becomes a much more helpful and usable application.

Figure 6
Figure 6. Click for full-size image

The common theme among the tips offered here is always to make the user feel comfortable and in control. If users don't possess the information they need in order to understand what's going on, they will view your application with apprehension, and its performance will suffer as a result.

Although this article focussed primarily on the process of remote scripting and its usability concerns, there are also points of accessibility that should be taken into account as you create a seamless web application. Example 3 is a much more complex version of the ecard application, which uses more powerful scripting and degrades accessibly for users without JavaScript or without XMLHttpRequest. Once you've mastered the techniques described above, you might want to have a look at this final example and start to make your applications really robust.