Normajean
Normajean

Reputation: 1265

Why would the same [click] event be handled twice?

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

Answers (2)

Armen Michaeli
Armen Michaeli

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

Normajean
Normajean

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.

Capturing and Bubbling phase

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

Related Questions