Javascript Add Event Listener to Multiple Elements
Tags: javascript, typescript, webdev, beginners
Published: 2022-10-20 20:11:41

If you've ever worked in vanilla Javascript, you might be familiar with adding event listeners to elements using the following formula:
```javascript
let element = document.querySelector('#button');

element.addEventListener('click', () => {
    console.log('some event content here...')
})
```

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:
```javascript
let elements = document.querySelectorAll('.button');
```


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](https://fjolt.com/article/javascript-foreach-on-queryselectorall). 

In modern browsers, `NodeList`s 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:
```javascript
let elements = document.querySelectorAll('.button');

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

**However**, in older browsers like Internet Explorer, `forEach` doesn't exist on `NodeList`s. 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 `NodeList`s. 
```javascript
let elements = document.querySelectorAll('.button');

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

## 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:
```html
<div id="holder">
    <div class="button">Hi</div>
    <div class="button">Hi</div>
    <div class="button">Hi</div>
</div>
```

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:
```javascript
let element = document.getElementById('holder');

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