DEV Community

Cover image for A Guide to Handling Browser Events
Sarah Chima
Sarah Chima

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

A Guide to Handling Browser Events

Cover Photo by Ambitious Creative Co. - Rick Barrett on Unsplash

Most web pages are interactive and users can perform actions on the web page. Examples of actions are clicking on a link, submitting a form or even scrolling through the web page.

There are times when you might want your page to behave in a certain way when the user performs a certain action. For instance, when a user clicks a button, you might want to display a modal. Or when a user fills a form on the page you might want to show the user if the input is valid. These actions are browser events.

In this article, we will discuss how we can handle events that occur in a browser. We will also discuss how to prevent default actions and what event propagation is. Let start right away.

An Introduction to Event Handlers

When an event occurs, we can tell the browser how to respond using event handlers. An event handler is a function that runs when an event occurs. There are three ways we add event handlers:

  1. Inline Event Handlers using HTML attribute
  2. DOM property
  3. Using the addEventListener method

Inline Event Handlers

A handler can be set using an HTML attribute. For instance, if we want a button to alert 'hi' whenever a user clicks the button we can do this:

<button onclick="alert('hi')">Say Hi</button>
Enter fullscreen mode Exit fullscreen mode

We can also do this for a lot of other events using the same syntax.

Writing a lot of code in an HTML element might not be convenient. We can also write a function in a script and call the function in the on<event> attribute.

<button onclick="sayHi()">Say Hi</button>

<script>
    function sayHi() {
        console.log('Hi')
    }
</script>
Enter fullscreen mode Exit fullscreen mode

DOM Property

Another way to handle an event is by using the DOM property. Let us use the example above to do this.

<button>Say Hi</button>

<script>
    var button = document.querySelector('button');
    button.onclick = () => {
        console.log('hi')
    }
</script>
Enter fullscreen mode Exit fullscreen mode

This produces the same result as the previous method.

What happens when we apply an inline event handler and a JavaScript handler to the same element?

<button onclick="console.log('hi')">Say Hi</button>

<script>
    var button = document.querySelector('button');
    button.onclick = () => {
        console.log('no')
    }
</script>
Enter fullscreen mode Exit fullscreen mode

The JavaScript handler takes precedence over the inline handler so 'no' is logged.

The addEventListener method

We can use the addEventListener method to listen for an event on a particular DOM element. Using this method, we provide a method that handles such an event. One advantage of this method is that it allows you to register as many events as possible on an element.

This is the syntax of the addEventListener method.

target.addEventListener(type, function, useCapture)
Enter fullscreen mode Exit fullscreen mode

Target is the element you want to listen for an event on.

Type is the type of event you want to listen for. For example, if it is a button, you can listen for a click event.

The function is the event handler. It specifies the function is carried out when an event occurs.

useCapture is optional. It is a boolean that specifies whether an event should be used in the capturing or bubbling phase. If it is true, it is used in the capturing phase. If it is false (default value), it is used in the bubbling phase. We will touch more on this later.

Here is an example of using this method.

<button>Say Hi</button>

<script>
    let button = document.querySelector('button');
    button.addEventListener('click', () => {
        console.log('Hi') //Hi
    })
</script>
Enter fullscreen mode Exit fullscreen mode

In this case, button is my target, the type of event is click, the arrow function is the function that is executed when this code runs. If you run this code, anytime the button is clicked, you get 'Hi' in the console.

A named function can also be passed as the callback function.

The Event Object

When an event occurs, the browser creates an event object and passes it as an argument to the event handler. This event object contains details of the event. For instance, you might want to know which button was clicked or which key was pressed or the mouse coordinates when the event occurred. You can get all of these from the event object.

Using our example above, we can do this:

<button>Say Hi</button>

<script>
    let button = document.querySelector('button');
    button.addEventListener('click', (event) => {
        console.log(event.clientX) //41
        console.log(event.clientY) //19
    })
</script>

Enter fullscreen mode Exit fullscreen mode

It prints the x any y-coordinates of where the event occurred which in this case is 41 and 19 respectively.

The information stored in the event depends on the type of event that occurred. To get full details of the event object, you can log the event object to the console console.log(event). In any case, it always has a type property that tells the type of event that occurred.

Default Actions

