Enhace, Extend, Transform

Web 2.0 Magazine

Subscribe to Web 2.0 Magazine: eMailAlertsEmail Alerts newslettersWeekly Newsletters
Get Web 2.0 Magazine: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn

Web 2.0 Authors: Glenda Sims, Serafima Alex, Xenia von Wedel, Harry Trott, Larry Alton

Related Topics: RIA Developer's Journal, AJAX World RIA Conference

RIA & Ajax: Article

Real-World AJAX Book Preview: Going Deep into the AJAX User Experience

Real-World AJAX Book Preview: Going Deep into the AJAX User Experience

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs for the special pre-order price, click here for more information. Aimed at everyone from enterprise developers to self-taught scripters, Real-World AJAX: Secrets of the Masters is the perfect book for anyone who wants to start developing AJAX applications.

Going Deep into the AJAX User Experience
The user experience is a very difficult thing to quantify. Through the years I've found that it's a very inexact science. There's usually at least one thing you can do to improve the experience, while at the same time hurting it. Below you will find 21 strategies for improving the user experience. Some strategies will be rules of thumb, some will be techniques, and some will have code.

Strategy #1 - Define Your Problem
All software, at least good software, solves problems for people. Sometimes identifying the problem is the hardest part of the process.

Users are very good at telling you what's wrong with something and asking additional questions will often elicit what is really happening. Sometimes it's a bug and sometimes that bug is actually the problem. But most of the time it's symptomatic of something else, like lack of training, a complicated business process, or complicated site navigation. Make sure you don't just try to fix the symptom because you might create a headache for someone else.

Second, don't confuse the solution with the problem. I heard just the other day that there was a problem with a component I built. The analyst said it didn't do A, B, or C, which meant to the analyst that the problem was the component. When I asked why it needed to do A, B or C rather than what it did do, I was told A, B, and C would solve problems X, Y, and Z. So I asked, "What if I modified the component to add feature D to solve, X, Y, and Z? "Can you do that?" I was asked. "Yes," I said, "and it's a lot easier to build than reworking A, B, and C."

Third, don't take anecdotal evidence as fact. In my ongoing quest to make the best application for each of my clients, I have asked sales staff over the years what the customer didn't like about the application. Sometimes I'd hear they didn't like the number of clicks. Yet, when I spoke with the business analyst, I was told the users asked for a 10-step process, and that they thought the process, was fine. So I'd go back to sales and ask, "I thought you said the users did not like the clicks." Then I'd hear that sales had been talking to the manager, who didn't even use the application.

Finally, sometimes you hear your application is too hard to use. Or it may be that your application is perceived to be too diffi cult to use. But until you look at the data and the transactions you really have no way to quantify that perception.

Steps to Defining Your Problem

  1. Don't just listen to users. They will give you symptoms, not problems.
  2. Don't confuse solutions with problems.
  3. Don't accept anecdotal evidence as fact and don't blow the symptoms out of proportion.
  4. Fight perceptions of the problem. Dig into the data and transactions.
Sometimes to help you identify your problem, you need to create a model of your application.

Strategy #2 - Create a Model
Models don't have to be fancy, require tools, or be some form of acronym. As long as it helps you understand your software, that's all you need. All you need is a pen and a sheet of paper.

What I'm talking about is a state machine. A state machine is a series of states and transitions. A state machine diagram (SMD), like Figure 7.1, consists of some circles and some labeled arrows. The arrows are called transitions and the circles are called states.

The state machine below represents a pen. You click it once it extends its tip, you click it again and the tip retracts. The states of the pen are in the circles. Pretty simple, right?

Why do state machines help the user experience? Because all user interaction with a computer program is essentially a state machine. Traditional Web applications are especially good at being drawn like state machines because each request is a state transition and each Web page is a state.

Creating models with SMDs point you to usability problem areas in an application and give you a countable, quantifiable measure of system usability.

Strategy #3 - Measure the "User Experience"
The user experience is hard to measure but you can do a lot by simply counting. In general the more states and transitions an application has the less usable it is.

The trick is knowing what a transition is. There are four different kinds of transitions in an application, which I've enumerated below and highlighted in Figure 7.2.

  1. Visual: Areas of the screen you need to look at for information
  2. Mechanical: Things you have to type, click, or scroll
  3. Cognitive: Things you have to think about, errors you have, or going to the help
  4. Artificial: Things your system and business rules impose on your user

