
Action, Change, Click, Control, DHTML, DOM, Events, Keyboard, Mouse, Move, Type, Widget
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".
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.
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 occurred. 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. Cleaner to just declare the button and deal with an event handler in a separate Javascript file. Since we always want this behaviour, we should declare it as soon as the page loads. To run something when the page loads, we can use another event handler, one 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 include 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 which 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 fifteen-line script, online. Dojo Toolkit also supports this behaviour as part of its sophisticated event library.
It's not always enough for the event handler to know that an event has occurred; it 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[4]. 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 "equaliser" 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 whereabouts the mouse was. The various event types are covered in the Decisions.
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 these out for more
info on events: http://www.quirksmode.org/js/events_compinfo.html, http://www.gatescript.com/events.html.
All handler functions accept a single parameter representing the event, and as discussed 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 equalise it (as in the shutdown(ev) examples above).
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 zipcode field. However, doing so is often counter-productive, as users generally perform faster when behaviour 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.
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”.
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, and 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 behaviour and for custom drag-and-drop functionality.
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”.
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 italicise text on wikipedia, you select the text and click the <i> icon, which then wraps mediawiki markup ('') around the selected text.
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-Mousepress action. However, not all attributes are always set, so you need to be careful in testing for portability.
Following are some of the portable, more frequently used, attributes of the event object.
target and srcElement indicate which element the event occurred on. To equalise 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.
type indicates which event type took place, e.g. " click".
A potential code smell, 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.
which and keyCode indicate the Unicode value of the key that was pressed[5]. Not completely consistent across browsers, but easy enough to equalise. 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.
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, 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, generally not advisable as it's not supported by IE, and in any event only available on certain keyboards.
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. As well as the differences above, beware of incompatibilities in handling of one button being depressed while another is already depressed; and also incompatibilities in which events provide this property (sometimes only mouse-clicks; sometimes others).
These indicates position of mouse pointer when the event took place, relative to browser window.
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 co-ordinates.
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 a disabled attribute available for standard controls, but for custom controls you may have created, you might use event redefinition to cancel or re-enable the effects of interacting with the control.
For actions which 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 decide where to route the event to.
Google Reader is a web-based RSS aggregator (Figure 1.16, “Google Reader”). 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 uses a dragging action to pan the map within a “Virtual Workspace”, and the arrow keys can also be used.
37Signals' Backpack 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.
Here are a couple of basic examples from the Ajax demos. The Basic Time Demo handles a button click like this:
$("defaultTime").onclick=requestDefaultTime;
The wiki tracks which 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;
}
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 colour. 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 okay, when a big context switch takes place, but basic updates are best controlled with Javascript.
The "richer form" is richer than static HTML, but less so than Ajax. It involves enhancing a standard form with dynamic behaviour, 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 popup instructions for a form field.
Display manipulation, as discussed in “Display Morphing” and “Page Rearrangement”, is often triggered by User Events.
???, as discussed in “XMLHttpRequest Call” and “IFrame Call”, is often triggered by User Events.