How to prevent an event from unintentionally triggering listeners on parent elements in Javascript
TL;DR => This is what you need to know:
In the child element, add this to the event listener:
event.stopPropagation();
If you need to see an example, check out this site here.
…But if you don’t know about why stopPropagation is a thing, stick around for three minutes. There’s something you ought to know about event objects in Javascript.
Why stopPropagation() works.
Let’s set the stage here. We’re developing a single-page app, doling out some data on cards. We want to handle when a user clicks on the card, and also when they click on the delete button on the card. Like so:
… without event.stopPropagation()
in the event listener for the delete button, when a user clicks on delete, the click event will also trigger the event listener attached to the card. Our app will try to delete the robot from our database and then try to add it to our robot army. Totally undesirable in this situation, we’ll end up getting errors when our app tries to add the deleted robot to our army.
This is happening because of the way that javascript handles events. In particular event bubbling.
The 3 phases of event propagation
Let’s say we have an html document with a <span>
nested in a <p>
nested in a <div>
:
<html>
<head>
</head>
<body>
<div>
<p>
<span>
Kinda like this.
</span>
</p>
</div>
</body>
</html>
When a user clicks on the span, they initiate an event. The w3 standard would describe the event propagation in 3 phases:
- Capture Phase: The event object begins propagation from the window object up to the span.
- Target Phase: The event object arrives at the target element.
- Bubbling Phase: The event object bubbles back up the DOM to the Window.
Though it doesn’t seem popular in practice, an event can trigger actions during the capture phase. To take advantage of this, add {capture: true}
or the shorthand true
to the event listener, like
element.addEventListener(..., {capture:true})
//or
element.addEventListener(..., true)
After the capture phase, the event is at the target phase, where most of us think of accessing the event. After the target phase, the event bubbles back to window, triggering any event listeners that are present on its way back. This is why we need an event.stopPropagation()
in a child element when we don’t want to trigger event listeners in the parent elements.
Further reading
The w3 standard description of event propagation. There’s some more interesting stuff here (like focus):
https://www.w3.org/TR/DOM-Level-3-Events/
Another article on javascript bubbling & capturing. This is where I first saw a way to use the event object to trigger listeners on the capture phase:
https://javascript.info/bubbling-and-capturing
W3schools’ documentation of stopPropagation(). Their demo code gave me the inspiration for the diagram I made of the 3 phases of event propagation: https://www.w3schools.com/jquery/event_stoppropagation.asp