Events

What was the difference between target and currentTarget again?
— Seconds before disaster

The Basics

Events can be used to react to "interesting changes" like user input. For example, you might want to do something if the user clicks a button, presses a key or resizes the window.

Consider the following simple HTML example:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Example</title>
  </head>
  <body>
    <a id="example-link" href="http://example.com/">Go to example page</a>
    <button id="example-button">Click me!</button>
    <div id="parent">
      <button id="child">Click the child!</button>
    </div>
    <script>
      /* Our JavaScript code will go here. Note that the script tag must be at the end of the body. */
    </script>
  </body>
</html>

We can use the addEventListener to register an event listener on the button to set up a function that should be called if the button is clicked.

The addEventListener function takes two arguments. The first argument is the event to listen to (which in this case will be 'click'). The second argument is a function that takes the specific event and handles it.

document.querySelector('#example-button').addEventListener('click', () => {
  console.log('Button was clicked!');
});

If you click the button, the text Button was clicked! will appear in the console.

The addEventListener is a typical example of a higher-order function since it takes a function as one of its arguments.

You can remove an event listener using the removeEventListener function:

const clickFunction = () => {
  console.log('Button was clicked!');
};

document.querySelector('#example-button').addEventListener('click', clickFunction);
document.querySelector('#example-button').removeEventListener('click', clickFunction);

The registered function can take the event object as an argument which is useful if you need to gather more information about the event:

document.querySelector('#example-button').addEventListener('click', (e) => {
  console.log(e);
});

Preventing Default Actions

Many events have default actions. For example, clicking a link will navigate you to a new page.

You can prevent the default action using event.preventDefault():

document.querySelector('#example-link').addEventListener('click', (e) => {
  e.preventDefault();
  console.log("You can't click this link, harharhar.");
});

While you should definitely not break links in this manner, event.preventDefault() is useful with other elements as we will see in the next sections.

Event Targets

An event object has two important properties that are a common source of confusion for beginners—target and currentTarget.

The target is the element that triggered the event.

However, the currentTarget is a reference to the element to which the event handler was attached in the first place. Put simply, currentTarget is the element that listens to the event.

The target and currentTarget can be the same element if the element that triggers the event is also the element that listens to the event. However, that doesn't have to be the case.

Consider our setup with a <div> element containing a <button>:

<div id="parent">
  <button id="child">Click the child!</button>
</div>

Let's put a click event listener on the parent <div>:

document.querySelector('#parent').addEventListener('click', (e) => {
  console.log('event.target:', e.target);
  console.log('event.currentTarget:', e.currentTarget);
});

Here, the target would be the child <button> because that was the element that actually triggered the event. After all, you click the child <button> and not the parent <div>.

However, the currentTarget would be the parent <div> because that was the element to which we attached the event handler.

You can verify this yourself. If you click the child button, you will see that event.target refers to the child <button> while event.currentTarget refers to the parent <div>.

It's also important to note that the value of event.currentTarget is only available while the event is being handled. This means that if you console.log() the event, store in a variable and then look at event.currentTarget in the console, you will get null. This can be extremely confusing if you're trying to debug events.

A Simple Example

Here is how we could create a button that dynamically adds a new task every time we click it:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Example</title>
  </head>
  <body>
    <div id="task-list">
      <button id="addTask">Add a task</button>
    </div>
    <script>
      function addTask() {
        const paragraph = document.createElement('p');
        paragraph.classList.add('task');
        paragraph.innerHTML = 'New task';
        const taskList = document.querySelector('#task-list');
        taskList.appendChild(paragraph);
      }
      document.querySelector('#task-list').addEventListener('click', () => {
        addTask();
      });
    </script>
  </body>
</html>

Note that all the new tasks will be lost if you refresh the page or close your browser. That's because they are currently only stored in the client (and not on the server).