DEV Community

Cover image for Javascript Add Event Listener to Multiple Elements
Johnny Simpson
Johnny Simpson

Posted on • Updated on • Originally published at fjolt.com

Javascript Add Event Listener to Multiple Elements

If you've ever worked in vanilla Javascript, you might be familiar with adding event listeners to elements using the following formula:

let element = document.querySelector('#button');

element.addEventListener('click', () => {
    console.log('some event content here...')
})
Enter fullscreen mode Exit fullscreen mode

The above code will, of course, trigger a function which fires when #button is fired. Sometimes, though, you need to add an event listener to multiple elements - say, every button that exists on a page. You might have found that even if you have multiple elements on a page, the above approach only adds your event to one element - the first one. What gives?

The issue is addEventListener is only good for adding an event to one DOM element - and querySelector only matches one element too. So how do you add an event listener to multiple elements on a page? Let's look at the solution.

Adding Event Listeners to Multiple Elements

Instead of using querySelector, we're going to use querySelectorAll to match all elements on our page. The following code returns an item of type NodeList, consisting of all DOM elements matching .button. To add events to every element, we're going to need to loop through every matched element from our querySelector, and add events to each:

let elements = document.querySelectorAll('.button');
Enter fullscreen mode Exit fullscreen mode

Javascript is weird because it doesn't return DOM elements as a simple array - it returns them as a NodeList. If you want to learn about NodeLists in more detail, read my guide on that here.

In modern browsers, NodeLists behave a lot like arrays, so we can use forEach to loop through each. To add an event to each .button then, we need to loop through it using forEach. So adding a click event to all .button elements looks like this:

let elements = document.querySelectorAll('.button');

let clickEvent = () => {
    console.log('some event content here...')
}
elements.forEach((item) => {
    item.addEventListener('click', clickEvent)
});
Enter fullscreen mode Exit fullscreen mode

However, in older browsers like Internet Explorer, forEach doesn't exist on NodeLists. Although this is not an issue in the modern day, you may find code where the result of querySelectorAll is changed into an array and looped through. This achieves the same thing, but it means that we are looping through arrays, not NodeLists.

let elements = document.querySelectorAll('.button');

let clickEvent = () => {
    console.log('some event content here...')
}
Array.prototype.forEach.call(elements, (item) => {
    item.addEventListener('click', clickEvent);
});
Enter fullscreen mode Exit fullscreen mode

Event Delegation Method

Another way to achieve this is via event delegation. This is where you assign the click event to a parent and then check where in the parent the user clicked. This can be done since events bubble up to parent elements in Javascript. This also means you only have to write one function, instead of looping through and adding your function to multiple elements.

For this to work, we use e.target to track which element was actually clicked - since e.target holds information on the DOM element the initial event was fired from.

Given we have some HTML like this:

<div id="holder">
    <div class="button">Hi</div>
    <div class="button">Hi</div>
    <div class="button">Hi</div>
</div>
Enter fullscreen mode Exit fullscreen mode

We can add an event to #holder and check if any item with the class .button was clicked. This is more efficient and means we don't have to use a for or forEach loop:

let element = document.getElementById('holder');

element.addEventListener('click', (e) => {
  if(e.target.classList.contains('button')) {
    console.log('some event content here...');
  }
});
Enter fullscreen mode Exit fullscreen mode

Top comments (5)

Collapse
 
zyabxwcd profile image
Akash • Edited

Nice little post. Cheers!
another option can be attaching event listener like this
$(document).on('click', '.button', listenerFn)
I am unaware of its equivalent in pure JS. This too comes under event delegation I suppose. One advantage I find using this is, it makes finding event listeners, if any, attached on elements we are interested in pretty straight forward, easy and unambiguous.
This is also more specific in the sense that attaching listener on the parent will call the listener function on every click on the parent and any of the other unwanted children element. To prevent this we have put a conditional inside the listener, which not only increases the mental overhead for a dev reading it but the underlying fact is that the listener does gets called.
I have my doubts on the efficiency of attaching the listener on the document and then giving it a class for filtering but I have found it to be pretty dev friendly because of the reasons mentioned above and also since it doesn't depend upon whether the element we want to attach the listener on is rendered on the DOM yet or not.

Collapse
 
jonrandy profile image
Jon Randy 🎖️

Event delegation would probably be a better, more memory efficient solution here

Collapse
 
smpnjn profile image
Johnny Simpson

you know, I never knew that that approach was called "event delegation". Both are indeed valid though, I will add that to this article if I find a free moment!

Collapse
 
jonrandy profile image
Jon Randy 🎖️ • Edited

If you're not going to go the delegation way, it's probably a good idea to attach the same event handler to each element instead of creating an identical function for each of them. That's just wasteful

Thread Thread
 
smpnjn profile image
Johnny Simpson

Have updated