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.matches("a.classA")) {
    console.log("Anchor element clicked!");
	}
});

Using the Element.matches API, we can see if the element matches our desired target.

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!

Recent Features

  • By
    Responsive Images: The Ultimate Guide

    Chances are that any Web designers using our Ghostlab browser testing app, which allows seamless testing across all devices simultaneously, will have worked with responsive design in some shape or form. And as today's websites and devices become ever more varied, a plethora of responsive images...

  • By
    5 Awesome New Mozilla Technologies You&#8217;ve Never Heard Of

    My trip to Mozilla Summit 2013 was incredible.  I've spent so much time focusing on my project that I had lost sight of all of the great work Mozillians were putting out.  MozSummit provided the perfect reminder of how brilliant my colleagues are and how much...

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!

    • Alexey

      Why not element.classList.contains(className)?

    • vip3out

      element.classList dont work in any browser, look for: http://caniuse.com/#search=classlist

    • Ajay

      I think in this way, we can use classList to detect a specific class on an element.

      event.target && event.target.nodeName == "A" && event.target.classList.contains("myClass")
    • If A has a className elementABC,but what we want to find really is class elementA, in this case, use indexOf('elementA') is not correct

  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.

    • David

      This is a really great explanation. Thank you for shedding more light on the gains. I had already begun adopting delegation just for the sake of avoiding the need to replace event handlers on dynamic elements, but the discussion of performance implications was really enlightening. Cheers!

  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.

    • rexs

      Can you elaborate more on your custom event binder?
      What is its benefits over the event delegation mentioned in this article?

  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?

    • Shubhransh

      target.parentElement.nodename

    • SorinN

      you should go with target.tagName it will bring back only the target element not all his childs. Nice, informal article, btw.

  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?

    • amitava

      I guess you are right. It is not needed. Even the whole “if” condition in the first example is not needed when we want e.target to be all the list elements and not some selected ones (with “className” or “id”).

  18. YiJu

    So, if you add blur or focus events on input.

    How to delegate its events for refresh new DOM events??

  19. Hi, this is an old post but still up to date. I was looking for Event Delegation and this article was very useful. Thank you so much!

    I just to add something that might be useful xD

    document.body.addEventListener('click', function(e){
    	[].forEach.call( document.body.querySelectorAll('.my-element'), function(item){
    		if(e.target == item){
                        //Do stuff!
                    }
    	});
    });
    

    You don’t need to distinguish if it’s a class, or any other valid css selector. You just have to compare the node instances… right?

  20. BryanYang
    $('ul').delegate('click','a',function(){
    
    })
    
  21. Siddharth

    Came here from react docs. Does react have an event listener at the top of the document, which is configured to listen to every built in as well as custom events? If so do all events get captured there in that listener?

  22. Raj

    Very well explained. It helped me to understand the concept of event delegation. Thank you.

  23. I was trying to build some event delegation library and then I simply made this, enjoy! -> https://github.com/MarekZeman91/element-event-delegation

  24. Subham

    Can’t we add a class to all the li elements and then add the event listener on the class selector ?? Even if we have to add an element, we would add the class to the element before adding it.

  25. So after reading this article and comments I wanted to ask which method should I use. Whether it would be event delegation or bubbling?

    As one person gave an example let’s doctors appointment calendar, for which I think event delegation is best option but in general which one should be preferred?

  26. I know it’s an old thread but it’s still relevant. The problem with this solution is that it only works if the target of the click is *precisely* the target of the event. For instance, suppose you want to observe clicks on all `<a>` tags on the page. If you click on a inside that `<a>` tag, the event target would be the thus not triggering the event.

    document.querySelector("body").addEventListener("click",function(e) {
      if (e.target.closest("[data-merch-arbo]")) {
        console.log("clic merch",e.target.closest("[data-merch-arbo]"));
    	}
    });
    

    There you trigger the event if the target is or is inside the CSS selector.

  27. Hi, would this approach work with individual ids as well instead of classes? I have an exercise in DOM which requires me using only id’s.

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