Events

Imagine you are listening to a radio program and they announce, "Big event! Huge! Aliens have landed on Earth!" You might think, "Yeah, whatever"; some other listeners might think "They come in peace"; and some might think, "We're all gonna die!". Similarly, the browser broadcasts events, and your code can be notified should it decide to tune in and listen to the events as they happen. Some example events are as follows:

  • The user clicks a button
  • The user types a character in a form field
  • The page finishes loading

You can attach a JavaScript function called event listener or event handler to a specific event and the browser will invoke your function as soon as the event occurs. Let's see how this is done.

Inline HTML attributes

Adding specific attributes to a tag is the laziest but the least maintainable way; take the following line of code as an example:

    <div onclick="alert('Ouch!')">click</div> 

In this case, when the user clicks on <div>, the click event fires and the string of JavaScript code contained in the onclick attribute is executed. There's no explicit function that listens to the click event; however, behind the scenes, a function is still created, and it contains the code you specified as a value of the onclick attribute.

Element Properties

Another way to have some code executed when a click event fires is to assign a function to the onclick property of a DOM node element. For example, take a look at the following piece of code:

    <div id="my-div">click</div> 
    <script> 
      var myelement = document.getElementById('my-div'), 
      myelement.onclick = function () { 
        alert('Ouch!'), 
        alert('And double ouch!'), 
      }; 
    </script> 

This way is better because it helps you keep your <div> tag clean of any JavaScript code. Always keep in mind that HTML is for content, JavaScript for behavior, and CSS for formatting, and you should keep these three separate as much as possible.

This method has the drawback that you can attach only one function to the event, as if the radio program has only one listener. It's true that you can have a lot happening inside the same function, but this is not always convenient, as if all the radio listeners are in the same room.

DOM event listeners

The best way to work with browser events is to use the event listener approach outlined in DOM Level 2, where you can have many functions listening to an event. When an event fires, all the functions are executed. All of the listeners don't need to know about each other and can work independently. They can tune in and out at any time, without affecting the other listeners.

Let's use the same simple markup from the previous section, which is available for you to play with at http://www.phpied.com/files/jsoop/ch7.html. It has this piece of markup, which is as follows:

    <p id="closer">final</p> 

Your JavaScript code can assign listeners to the click event using the addEventListener() method. Let's attach two listeners as follows:

    var mypara = document.getElementById('closer'), 
    mypara.addEventListener('click', function () { 
      alert('Boo!'), 
    }, false); 
    mypara.addEventListener( 
      'click', console.log.bind(console), false); 

As you can see, addEventListeners

Capturing and bubbling

In the calls to addEventListener(), there was a third parameter-false. Let's see what it is for.

Let's say you have a link inside an unordered list, which is as follows:

    <body> 
      <ul> 
        <li><a href="http://phpied.com">my blog</a></li> 
      </ul> 
    </body> 

When you click on the link, you're actually also clicking on the list item, <li>, the <ul> list, the <body> tag, and eventually, the document as a whole. This is called event propagation. A click on a link can also be seen as a click on the document. The process of propagating an event can be implemented in the two following ways:

  • Event capturing: This click happens in the document first, then it propagates down to the body, the list, the list item, and finally, to the link
  • Event bubbling: This click happens on the link and then bubbles up to the document

DOM level 2 events specification suggests that the events propagate in three phases, namely, capturing, at target, and bubbling. This means that the event propagates from the document to the link (target) and then bubbles back up to the document. The event objects have an eventPhase property, which reflects the current phase:

Capturing and bubbling

Historically, IE and Netscape (working on their own and without a standard to follow) implemented the exact opposites. IE implemented only bubbling and Netscape only capturing. Today, long after the DOM specification, modern browsers implement all three phases.

The practical implications related to the event propagation are as follows:

  • The third parameter to addEventListener() specifies whether or not capturing should be used. In order to have your code more portable across browsers, it's better to always set this parameter to false and use bubbling only.
  • You can stop the propagation of the event in your listeners so that it stops bubbling up and never reaches the document. To do this, you can call the stopPropagation() method of the event object; there is an example in the next section.
  • You can also use event delegation. If you have ten buttons inside <div>, you can always attach ten event listeners, one for each button. However, a smarter thing to do is to attach only one listener to the wrapping <div> and once the event happens, check which button was the target of the click.

As a side note, there is a way to use event capturing in old IEs too (using setCapture() and releaseCapture() methods) but only for mouse events. Capturing any other events (keystroke events for example) is not supported.

Stop propagation

Let's see an example of how you can stop the event from bubbling up. Going back to the test document, there is this piece of code:

    <p id="closer">final</p> 

Let's define a function that handles clicks on the paragraph, as follows:

    function paraHandler() { 
      alert('clicked paragraph'), 
    } 

Now, let's attach this function as a listener to the click event:

    var para = document.getElementById('closer'), 
    para.addEventListener('click', paraHandler, false); 

Let's also attach listeners to the click event on the body, the document, and the browser window:

    document.body.addEventListener('click', function () { 
      alert('clicked body'), 
    }, false); 
    document.addEventListener('click', function () { 
      alert('clicked doc'), 
    }, false); 
    window.addEventListener('click', function () { 
      alert('clicked window'), 
    }, false); 

Note that the DOM specifications don't say anything about events on the window. And why would they, as DOM deals with the document and not the browser. So browsers implement window events inconsistently.

Now, if you click on the paragraph, you'll see four alerts saying:

  • clicked paragraph
  • clicked body
  • clicked doc
  • clicked window

