It's frustrating how children inherit their parent's event listeners by default, even if they are added after the listener was created. This just seems counterintuitive to me. I only want to add a listener to the parent element, not to all of its children. Using stopPropagation and preventDefault as a solution feels like a hack to me. And the fact that you can't remove the children's inherited listeners adds another layer of complexity!
Today, I stumbled upon this issue and it's causing chaos in the design of my dropdown menus!
Imagine a simple dropdown menu structure:
- A parent DIV
- A child DIV used to toggle the menu
- Another DIV wrapper with more children serving as options, each needing their own unique listeners
Initially, setting up this dropdown with just a click listener on the first child to toggle the menu was straightforward. But in my design, I also wanted to include a focusout event. The problem arose when I realized that I needed the focusout event on the parent DIV, which resulted in all the parent DIV's children inheriting this event. So, when a user interacts with the unique options, both focusout events fire, causing the menu to toggle twice. Similarly, if the user clicks the toggling DIV while the menu is open, both the click and focusout events trigger simultaneously. This has been quite a headache!
My dropdown is dynamically created and the code snippet below illustrates it:
var dropdown = document.createElement("div");
dropdown.setAttribute("tabindex", "0");
var toggle_button = document.createElement("div");
toggle_button.addEventListener("click", function() {
toggleDropdown(dropdown);
});
dropdown.addEventListener("focusout", function() {
toggleDropdown(dropdown);
});
dropdown.appendChild(toggle_button);
document.body.appendChild(dropdown);
Here's the function for toggling the dropdown:
function toggleDropdown(dropdown) {
if (dropdown.classList.contains("open")) {
dropdown.blur();
var options_wrap = dropdown.getElementsByClassName("options_wrap")[0];
dropdown.removeChild(options_wrap);
dropdown.classList.remove("open");
} else {
dropdown.focus();
var options_wrap = document.createElement("div");
options_wrap.setAttribute("class", "options_wrap");
var opt1 = document.createElement("div");
var opt2 = document.createElement("div");
opt1.addEventListener("click", function() { /* perform unique actions */ });
opt2.addEventListener("click", function() { /* perform unique actions */ });
options_wrap.appendChild(opt1);
options_wrap.appendChild(opt2);
dropdown.appendChild(options_wrap);
dropdown.classList.add("open");
}
}
You can access the JSfiddle for this setup here
I attempted using event delegation, adding event capturing to the dropdown, and using stopPropagation in the listener. I also experimented with preventDefault, but the child events continued to trigger the focusout event twice.
Furthermore, I tried checking the event's target and comparing it with the clicked element. Unfortunately, both events persisted in firing.
What I truly desire is to have control over whether children inherit parent events. This default behavior is bewildering, leading to complex workarounds that I find quite frustrating.