Problems
The OP 1 (aka Original Poster -- aka @NIKHIL CHANDRA ROY) use (misuse?...abuse?) of setInterval()
is just a means to switch the "state" (ex. .ok
and .rds
) of .box
I assume.
Concerning OP's misconceptions about (or perhaps failure to properly explain) the following statement:
document.onclick = null; // important idea
From what I gather is that OP is worried that document
will detect the click event and call the event handler, thereby incurring two calls to the event handler in rapid succession. Once by document
and the other by .control
-- hence the misunderstanding that setInterval()
was useful in this particular situation.
OP treats the event handlers as normal anonymous functions (which is why OP is creating two separate functions to change two separate "states" and struggling with when a call happens and when a call shouldn't).
Standard Anonymous Function
Anonymous functions are defined and invoked when parsed. When the browser notices the parenthesis (ex. function()
) it will interpret it as: "Run this function NOW").
const nodeRef = () => {...
/* or */
var nodeRef = function() {...
Event Handler
Event handlers are functions that wait for a registered event to happen before they run. Note the similarities they share:
//~~~~ Defined (aka Named)
domNode.onclick = functionName;
/* or */
domNode.addEventListener('click', functionName);
function functionName(eventObject) {...
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
//~~~ Anonymous
domNode.onclick = function(eventObject) {...
/* or */
domNode.addEventListener('click', function(eventObject) {...
When registered event is triggered the event handler is called, thus the only time parenthesis are included is when an event handler is defined. Note a defined event handler does not have parenthesis ()
because their definitions are hoisted to the top.
Also by default event handlers pass the Event Object. Always include it as the first parameter when defining an event handler. By explicitly passing the event object (typically as e
, event
, evt
...) the event handler can access the event object's properties such as event.type
, event.currentTarget
, and event.target
.
event.currentTarget
is an object (typically a HTMLElement) that is registered to an event (it "listens" for a specific event to happen). It is also referred to as this
within the definition of its associated event handler.
event.target
is the object to which the registered event originated from. For example, if the fifth button out of a hundred identical buttons was clicked, event.target
will accurately reference that particular button.
Hopefully what an event handler is and what it does is a little more clearer. Moreover how indispensable Event Object is. The next section explains Event Phases which details the unseen chain of events happening within the DOM tree.
<!DOCTYPE html><!--document-------------------->
<html><!-----------document.documentElement---->
...
<body><!--------document.body--------------->
<form id="ui"><!--document.forms.ui-----➕--HTMLFormElement Interface
---event.currentTarget---➕--Registered to click event
---this-------------------->
<button class='ctx'><!--event.target--⏬-->
CLICKED!
</button>
<fieldset class='box ok'><!--event.target.nextElementSibling---❙--->
</fieldset>
</form>
</body>
</html>
Capture Phase
- ex. User clicks
button.ctx
and becomes event.target
event.target
parent is event.currentTarget
(ie form
)
- Event path:
document
, html
, body
, form
.
- The path (and current capture phase) ends when the parent of the event origin (aka
event.target
) has been reached.
- This phase is rarely used specifically by the developer and is usually disregarded (there some exceptions such as key events)
Target Phase
- ex. After completing the path from
document
to the parent element (ie document.forms.ui
) of the button
the target phase begins
- Once
event.target
is determined, the element positioned immediately after it (ie event.target.nextElementSibling
) will change "state" (.box.ok
OR .box.rd
).
- The event handler stops propagation (ie stops the bubbling phase from continuing) when target phase is completed or
.ctx
wasn't clicked. This desired behavior is done by ending statement: event.stopPropagation()
.
- Event path has only a single reference to
event.target
.
- This phase is the most important one because it singles out the clicked button even if there were a thousand buttons.
Bubbling Phase
- ex. Normally, after the target phase is complete the path is reversed.
- Since there where no other objectives it's pointless to continue the event chain so
event.stopPropagation()
prevents the click event from going any further up the DOM tree.
- Incidentally, this also excludes ancestor elements / objects such as
document
from the click event.
Consider the programming pattern called Event Delegation.
Advantages
Multiple targeted elements 3 (aka event.target
) -- ex. a <button>
user has clicked) can be specified and exclude non-targeted elements 4 (ex. document
or body
which could interfere and inhibit proper behavior if a click anywhere triggered an event handler).
Only register one element to handle one or more targeted elements at once.
The targeted elements include ones that are dynamically added in the future as well.
Requirements
An ancestor element that all te have in common. It can be a top-level object such as document
or window
but it's better to assign or create an element that is as close to the te as possible. The closer the less likely buggy behavior will occur (such as double calls to event handlers).
Register the event to said ancestor element. There are three ways to register an element to an event using plain JavaScript.
On-event Property Event
My personal favorite because it's terse
document.forms.ui.onclick = ctrl;
EventListener
In general the most recommended way -- the only significant difference between them is that the third optional parameter will use the capture phase instead of the default bubbling phase by passing true
.
document.forms.ui.addEventListener('click', ctrl, true);
On-event Attribute Event
This way of event handling is as old as dirt and its use is not recommended due to it's many limitations. I have included it just the sake of completeness, DO NOT IMPLEMENT THIS TECHNIQUE.
<form id='ui' onclick="ctrl(e); return false">
<button class='ctx' type='button'>THIS IS LAME!</button>
<fieldset class='box ok'></fieldset>
</form>
When defining the event handler, establish the following:
Ensure that the current event.target
(remember the event phases are not idle) isn't event.currentTarget
. Once that's establish narrow down the possibilities further by using control or ternary statements.
...
if (event.target !== this) {
if (event.target.classList.contains('ctx')) {...
...
By implementing narrow criterion and using event.stopPropagation()
, excluding everything else is simple.
...
} else {
event.stopPropagation();
}
}
event.stopPropagation();
}
Demo
const ui = document.forms.ui;
ui.onclick = ctrl;
function ctrl(event) {
const clicked = event.target;
if (clicked !== this) {
if (clicked.classList.contains('ctx')) {
const box = clicked.nextElementSibling;
box.classList.toggle('ok');
box.classList.toggle('rd');
} else {
event.stopPropagation();
}
}
event.stopPropagation();
}
.ctx {
font-size: 1.5rem;
cursor: pointer;
}
.box {
width: 200px;
height: 200px;
background: red;
border: 0;
transition: 1s;
}
.rd {
border-radius: 50%;
}
.ok {
border: 10px dotted blue;
}
<form id='ui'>
<button class="ctx" type='button'>Control</button>
<fieldset class="box ok"></fieldset>
</form>
Footnotes
1 In the SO community (not sure about the SE as a whole), OP is used interchangeably between Original Poster (the member asking the question) or Original Post (the question itself).
2 At times the terms event handler, event listener, and callback may be used to refer to a function called after an event happens. For the purpose of this answer that holds true. For a more definitive explanation refer to this post.
3 The term targeted elements (te)is coined by myself to refer to elements within the DOM that share a common ancestor and can be a potential event.target
made possible by well crafted HTML/CSS and JavaScript configured to effectively delegate events.
4 Non-targeted elements (nte) is an ad-hoc term to describe elements that should be excluded from reacting to certain events for various reasons. Commonly nte are the ancestor elements/objects of the event.currentTarget
such as document
, body
, etc. While there are times when high level nodes on the DOM tree serve well as event.currentTarget
, it's best to keep event.currentTarget
as close to the te as possible.
References
The following references cover other aspects I didn't cover thoroughly in order to avoid info overload (in retrospect I believe I have failed).
HTMLFormElement
const ui = document.forms.ui;
.nextElementSibling
clicked.nextElementSibling;