In the example above, you can count the number of areas I have to go to navigate or search. I count three search boxes and three navigation bars. There could also be a few mechanical transitions when moving the scrollbar to find the book I want.

There might be two or three artificial transitions because the category that I want to search must be selected before my search results can be displayed.

Finally, the number of cognitive transitions is low. The optimized search results are at the top so hopefully I don't have to think much.

So whether you're looking at a storyboard (Strategy #6) or a walkthrough (Strategy #7), you have something you can count as you compare. By counting how much a user has to think, click back, or ask questions, you can get a quantitative measure of the system's usability. You can plug those numbers into your favorite spreadsheet program and tally. Just realize that the tally might not give you the whole story.

For example, you could have a wizard-like application with a high click count (M) and low think count (C). This might be good for novice users, but won't be for experienced users. On the other hand, configuring via a text file, as in the Unix style, may be the easiest method for experienced users, but not for the novice. That's why it helps to create personas when testing your application.

Strategy #4 - Create Personas
It's impossible to talk to every user of your software, but you can put a face on them by creating imaginary users that share traits with your real users.

Like every business has a target customer, every application has target users. We call these target users personas. For example:

Each of these people will see your application differently, and each of them will have a different user experience. So creating a single application metaphor (a method for using an application while cheaper to write, won't make everyone happy.

In the end, your application has a business goal, which could be more productivity, more online sales, more revenue, or more visits. If your user experience doesn't reinforce this goal, it could have dire consequences for your application and your business.

To help get an early feel for your application's usability, create a storyboard for your users.

Strategy #5 - Create a Storyboard
A storyboard of your application is a short sequence with pictures, screenshots, or words describing a scenario.

Creating a storyboard is very similar to creating your model from Strategy #2, except this time you do it with pictures or screenshots rather than circles and arrows.

A good level for storyboarding is to take an overview of your system navigation or process flow, not the ins and outs of data entry or the complicated business rules of an application.

Storyboards are good at displaying a high-level overview for your customer and users. It's at a high enough level to get feedback on process and your model, but not detailed enough to get people talking about details and designing pages, which we don't want to do with this method.

For a more detailed look at the application's usability, do a walkthrough.

Strategy #6 - Perform an Application Walkthrough
Once you complete the storyboarding phase of your project and build some working code, it might be time to walk through your application with some actual users.

This is well before "productization", even before beta. But again, it's a risk-reducing strategy to ensure you're on the right track.

To do a walkthrough:

  1. Create the scenario you want to test. Usually it's some of the first parts of the application you've built.
  2. Create a list of your target users for this scenario.
  3. Create the task those users will be asked to complete.
  4. Create measurements for the scenario with your users.
  5. Hypothesize what you think the results of the test will be.
  6. Test the users in the scenario.
  7. Draw your conclusions.
When showing a scenario, you have a few options. (1.) Show the application by itself. This is good option but you risk the users comparing your application to the application of their dreams. (2.) Show the user two different applications. This is a little safer. But keep in mind that they might like one better, no matter which order they are shown. And you have to avoid biasing the walkthrough.

The best approach is to also compare your application measures to a control. A control is an application or scenario with known usability/user experience scores. You can compare each of your two options against it. So pick something like a Web mail application. This comparison to a known application control will neutralize bias and give you a relative measure.

Strategy #7 - Do a Heuristic Analysis
If you can't get any users at to walk through your application, at least do a heuristic analysis. A heuristic analysis is a quantitative comparison of your application to known good traits in well designed applications.

Examples of heuristic analysis points include (from www.useit.com/papers/heuristic/heuristic_list.html):

  1. Visibility of system status
  2. Match between the system and the real world
  3. User control and freedom
  4. Consistency and standards
  5. Error prevention
  6. Recognition rather than recall
  7. Flexibility and efficiency of use
  8. Aesthetic and minimalist design
  9. Help users recognize, diagnose, and recover from errors
  10. Help and documentation
For a deeper understanding of these examples, please visit www.uscif.com/papers/heuristic/heuristic.list.html.

I've found a good rating system is to use an Excel spreadsheet and rate the issues on a scale of 1-10. Then put versions of your application side-by-side and pick the winner.

A tip in doing this analysis is to create concrete items that you can measure for each category. For example, the visibility of system status would measure when things happen in the background, or indicate how far you are within a process. Another example regarding error prevention, the measure could just be the number of errors or back-button clicks the user made.

Strategy #8 - Create a Cheat Sheet
No one likes to read large amounts of documentation so make it easy on everyone on your team and create cheat sheets. Cheat sheets summarize important information but are generally restricted to just the most critical information. A cheat sheet for managers might be different than for developers, while cheat sheets for analysts and developers might share some items.

Put five or 10 items on the list and distribute it to all your team members. Make them put it in their cubes next to their Dilbert cartoons and pictures of their cat.

Strategy #9 - Go Classic
There are times when no matter how hard you try, the best solution is a classic Web solution and don't worry about adding AJAX.

If you hit two or more of the following items, it might be time to go classic.

  1. Uncertain of the system age and browser.
  2. You're working at LAN speeds, and round-trips (request-response) are <one second.
  3. It's faster to use.
  4. It takes less time to build.
  5. It costs less.
While each situation may vary, don't be afraid that your application will be stale or out-of-date. You'll have to change it in a few years or months anyway.

One area that should definitely stay classic is site navigation.

Strategy #10 - Don't Use AJAX for Navigation
Your site navigation takes people and search engines to other pages, so this isn't the right place for AJAX. If you're building a Web application and your site won't be indexed via a bot, you'll still want your navigation to take a user from page-to-page because that's what he expects will happen. While most AJAX metaphors change the paradigm to "page as an application," this is limited practically speaking because maintaining a 10,000-line page is more difficult to test and maintain than 10 1,000-line pages.

When you're moving between pages, don't use JavaScript to take you to pages. Use links instead.

Strategy #11 - Eat Your Own Dog Food
If you don't have time to use your own code, don't expect others to.

One reason I don't like most frameworks is that they take longer to use and figure out than just doing it yourself. There are times when you think you've created the coolest widget or framework. You think it solves all user needs and will vastly improve the user experience. But you never use it outside of testing. In real life, if you don't have time to use your new widget, don't make someone else experiment with it.

Another way to do this is to actually use the applications you build. Be a user for a day or two and actually do his job with the software you built. You'll find it really helpful in how you design and build.

Finally, you may find that your requirements makers (managers) don't use the application at all but still think they know what it needs. Sometimes it's good to test them or observe them using the application with their requirements. They often will have a change of heart.

Strategy #12 - Be Consistent
Consistency is very important in software. User expectations are one of the cornerstones of software effectiveness. When you change what the user has come to expect you may make your software less effective.

If you're upgrading a traditional Web application but only upgrade parts of it, be very careful. By making parts of the application work completely different than other parts, you introduce the unexpected and users don't respond well to that. While you might make new components very clever and nice, the users will have a difficult time transitioning between two metaphors and this increases how much they have to think. Generally, this is not a good idea.

Strategy #13 - Completely Test What You Build
I thought the days of coding for different browsers were done, but no, here we go again.

If you can't control your browser environment and can't test all browser/platform combinations accessing your site now and in the future, you might want to stick with a traditional Web application.

If you have an internal Web application you're designing, try to get commitments from your client to pick a browser version and stick to it. Be aware of security patches that suddenly break your application.

If you don't have access to the various hardware or operating systems to test your application, there are plenty of virtual machines out there, where you can install all the major operating systems, including older ones with old browser versions.

Strategy #14 - Break the Back Button (When It Matters)
It's okay to break the back button. Sometimes error prevention takes precedence over the golden rule.

In my experience, using the back button is fine in Web applications as long as the application doesn't try to resubmit the data twice. So rather than make a blanket statement to "never break the back button", I'd offer slightly different advice: Break the back button (when it matters).

When is it okay to break the back button? When the user is using a transaction-based application, like creating, updating, or deleting a record.

Let's say your user is at a shopping cart. If she's already confirmed what she wants and has placed an order, you don't want her going back and submitting it again and again, adding the same items two or three times. Here, you want to break the back button.

One technique used to break the back button is a token exchange. To understand this process, follow this sequence:

In AJAX use a different route than a hidden field. As an example, in the XmlHttpRequest (XHR) object, add a property called token. In the request, make sure to send the token to the server. When the response is parsed, make sure the token is retrieved.

Strategy #15 - Don't Break the Back Button (When It Matters)
The rest of the time, don't break the back-button.

I don't know how often I've heard people say, even about traditional Web applications, "Don't use the back button." Why? Because some developer assumed people will only move forward in their Web application and never want to go back to a previous page. Certain browser mechanisms, like a POST request, give the user nice error messages when they click the back button.

When using AJAX, there are two separate methods that don't break the back button. You guessed it. One for Internet Explorer and one is for everybody else. (I haven't tested this with Safari.)

