7.1. User ActionAction, Change, Click, Control, DHTML, DOM, Events, Keyboard, Mouse, Move, Type, Widget Figure 7-1. User Action7.1.1. Goal StoryPam 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." 7.1.2. ProblemHow can the application respond to user activity? 7.1.3. Forces
7.1.4. SolutionHandle 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 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 />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 functionsomitting 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. 7.1.5. Decisions7.1.5.1. 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), orif you care about the event detailsinclude the parameter and equalize it (as in the shutdown(ev) examples above).
7.1.5.2. 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:
7.1.5.3. 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:
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. 7.1.6. Real-World Examples7.1.6.1. Google ReaderGoogle 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 shortcutswhen the page contains numerous articles, clicking "j" and "k" will scroll up and down to the previous or next story. Figure 7-2. Google Reader7.1.6.2. Google MapsGoogle 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. 7.1.6.3. Backpack37Signals' 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. 7.1.7. Code Example: Basic AjaxPatterns DemosHere 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; } 7.1.8. Alternatives7.1.8.1. "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. 7.1.8.2. Richer formsThe "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. 7.1.9. Related Patterns7.1.9.1. Display Morphing, Page RearrangementDisplay manipulation, as discussed in Display Morphing and Page Rearrangement (Chapter 5), is often triggered by User Events. 7.1.9.2. XMLHttpRequest Call, IFrame CallWeb remoting, as discussed in XMLHttpRequest Call and IFrame Call (Chapter 6), is often triggered by User Actions. |