17.2. Advanced Event Handling with DOM Level 2The event-handling techniques presented so far in this chapter are part of DOM Level 0, the de facto standard API that is supported by every JavaScript-enabled browser. DOM Level 2 defines an advanced event-handling API that is significantly different (and quite a bit more powerful) than the Level 0 API. The Level 2 standard does not incorporate the existing API into the standard DOM, but there is no danger of the Level 0 API being dropped. For basic event-handling tasks, you should feel free to continue to use the simple API. The DOM Level 2 event model is supported by all modern browsers except Internet Explorer. 17.2.1. Event PropagationIn the Level 0 event model, the browser dispatches events to the document elements on which they occur. If that object has an appropriate event handler, that handler is run. There is nothing more to it. The situation is more complex in DOM Level 2. In this advanced event model, when an event occurs on a document element (known as the event target), the target's event handler or handlers are triggered, but in addition, each of the target's ancestor elements has one or two opportunities to handle that event. Event propagation proceeds in three phases. First, during the capturing phase, events propagate from the Document object down through the document tree to the target node. If any of the ancestors of the target (but not the target itself ) has a specially registered capturing event handler, those handlers are run during this phase of event propagation. (You'll see how both regular and capturing event handlers are registered shortly.) The next phase of event propagation occurs at the target node itself: any appropriate event handlers registered directly on the target are run. This is akin to the kind of event handling provided by the Level 0 event model. The third phase of event propagation is the bubbling phase, in which the event propagates or bubbles back up the document hierarchy from the target element up to the Document object. Although all events are subject to the capturing phase of event propagation, not all types of events bubble: for example, it does not make sense for a submit event to propagate up the document beyond the <form> element to which it is directed. On the other hand, generic events such as mousedown events can be of interest to any element in the document, so they do bubble up through the document hierarchy, triggering any appropriate event handlers on each of the ancestors of the target element. In general, raw input events bubble while higher-level semantic events do not. (See Table 17-3 later in this chapter for a definitive list of which events bubble and which do not.) During event propagation, it is possible for any event handler to stop further propagation of the event by calling the stopPropagation( ) method of the Event object that represents the event. The Event object and its stopPropagation( ) method are discussed further later in this chapter. Some events cause an associated default action to be performed by the web browser. For example, when a click event occurs on an <a> tag, the browser's default action is to follow the hyperlink. Default actions like these are performed only after all three phases of event propagation complete, and any of the handlers invoked during event propagation can prevent the default action from occurring by calling the preventDefault( ) method of the Event object. Although this kind of event propagation may seem convoluted, it can help you centralize your event-handling code. DOM Level 1 exposes all document elements and allows events (such as mouseover events) to occur on any of those elements. This means that there are many, many more places for event handlers to be registered than there were with the old Level 0 event model. Suppose you want to trigger an event handler whenever the user moves the mouse over a <p> element in your document. Instead of registering an onmouseover event handler for each <p> tag, you can instead register a single event handler on the Document object and handle these events during either the capturing or bubbling phase of event propagation. There is one other important detail about event propagation. In the Level 0 model, you can register only a single event handler for a particular type of event for a particular object. In the Level 2 model, however, you can register any number of handler functions for a particular event type on a particular object. This applies also to ancestors of an event target whose handler function or functions are invoked during the capturing or bubbling phases of event propagation. 17.2.2. Event Handler RegistrationIn the Level 0 API, you register an event handler by setting an attribute in your HTML or an object property in your JavaScript code. In the Level 2 event model, you register an event handler for a particular element by calling the addEventListener( ) method of that object. (The DOM standard uses the term listener in its API, but I'll continue to use the synonymous word handler in this discussion.) This method takes three arguments. The first is the name of the event type for which the handler is being registered. The event type should be a string that contains the lowercase name of the HTML handler attribute, with the leading "on" removed. Thus, if you use an onmousedown HTML attribute or onmousedown property in the Level 0 model, you'll use the string "mousedown" in the Level 2 event model. The second argument to addEventListener( ) is the handler (or listener) function that should be invoked when the specified type of event occurs. When your function is invoked, it is passed an Event object as its only argument. This object contains details about the event (such as which mouse button was pressed) and defines methods such as stopPropagation( ). The Event interface and its subinterfaces are discussed further later in this chapter. The final argument to addEventListener( ) is a boolean value. If true, the specified event handler captures events during the capturing phase of event propagation. If the argument is false, the event handler is a normal event handler and is triggered when the event occurs directly on the object or on a descendant of the element and subsequently bubbles up to the element. For example, you might use addEventListener( ) as follows to register a handler for submit events on a <form> element: document.myform.addEventListener("submit", function(e) {return validate(e.target); } false); If you wanted to capture all mousedown events that occur within a particular named <div> element, you might use addEventListener( ) like this: var mydiv = document.getElementById("mydiv"); mydiv.addEventListener("mousedown", handleMouseDown, true); Note that these examples assume that you've defined functions named validate( ) and handleMouseDown( ) elsewhere in your JavaScript code. Event handlers registered with addEventListener( ) are executed in the scope in which they are defined. They are not invoked with the augmented scope chain described in Section 17.1.6. Because event handlers are registered in the Level 2 model by invoking a method rather than by setting an attribute or property, you can register more than one event handler for a given type of event on a given object. If you call addEventListener( ) multiple times to register more than one handler function for the same event type on the same object, all the functions you've registered are invoked when an event of that type occurs on (or bubbles up to, or is captured by) that object. It is important to understand that the DOM standard makes no guarantees about the order in which the handler functions of a single object are invoked, so you should not rely on them being called in the order in which you registered them. Also note that if you register the same handler function more than once on the same element, all registrations after the first are ignored. Why would you want to have more than one handler function for the same event on the same object? This can be quite useful for modularizing your software. Suppose, for example, that you've written a reusable module of JavaScript code that uses mouseover events on images to perform image rollovers. Now suppose that you have another module that wants to use the same mouseover events to display additional information about the image in a DHTML pop up or tool tip. With the Level 0 API, you'd have to merge your two modules into one so that they could share the single onmouseover property of the Image object. With the Level 2 API, on the other hand, each module can register the event handler it needs without knowing about or interfering with the other module. addEventListener( ) is paired with a removeEventListener( ) method that expects the same three arguments but removes an event-handler function from an object rather than adding it. It is often useful to temporarily register an event handler and then remove it soon afterward. For example, when you get a mousedown event, you might register temporary capturing event handlers for mousemove and mouseup events so that you can see if the user drags the mouse. You'd then deregister these handlers when the mouseup event arrives. In such a situation, your event-handler removal code might look as follows: document.removeEventListener("mousemove", handleMouseMove, true); document.removeEventListener("mouseup", handleMouseUp, true); Both the addEventListener( ) and removeEventListener( ) methods are defined by the EventTarget interface. In web browsers that support the DOM Level 2 Events module, Element and Document nodes implement this interface and provide these event-registration methods.[*] Part IV documents these methods under Document and Element and does not have an entry for the EventTarget interface itself.
17.2.3. addEventListener( ) and the this KeywordIn the original Level 0 event model, when a function is registered as an event handler for a document element, it becomes a method of that document element (as discussed previously in Section 17.1.5.). When the event handler is invoked, it is invoked as a method of the element, and, within the function, the this keyword refers to the element on which the event occurred. DOM Level 2 is written in a language-independent fashion and specifies that event listeners are objects rather than simple functions. The JavaScript binding of the DOM makes JavaScript functions event handlers instead of requiring the use of a JavaScript object. Unfortunately, the binding does not actually say how the handler function is invoked and does not specify the value of the this keyword. Despite the lack of standardization, all known implementations invoke handlers registered with addEventListener( ) as if they were methods of the target element. That is, when the handler is invoked, the this keyword refers to the object on which the handler was registered. If you prefer not to rely on this unspecified behavior, you can use the currentTarget property of the Event object that is passed to your handler functions. As you'll see when the Event object is discussed later in this chapter, the currentTarget property refers to the object on which the event handler was registered. 17.2.4. Registering Objects as Event HandlersaddEventListener( ) allows you to register event-handler functions. For object-oriented programming, you may prefer to define event handlers as methods of a custom object and then have them invoked as methods of that object. For Java programmers, the DOM standard allows exactly this: it specifies that event handlers are objects that implement the EventListener interface and a method named handleEvent( ). In Java, when you register an event handler, you pass an object to addEventListener( ), not a function. For simplicity, the JavaScript binding of the DOM API does not require you to implement an EventListener interface and instead allows you to pass function references directly to addEventListener( ). However, if you are writing an object-oriented JavaScript program and prefer to use objects as event handlers, you might use a function like this to register them: function registerObjectEventHandler(element, eventtype, listener, captures) { element.addEventListener(eventtype, function(event) { listener.handleEvent(event); } captures); } Any object can be registered as an event listener with this function, as long as it defines a method named handleEvent( ). That method is invoked as a method of the listener object, and the this keyword refers to the listener object, not to the document element that generated the event. Although it is not part of the DOM specification, Firefox (and other browsers built on the Mozilla codebase) allow event listener objects that define a handleEvent( ) method to be passed directly to addEventListener( ) instead of a function. For these browsers, a special registration function like the one just shown is not necessary. 17.2.5. Event Modules and Event TypesAs I've noted before, DOM Level 2 is modularized, so an implementation can support parts of it and omit support for other parts. The Events API is one such module. You can test whether a browser supports this module with code like this: document.implementation.hasFeature("Events", "2.0") The Events module contains only the API for the basic event-handling infrastructure, however. Support for specific types of events is delegated to submodules. Each submodule provides support for a category of related event types and defines an Event type that is passed to event handlers for each of those types. For example, the submodule named MouseEvents provides support for mousedown, mouseup, click, and related event types. It also defines the MouseEvent interface. An object that implements this interface is passed to the handler function for any event type supported by the module. Table 17-2 lists each event module, the event interface it defines, and the types of events it supports. Note that DOM Level 2 does not standardize any type of keyboard event, so no module of key events is listed here. Current browsers do support key events, however, and you'll learn more about them later in this chapter. Table 17-2, and the rest of this book, omit coverage for the MutationEvents module. Mutation events are triggered when the structure of a document is changed. They are useful for applications such as HTML editors but are not commonly implemented by web browsers or used by web programmers.
As you can see from Table 17-2, the HTMLEvents and MouseEvents modules define event types that are familiar from the Level 0 event module. The UIEvents module defines event types that are similar to the focus, blur, and click events supported by HTML form elements but are generalized so that they can be generated by any document element that can receive focus or be activated in some way. As I noted earlier, when an event occurs, its handler is passed an object that implements the Event interface associated with that type of event. The properties of this object provide details about the event that may be useful to the handler. Table 17-3 lists the standard events again, but this time organizes them by event type rather than by event module. For each event type, this table specifies the kind of event object that is passed to its handler, whether this type of event bubbles up the document hierarchy during event propagation (the "B" column), and whether the event has a default action that can be canceled with the preventDefault( ) method (the "C" column). For events in the HTMLEvents module, the fifth column of the table specifies which HTML elements can generate the event. For all other event types, the fifth column specifies which properties of the event object contain meaningful event details (these properties are documented in the next section). Note that the properties listed in this column do not include the properties that are defined by the basic Event interface, which contain meaningful values for all event types.
It is useful to compare Table 17-3 with Table 17-1, which lists the Level 0 event handlers defined by HTML 4. The event types supported by the two models are largely the same (excluding the UIEvents module). The DOM Level 2 standard adds support for the abort, error, resize, and scroll event types that were not standardized by HTML 4, but it does not support the key events or dblclick event that are part of the HTML 4 standard. (Instead, as you'll see shortly, the detail property of the object passed to a click event handler specifies the number of consecutive clicks that have occurred.) 17.2.6. Event Interfaces and Event DetailsWhen an event occurs, the DOM Level 2 API provides additional details about the event (such as when and where it occurred) as properties of an object that is passed to the event handler. Each event module has an associated event interface that specifies details appropriate to that type of event. Table 17-2 listed three different event modules and three different event interfaces. These three interfaces are actually related to one another and form a hierarchy. The Event interface is the root of the hierarchy; all event objects implement this most basic event interface. UIEvent is a subinterface of Event: any event object that implements UIEvent also implements all the methods and properties of Event. The MouseEvent interface is a subinterface of UIEvent. This means, for example, that the event object passed to an event handler for a click event implements all the methods and properties defined by each of the MouseEvent, UIEvent, and Event interfaces. The following sections introduce each event interface and highlight their most important properties and methods. You will find complete details about each interface in Part IV. 17.2.6.1. EventThe event types defined by the HTMLEvents module use the Event interface. All other event types use subinterfaces of this interface, which means that Event is implemented by all event objects and provides detailed information that applies to all event types. The Event interface defines the following properties (note that these properties, and the properties of all Event subinterfaces, are read-only):
In addition to these seven properties, the Event interface defines two methods that are also implemented by all event objects: stopPropagation( ) and preventDefault( ). Any event handler can call stopPropagation( ) to prevent the event from being propagated beyond the node at which it is currently being handled. Any event handler can call preventDefault( ) to prevent the browser from performing a default action associated with the event. Calling preventDefault( ) in the DOM Level 2 API is like returning false in the Level 0 event model. 17.2.6.2. UIEventThe UIEvent interface is a subinterface of Event. It defines the type of event object passed to events of type DOMFocusIn, DOMFocusOut, and DOMActivate. These event types are not commonly used; what is more important about the UIEvent interface is that it is the parent interface of MouseEvent. UIEvent defines two properties in addition to those defined by Event:
17.2.6.3. MouseEventThe MouseEvent interface inherits the properties and methods of Event and UIEvent, and defines the following additional properties:
17.2.7. Mixing Event ModelsSo far, I've discussed the traditional Level 0 event model and the new standard DOM Level 2 model. For backward compatibility, browsers that support the Level 2 model will continue to support the Level 0 event model. This means that you can mix event models within a document. It is important to understand that web browsers that support the Level 2 event model always pass an event object to event handlerseven handlers registered by setting an HTML attribute or a JavaScript property using the Level 0 model. When an event handler is defined as an HTML attribute, it is implicitly converted to a function that has an argument named event. This means that such an event handler can use the identifier event to refer to the event object. (You'll see later that using the identifier event in an HTML attribute is also compatible with the IE event model.) The DOM standard recognizes that the Level 0 event model will remain in use and specifies that implementations that support the Level 0 model treat handlers registered with that model as if they were registered using addEventListener( ). That is, if you assign a function f to the onclick property of a document element e (or set the corresponding HTML onclick attribute), it is equivalent to registering that function as follows: e.addEventListener("click", f, false); When f is invoked, it is passed an event object as its argument, even though it was registered using the Level 0 model. |