Event Propagation & Event delegation

What are browser events?

Marju Hirsh
6 min readJul 26, 2018

HTML events are things that happen to HTML elements. They can be something the browser does, or something a user does.

This results in calling a web API to instantiate an event object with the interface defining the type of event that needs to be fired.

addEventListener()gives access to the Event object, logging it out shows all the properties and methods it has.

If an event listener is set with JS, it can run a callback as a reaction to these events when they are detected

Here are some examples of HTML events:

  • An HTML web page has finished loading
  • An HTML input field was changed
  • An HTML button was clicked

What is event propagation?

<div>
<ul>
<li></li>
<li></li>
</ul>
<div>

HTML elements on a page are nested instead of covering each other, this means that when a <li> is clicked, every parent element receives a click event as well. That, in a nutshell is event propagation.

Event instance has a path property which gives access to ancestor nodes

console.log(e.path) on button1 click puts out an array of all the ancestors of the the event target

Propagation has 3 phases.

  1. Capturing phase — the event is first registered with the outermost element, then it propagates down the DOM tree towards target element through it’s child nodes running event handlers on all elements which have an event handler registered (turned off by default).
  2. Target phase — Event is registered with the target, if there are event handlers, they are run.
  3. Bubbling phase —The event is registered with the target element, handler is run, then all the parent nodes of the target consecutively receive the event and handlers attached to that event+parent element are run accordingly.

Bubbling Phase

HTML set up with nested elements.
Event handlers for click event added to a button and all the parent elements.
Visible page
Output on console when button 1 was clicked.
Output on console when the light-blue button container was clicked.

Capturing phase is turned off by default so event handlers are not triggered. To catch an event on the capturing phase, the third argument of addEventListener() should be set to true.

There are two possible values for that optional last argument of addEventListener():

  • If it’s false or omitted, then the handler is set on the bubbling phase.
  • If it’s true, then the handler is set on the capturing phase.
The third argument (useCapture argument) of addEventListener is set to true

This time when useCapture argument was set to true on each element’s addEventListener() and button 1 was clicked, the first element which triggered the handler was the outermost element with click event listener which was <body>, then the event propagated down the three structure running click event handlers for all the child elements until reaching the target.

Events capture and bubble for historical reasons. During the time of “browser wars” Different browsers had conflicting ways to deal with propagation. Microsoft and Netscape developers agreed that the parent elements should be aware if an event has occurred on a child element but disagreed on which order should they be notified.

One model was event capture (advocated by the Netscape developers). This notified the html element first and worked its way down the tree.

Microsoft supported event bubbling which notified the target element first, and worked its way up the tree, Netscape’s model notified html first and went down the structure towards the target.

The eventual compromise was that both bubbling and capturing should be the thing, so the event works its way down the tree and then back up again.

stopPropagation()?

Nested elements might have the same event listener and if the effect of running the parents parents event handler is not wanted when a child element receives an event then one of the ways to stop the default behaviour of bubbling or capturing is to use stopPropagation()method on the event

box1 element has stopPropagation function called on it’s click event which stops bubbling
grid-container and body’s event listener’s callback is not run as bubbling is stopped at box1

Sometimes event.stopPropagation() can create problems down the line, for example if an analytics tool is added for tracking user’s behaviour with regards to clicks. If there is section on the page where propagation is disabled, it turns into a dark area where clicks are not registering

A great way to retain all events while keeping the desired functionality is to use event delegation

Event Delegation

Event delegation takes advantage of event propagation and so, allows the event listener to be set on a parent element, thus avoiding adding event listeners to specific nodes. That event listener analyses bubbled events to match itself or any child elements to pass to an event handler.

In this example, the bubbling side-effect of firing all event handlers on all elements with that event is gone but the functionality is retained. — The event listener is set on a parent element which listens for click events. If this parent element is clicked, the callback will compare event.target (element which was clicked) against a common property. E.g — if event.target is a button, log that button’s id

Another example where event listener is set on a ul element, the callback checks if the event target belongs to a group of elements with class ‘note-list-element’, if so, note container is emptied and and new request to the API is made using the id of the target and note container is filled with note’s information.

Another way to add event listeners dynamically would have been on creation of each li element of of a note, an event listener would have been added for each note. If a page would have hundreds of these elements, each with it’s own event listener method it can use up a lot of memory.

When you add an event listener for a certain event, the name of the event listener is added to a list. Then, when the event occurs, a loop goes through the event listeners in the list and calls the appropriate method for each one individually. So the event listener is just an ordinary object, and the “listening” methods are just ordinary methods that get called by other code at the appropriate time.

If you have several pages which lists different categories of notes, each category containing hundreds of notes, when a user navigates between those pages and elements are dynamically added and removed each with their own event listeners, the listeners don’t get removed automatically but the list of the events which is looped over to find the corresponding response grows.The looping over this long list of listeners slows down a page and can result in bad user experience.

Not all events bubble

The blur, focus, load and unload events don’t bubble like other events. The blur and focus events can actually be accessed using the capturing phase (in browsers other than IE which still hasn’t compromised on adding a capturing phase) instead of the bubbling phase

Event reference by MDN shows which events bubble

JSFiddle

--

--