19.1 Basic Event HandlingIn 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 TypesDifferent 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>.
19.1.2 Event Handlers as AttributesAs 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 PropertiesWe'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 handlersBecause 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 ValuesIn 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 KeywordWhether 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 HandlersAs 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.[1]
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! |