Best practices for where to add event listeners
Asked Answered
B

2

8

On my page, the user clicks on an element in order to edit it. To facilitate this, I assign the class editable to all such elements.

How should I listen for clicks on all these elements? Currently, I'm doing this:

document.body.addEventListener("click", (event) => {
  if (event.target.classList.contains("editable")) {
    // do stuff
  }
});

The alternative would be to set a listener on every element, like this:

const editables = document.getElementsByClassName("editable");
for (const editable of editables) {
    editable.addEventListener("click", editElement);
}

It seems to me that the first way must be better for performance, since it's only one element being listened on, but is it possible to degrade performance by attaching all such events to the body element? Are there any other considerations (e.g. browser implementations of event handling) that I'm neglecting which would suggest doing it the second way?

Bemean answered 29/9, 2014 at 16:26 Comment(1)
Notice that your first snippet doesn't catch any clicks on child elements of your editables, while the second snippet does.Barocchio
B
12

Short answer: definitely do it the first way. Event delegation is way more performant, but requires extra conditionals in your code, so it's basically a complexity versus performance tradeoff.

Longer Answer: For a small number of elements, adding individual event handlers works fine. However, as you add more and more event handlers, the browser's performance begins to degrade. The reason is that listening for events is memory intensive.

However, in the DOM, events "bubble up" from the most specific target to the most general triggering any event handlers along the way. Here's an example:

<html>
    <body>
        <div>
            <a>
                <img>
            </a>
         </div>
    </body>
</html>

If you clicked on the <img> tag, that click event would fire any event handlers in this order:

  1. img
  2. a
  3. div
  4. body
  5. html
  6. document object

Event delegation is the technique of listening to a parent (say <div>) for a bunch of event handlers instead of the specific element you care about (say <img>). The event object will have a target property which points to the specific dom element from which the event originated (in this case <img>).

Your code for event delegation might look something like this:

$(document).ready(function(){
    $('<div>').on('click', function(e) {
        // check if e.target is an img tag
        // do whatever in response to the image being clicked
    });
});

For more information checkout Dave Walsh's blog post on Event Delegation or duckduckgo "event delegation".

NOTE ON CODE SAMPLE IN OP: In the first example, target.hasClass('editable') means that the specific thing clicked on must have the class editable for the if block to execute. As one of the commenters pointed out, that's probably not what you want. You might want to try something along these lines instead:

$(document).on('click', function(e) {
   if ($(e.target).parents(".editable").length) {
       // Do whatever
   }
});

Let's break that down a bit:

  • $(e.target) - anything that on the page that was clicked converted to jQuery
  • .parents(".editable") - find all the ancestors of the element clicked, then filter to only include ones with the class "editable"
  • .length - this should be an integer. If 0, this means there are no parents with "editable" class
Betimes answered 29/9, 2014 at 16:41 Comment(0)
S
0

Another plus point for the first method

I was using the second (alternative) method that you have mentioned I noticed that when the ajax loaded... the newly created elements were not listening the event. I had to redo the for loop after ajax every time.

With the first method which looks like following in my code also works with ajax.

document.addEventListener('click', function (e) {
    if (hasClass(e.target, 'classname')) { 
        // do stuff
    } 
}, false);

So first one is better

Sundaysundberg answered 13/9, 2021 at 6:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.