This is an extensive exploration of the intricacies surrounding DOM events. First and foremost, we will delve into the various types of DOM events, examining each of their unique characteristics and how they are triggered. Additionally, we will take a closer look at some of the more commonly misunderstood aspects of DOM events, such as the propagation and bubbling of events.
What is a browser event?
Let's simply put it this way an event is an object which has the property that contains information about that particular event, such as on what element the event is triggered and many other information about that particular event.
Events can be generalised into 2 categories
- User-agent events
- synthetic events
User-agent events are dispatched by the browser based on user interactions or any other browser(page) task is completed.
For example
- The user clicks on the button
- The user submits the form
- All the resources have been downloaded.
And may more, we can add a listener to the event in order to listen to the events
window.addEventlistner('load',(event)=>{console.log(event));
Synthetic events
On the other hand, synthetic events are the events that are created/emitted by applications.
For example
const event = new Event('myEvent', {bubbles:true, cancelable: true});
//dispatch events
document.dispatchEvent(event);
//events can be dispatched from any element
document.getElementbyId('myDiv').dispatchEvent(event)
If we want to pass the data then we can use CustomEvent()
.
const event = new CustomEvent('myCustomEvent', {detail: {message:'hello world'});
elem.addEventListner('myCustomEven', (event)=>{
console.log(event.detial.message)
})
The isTrusted
is the property in the event object that is true for user-agent dispatched events and false for the applications(developers) initiated.
Event phase
When we further inspect the event object we also get a property name eventPhase
. In order to understand this property let us first understand what is CAPTURING_PHASE
and BUBBLEING_PHASE
.
When an event is dispatched it will occur in the event will reach to document(root) and then back to the target element(in the above diagram button). The flow from top to bottom is called capturing phase and the bottom to top is called the *****bubbling phase.***** let us take a step forward and understand the above diagram with code
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body >
<form id='submit_form' style="border:1px solid red; padding: 4rem">
<button type='submit' id='submit_button'>Submit<button>
</div>
</form>
<script>
function eventListener(event) {
console.log({
target: event.target,
currentTarget: event.currentTarget,
phase: event.eventPhase,
type: event.type
});
}
const form = document.getElementById("submit_form");
const buttonContainer = document.getElementById("button-wrapper");
const submit = document.getElementById("submit_button");
form.addEventListener("click", eventListener);
buttonContainer.addEventListener("click", eventListener);
submit.addEventListener("click", eventListener);
</script>
</body>
</html>
This is a long example let's break it down. We have a simple form and later we are attaching some event listeners to it. The addEventListner()
function takes 3 arguments
- event name that the event listener should listen
- a callback function that should be called.
- And the last one is an option or useCapture. It is an optional argument.
All the arguments are pretty much clear by the name the only argument that we care about is option and useCapture.
The option has these parameters.
- capture: The default value is false. The event will first be captured with the current element to which the event listener is attached (i.e element above in the DOM tree) and then later will be dispatched to any event target
- once: The events should be invoked once and removed by default
- passive: If true it tells the browser that the callback functions will not have the
preventDefault().
Even if thepreventDefault
is there in the callback function the browser will only throw and warning in the console. - signal: It's used for removing the event listener by providing the
AbortSignal
. We will talk about the abort signal later in this blog.
Capturing and bubbling phase
In the example above we have attached listeners at the document, body, form and button We run the above example the output will be like this
{target: button#submit_button, currentTarget: button#submit_button, phase: 2, type: 'click'}
{target: button#submit_button, currentTarget: div#button-wrapper, phase: 3, type: 'click'}
{target: button#submit_button, currentTarget: form#submit_form, phase: 3, type: 'click'}
To understand this we first need to look into the Event
object. The user agent-generated event object mainly has these properties
-
type
: The type is the type of event for e.g click, submit etc. You can read more about it here -
eventTaget
: Returns the element on which the event occurred or null. -
currentTarget
: Returns the element which is listening to the event -
eventPhase
: This one is the interesting one. The event phase generally returns one of these values 0,1,2,3. What are these values? The event phase has these enums
NONE = 0
CAPTURING_PHASE = 1;
AT_TARGET = 2;
BUBBELING_PHASE = 3;
Let us understand each of these values one by one. In the above example context, the <button />
element is wrapped by a div
, which is further nested inside a form
. So when the button has been clicked the div
and from
are in the capturing phase. But in the console, we don't see any eventPhase
as 1 (i.e capturing) event listener is not being invoked.
To invoke an event listener callback function in the capturing phase we need to specify true
in the event listeners
document.body.addEventListener("click", eventListener, true);
// or
document.body.addEventListener("click", eventListener, { capture:true });
Propagation
Now that we have a solid understanding of the event phases. Let us further look into how to stop handlers from getting called. We have 2 methods for stopping the propagation of the event.
-
event.stopPropagation()
and event.stopImmediatePropagation()
Assume we have multiple event handlers attached to an element. We have stopHandler()
and eventListenerLogs()
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<form id='submit_form' style="border:1px solid red; padding: 4rem">
<div id='button-wrapper' style="border:1px solid black; padding: 4rem"">
<button type='button' id='submit_button'>Submit<button>
</div>
</form>
<script>
function stopHandler(event) {
event.stopPropagation();
console.log({
target: event.target,
element: event.currentTarget,
phase: event.eventPhase,
type: event.type
});
}
const form = document.getElementById("submit_form");
document.body.addEventListener("click", eventListener, false);
form.addEventListener("click", stopHandler, false);
form.addEventListener("click", eventListener, false);
</script>
</body>
</html>
We have attached the event handler to body
, and form
element. When we click on the button the event occurs in BUBBLEING_PHASE
and the order of event propagation should be supposed to be
But since we have event.stopPropagation()
, at the form level. the console o/p is
{target: button#submit_button, element: form#submit_form, phase: 3, type: 'click'}
{target: button#submit_button, element: form#submit_form, phase: 3, type: 'click'}
As we have to event handler attached to form
element.
What if we want to only listen to the event only once then we can use event.stopImmediatePropagation()
function stopHandler(event) {
event.stopImmediatePropagation();
console.log({
target: event.target,
element: event.currentTarget,
phase: event.eventPhase,
type: event.type
});
}
Now the output is
{target: button#submit_button, element: form#submit_form, phase: 3, type: 'click'}
Cancelable
A cancelable event’s read-only property returns true
or false
based on how the event was initialized. Most of the native events are cancelable using event.preventDefault()
. Calling this function is like the event never happened. We can create a syntheticEvent
and make it non-cancelable.
const myCoustomEvent = new CoustomEvent("my-coustom",{
cancelable:true
})
document.addEventListener('my-event', function (event) {
event.preventDefault()
})
console.log(
document.dispatchEvent(event) ? 'Event was not cancelled' : 'Event was cancelled'
)
//output
//"Event was cancelled"
If we change the cancelable
property to false the output will be Event was not cancelled
.
PreventDefault
event.preventDefault()
will stop the native element action from happening. This means that the event will not work in the same way as if the event never happened.
Conclusion.
That concludes our discussion on events and how to create custom ones. We covered the various phases involved in triggering events and explored the cancelable event.
Thank you for reading. Please let me know in the comments section if I missed anything.
Top comments (1)
Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