1

In short, I have addEventListener inside of addEventListener. The event I'm listening to is click. Here's an example of my code:

function toggleElement() {
    // function stuff
}

anchor.addEventListener('click', (e) => {
    e.preventDefault();

    if (!anchorWrapper.classList.contains('active')) {
        // do some stuff
        window.addEventListener('click', toggleElement, false);
    } else {
        // do some stuff
        window.removeEventListener('click', toggleElement, false);
    }
});

Every time the anchor element is clicked, I assign an event listener to the window which listens to clicks. I'd expect the user to click the anchor, which in turn SHOULDN'T initialize toggleElement, but just assign a event listener to the window. Then, according to my thinking (which turns out to be wrong), the function should only be initialized on the next, second click anywhere on the window. However, it get's run on the click on the anchor element.

What am I not getting right? How can I prevent the toggleElement function to not be initialized when a user clicks on the anchor element, but instead wait for click events on the window?

3 Answers3

2

The problem is that your first event is propagating through the DOM up to the node (in your case, window) that you have just added an event handler to. Because the propagation continues after your callback exits, which means after you have added the second event handler, it will hit the second event handler.

You can avoid this by using the stopPropagation method in your event. You can also use some other technique such as a promise or a zero length timeout to relegate it to the next JS 'task' in the engine, but there are also reasons why you wouldn't want to do that.

I have created some example code here, which also deals with the case where you want subsequent clicks inside the button (or whatever) to also trigger the second event handler. Note also that I am having the second event handler unregister itself, which makes this logic slightly simpler.

let target = document.getElementById("target");
let targetWrapper = document.getElementById("targetWrapper");

function secondFunction() {
    console.log("Called second callback");
    //alert("Second Function Called");
    
    targetWrapper.removeEventListener('click', secondFunction, false);
    targetWrapper.classList.remove("active");
}

target.addEventListener('click', (e) => {
 console.log("Called first callback")

    e.preventDefault();
    
    
    if(!targetWrapper.classList.contains("active")){
     targetWrapper.addEventListener('click', secondFunction, false);
      targetWrapper.classList.add("active");
      e.stopPropagation();
    }
});
body {
  font-size: 20px;
  font-family:sans-serif;
}

#targetWrapper {
  min-height: 400px;
  min-width: 400px;
  text-align: center;
  background-color: green;
  position:relative;
}

#target {
  vertical-align: middle;
  background-color:blue;
  color:red;
  padding:50px;
  position:absolute;
  top:100px;
  left:50px;
}

#targetWrapper.active{
  outline: 3px solid red;
}
<div id="targetWrapper">
  <a id="target">
  Click me!
  
  </a>
</div>

More info on event propagation and handlers:

DOM level 3 events

A question I answered earlier about order of event handling

Example of event propagation

ChrisM
  • 1,565
  • 14
  • 24
  • 1
    Ah I see that I have been beaten to the punch on this one. No matter; I hope that my explanation is useful to someone anyway. – ChrisM Apr 07 '19 at 14:00
1

Doesn't toggleElement get the "old" click event? Try putting it in a zero timeout:

setTimeout(function() { window.addEventListener('click', toggleElement, false); });
mbojko
  • 13,503
  • 1
  • 16
  • 26
  • Thanks, solved it & will accept the answer in a bit. Yes, it does get the old click. My thinking was that with `window.addEventListener` a new event separate from the old click will be expected. But I was wrong. –  Apr 07 '19 at 13:11
  • Accepted the answer that included `e.stopPropagation()` as it solved my issue in a much neater way. –  Apr 07 '19 at 13:22
1

Maybe the code is faster than your click? I think when you click, the eventlistener is assigned but you are still 'clicking' so the second eventlistener detects the click as well. Correct me if I'm wrong.

Try putting e.stopPropagation(); after e.preventDefault();.

Wout Rombouts
  • 1,479
  • 2
  • 9
  • 15
  • Actually, the issue is the propagation of the event itself. Adding `e.stopPropagation()` solves the issue in a much neater way than the `setTimeout` function I think. So, I'm accepting this answer. Thanks! –  Apr 07 '19 at 13:22
  • No problem, glad I could help! – Wout Rombouts Apr 07 '19 at 13:29