Reputation: 133
By default children inherit their parent's event listeners, whether or not the child is added after the the listener was created. This is bananas to me. I only want to add a listener to the parent element, not all of it's children. And using stopPropagation and preventDefault as a solution seems very hacky to me. Furthermore, you can't remove the children's inherited listeners! Yikes!
I just learned this today and it is wreaking havoc on my design for my dropdown menus!
Essentially, I have a simple dropdown menu:
Creating this dropdown is easy enough when all I have is a click listener on the first child that toggles the menu. However, in my design I want the focusout event so that if the user clicks away from the dropdown anywhere else in the window, then we can toggle the menu closed. Problem is, I need the focusout event on the parent DIV and this creates my dilemma as all of the parent DIV'S children inherit the focusout event. Thus creating a situation where when a user clicks any of the unique options, two focusout events fire and my menu toggles twice. Plus if the user clicks the toggling DIV while the menu is open, then two events fire also, the click and the focusout. My code below..
I am creating the dropdown dynamically.
var dropdown = document.createElement("div") ;
dropdown.setAttribute("tabindex" , "0") ; // enables the DIV to allow focus
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) ;
My toggle function.
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() { /* do unique stuff. */ }) ;
opt2.addEventListener("click" , function() { /* do unique stuff. */ }) ;
options_wrap.appendChild(opt1) ;
options_wrap.appendChild(opt2) ;
dropdown.appendChild(options_wrap) ;
dropdown.classList.add("open") ;
}
}
I created a JSfiddle
I tried using event delegation. Adding event capturing to the dropdown and using stopPropagation in the listener. I tried adding preventDefault as well, but my child events still fire the focusout event twice.
I also tried checking the event's target and comparing it with what is being clicked. But as usual both my events fire.
What I really want is to disable the default nature of children inheriting parent events. That behavior is bananas to me and prompts serious workaround solutions. I dislike workarounds with a passion.
Upvotes: -2
Views: 113
Reputation: 133
Okay I figured out what was going on. In fact, my click event was firing first when the menu is open and I click the toggle_button
. And my call in my toggle function blur()
was firing the focusout
event. I just needed to pass a check from the click event so that my toggle function knows not to blur. Now my code looks like this...
var dropdown = document.createElement("div") ;
dropdown.setAttribute("tabindex" , "0") ; // enables the DIV to allow focus
var toggle_button = document.createElement("div") ;
toggle_button.addEventListener("click" , function() {
toggleDropdown(dropdown , true) ; // add a boolean for checking
}) ;
dropdown.addEventListener("focusout" , function() {
toggleDropdown(dropdown) ;
}) ;
dropdown.appendChild(toggle_button) ;
document.body.appendChild(dropdown) ;
And my function like so...
function toggleDropdown( dropdown , check ) {
if ( dropdown.classList.contains("open") )
{
// The blur function fires the focusout event.
// Don't fire if checked.
if (!check) { 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() { /* do unique stuff. */ }) ;
opt2.addEventListener("click" , function() { /* do unique stuff. */ }) ;
options_wrap.appendChild(opt1) ;
options_wrap.appendChild(opt2) ;
dropdown.appendChild(options_wrap) ;
dropdown.classList.add("open") ;
}
}
Upvotes: 0