A List Apart

Menu

User-Proofing Ajax

Issue № 228

User-Proofing Ajax

by Published in JavaScript · 22 Comments

We’ve all felt the clean slickness of a well-oiled Ajax app. But think back to the last time something went wrong while you were using an Ajax app: how did it feel?

Article Continues Below

Chances are, it felt awkward and foreign. You probably stared at the little spinning “wait” graphic and wondered whether or not you should reload the page. You might have glanced at your watch and thought that the app was taking its sweet time—or maybe you wondered if your request had been made at all. You didn’t know what was going on—and you’re a developer! Imagine what a non-technical user goes through every time there’s a hiccup.

When good apps go bad

Ajax is handy because it avoids needless browser behavior such as page reloads, but it can also be dangerous because it avoids useful browser behavior like error handling. Gone are the “Server not found” error messages and that familiar escape hatch, the Back button. When Ajax circumvents useful behavior, we have to make sure we replace it as well as we can.

Perfecting your backup plan

One of the cardinal rules of client-side scripting is to accommodate users who have scripting turned off. Ajax implementation makes this easy: when hijacking (or “hijaxing”) a link or form to trigger your Ajax functionality, make sure that link or form resolves to a valid URI that will carry out the intended action using the traditional method. (Line wraps marked » —Ed.)

<form action="traditional_action.php" method="post" »
onsubmit="perform_ajax_action(); return false;">

If you take this approach, script-enabled browsers will carry out your Ajax actions and other browsers, recognizing neither the onsubmit event handler nor its return false directive which skips form submission, will submit the form normally and fall back to the traditional form action.

(Note: it’s almost always a better idea to do the hijacking deep in your JavaScript code. Inline JavaScript event handling is best suited to, well, demonstration.)

We could also take this a step further in anticipation of script-enabled browsers that don’t support any type of XMLHttpRequest object. Instead of using an explicit return false in the onsubmit event handler, we’d test for Ajax ability in the handling function, carrying out the Ajax script if it’s supported and triggering the form if not. Let’s say you have a JavaScript function named createAjaxRequest() that creates a browser-appropriate XMLHttpRequest object or returns false on failure:

var request; // our request object

function perform_ajax_action(){
  if ( !request = createAjaxRequest() ){
    return true; // abort Ajax attempt, submit form
  }
  // proceed with your regular Ajax functionality
  // ...
  return false; // do not submit the form
}

At the form level, we’d rewrite our event handler to fall back to regular form submission at the first sign of trouble. (Line wraps marked » —Ed.)

<form action="traditional_action.php" method="post" »
onsubmit="return perform_ajax_action();">

If all goes well, the Ajax code will be executed and the form submission will be skipped. If any problems occur with the user agent’s Ajax support, the form will be submitted and the traditional version of the action will be performed.

Keeping your users informed

Much Ajax functionality exists to bypass the standard browser interface, so we need to replace the core usability safeguards that your users are accustomed to. First, we need an error handler that reports errors to your user when the browser can’t. The way in which you deliver the error reports depends on your individual application; in my examples, I put the error message in a hidden div that appears when an error occurs.

What kind of errors do we report? Connectivity errors, to begin with. If there’s a problem connecting to your behind-the-scenes files, the browser won’t report it natively. If the response fails with a regular HTTP error code, we can pull that from the XMLHttpRequest object and report it properly. Here’s an excerpt from the function set as the onreadystatechange event handler, with the XMLHttpRequest object request: (Line wraps marked » —Ed.)

if ( request.readyState == 4 ){  // 4 is "complete" 
  if ( request.status == 200 ){
    // HTTP OK, carry out your normal Ajax processing
    // ... 
  }else{
    // something went wrong, report the error
    error( "HTTP "+request.status+".  An error was »
             encountered: "+ request.statusText );
  }
}

The next type of error to address is the application-level error. These errors occur when something goes wrong within your application, and though the way in which you handle these errors depends on your specific application, there are some basic principles to keep in mind.

Let the cycle be unbroken

Most importantly, the Ajax communication cycle must be preserved. Your server-side execution must complete and return a valid XML document—otherwise, you leave the browser (and user) hanging, with no clue as to what’s happened. If your server-side script crashes, the error message will be lost and your application will silently fail. That’s why it’s essential to perform assertions and boundary checking on anything in your code that could crash your program, like this PHP snippet which logs an error and exits when a file can’t be opened:

if ( !$fp = fopen( $filename, "r" ) ){
  $error_msg = "Could not open file " . $filename;
  exit();
}else{
  // proceed with the rest of the program
  // ...
}

This is good programming practice in general, but in this situation, it’s vital to your application’s usability.

Communicate errors

Once your server-side program completes, you should check for success and report any errors to the user. The XML document sent back from the server should show error reporting—even something basic with just a code and a message.

In this example, the XML document has a success element, which contains the value “1” for success and “-1” for failure. The XML document also carries an error element, which contains the error message to be reported. The server-side script performs error checking, and returns valuable error messages to keep the user informed, and guide him or her through the user process. (The errors that the server-side script checks for in the example are simple content validation tests that should of course be performed with client-side validation; it’s a very basic Ajax app put together only for demonstration purposes.)

Solving the real Ajax killer: silent errors

What about silent errors like timeouts and dropped packets? When confronted with a web application that appears to have mysteriously ceased to function, users generally respond by losing patience, hitting the Back button, and trying again. This probably won’t be the ideal case for your Ajax app: with its lack of complete page reloads, the Back button will take the user back multiple steps in the user process. If the Ajax app is well-designed, no information should be lost—after all, you’ve been saving data to the server the whole time—but it’s still an extra step for the user that can be avoided.

How? Keep your user informed. Keep track of the time elapsed since you made your request. If a longer-than-reasonable amount of time goes by before you get a response, pop up a warning dialogue informing the user that something may have gone wrong. Explain the situation and offer them options: at the very least, give them a choice between waiting a little bit longer and reloading the page. It’s a simple but effective catch-all error handler that maintains user confidence in your application.

This second example puts this idea to use, letting you set a server-side delay to simulate a packet delay. If a request is made and more than 30 seconds go by with no response, a dialogue pops up asking the user if they’d like to do something about it. Try varying the server-side delay time to see how it feels. Pretend you’re on a dial-up connection and are very used to dropped packets. You’ll be surprised at how appreciative you are of the application that’s sensitive to your frustration.

Only the beginning

This is by no means a comprehensive guide to error handling with Ajax. There are many other ways to use Ajax to enhance your application’s user experience, and many more problems and solutions will undoubtedly surface as Ajax grows and matures. Whatever methods you use, remember that Ajax is meant to create a graceful, cohesive user experience, so make sure your error handling fits with that principle and complements your application. The more graceful and cohesive your error handling, the more confidence your app will inspire—even when it’s breaking down.

About the Author

22 Reader Comments

Load Comments