The DOM2 Event model specification (http://www.w3.org/TR/DOM-Level-2-Events/) describes a standard way to create, capture, handle, and cancel events in a tree-like structure such as an (X)HTML document’s object hierarchy. It also describes event propagation behavior, that is, how an event arrives at its target and what happens to it afterward.
The DOM2 approach to events accommodates the basic event model and marries important concepts from the proprietary models. This essentially means that the basic event model works exactly as advertised in a DOM2-supporting browser. Also, everything that you can do in Netscape 4 and Internet Explorer you can do in a DOM2 browser, but the syntax is different.
The hybridization of the proprietary models is evident in how events propagate in a DOM2-supporting browser. Events begin their lifecycle at the top of the hierarchy (at the Document) and make their way down through containing objects to the target. This is known as the capture phase because it mimics the behavior of Netscape 4. During its descent, an event may be pre-processed, handled, or redirected by any intervening object. Once the event reaches its target and the handler there has executed, the event proceeds back up the hierarchy to the top. This is known as the bubbling phase because of its obvious connections to the model of Internet Explorer 4+.
Mozilla-based browsers were the first major browsers to implement the DOM2 Events standard. These browsers include Mozilla itself, Netscape 6+, Firefox, Camino, and others. Opera 7 has nearly complete support, as does Safari (a popular MacOS browser). In fact, most browsers (with the exception of those from Microsoft) support or will soon support DOM2 Events. This is as it should be; uniformity of interface is the reason we have standards in the first place.
The fly in the ointment is that, as of version 6, Internet Explorer doesn’t support DOM2 Events. Microsoft does not appear to have plans to add support in the near future, and it’s unclear whether they plan to in the medium- or long-term. So, unfortunately, Web programmers aren’t likely to be free of the headache of cross-browser scripting for events any time soon and should be wary of focusing solely on the DOM2 Event specification.
The easiest way to bind event handlers to elements under DOM Level 2 is to use the (X)HTML attributes like onclick that you should be familiar with by now. Nothing changes for DOM2-supporting browsers when you bind events in this way, except that only support for events in the (X)HTML standard is guaranteed (though some browsers support more events).
Because there is no official DOM2 way for script in the text of event handler attributes to access an Event object, the preferred binding technique is to use JavaScript. The same syntax is used as with the basic event model:
<<p id="myElement">>Click on me<</p>> <<p>>Not on me<</p>> <<script type="text/javascript">> <<!-- function handleClick(e) { alert("Got a click: " + e); // IE5&6 will show an undefined in alert since they are not DOM2 } document.getElementById("myElement").onclick = handleClick; //-->> <</script>>
Notice in this example how the handler accepts an argument. DOM2 browsers pass an Event object containing extra information about the event to handlers. The name of the argument is arbitrary, but “event,” “e,” and “evt” are most commonly used. We’ll discuss the Event object in more detail in an upcoming section.
You can also use the new addEventListener() method introduced by DOM2 to engage an event handler in a page. There are three reasons you might wish to use this function instead of directly setting an object’s event handler property. The first is that it enables you to bind multiple handlers to an object for the same event. When handlers are bound in this fashion, each handler is invoked when the specified event occurs, though the order in which they are invoked is arbitrary. The second reason to use addEventListener() is that it enables you to handle events during the capture phase (when an event “trickles down” to its target). Event handlers bound to event handler attributes like onclick and onsubmit are only invoked during the bubbling phase. The third reason is that this method enables you to bind handlers to text nodes, an impossible task prior to DOM2.
The syntax of the addEventListener() method is as follows:
object.addEventListener(“event“, handler, capturePhase);
object is the node to which the listener is to be bound.
"event" is a string indicating the event it is to listen for.
handler is the function that should be invoked when the event occurs.
capturePhase is a Boolean indicating whether the handler should be invoked during the capture phase (true) or bubbling phase (false).
For example, to register a function changeColor() as the capture-phase mouseover handler for a paragraph with id of myText you might write
To add a bubble phase handler swapImage():
document.getElementById('myText').addEventListener("mouseover", swapImage, false);
Handlers are removed using removeEventListener() with the same arguments as given when the event was added. So to remove the first handler in the previous example (but keep the second) you would invoke
document.getElementById('myText').removeEventListener("mouseover", changeColor, true);
We’ll see some specific examples using the addEventListener() later on in the chapter.
As previously mentioned, browsers supporting DOM2 Events pass an Event object as an argument to handlers. This object contains extra information about the event that occurred, and is in fact quite similar to the Event objects of the proprietary models. The exact properties of this object depend on the event that occurred, but all Event objects have the read-only properties listed in Table 11-10.
Read-Only Property |
Description |
---|---|
>bubbles |
Boolean indicating whether the event bubbles |
>cancelable |
Boolean indicating whether the event can be canceled |
>currentTarget |
Node whose handler is currently executing (i.e., the node the handler |
>eventPhase |
Numeric value indicating the current phase of the event flow (1 for capture, 2 if at the target, 3 for bubble) |
>type |
String indicating the type of the event (such as "click") |
>target |
Node to which the event was originally dispatched (i.e., the node at which the event occurred) |
Note |
You can use the symbolic constants Event.CAPTURING_PHASE, Event.AT_TARGET, and Event.BUBBLING_PHASE instead of the numeric values 1, 2, and 3 when examining the eventPhase property. |
We list the properties specific to each event in the following sections as we discuss the different kinds of events DOM2-supporting browsers enable you to handle.
The mouse events defined by DOM2 are those from (X)HTML. They’re listed in Table 11-11. Since, under DOM2, not all events include a bubbling phase and all default actions can be canceled, Table 11-11 also lists these behaviors for each event.
Event |
Bubbles? |
Cancelable? |
---|---|---|
click |
Yes |
Yes |
mousedown |
Yes |
Yes |
mouseup |
Yes |
Yes |
mouseover |
Yes |
Yes |
mousemove |
Yes |
No |
mouseout |
Yes |
Yes |
When a mouse event occurs, the browser fills the Event object with the extra information shown in Table 11-12.
Property |
Description |
---|---|
>altKey |
Boolean indicating if the alt key was depressed |
>button |
Numeric value indicating which mouse button was used (typically 0 for left, |
>clientX |
Horizontal coordinate of the event relative to the browser's content pane |
>clientY |
Vertical coordinate of the event relative to the browser's content pane |
>ctrlKey |
Boolean indicating if the ctrl key was depressed during event |
>detail |
Indicating the number of times the mouse button was clicked (if at all) |
>metaKey |
Boolean indicating if the meta key was depressed during event |
>relatedTarget |
Reference to a node related to the event—for example, on a mouseover it references the node the mouse is leaving; on mouseout it references the node to which the mouse is moving |
>screenX |
Horizontal coordinate of the event relative to the whole screen |
>screenY |
Vertical coordinate of the event relative to the whole screen |
>shiftKey |
Boolean indicating if the shift key was depressed during event |
The following example illustrates their use:
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>DOM2 Mouse Events<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<body>> <<h2>>DOM2 Mouse Events<</h2>> <<form id="mouseform" name="mouseform" action="#" method="get">> Alt Key down? <<input id="altkey" type="text" />><<br />> Control Key down? <<input id="controlkey" type="text" />><<br />> Meta Key down? <<input id="metakey" type="text" />><<br />> Shift Key down? <<input id="shiftkey" type="text" />><<br />> Browser coordinates of click: <<input id="clientx" type="text" />>, <<input id="clienty" type="text" />> <<br />> Screen coordinates of click: <<input id="screenx" type="text" />>, <<input id="screeny" type="text" />> <<br />> Button used: <<input id="buttonused" type="text" />><<br />><<br />> <</form>> <<hr />> Click anywhere on the document... <<script type="text/javascript">> <<!-- function showMouseDetails(event) { var theForm = document.mouseform; theForm.altkey.value = event.altKey; theForm.controlkey.value = event.ctrlKey; theForm.shiftkey.value = event.shiftKey; theForm.metakey.value = event.metaKey; theForm.clientx.value = event.clientX; theForm.clienty.value = event.clientY; theForm.screenx.value = event.screenX; theForm.screeny.value = event.screenY; if (event.button == 0) theForm.buttonused.value = "left"; else if (event.button == 1) theForm.buttonused.value = "middle"; else theForm.buttonused.value = "right"; } document.addEventListener("click", showMouseDetails, true); //-->> <</script>> <</body>> <</html>>
The result of a click is shown in Figure 11-3.
Surprisingly, DOM Level 2 does not define keyboard events. They will be specified in a future standard, the genesis of which you can see in DOM Level 3. Fortunately, because (X)HTML allows keyup, keydown, and keypress events for many elements, you’ll find that browsers support them. Furthermore, with IE dominating the picture, you still have that event model to fall back on. Table 11-13 lists the keyboard-related events for DOM2-compliant browsers, as well as their behaviors.
Event |
Bubbles? |
Cancelable? |
---|---|---|
keyup |
Yes |
Yes |
keydown |
Yes |
Yes |
keypress |
Yes |
Yes |
The Mozilla-specific key-related properties of the Event object are listed in Table 11-14.
Property |
Description |
---|---|
>altKey |
Boolean indicating if the alt key was depressed |
>charCode |
For printable characters, a numeric value indicating the Unicode value of the key depressed |
>ctrlKey |
Boolean indicating if the ctrl key was depressed during event |
>isChar |
Boolean indicating whether the keypress produced a character (useful because some key sequences such as ctrl-alt do not) |
>keyCode |
For non-printable characters, a numeric value indicating the Unicode value of the key depressed |
>metaKey |
Boolean indicating if the meta key was depressed during event |
>shiftKey |
Boolean indicating if the shift key was depressed during event |
DOM2 browsers support the familiar browser and form-related events found in all major browsers. The list of these events is found in Table 11-15.
Event |
Bubbles? |
Cancelable? |
---|---|---|
load |
No |
No |
unload |
No |
No |
abort |
Yes |
No |
error |
Yes |
No |
select |
Yes |
No |
change |
Yes |
No |
submit |
Yes |
Yes |
reset |
Yes |
No |
focus |
No |
No |
Blur |
No |
No |
resize |
Yes |
No |
scroll |
Yes |
No |
Although DOM Level 2 builds primarily on those events found in the (X)HTML specification (and DOM Level 0), it adds a few new User Interface (UI) events to round out the field. These events are prefixed with “DOM” to distinguish them from “normal” events. These events are listed in Table 11-16.
Event |
Bubbles? |
Cancelable? |
---|---|---|
DOMFocusIn |
Yes |
No |
DOMFocusOut |
Yes |
No |
DOMActivate |
Yes |
Yes |
The need for and meaning of these events is not necessarily obvious. DOMFocusIn and DOMFocusOut are very similar to the traditional focus and blur events, but can be applied to any element, not just form fields. The DOMActivate event is fired when an object is receiving activity from the user. For example, it fires on a link when it is clicked and on a select menu when the user activates the pull-down menu. This event is useful when you don’t care how the user invokes the element’s functionality, just that it is being used. For example, instead of using both an onclick and onkeypress handler to trap link activation (via the mouse or keyboard) you could register to receive the DOMActivate event. While these new events are rarely used, it is helpful to be aware of them should you encounter them in new scripts.
Because of the capabilities for dynamic modification of the document object hierarchy found in DOM-compliant browsers, DOM2 includes events to detect structural and logical changes to the document. These events, which are known as mutation events because they occur when the document hierarchy changes, are only briefly mentioned here. They require a detailed description of the mutation event interface to use effectively and actually aren’t supported in any major browser at the time of this edition’s writing. These events are listed in Table 11-17. For complete details on mutation events, see the W3C DOM2 event specification at http://www.w3.org/TR/DOM-Level-2-Events/.
Event |
Bubbles? |
Cancelable? |
Description |
---|---|---|---|
DOMSubtreeModified |
Yes |
No |
Implementation-dependent; fires when a portion of the node's subtree has been modified |
DOMNodeInserted |
Yes |
No |
Fires on a node inserted as the child of another node |
DOMNodeRemoved |
Yes |
No |
Fires on a node that has been removed from its parent |
DOMNodeRemovedFromDocument |
No |
No |
Fires on a node when it is about to be removed from the document |
DOMNodeInsertedIntoDocument |
No |
No |
Fires on a node when it has been inserted into the document |
DOMAttrModified |
Yes |
No |
Fires on a node when one of its attributes has been modified |
DOMCharacterDataModified |
Yes |
No |
Fires on a node when the data it contains is modified |
As with more traditional models, DOM Level 2 allows you to cancel the default action associated with an event by returning false from a handler. It also provides the preventDefault() method of Event objects. If, at any time during an Event’s lifetime, a handler calls preventDefault(), the default action for the event is canceled. This is an important point: if preventDefault() is ever called on an event, its default action will be canceled; even other handlers returning true cannot cause the default action to proceed.
The following simple example prevents clicks anywhere in the document from having their intended effect.
Try clicking <<a href="/">>this link<</a>>. <<script type="text/javascript">> <<!-- // DOM 2 browsers only, no IE6 support function killClicks(event) { event.preventDefault(); } document.addEventListener("click", killClicks, true); // -->> <</script>>
It’s important to remember that canceling an event’s default action does not stop the event from continuing on its voyage through the document object hierarchy. Consider the following script, similar to the last example, except this time with an onclick handler defined for the link:
Try clicking <<a id="mylink" href="/">>this link<</a>>. <<script type="text/javascript">> <<!-- // DOM 2 browsers only, no IE6 support function killClicks(event) { event.preventDefault(); } document.addEventListener("click", killClicks, true); document.getElementById("mylink").onclick = function() { alert("A click event got through to the link node"); } //-->> <</script>>
When the link is clicked, its default action is canceled by killClick(), but as you can see in Figure 11-4, the click event still makes it to the link. This illustrates the fact that event propagation through the document object hierarchy is independent of whether the event’s default action has been canceled.
As mentioned in the beginning of this section, events in DOM2-supporting browsers begin at the Document and make their way “down” through the containment hierarchy to their target (the object corresponding to the element at which the event is occurring). During this phase, any intervening objects with handlers for the event type that have registered to receive events in the capture phase will be invoked. When the event reaches its target and any handlers at the target have had a chance to run, the event makes its way back “up” the hierarchy to where it began, the Document. During this phase, any intervening objects with handlers for the event type that have registered to receive events in the bubbling phase will be invoked, including any handlers bound using (X)HTML attributes.
Listening for events in the capture and bubbling phases can be tricky business because of the parent-child relationship of nodes in the DOM. A handler will be invoked for an event only if the event is targeted for a node that is in the subtree rooted at the node to which the listener is attached. Because containment relationships for different parts of the page often change, many programmers find it convenient to capture events at a major object they know will contain the objects of interest, for example, at the Document or Form level.
If, at any point during an Event’s lifetime a handler invokes its stopPropagation() method, the event ceases its motion through the document object hierarchy and expires after all handlers bound to the current object have executed. That is, when stopPropagation() is called, the only handlers that will further be invoked are those bound to the current object.
If we add a call to stopPropagation() to our previous example, we can prevent the onclick handler of the link from being executed:
Try clicking <<a id="mylink" href="/">> this link<</a>>. <<script type="text/javascript">> <<!-- // DOM 2 browsers only, no IE6 support function killClicks(event) { event.preventDefault(); event.stopPropagation(); } document.addEventListener("click", killClicks, true); document.getElementById("mylink").onclick = function() { alert("A click event got through to the link node"); } //-->> <</script>>
The killClick handler is registered as a listener in the capture phase, so it is executed while the event is on its way down to the link. It prevents clicks from doing what they normally do, and then signals that no further processing of the event should be carried out by invoking stopPropagation().
Note |
Keep in mind that not all events under DOM2 are cancelable, and calling preventDefault() for one of these events has no effect. |
Every node has a dispatchEvent() method that can be invoked to redirect an event to that node. This method takes an Event as an argument and returns false if any handler processing the event invokes preventDefault() or returns false. For example, suppose you wanted to route events to an element with id of “eventprocessor.” You might use
function routeClick(event) { var rv = document.getElementById("eventprocessor").dispatchEvent(event); if (rv) alert("Event processor canceled default behavior"); else alert("Event processor permitted default behavior"); }
Functions like this would let you bind your event handling functions to a single object implementing centralized event management routines, an object that wouldn’t have to contain all the elements for which it was managing events.
When redirecting an event in this manner, the node on which dispatchEvent() is invoked becomes the new event target. The browser pretends that the event actually occurred there. This means that it sends the event along the normal flow from Document down to this new target and back up again. For this reason, you need to be careful to avoid infinite loops caused by routing events to a target for which the dispatching handler is listening. Doing so sends the event in endless circles between the Document and the handler, and can quickly crash the browser.
The last DOM 2 Event topic we mention is not often used nor implemented in browsers, but is interesting nonetheless—event creation. The DOM2 Event specification allows for synthetic events to be created by the user using document.createEvent(). You first create the type of event you want, say an HTML-related event:
evt = document.createEvent("HTMLEvents");
Then once your event is created you pass it various attributes related to the event type. Here, for example, we pass the type of event "click" and Boolean values indicating it is bubble-able and cancelable:
evt.initEvent("click","true","true");
Finally, we find a node in the document tree and dispatch the event to it:
currentNode.dispatchEvent(evt);
The event then is triggered and reacts as any other event.
The following example shows DOM2 event creation in action and allows you to
move around the tree and fire clicks at various locations. The addEventListener() and removeEventListener() are added into the example so you do not have to observe click events until you are ready.
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>DOM2 Event Creation<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<body>> <<h2>>DOM2 Event Creation<</h2>> <<form id="mouseform" name="mouseform" action="#" method="get">> Browser coordinates of click: <<input id="clientx" type="text" />>, <<input id="clienty" type="text" />> <<br />> <</form>> <<br />><<hr />><<br />> <<script type="text/javascript">> <<!-- // DOM 2 Only - no IE6 support function showMouseDetails(event) { document.mouseform.clientx.value = event.clientX; document.mouseform.clienty.value = event.clientY; } function makeEvent() { evt = document.createEvent("HTMLEvents"); evt.initEvent("click","true","true"); currentNode.dispatchEvent(evt); } function startListen() { document.addEventListener("click", showMouseDetails, true); } function stopListen() { document.removeEventListener("click", showMouseDetails, true); } startListen(); //-->> <</script>> <<form action="#" method="get" id="myForm" name="myForm">> Current Node: <<input type="text" name="statusField" value="" />> <<br />> <<input type="button" value="parent" onclick="if (currentNode.parentNode) currentNode = currentNode.parentNode; document.myForm.statusField.value = currentNode.nodeName;" />> <<input type="button" value="First Child" onclick="if (currentNode.firstChild) currentNode = currentNode.firstChild; document.myForm.statusField.value = currentNode.nodeName;" />> <<input type="button" value="Next Sibling" onclick="if (currentNode.nextSibling) currentNode = currentNode.nextSibling; document.myForm.statusField.value = currentNode.nodeName;" />> <<input type="button" value="Previous Sibling" onclick="if (currentNode.previousSibling) currentNode = currentNode.previousSibling; document.myForm.statusField.value = currentNode.nodeName;" />> <<br />><<br />> <<input type="button" value="Start Event Listener" onclick="startListen();" />> <<input type="button" value="Stop Event Listener" onclick="stopListen();" />> <<br />><<br />> <<input type="button" value="Create Event" onclick="makeEvent();" />> <</form>> <<script type="text/javascript">> <<!-- var currentNode = document.body; document.myForm.statusField.value = currentNode.nodeName; //-->> <</script>> <</body>> <</html>>
There are a number of details to DOM2 Event creation that we forgo primarily because it is not widely implemented in browsers. In fact with much of this section it should always be kept in mind that the DOM2 currently is probably not the best approach to making events work across browsers since it is not supported by Internet Explorer. We review the sorry state of affairs for event support in browsers briefly now.