Reputation: 1265
The below code is a simplification of my problem. When you click on a_element, b_element will show and an event listener will be created to listen for a second click, which will hide b_element. Problem is, the first click that makes b_element show is also triggering the nested event listener i.e. window.addEventListener
. Is there a way I can write this window event listener so it won't execute on the initial click?
var a_element = document.getElementById("a");
var b_element = document.getElementById("b");
function show_block () {
b_element.style.display = "block";
console.log("showing block");
a_element.removeEventListener('mouseup', show, false);
window.addEventListener('mouseup', hide = function () {
b_element.style.display = "none";
console.log("hiding block");
window.removeEventListener('mouseup', hide, false);
a_element.addEventListener('mouseup', show = function () {
show_block();
}, false);
}, false);
}
a_element.addEventListener('mouseup', show = function () {
show_block();
}, false);
#a {
position: absolute;
height: 50px;
width: 50px;
background-color: rgba(100,120,140,1);
}
#b {
display: none;
position: absolute;
left: 200px;
height: 50px;
width: 50px;
background-color: rgba(200,220,240,1);
}
<div id="a"></div>
<div id="b"></div>
And a simplified version:
var a_block = document.getElementById("a");
function message () {
window.addEventListener('mouseup', message = function () {
console.log("you clicked twice");
}, false);
}
a_block.addEventListener('mouseup', clicked = function () {
message();
console.log("you clicked once");
}, false);
#a {
height: 50px;
width: 50px;
position: absolute;
background-color: #000000;
}
<div id="a"></div>
Upvotes: 1
Views: 1563
Reputation: 9140
The reason the function referred to with hide
is called with the first "click" is because mouse events first trigger all those event listeners attached to any ancestor (parent, grandparent, etc) of the element that is the "target" of the click (the actual element being interacted with), that are registered with the capture flag set (the second parameter of the addEventListener
that you're using, if omitted the capture flag is set by default). This is called the capture phase of event dispatching -- if you click the "a" element, if there was an event listener attached to the window (the ancestor for all elements with event dispatching) with the second argument to addEventListener
set to true
(or not specified at all), the event listener would be called first. And so on towards the element, and ending with calling event listeners added on the element itself.
After the "capture phase" above, the event "bubbles back up" upwards through the hierarchy of elements starting with the target element and ending with the window object. This is called the "bubbling phase" -- it's just like with the "capture phase" for the event, but in reverse order, and this time only event listeners added with the second argument to addEventListner
being set to false
explicitly, are called, ending with event listener(s) added to the window object.
What happens in your case is that since you add an event listener to the window object with false
as second argument to addEventListener
while the event is still being dispatched, as the event finishes "bubbling up" and when it's turn to call event listeners (those added for the "bubbling phase") on the window object, your newly added (to the window) event listener is called!
Like I said, I don't know why there is false
specified for every call to addEventListener
in your code, it's what I'd call "code smell" -- a sign that something is probably wrong. Which it happens to be the case, for better or worse? Do you have a reason to handle events during "bubbling" of these, as opposed to the default (no second argument to addEventListener
specified) which is for during "capturing"? Incidentally, this is what has gotten you the trouble you're dealing with. If in doubt, don't specify things -- you have at most equal chance of getting it wrong anyway, why take the risk with extra arguments that are confusing the reader, and incidentally bring about wrong behaviour?
Practically, there are other issues with your code -- you attach anonymous functions that do the same thing but are not the same function (as in, same object created once), you re-assign show
and hide
although these refer to the same de-facto behaviour -- meaning they should basically be the functions "show" and "hide", without elaborate anonymous functions being assigned to window properties (no declarations of show
and hide
, which makes these being assigned on the window/global object).
But without knowing what behaviour you want exactly, I am afraid I cannot provide you with any code. Without knowing what you want, I cannot tell you what is wrong with the code, although all of the above information still applies, obviously -- I am just not sure how it applies in your case.
Upvotes: 0
Reputation: 1265
The event moves from the window to the target (capturing) and then back from the target to the window (bubbling). This happens for every event. Since a_block is the target, it doesn't matter if I set it to true or false (true will trigger it at the very end of the capturing phase and false will trigger it at the very beginning of the bubbling phase but since a_block is the apex of the movement path it is essentially the same point). So the event travels from the window, then to the target, which at that point creates the event listener at the window. Then the event, which is still at the target, travels from the target to the window. If the window event listener is set to bubbling phase i.e. false, then it will trigger when the event reaches the window. Otherwise, if the window event listener is set to the capturing phase i.e. true, the event will trigger on a capturing phase which can only happen at that point by clicking the mouse a second time.
TLDR: Change the nested listener to true:
var a_block = document.getElementById("a");
function message () {
window.addEventListener('mouseup', message = function () {
console.log("you clicked twice");
}, true);
}
a_block.addEventListener('mouseup', clicked = function () {
message();
console.log("you clicked once");
}, false);
#a {
height: 50px;
width: 50px;
position: absolute;
background-color: #000000;
}
<div id="a"></div>
Upvotes: 1