What are event objects?
As we learnt in the previous JavaScript Events — Basics chapter, when an event occurs on a given target, its corresponding registered handler function, if there is any, is invoked with a single argument of type object.
This argument is referred to as an event object.
So to restate it:
An event object simply represents an event. It contains contextual information regarding the event that caused a given handler to be invoked.
This can include things like the time at which the event occurred, or maybe the name of the event (such as 'click'
), or maybe the target on which the event occurred, and so on and so forth.
In the case of event-handling properties and the addEventListener()
method, since we define a function ourselves to act as an event's handler, we could name the parameter any way we like and then use it inside the function.
Conventional names are e
, event
, or even evt
, following the fact that the parameter represents an event. It's almost always good to stick to conventions.
Shown below are a couple of examples:
// Assume target is already defined.
target.onclick = function(e) {
// The event object is: e
}
target.addEventListener('click', function(event) {
// The event object is: event
});
function clickHandler(nameItAsYouLike) {
// The event object is: nameItAsYouLike
}
target.addEventListener('click', clickHandler);
Moving on, in the case of event-handling HTML attributes, since they don't hold a function, there's no point of even talking about parameters — that's because there ain't any!
Instead what happens in this case is that the event object is available via a local variable called event
that's automatically created by the JavaScript engine when execution enters the context of an event-handling HTML attribute.
Shown below is an example:
<!-- Works -->
<div onclick="console.log(event)"></div>
<!-- Error: e doesn't exist! -->
<div onclick="console.log(e)"></div>
Simple, isn't this?
An event object implements the Event
interface, at the very least.
The Event
interface defines a wide variety of useful properties and methods to help us in working more effectively with the Events API in JavaScript. Let's explore it in more detail.
The Event
interface
As we already know, there is plethora of possibilities for the kinds of events that could occur on a web page, and likewise, a huge API to work with events in JavaScript.
This obviously means that there possibly has to be a large collection of interfaces, often inheriting from one another, in order to represent all these events. And there actually are many.
In this section, we'll quickly take a look over the fundamental interface representing all event objects in JavaScript, i.e. the Event
interface.
As its generic name implies:
Event
interface is the foundation of all event objects in JavaScript.It defines those features that are common to all events, no matter what their type. This includes things such as the event's name, its target, the time at which is occurred, and so on.
There are even a couple of handy methods defined on the Event
interface that allow us to configure the behavior and flow of events in the language, including preventDefault()
, stopPropagation()
, stopImmediatePropagation()
, and so on.
The following table summarizes some of the properties and methods of the Event
interface that we'll explore in this chapter, and in the next chapter:
Property/Method | Purpose |
---|---|
target | The precise object on which the event occurred. |
type | The type of the event, also known as the name of the event, denoted as a string. For example, 'click' . |
time | A number representing the time at which the event occurred. It holds the number of milliseconds elapsed since the underlying document loaded. |
cancelable | A Boolean indicating whether or not the event's default action could be prevented. |
defaultPrevented | A Boolean indicating whether or not the event's default action has been prevented. |
preventDefault() | Prevents the default action associated with the event. |
stopPropagation() | Stops the event from propagating. |
stopImmediatePropagation() | - |
Now clearly, since every single event couldn't sensibly be represented to its entirety by just one single interface, i.e. Event
, there are some specialized interfaces extending Event
that represent specific sets of events.
For example, many events in JavaScript could be grouped under the category of mouse events, by virtue of being initiated by the mouse. They share common information and functionality, such as denoting the x and y coordinates of the mouse pointer. Likewise, in JavaScript, the MouseEvent
interface is made to specifically represent these mouse events.
MouseEvent
extends UIEvent
, which is yet another interface meant to represent events initiated inside or by the user interface. UIEvent
extends Event
, which we've already discussed about.
Event
extends the Object
interface.As we shall see throughout this unit and the latter part of this course, there are a handful of interfaces representing certain kinds of UI-related events in JavaScript, all inheriting from UIEvent
. Examples include KeyboardEvent
, TouchEvent
, PointerEvent
, etc.
For now, it's more than sufficient to know about the core Event
interface and some of its useful properties and methods. Once we get done with this, we can then start to explore each class of events in more detail.
A quick example
Let's consider a simple example to understand everything much better.
In the following code, we have the <body>
element of an HTML page with three elements: <h1>
, <p>
and <button>
.
<body style="border: 1px solid black;">
<h1 style="border: 1px solid blue;">A heading</h1>
<p style="border: 1px solid red;">A paragraph</p>
<button>A button</button>
</body>
The borders have been applied so that we can easily distinguish between different elements in the document.
With this HTML in place, next we'll add a click
handler to window
. As a result, a click anywhere on the document viewport will get this handler to be invoked:
window.onclick = function(e) {
alert(e.target.nodeName);
}
Inside the handler function, we use the event object e
to determine the tag name of the exact node that got clicked with the help of its target
property and then alert the name. (Recall the nodeName
property from the chapter JavaScript HTML DOM — Nodes?)
Let's try the code out.
When we click the <button>
, 'BUTTON'
is alerted. When we click the <p>
, 'P'
is alerted. When we click the <h1>
, 'h1'
is alerted. When we click anywhere outside these, 'BODY'
is alerted.
With the help of the target
property, we were able to determine the exact element node on which the click was made, and then used the nodeName
property of this node to inspect its tag name.
Peaches and cream, wasn't this?
Now that we've gotten a hands-on experience of using target
, it is actually the high time to make the distinction between the this
binding of an event handler and this target
property (of an event object).
Handler's this
vs. event object's target
Given that an event handler's this
hasn't been configured by virtue of a bind()
call on it, it points to the object on which it was set.
For example, if we log this
in the handler shown in the code above, it'll be equal to window
, simply because we set up the handler on window
:
window.onclick = function(e) {
console.log(e); // window
console.log(e === window);
}
Window {window: Window, self: Window, document: document, name: '', location: Location, …}
However, as you just saw above, e.target
isn't the same as this
inside the handler.
e.target
returned the element node that received the click event. In contrary, this
always returned the window
object.
In the case above, the handler's this
wasn't equal to its event object's target
property. But this can happen sometimes; in fact, many times.
Let's see how. If we set up a click
handler on a button element and then inside the handler function, log its this
and its event object's target
, both will be identical to one another.
This can be seen as follows:
<button>Click me</button>
var buttonElement = document.querySelector('button');
buttonElement.onclick = function(e) {
console.log(this === e.target); // true
}
As the target
of the click
event is always going to be buttonElement
, the handler's this
binding above would always be identical to this target
even though theoretically they both don't refer to the same concept.
Knowing about this distinction between an event handler's this
binding and its corresponding event's target
is absolutely critical when writing programs. It can sometimes lead to unexpected behavior, or even fatal exceptions.
To help illustrate this fact better, we've put together the following example:
<button>A button with a <span>span</span></button>
What we want to do over here is to simply change the background color of the button to yellow the moment we click on it.
On first glance, the task seems quite straightforward.
In the code below, we try accomplishing it, using the event's target
property:
var buttonElement = document.querySelector('button');
buttonElement.onclick = function(e) {
e.target.style.backgroundColor = 'yellow';
}
Let's test the code. (Try clicking inside the <span>
.)
Surprisingly, there's a weird issue (actually, it's not weird at all!).
When we click anywhere inside the button where the <span>
element doesn't exist, we get the expected outcome. However, when we click inside the <span>
, instead of the button's background changing, the <span>
's background changes.
So what's causing this issue?
Well, the reason that we're facing this bug is because we're using the event's target
property where we could've otherwise used this
.
When we set up an event handler on the <button>
element directly, we could've easily accessed it using this
. Using e.target
, we refer to the precise element that triggered the click on the button — which might turn out to be the <span>
element nested inside the button.
Hence, when we clicked on <span>
, e.target
resolved down to that <span>
node and therefore, ultimately, got its background changed.
So whether one needs to use this
or e.target
depends totally on the situation at hand. Sometimes, an event handler's this
might be bound to some other object, in which case we might have to work our way around e.target
.
this
value, the object is already stored in some kind of an identifier and is likewise accessible inside the handler using that identifier. In other words, if this
gets configured, usually there's no need to resort to the event's target
.Preventing default behavior
As you create more and more JavaScript programs leveraging the Events API, you'll eventually come to a point where you'll want to tap into the default behavior associated with a particular event.
For instance, you might want to prevent the click event on links (i.e. <a>
elements) from loading the page pointed to by their href
. Or you might want to prevent data from being displayed in an <input>
field the moment it's entered into it. Or you might want to prevent a form from being submitted when its submit button is pressed.
Whatever the case be, you'll someday want to prevent the normal behavior that user agents perform upon the occurence of a particular event.
Fortunately, there's a very simple and neat way to do so using JavaScript — just call the preventDefault()
method of the event.
preventDefault()
method of the Event
interface is made to instruct the browser engine to prevent the default behavior associated with the current event.No argument needs to be provided to the method. Conventionally, it's called right at the start of the handler function, but there's no such necessity — you could even make it the last statement of your function body, or put it simply anywhere in between.
Let's consider an example.
In the code below, we have two links set up. The second one has an id
so that we can easily select it and apply a click
handler to it:
<a href="https://www.codeguage.com">Link 1</a>
<br>
<a id="a1" href="https://www.codeguage.com">Link 2 (with preventDefault())</a>
Inside the click handler, we invoke the preventDefault()
method of the event and then make a console log:
var anchorElement = document.getElementById('a1');
anchorElement.onclick = function(e) {
e.preventDefault();
console.log('Link 2 clicked');
}
Let's see the difference between both these links once they're clicked.
As you can try out, when the first link is clicked, the browser navigates to its underlying href
— the same old behavior that we're all used to. However, when the second link is clicked, no navigation happens. Instead, a log is made in the console, as follows:
So what's exactly happening with the second link?
Well, by calling e.preventDefault()
in the second link's handler, we essentially informed the browser not to execute the default action associated with the link's click (which is to navigate the current window to the link's href
).
As you could probably guess generally and even by the introductory paragraph in this section, preventDefault()
isn't just limited to click
events. We could use preventDefault()
in a wide variety of events, besides click. It's very useful and actually more than just common in nearly every other JavaScript application.
preventDefault()
in literally all different kinds of events. But such a call won't have any effect, whatsoever, in certain events. Those events are commonly referred to as not cancelable. We'll understand what this means in the next section.Moving on, we could easily inspect whether preventDefault()
has been invoked upon an event object previously or not by referring to its defaultPrevented
Boolean property.
defaultPrevented
indicates whether or not an event's default action has been prevented.
Let's consider an example.
In the code below, we modify the anchorElement
's onclick
method shown above and use it to inspect the event's defaultPrevented
property, before and after calling preventDefault()
:
var anchorElement = document.getElementById('a1');
anchorElement.onclick = function(e) {
console.log('Before', e.defaultPrevented);
e.preventDefault();
console.log('After', e.defaultPrevented);
}
The moment we click the second link, which ultimately activates this click handler, here's what gets displayed in the console:
false
true
As you can notice, prior to calling preventDefault()
, defaultPrevented
returns false
. But after calling the method, the property returns true
which confirms that the event's default action, associated with the target on which it occurred, has indeed been prevented.
More on preventDefault()
In this section, we explore little internal details of how exactly preventDefault()
works based on the standards published, at the time of this writing, relating to events.
preventDefault()
might seem like magic, but there really isn't anything such as magic in the world of a programmer, is there? Everything is theoretical and/or practical.
Talking about preventDefault()
, it executes an almost-one-liner command which has absolutely nothing magical in it. It's rather the way an event gets fired in JavaScript that allows us to tap into its (implementation-defined) default behavior for a given target and stop it from being executed.
The supposed magic lies purely in the event dispatch algorithm.
It's time to unravel some of the pseudo-magic of this algorithm along with what exactly preventDefault()
does.
The moment preventDefault()
is called on a particular event object, the browser engine sets an internal canceled flag for that event object.
The internal 'canceled' flag
As the name suggests, the 'canceled' flag simply indicates whether the underlying event has been canceled or not. The browser uses this flag later on to determine if it should prevent the default action of the event (for the particular target that it occurred on).
Certain kinds of targets in JavaScript define a default behavior to be performed when a particular event occurs on them. As per the DOM standard, this is referred to as the activation behavior of the target for that particular event.
For example, the <a>
element (which acts as the target) defines an activation behavior for the click
event, which is simply to follow its href
and load the new webpage.
Note that activation behaviors of targets are only defined for a limited number of events (occurring on that target). It's absolutely NOT the case that a given target has an activation behavior for every single event — that just doesn't make sense!
When an event is dispatched on a given target, after performing a couple of steps of the event dispatch algorithm (including propagation, which we'll cover in the next chapter), in the end, the engine is done figuring out whether the target or any of its ancestors has an activation behavior defined for the occurred event.
Such an object is referred to as the activation target of the fired event, once again as per the DOM Events standard.
To restate it, the activation target of a fired event is its target or one of the ancestors of its target that has an activation behavior defined for the event, or it is just undefined.
Coming back to the discussion, near the end of the event dispatch algorithm, if the engine has found an activation target of the fired event, the activation behavior of that activation target is executed.
But as you can guess, this execution is conditional, i.e. if the event's internal canceled flag is set, which simply means that the event's default action must not be performed, then the activation behavior of its activation target isn't executed.
As simple as that.
To summarize and simplify these last two paragraphs, if an event is canceled, for e.g. via a call to preventDefault()
, then its default action, associated with the target on which it occurred or any of the target's parent, is not performed.
The cancelable
property
When JavaScript constructs an event object and then dispatches it on a given target, it sets its cancelable
attribute to a Boolean value as per a given events specification.
The cancelable
property of the Event
interface simply tells whether or not a given event is cancelable. In other words, it states whether calling preventDefault()
on an event would have any effect or not.
For instance, as defined in the UI Events standard, the cancelable
property of the click
event is true
. Hence, applications could invoke preventDefault()
on a click
event and then expect it to have an effect (such as preventing the browser from following the href
of an <a>
element).
Events whose cancelable
property is false
, calling preventDefault()
has absolutely no effect on them. An example would be the blur
event.
preventDefault()
on a non-cancelable event doesn't change the value of the event's internal canceled flag.Keep in mind that it isn't necessarily the case that non-cancelable events (i.e. the ones that ignore a call to preventDefault()
) have no default action associated with them.
For instance, the dblclick
event is not cancelable, yet it still has a default action tied to it. (Can you guess what the event is and what might be its default action?)
Hence, it's very important to keep this in mind that cancelable
only specifies whether or not an event's internal canceled flag could be set; nothing more than that. It doesn't at all specify whether an event has a default action or not for a given target.
preventDefault()
on a non-cancelable event doesn't cause any errors.Legacy properties
At this stage of this course, you are already familiar with the idea of legacy stuff in JavaScript from the legacy DOM era (late 90s) — the likes of the document.all
collection; certain properties of the Node
interface; the event-handling HTML attributes; and so on.
There are also some legacy features in the language if we specifically talk about events objects:
They include:
- The global
event
object. - The
returnValue
property of theEvent
interface. - The
srcElement
property of theEvent
interface.
Let's explore each of these quickly.
The global event
object
Even though today you might not want to support very ancient browsers such as IE8, it's still not a bad idea to know a little bit about the global event
object which is a remnant of the legacy DOM era, arising from IE itself.
Recall that we stated above that when an event handler is called, it's passed in an argument which is known as an event object and simply represents the occurred event. In IE8 and earlier, this wasn't the case, unfortunately.
Instead, they implemented event objects in one single property, available globally on the window
object, named event
.
To date, the global event
object is still supported for... you guessed it... backwards-compatibility purposes.
event
object.Event
interface's returnValue
property
Recall the preventDefault()
method? To recap it, it's used to prevent the default behavior of an event associated with a given target.
Now back in time, in the legacy DOM era, JavaScript implementations relied on the return value of event handler functions to figure out whether or not to prevent the default behavior of the event (for the given target). There was nothing such as preventDefault()
in those early days.
The whole idea was that returning false
from within the event handler function allowed us to cancel the event, just like calling preventDefault()
as we saw above. Even today, this feature is supported in browsers and in use across some codebases, so it's good to know a little bit about it.
However, as you can probably guess, sometimes it might not be possible to return a value from the event handler function if we are in another function called from within that handler.
In such cases, we can surely very easily pass on the event object to that inner function so that it can maybe call given methods on it, but returning a value from that inner function and then ultimately returning that same value from the handler function won't be as straightforward.
For this, back then, a new property was introduced to the Event
interface, called returnValue
(a very obvious nomenclature).
Its purpose was just to make it easier to cancel an event from any function having access to the event object. If set to false
, returnValue
effectively did the same thing as actually returning false
from within the event handler function.
The returnValue
property is supported to date for backwards-compatibility purposes.
returnValue
is an accessor property. Setting it to true
doesn't have any effect; only the value false
is entertained.Event
interface's srcElement
property
The srcElement
property of the Event
interface is merely a legacy alias of target
.
That's it.
Some very old codebases might still use it, but modern-day code must stick to using the more meaningful target
property.
Moving on
You might've noticed that we've not talked about a couple of other properties and methods of the Event
interface in this chapter, such as eventTarget
, stopPropagation()
, cancelBubble
, and so on.
The thing is that all of these properties and methods relate to the idea of event propagation which we'll discuss in the next chapter in great detail. And it's there that we'll unravel the intuition behind all these properties and methods.
For now, it's super important that you understand the concept of event objects really really well and even practice it using the next exercises. This is because event objects represent an extremely useful concept in JavaScript, used in every single complex application, that we'll also use quite a lot in the upcoming chapters.