Course: JavaScript

Progress (0%)

JavaScript Event Objects

Chapter 61 39 mins

Learning outcomes:

  1. What are event objects
  2. The fundamental Event interface
  3. A quick example of using an event object
  4. Handler's this vs. event object's target
  5. Preventing event's default behavior using preventDefault()
  6. More about preventDefault() and event dispatch
  7. The cancelable property
  8. Legacy stuff — global event object, returnValue and srcElement

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 refers to the argument sent into an event handler function.

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:

The 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/MethodPurpose
targetThe precise object on which the event occurred.
typeThe type of the event, also known as the name of the event, denoted as a string. For example, 'click'.
timeA number representing the time at which the event occurred. It holds the number of milliseconds elapsed since the underlying document loaded.
cancelableA Boolean indicating whether or not the event's default action could be prevented.
defaultPreventedA 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.

As you might've guessed, 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.

Live Example

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, …}
true

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>.)

Live Example

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.

Typically when the event handler of an object is bound to a custom 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.

The 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.

Live Example

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:

Link 2 clicked

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.

Technically, we can call 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:

Before
false
After
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()

This section is a bit technical and not really necessary for you to understand in order to be able to work with events effectively, so feel free to skip it.

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.

Activation behavior is discussed slightly here in the WHATWG DOM standard. For more information, however, a better choice is to refer to the definition of the activation behavior in the W3C UI Events standard.

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.

To state it even more precisely, invoking 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.

Calling 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:

  1. The global event object.
  2. The returnValue property of the Event interface.
  3. The srcElement property of the Event 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.

As far as we developers are concerned, we must stick to using event objects supplied as event handler arguments. Only polyfilling libraries or other browser-inconsistency-removal tooling should be concerned with using the global 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.

"I created Codeguage to save you from falling into the same learning conundrums that I fell into."

— Bilal Adnan, Founder of Codeguage