When an event occurs on some elements, there are actions that take place by default. For instance, when a link is clicked, it takes you to the link's target. When a button inside a form is clicked, the form is submitted. These are default actions of the browser.

Event handlers are called for most events before the default takes place. It is possible to prevent the default action using the event.preventDefault() method. This method stops the default action from taking place. Here is an example:

<a href="https://sarahchima.com">Sarah Chima</a>

<script>
    let link = document.querySelector('a');
    link.addEventListener('click', (event) => {
        console.log("Nope, you ain't visiting her today")
        event.preventDefault()
    })
</script>

Enter fullscreen mode Exit fullscreen mode

When this link is clicked, it does not take you to the expected URL, rather it logs the statement into the console.

This can also be used to implement keyboard shortcuts on a page or a context menu.

However, for good user experience, it is best to avoid intercepting expected behaviour of an element. Such actions might make the user think that your website is broken.

Events and Propagation

When an event occurs on an element, the event handler on the target element first runs. Then the handler of its immediate parent, if any, runs and that of its parent's parent all the way up to outermost element that has a handler. This is called propagation.

An example will do some good here.

<style>
    * {
        border: 1px solid green;
        margin: 15px
    }
</style>
<div onclick="alert('div')"> DIV
    <p onclick="alert('p')"> P 
        <span onclick="alert('span')"> SPAN</span>
    </p>
</div>
Enter fullscreen mode Exit fullscreen mode

I added a little style to differentiate the elements. If you run this HTML code and click on the span element, you will notice that three handlers are fired. First, the one on the span, next, the one on the p tag and then the one on the div tag. This because when an event occurs it propagates to the outer elements and their handlers are also triggered.

There are two types of propagation that can occur in an element: Bubbling and Capturing. We have seen this before when we were discussing the syntax of the addEventListener method.

The difference between bubbling and capturing is that in the bubbling phase, events propagate outwards. That is, the handler on the innermost element gets triggered first, then the parent all the way up.

In capturing, the reverse occurs. The handler on the outermost element is triggered first and then the child handler all the way down to the element where the event occurred. This is the reverse of what occurred in our example.

For event handlers, bubbling is the default but if the capture argument is set to true, then capturing occurs. To learn more about this, you can read this article on it.

You might not want this propagation behaviour to occur when you click on an element. Thankfully, JavaScript provides a way to do so. To stop propagation, you can call the event.stopPropagation() method. This method prevents events from propagating.

In our previous example, we can do this:

<div onclick="alert('div')"> DIV
    <p onclick="alert('p')"> P 
        <span> SPAN</span>
    </p>
</div>