The first method works with Firefox under Windows XP SP2. It involves using the location object's hash property. With Firefox, modifying the location.hash creates a history. With Internet Explorer, modifying the loction hash modifies the URL but doesn't add to the history. Listings 7.1-7.4 focus on Firefox. Listings 7.5-7.9 focus on Internet Explorer.

Listing 7.1 has a span and a link that invokes the test for the back button.

Listing 7.1

<p><a href="javascript:firefox();">Back Button - Firefox Test</a>
<span id="ffTest"></span></p>

Listing 7.2 will create a static page level array of all back button states called bbArray() and a currentIndex holding the current state.

The Firefox() function creates a series of events, 200 milliseconds apart to simulate the clicking of items on a page and adding to the history. At the end of this sequence, in three seconds it will start looking to see if the back button was clicked via the startChecking() function.

Listing 7.2

var bbArray = new Array();
var currentIndex = 0;
var usedBack = false;

function firefox() {
    for (var i=0;i<10;i++) {

The setInner() function in Listing 7.3 increments the innerHTML value by an index. It pushes the text into an array and also sets the location.hash to the index. Changing the location.hash also in Firefox adds to the history. I put this at the bottom of the function.

There should be 11 items in the history: the initial page state and the state after 10 hashes have been updated via the for loop above. Now we're ready to start looking for a change in the history. This is done via the startChecking() function and is called after the time-out of three seconds included in the function above.

Listing 7.3

function setInner(i) {
    var ffHTML = document.getElementById("ffTest");
    var txt = "testing " + i;
    ffHTML.innerHTML = txt;
    location.hash = i; // adds to Firefox history.

function startChecking() {

Every 200 milliseconds the browser will look for a back-button click. It does this by looking at the page's current state and comparing it to what's in its hash property.

The page can be in one of the following states, as shown in Listing 7.4.

  1. Initial State ‡ bbArray.length = hash and currentIndex = hash and usedBack = false (do nothing)
  2. Initial Back Button State ‡ hash is different than length of array and usedBack = false. Action ‡ Change innerHTML to this state.
  3. Subseqent States ‡ usedBack is true. Action ‡ Change innerHTML to this state.
Listing 7.4

function checkUrl() {
    var hash = (location.hash).substr(1,(location.hash).length);
    if (bbArray.length != hash && hash != currentIndex) {
      var ffHTML = document.getElementById("ffTest");
      ffHTML.innerHTML = bbArray[hash];
      currentStack = hash;
      usedBack = true;
    if (bbArray.length == hash && usedBack) {
      var ffHTML = document.getElementById("ffTest");
      ffHTML.innerHTML = bbArray[hash];
      currentStack = hash;
      usedBack = true;

That does it for Firefox.

Now for Internet Explorer 6.0 SP2 on Windows XP. As mentioned previously, the hash doesn't add to the history in IE. Listing 7.5 has the same basic layout as before except for the hidden IFRAME. By actually writing new documents to the IFRAME, Internet Explorer adds history just like Firefox did by updating the hash.

Listing 7.5 is an example of an HTML page with a function called ie(), a SPAN with an innerHTML that we want to change, and is followed by a hidden IFRAME. The function below will add to the history 10 times, once every 200 milliseconds. I will then tell the browser to start looking for back-button clicks.

Listing 7.5

<p><a href="javascript:ie();">Back Button - IE Test</a>
<span id="ieTest"></span><br><iframe id="historyFrame" width="50" height="50"
scrolling="no" style="display:none;"></iframe></p>

function ie() {
    for (var i=0;i<10;i++) {

I created one additional function in Listing 7.6 to get the document object from the IFRAME called getIFrameDoc().

Note: This can be done in Firefox too, but it's not possible to access a document object in the cache (history). A permission-denied error is created.

Listing 7.6

function getIFrameDoc() {
    var histFrame = document.getElementById("historyFrame");
    var doc = histFrame.contentWindow.document;
    return doc;

In Listing 7.7, the innerHTML of the ieText span tag is set the same way as it was in Firefox. Some additional coding is needed to update the IFRAME. First, the document object of the IFRAME needs to be retrieved. Then write to the body of the document by opening it, writing to it with the index value, then closing it. This creates the history in Internet Explorer. Now we're ready to start looking for a back button.

Listing 7.7

function setInner2(i) {
    var ffHTML = document.getElementById("ieTest");
    var txt = "testing " + i;
    ffHTML.innerHTML = txt;
    location.hash = i;
    // additional stuff for IE
    var doc = getIFrameDoc();

In Listing 7.8 with its checkUrl2, it's almost the same as Listing 7.4, except that rather than looking for the hash, I look at the innerHTML of the doc.body. Finally, I update the hash of the location object so the behavior is similar in IE and Firefox. (Note: While this can be done in Firefox for the current IFRAME, it can't be done for an IFRAME document in its cache. It gives me an "access denied" error.)

Listing 7.8

function checkUrl2() {
    var doc = getIFrameDoc();
    var hash = doc.body.innerHTML;
    if (bbArray.length != hash && hash != currentIndex) {
      var ffHTML = document.getElementById("ieTest");
      ffHTML.innerHTML = bbArray[hash];
      currentStack = hash;
      usedBack = true;
    if (bbArray.length == hash && usedBack) {
      var ffHTML = document.getElementById("ieTest");
      ffHTML.innerHTML = bbArray[hash];
      currentStack = hash;
      usedBack = true;
    location.hash = hash;

There are frameworks available to encapsulate the functionality for both browsers. You can even do this with what I showed you above, but our focus here is on the user experience not the code.

All of this works fine while the user is on the same page, but what happens if he leaves the application by going to Google to search for something and then wants to come back? You'll want to preserve the state of the page but the static JavaScript array has now been released and is gone.

The best thing to do here is to serialize the object's holding state with JSON (JavaScript Object Notation). (More information can be found at www.json.org.) Here you call the onUnload event.

In Listing 7.9, I put the array in an alert box. You might prefer to make a call to the server and store it in a session or persist it outside of the session. Then if the user returns to the page, he retrieves this session via the session or application by specifying the call in an onLoad event.

Listing 7.9

function unloadMe() {
function reloadMe() {
    // code here to get from server/session
<body onUnload="unloadMe()" onLoad="reloadMe()">

Like the way the Firefox back-button issues were handled, the same method of using the location. hash property for bookmarks and forwarding links can be employed.

Strategy #16 - Allow Bookmarks and Forwarding Links
People use AJAX pages the same way they use traditional ones. They want to bookmark and forward links. So let them do that.

Because most of the state changes occur within a page versus changing from page-to-page, saving a bookmark or forwarding a link has to be accounted and planned for. For this, use the location object's hash property.

If the page is simple, just append the hash with a keyword or similar item, i.e., http://www.someserver. com/somepage.php#keyword.

Then with an onLoad event get the hash and do something with it. For example, Listing 7.10 creates the ability to bookmark a particular page within an AJAX application.

Listing 7.10

<body onload="init()">
<p><a href="javascript:bookmark();">Bookmarking Example</a></p>

The bookmark() function in Listing 7.11 just redirects back to the sample page with a newDate in the query string. This will simulate coming from a bookmark since modifying the hash doesn't involve a new request.

Listing 7.11

function bookmark() {
    location.href="sample.html?"+new Date().getTime()+"#from_bookmark";

The next function init() will be called via the onLoad event. Here we check the hash to do something. If you have a state to set or need to make a call to the server to get some data from a previous session, you can do that here. If you want to persist data between sessions, you can retrieve that session and all associated data with it by placing your persistent session identifier in the hash.

Listing 7.12

// check onload
function init() {
    if (location.hash == "#from_bookmark") {
      alert("from a bookmark, now do something with value, in HASH!" + location.hash);

Another option for more complicated pages is to create a QUERY_STRING like a parameter mechanism. So rather than using a ? to delimit the start of a QUERY_STRING, we can use the hash (#). Then we can still separate our key value pairs after it like:


While I don't recommend this for long strings since the URL has a limit, using a few to keep the place and allow for bookmarking and link forwarding should be fine.

Strategy #17 - Keep Users Informed
One of my pet peeves about software is that more often than not, it tries to think for me too much. As a result of all of this thinking, stuff happens behind the scenes, the user tries to do something (while something else is going on), and before you know it you have a bug report, a disgruntled user, and maybe even some lost revenue. To avoid this, keep users informed by providing notifications at the appropriate time.

Depending on the nature of your application there are different kinds of notification that could be used. Maybe you want to display an error message. Maybe you are loading some data. Or a user is uploading a file. Maybe to avoid breaking rule #1, which forbids doing the unexpected, you should tell the user exactly what's going on.

Use appropriate messages for notifications.

Notifications aren't that complicated. It's best to put them in the same place so users can expect, when they click or do something, that a notification will acknowledge it.

Listing 7.13

<p><a href="javascript:notify();">Notifications</a></p>
<div id="notifications"></div>

I'll create a test method for notifications called notify that will create our notification object. You could also create your notifications tag on-the-fly, though I have a static object on the HTML page above.

The notification has a time-out property that you can use if you choose. But you'll most likely want to add this notification object to your XHR callback and then call notifyOff() when you're done with your background action.

Listing 7.14


function notify() {
    var notice = new Notification("testing...");

function Notification(msg) {
    this.message = "";
    this.timeout = 2000;
    if (msg != undefined) {
      this.message = msg;

Notification.prototype.setTimeout = function(time) {
    this.timeout = time;

Notification.prototype.notifyTest = function() {

Notification.prototype.notifyOn = function() {
    document.getElementById("notifications").innerHTML = this.message;
    document.getElementById("notifications").style.display = "inline";

Notification.prototype.notifyOff = function() {
    document.getElementById("notifications").style.display = "none";

What if you don't want ad hoc notifications and want to be consistent for all developers on a team? You will need to create a mechanism so everyone can use the same types of notifications.

The first step is to categorize the notifications and put these in your lib.js where you have your application constants.

Listing 7.15

var NOTIFICATION_ERROR = "Error Has Occurred...";
var NOTIFICATION_WARNING = "Update Warning...";
var NOTIFICATION_INFO = "Processing...";
var NOTIFICATION_LOADING = "Loading...";


function getNotification(msg) {
    var isValidNotification = false;
    for (var i=0;i<notifications.lenth;i++) {
      if (msg == notifications[i]) {
        isValid = true;
if (isValid) {
      return new Notification(msg);
    } else {
        alert("invalid notification type");


Strategy #18 - Don't Ignore the Browser-Challenged
We know from working with the back button that not all browsers are created equal. But what about users with disabilities or without access to the latest and greatest browser. Section 508 of the Americans with Disabilities Act and the Web Content Accessibility Guidelines (WCAG) has to be taken into account when designing AJAX applications.

For example, if you're building a site that has to be WCAG 1.0-compliant, your application must act the same way if the JavaScript is turned off. If JavaScript is turned off, there goes your AJAX application.

Some of the certifications you can get are levels A, AA, and AAA depending on the requirements your application meets.

These guidelines are seven years old and have now been updated with a version 2.0. Under the right conditions, you're allowed to use JavaScript, but there are restrictions. For example, you can add objects to the DOM via createElement(), but not with document.write().

Speaking practically, I find limiting the JavaScript use paradoxical because the institutions that require it to be compliant also use office suites and other client applications that don't have to meet the same degree of compliance.

I was going to start listing the guidelines but I found them too impractical to follow for Web applications, let alone AJAX-enabled applications. If I followed them to the letter, I would end up with a traditional Web application.

In the past I've worked with state and local governments that required the application to be WCAGcompliant. Yet when they looked at the application and it didn't have the functionality they wanted, they hated it. I had to tell them, if you want it both ways, you have to pay for it.

I've also had occasion to demo an "accessible" version of an application only to find that it wasn't purchased because it looked dull.

Strategy #19 - Notifying the User When Something Has Changed
Sometimes pages are large and the data the users are working with becomes unruly, especially if they have to scroll. So make it easy on your users. Show them what's changed.

When users make changes to fields in Web applications or when the state of the page changes, it's often useful to make users aware that something has changed. An example of this is the typical email client. Unread mails are usually in bold while the read ones are normal. Note: Avoid using reds and greens. They look the same to the color blind.

If you have a form field, you can make changes as well. Look at Listing 7.16.

Listing 7.16

function modified(elt) {

Input Field: <input type="text" value="testing" onchange="modified(this)">

Another option is iterating through the DOM and attaching events to each of the fields in the form. Just remember to change the color back to its original state once the data has been submitted.

Strategy #20 - Do Things in the Background to Help the User
Users don't like to wait, so without doing too much thinking for them, it's okay to do a few things as long as they speed things up.

Sometimes you will want to perform some operation in the background. Perhaps it's a timer checking for changes at certain intervals or maybe it's a user-initiated action. In the case of a user-initiated event, let's use an onchange again. One thing I don't like is having to wait to submit a form especially if I have attachments. To improve the user experience and prevent the user from waiting, we can upload their attachments in the background.

Listing 7.17

<p>Background Operations :
<div id="uploadDiv">
<input type="file" onchange="uploading(this)" size="50" style="margin:4px;">

By combining this with the notification object that we created earlier we have the following example.

Notice that we prevented the user from changing anything while this was happening by disabling the field. We also changed the background color to let the user know that the data had changed. We also added an uploading message in the notifications area of the page.

Listing 7.18

var uploadElt;
var uploadNotify = new Notification("Uploading...");

function uploading(elt) {
    uploadElt = elt;

function uploadDone() {
    var val = uploadElt.value;
    document.getElementById("uploadDiv").innerHTML = "<input type=checkbox checked> "+val;

Something else along the lines of doing something for the user but not thinking too much for her is to auto-complete some fields.

Strategy #21 - Auto-Complete Fields (If You Can)
Again, without trying to think too much for the users, auto-completion is another way to speed up what they do.

Auto-completion involves three steps:

  1. Reading what the user is typing.
  2. Making a server-side query that filters a large list into a smaller list based on what the user typed.
  3. Providing an easy mechanism for the user to select the suggested auto-completion instead of typing out the whole thing.
The example I'm going to use doesn't go to the server. It just looks at a simple array. Note: Sometimes it's faster to download all the data than make multiple requests.

In Listing 7.9, I have a text field and a select field. The text field will capture what the user is typing and the select field will provide an easy way to cycle through possible suggestions for autocompletion.

Listing 7.19

<p>Auto Complete <br>
<input type="text" size="40" onkeyup="prepop(this.value,event);"
   style="width:300px;" id="txtsug"><br>
   <select name="sug" id="suggest" size="10" style="width:305px;"></select>

The first getKey method is simple. We pass our onKeyDown event to this method and, depending on our browser, we get the number of the key, provided it's not empty.

Listing 7.20

function getKey(e) {
    var keynum;
    if(window.event) { // IE
      keynum = e.keyCode
    } else if(e.which) {// Netscape/Firefox/Opera
      keynum = e.which
    return keynum;

The first thing I do is to create a static variable for the select position in the suggestion box. I'll keep track of this index as I move up or down.

var selectIndex = 0;

If the key is up or down, then we'll assume that the user is selecting something from the select box. If enter, then the user has selected the auto-completed item and it's ready to be submitted.

Assuming it's not an arrow or enter key, we want to filter our dataset with the items from the textbox. So to do that I pass the value of the textbox to the createList function and then I make the select box viewable.

Listing 7.21

function prepop(last,evt) {
    var key = getKey(evt);
    if (last.length >0) {
      // up or down
      if (key == 40 || key == 38) {
      // return
      if (key == 13) {
      // resets select
      selectIndex = 0;

      var v = document.getElementById("txtsug");
      var sug = document.getElementById("suggest");
      if (sug.options.length >0) {
        sug.style.display = ‘inline';
        sug.options[selectIndex].selected = true;

The set select function is called if the up or down arrow is pressed. Here I just have to set the selectedIndex of the item in the list to either plus or minus 1, and I need to auto-complete the text field with the value of the option field.

Listing 7.22

function setSelect(evt) {
    var sug = document.getElementById("suggest");
    if (getKey(evt) == 40 && selectIndex < sug.options.length-1) {
      sug.options[selectIndex].selected = true;
      selectIndex = selectIndex + 1;
    if (getKey(evt) == 38 && selectIndex >0) {
      selectIndex = selectIndex - 1;
    sug.options[selectIndex].selected = true;
    var t = sug.options[sug.selectedIndex].text;
    document.getElementById("txtsug").value = t;

The create list function, depending on performance considerations, either looks through an array or array of objects, or makes a call to the server to get its data. In this case, I have the getNames function return an array of names with AJAX in it.

Listing 7.23

function getNames() {
    var n = ["Ajax1","Ajax12","Ajax123","Ajax1234","Ajax12345","Ajax123456","Ajax1234567",
    return n;

The filter process below is just going to match sub-strings of the textbox with the different options in the select box. So each time I get the list I have to remove all the options from the field and then add them back where they meet my search criteria.

function createList(v) {
    var sel = document.getElementById("suggest");
    // first remove all
    while (sel.length >0) {
    var names = getNames();
    for (var i=0;i<names.length;i++) {
      if (names[i].substr(0,v.length).toLowerCase() == v.toLowerCase()) {
        var opt = document.createElement("option");
        opt.text = names[i];
        opt.value = names[i];

I only recommend using auto-completion if you can get good performance characteristics from your AJAX application and the patterns of auto-completion are intuitive to the user. Remember, you still have to avoid the pitfalls.

Chapter Summary
I'm going to give you a cheat sheet here. Something you can print out, hang in your cube, and reference until it becomes habit.

AJAX User Experience Strategies

  1. Define Your Problem: All software, at least all good software, solves problems for people. Sometimes identifying the problem is the hardest part.
  2. Create a Model: Models don't have to be fancy, require tools, or be some form of acronym as long as they help you understand your software. That's all you need. It just takes a pen and a sheet of paper.
  3. Measure User Experience: User experience is difficult to measure, but you can do a lot by counting. In general the more states and transitions the less usable the software is.
  4. Create Personas: It's impossible to talk to every user of your software, but you can put a face on them by creating imaginary users that share traits with your real users.
  5. Create a Storyboard: A storyboard of your application is a short sequence with pictures or screenshots or words describing a scenario.
  6. Create an Application Walkthrough: Once you do the storyboard phase of your project and build some working code, it might be time to walk through your application with some actual users.
  7. Do a Heuristic Analysis: If you can't get any users, at least do a heuristic analysis.
  8. Create a Cheat Sheet: No one likes to read large amounts of documentation, so make it easy on everyone on your team and create cheat sheets.
  9. Go Classic: There are times when no matter how hard you try, the best solution is a classic Web solution.
  10. Don't Use AJAX for Navigation: Your site navigation takes people and search engines to other pages, so this isn't the right place for AJAX.
  11. Eat Your Own Dog Food: If you don't have time to use your own code, don't expect others to.
  12. Be Consistent: Consistency is important in software. Meeting user expectations is a cornerstone of effective software. When you change what the user expects, you make your software less effective.
  13. Completely Test What You Build: I thought the days of coding for different browsers were over, but no, here we go again.
  14. Break the Back Button: It's okay to break the back button. Sometimes error prevention takes precedence over the golden rule.
  15. Don't Break the Back Button: The rest of the time, don't break the back button.
  16. Bookmarking and Forwarding: Users will use your AJAX pages the same way they use traditional ones, they will bookmark and forward links, so let them do that.
  17. Keep Users Informed: One of my pet peeves about software is that more often than not it tries to think for me too much and doesn't let me know what is happening.
  18. Don't Ignore the Browser-Challenged: We know from working with the back-button that not all browsers are created equal. But don't forget about users with disabilities or without access to the latest and greatest browser.
  19. Notify Users When Things Change: Sometimes pages are large and the data users are working with becomes unruly, especially if they have to scroll. So make it easy on your users, show them what's changed.
  20. Perform Background Operations: Users don't like to wait, so without doing too much thinking for them, it's okay to do a few things so long as it speeds things up.
  21. Auto-Complete: Again, without trying to think too much for users, auto-completion is another way to speed up what they do.

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs, click here to order.

More Stories By Scott Preston

Scott Preston lives in Columbus, Ohio, with his wife Emily and dog Castle. He has been developing Web applications since graduating from The Ohio State University, in 1996. Scott started out building Web apps with PERL, and Active Server Pages, then moved on to J2EE, and .NET before settling in on his current favorite, LAMP (Linux, Apache, MySQL, and PHP). Scott is also a member of the Java Community Process and just finished his first book " The Definitive Guide to Building Java Robots ."

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.