This illustrates how the same single click event propagates (bubbles up) from the target all the way up to the window.

The opposite of addEventLister() is removeEventListener(), and it accepts exactly the same parameters. Let's remove the listener attached to the paragraph by writing the following line of code:

    > para.removeEventListener('click', paraHandler, false); 

If you try now, you'll see alerts only for the click event on the body, document, and window, but not on the paragraph.

Now, let's stop the propagation of the event. The function you add as a listener receives the event object as a parameter, and you can call the stopPropagation() method of that event object as follows:

    function paraHandler(e) { 
      alert('clicked paragraph'), 
      e.stopPropagation(); 
    } 

Adding the modified listener is done as follows:

    para.addEventListener('click', paraHandler, false); 

Now, when you click on the paragraph, you will see only one alert because the event doesn't bubble up to the body, the document, or the window.

Note that when you remove a listener, you have to pass a pointer to the same function you previously attached. Otherwise, doing the following does not work because the second argument is a new function, not the same you passed when adding the event listener, even if the body is exactly the same. Consider the following code:

    document.body.removeEventListener('click',  
      function () { 
        alert('clicked body'), 
      },  
    false); //  does NOT remove the handler 

Prevent default behavior

Some browser events have a predefined behavior. For example, clicking a link causes the browser to navigate to another page. You can attach listeners to clicks on a link, and you can also disable the default behavior by calling the preventDefault() method on the event object.

Let's see how you can annoy your visitors by asking "Are you sure you want to follow this link?" every time they click a link? If the user clicks on Cancel (causing confirm() to return false), the preventDefault() method is called, which is shown as follows:

    // all links 
    var all_links = document.getElementsByTagName('a'),  
    for (var i = 0; i < all_links.length; i++) { // loop all links 
      all_links[i].addEventListener( 
        'click',       // event type 
        function (e) { // handler 
          if (!confirm('Sure you want to follow this link?')) { 
            e.preventDefault(); 
          } 
        }, 
        false // don't use capturing 
      );  
    } 

Note that not all events allow you to prevent the default behavior. Most do, but if you want to be sure, you can check the cancellable property of the event object.

Cross-browser event listeners

As you already know, most modern browsers almost fully implement the DOM Level 1 specification. However, the events were not standardized until DOM 2. As a result, there are quite a few differences in how IE, before version 9, implements this functionality compared to modern browsers.

Check out an example that causes nodeName of a clicked element (the target element) to be written to the console:

    document.addEventListener('click', function (e) { 
      console.log(e.target.nodeName); 
    }, false); 

Now, let's take a look at how IE is different:

  • In IE, there's no addEventListener() method; although, since IE Version 5, there is an equivalent attachEvent() method. For earlier versions, your only choice is accessing the property directly, such as onclick.
  • The click event becomes onclick when using attachEvent().
  • If you listen to events the old fashioned way (for example, by setting a function value to the onclick property), when the callback function is invoked, it doesn't get an event object passed as a parameter. However, regardless of how you attach the listener in IE, there is always a global object window.event that points to the latest event.
  • In IE, the event object doesn't get a target attribute telling you the element on which the event fired, but it does have an equivalent property called srcElement.
  • As mentioned earlier, event capturing doesn't apply to all events, so only bubbling should be used.
  • There's no stopPropagation() method, but you can set the IE-only cancelBubble property to true.
  • There's no preventDefault() method, but you can set the IE-only returnValue property to false.
  • To stop listening to an event, instead of removeEventListener() in IE, you'll need detachEvent().

So, here's the revised version of the previous code that works across browsers:

    function callback(evt) { 
      // prep work 
      evt = evt || window.event; 
      var target = evt.target || evt.srcElement; 
 
     // actual callback work 
      console.log(target.nodeName); 
    } 
 
    //  start listening for click events 
    if (document.addEventListener) { // Modern browsers 
      document.addEventListener('click', callback, false); 
    } else if (document.attachEvent) { // old IE 
      document.attachEvent('onclick', callback); 
    } else { 
      document.onclick = callback; // ancient 
    } 

Types of events

Now you know how to handle cross-browser events. However, all of the preceding examples used only click events. What other events are happening out there? As you can probably guess, different browsers provide different events. There is a subset of cross-browser events, and some browser-specific ones. For a full list of events, you should consult the browser's documentation, but here's a selection of cross-browser events:

  • Mouse events
    • mouseup, mousedown, click (the sequence is mousedown-up-click), dblclick
    • mouseover (mouse is over an element), mouseout (mouse was over an element but left it), mousemove

  • Keyboard events
    • keydown, keypress, keyup (occur in this sequence)

  • Loading/window events
    • load (an image or a page and all of its components are done loading), unload (user leaves the page), beforeunload (the script can provide the user with an option to stop the unload)
    • abort (user stops loading the page or an image in IE), error (a JavaScript error, also when an image cannot be loaded in IE)
    • resize (the browser window is resized), scroll (the page is scrolled), contextmenu (the right-click menu appears)

  • Form events
    • focus (enter a form field), blur (leave the form field)
    • change (leave a field after the value has changed), select (select text in a text field)
    • reset (wipe out all user input), submit (send the form)

Additionally, modern browsers provide drag events (dragstart, dragend, drop, and among others) and touch devices provide touchstart, touchmove, and touchend.

This concludes the discussion of events. Refer to the exercise section at the end of this chapter for a little challenge of creating your own event utility to handle cross-browser events.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.103.154