Section 17.3. The Internet Explorer Event Model


17.3. The Internet Explorer Event Model

The event model supported by Internet Explorer 4, 5, 5.5, and 6 is an intermediate model, halfway between the original Level 0 model and the standard DOM Level 2 model. The IE event model includes an Event object that provides details about events that occur. Instead of being passed to event-handler functions, however, the Event object is made available as a property of the Window object. The IE model supports event propagation by bubblingbut not by capturing, as the DOM model does (although IE 5 and later provide special functionality for capturing mouse events). In IE 4, event handlers are registered in the same way as they are in the original Level 0 model. In IE 5 and later, however, multiple handlers may be registered with special (but nonstandard) registration functions.

The following sections provide more detail about this event model and compare it with the original Level 0 event model and the standard Level 2 event model. You should be sure you understand those two event models before reading about the IE model.

17.3.1. The IE Event Object

Like the standard DOM Level 2 event model, the IE event model provides details about each event in the properties of an Event object. The Event objects defined in the standard model were in fact modeled on the IE Event object, so you'll notice a number of similarities between the properties of the IE Event object and the properties of the DOM Event, UIEvent, and MouseEvent objects.

The most important properties of the IE Event object are:


type

A string that specifies the type of event that occurred. The value of this property is the name of the event handler with the leading "on" removed (e.g., "click" or "mouseover"). Compatible with the type property of the DOM Event object.


srcElement

The document element on which the event occurred. Compatible with the target property of the DOM Event object.


button

An integer that specifies the mouse button that was pressed. A value of 1 indicates the left button, 2 indicates the right button, and 4 indicates the middle button. If multiple buttons are pressed, these values are added together; the left and right buttons together produce a value of 3, for example. Compare this with the button property of the DOM Level 2 MouseEvent object, but note that although the property names are the same, the interpretation of the property values differs.


clientX , clientY

These integer properties specify the mouse coordinates at the time of the event, relative to the upper-left corner of the containing window. These properties are compatible with the DOM Level 2 MouseEvent properties of the same name. Note that for documents that are larger than the window, these coordinates are not the same as the position within the document. To convert from these window coordinates to document coordinates, you need to add the amount that the document has scrolled. See Section 14.3.1. for information on how to do this.


offsetX, offsetY

These integer properties specify the position of the mouse pointer relative to the source element. They enable you to determine which pixel of an Image object was clicked on, for example. These properties have no equivalent in the DOM event model.


altKey , ctrlKey, shiftKey

These boolean properties specify whether the Alt, Ctrl, and Shift keys were held down when the event occurred. These properties are compatible with the properties of the same name in the DOM MouseEvent object. Note, however, that the IE Event object does not have a metaKey property.


keyCode

This integer property specifies the keycode for keydown and keyup events and the Unicode character code for keypress events. Use String.fromCharCode( ) to convert character codes to strings. Key events are covered in more detail later in this chapter.


fromElement, toElement

fromElement specifies the document element that the mouse came from for mouseover events. toElement specifies the document element that the mouse has moved to for mouseout events. Comparable to the relatedTarget property of the DOM MouseEvent object.


cancelBubble

A boolean property that, when set to TRue, prevents the current event from bubbling any further up the element containment hierarchy. Comparable to the stopPropagation( ) method of the DOM Event object.


returnValue

A boolean property that can be set to false to prevent the browser from performing the default action associated with the event. This is an alternative to the traditional technique of returning false from the event handler. Comparable to the preventDefault( ) method of the DOM Event object.

You can find complete documentation for the IE Event object in Part IV.

17.3.2. The IE Event Object as a Global Variable

Although the IE event model provides event details in an Event object, it never passes Event objects as arguments to event handlers. Instead, it makes the Event object available as the event property of the global Window object. This means that an event-handling function in IE can refer to the Event object as window.event, or simply as event. Although it seems strange to use a global variable where a function argument would do, the IE scheme works because it is implicit in the event-driven programming model that only one event at a time is being processed. Since two events are never handled concurrently, it is safe to use a global variable to store details on the event that is currently being processed.

