This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the javascript category.
Last Updated: 2024-11-21
I was working on a website's mailing list sign up modal.
This had an outer frame (that dimmed the background of the whole web page) and a smaller inner frame containing the actual form for harvesting emails. Here's the HTML structure:
<div class="dimmed-background">
<div class="content">
</div>
</div>
I wanted to implement a feature whereby clicking on the dimmed background (i.e.
on the .dimmed-background
div) would close the modal. So I attached a click
listener to this outer frame. However, this didn't quite work: Clicking on the
inner frame (.content
) also hid the modal, thereby preventing all mailing list
sign ups.
This happened because the code did not take into account the way JavaScript propagates events.
Events such as clicks that happen on an target element (.content
here) first go through a "capture" stage - starting from the window
element
and then moving on to each nested element in turn until it reaches the target,
the "capture listeners" are called (those registered with addEventListener(x,y,
true)
(true as the third parameter - useCapture
in their docs). We don't have
capture listeners in this code, so that can't be the bug.
Once the event reaches its target (.content
here) it then "bubbles" up all the
way to the window again - i.e. event propagation is bidirectional. The
addEventListener(x,y)
we were using without the 3rd, useCapture
parameter being set
defaults it to false
, which has the effect that the listener responds to bubble
events like these. We've found our bug! In our case, the listener on .dimmed-background
was on the way up, so it got called during this bubbling stage.
What I should have done is either:
dimmed-background
modal is clicked. This is
done by inspecting the event target:dimmedBackground.addEventListener('click', function (event) {
var clickedElem = event.target;
// matches() takes any CSS selector string:
https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
if (clickedEl.matches('.dimmed-background')) {
// code to actually close the modal
...
}
});
function modalClick(e) {
e.stopPropagation();
return false;
}
contentDiv.addEventListener('click', modalClick)
This second way may be inadvisable for click events, because it prevents items further up the HTML tree from responding to these when bubbled up (e.g. click-tracking analytics software).
"Event propagation" is the blanket term for both "event bubbling" and "event capturing". The "event target" is the element you sent the (e.g. click) event to.