Chapter 19. Events and Event Handling
As we saw in Chapter 12, interactive JavaScript programs use an
event-driven programming model. In this style of programming, the web
browser generates an event whenever something
interesting happens to the document or to some element of it. For
example, the web browser generates an event when it finishes loading
a document, when the user moves the mouse over a hyperlink, or when
the user clicks on the Submit button
of a form. If a JavaScript application cares about a particular type
of event for a particular document element, it can register an
event handler -- a JavaScript function or snippet
of code -- for that type of event on the element of interest.
Then, when that particular event occurs, the browser invokes the
handler code. All applications with graphical user interfaces are
designed this way: they sit around waiting for the user to do
something interesting (i.e., they wait for events to occur) and then
they respond.
As an aside, it is worth noting that timers and error handlers (both
of which are described in Chapter 13) are related
to the event-driven programming model. Like the event handlers
described in this chapter, timers and error handlers work by
registering a function with the browser and allowing the browser to
call that function when the appropriate event occurs. In these cases,
however, the event of interest is the passage of a specified amount
of time or the occurrence of a JavaScript error. Although timers and
error handlers are not discussed in this chapter, it is useful to
think of them as related to event handling, and I encourage you to
reread Section 13.4, and Section 13.5, in the context of this
chapter.
Most nontrivial JavaScript programs rely heavily on event handlers.
We've already seen a number of JavaScript examples that use
simple event handlers. This chapter fills in all the missing details
about events and event handling. Unfortunately, these details are
more complex than they ought to be, because there are four distinct
and incompatible event-handling models in use. These
models are:
The original event model. This is the simple event-handling scheme that
we've used (but not thoroughly documented) so far in this book.
It was codified, to a limited extent, by the HTML 4 standard, and is
informally considered to be part of the DOM Level 0 API. Although its
features are limited, it is supported by all JavaScript-enabled web
browsers and is therefore portable.
The standard event model. This powerful and full-featured event model was standardized by the DOM
Level 2 standard. It is supported by the Netscape 6 and Mozilla
browsers.
The Internet Explorer event model. This event model is implemented by IE 4 and
later and has some, but not all, of the advanced features of the
standard event model. Although Microsoft participated in the creation
of the DOM Level 2 event model and had plenty of time to implement
this standard event model in IE 5.5 and IE 6, they have stuck with
their proprietary event model instead. This means that JavaScript
programmers who want to used advanced event-handling features must
write special code for IE browsers.
The Netscape 4 event model. This event model
was implemented in Netscape 4 and continues to be (mostly, but not
fully) supported in Netscape 6, although it has been superseded by
the standard event model. It has some, but not all, of the advanced
features of the standard event model. JavaScript programmers who want
to use advanced event-handling features and retain compatibility with
Netscape 4 need to understand this model.
The rest of this chapter documents each of these event models in
turn.
19.1. Basic Event Handling
In the code we've seen so far in
this book, event handlers have been written as strings of JavaScript
code that are used as the values of certain
HTML attributes, such as
onclick. Although this is the key to the original
event model, there are a number of additional details, described in
the following sections, that you should understand.
19.1.1. Events and Event Types
Different types of occurrences
generate different types of
events. When the user moves the mouse over
a hyperlink, it causes a different type of event than when the user
clicks the mouse on the Submit
button of a form. Even the same occurrence can generate different
types of events based on context: when the user clicks the mouse over
a Submit button, for example, it
generates a different event than when the user clicks the mouse over
the Reset button of a form.
In the original event model, an event is an abstraction internal to
the web browser, and JavaScript code cannot manipulate an event
directly. When we speak of an event type in the
original event model, what we really mean is the name of the event
handler that is invoked in response to the event. In this model,
event-handling code is specified using the attributes of HTML
elements (and the corresponding properties of the associated
JavaScript objects). Thus, if your application needs to know when the
user moves the mouse over a specific hyperlink, you use the
onmouseover
attribute of the <a> tag that defines the
hyperlink. And if the application needs to know when the user clicks
the Submit button, you use the
onclick
attribute of the <input> tag that defines
the button or the
onsubmit
attribute of the <form> element that
contains that button.
There are quite a few different event handler attributes that you can
use in the original event model. They are listed in Table 19-1, which also specifies when these event
handlers are triggered and which HTML elements support the handler
attributes.
As client-side JavaScript programming has evolved, so has the event
model it supports. With each new browser version, new event handler
attributes have been added. Finally, the HTML 4 specification
codified a standard set of event handler attributes for HTML tags.
The third column of Table 19-1 specifies which HTML
elements support each event handler attribute, and it also specifies
which browser versions support that event handler for that tag and
whether the event handler is a standard part of HTML 4 for that tag.
In this third column, "N" is an abbreviation for Netscape
and "IE" is an abbreviation for Internet Explorer. Each
browser version is backward compatible with previous versions, so
"N3," for example, means Netscape 3 and all later
versions.
If you study the various event handler attributes in Table 19-1 closely, you can discern two broad categories
of events. One category is raw events or
input events. These are the events that are
generated when the user moves or clicks the mouse or presses a key on
the keyboard. These low-level events simply describe a user's
gesture and have no other meaning. The second category of events are
semantic events. These higher-level events have
a more complex meaning and can typically occur only in specific
contexts: when the browser has finished loading the document or a
form is about to be submitted, for example. A semantic event often
occurs as a side effect of a lower-level event. For example, when the
user clicks the mouse over a Submit
button, three input handlers are triggered:
onmousedown, onmouseup, and
onclick. And, as a result of this mouse-click, the
HTML form that contains the button generates an
onsubmit event.
One final note about Table 19-1 is required. For
raw mouse event handlers, column three specifies that the handler
attribute is supported (in HTML 4, at least) by "most
elements." The HTML elements that do not support these event
handlers are typically elements that belong in the
<head> of a document or do not have a
graphical representation of their own. The tags that do not support the nearly
universal mouse event handler attributes are:
<applet>, <bdo>,
<br>, <font>,
<frame>,
<frameset>, <head>,
<html>, <iframe>,
<isindex>, <meta>,
and <style>.
Table 19-1. Event handlers and the HTML elements that support them
Handler
|
Triggered when
|
Supported by
|
onabort
|
Image loading interrupted.
|
N3, IE4: <img>
|
onblur
|
Element loses input focus.
|
HTML4, N2, IE3: <button>,
<input>, <label>,
<select>,
<textarea>
N3, IE4: <body>
|
onchange
|
Selection in a <select> element or other
form element loses focus and its value has changed since it gained
focus.
|
HTML4, N2, IE3: <input>,
<select>,
<textarea>
|
onclick
|
Mouse press and release; follows mouseup event. Return
false to cancel default action (i.e., follow link,
reset, submit).
|
N2, IE3: <a>,
<area>, <input>
HTML4, N6, IE4: most elements
|
ondblclick
|
Double-click.
|
HTML4, N6, IE4: most elements
|
onerror
|
Error when loading image.
|
N3, IE4: <img>
|
onfocus
|
Element gains input focus.
|
HTML4, N2, IE3: <button>,
<input>, <label>,
<select>,
<textarea>
N3, IE4: <body>
|
onkeydown
|
Key pressed down. Return false to cancel.
|
N4: <input>,
<textarea>
HTML4, N6, IE4: form elements and <body>
|
onkeypress
|
Key pressed and released. Return false to cancel.
|
N4: <input>,
<textarea>
HTML4, N6, IE4: form elements and <body>
|
onkeyup
|
Key released.
|
N4: <input>,
<textarea>
HTML4, N6, IE4: form elements and <body>
|
onload
|
Document load complete.
|
HTML4, N2, IE3: <body>,
<frameset>
N3, IE4: <img>
N6, IE4: <iframe>,
<object>
|
onmousedown
|
Mouse button pressed.
|
N4: <a>, <area>,
<img>
HTML4, N6, IE4: most elements
|
onmousemove
|
Mouse moved.
|
HTML4, N6, IE4: most elements
|
onmouseout
|
Mouse moves off element.
|
N3: <a>, <area>
HTML4, N6, IE4: most elements
|
onmouseover
|
Mouse moves over element. For links, return true
to prevent URL from appearing in status bar.
|
N2, IE3: <a>,
<area>
HTML4, N6, IE4: most elements
|
onmouseup
|
Mouse button released.
|
N4: <a>, <area>,
<img>
HTML4, N6, IE4: most elements
|
onreset
|
Form reset requested. Return false to prevent
reset.
|
HTML4, N3, IE4: <form>
|
onresize
|
Window size changes.
|
N4, IE4: <body>,
<frameset>
|
onselect
|
Text selected.
|
HTML4, N6, IE3: <input>,
<textarea>
|
onsubmit
|
Form submission requested. Return false to prevent
submission.
|
HTML4, N3, IE4: <form>
|
onunload
|
Document or frameset unloaded.
|
HTML4, N2, IE3: <body>,
<frameset>
|
19.1.2. Event Handlers as Attributes
As we've seen in a number of examples
prior to this chapter, event handlers are specified (in the original
event model) as strings of JavaScript code used for the values of
HTML attributes. So, for example, to execute JavaScript code when the
user clicks a button, specify that code as the value of the
onclick attribute of the
<input> tag:
<input type="button" value="Press Me" onclick="alert('thanks');">
The value of an event handler attribute is an arbitrary string of
JavaScript code. If the handler consists of multiple JavaScript
statements, the statements must be separated
from each other by semicolons. For example:
<input type="button" value="Click Here"
onclick="if (window.numclicks) numclicks++; else numclicks=1;
this.value='Click # ' + numclicks;">
When an event handler requires multiple statements, it is usually
easier to define them in the body of a
function and then use the HTML event
handler attribute to invoke that function. For example, if you want
to validate a user's form input before submitting the form, you
can use the onsubmit attribute of the
<form> tag. Form validation typically
requires several lines of code, at a minimum, so instead of cramming
all this code into one long attribute value, it makes more sense to
define a form-validation function and simply use the
onclick attribute to invoke that function. For
example, if you defined a function named validateForm(
) to perform validation, you could invoke it from an event
handler like this:
<form action="processform.cgi" onsubmit="return validateForm( );">
Remember that HTML
is case-insensitive, so you can capitalize event handler attributes
any way you choose. One common convention is to use mixed-case
capitalization, with the initial "on" prefix in
lowercase: onClick, onLoad,
onMouseOut, and so on. In this book, I've
chosen to use all lowercase, however, for compatibility with XHTML,
which is case-sensitive.
The JavaScript code in an event handler attribute may contain a
return statement, and the return value may have
special meaning to the browser. This is discussed shortly. Also, note
that the JavaScript code of an event handler runs in a different
scope (see Chapter 4) than global JavaScript code.
This, too, is discussed in more detail later in this section.
19.1.3. Event Handlers as Properties
We've
seen that each HTML
element in a document has
a corresponding JavaScript object in the document tree, and the
properties of this JavaScript object correspond to the attributes of
the HTML element. In JavaScript 1.1 and later, this applies to event
handler attributes as well. So if an <input>
tag has an onclick attribute, the event handler it
contains can be referred to with the onclick
property of the form element object. ( JavaScript is case-sensitive,
so regardless of the capitalization used for the HTML attribute, the
JavaScript property must be all
lowercase.)
Technically, the DOM specification does not support
the original event model we've described here and does not
define JavaScript attributes that correspond to the event handler
attributes standardized by HTML 4. Despite the lack of formal
standardization by the DOM, this event model is so widely used that
all JavaScript-enabled web browsers allow event handlers to be
referred to as JavaScript properties.
Since the value of an HTML event handler attribute is a
string of JavaScript code,
you might expect the value of the corresponding JavaScript property
to be a string as well. This is not the case: when accessed through
JavaScript, event handler properties are functions. You can verify
this with a simple example:
<input type="button" value="Click Here" onclick="alert(typeof this.onclick);">
If you click the button, it displays a dialog box containing the word
"function," not the word "string." (Note that
in event handlers, the this keyword refers to the
object on which the event occurred. We'll discuss the
this keyword shortly.)
To assign an event handler to a document element using JavaScript,
simply set the event handler property to the desired function. For
example, consider the following HTML form:
<form name="f1">
<input name="b1" type="button" value="Press Me">
</form>
The button in this form can be referred to as
document.f1.b1, which means that an event handler
can be assigned with a line of JavaScript like this one:
document.f1.b1.onclick=function( ) { alert('Thanks!'); };
An event handler can also be assigned like this:
function plead( ) { window.status = "Please Press Me!"; }
document.f1.b1.onmouseover = plead;
Pay particular attention to that last line: there are no
parentheses
after the name of the function. To define an event handler, we are
assigning the function itself to the event handler property, not the
result of invoking the function. This is an area that often trips up
beginning JavaScript programmers.
There are a couple of advantages to expressing event handlers as
JavaScript properties. First, it reduces the intermingling of HTML
and JavaScript, promoting modularity and cleaner, more maintainable
code. Second, it allows event handler functions to be dynamic. Unlike
HTML attributes, which are a static part of the document and can be
set only when the document is created, JavaScript properties can be
changed at any time. In complex interactive programs, it can
sometimes be useful to dynamically change the event handlers
registered for HTML elements. One minor disadvantage to defining
event handlers in JavaScript is that it separates the handler from
the element to which it belongs. If the user interacts with a
document element before the document is fully loaded (and before all
its scripts have executed), the event handlers for the document
element may not yet be defined.
Example 19-1 shows how you can specify a single
function to be the
event handler for many document
elements. The example is a simple function that defines an
onclick event handler for every link in a
document. The event handler asks for the user's confirmation
before allowing the browser to follow the
hyperlink on which the user has just
clicked. The event handler function returns false
if the user does not confirm, which prevents the browser from
following the link. Event handler return values will be discussed
shortly.
Example 19-1. One function, many event handlers
// This function is suitable for use as an onclick event handler for <a> and
// <area> elements. It uses the this keyword to refer to the document element
// and may return false to prevent the browser from following the link.
function confirmLink( ) {
return confirm("Do you really want to visit " + this.href + "?");
}
// This function loops through all the hyperlinks in a document and assigns
// the confirmLink function to each one as an event handler. Don't call it
// before the document is parsed and the links are all defined. It is best
// to call it from the onload event handler of a <body> tag.
function confirmAllLinks( ) {
for(var i = 0; i < document.links.length; i++) {
document.links[i].onclick = confirmLink;
}
}
19.1.3.1. Explicitly invoking event handlers
Because the values of
JavaScript event handler properties are functions, we can use
JavaScript to invoke event handler functions directly. For example,
if we've used the onsubmit attribute of a
<form> tag to define a form-validation
function and we want to validate the form at some point before the
user attempts to submit it, we can use the
onsubmit property of the Form object to invoke the
event handler function. The code might look like this:
document.myform.onsubmit( );
Note, however, that invoking an event handler is not a way to
simulate what happens when the event actually occurs. If we invoke
the onclick method of a Link object, for example,
it does not make the browser follow the link and load a new document.
It merely executes whatever function we've defined as the value
of that property. To make the browser load a new document, we have to
set the location property of the Window object, as
we saw in Chapter 13. The same is true of the
onsubmit method of a Form object or the
onclick method of a Submit object: invoking the
method runs the event handler function but does not cause the form to
be submitted. (To actually submit the form, we call the
submit( ) method of the Form object.)
One reason that you might want to explicitly invoke an event handler
function is if you want to use JavaScript to augment an event handler
that is (or may be) already defined by HTML code. Suppose you want to
take a special action when the user clicks a button, but you do not
want to disrupt any onclick event handler that may
have been defined in the HTML document itself. (This is one of the
problems with the code in Example 19-1 -- by
adding a handler for each hyperlink, it overwrites any
onclick handlers that were already defined for
those hyperlinks.) You might accomplish this with code like the
following:
var b = document.myform.mybutton; // This is the button we're interested in
var oldHandler = b.onclick; // Save the HTML event handler
function newHandler( ) { /* My event-handling code goes here */ }
// Now assign a new event handler that calls both the old and new handlers
b.onclick = function() { oldHandler(); newHandler( ); }
19.1.4. Event Handler Return Values
In many cases, an event
handler (whether specified by HTML
attribute or JavaScript property) uses its return value to indicate
the disposition of the event. For example, if you use the
onsubmit event handler of a Form object to perform
form validation and discover that the user has not filled in all the
fields, you can return false from the handler to
prevent the form from actually being submitted. You can ensure that a
form is not submitted with an empty text field like this:
<form action="search.cgi"
onsubmit="if (this.elements[0].value.length == 0) return false;">
<input type="text">
</form>
Generally, if the web browser performs some kind of default action in
response to an event, you can return false to
prevent the browser from performing that action. In addition to
onsubmit, other event handlers from which you can
return false to prevent the default action include
onclick, onkeydown,
onkeypress, onmousedown,
onmouseup, and onreset. The
second column of Table 19-1 contains a note about
the return values for these event handlers.
There is one exception to the rule about returning
false to cancel: when the user moves the mouse
over a hyperlink (or image map), the browser's default action
is to display the link's URL in the status line. To prevent
this from happening, you must return true from the
onmouseover event handler. For example, you can
display a message other than a URL with code like this:
<a href="help.htm" onmouseover="window.status='Help!!'; return true;">Help</a>
There is no good reason for this exception: it is this way simply
because that is always the way it has been.
Note that event handlers are never required to explicitly return a
value. If you don't return a value, the default behavior
occurs.
19.1.5. Event Handlers and the this Keyword
Whether you define an event handler with an HTML
attribute or with a JavaScript property, what you are doing is
assigning a function to a property of a document element. In other
words, you're defining a new method of the document element.
When your event handler is invoked, it is invoked as a method of the
element on which the event occurred, so the this
keyword refers to that target element. This behavior is useful and
unsurprising.
Be sure, however, that you understand the implications. Suppose you
have an object o with a method
mymethod. You might register an event handler like
this:
button.onclick= o.mymethod;
This statement makes button.onclick refer to the
same function that o.mymethod does. This function
is now a method of both o and
button. When the browser triggers this event
handler, it invokes the function as a method of the
button object, not as a method of
o. The this keyword refers to
the Button object, not to your object o. Do not
make the mistake of thinking you can trick the browser into invoking
an event handler as a method of some other object. If you want to do
that, you must do it explicitly, like this:
button.onclick = function() { o.mymethod( ); }
19.1.6. Scope of Event Handlers
As we
discussed
in Chapter 11, functions in JavaScript are
lexically scoped. This means that they run in the scope in which they
were defined, not in the scope from which they are called. When you
define an event handler by setting the value of an HTML attribute to
a string of JavaScript code, you are implicitly defining a JavaScript
function (as you can see when you examine the type of the
corresponding event handler property in JavaScript). It is important
to understand that the scope of an event handler function defined in
this way is not the same as the scope of other normally defined
global JavaScript functions. This means that event handlers defined
as HTML attributes execute in a different scope than other
functions.[70]
Recall from the discussion in Chapter 4 that the
scope of a function is defined by a scope chain, or list of objects,
that is searched, in turn, for variable definitions. When a variable
x is looked up or resolved in a normal function,
JavaScript first looks for a local variable or argument by checking
the call object of the function for a property of that name. If no
such property is found, JavaScript proceeds to the next object in the
scope chain: the global object. It checks the properties of the
global object to see if the variable is a global variable.
Event handlers defined as HTML attributes have a more complex scope
chain than this. The head of the scope chain is the call object. Any
arguments passed to the event handler are defined here (we'll
see later in this chapter that in some advanced event models, event
handlers are passed an argument), as are any local variables declared
in the body of the event handler. The next object in an event
handler's scope chain isn't the global object, however;
it is the object that triggered the event handler. So, for example,
suppose you use an <input> tag to define a
Button object in an HTML form and then use the
onclick attribute to define an event handler. If
the code for the event handler uses a variable named
form, that variable is resolved to the
form property of the Button object. This can be a
useful shortcut when writing event handlers as HTML attributes.
The scope chain of an event handler does not stop with the object
that defines the handler: it proceeds up the containment hierarchy.
For the onclick event handler described earlier,
the scope chain begins with the call object of the handler function.
Then it proceeds to the Button object, as we've discussed.
After that, it continues up the HTML element containment hierarchy
and includes, at a minimum, the HTML <form>
element that contains the button and the Document object that
contains the form. The precise composition of the scope chain has
never been standardized and is implementation-dependent. Netscape 6
and Mozilla include all containing objects (even things such as
<div> tags), while IE 6 sticks to a more
minimal set that includes the target element, plus the containing
Form object (if any) and the Document object. Regardless of the
browser, the final object in the scope chain is the Window object, as
it always is in client-side JavaScript.
Having the target object in the scope
chain of an event handler can be a useful shortcut. But having an
extended scope chain that includes other document elements can be a
nuisance. Consider, for example, that both the Window and Document
objects define methods named open( ). If you use
the identifier open without qualification, you are
almost always referring to the window.open( )
method. In an event handler defined as an HTML attribute, however,
the Document object is in the scope chain before the Window object,
and using open by itself refers to the
document.open( ) method. Similarly, consider what
would happen if you added a property named window
to a Form object (or defined an input field with
name="window"). Then, if you define an event
handler within the form that uses the expression
window.open( ), the identifier
window resolves to the property of the Form object
rather than the global Window object, and event handlers within the
form have no easy way to refer to the global Window object or to call
the window.open( ) method!
The moral is that you must be careful when defining event handlers as
HTML attributes. Your safest bet is to keep any such handlers very
simple. Ideally, they should just call a global function defined
elsewhere and perhaps return the result:
<script>
function validateForm( ) {
/* Form validation code here */
return true;
}
</script>
<input type="submit" onclick="return validateForm( );">
A simple event handler like this
is still executed using an unusual scope chain, and you can subvert
it by defining a validateForm( ) method on one of
the containing elements. But, assuming that the intended global
function does get called, that function executes in the normal global
scope. Once again, remember that functions are executed using the
scope in which they were defined, not the scope from which they are
invoked. So, even though our validateForm( )
method is invoked from an unusual scope, it is still executed in its
own global scope with no possibility for confusion.
Furthermore, since there is no standard for the precise composition
of the scope chain of an event handler, it is safest to assume that
it contains only the target element and the global Window object. For
example, use this to refer to the target element,
and when the target is an <input> element,
feel free to use form to refer to the containing
Form object. But don't rely on the Form or Document objects
being in the scope chain. For example, don't use the
unqualified identifier write to refer to the
Document's write( ) method. Instead, spell
out that you mean document.write( ).
Keep in mind that this entire discussion of event-handler scope
applies only to event handlers defined as
HTML attributes. If you specify an event
handler by assigning a function to an appropriate JavaScript event
handler
property, there
is no special scope chain involved, and your function executes in the
scope in which it was defined. This is almost always the global
scope, unless it is a nested function, in which case the scope chain
can
get
interesting again!
 |  |  | 18.5. Other DOM APIs for Styles and Style Sheets |  | 19.2. Advanced Event Handling with DOM Level 2 |
Copyright © 2003 O'Reilly & Associates. All rights reserved.
|