Treehouse

How JavaScript Event Delegation Works

By on  

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 are capable of advanced delegation and element identification.

Hopefully this helps you visually the concept behind event delegation and convinces you of delegation's power!

ydkjs-3.png

Recent Features

Incredible Demos

Discussion

  1. Good overview. I was just wondering could use:


    e.target.className.split(" ").indexOf(class) !=-1

    Instead of the loop. I know it is not supported everywhere but everyone with addEventListener supports indexOf

    • Daniel

      The availability of Array.indexOf is independent of any one method or nodeList, it is dependent on the browser you are using and the version of JavaScript it implements. Firefox has had indexOf for some time now, so to with later version of Google Chrome.

    • Art Taylor

      While indexOf isnt supported by some older browsers, a polyfill can be found at:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf

    • Hott Dogg

      I think it will be even better to use RegExp for class testing :)
      var re = new RegExp('\\b'+class+'\\b');
      re.test(e.target.className);

    • Yep, that would certainly work well too!

  2. Nice post, the importance of event delegation can’t be hyped enough!

  3. Blaine

    Good concept. When dealing with dynamic content that needed events, I typically loaded with a “rel” set to “untouched”, or without one at all and load a function that would look for all untouched methods or without the “touched” rel, give them the correct event, and set their rel’s to “touched”.

    Is there a performance improvement done by using your method?

    • Depends on what you’re doing. It occurs to me, however, that you may need to run the event routine multiple times if you ever add elements dynamically. The rel tag solution, if you only want to do something once, isn’t a bad idea. Elitists would tell you to use data-[whateverAttributeName].

  4. santhanam

    Good Example. Is there is any thing for Memoization.?

  5. Patrick Hayes


    var classes = e.target.className.split(" ");
    // Search for the CSS class!
    if(classes) {
    //...
    }

    When className returns an empty string, split should return an array containing one empty string, which evaluates to true in an if statement. Otherwise split will return the split elements or an array with the original string. That being said, is “if (classes)” cautionary, is it directed at a known browser, is it unnecessary, or am I missing a case where the if would be false? I can’t blame one for being cautious especially with browser reputation being what it is and can’t rule out that I’m missing something!

  6. Ralph

    I’m an experienced programmer, but relatively new to javascript, but not new to GUI programming (Windows, Swing, Android, etc.).

    I read a lot about the importance of event delegation, but I’m skeptical. To me, it seems like it’s a venerated “best practice” that’s no longer relevant. I just can’t believe that modern computers and browsers are affected by the very marginal gains (?) offered by event delegation. What exactly *are* the benefits?

    From a coding style perspective, event delegation shows odd cohesion.When coding event handling in other environments (like desktop, etc.) one normally doesn’t have a central event handler with an embedded case structure to test what was clicked. Especially with the common handler tied to some UI implementation which could very well change as users ask for changes to the UI. User: “Please move that button on the toolbar.” Of course, event handlers on the individual items would mean such changes are not problematic.

    The author says “what if LI elements are frequently added and removed from the list? Adding and removing event listeners would be a nightmare.” Ok, for *semantically* grouped items, like items in a list, I can see it (just like you’d have a single event handler for a combo box, and not on the items in the box).

    So… Is this some venerated yet anachronistic style, or are there any statistics or tests I can run that show the benefits.

    • Chris Baker

      There are indeed direct benefits. In standard application programming, one may deal more directly with the garbage collector or at least have a better conception of how the GC operates. In web development, the GC is completely variable per user agent — you have literally no idea how or when it will operate because each user may potentially use a different agent.

      Imagine a use case where you have a grid of several hundred dynamically generated table cells (think an hour-by-hour availability calendar): attaching an event to each of these cells would, for starters, take a moment. While you’re looping over them, the browser UI thread is locked and the user perceives bad performance. You also wind up with several hundred listeners, and depending on your script architecture that could also mean several hundred function references. This is in itself weighty… but now the user switches dates and you have a new batch of cells injected into the table. Do you loop the old cells and remove the listener from each? That will take time, then you have to loop again to apply the new listeners. Do you just ignore the old and only worry about the new? Now you have several hundred references sitting in memory, waiting for the mythical GC that will fire who-knows-when. Plus, you may have created circular references and those listeners will never leave memory, *even when the user navigates to a completely different page*. Your function references are still sitting in memory, too. That is one quite feasible use-case where a single event listener and delegation eliminates all those references and concerns.

      In your toolbar button example, the delegating event would be attached to the toolbar itself. This pattern actually compliments a more flexible UI; you don’t think about the event, you just move the button. Or add a new one. Or delete one. No matter what change takes place, the single event listener will bubble up any actions.

      I don’t think the cohesion is odd at all — one would use delegate events to apply an action to a specific group of elements. Cohesion already existed; these buttons are toolbar buttons, and when you click on, a centralized handler will determine what happens. This is how you already code (presumably), the real differences is that without delegation, you have more references to keep track of.

      Delegation makes most sense when it is used with mutually cohesive elements: LIs in a UL, TRs in a TABLE, etc. Control icons and the like are also another common use, though they aren’t semantically grouped they still have an underlying cohesion whether you used delegation or not.

  7. Carlos

    hi, i try create a function same live, delegate, on of jquery, any idea?

  8. Anup

    Thanks for explaining this in very simple manner. Now I can take benefit of it in jquery OR all js libraries as well.
    No need treat it as like magic.

  9. Just for the record…

    In jQuery you can implement it using CSS selectors like this:

    jQuery("#myDiv a.classA").bind("click", function(e) {
        var clickedElement_jQuery = $(e.currentTarget); // gets the jQuery object for the "current target" element, i.e. the clicked element
        var clickedElement_DOM = clickedElement_jQuery.get();
    
        // Do something...
    
    });
    
    • It’s actually even easier than that! Using jQuery’s on the value of this in the callback is bound to the target element. So:

      jQuery("#myDiv").on("click", "a.classA", function() {
         var targetElement = $(this);
      });
      
    • krishna

      this is not event delegation method this is direct method

      Event Delegation:

      $("#myDiv").on("click", "a.classA", function(e){
      
      });
      
  10. This article is a very good example of event handle in JS. But it would be better if the author can talk more about the cross browser issue.

  11. pradeep

    Good article but
    i prefer to bind all events in custom event binder.
    just give a JSON to event binder and boom.

  12. Hassan

    The target.nodeName check fails if there’s an span tag inside the A element, so the nodeName returns “SPAN” and not “A”. Is there an easy way to get around this?

  13. Hey – thanks a lot for this post! This solution will tidy up my implementation nicely.

  14. Alex

    Good explanation, it makes things clear

  15. I liked the post… I used it as inspiration for my own article :)
    http://robbypelssers.blogspot.nl/2014/04/javascript-event-delegation-bubbling.html

  16. natee

    I think the className should handle like this:

    var classes = e.target.className.replace(/\s+/g," ");
    

    for when there is more than one space in attribute class

  17. I’m just wondering why you are checking if e.target exists, in which cases it wouldn’t?

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!