addEventListener
Tags: webdev, javascript, beginners, tutorial
Published: 2022-11-06 15:08:26

`addEventListener` is an easy way to start making our page interactive. In this article, we will learn how to `addEventListener` and what you should know. You can follow this article with your favorite code editor or on [stackblitz](https://stackblitz.com/). You can use this [template](https://stackblitz.com/edit/web-platform-gna4vq?file=styles.css,index.html,script.js) that I crated on stackblitz.

## Introduction
Events happen all the time in the browser. When we call `addEventListener` on a target, we are telling the browser that "Hey, from now on when that event is triggered by this target, I want to do something". Later when the event is triggered by the target, browser will run our instruction with the latest information from that event.

## How to add event listener
We can add event listener on DOM elements (The things you see on the page). Let's create an element in HTML and select it with JavaScript first.

index.html
```html
<body>
  <div>hello</div>
</body>
```

main.js
```js
const div = document.querySelector('div');
console.log(div);
```
Open stackblitz console
![stackblitz console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qw597y603pfgqpuzxj3e.gif)


If you want to use the browser console, you need to select an element with devtools(press F12 to open) and switch to the console tab as shown below. 
![read console.log on stackblitz](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/47fnyum743s61qe3mx3u.gif)
We need to do this step _every_ time we refresh the page. By the way, pressing `Ctrl` + `S` to save the file will auto refresh the page.

Now we have an element, we can add event listener on it. `addEventListener` is a function available on DOM elements. In other words, we have an object that has a method `addEventListener`, we can use it like this `div.addEventListener()`. `addEventListener` first parameter is a string represents the event type we want to listen. The second parameter is a callback [function](https://dev.to/gohomewho/function-5fgn) which is the instruction for browser to do when the event is triggered.

main.js
```js
div.addEventListener('click', () => {
  console.log('Yay!');
});
```

Each time we click on the element, the callback function will be run.
![click on the element three times](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z80744szu074md9d1y9c.gif)

`addEventListener` accept a third parameter to configure its behavior. For example, we can make a event listener that will only be run once like this. 

main.js
```js
// configure this event listener with { once: true }
div.addEventListener(
  'click',
  () => {
    console.log('Yay!');
  },
  {
    once: true,
  }
);
```

Now no matter how hard you press, The callback function will only be run once.
![many clicks but callback only run once](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdewtd1jmv7kyj5wut3t.gif)

We can add many event listeners on the same element.

main.js
```js
// add different event listeners to a div
div.addEventListener('click', () => console.log('click'));
div.addEventListener('mousemove', () => console.log('mousemove'));
div.addEventListener('mouseover', () => console.log('mouseover'));
div.addEventListener('mouseout', () => console.log('mouseout'));
```

![trigger many events](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6jp5no0sqsfmol3s9xld.gif)

We can add many event listeners of the same event type. 
main.js
```js
// add three 'click' event listeners to a div
div.addEventListener('click', () => console.log('a'));
div.addEventListener('click', () => console.log('b'));
div.addEventListener('click', () => console.log('c'));
```

Whenever click event is triggered, callback functions are run in the order we add them.

![add many event listeners on the same event type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r3y25v4crmyswuijea9z.gif)

---


## Event information
Our callback functions are actually called with one argument, an object which contains the information of the event. If we need to use those information, we should make our function accept a parameter, otherwise we can omit it. 

main.js
```js
// provide a callback that accepts a parameter
div.addEventListener('click', (e) => console.log(e));
```
You can briefly read what it logs.


---


## Event propagation
When we click on an element, it is not only one click event trigged by the element. There are actually three phrases occur during a click action that can trigger many more click events than you expected. 

1.Capturing phrase

> The event is being propagated through the target's ancestor objects. This process starts with the Window, then Document, then the HTMLHtmlElement, and so on through the elements until the target's parent is reached.


2.At target

> The event has arrived at the event's target. Event listeners registered for this phase are called at this time. If Event.bubbles is false, processing the event is finished after this phase is complete.


3.Bubbling phrase

> The event is propagating back up through the target's ancestors in reverse order, starting with the parent, and eventually reaching the containing Window. This is known as bubbling, and occurs only if Event.bubbles is true.

To simply put, the target that we click on might not be the element that we add listener on, it can also be one of the element's descendant because of bubbling. One thing to remember is that events are not created by the `addEventListener`. What it does is that we want to listen to a small part of all events that happening behind the scene.

Let's see some examples.

index.html
```html
<div class="a">
  a
  <div class="b">
    b
    <div class="c">
     c
    </div>
  </div>
</div>
```

Add some CSS for visualization.

styles.css
```css
div {
  padding: 16px;
  margin: 8px;
  border: 1px solid;
}
```

Add a click event listener on `a` element.

main.js
```js
const a = document.querySelector('.a');
const b = document.querySelector('.b');
const c = document.querySelector('.c');

a.addEventListener('click', () => {
  console.log('click');
});
```

We can see that click on `a` or `b` or `c`, all trigger the click event  on `a`. Clicking outside of `a` doesn't trigger the click event, which is quite reasonable.

![click on `a` or `b` or `c`, all trigger click on `a`](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j8qaqsyvtw60tmuun06g.gif)

Let's add a click event listener on `c` element which is a descendant of `a`.

main.js
```js
c.addEventListener('click', () => {
  console.log('c');
});
```

In modern browsers, by default, all event handlers are registered for the bubbling phase. That's why we see 'c' -> 'a' instead of 'a' -> 'c' when we click on `c` element.

![click on a and c to see how it works](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xb6m8ihr3ivfkxqdf8o6.gif)

We can configure `addEventListener` to register event handlers for capturing phrase. We can provide a third argument to `addEventListener`. It can be a boolean value `true` or an object that has `capture: true`. This doesn't mean that the event handlers will only be run in the capturing phrase. It is saying that run the event handlers during capturing phrase whenever possible.

main.js
```js
a.addEventListener(
  'click',
  () => {
    console.log('a');
  },
  true // a boolean
);

c.addEventListener(
  'click',
  () => {
    console.log('c');
  },
  { capture: true } // an object
);
```

We click on `c`, it runs the `a` event handler first because it is registered for capturing phrase.

![click on c run a event handler first ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8sqpv03qmfty353c5dec.gif)

We can read [eventPhrase](https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase) from the event information to see during which phrase that the event handler is triggered. Capturing phrase is 1. At target is 2. Bubbling phrase is 3.

main.js
```js
// a will run in target phrase or capturing phrase if possible.
a.addEventListener(
  'click',
  (e) => {
    console.log('a', e.eventPhase);
  },
  true
);

// b will run in target phrase or bubbling phrase if possible.
b.addEventListener('click', (e) => {
  console.log('b', e.eventPhase);
});

// c will run in target phrase or capturing phrase if possible.
c.addEventListener(
  'click',
  (e) => {
    console.log('c', e.eventPhase);
  },
  { capture: true }
);
```

![eventPhase](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1qzb18cnifp3qndn3066.gif)

Some events don't bubble. For example, `focus` and `blur`. 
> All JavaScript events go through the capturing and target phases. Whether an event enters the bubbling phase can be checked by the read-only bubbles property.

Sometimes we don't want this event propagation behavior. We can prevent it by calling `e.stopPropagation()` or `e.stopImmediatePropagation()` on the event object.

`e.stopPropagation()` stops event from propagating to other elements.

main.js
```js
a.addEventListener(
  'click',
  (e) => {
    e.stopPropagation(); // stops propagating 
    console.log('a');
  },
  true // capture 
);

b.addEventListener('click', (e) => {
  console.log('b');
});

c.addEventListener('click', (e) => {
  console.log('c1');
});

c.addEventListener('click', (e) => {
  console.log('c2');
});

c.addEventListener('click', (e) => {
  console.log('c3');
});
```

Click on `a` or `b` or `c` all trigger the event handler on `a` first. We call `e.stopPropagation()` in the event handler, so event handlers on `b` and `c` will not run because event propagation is stopped.
![e.stopPropagation()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3f66z31qbk962ju1tfsu.gif)


`e.stopImmediatePropagation()` does what `e.stopPropagation()` does but also stops other event handlers on the target element. Let's see an example with `e.stopPropagation()` first and then replace it with `e.stopImmediatePropagation()` next.

main.js
```js
a.addEventListener(
  'click',
  (e) => {
    console.log('a');
  },
  true // capture
);

b.addEventListener('click', (e) => {
  console.log('b');
});

c.addEventListener('click', (e) => {
  console.log('c1');
  e.stopPropagation(); // stops propagation here
});

c.addEventListener('click', (e) => {
  console.log('c2');
});

c.addEventListener('click', (e) => {
  console.log('c3');
});
```

Click on `c`, the event handlers on `a` run first because it captures, the event reach the target `c` and all `c` event handlers are called. `b` event handler doesn't run because event doesn't bubble to it by calling `e.stopPropagation()` in one of the c event handlers
![e.stopPropagation() example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xmmjylywdindh90uhq78.gif)

Let's simply replace `e.stopPropagation()` in the example with `e.stopImmediatePropagation()` 
```js
c.addEventListener('click', (e) => {
  console.log('c1');
  // e.stopPropagation(); change this
  e.stopImmediatePropagation(); // to this
});
```

This time only one event handler of `c` that is before `e.stopImmediatePropagation()` is called, others are stopped. So `e.stopImmediatePropagation()` not only stops the event propagation, it also stops the rest of the event handlers of the target from running.
![e.stopImmediatePropagation()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k43mw9c8b3bvkwdbwz0x.gif)


---


## Event delegation
Event delegation is a pattern that utilizes event propagation. We know that event can propagate to ancestor elements. If we have 1000 list items that behave in a similar way, we can add event listeners on their common ancestor rather than add event listeners on each item.

Let's see how it works with examples.

styles.css
```css
div {
  margin: 4px;
  padding: 4px;
  border: 1px solid;
}
```

main.js
```js
function createItems() {
  // make items HTML
  const divsHTML = Array.from({ length: 10 })
    .map((a, index) => {
      return `<div class="item">${index}</div>`;
    })
    .join('');

  // wrap items in an element and add them to body
  document.body.innerHTML = `
    <div class="list">
      ${divsHTML}
    </div>`;
}

createItems();
```

Add event listener to each item. When each item are clicked, `doSomething(item)` will be called with the item. 

main.js
```js
function doSomething(item) {
  console.log('do something with this item', item);
}

const items = document.querySelectorAll('.item');
items.forEach((item) => {
  item.addEventListener('click', () => {
    doSomething(item);
  });
});
```

When we click on each item, `doSomething()` is called with the item that was clicked.
![click item](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ve00kppr60sfkh7m7k48.gif)

Let's use event delegation to add a event listener on items' common ancestor `.list`, so we can add only one event listener instead of 10.

main.js
```js
function doSomething(item) {
  console.log('do something with this item', item);
}

const list = document.querySelector('.list');
list.addEventListener('click', (e) => {
  // We can tell which element trigger the event by accessing `e.target`
  doSomething(e.target);
});
```

This would work, but it has a problem. `doSomething` expect to be called with an item. In this example, we call `doSomething()` with `e.target`, but e.target doesn't guarantee to be the item. It can be anything that can trigger the event handler. It can be the `.list` element itself or other elements inside `.list`. 

Update the `createItems` function
```js
function createItems() {
  const divsHTML = Array.from({ length: 10 })
    .map((a, index) => {
      return `
      <div class="item">
        ${index}
        <button>a feature</button> // add a button here
      </div>`;
    })
    .join('');

  document.body.innerHTML = `
    <div class="list">
      ${divsHTML}
    </div>`;
}
```

When we click on the `.list` itself, an item, and a button inside the item, They all trigger the event handler on `.list`. But the `e.target` are different. This means that we can't simply pass e.target without any check to a function that expects an item. That would break our app.
![e.target can be different](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vzhjyrrlknfhp8l7896o.gif)

Luckily, there are two methods we can call on an element to check if we get the correct target, `element.matches()` and `element.closest()`. Their parameter is the same as `element.querySelector()`. `element.matches(selector)` will return true if it matches the selector. 

main.js
```js
list.addEventListener('click', (e) => {
  // if e.target does not match .item
  // we don't want to run the rest of the code
  if (!e.target.matches('.item')) return;

  doSomething(e.target);
});
```

Now only click on '.item' will trigger the event listener on `.list`. 
![e.target.matches](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9tignz8p0l978f1i7t2y.gif)

Clicking on `.list` and the button doesn't trigger the event handler, since they are not relevant to `doSomething(item)`. This is what we want. What if we have other elements inside an `.item` but clicking on them should be consider clicking on the item. 

Update `createItems` function again
```js
function createItems() {
  // ...
      <div class="item">
        ${index}
        <button>a feature</button>
        <span>lorem</span> // add this line
      </div>`;
 // ...
}
```

Let's say we have some description about the item. So clicking on them can be perceived as clicking on the item. But `span` doesn't match `.item`, the event handler won't run.
![click on lorem doesn't run event handler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m5hvmyxb0zg4rk7iqhz5.gif)

styles.css
```css
/* making all descendants inside item non clickable */
.item * {
  pointer-events: none;
}

/* we can use this on an element inside .item to revert the above declaration. */
.pointer-events-auto {
  pointer-events: auto;
}
```

Add the class to the button inside `createItems` function.

main.js
```js
<button class="pointer-events-auto">a feature</button>
```

Now clicking on the `span` element can trigger the event handler on `.list` that runs doSomething(item). In fact, we are not clicking on the span, we are clicking the `.item`. The span is not clickable because of the CSS rule we set. 
![click on span works](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5vf96saeapp4agcckwk.gif)

Let's try another solution with `element.closest()`. First, remove the CSS we did.

`element.closest(selector)` will return the closest element that matches the selector, including the element itself. This means if we click on any element inside `.item` or `.item` itself, `e.target.closest('.item')` will return the `.item` element.


main.js
```js
list.addEventListener('click', (e) => {
  // find the closest item element from e.target
  const item = e.target.closest('.item');
  // if we don't have an item
  // we don't want to run rest of the code
  if (!item) return;

  doSomething(item);
});
```

Now clicking on every element in `.item` works. But clicking the button shouldn't trigger the doSomething(item), because we have other things for it to do.
![use closest](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tamjxzmrnnki9kfrdsgp.gif)

We can use `element.matches()` and `element.closest()` together.

main.js
```js
function createItems() {
  //...
    // add a class to button so we can filter this element with its selector more precisely
    <button class="item-feature-btn">a feature</button> 
  //...
}

list.addEventListener('click', (e) => {
  // we don't want this handler to run when it is clicked on the button
  if (e.target.matches('.item-feature-btn')) return;
  const item = e.target.closest('.item');
  if (!item) return;
  doSomething(item);
});

// do something when the button is clicked
list.addEventListener('click', (e) => {
  if (!e.target.matches('.item-feature-btn')) return;
  console.log('handle button click');
});
```

Events are handled properly.
![nice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l20t3297qvie6inbq4av.gif)


We can also stop the propagation on button. But in this scenario we use event delegation one the same target. We'll need to consider the order of `addEventListener` and call `e.stopImmediatePropagation()` at the right place. It will be much simpler to do so when we `addEventListener` directly on the button and call `e.stopPropagation()`. 

---


## Wrap up
`addEventListener` is easy to use, but it has a lot of detail that we should be aware of. I hope this article can help you start using `addEventListener` with confidence and be able to identify the problem when something doesn't work as expected.