Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't triggering click() inside a click event listener cause an infinite loop?

Can someone explain the program flow of this JavaScript code:

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
  console.log(a);
  console.log("check");
  a++;
  $leaveRoom.click();
  console.log(a);
  a++;
});
<button id="leave-button">Leave Room</button>
The Output was:
1
check
2
check
3
4

This question may sound dumb but I am new to JavaScript. I am not able to understand the program flow of this code. I want to know how did I get 3 & 4 in the output.

like image 217
Tushar Agarwal Avatar asked May 04 '21 21:05

Tushar Agarwal


People also ask

How do you trigger an event on click?

If you just need to trigger a click event, you can omit the line that begins with for( . @Parag: Read it again. The loop is to click the same link 50 times, which is what it does.

How do I stop event listener after clicking?

The removeEventListener() is an inbuilt function in JavaScript which removes an event handler from an element for a attached event. for example, if a button is disabled after one click you can use removeEventListener() to remove a click event listener.

What happens if you add an event listener twice?

If multiple identical EventListeners are registered on the same EventTarget with the same parameters, the duplicate instances are discarded.

Can you add multiple click event listeners?

You can add many event handlers to one element. You can add many event handlers of the same type to one element, i.e two "click" events. You can add event listeners to any DOM object not only HTML elements. i.e the window object.


3 Answers

The key to this question is the presence of a hidden Flag on each element.click() method.

Each element has an associated click in progress flag, which is initially unset.

the doc: https://html.spec.whatwg.org/multipage/interaction.html#dom-click

As soon as this method is activated, this flag changes from progess Status == unset to progess Status == active (pseudo code)

(then it returns to its initial status once the code it contains is fully executed)

When this flag is in the active state, then any call to this method is ignored.


Here is my original post to show the execution sequence of `console.log()`

const bt_leaveRoom = document.querySelector('#leave-button')
var counter = 0
var origin  = 'event clic'

bt_leaveRoom.addEventListener('click', () => 
  {
  let source = origin
  console.log(`_first console.log(): counter = ${ ++counter }, origin = ${source}`)

  origin = 'call'
  bt_leaveRoom.click()
  
  console.log(`second console.log(): counter = ${ ++counter }, origin = ${source}`)
  })
<button id="leave-button">Leave Room</button>

the Hidden Flag act like in the same way if I have coded this way :
replace this line:
bt_leaveRoom.click()
to:
if (source !== 'call') bt_leaveRoom.click()

But in fact the system use the method hidden flag (named progress flag ?)
which can be (in pseudo code)

if (progress_flag_of_bt_leaveRoom.click() is unset) do { bt_leaveRoom.click() }  
like image 171
Mister Jojo Avatar answered Oct 16 '22 12:10

Mister Jojo


I tried a couple of things to find an answer to this question. I haven't really found a definitive answer to what's going on here, but I do believe that what I share here feeds into this excellent problem.

I simplified the code to focus on the recursive triggering of events.

Simplified Code

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
    console.log(a++);
    $leaveRoom.click();
});
<button id="leave-button">Leave Room</button>

We can see here that we have two calls for console.log, the first one is for the actual clicking of the button, and the second one is for the call to $leaveRoom.click();. It seems to stop there for some reason.

Using dispatchEvent

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
    console.log(a++);
    $leaveRoom.dispatchEvent(new Event('click'));
});
<button id="leave-button">Leave Room</button>

Here, the event is triggered multiple times (44 for me), this could be due to how fast your machine is. It seems to stop triggering eventually though, so I'm thinking the same phenomenon is happening here as well.

Using setTimeout

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
    console.log(a++);
    setTimeout(() => { $leaveRoom.click(); });
});
<button id="leave-button">Leave Room</button>

If you are looking for a way to infinitely trigger the click event, regardless of why the previous methods fail. This does seem to do the trick.

Having said all this, I still have no clue about this hidden force that stops the recursion for the previous methods. Maybe someone could shed some light on this.

like image 14
Ghassen Louhaichi Avatar answered Oct 16 '22 12:10

Ghassen Louhaichi


const click1 = document.querySelector('#click1')
const click2 = document.querySelector('#click2')
const click3 = document.querySelector('#click3')

click1.addEventListener('click', (event) => {
  console.log("click1")
  click2.click()
});
click2.addEventListener('click', (event) => {
  console.log("click2")
  click3.click()
});
click3.addEventListener('click', (event) => {
  console.log("click3")
  click1.click()
});
<button id="click1">Click 1</button>
<button id="click2">Click 2</button>
<button id="click3">Click 3</button>

Actually it appears there is loop breaking logic in the event handling. The handlers will happily daisy chain user input events until the initial interaction dispatches a duplicate event. Then it will no longer dispatch any.

like image 2
Andrew Gillis Avatar answered Oct 16 '22 12:10

Andrew Gillis