What are Events in JavaScript and How to Handle Them?

What are Events in JavaScript and How to Handle Them?

Originally published on my personal blog.

Events in JavaScript allow developers to listen for changes in the document and handle them as necessary. For example, you can listen to when a button is clicked to open a modal. Another example is showing the "scroll up" button when the user scrolls halfway through the page.

This article helps beginners understand what Events are in JavaScript and how to handle them.

Why Use Events

Modern websites have evolved to be interactive and reactive. Instead of presenting information or functionalities all at once, some can be shown to the user based on a specific action performed. The example mentioned earlier in the article illustrates this.

Events are also helpful in detecting different states of your website. For example, detecting when a screen is resized can help to ensure the responsiveness of certain elements on the page.

The list of use cases where events are helpful is endless. Events are an essential part of web development with JavaScript and it's important to understand how to use them.

Add Event Handler

To listen to events, you can use the addEventListener method. This method is available on every Element and Node in the document.

addEventListener accepts three parameters: the first parameter is the name of the event to listen to; the second parameter is the function that handles this event; and the third parameter is an optional parameter that allows you to set more options for the listener.

In most cases, you only need to use the first two parameters. Here's an example of handling the click event on a button:

const button = document.querySelector("button");
button.addEventListener("click", function (e) {
    alert("Button clicked");
});

This retrieves the first button on the page and, when the button is clicked, shows an alert.

Can an Event Have More Than One Handler?

The same event can have many handlers. The handlers are triggered one by one when the event occurs.

Event Object

As you can see, the function passed as a second parameter to addEventListener received the argument e. This is an Event object. This object may have different properties depending on its underlying type. However, it has some common important properties.

A frequently used property on the Event object is the target property. This is the element that the event was triggered on. This can be helpful when you have the same handler for more than one element.

For example:

document.querySelector("input").addEventListener("change", function (e) {
    alert(e.target.value);
});

This code snippet adds an event handler to the first input element on the change event. The change event is triggered when you change the value of the input, then move the focus from the input (for example, click on something else on the page).

When the change event occurs and the handler is triggered, the value of the input is retrieved using e.target.value and used in an alert.

Prevent Default Behavior of the Event

In some cases, you want to prevent the default behavior of certain events. For example, when a link is clicked on a page, you might want to show a confirmation message before the user is actually taken to a new page.

To prevent the default behavior of an event, you can use the preventDefault() method on the Event object.

For example:

const a = document.querySelector("a.some-link");
a.addEventListener("click", function (e) {
    e.preventDefault();
    if(confirm("Are you sure?")) {
      window.location.href = e.target.href;
    }
});

This adds an event handler to the click events on a link that has the class some-link. In the event handler, e.preventDefault() is used to prevent the default behavior of opening the page indicated in the href attribute.

It then shows the user a confirmation window, and if the user clicks Yes on the confirmation window, it then opens the page as expected.

Prevent Other Handlers from Handling this Event

As mentioned earlier, an event can have more than one handler. However, in some cases, you might want to cancel other events from handling this event. For example, if a request is sent to the server on form submission and you don't want other event handlers of form submission to send more requests.

There are two methods that can be used to prevent other handlers from handling the same event: stopPropagation and stopImmediatePropagation.

stopPropagation is used to prevent other event handlers from handling an event during the bubbling phase.

Bubbling is when you have the same event on both child and parent elements. The event will be triggered first on the child element, then "bubbled" to the parent element.

In this case, stopPropagation prevents the event from being triggered on the parent element if called in the handler of the child element. However, it does not prevent the event from propagating to other event handlers.

To prevent the event from being handled by subsequent handlers, you can use stopImmediatePropagation.

For example:

const button = document.querySelector("button");

button.addEventListener("click", function (e) {
  e.stopImmediatePropagation();
  alert("Hello!");
});

button.addEventListener("click", function (e) {
  alert("Bye!")
})

Although the button has two event handlers that listen to the click event, only the first one will run since it calls the stopImmediatePropagation function.

Difference Between preventDefault, stopPropagation, and stopImmediatePropagation

preventDefault only prevents the default behavior of the event. However, it does not prevent other handlers from handling the event.

stopPropagation and stopImmediatePropagation only prevent other handlers from handling the event. However, they don't prevent the default behavior of the event.

If you want to prevent both the default behavior and other handlers from handling the event, use preventDefault with either stopPropagation or stopImmediatePropagation.

Handle Events on Dynamic Elements

Consider the following example:

const buttons = document.querySelectorAll(".btn");

buttons.forEach((btn) => {
  btn.addEventListener("click", function (e) {
    alert("Hello!");
  });
})

//create new button
const button = document.createElement('button');
button.classList.add('btn');
button.textContent = "Bye";
document.body.append(button);

In this example, you first retrieve all buttons that have the class btn. Then, you loop over these buttons and add an event listener that just shows the alert "Hello" when the button is clicked.

Then, you dynamically create a new button, add the class btn to that button, and add it to the body of the document.

If you try clicking buttons that were added in the HTML of the document or that were added prior to adding the event listener, the alert will be shown as expected. However, if you click on the new button, no alert will be shown.

This is because the element was not part of the elements retrieved using querySelectorAll as it didn't exist at the time.

Although this is a reasonable outcome, in larger websites, you might not be able to attach an event handler every time you create a new element, or you might want to ensure that the event handler is added to all elements, whether originally or dynamically created, similarly.

In that case, instead of adding the event handler directly to each element separately, you can add the event handler to the document's body and use e.target to check if the element triggered matches a specific condition.

For example, you can transform the previous example to the following:

document.body.addEventListener("click", function (e) {
  if (e.target.classList.contains("btn")) {
    alert("Hello!");
  }
})

//create new button
const button = document.createElement('button');
button.classList.add('btn');
button.textContent = "Bye";
document.body.append(button);

You add a handler to the click event on the body. In the handler, you check if e.target has the class btn and only perform the necessary action if it does.

Now, when any of the elements that have the class btn, whether dynamically or initially created, are clicked, the event handler will run for them.

This approach, however, will run the event listener on every click event on the body. So, it's important to handle it based on the target carefully.

Remove Event Handler

In some cases, you might want to remove an event handler for an element. To do that, you can use the removeEventListener method. This method is available on every Element and Node in the document.

This method receives the same parameters addEventListener receives. It's important to pass the same function that you passed to addEventListener. For that reason, people commonly define the function separately, then pass it to addEventListener and removeEventListener.

For example:

const button = document.querySelector("button");

function handleClick (e) {
  alert("Hello");
}

button.addEventListener("click", handleClick);

//some time later
button.removeEventListener("click", handleClick);

Conclusion

This article scratches the surface of Events and helps you understand them better. To learn more about Events in JavaScript, check out the following resources:

  1. Introduction to Events
  2. Event Reference