The fact that the Event object is a global variable is incompatible with the standard DOM Level 2 event model, but there is a one-line workaround. If you want to write an event-handler function that works with either event model, write the function so that it expects an argument, and then, if no argument is passed, initialize the argument from the global variable. For example:

 function portableEventHandler(e) {     if (!e) e = window.event;  // Get event details for IE     // Body of the event handler goes here } 

Another common idiom you may see relies on the || to return its first defined argument:

 function portableEventHandler(event) {     var e = event || window.event;     // Body of the event handler goes here } 

17.3.3. IE Event-Handler Registration

In IE 4, event handlers are registered in the same way they are in the original Level 0 event model: by specifying them as HTML attributes or assigning functions to the event handler properties of document elements.

IE 5 and later introduced the attachEvent( ) and detachEvent( ) methods, which provide a way to register more than one handler function for a given event type on a given object. You can use attachEvent( ) to register an event handler as follows:

 function highlight( ) { /* Event-handler code goes here */ } document.getElementById("myelt").attachEvent("onmouseover", highlight); 

The attachEvent( ) and detachEvent( ) methods work like addEventListener( ) and removeEventListener( ), with the following exceptions:

  • Since the IE event model does not support event capturing, attachEvent( ) and detachEvent( ) expect only two arguments: the event type and the handler function.

  • The event-handler names passed to the IE methods should include the "on" prefix. For example, use "onclick" with attachEvent( ) instead of "click" with addEventListener( ).

  • Functions registered with attachEvent( ) are invoked as global functions, rather than as methods of the document element on which the event occurred. That is, when an event handler registered with attachEvent( ) executes, the this keyword refers to the Window object, not to the event's target element.

  • attachEvent( ) allows the same event-handler function to be registered more than once. When an event of the specified type occurs, the registered function will be invoked as many times as it was registered.

17.3.4. Event Bubbling in IE

The IE event model does not have any notion of event capturing, as the DOM Level 2 model does. However, events do bubble up through the containment hierarchy in the IE model, just as they do in the Level 2 model. As with the Level 2 model, event bubbling applies only to raw or input events (primarily mouse and keyboard events), not to higher-level semantic events. The primary difference between event bubbling in the IE and DOM Level 2 event models is the way that you stop bubbling. The IE Event object does not have a stopPropagation( ) method, as the DOM Event object does. To prevent an event from bubbling or stop it from bubbling any further up the containment hierarchy, an IE event handler must set the cancelBubble property of the Event object to true:

 window.event.cancelBubble = true; 

Note that setting cancelBubble applies only to the current event. When a new event is generated, a new Event object is assigned to window.event, and cancelBubble is restored to its default value of false.

17.3.5. Capturing Mouse Events

To implement any user interface that involves dragging the mouse (such as pull-down menus or drag-and-drop), it is important to be able to capture mouse events so that the mouse drag can be properly handled regardless of what the user drags over. In the DOM event model, this can be done with capturing event handlers. In IE 5 and later, it is done with the setCapture( ) and releaseCapture( ) methods.

setCapture( ) and releaseCapture( ) are methods of all HTML elements. When you call setCapture( ) on an element, all subsequent mouse events are directed to that element, and that element's handlers can handle the events before they bubble up. Note that this applies only to mouse events and that it includes all mouse-related events: mousedown, mouseup, mousemove, mouseover, mouseout, click, and dblclick.

When you call setCapture( ), mouse events are dispatched specially until you call releaseCapture( ) or until the capture is interrupted. Mouse capture can be interrupted if your web browser loses focus, an alert( ) dialog appears, a system menu is displayed, or in similar cases. If any of these things happen, the element on which setCapture( ) was called will receive an onlosecapture event to notify it of the fact that it is no longer receiving captured mouse events.

In the most common scenario, setCapture( ) is called in response to a mousedown event, ensuring that subsequent mousemove events are received by the same element. The element performs its drag operation in response to mousemove events and calls releaseCapture( ) in response to a (captured) mouseup event.

See Example 17-4 for an example that uses setCapture( ) and releaseCapture( ).

17.3.6. attachEvent( ) and the this Keyword

As noted earlier, event handlers registered with attachEvent( ) are invoked as global functions instead of as methods of the element on which they are registered. This means that the this keyword refers to the global window object. By itself, this is not such a problem. It is compounded, however, by the fact that the IE event object has no equivalent to the DOM currentTarget property. srcElement specifies the element that generated the event, but if the event has bubbled, this may be different from the element that is handling the event.

If you want to write a generic event handler that can be registered on any element, and if that handler needs to know what element it is registered on, you cannot use attachEvent( ) to register the handler. You must either register the handler using the Level 0 event model or define a wrapper function around the handler and register that wrapper function:

 // Here are an event handler and an element we want to register it on function genericHandler( ) { /* code that uses the this keyword */ } var element = document.getElementById("myelement"); // We can register this handler with the Level 0 API element.onmouseover = genericHandler; // Or we can use a closure element.attachEvent("onmouseover", function( ) {                        // Invoke the handler as a method of element                        genericHandler.call(element, event);                     }); 

The problem with the Level 0 API is that it doesn't allow multiple handler functions to be registered, and the problem with using closures is that they lead to memory leaks in IE. The next section has details.

17.3.7. Event Handlers and Memory Leaks

As discussed in Section 8.8.4.2, Internet Explorer (up to version 6, at least) is vulnerable to a class of memory leaks that can occur when you use nested functions as event handlers. Consider the following code:

 // Add a validation event handler to a form function addValidationHandler(form) {     form.attachEvent("onsubmit", function( ) { return validate( ); }); } 

When this function is called, it adds an event handler to the specified form element. The handler is defined as a nested function, and although the function itself does not refer to any form elements, its scope, which is captured as part of the closure, does. As a result, a form element refers to a JavaScript Function object, and that object (via its scope chain) refers back to the form object. This is the kind of circular reference that causes memory leaks in IE.

One solution to this problem is to studiously avoid nested functions when programming for IE. Another solution is to carefully remove all your event handlers in response to an onunload( ) event. The code shown in the next section takes this latter approach.

17.3.8. Example: Event Model Compatibility for IE

This section has highlighted a number of incompatibilities between the IE event model and the standard DOM Level 2 model. Example 17-2 is a module of code that addresses many of these incompatibilities. It defines two functions, Handler.add( ) and Handler.remove( ), to add and remove event handlers from a specified element. On platforms that support addEventListener( ), these functions are trivial wrappers around these standard methods. On IE 5 and later, however, Example 17-2 defines these methods in such as way as to fix the following incompatibilities:

  • Event handlers are invoked as methods of the element on which they are registered.

  • Event handlers are passed a simulated event object that matches the DOM standard event object to the extent possible.

  • Duplicate registration of event handlers is ignored.

  • All handlers are deregistered on document unload to prevent memory leaks in IE.

In order to invoke event handlers with the correct value of the this keyword and pass a simulated event object, Example 17-2 must wrap the specified handler function within another function that invokes it correctly. The trickiest part of this example is the code that maps from the handler function passed to Handler.add( ) and the wrapper function that is actually registered with attachEvent( ). This mapping must be maintained so that Handler.remove( ) can remove the correct wrapper function and so that handlers can be deleted on document unload.

Example 17-2. An event compatibility layer for IE

 /*  * Handler.js -- Portable event-handler registration functions  *  * This module defines event-handler registration and deregistration functions  * Handler.add( ) and Handler.remove( ). Both functions take three arguments:  *  *   element: the DOM element, document, or window on which the handler  *      is to be added or removed.  *  *   eventType: a string that specifies the type of event for which the  *      handler is to be invoked. Use DOM-standard type names, which do  *      not include an "on" prefix. Examples: "click", "load", "mouseover".  *  *   handler: The function to be invoked when an event of the specified type  *      occurs on the specified element. This function will be invoked as  *      a method of the element on which it is registered, and the "this"  *      keyword will refer to that element. The handler function will be  *      passed an event object as its sole argument. This event object will  *      either be a DOM-standard Event object or a simulated one. If a  *      simulated event object is passed, it will have the following DOM-  *      compliant properties: type, target, currentTarget, relatedTarget,  *      eventPhase, clientX, clientY, screenX, screenY, altKey, ctrlKey,  *      shiftKey, charCode, stopPropagation( ), and preventDefault( )  *  * Handler.add( ) and Handler.remove( ) have no return value.  *  * Handler.add( ) ignores duplicate registrations of the same handler for  * the same event type and element. Handler.remove( ) does nothing if called  * to remove a handler that has not been registered.  *  * Implementation notes:  *  * In browsers that support the DOM standard addEventListener( ) and  * removeEventListener( ) event-registration functions, Handler.add( ) and  * Handler.remove( ) simply invoke these functions, passing false as the  * third argument (meaning that the event handlers are never registered as  * capturing event handlers).  *  * In versions of Internet Explorer that support attachEvent( ), Handler.add( )  * and Handler.remove() use attachEvent( ) and detachEvent( ). To  * invoke the handler function with the correct this keyword, a closure is  * used. Since closures of this sort cause memory leaks in Internet Explorer,  * Handler.add( ) automatically registers an onunload handler to deregister  * all event handlers when the page is unloaded. To keep track of  * registered handlers, Handler.add( ) creates a property named _allHandlers on  * the window object and creates a property named _handlers on any element on  * which a handler is registered.  */ var Handler = {}; // In DOM-compliant browsers, our functions are trivial wrappers around // addEventListener( ) and removeEventListener( ). if (document.addEventListener) {     Handler.add = function(element, eventType, handler) {         element.addEventListener(eventType, handler, false);     };     Handler.remove = function(element, eventType, handler) {         element.removeEventListener(eventType, handler, false);     }; } // In IE 5 and later, we use attachEvent( ) and detachEvent( ), with a number of // hacks to make them compatible with addEventListener and removeEventListener. else if (document.attachEvent) {     Handler.add = function(element, eventType, handler) {         // Don't allow duplicate handler registrations         // _find( ) is a private utility function defined below.         if (Handler._find(element, eventType, handler) != -1) return;         // To invoke the handler function as a method of the         // element, we've got to define this nested function and register         // it instead of the handler function itself.         var wrappedHandler = function(e) {             if (!e) e = window.event;             // Create a synthetic event object with partial compatibility             // with DOM events.             var event = {                 _event: e,    // In case we really want the IE event object                 type: e.type,           // Event type                 target: e.srcElement,   // Where the event happened                 currentTarget: element, // Where we're handling it                 relatedTarget: e.fromElement?e.fromElement:e.toElement,                 eventPhase: (e.srcElement==element)?2:3,                 // Mouse coordinates                 clientX: e.clientX, clientY: e.clientY,                 screenX: e.screenX, screenY: e.screenY,                // Key state                 altKey: e.altKey, ctrlKey: e.ctrlKey,                 shiftKey: e.shiftKey, charCode: e.keyCode,                 // Event-management functions                 stopPropagation: function( ) {this._event.cancelBubble = true;},                 preventDefault: function( ) {this._event.returnValue = false;}             }             // Invoke the handler function as a method of the element, passing             // the synthetic event object as its single argument.             // Use Function.call( ) if defined; otherwise do a hack             if (Function.prototype.call)                 handler.call(element, event);             else {                 // If we don't have Function.call, fake it like this.                 element._currentHandler = handler;                 element._currentHandler(event);                 element._currentHandler = null;             }         };         // Now register that nested function as our event handler.         element.attachEvent("on" + eventType, wrappedHandler);         // Now we must do some record keeping to associate the user-supplied         // handler function and the nested function that invokes it.         // We have to do this so that we can deregister the handler with the         // remove( ) method and also deregister it automatically on page unload.         // Store all info about this handler into an object.         var h = {             element: element,             eventType: eventType,             handler: handler,             wrappedHandler: wrappedHandler         };         // Figure out what document this handler is part of.         // If the element has no "document" property, it is not         // a window or a document element, so it must be the document         // object itself.         var d = element.document || element;         // Now get the window associated with that document.         var w = d.parentWindow;         // We have to associate this handler with the window,         // so we can remove it when the window is unloaded.         var id = Handler._uid( );  // Generate a unique property name         if (!w._allHandlers) w._allHandlers = {};  // Create object if needed         w._allHandlers[id] = h; // Store the handler info in this object         // And associate the id of the handler info with this element as well.         if (!element._handlers) element._handlers = [];         element._handlers.push(id);         // If there is not an onunload handler associated with the window,         // register one now.         if (!w._onunloadHandlerRegistered) {             w._onunloadHandlerRegistered = true;             w.attachEvent("onunload", Handler._removeAllHandlers);         }     };     Handler.remove = function(element, eventType, handler) {         // Find this handler in the element._handlers[] array.         var i = Handler._find(element, eventType, handler);         if (i == -1) return;  // If the handler was not registered, do nothing         // Get the window of this element.         var d = element.document || element;         var w = d.parentWindow;         // Look up the unique id of this handler.         var handlerId = element._handlers[i];         // And use that to look up the handler info.         var h = w._allHandlers[handlerId];         // Using that info, we can detach the handler from the element.         element.detachEvent("on" + eventType, h.wrappedHandler);         // Remove one element from the element._handlers array.         element._handlers.splice(i, 1);         // And delete the handler info from the per-window _allHandlers object.         delete w._allHandlers[handlerId];     };     // A utility function to find a handler in the element._handlers array     // Returns an array index or -1 if no matching handler is found     Handler._find = function(element, eventType, handler) {         var handlers = element._handlers;         if (!handlers) return -1;  // if no handlers registered, nothing found         // Get the window of this element         var d = element.document || element;         var w = d.parentWindow;         // Loop through the handlers associated with this element, looking         // for one with the right type and function.         // We loop backward because the most recently registered handler         // is most likely to be the first removed one.         for(var i = handlers.length-1; i >= 0; i--) {             var handlerId = handlers[i];        // get handler id             var h = w._allHandlers[handlerId];  // get handler info             // If handler info matches type and handler function, we found it.             if (h.eventType == eventType && h.handler == handler)                 return i;         }         return -1;  // No match found     };     Handler._removeAllHandlers = function( ) {         // This function is registered as the onunload handler with         // attachEvent. This means that the this keyword refers to the         // window in which the event occurred.         var w = this;         // Iterate through all registered handlers         for(id in w._allHandlers) {             // Get handler info for this handler id             var h = w._allHandlers[id];             // Use the info to detach the handler             h.element.detachEvent("on" + h.eventType, h.wrappedHandler);             // Delete the handler info from the window             delete w._allHandlers[id];         }     }     // Private utility to generate unique handler ids     Handler._counter = 0;     Handler._uid = function( ) { return "h" + Handler._counter++; }; } 




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net