<script>
    var span = document.querySelector('span');
    span.addEventListener('click', (event) => {
        alert('span')
        event.stopPropagation();
    }
</script>
Enter fullscreen mode Exit fullscreen mode

If you run this code, you will notice that only the handler on the span is triggered.

As with all default behaviours, preventing propagation should only be done when it is necessary.

One last thing before we go.

Removing an Event Listener

It is possible to remove an event listener using the removeEventListener method. We use it the same way we use the addEventListener method. We pass in the type and the function just the same way. However, the function must be stored in a variable for it to work.

Here is an example

<button>Say Hi</button>

<script>
    let button = document.querySelector('button');

    function sayHi() {
        console.log('Hi')
    }
    button.addEventListener('click', sayHi)
    button.removeEventListener('click', sayHi)
</script>
Enter fullscreen mode Exit fullscreen mode

Run this code and notice that the event listener function never runs. This is because the eventListener has been removed.

If we do not store the function in a variable like in the example below, the removeEventListener will not work.

<button>Say Hi</button>

<script>
    let button = document.querySelector('button');
    button.addEventListener('click', () => {
        console.log('hi')
    })
    button.removeEventListener('click',  () => {
        console.log('hi')
    })
</script>
Enter fullscreen mode Exit fullscreen mode

Conclusion

We discussed how we can handle events using HTML attributes, DOM property and the addEventListener method. We saw that we can prevent default actions by using the event.preventDefault method. We also learnt about event propagation.

Handling events can help you control the way the browser responds to user actions and we have discussed how we can do this. In my next article, I will discuss common events that you can listen for in a browser.

Got any question or addition? Leave a comment.

Thank you for reading 😊.

Top comments (15)

Collapse
 
karataev profile image
Eugene Karataev

Thanks for the article, Sarah.
One question about the browser events bothers me for some time. You mentioned the third parameter useCapture when adding a listener to a DOM node. When readng fellow developers code sometimes I see boolean values true/false, sometimes objects {useCapture: true}. What's the difference between them? And what was the purpose to implement both options in browsers if there is no difference between them?

Collapse
 
nickytonline profile image
Nick Taylor

Sarah mentions it in the article. 😀

"The difference between bubbling and capturing is that in the bubbling phase, events propagate outwards. That is, the handler on the innermost element gets triggered first, then the parent all the way up.

In capturing, the reverse occurs. The handler on the outermost element is triggered first and then the child handler all the way down to the element where the event occurred. "

Collapse
 
karataev profile image
Eugene Karataev

I mean there are two ways to add event listener in capture phase:

  1. node.addEventListener('click', e => console.log(e.eventPhase), true);
  2. node.addEventListener('click', e => console.log(e.eventPhase), {capture: true});

The result will be the same.
So my question was: is there any difference in these statements or they do exactly the same thing?

Thread Thread
 
sarah_chima profile image
Sarah Chima

Hi Eugene.

They do exactly the same thing. true is a shorthand for {capture: true}.

Thanks for reading my article.

Thread Thread
 
nickytonline profile image
Nick Taylor

Ahh, sorry misunderstood what you were asking. 🙃 I believe Sarah just answered you.

Collapse
 
thefoxboxman profile image
John De Costa

Excellent explanation Sarah. Keep up this great work.

Collapse
 
sarah_chima profile image
Sarah Chima

Thank you John.

Collapse
 
budyk profile image
Budy • Edited

Thanks for sharing.. Just want to add, using arrow function as eventListener callback will make the element lose the this context...

var span = document.querySelector('span');
span.addEventListener('click', (event) => {
   console.log(this); // Window Object
}

compared to this

var span = document.querySelector('span');
span.addEventListener('click', function(event) {
   console.log(this); // Element Object
   this.innerHTML = 'Hola';
}
Collapse
 
fc250152 profile image
Nando

Budy, sorry but I don't notice any difference in the two snippets ... maybe the latter should be a regular function declaration such as "function(event) {...}" ?
thank you, happy Easter!

Collapse
 
budyk profile image
Budy

Sorry, I've updated the snippet :)

Collapse
 
wesgrimes profile image
Wes

Nice summary 👌

Collapse
 
nickytonline profile image
Nick Taylor

Great explanation Sarah. Bubbling is so powerful because it allows you to reduce the potential number of events added to a page. Too many events can bog down a site.

I did a hello world in web components a couple of years ago, but haven't touched them since. Thanks for the refresher!

I'd just make one suggestion about adding event listeners. This is not web component specific, but for adding event listeners in general. In the case of the rainbow-text component, the number of <span />s increases for every additional letter in the text attribute, so the number of event listeners per instance of the component is n letters * 2 (mouse over + animation end events).

You can end up with a lot of events very quickly just for one instance of the component. What you can do is add an event listener for each event type on the parent <div /> you create in aspittel/rainbow-word-webcomponent and then the power of event bubbling is your friend.

e.g.

class RainbowText extends HTMLElement {
  …

  addEventListeners(div) {
    div.addEventListener("mouseover", e => {
      const { target } = e;

      if (target.tagName === "SPAN") {
        target.classList.add("hovered");
        console.log(`mousing over ${target.tagName}`);
      }
    });

    div.addEventListener("animationend", e => {
      const { target } = e;

      if (target.tagName === "SPAN") {
        target.classList.remove("hovered");
        console.log(`mousing over ${target.tagName}`);
      }
    });
  }

  …

  render() {
    const div = document.createElement("div");
    div.classList.add("header");
    this.addEventListeners(div);
    this.shadowRoot.appendChild(div);
    this.addSpans(div);
    this.addStyle();
  }
}
Collapse
 
madiallo profile image
Mamadou Alpha Diallo

great code

Collapse
 
fc250152 profile image
Nando

thank you, Sarah. simple and clear. have nice days

Collapse
 
annarankin profile image
Anna Rankin

This is a great overview, thank you Sarah! Always love to see great vanilla JS resources.