O'Reilly logo

Ajax Design Patterns by Michael Mahemoff

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 7. Dynamic Behavior

With the technologies covered so far, we can change the display and talk to the server. But what will trigger these actions? Ajax Apps are generally driven from within the browser, so that’s where the actions will be triggered. Broadly speaking, there are two types of triggers, each covered here: User Actions, such as mouse clicks and keypresses, and Scheduling, where actions are scheduled to be run at some point in the future.

User Action

⊙⊙⊙ Action, Change, Click, Control, DHTML, DOM, Events, Keyboard, Mouse, Move, Type, Widget

User Action
Figure 7-1. User Action

Goal Story

Pam is booking a trip on the corporate travel planner. She sees a form with the usual fields and clicks on location. Suddenly, a list of cities fades in beside the form, and Pam selects Paris. Beside the city list, a second list appears, this one showing approved hotels. Pam chooses the Hilton, and both lists disappear. Pam’s pleased to proceed with the destination on the updated form, which now reads, “Paris Hilton.”

Problem

How can the application respond to user activity?

Forces

  • A rich application allows users to interact with it, frequently and in different ways.

  • Responses must be as quick as possible, so as to streamline performance, keep user attention, and help the user understand the application’s cause-effect model.

  • Using form submissions as the only response to user activity is slow and limits interactivity.

Solution

Handle most User Actions within JavaScript, using event handlers. The essence of Ajax is rich browser-based interaction, and DOM events are the technology that make it happen. DOM objects can register event handlers, functions that are notified when events occur. This callback model should be familiar to anyone who’s worked with desktop GUI frameworks.

Let’s say you want to run the following function when the user clicks a shutdown button:

  function shutdown( ) {
    if (confirm("Are you sure you want to shutdown?")) {
      postShutdownMessageToServer( );
    }
  }

The simplest way to set this up is to declare a button with an onclick event handler:

  <button id="quitButton" onclick="shutdown( );"/>Quit</button>
<!—Obtrusive -->

Now the web browser will arrange for shutdown( ) to be called whenever the button is clicked. However, we can improve on this, because the above declaration mixes JavaScript with HTML. It’s cleaner to just declare the button and deal with an event handler in a separate JavaScript file. Since we always want this behavior, we should declare it as soon as the page loads. To run something when the page loads, we can use another event handler that is triggered by browser activity rather than a User Action: onload.

  [HTML]
  <button id="quitButton"/>Quit</button>

  [Javascript]
  window.onload = function( ) {
    quitButton.onclick = shutdown;
  }

Note that we’re declaring this inside window.onload instead of “out in the open”; if you do the latter, you might get an error because the script might be executed before the button is actually on the page. You’ll see the window.onload idiom used in most JavaScript code, including all the Ajax Patterns demos.

Instead of referencing a callback function, it’s sometimes convenient to define the callback as a closure (anonymous function), as in:

  quitButton.onclick = function( ) {
    if (confirm("Are you sure you want to shutdown?")) {
      postShutdownMessageToServer( );
      quitButton.onclick=null;
    }
  }

Registering events with JavaScript, as opposed to in HTML tags, is an example of unobtrusive JavaScript because it separates JavaScript from HTML. And defining the event handler in JavaScript also has another benefit: you can dynamically redefine actions in response to system events. Our shutdown( ) method could also redefine the handler to avoid a double shutdown:

  function shutdown( ) {
    if (confirm("Are you sure you want to shutdown?")) {
      postShutdownMessageToServer( );
      quitButton.onclick=null; // Quit button no longer
triggers an event.
    }
  }

Notice the model here involves a single handler for any event type; the above commands set the handler, in a manner that will remove any existing handlers. In most cases, that’s just fine, and it has the merit of being completely portable. In some situations, though, it’s nice to add a handler instead as it makes the code more modular. Two separate library functions can then register for the same events, without having to be aware of each other. Likewise, a function for removing would also be nice:

  addEvent(quitButton, "click", postShutdownMessageToServer);
  ...
  removeEvent(quitButton, "click", postShutdownMessageToServer);

