Wednesday, April 10, 2013

event delegation and listener handler in javascript what its do why its required for html element

JavaScript events are the bedrock of all interactivity on web pages (I mean serious interactivity, not those dinky CSS drop-down menus). In traditional event handling you add or remove event handlers from each element as needed. However, event handlers can potentially cause of memory leaks and performance degradation — the more you have, the greater the risk. JavaScript event delegation is a simple technique by which you add a single event handler to a parent element in order to avoid having to add event handlers to multiple child elements.

How does it work?

Event delegation makes use of two often overlooked features of JavaScript events: event bubbling and the target element. When an event is triggered on an element, for example a mouse click on a button, the same event is also triggered on all of that element’s ancestors. This process is known as event bubbling; the event bubbles up from the originating element to the top of the DOM tree. The target element of any event is the originating element, the button in our example, and is stored in a property of the event object. Using event delegation it’s possible to add an event handler to an element, wait for an event to bubble up from a child element and easily determine from which element the event originated.

One of the hot methodologies in the JavaScript world is event delegation, and for good reason.  Event delegation allows you to avoid adding event listeners to specific nodes;  instead, the event listener is added to one parent.  That event listener analyzes bubbled events to find a match on child elements.  The base concept is fairly simple but many people don't understand just how event delegation works.  Let me explain the how event delegation works and provide pure JavaScript example of basic event delegation.
Let's say that we have a parent UL element with several child elements:
<ul id="parent-list">
 <li id="post-1">Item 1</li>
 <li id="post-2">Item 2</li>
 <li id="post-3">Item 3</li>
 <li id="post-4">Item 4</li>
 <li id="post-5">Item 5</li>
 <li id="post-6">Item 6</li>
</ul>
Let's also say that something needs to happen when each child element is clicked.  You could add a separate event listener to each individual LI element, but what if LI elements are frequently added and removed from the list?  Adding and removing event listeners would be a nightmare, especially if addition and removal code is in different places within your app.  The better solution is to add an event listener to the parent UL element.  But if you add the event listener to the parent, how will you know which element was clicked?
Simple:  when the event bubbles up to the UL element, you check the event object's target property to gain a reference to the actual clicked node.  Here's a very basic JavaScript snippet which illustrates event delegation:
// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click",function(e) {
 // e.target is the clicked element!
 // If it was a list item
 if(e.target && e.target.nodeName == "LI") {
  // List item found!  Output the ID!
  console.log("List item ",e.target.id.replace("post-"),
 " was clicked!");
 }
});
Start by adding a click event listener to the parent element.  When the event listener is triggered, check the event element to ensure it's the type of element to react to.  If it is an LI element, boom:  we have what we need!  If it's not an element that we want, the event can be ignored.  This example is pretty simple -- UL and LI is a straight-forward comparison.  Let's try something more difficult.  Let's have a parent DIV with many children but all we care about is an A tag with the "classA" CSS class:
// Get the parent DIV, add click listener...
document.getElementById("myDiv").addEventListener("click",function(e) {
 // e.target was the clicked element
 if(e.target && e.target.nodeName == "A") {
  // Get the CSS classes
  var classes = e.target.className.split(" ");
  // Search for the CSS class!
  if(classes) {
   // For every CSS class the element has...
   for(var x = 0; x < classes.length; x++) {
    // If it has the CSS class we want...
    if(classes[x] == "classA") {
     // Bingo!
     console.log("Anchor element clicked!");
     
     
     // Now do something here....
     
     
     
    }
   }
  }
  
 }
});
The example above requires not only a tag match but also a CSS class match.  While this is a bit more complex, it's still fairly simple in the grand scheme of things.  For example, if the A element had a SPAN tag in it, the SPAN tag would be the target element.  In that case, we'd need to walk up the DOM tree to find out if it contained an A.classA element we were looking for.
Since most developers use a JavaScript library for their DOM element and event handling, I recommend using the library's method of event delegation, as they capable of advanced delegation and element identification.
Hopefully this helps you visually the concept behind event delegation and convinces you of delegation's power!

Imagine that you have five hundred anchor tags on your page. As you might imagine, adding a click event to every one of those would be time consuming, and unnecessary. On top of that, what happens if, once you click on those anchor tags, we add additional anchor elements to the page? Would those new anchors be bound to the click event as well? The answer is no. You would then have to reattach a listener to those newly created elements.

Enter Event Delegation

Instead, with event delegation, we simply add a single event listener to an ancestor element, maybe something like a “ul.” Then, when the user clicks on one of its child elements, like an anchor tag, we only check to see if the target of the click was, in fact, an anchor tag. If it was, we proceed per usual.
  1. $('ul').click(function(e) {  
  2.       
  3.     if ( $(e.target).is('a') ) {  
  4.         alert('clicked');  
  5.     }  
  6. });  

Advantages

  • Only attach one event listener to the page, rather than five hundred (in our example)
  • Dynamically created elements will still be bound to the event handler.

Why Does this Work?

It works because of the way elements are captured (not IE) and bubble up. For instance, consider the following simple structure.
  1. <ul>  
  2.    <li><a href="http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-javascript-event-delegation-in-4-minutes/#">Anchor</a></li>  
  3. </ul>  
When you click on the anchor tag, you’re also clicking on the ‘li’ and the ‘ul’ and even the ‘body’ element. This is referred to as bubbling up.

Notes About this Screencast

Please keep in mind that this is just a simple example to explain the functionality. We used jQuery, only because I had four minutes to record! In that particular example (watch the screencast first), we could have used two alternative options:
  1. Pass true as a parameter of the clone() method. This would then clone the element, as well as any event handlers.
  2. Use the live() method instead. However, be careful when using this method: it reattaches the event handler X times. This may not necessarily be needed.
Mostly, this was meant to demonstrate the idea. With regular JavaScript, you could do something like:
  1. // Get some unordered list, which contains anchor tags  
  2. var ul = document.getElementById('items');  
  3.   
  4. // Quick and simple cross-browser event handler - to compensate for IE's attachEvent handler  
  5. function addEvent(obj, evt, fn, capture) {  
  6.     if ( window.attachEvent ) {  
  7.         obj.attachEvent("on" + evt, fn);  
  8.     }  
  9.     else {  
  10.         if ( !capture ) capture = false// capture  
  11.         obj.addEventListener(evt, fn, capture)  
  12.     }  
  13. }  
  14.   
  15. // Check to see if the node that was clicked is an anchor tag. If so, proceed per usual.   
  16. addEvent(ul, "click"function(e) {  
  17.   // Firefox and IE access the target element different. e.target, and event.srcElement, respectively.  
  18.   var target = e ? e.target : window.event.srcElement;  
  19.   if ( target.nodeName.toLowerCase() === 'a' ) {  
  20.      alert("clicked");  
  21.      return false;  
  22.   }  
  23. }); 

No comments:

Post a Comment