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
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
andonkeydown
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
andondblclick
indicate a button has been clicked or double-clicked.onmousedown
andonmouseup
indicate a button has been depressed or released. These latter events are more fine-grained than clicking, which implies the sequence ofmousedown
followed bymouseup
has completed, both on the same element (note that click won’t fire if the user releases the mouse button over a different element). Thebutton
control is specifically geared for catchingclick
events to let the user do something, andradiobutton
s andcheckboxes
can also be associated with click listeners to indicate changes.onmousedown
andonmouseup
can be used for panning behavior and for custom drag-and-drop functionality.
- Mouse movement—onmouseover, onmouseout
onmouseover
andonmouseout
indicate the mouse has just moved over, or has just left, an element. It can be useful to keep apointerElement
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 toonblur
. Unlikeonblur
, 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
andsrcElement
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
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
andkeyCode
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
, andshiftKey
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( )
andstop( )
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 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
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).
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).
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.
Get Ajax Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.