Preventing element's default action and event propagation

It is interesting how sometimes we just code something and it feel completely right and natural but when it comes to explaining even simple concepts we get stuck. This post will walk you through some simple but very helpful concepts.

preventDefault()

Let’s say we have a form and before submitting it we want to do simple client-side validation.

<form>
  <input type="text" name="username" placeholder="Enter your username" />
  <button>Test</button>
</form>

What we would need to do in this case is prevent the default action of the form, we want to prevent form from submitting. We can do this by using preventDefault method on the Event class.

const form = document.querySelector('form');
form.addEventListener('submit', (e) => {
  e.preventDefault();

  if (e.target.username.value.length) {
    e.target.submit();
  } else {
    e.target.username.style.border  = '1px solid red';
  }
});

Let’s walk through the code.

First, we query the document object and return the first element that matches the form selector. Then we add an event listener to the form which gets triggered on form submission. The second argument is a listener, a callback that accepts a single parameter, an event (e) that describes the event which occurred. The next instruction is to prevent the default action of the event, so in this case we are preventing submission of the form. Lastly, we check then value of the input field and based on the result we either submit or render an error border.

The important point to remember is that by using preventDefault we are preventing the default action of any element, be it a form, a button or a link. We might want to prevent the default action of a link and display a confirmation message before following through to the new location.

<a href="https://google.co.uk">Google</a>
const a = document.querySelector('a');
a.addEventListener('click', (e) => {
  e.preventDefault();

  const link = e.target.getAttribute('href');
  const isConfirmed = confirm('Do you want to the leave the page?');

  if (isConfirmed) {
    window.location = link;
  }
});

stopPropagation()

If an element has children, an event registered on a parent element gets passed down to its children, in other words they will inherit the event. If a child element has an event registered on it, it will have priority over the parent event and parent elements will see the event when it gets triggered. Events propagate outwards from the deepest element to its parent. This is called event bubbling. We can use stopPropagation method to stop an event from bubbling up and the handlers from receiving that event.

Let’s have a look at the example.

<html>
<head>
  <title>Event propagation</title>
</head>
<body>
  <div>
    I am a div
    <p>
      I am a paragraph
      <a href="https://google.co.uk">Visit Google</a>
    </p>
  </div>
  <script>
    const a = document.querySelector('a');
    const p = document.querySelector('p');
    const div = document.querySelector('div');

    a.addEventListener('click', (e) => {
      e.preventDefault();
      console.log('I am an a tag');
    });

    p.addEventListener('click', (e) => {
      e.preventDefault();
      console.log('I am a p tag');
    });

    div.addEventListener('click', (e) => {
      e.preventDefault();
      console.log('I am a div tag');
    });
  </script>
</body>
</html>

We have three elements with event listeners of type click registered on each one. If we click on the link and check the console, there should be 3 messages printed. This shows us that the child event propagates outwards and parent elements events get triggered too. If we want to stop propagation at a certain point, we need to apply stopPropagation method.

p.addEventListener('click', (e) => {
  e.preventDefault();
  e.stopPropagation();

  console.log('I am a p tag');
});

If we want to prevent the default action and stop an event from bubbling up, we need to apply both methods. Which method to use will obviously depend on what you are trying to achieve.