Chapter 11 – DOM Events

最后更新于:2022-04-01 04:39:31

## 11.1 DOM events overview An event, in terms of the DOM, is either a pre-defined or custom moment in time that occurs in relationship with an element in the DOM, the *document* object, or the *window* object. These moments are typically predetermined and programaticlly accounted for by associating functionality (i.e. handlers/callbacks) to occur when these moments in time come to pass. These moments can be initiated by that state of the UI (e.g. input is focused or something has been dragged), the state of the enviroment that is running the JavaScript program (e.g. page is loaded or XHR request has finished), or the state of the program itself (e.g. start monitor users ui interaction for 30 seconds after the page has loaded). Setting up events can be accomplished using inline attribute event handlers, property event handlers, or the*addEventListener()* method. In the code below I'm demonstrating these three patterns for setting up an event. All three patterns add a *click* event that is invoked whenever the *`<div>`* in the html document is clicked by the mouse. live code: [http://jsfiddle.net/domenlightenment/4EPjN](http://jsfiddle.net/domenlightenment/4EPjN) ~~~ <!DOCTYPE html> <html lang="en"> <!-- inline attribure event handler pattern --> <body onclick="console.log('fire/trigger attribure event handler')"> <div>click me</div> <script> var elementDiv = document.querySelector('div'); // property event handler pattern elementDiv.onclick = function(){console.log('fire/trigger property event handler')}; //addEventListener method pattern elementDiv.addEventListener('click',function(){console.log('fire/trigger addEventListener')}, false); </script> </body> </html> ~~~ Notice that one of the events is attached to the *`<body> `* element. If you find it odd that the attribute event handler on the *`<body>`* fires by clicking the *`<div>`* element consider that when the *`<div>`* is clicked, are you not also clicking on the *`<body> `* element. Click anywhere but on the *`<div>`* and you still see the attribute handler fire on the*`<body> `* element alone. While all three of these patterns for attaching an event to the DOM programatically schedule the event, only the*addEventListener()* provides a robust and organized solution. The inline attribute event handler mixes together JavaScript and HTML and best practices advise keeping these things seperate. The downside to using a property event handler is that only one value can be assigned to the event property at a time. Meaning, you can't add more than one propety event handler to a DOM node when assigning events as property values. The code below shows an example of this by assigning a value to the *onclick* property twice, the last value set is used when the event is invoked. live code: [http://jsfiddle.net/domenlightenment/U8bWR](http://jsfiddle.net/domenlightenment/U8bWR) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me</div> <script> var elementDiv = document.querySelector('div'); // property event handler elementDiv.onclick = function(){console.log('I\'m first, but I get overidden/replace')}; //overrides/replaces the prior value elementDiv.onclick = function(){console.log('I win')}; </script> </body> </html> ~~~ Additionaly, using event handlers inline or property event handlers can suffer from scoping nuances as one attempts to leverage the scope chain from the function that is invoked by the event. The *addEventListener()*smooths out all of these issues, and will be used throughout this chapter. ### Notes *Element* nodes typically support inline event handlers (e.g. *`<div onclick=""></div>`*), property event handlers (e.g.*document.querySelector('div').onclick = function(){}*), and the use of the *addEventListener()* method. The *Document* node supports property event handlers (e.g.* document.onclick = funciton()*) and the use of the*addEventListener()* method. The *window* object supports inline event handler's via the * `<body>`* or *`<frameset>`* element (e.g. *`<body onload=""></body>`*), property event handlers (e.g. *window.load = function(){}*), and the use of the *addEventListener()* method. A property event handler historically has been refered to as a "DOM level 0 event". And the *addEventListener()* is often refered to as a "DOM level 2 event". Which is rather confusing considering there is no level 0 event or level 1 event. Addintioanlly, inline event handlers are known to be called, "HTML event handlers". ## 11.2 DOM event types In the tables below I detail the most common pre-defined events that can be attached to *Element* nodes, the*document* object, and the *window* object. Of course not all events are directly applicable to the node or object it can be attached too. That is, just because you can attach the event without error, and most likley invoke the event (i.e. bubbling events like *onchange* to *window*), does not mean that adding something like *window.onchange *is logical given that this event, by design was not meant for the *window* object. **User interface events** | Event Type | Event Interface | Description | Event Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *load* | *Event*,*UIEvent* | fires when an asset (HTML page, image, CSS, frameset,*`<object>`*, or JS file) is loaded. | *Element*, *Document*,*window*,*XMLHttpRequest*,*XMLHttpRequestUpload* | No | No | | *unload* | *UIEvent* | fires when user agent removes the resource (document, element, defaultView) or any depending resources (images, CSS file, etc.) | *window*, *`<body>`*,*`<frameset>`* | No | No | | *abort* | *Event*,*UIEvent* | Fires when an resource (object/image) is stopped from loading before completely loaded | *Element*,*XMLHttpRequest*,*XMLHttpRequestUpload* | Yes | No | | *error* | *Event*,*UIEvent* | Fires when a resource failed to load, or has been loaded but cannot be interpreted according to its semantics, such as an invalid image, a script execution error, or non-well-formed XML | *Element*,*XMLHttpRequest*,*XMLHttpRequestUpload* | Yes | No | | *resize* | *UIEvent* | Fires when a document view has been resized. This event type is dispatched after all effects for that occurrence of resizing of that particular event target have been executed by the user agent | *window*,  *`<body>`*,*`<frameset>`* | Yes | No | | *scroll* | *UIEvent* | Fires when a user scrolls a document or an element. | *Element*, *Document*,*window* | Yes | No | | *contextmenu* | *MouseEvent* | fires by right clicking an element | *Element* | Yes | Yes | **Focus events** | Event Type | Event Interface | Description | Events Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *blur* | *FocusEvent* | Fires when an element loses focus either via the mouse or tabbing | *Element* (except*`<body>`* and*`<frameseet>`* ),*Document* | No | No | | *focus* | *FocusEvent* | Fires when an element receives focus | *Element* (except*`<body>`* and*`<frameseet>`* ),*Document* | No | No | | *focusin* | *FocusEvent* | Fires when an event target is about to receive focus but before the focus is shifted. This event occurs right before the focus event | *Element* | Yes | No | | *focusout* | *FocusEvent* | Fires when an event target is about to lose focus but before the focus is shifted. This event occurs right before the blur event | *Element* | Yes | No | **Form events** | Event Type | Event Interface | Description | Event Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *change* | specific to HTML forms | Fires when a control loses the input focus and its value has been modified since gaining focus | *Element* | Yes | No | | *reset* | specific to HTML forms | Fires when a form is reset | *Element* | Yes | No | | *submit* | specific to HTML forms | Fires when a form is submitted | *Element* | Yes | Yes | | *select* | specific to HTML forms | Fires when a user selects some text in a text field, including input and textarea | *Element* | Yes | No | **Mouse events** | Event Type | Event Interface | Description | Event Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *click* | *MouseEvent* | Fires when mouse pointer is clicked (or user presses enter key) over an element. A click is defined as a mousedown and mouseup over the same screen location. The sequence of these events is *mousedown*>*mouseup*>*click*. Depending upon the environment configuration, the click event may be dispatched if one or more of the event types mouseover, mousemove, and mouseout occur between the press and release of the pointing device button. The click event may also be followed by the dblclick event | *Element*,*Document*,*window* | Yes | Yes | | *dblclick* | *MouseEvent* | Fires when a mouse pointer is clicked twice over an element. The definition of a double click depends on the environment configuration, except that the event target must be the same between *mousedown*, *mouseup*, and *dblclick*. This event type must be dispatched after the event typeclick if a click and double click occur simultaneously, and after the event type *mouseup* otherwise | *Element*,*Document*,*window* | Yes | Yes | | *mousedown* | *MouseEvent* | Fires when mouse pointer is pressed over an element | *Element*,*Document*,*window* | Yes | Yes | | *mouseenter* | *MouseEvent* | Fires when mouse pointer is moved onto the boundaries of an element or one of its descendent elements. This event type is similar to mouseover, but differs in that it does not bubble, and must not be dispatched when the pointer device moves from an element onto the boundaries of one of its descendent elements | *Element*,*Document*,*window* | No | No | | *mouseleave* | *MouseEvent* | Fires when mouse pointer is moved off of the boundaries of an element and all of its descendent elements. This event type is similar to mouseout, but differs in that does not bubble, and that it must not be dispatched until the pointing device has left the boundaries of the element and the boundaries of all of its children | *Element*,*Document*,*window* | No | No | | *mousemove* | *MouseEvent* | Fires when mouse pointer is moved while it is over an element. The frequency rate of events while the pointing device is moved is implementation-, device-, and platform-specific, but multiple consecutive mousemove events should be fired for sustained pointer-device movement, rather than a single event for each instance of mouse movement. Implementations are encouraged to determine the optimal frequency rate to balance responsiveness with performance | *Element*,*Document*,*window* | Yes | No | | *mouseout* | *MouseEvent* | Fires when mouse pointer is moved off of the boundaries of an element. This event type is similar to *mouseleave*, but differs in that does bubble, and that it must be dispatched when the pointer device moves from an element onto the boundaries of one of its descendent elements | *Element*,*Document*,*window* | Yes | Yes | | *mouseup* | *MouseEvent* | Fires when mouse pointer button is released over an element | *Element*,*Document*,*window* | Yes | Yes | | *mouseover* | *MouseEvent* | Fires when mouse pointer is moved over an element | *Element*,*Document*,*window* | Yes | Yes | **Wheel events** | Event Type | Event Interface | Description | Event Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *wheel*(browsers use*mousewheel*but the specification uses *wheel*) | *WheelEvent* | Fires when a mouse wheel has been rotated around any axis, or when an equivalent input device (such as a mouse-ball, certain tablets or touchpads, etc.) has emulated such an action. Depending on the platform and input device, diagonal wheel deltas may be delivered either as a singlewheel event with multiple non-zero axes or as separate wheel events for each non-zero axis. Some helpful details about browser support can be found [here](http://www.quirksmode.org/dom/events/scroll.html). | *Element*,*Document*,*Window* | Yes | Yes | **Keyboard events** | Event Type | Event Interface | Description | Event Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *keydown* | *KeyboardEvent* | Fires when a key is initially pressed. This is sent after any key mapping is performed, but before any input method editors receive the keypress. This is sent for any key, even if it doesn't generate a character code. | *Element*,*Document* | Yes | Yes | | *keypress* | *KeyboardEvent* | Fires when a key is initially pressed, but only if that key normally produces a character value. This is sent after any key mapping is performed, but before any input method editors receive the keypress. | *Element*,*Document* | Yes | Yes | | *keyup* | *KeyboardEvent* | Fires when a key is released. This is sent after any key mapping is performed, and always follows thecorresponding*keydown* and *keypress* events. | *Element*,*Document* | Yes | Yes | **Touch events** | Event Type | Event Interface | Description | Event Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *touchstart* | *TouchEvent* | Fires event to indicate when the user places a touch point on the touch surface | *Element*,*Document*,*window* | Yes | Yes | | *touchend* | *TouchEvent* | Fires event to indicate when the user removes a touch poin[t](http://www.w3.org/TR/2011/WD-touch-events-20110505/#dfn-touch-point) from the touch surface, also including cases where the touch point physically leaves the touch surface, such as being dragged off of the screen | *Element*,*Document*,*window* | Yes | Yes | | *touchmove* | *TouchEvent* | Fires event to indicate when the user moves a touch point along the touch surface | *Element*,*Document*,*window* | Yes | Yes | | *touchenter* | *TouchEvent* | Fires event to indicate when a touch point moves onto the interactive area defined by a DOM element | *Element*,*Document*,*window* | No | ? | | *toucheleave* | *TouchEvent* | Fires event to indicate when a touch point moves off the interactive area defined by a DOM element | *Element*,*Document*,*window* | No | ? | | *touchcancel* | *TouchEvent* | Fires event to indicate when a touch point has been disrupted in an implementation-specific manner, such as a synchronous event or action originating from the UA canceling the touch, or the touch point leaving the document window into a non-document area which is capable of handling user interactions. | *Element*,*Document*,*window* | Yes | No | ### Notes Touch events are typically only supported iOS, Andorid, and Blackberry browsers or browsers (e.g. chrome) that can switch on touch modes **Window, *`<body>`*, and frame specific events** | Event Type | Event Interface | Description | Event Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *afterprint* | ? | Fires on the object immediately after its associated document prints or previews for printing | *window*,*`<body>`*, *`<frameset>`* | No | No | | *beforeprint* | ? | Fires on the object before its associated document prints or previews for printing | *window*,*`<body>`*, *`<frameset>`* | No | No | | *beforeunload* | ? | Fires prior to a document being unloaded | *window*,*`<body>`*, *`<frameset>`* | No | Yes | | *hashchange* | *HashChangeEvent* | Fires when there are changes to the portion of a URL that follows the number sign (#) | *window*,*`<body>`*, *`<frameset>`* | No | No | | *messsage* | ? | Fires when the user sends a cross-document message or a message is sent from a *Worker* with *postMessage* | *window*,*`<body>`*, *`<frameset>`* | No | No | | *offline* | *NavigatorOnLine* | Fires when browser is working offline | *window*,*`<body>`*, *`<frameset>`* | No | No | | *online* | *NavigatorOnLine* | Fires when browser is working online | *window*,*`<body>`*, *`<frameset>`* | No | No | | *pagehide* | *PageTransitionEvent* | Fires when traversing from a session history entry | *window*,*`<body>`*, *`<frameset>`* | No | No | | *pageshow* | *PageTransitionEvent* | The pagehide event is fired when traversing from a session history entry | *window*,*`<body>`*, *`<frameset>`* | No | No | **Document specific events** | Event Type | Event Interface | Description | Event Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *readystatechange* | *Event* | Fires event when *readyState* is changed | *Document*,*XMLHttpRequest* | No | No | | *DOMContentLoaded* | *Event* | Fires when a webpage has been parsed, but before all resources have been fully downloaded | *Document* | Yes | No | **Drag events** | Event Type | Event Interface | Description | Event Targets | Bubbles | Cancelable | | --- | --- | --- | --- | --- | --- | | *drag* | *DragEvent* | Fires on the source object continuously during a drag operation. | *Element*,*Document*,*window* | Yes | Yes | | *dragstart* | *DragEvent* | Fires on the source object when the user starts to drag a text selection or selected object. The ondragstart event is the first to fire when the user starts to drag the mouse. | *Element*,*Document*,*window* | Yes | Yes | | *dragend* | *DragEvent* | Fires on the source object when the user releases the mouse at the close of a drag operation. The ondragend event is the final drag event to fire, following the ondragleave event, which fires on the target object. | *Element*,*Document*,*window* | Yes | No | | *dragenter* | *DragEvent* | Fires on the target element when the user drags the object to a valid drop target. | *Element*,*Document*,*window* | Yes | Yes | | *dragleave* | *DragEvent* | Fires on the target object when the user moves the mouse out of a valid drop target during a drag operation. | *Element*,*Document*,*window* | Yes | No | | *dragover* | *DragEvent* | Fires on the target element continuously while the user drags the object over a valid drop target. The ondragover event fires on the target object after the ondragenter event has fired. | *Element*,*Document*,*window* | Yes | Yes | | *drop* | *DragEvent* | Fires on the target object when the mouse button is released during a drag-and-drop operation. The ondrop event fires before the ondragleave and ondragend events. | *Element*,*Document*,*window* | Yes | Yes | ### Notes The tables below were crafted from the following three resources [Document Object Model (DOM) Level 3 Events Specification 5 User Event Module](http://www.w3.org/TR/DOM-Level-3-Events/#events-module), [DOM event reference](https://developer.mozilla.org/en/DOM/DOM_event_reference), [HTML Living Standard 7.1.6 Event handlers on elements, Document objects, and Window objects,](http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#events), and [Event compatibility tables](http://www.quirksmode.org/dom/events/). I've only mentioned here in this section the most common event types. Keep in mind there are numerous HTML5 api's that I've excluded from the this section (e.g. [media events](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#event-definitions) for *`<video>`* and *`<audio>`* elements or all state change events for the[XMLHttpRequest Level 2](http://www.w3.org/TR/XMLHttpRequest/#event-handlers)). The *copy*, *cut*, and *textinput* event are not defined by DOM 3 events or HTML5 Use mouseenter and mouseleave instead of mouseover and mouseout. Unfortunately Firefox, Chrome, and Safari still haven’t added these events! ## 11.3 The event flow When an event is invoked the [event flows or propagates through the DOM](http://www.w3.org/TR/DOM-Level-3-Events/#dom-event-architecture), firing the same event on other nodes and JavaScript objects. The event flow can be programmed to occur as a capture phase (i.e. DOM tree trunk to branch) or bubbling phase (i.e. DOM tree branches to trunk), or both. In the code below I set up 10 event listeners that can all be invoked, due to the event flow, by clicking once on the*`<div>`* element in the HTML document. When the *`<div>`* is clicked the capture phase begins at the *window* object and propagates down the DOM tree firing the *click* event for each object (i.e. *window* **>** *document* **>**`<html>` **** >****** >** `<body>`event target) until it hits the event target. Once the capture phase ends the target phase starts, firing the *click* event on the target element itself. Next the propagation phase propagates up from the event target firing the *click* event until it reaches the *window* object (i.e. event target **>** **`<body>` **>**`<html>` ** **>** *document* **>***window*). With this knowledge it should be obvious why clicking the *`<div>`* in the code example logs to the console 1,2,3,4,5,6,7,8,9,11. live code: [http://jsfiddle.net/domenlightenment/CAdTv](http://jsfiddle.net/domenlightenment/CAdTv) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me to start event flow</div> <script> /*notice that I am passing the addEventListener() a boolean parameter of true so capture events fire, not just bubbling events*/ //1 capture phase window.addEventListener('click',function(){console.log(1);},true); //2 capture phase document.addEventListener('click',function(){console.log(2);},true); //3 capture phase document.documentElement.addEventListener('click',function(){console.log(3);},true); //4 capture phase document.body.addEventListener('click',function(){console.log(4);},true); //5 target phase occurs during capture phase document.querySelector('div').addEventListener('click',function(){console.log(5);},true); //6 target phase occurs during bubbling phase document.querySelector('div').addEventListener('click',function(){console.log(6);},false); //7 bubbling phase document.body.addEventListener('click',function(){console.log(7);},false); //8 bubbling phase document.documentElement.addEventListener('click',function(){console.log(8);},false); //9 bubbling phase document.addEventListener('click',function(){console.log(9);},false); //10 bubbling phase window.addEventListener('click',function(){console.log(10)},false); </script> </body> </html> ~~~ After the *`<div>`* is clicked, the event flow proceeds in this order: 1. capture phase invokes click events on window that are set to fire on capture 2. capture phase invokes click events on document that are set to fire on capture 3. capture phase invokes click events on html element that are set to fire on capture 4. capture phase invokes click events on body element that are set to fire on capture 5. target phase invokes click events on div element that are set to fire on capture 6. target phase invokes click events on div element that are set to fire on bubble 7. bubbling phase invokes click events on body element are set to fire on bubble 8. bubbling phase invokes click events on html element are set to fire on bubble 9. bubbling phase invokes click events on document are set to fire on bubble 10. bubbling phase invokes click events on window are set to fire on bubble The use of the capture phase is not all that common due to a lack of browser support for this phase. Typically events are assumed to be inovked during the bubbling phase. In the code below I remove the capture phase from the previous code example and demostrate typically what is occuring during an event invocation. live code: [http://jsfiddle.net/domenlightenment/C6qmZ](http://jsfiddle.net/domenlightenment/C6qmZ) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me to start event flow</div> <script> //1 target phase occurs during bubbling phase document.querySelector('div').addEventListener('click',function(){console.log(1);},false); //2 bubbling phase document.body.addEventListener('click',function(){console.log(2);},false); //3 bubbling phase document.documentElement.addEventListener('click',function(){console.log(3);},false); //4 bubbling phase document.addEventListener('click',function(){console.log(4);},false); //5 bubbling phase window.addEventListener('click',function(){console.log(5)},false); </script> </body> </html> ~~~ Notice in the last code example that if the click event is initiated (click anywhere except on the *`<div>`*) on the*`<body>`* element the click event attached to the *`<div>`* is not invoked and bubbling invocation starts on the*`<body>`*. This is due to the fact the the event target is no longer the *`<div>`* but instead the *`<body>`* element. ### Notes Modern browsers do support the use of the capture phase so what was once considered unreliable might just server some value today. For example, one could intercept an event before it occurs on the event target. Keep this knowledge of event capturing and bubbling at the forefront of your thoughts when you read the event delegation section of this chapter. The event object passed to event listener functions contains a *eventPhase* property containing a number which indicates which phase an event is inoked in. A value of 1 indicates the capture phase. A value of 2 indicates the target phase. And a value of 3 indicates bubbling phase. ## 11.4 Adding event listeners to *Element* nodes, *window* object, and*Document* object The *addEventListener()* method is avaliabe on all *Element* nodes, the *window* object, and the *document*object providing the ability to added event listeners to parts of an HTML document as well as JavaScript objects relating to the DOM and [BOM](https://developer.mozilla.org/en-US/docs/DOM/window) (browser object model). In the code below I leverage this method to add a*mousemove* event to a *`<div>`* element, the *document* object, and the *window* object. Notice, due to the event flow, that mouse movement specifically over the *`<div>`* will invoke all three listeners each to time a movement occurs. live code: [http://jsfiddle.net/domenlightenment/sSFK5](http://jsfiddle.net/domenlightenment/sSFK5) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>mouse over me</div> <script> //add a mousemove event to the window object, invoking the event during the bubbling phase window.addEventListener('mousemove',function(){console.log('moving over window');},false); //add a mousemove event to the document object, invoking the event during the bubbling phase document.addEventListener('mousemove',function(){console.log('moving over document');},false); //add a mousemove event to a <div> element object, invoking the event during the bubbling phase document.querySelector('div').addEventListener('mousemove',function(){console.log('moving over div');},false); </script> </body> </html> ~~~ The *addEventListener()* method used in the above code example takes three arguments. The first argument is the type of event to listen for. Notice that the event type string does not contain the "on" prefix (i.e. *onmousemove*) that event handlers require. The second argument is the function to be invoked when the event occurs. The third parameter is a boolean indicating if the event should be fired during the capture phase or bubbling phase of the event flow. ### Notes I've purposfully avoided dicussing inline event handlers & property event handlers in favor of promoting the use of*addEventListener()* Typically a developer wants events to fire during the bubbling phase so that object eventing handles the event before bubbling the event up the DOM. Because of this you almost always provide a *false* value as the last argument to the*addEventListener()*. In modern browsers if the 3rd parameter is not specified it will default to false. You should be aware that the *addEventListener()* method can be used on the *XMLHttpRequest* object ## 11.5 Removing event listeners The *removeEventListener()* method can be used to remove events listeners, if the orginal listener was not added using an anonymous function. In the code below I add two events listeners to the HTML document and attempt to remove both of them. However, only the listener that was attached using a function reference is removed. live code: [http://jsfiddle.net/domenlightenment/XP2Ug](http://jsfiddle.net/domenlightenment/XP2Ug) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click to say hi</div> <script> var sayHi = function(){console.log('hi')}; //adding event listener using anonymous function document.body.addEventListener('click',function(){console.log('dude');},false); //adding event listener using function reference document.querySelector('div').addEventListener('click',sayHi,false); //attempt to remove both event listeners, but only the listener added with a funtions reference is removed document.querySelector('div').removeEventListener('click',sayHi,false); //this of course does not work as the function passed to removeEventListener is a new and different function document.body.removeEventListener('click',function(){console.log('dude');},false); //clicking the div will still invoke the click event attached to the body element, this event was not removed </script> </body> </html> ~~~ Anonymous functions added using *addEventListener()* method simply cannot be removed. ## 11.6 Getting event properties from the event object The handler or callback function invoked for events is sent by default a parameter that contains all relevant information about an event itself. In the code below I demostrate access to this event object and log all of its properties and values for a load event as well as a click event. Make sure you click the *`<div>`* to see the properties assocaited with a click event. live code: [http://jsfiddle.net/domenlightenment/d4SnQ](http://jsfiddle.net/domenlightenment/d4SnQ) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me</div> <script> document.querySelector('div').addEventListener('click',function(event){Object.keys(event).sort().forEach(function(item){     console.log(item+' = '+event[item]); //logs event propeties and values});     },false); //assumes 'this' is window this.addEventListener('load',function(event){Object.keys(event).sort().forEach(function(item){     console.log(item+' = '+event[item]); //logs event propeties and values});     },false); </script> </body> </html> ~~~ Keep in mind that each event will contain slightly different properties based on the event type (e.g. [MouseEvent](https://developer.mozilla.org/en/DOM/MouseEvent),[KeyboardEvent](https://developer.mozilla.org/en/DOM/KeyboardEvent), [WheelEvent](https://developer.mozilla.org/en/DOM/WheelEvent)). ### Notes The event object also provides the* stopPropagation()*, *stopImediatePropagation()*, and *preventDefault()* methods. In this book I use the argument name *event* to reference the event object. In truth you can use any name you like and its not uncommon to see *e* or *evt*. ## 11.7 The value of *this* when using *addEventListener()* The value of *this* inside of the event listener function passed to the *addEventListener()* method will be a reference to the node or object the event is attached too. In the code below I attach an event to a *`<div>`* and then using *this* inside of the event listener gain access to the *`<div>`* element the event is attached too. live code: [http://jsfiddle.net/domenlightenment/HwKgH](http://jsfiddle.net/domenlightenment/HwKgH) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me</div> <script> document.querySelector('div').addEventListener('click',function(){ // 'this' will be the element or node the event listener is attached too console.log(this); //logs '<div>' },false); </script> </body> </html> ~~~ When events are invoked as part of the event flow the *this* value will remain the value of the node or object that the event listener is attached too. In the code below we add a *click* event listener to the *`<body>`* and regardless of if you click on the *`<div>`* or the *`<body>`* the value of *this* always points to *`<body>`*. live code: [http://jsfiddle.net/domenlightenment/NF2gn](http://jsfiddle.net/domenlightenment/NF2gn) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me</div> <script> //click on the <div> or the <body> the value of this remains the <body> element node document.body.addEventListener('click',function(){ console.log(this); //log <body>...</body> },false); </script> </body> </html> ~~~ Additionally its possible using the *event.currentTarget* property to get the same reference, to the node or object invoking the event listener, that the *this* property provides. In the code below I leverage the*event.currentTarget* event object property showcasing that it returns the same value as *this*. live code: [http://jsfiddle.net/domenlightenment/uQm3f](http://jsfiddle.net/domenlightenment/uQm3f) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me</div> <script> document.addEventListener('click',function(event){ console.log(event.currentTarget); //logs '#document' //same as... console.log(this); },false); document.body.addEventListener('click',function(event){ console.log(event.currentTarget); //logs '<body>' //same as... console.log(this); },false); document.querySelector('div').addEventListener('click',function(event){ console.log(event.currentTarget); //logs '<div>' //same as... console.log(this); },false); </script> </body> </html> ~~~ ## 11.8 Referencing the *target* of an event and not the node or object the event is invoked on Because of the event flow its possible to click a *`<div>`*, contained inside of a *`<body>`* element and have a *click*event listener attached to the *`<body>`* element get invoked. When this happens, the event object passed to the event listener function attached to the *`<body>`* provides a reference (i.e. *event.target*) to the node or object that the event originated on (i.e. the target). In the code below when the *`<div>`* is clicked, the *`<body>`* element's*click* event listener is invoked and the *event.target* property references the orginal *`<div>`* that was the target of the click event. The *event.target* can be extremely useful when an event that fires because of the event flow needs knowledge about the origin of the event. live code: [http://jsfiddle.net/domenlightenment/dGkTQ](http://jsfiddle.net/domenlightenment/dGkTQ) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me</div> <script> document.body.addEventListener('click',function(event){ //when the <div> is clicked logs '<div>' because the <div> was the target in the event flow console.log(event.target); },false); </script> </body> </html> ~~~ Consider that in our code example if the *`<body>`* element is clicked instead of the *`<div>`* then the event *target*and the element node that the event listener is invoked on are the same. Therefore *event.target*, *this*, and*event.currentTarget* will all contain a reference to the *`<body>`* element. ## 11.9 Cancelling default browser events using *preventDefault()* Browsers provide several events already wired up when an HTML page is presented to a user. For example, clicking a link has a corresponding event (i.e. you navigate to a url). So does clicking a checkbox (i.e. box is checked) or typing text into a text field (i.e. text is inputed and appears on screen). These browser events can be prevented by calling the *preventDefault()* method inside of the event handler function associated with a node or object that invokes a browser default event. In the code below I prevent the default event that occurs on a *`<a>`*,*`<input>`*, and *`<textarea>`*. live code: [http://jsfiddle.net/domenlightenment/Ywcyh](http://jsfiddle.net/domenlightenment/Ywcyh) ~~~ <!DOCTYPE html> <html lang="en"> <body> <a href="google.com">no go</div> <input type="checkbox" /> <textarea></textarea> <script> document.querySelector('a').addEventListener('click',function(event){ event.preventDefault(); //stop the default event for <a> which would be to load a url },false); document.querySelector('input').addEventListener('click',function(event){ event.preventDefault(); //stop default event for checkbox, which would be to toggle checkbox state },false); document.querySelector('textarea').addEventListener('keypress',function(event){ event.preventDefault(); //stop default event for textarea, which would be to add characters typed },false); /*keep in mind that events still propagate, clicking the link in this html document will stop the default event but not event bubbling*/ document.body.addEventListener('click',function(){console.log('the event flow still flows!');},false); </script> </body> </html> ~~~ All attempts to click the link, check the box, or type in the text input in the previous code example will fail because I am preventing the default events for these elements from occuring. ### Notes The *preventDefault()* methods does not stop events from propagating (i.e. bubbling or capture phases) Providing a *return false* at the end of the body of the event listener has the same result as call the *preventDefault()*method The event object passed to event listener functions contains a boolean *cancelable* property which indicates if the event will respond to preveentDefault() method and canceling default behavior The event object passed to event listener functions contains a *defaultPrevented* property which indicates true if*preventDefault()* has been invoked for a bubbling event. ## 11.10 Stoping the event flow using *stopPropagation()* Calling *stopProgagation()* from within an event handler/listener will stop the capture and bubble event flow phases, but any events directly attached to the node or object will still be invoked. In the code below the *onclick*event attached to the *`<body>`* is never gets invoked because we are stopping the event from bubbling up the DOM when clicking on the *`<div>`*. live code: [http://jsfiddle.net/domenlightenment/RFKmA](http://jsfiddle.net/domenlightenment/RFKmA) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me</div> <script> document.querySelector('div').addEventListener('click',function(){ console.log('me too, but nothing from the event flow!'); },false); document.querySelector('div').addEventListener('click',function(event){ console.log('invoked all click events attached, but cancel capture and bubble event phases'); event.stopPropagation(); },false); document.querySelector('div').addEventListener('click',function(){ console.log('me too, but nothing from the event flow!'); },false); /*when the <div> is clicked this event is not invoked because one of the events attached to the <div> stops the capture and bubble flow.*/ document.body.addEventListener('click',function(){console.log('What, denied from being invoked!');},false); </script> </body> </html> ~~~ Notice that other click events attached to the the *`<div>`* still get invoked! Additionally using *stopPropagation()*does not prevent default events. Had the *`<div>`* in our code example been a *`<a>`* with an href value calling stopPropagation would not have stopped the browser default events from getting invoked. ## 11.11 Stoping the event flow as well as other like events on the same target using *stopImmediatePropagation()* Calling the *stopImmediatePropagation()* from within an event handler/listener will stop the event flow phases (i.e. *stopPropagation()*), as well as any other like events attached to the event target that are attached after the event listener that invokes the *stopImmediatePropagation()* method. In the code example below If we call*stopImmediatePropagation()*from the second event listener attached to the *`<div>`* the click event that follows will not get invoked. live code: [http://jsfiddle.net/domenlightenment/znSjM](http://jsfiddle.net/domenlightenment/znSjM) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me</div> <script> //first event attached document.querySelector('div').addEventListener('click',function(){ console.log('I get invoked because I was attached first'); },false); //seond event attached document.querySelector('div').addEventListener('click',function(event){ console.log('I get invoked, but stop any other click events on this target'); event.stopImmediatePropagation(); },false); //third event attached, but because stopImmediatePropagation() was called above this event does not get invoked document.querySelector('div').addEventListener('click',function(){ console.log('I get stopped from the previous click event listener'); },false); //notice that the event flow is also cancelled as if stopPropagation was called too document.body.addEventListener('click',function(){console.log('What, denied from being invoked!');},false); </script> </body> </html> ~~~ ### Notes Using the *stopImmediatePropagation()* does not prevent default events. Browser default events still get invoked and only calling *preventDefault()* will stop these events. ## 11.12 Custom events A developer is not limited to the predefined event types. Its possible to attach and invoke a custom event, using the *addEventListener()* method like normal in combiniation with *document.createEvent()*,*initCustomEvent()*, and *dispatchEvent()*. In the code below I create a custom event called *goBigBlue* and invoke that event*.* live code: [http://jsfiddle.net/domenlightenment/fRndj](http://jsfiddle.net/domenlightenment/fRndj) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>click me</div>​​​​​ <script> var divElement = document.querySelector('div');//create the custom eventvar cheer = document.createEvent('CustomEvent'); //the 'CustomEvent' parameter is required//create an event listener for the custom eventdivElement.addEventListener('goBigBlue',function(event){    console.log(event.detail.goBigBlueIs)},false);/*Use the initCustomEvent method to setup the details of the custom event. Parameters for initCustomEvent are: (event, bubble?, cancelable?, pass values to event.detail)*/cheer.initCustomEvent('goBigBlue',true,false,{goBigBlueIs:'its gone!'});    //invoke the custom event using dispatchEventdivElement.dispatchEvent(cheer);​ </script> </body> </html> ~~~ ### Notes IE9 requires (not optinal) the fourth parameter on* initiCustomEvent()* The [DOM 4 specifiction added a *CustomEvent()* constructor](http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-customevent) that has simplified the life cycle of a custom event but its not supported in ie9 and as of this writting and is still in flux ## 11.13 Simulating/Triggering mouse events Simiulating an event is not unlike creating a custom event. In the case of simulating a mouse event we create a*'MouseEvent'* using *document.createEvent()*. Then, using *initMouseEvent()* we setup the mouse event that is going to occur. Next the mouse event is dispatched on the element that we'd like to simulate an event on (i.e the *`<div>`* in the html document). In the code below a click event is attached to the *`<div>`* in the page. Instead of clicking the *`<div>`* to invoke the click event the event is triggered or simulated by programatically setting up a mouse event and dispatching the event to the *`<div>`*. live code: [http://jsfiddle.net/domenlightenment/kx7zJ](http://jsfiddle.net/domenlightenment/kx7zJ) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div>no need to click, we programatically trigger it</div>​​​​ <script> var divElement = document.querySelector('div'); //setup click event that will be simulateddivElement.addEventListener('click',function(event){    console.log(Object.keys(event));},false);//create simulated mouse event 'click'var simulateDivClick = document.createEvent('MouseEvents'); /*setup simulated mouse 'click' initMouseEvent(type,bubbles,cancelable,view,detail,screenx,screeny,clientx,clienty,ctrlKey,altKey,shiftKey,metaKey,button,relatedTarget)* simulateDivClick.initMouseEvent('click',true,true,document.defaultView,0,0,0,0,0,false,false,false,0,null,null); //invoke simulated clicked eventdivElement.dispatchEvent(simulateDivClick);</script> </body> </html> ~~~ ### Notes Simulating/triggering mouse events as of this writing works in all modern browsers. Simulating other event types quickly becomes more complicated and leveraging [simulate.js](https://github.com/airportyh/simulate.js) or jQuery (e.g. jQuery *trigger()* method) becomes neccsary. ## 11.14 Event delegation Event delegation, stated simply, is the programmatic act of leveraging the event flow and a single event listener to deal with multiple event targets. A side effect of event delegation is that the event targets don't have to be in the DOM when the event is created in order for the targets to respond to the event. This is of course rather handy when dealing with XHR responses that update the DOM. By implementing event delegation new content that is added to the DOM post JavaScript load parsing can immediately start responding to events. Imagine you have a table with an unlimited number of rows and columns. Using event delegation we can add a single event listener to the *`<table>`* node which acts as a delegate for the node or object that is the initial target of the event. In the code example below, clicking any of the *`<td>`*'s (i.e. the target of the event) will delegate its event to the *click* listener on the *`<table>`*. Don't forget this is all made possible because of the event flow and in this specific case the bubbling phase. live code: [http://jsfiddle.net/domenlightenment/BRkVL](http://jsfiddle.net/domenlightenment/BRkVL) ~~~ <!DOCTYPE html> <html lang="en"> <body> <p>Click a table cell</p> <table border="1">    <tbody>        <tr><td>row 1 column 1</td><td>row 1 column 2</td></tr>        <tr><td>row 2 column 1</td><td>row 2 column 2</td></tr>        <tr><td>row 3 column 1</td><td>row 3 column 2</td></tr>        <tr><td>row 4 column 1</td><td>row 4 column 2</td></tr>        <tr><td>row 5 column 1</td><td>row 5 column 2</td></tr>        <tr><td>row 6 column 1</td><td>row 6 column 2</td></tr>    </tbody></table>​​​​​​​​ <script> document.querySelector('table').addEventListener('click',function(event){ if(event.target.tagName.toLowerCase() === 'td'){ //make sure we only run code if a td is the target console.log(event.target.textContent); //use event.target to gain access to target of the event which is the td  }      },false); </script> </body> </html> ~~~ If we were to update the table in the code example with new rows, the new rows would responded to the *click*event as soon as they were render to the screen because the click event is delegated to the *`<table>`* element node. ### Notes Event delegation is ideally leverage when you are dealing with a *click*, *mousedown*, *mouseup*, *keydown*, *keyup*, and *keypress*event type.
';