Browsers do offer support for this functionality, but it’s unfortunately varied, and a portable solution has been notoriously difficult. So much so that a competition was recently held to find the best addEvent( ) and removeEvent( ) functions, and you can find the winner, a 15-line script online (http://www.quirksmode.org/blog/archives/2005/10/how_do_i_create.html). Dojo Toolkit (http://dojotoolkit.org) also supports this behavior as part of its sophisticated event library.

It’s not always enough for the event handler to know that an event has occurred; it also needs to know about the event. For example, the same event handler might be used for three different buttons, in which case it will need to know which of the buttons was clicked. For this reason, the web browser creates an event object upon each user event, containing various bits of information. In Firefox, it’s passed to the event handler, so you just ensure an event parameter exists:

  function shutdown(ev) {
    ...
  }

In previous examples, we omitted the event parameter, which is just fine since parameters are optional in JavaScript functions—omitting them just means you don’t get an opportunity to use them.[*] As it happens, IE doesn’t pass the value in anyway, and instead holds the event in a window attribute. Again, JavaScript’s loose handling of parameters means you won’t actually get an error by including the parameter in IE. However, the value will always be null, which isn’t very useful. What all this leads to is the following boilerplate code, which you can use whenever you care about the event. An “equalizer” statement gets hold of the event whichever browser we’re in:

  function shutdown(ev) {
    event = event || window.event;
    ....
  }

The event object contains various information, such as which element was clicked and where the mouse was. The various event types are covered next.

Decisions

What events will the script listen for?

Many events are made available to JavaScript code, and more come out with each new browser upgrade. Following are some frequently used and portable events, along with typical applications. Check the following out for more info on events: http://www.quirksmode.org/js/events_compinfo.html, and http://www.gatescript.com/events.html.

All handler functions accept a single parameter representing the event, and as discussed earlier in the "Solution,” you have two options: ignore the parameter altogether (as in the initial shutdown( ) examples), or—if you care about the event details—include the parameter and equalize it (as in the shutdown(ev) examples above).

Key pressing—onkeypress, onkeydown, onkeyup
  • onkeypress and onkeydown occur immediately after a key is pressed, and will also repeat if the key is held down. onkeyup is called just once, upon the key’s release.

  • They can be used to enhance standard text editing. For instance, you can confine a phone number text field to contain only numbers, or you can show a word count while the user types.

  • They’re sometimes used to automatically leave a field once it’s valid—e.g., to proceed to the next field after five digits have been added to a zip code field. However, doing so is often counter-productive, as users generally perform faster when behavior is consistent, even at the expense of minor technical shortcuts.

  • They can be used to create keyboard shortcuts for custom controls. You can determine if the user’s mouse is over the control with the onmouse* functions, and if so, respond to particular keypresses.

Keyboard focus—onblur, onfocus
  • In the case of editable fields, onblur indicates keyboard focus has been lost, suggesting an update has probably occurred, so is often used to initiate a remote call or some validation technique.

  • onfocus suggests the user has begun working on a particular object, so it can be used to show online help or change the display inside a Status Area.

Mouse button clicking—onmouseup, onmousedown, onclick, ondblclick
  • onclick and ondblclick indicate a button has been clicked or double-clicked. onmousedown and onmouseup indicate a button has been depressed or released. These latter events are more fine-grained than clicking, which implies the sequence of mousedown followed by mouseup has completed, both on the same element (note that click won’t fire if the user releases the mouse button over a different element). The button control is specifically geared for catching click events to let the user do something, and radiobuttons and checkboxes can also be associated with click listeners to indicate changes.

  • onmousedown and onmouseup can be used for panning behavior and for custom drag-and-drop functionality.

Mouse movement—onmouseover, onmouseout
  • onmouseover and onmouseout indicate the mouse has just moved over, or has just left, an element. It can be useful to keep a pointerElement variable to track which element is currently selected.

  • They can be used to change an element’s style when the mouse rolls over it. This shows it’s active and can convey that this is the element that will be affected if the user clicks the mouse button right now, or perhaps hits a certain key.

  • They can also be used to provide help or further information in a Status Area or a Popup.

Selection—onselect
  • onselect indicates when the user has selected some text.

  • By tracking the selection, the application can provide information based on what the user’s selected. For example, you could let the user search on a term by selecting it, and then morph the Search Results element.

  • By tracking the selection, the application can also allow transformations to occur. For example, the textarea in many modern content management applications, such as mediawiki, allows the user to select some text and then change it, just like in a word processor. To italicize text on Wikipedia, select the text and click the <i> icon, which then wraps mediawiki markup ('') around the selected text.

Value change—onchange
  • onchange indicates a value has changed, so it’s often used to initiate a remote call or some validation technique. This is an alternative to onblur. Unlike onblur, it is only called if the value is actually altered.

What attributes of the event will be inspected?

The event object contains several useful pieces of information about the event and what was going on at the time. Note that some of these attributes are set even for events you may not expect. For example, the ctrlKey modifier will be set even for a mouse-click event. This would allow you to detect a Ctrl-mouse press action. However, not all attributes are always set, so you need to be careful in testing for portability.

Following are some of the portable and more frequently used attributes of the event object:

Element—target (Firefox), srcElement (IE)
  • target and srcElement indicate which element the event occurred on. To equalize across browsers:

      el = ev.target || ev.srcElement

    This is useful if you have a single function listening to lot of elements—for instance, an e-commerce itemClickListener monitoring all items for a click event. Inspecting this property will tell it which particular item was clicked.

Event Type—type
  • type indicates which event type took place; e.g., click.

  • This is a potential code issue, because it suggests the same function has been configured to handle multiple events. If it then needs to distinguish among the different types of events, it might be worth breaking it out into a handler function for each event type, with any common routines placed elsewhere.

Key code—which (Firefox), keyCode (IE)
  • which and keyCode indicate the Unicode value of the key that was pressed.[*] This isn’t completely consistent across browsers but is easy enough to equalize. Since you can’t directly register a function against a specific key, this property is the only way to decide if a certain key was pressed.

Key modifiers—altKey, ctrlKey, shiftKey
  • The altKey, ctrlKey, and shiftKey are modifiers indicating if the special keys Alt, Ctrl, and Shift were being held down while a key event occurred. You can use the modifiers to introduce keyboard shortcuts to the application. Since many single-modifier shortcuts are already used by one browser or another, portable applications often need to use double-modifiers. Thus, the key-handling function will need to perform a check like:

      if (ev.ctrlKey && ev.shiftKey) {
        ... // perform ctl-shift shortcut
      }
  • There is also a meta-key modifier, which is generally not advisable as it’s not supported by IE, and in any event, available only on certain keyboards.

Mouse buttons—button
  • This indicates which mouse buttons were being associated with the event. In IE, 1 is left, 2 is right, and middle is 4. The value represents the sum of all buttons being pressed, allowing you to catch “chords”—multiple keys held down at once. In Firefox, 0 is left, 1 is middle, and 2 is right.

  • This is a painful area due to serious incompatibility issues (http://www.quirksmode.org/js/events_compinfo.html). As well as the differences above, beware of incompatibilities when one button is being depressed while another is already depressed, and also incompatibilities in which events provide this property (sometimes only mouse clicks; sometimes others).

Mouse position—clientX, clientY
  • These indicates the position of the mouse pointer when the event took place, relative to the browser window.

  • This is useful for image-based applications, such as maps and simulators. It’s often not practical to register event handlers here, so JavaScript code—with possible help of web remoting—can determine exactly what the user clicked on by examining the coordinates.

Will event handlers be registered after the page has loaded?

Using JavaScript and the DOM, redefining event handlers is easy enough to do, but should you do it? Redefining the effect of user events must be done with caution, as there is great potential to confuse users. Sometimes, event redefinition occurs simply because the programmer can’t be bothered adding a new control, or the UI is so small that designers want to reuse an existing control. So before deciding to redefine an event, ask yourself if there are alternatives. For example, could you add a second button instead of redefining the first button’s action?

A few examples where event redefinition might be worthwhile:

  • For state transitions. The JavaScript may have separate start( ) and stop( ) methods, and you need a toggle button to flip the state, since that is clearer and less error-prone than separate “on” and “off” buttons.

  • For enabling and disabling. There is already disabled a available for standard controls (http://www.quirksmode.org/js/disabled.html), but for custom controls that you may have created, you might use event redefinition to cancel or re-enable the effects of interacting with the control.

  • For actions that depend on dynamic information, such as which field has input focus.

However, in all of these cases, it’s usually simpler to have a single method, always registered in the same way, and to allow that method’s JavaScript to decide where to route the event.

Real-World Examples

Google Reader

Google Reader (http://google.com/reader) is a web-based RSS aggregator (Figure 7-2). You can change the current article by mouse-clicking on article titles. An interesting feature is keyboard shortcuts—when the page contains numerous articles, clicking “j” and “k” will scroll up and down to the previous or next story.

Google Reader
Figure 7-2. Google Reader

Google Maps

Google Maps (http://maps.google.com) uses a dragging action to pan the map within a Virtual Workspace, and the arrow keys can also be used.

Backpack

37Signals’ Backpack (http://www.backpackit.com/) maintains items in a list and illustrates how you can use Drag-And-Drop in an Ajax App. Drag-And-Drop relies on monitoring the mouse button as well as position.

Code Example: Basic AjaxPatterns Demos

Here are a couple of basic examples from the Ajax demos. The Basic Time Demo (http://ajaxify.com/run/time) handles a button click like this:

  $("defaultTime").onclick=requestDefaultTime;

The wiki tracks that focus and blur events in order to show the user which message is being edited and to upload any messages after a blur occurs. It also tracks mouse movement over each area, to provide an affordance indicating that the fields can be edited:

  messageArea.onmouseout = onMessageMouseOut;
  messageArea.onmouseover = onMessageMouseOver;
  messageArea.onfocus = onMessageFocus;
  messageArea.onblur = onMessageBlur;

Each of these passes to getMessage, which identifies the message element that was acted upon:

  function getMessage(event) {
    event = event || window.event;
    return event.target || event.srcElement;
  }

Alternatives

“Click ‘n’ Wait”

The conventional web app follows the “click ‘n’ wait” pattern, popular in 1970s mainframe-based client-server applications and revived in time for the late-1990s web generation, albeit in color. The only type of interactivity is the user submitting a static form to a server-side CGI script or clicking on a link. The script then reads some variables, does something, and outputs a whole new page of HTML. A full page refresh once in a while is OK, when a big context switch takes place, but basic updates are best controlled with JavaScript.

Richer forms

The “richer form” is richer than static HTML, but less so than Ajax. It involves enhancing a standard form with dynamic behavior, so as to make things clearer and help prevent the frustrating validation errors that often come back from the server. For instance, DHTML can be used to ensure a user enters only digits into a credit card field or to add some pop-up instructions for a form field.

Related Patterns

Display Morphing, Page Rearrangement

Display manipulation, as discussed in Display Morphing and Page Rearrangement (Chapter 5), is often triggered by User Events.

XMLHttpRequest Call, IFrame Call

Web remoting, as discussed in XMLHttpRequest Call and IFrame Call (Chapter 6), is often triggered by User Actions.

Scheduling

⊙⊙⊙ Cron, Event, Future, Loop, Periodic, Plan, Repeating, Schedule, Sequence, Timeout

Scheduling
Figure 7-3. Scheduling

Goal Story

Frank’s panning across a map of the factory. To ensure he monitors all regions, each is color-coded according to how recently it was investigated. It’s implemented with Scheduling: after 5 idle minutes, the room turns orange; after 10 minutes, it turns red.

Problem

How can you run actions in the future, or repeatedly?

Forces

  • Sometimes, an application needs to repeatedly run the same action; e.g., to extract new data from the server or to monitor application state.

  • Sometimes, an application needs to run an action at some future time; e.g., to warn a user his session is about to time out.

  • The server can’t initiate a connection to the client, so there’s no way to have the server “wake up” the client according to a schedule.

Solution

Use JavaScript timers to schedule actions. JavaScript’s timer mechanism lets you schedule a one-off action or an action repeating at fixed intervals. In either case, what you specify is an action and a period of time in milliseconds. Note: an online demo (http://ajaxify.com/run/scheduling) illustrates the code concepts throughout this section and the code snippets loosely follow from the demo.

The naïve way to run an event in the future would be:

  sleep(5000); // Nope, won't work.
  expire( );

That won’t work because JavaScript doesn’t have a sleep( ) capability—you can’t just block in the middle of a script.[*] Instead, you need to schedule the execution.

The most basic usage is planning a one-off event in the future. For example, suppose an ecommerce application wants to expire a price offer after five seconds:

  setTimeout(expire, 5000);
  function expire( ) { $("price").innerHTML = "Expired"; }

What if something happens and you want to cancel the timer. For example, the user starts typing in a deal quantity, and the e-commerce application wants to hold off to give the user some more time. setTimeout actually returns a timer object, which allows for cancellation:

  var expiryTimer;
  ...
  expiryTimer = setTimeout(expire, 5000);
  $("dealQuantity").onkeypress = function( ) { // User typed something.
    clearTimeout(expiryTimer);
  };

In this example, it might make more sense to postpone, rather than cancel, expiry altogether. You can achieve this by creating a new timer:

  var expiryTimer;
  ...
  expiryTimer = setTimeout(expire, 5000);
  $("dealQuantity").onkeypress = function( ) { // User typed something.
    clearTimeout(expiryTimer);
    expiryTimer = setTimeout(expiryTimer, 2000); // 2 secs more
after a keystroke.
  };

So far, the future action has been a single function call (expire( )). Sometimes, it’s more convenient to say what happens as part of the timeout, in which case you can wrap it all in a string. This prevents the need to create a function specifically to handle the timeout. The string will be evaluated upon timeout:

  setTimeout("'$('price').innerHTML = 'Expired'", 5000); // Got rid of the function.

A string is also useful when you want to specify an argument, either fixed or dependent on some variable:

  setTimeout("expireWithMessage('The deal is off!')", 5000);
  setTimeout("expireWithMessage(name + ' deal is off!')", 5000);

You can pass a function instead of a string:

  setTimeout(function( ) {
    expireWithMessage(name + ' deal is off!'); // Caution!
  }, 5000);

That will work, but beware: the expression will evaluate at time of execution, not declaration. The name variable will resolve to the value of name when the timer fires, not when it’s created. To make it more concrete, here’s a script that issues two alerts. What do you think they say?

  var name = "Ye Olde DotCom";
  setTimeout("alert('Dealing with " + name + "')", 3000);
  setTimeout(function( ) {
    alert(name + ' deal is off!'); // Caution!
  }, 5000);
  name = "New Formula 2.0";

If you run this script at http://ajaxify.com/run/scheduling/name/, you’ll see two alerts:

  Dealing with Ye Olde DotCom
  New Formula 2.0 is off

What’s going on? In the first case, name is packed into a new string object when the timer’s created. That complete string will be used when the timer fires. In contrast, the second case involves a variable, name, which—being an ordinary JavaScript variable—is a pointer to some memory location. The value in the memory location will only be looked up when the timer fires.

How, then, do you pass an argument to the scheduled function so that it will evaluate when you set up the timer rather than when the timer actually fires? The easiest way is to build up a string, as shown above, but that’s ugly for a long block of code. There’s another technique based on closures, illustrated in a further refactoring (http://ajaxify.com/run/scheduling/name/). It’s based on an idea by Scott Isaacs; see his explanation for more details (http://spaces.msn.com/members/siteexperts/Blog/cns!1pNcL8JwTfkkjv4gg6LkVCpw!340.entry).

The second type of Scheduling is repetition, and the mechanism is almost identical to one-off event handling. Instead of setTimeout, use setInterval. Again, the call will return a timer object, useful if you want the option of canceling the loop with clearTimeout. The second argument is again a period of time, but with setInterval, it represents the loop interval. The following code will call refreshData every five seconds:

  setInterval(refreshData, 5000);

A common alternative is to loop with setInterval. Here, a new call to the same function is rescheduled, usually subject to some condition. Thus, the function call is repeated until some criterion has been reached.

The timing mechanism isn’t super-precise, especially when the user’s running lots of programs at the same time and the browser’s managing several web pages at once. If timing is important, be sure to test on the targeted browser platforms and ensure your program compensates for any lag. For instance, you might need to cut off an animation effect if periodic timestamp queries suggest it’s taking too long.

Real-World Examples

Claude Hussenet’s Portal

Claude Hussenet’s portal (http://claudehussenet.com/; see Figure 7-4) shows various news information, and uses a timer to keep it fresh, as discussed in Periodic Refresh (Chapter 10).

Claude Hussenet’s portal
Figure 7-4. Claude Hussenet’s portal

Google Suggest

Google Suggest (http://www.google.com/webhp?complete=1&hl=en) offers Suggestions from the server as you type a query (Figure 7-5). Instead of submitting the query string upon each keystroke, it uses a timer to limit how many queries are sent per second. The pattern’s described in Submission Throttling (Chapter 10).

Google Suggest
Figure 7-5. Google Suggest

Apple iTunes counter

As iTunes Music Store neared its 500 millionth song download, Apple decorated its homepage (http://apple.com) with a counter that appeared to show the number of downloads in real-time. In reality, it was a Guesstimate. Every few minutes, it would grab the real sales data from the server and estimate how many songs are being sold per second. Between those checkpoints, it would use a timer, combined with the estimated sales rate, to continuously update the counter display.

Backpack

37Signals’ Backpack (http://www.backpackit.com/) maintains items in a list. When data changes, it uses a visual effect known as the “Yellow Fade Technique,” where some text lights up and then fades away. As with most visual effects (see One-Second Spotlight, One-Second Mutation , and One-Second Motion [Chapter 16]), there’s a reliance on timers to coordinate the display across time.

Code Example: AjaxPatterns Basic Wiki

The Periodic Refresh Basic Wiki Demo (http://ajaxify.com/run/wiki) involves a loop to synchronize with the server. The functions to start and stop the loop are encapsulated in their own functions, so as to hide timer details from the rest of the code:

  function startPeriodicSync( ) {
    stopPeriodicSync( );
    syncTimer = setInterval(synchronise, 5000);
  }
  function stopPeriodicSync( ) {
    clearInterval(syncTimer);
  }

How are these used? Upon loading, an initial synchronization is performed, and startPeriodicSync( ) is called to synchronize thereafter. When the user starts typing inside a message, stopPeriodicSync is called, and the loop starts up again when the focus leaves the message area:

  window.onload = function( ) {
    synchronise( );
    startPeriodicSync( );
  }
  function onMessageFocus(event) {
    ...
    stopPeriodicSync( );
  }
  function onMessageBlur(event) {
    ...
    startPeriodicSync( );
  }

Alternatives

HTTP Meta Refresh

The HTTP Meta Refresh tag schedules the browser to load an entire page at some future time. It’s used by conventional news portals, for example, to refresh all content every 15 minutes or so. However, it’s very limited since it accepts no input from the browser and forces a complete page refresh, thus destroying all browser application states.

Metaphor

Alarm clocks perform an action at a specified time, and most also have a repetition capability allowing them to annoy their owners at the same time every day.



[*] Strictly speaking, you can still read all parameter values using the special arguments array.

[*] Find character codes with the Unicode chart at http://www.macchiato.com/unicode/charts.html.

[*] There are some workarounds (http://www.faqts.com/knowledge_base/view.phtml/aid/1602/fid/143), though they’re not really suitable for production.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required