Bubbling in JavaScript: What It's Good For

| Comments

Following on from the post about JavaScript event handlers, this post focuses on event delegation, a useful technique made possible by bubbling.

Consider the following markup:

1
2
3
4
5
<ul>
  <li>Apples</li>
  <li>Pears</li>
  <li>Bananas</li>
</ul>

There are two ways to register an event listener on the li elements:

  1. Once
  2. As many times as the list is long

We can register an event listener once, on a common ancestor - the ul element would do nicely here - and listen out for events from there. Or, we can register a listener on each li element.

Basically, we have a choice between less work and more work. Bubbling gives us this choice; an event bubbles up the family tree - all the way to the window object - and any of the elements along the way may be instructed to listen out for it.

ONCE AND ONLY ONCE

Once and Only Once is a core principal of software development. An explosion of event listeners in a web page has a cost - a measurable performance loss if lists are long enough / sites are big enough. To keep things peppy (and site visitors happy), it seems we must keep our event listeners to a minimum. Once and Only Once also improves programmer performance.

This is because fewer event listeners means clearer code that’s easier to maintain. We typically set our event listeners as soon as the Document Object Model (DOM) has loaded. If we do things right, we don’t need to worry about attaching event listeners to li elements created on the fly after this point. Here’s a silly example to illustrate:

  • Apples
  • Pears
  • Bananas

Thanks to bubbling, we can listen for click events on newly created grapes li elements the future proof way. Our event listener is set on the ul element, so no worries…

1
2
3
4
5
6
7
var list = document.getElementById('fruits');

list.addEventListener('click', function(event){
  if(event.target.innerHTML === 'Grapes (CLICK ME!)'){
    alert('I love Grapes!');
  }
}, false);

TELL MY PARENTS (AND ANCESTORS)

There may be multiple scripts listening out for events on the same DOM element; if so, we don’t want to be rude and stop up the bubbling action (by calling stopPropagation() on the event object). Telling the parents / ancestors is generally important. What we’ll invariably want to do, though, is prevent the browser from performing its default action. This is how we stop our form (above) from loading a new page whenever Add Grapes is pressed:

1
2
3
4
5
6
7
8
  form.addEventListener('submit', function(event){
    var newLi = document.createElement('li');
    newLi.innerHTML = 'Grapes (CLICK ME!)';
    list.insertBefore(newLi, list.firstChild);

    // PREVENT DEFAULT
    event.preventDefault();
  }, false);

Notice we pass false as the final parameter when calling addEventListener. This parameter, which takes a boolean value, decides whether we’re using bubbling (false), or trickling / capturing (true). Trickling / capturing is bubbling in reverse (top-down). Bubbling makes more sense and is supported by all modern browsers (pre-IE9 browsers don’t support trickling / capturing).

Comments