Event's name (or type)
Every event in JavaScript is denoted by a name. This name is typically a verb, since an event represents an action (and a verb also represents an action).
Let's take the example of a mouse click. In JavaScript, the click action is referred to as the click
event. So when we click on a certain element in a webpage, the click
event occurs. Simple.
As another example, and not far away from this basic click action, the click of the mouse's right button is referred to as the contextmenu
event. So when we right-click the mouse, the contextmenu
event occurs.
Sometimes the event's name is also referred to as the event's type.
In this regard, henceforth, the type of the click event would be, well, click
. Similarly, the type of the right-click event would be contextmenu
.
Knowing this second term is important since the Events API provides us with a type
property on an event object to inspect about the type of an event; or in other words, inspect its name. We'll see the details to this in a later chapter in this very unit.
Keep in mind that these names are very important for us to remember because we'll eventually need them when handling the underlying events.
Now to set a convention from this point onwards, throughout this course, we'll refer to events in the following way, taking the example of the click event: click
— just like any other piece of code. This will help us easily distinguish when we are talking about an action leading to an event vs. when we are talking about that very event specifically from the perspective of code.
Event's target
Apart from the name, every single event has an underlying target.
For instance, if we click on a <div>
element, then the target of the click
event would be that <div>
element. Similarly, if a given <script>
loads completely, then the target of the consequently occuring load
event would be that <script>
element.
load
event in the JavaScript Load Events chapter.There can never be an event without a target. It's that simple.
Typically, as we shall see throughout this unit, the targets of events are instances of Element
. However, there are times when the targets might be other kinds of objects, such as instances of Window
, XMLHttpRequest
, FileReader
, IndexedDBRequest
, and so on and so forth.
Event's handler
When an event occurs on a webpage, the corresponding JavaScript engine gets notified of it and then a corresponding block of code gets executed, if there is any.
Such a block of code is set up the developer in the form of a function. This shouldn't be any surprising because it's only the function construct that can hold a block of code and execute it on demand.
This function is what we refer to as the event's handler.
Sometimes, the handler is also called the event's listener function, or simply listener.
Event handlers are automatically invoked by the JavaScript engine when their underlying event occurs.
And when that does happen, they even get provided with an argument object. This argument is commonly known as the event object and serves to host a plethora of properties and methods to allow the developer to inspect details of the occured event.
We'll explore event objects in the next chapter JavaScript Events — Event Objects.
Let's now see how to set up event handlers in JavaScript, which is perhaps the most important thing in the entire events ecosystem.
Event-handling properties
In JavaScript, there are essentially three ways to setup event handlers:
- Use event-handling properties
- Use the
EventTarget
interface - Use event-handling HTML attributes
We'll consider the very first option for now, leaving the last two to be discovered in detail later on in this chapter.
Many interfaces in JavaScript are extended by these so-called event-handling properties. They begin with the word 'on' followed by the name of an event, all in lowercase, and simply serve one purpose — to hold a handler for the given event.
For instance, taking the click
event as an example, its corresponding event-handling property is onclick
. This property is available on all objects that can potentially receive a mouse click on a web page. To be precise, onclick
is defined on the HTMLElement
interface.
As a quick example, if we wish to handle the click
event on the following <button>
element,
<button>Click me!</button>
we'd write something as follows:
var buttonElement = document.querySelector('button');
buttonElement.onclick = function() {
// Handle the click event over here.
}
Here, the function assigned to the onclick
property is what we'll call the click
event's handler.
Now, since onclick
is just another JavaScript property, we can even assign it a function identifier, instead of assigning it an anonymous function expression directly, as demonstrated below:
var buttonElement = document.querySelector('button');
function buttonClickHandler() {
// Handle the click event over here.
}
buttonElement.onclick = buttonClickHandler;
Note that we don't have to call the function buttonClickHandler()
; just pass its reference to the onclick
property, which will then automatically be called by the JavaScript engine on the occurence of a click.
In the next section, we'll consider all these examples in more detail.
A simple example
In the chapter CSSOM Viewport, we saw how to set up a click
event handler on a <button>
element. Let's now take a look at another such example, this time with the aim of exploring it in even more detail than before.
Consider the following HTML:
<button>Show alert</button>
Just a single button as the body of the web page.
Now what we want to do here is to show up an alert box when the user clicks this button.
How to do this? Well, it's really easy to do.
First, we ought to select the <button>
element. Then, we need to set up a handler function for its click
event. For now, we'll do so using the onclick
property of the <button>
element. Finally, inside the handler, we'll make the desired call to alert()
.
And that's it.
Here's the complete code:
var buttonElement = document.querySelector('button');
buttonElement.onclick = function() {
alert('You clicked the button!');
}
We can now click the button and witness the alert box showing up.
Great!
It doesn't hurt to know a little more about how exactly does the alert get made in the code above after the instant where we click the button.
Here is a high-level overview of the steps involved:
- A click is made over the
<button>
element. - The OS (Operating Sytem) notices a change in the mouse's state and realizes that it's because of a click. It passes this information to the web browser software which then extracts further information from the OS, most importantly the coordinates of the pointer at the time of the click.
- Using these coordinates and the layout of the page (i.e. what's show currently in the viewport), the browser determines which element was there at the position of the pointer.
- In our case, since the mouse pointer was atop the button, the browser determines that it was the
<button>
element there where the pointer was at the time of the click. Consequently, it dispatches the click event on the<button>
element. - During this stage, all the registered click handlers of
<button>
are invoked one after another. In our case, we had only oneonclick
handler set up and so that's only what gets called. - The handler makes a call to
alert()
, which leads to an alert message in the browser. Finally, once thealert()
call completes (which is when we close the alert box), the handler reaches completion.
And this completes the whole lifecycle for the click event since the moment it originates in the <button>
element.
Precisely speaking, there are many many phases skipped in this list but that's only for the sake of brevity. If we were to add everything right now, the whole process of firing and subsequently handling an event would become really intimidating.
We'll surely understand each and every step involved in this whole process but only gradually as this unit proceeds, in order to keep everything simple and sound.
The EventTarget
interface
The property-based approach above to set up event handlers works well in all but some complex cases. These complex cases are when we want to use more than one event handler.
For instance, consider the following hypothetical JavaScript code:
var buttonElement = document.querySelector('button');
function handler1() { /* ... */ }
function handler2() { /* ... */ }
buttonElement.onclick = handler1;
buttonElement.onclick = handler2;
We wish to set up two click event handlers here on the <button>
element that should both execute when the button is clicked.
However, we can't assign them one after another to the <button>
's onclick
property, since the second assignment, to handler2
, overrides the first one.
With the onclick
approach, the only way to use multiple handling functions is by manually calling them inside one single function and then using this function as the handler function, as shown below:
var buttonElement = document.querySelector('button');
function handler1() { /* ... */ }
function handler2() { /* ... */ }
buttonElement.onclick = function() {
handler1();
handler2();
}
However, this could only work when we have access to the handling functions upfront.
Suppose that the handler is obtained from another program, or worse yet, another program itself wants to add its own handlers in addition to your handlers. In both these cases, it would be totally impossible for us to set up all the handlers by directly working with onclick
.
Absolutely, no way!
And that's where the EventTarget
interface comes to the rescue.
The DOM Level 2 standard introduced the EventTarget
inteface as another, more mature and powerful, means to work with events in JavaScript besides the legacy DOM approach of using event-handling properties.
The interface defines two really basic, yet extremely flexible, methods to set up and remove event handlers from given target objects. They are addEventListener()
and removeEventListener()
, respectively.
addEventListener()
As the name suggests, the addEventListener()
method is used to add an event listener for the target object it's called on.
Here's the syntax of addEventListener()
:
targetObj.addEventListener(name, listener)
targetObject
is an instance of EventTarget
on which we wish to handle a given event, name
is a string representing the name of the event (a.k.a. its type), and listener
is the listener function (a.k.a. the handler function) for the event.
Recall from the HTML DOM chapter, all nodes in the DOM tree are instances of the Node
interface, which inherits from the EventTarget
interface. Thus, all element nodes have the methods addEventListener()
and removeListener()
available to them.
Let's consider an example — the same one we saw above, showing an alert box, just this time utilizing addEventListener()
instead of onclick
.
In the following code, we set up a click handler on the <button>
element in order to make an alert when it's clicked:
var buttonElement = document.querySelector('button');
buttonElement.addEventListener('click', function() {
alert('You clicked the button!');
});
Notice the differences here from the snippet above.
Let's understand what's happening here:
- First, we select the
<button>
, as before. - Next, we invoke
addEventListener()
on the selected element in order to set up aclick
handler. - The first argument is
'click'
as we want to listen for theclick
event occuring on the given button. - The second argument is the handler function, which is the exact same as before.
And that's it!
removeEventListener()
As with the event-handling property approach that we saw above, it's possible to remove an event handler set up via addEventListener()
. That is by using removeEventListener()
.
However, unlike the former approach, where we ought to set an event-handling property to null
in order to remove the corresponding handler, we need to pass the handler's reference to removeEventListener()
that we wish to remove.
Here's the syntax of the method:
targetObj.removeEventListener(name, listener)
The first argument is the name of the event whose handler we wish to remove from targetObj
while the second argument is a reference to the handler function, provided at the time of calling addEventListener()
, that we wish to remove.
removeEventListener()
requires an existing function!
Making intuition of the second argument here ain't that difficult. Internally, the EventTarget
interface maintains a list of handler functions associated with a given event.
When we wish to remove a particular handler function, there is no way for the interface to figure out that handler function without a reference to it. Once the reference is there, removeEventListener()
searches for the handler function in the list and then removes it once it's found there.
Simple?
Henceforth, if we plan to remove an event listener later on in our code, then it's utmost important to keep its reference with us until we remove it from the target object.
Consider the example below:
var buttonElement = document.querySelector('button');
function clickHandler() {
alert('You clicked the button!');
buttonElement.removeEventListener('click', clickHandler);
}
buttonElement.addEventListener('click', clickHandler);
After the button is clicked for the very first time, removeEventListener()
is used to remove its click handler and thereby prevent any further alerts from being made upon its click.
Notice that in both the calls, i.e. to addEventListener()
and to removeEventListener()
, we pass the same function in the second argument. This is not just a nice thing, but also a requirement, as mentioned at the start of this discussion.
If we had added the event handler as follows:
var buttonElement = document.querySelector('button');
buttonElement.addEventListener('click', function() {
alert('You clicked the button!');
});
without storing it in some kind of an identifier, there would be no way for us to remove it anyhow!
Event handler's this
binding
We've seen the this
keyword in great detail in the JavaScript Functions — Basics chapter. When used inside a method of an object obj
, this
resolves to that object obj
(given that the method is called on the obj
).
Intuitively and logically, the case is quite similar for event handler functions.
That is, when JavaScript calls an event handler, it sets its this
value to the object on which the event handler was set up, initially.
For instance, consider the following code:
var buttonElement = document.querySelector('button');
buttonElement.onclick = function() {
console.log(this === buttonElement);
}
Normally, we'd expect the this
inside onclick()
to resolve to the buttonElement
object, as onclick()
is a method of buttonElement
. Fortunately, this is the actually the case.
The console's log confirms this:
The value true
means that this
points to the same object inside onclick()
above that buttonElement
points to.
The case is the exact same for event handlers set up using addEventListener()
.
For example, if we were to rewrite the code above as follows:
var buttonElement = document.querySelector('button');
buttonElement.addEventListener('click', function() {
console.log(this === buttonElement);
});
the this
inside the handler function above would still resolve to the buttonElement
object on which the handler was set up (i.e. the object on which addEventListener()
was called).
Let's now consider another example.
In the code below, we have two <button>
elements, each with a click
handler set up, but to the exact same function, clickHandler()
:
<button>Button 1</button>
<button>Button 2</button>
var buttonElements = document.querySelectorAll('button');
function clickHandler() {
alert(this.innerHTML);
}
buttonElements[0].onclick = clickHandler;
buttonElements[1].onclick = clickHandler;
When clickHandler()
gets invoked, it alerts the innerHTML
of its this
object, which might either be the first <button>
or the second one.
See how with the help of this
, we are able to reuse the same click handler on multiple targets.
It's worthwhile mentioning here that often times it might be much more convenient to bind the this
of an event handler to some other object instead of the one on which it was set up. Such a use case typically arises when building OOP applications in JavaScript.
In this case, as you may already know, we can use the power-packed bind()
function method to create a bound function configured with a given this
, and then use this bound function as the event's handler.
Shown below is an example:
class AlertButton {
constructor(alertMessage) {
this.message = alertMessage;
this.clickHandler = this.clickHandler.bind(this);
this.init();
}
init() {
var buttonElement = document.createElement('button');
buttonElement.innerHTML = 'Alert button';
buttonElement.onclick = this.clickHandler;
document.body.appendChild(buttonElement);
}
clickHandler() {
alert(this.message);
}
}
new AlertButton('This is the message.');
When a new AlertButton
is instantiated by providing a message argument to it, the instance saves the message in its message
property and then later on uses it when making an alert upon its <button>
's click.
By binding the clickHandler()
method's this
with the current AlertButton
instance, we make sure that when clickHandler()
is called, the expression this.message
resolves correctly, using the message
property of the AlertButton
instance.
Otherwise, as we already know, this
would resolve to the <button>
element on which the click occured, and thus have no message
property on it, eventually leading to the wrong alert.
HTML event-handling attributes
Apart from event-handling attributes, and the EventTarget
interface, there is yet another way to set up event handlers in JavaScript. That is, by using HTML on-
attributes.
The naming of these attributes is exactly the same as the naming of event-handling properties in JavaScript. That is, they also begin with the word on
followed the name of the event, for e.g. onclick
.
An important thing to keep in mind, however, is that the value of an HTML event-handling attribute is NOT meant to be a function. Rather, it's meant to be a short script defining the code to be executed upon that event's occurence.
In other words,
Let's see what this means.
Suppose we want to make an alert when the following <button>
is clicked.
<button>Click me</button>
We'd write the following if we were to go with the event-handler property approach:
var buttonElement = document.querySelector('button');
buttonElement.onclick = function() {
alert('Button clicked.');
}
But going with the event-handling HTML attribute approach, we'd write the following:
<button onclick="alert('Button clicked')">Click me</button>
Notice the value of onclick
here.
- Firstly, it's an HTML attribute and therefore we must enclose it within quotes (
""
). - Secondly, it's meant to be a short script handling the event, and therefore we directly call the
alert()
function in there.
There is no point of defining a function inside an event-handling HTML attribute. In fact, if we did so, the event's occurence won't get the function to be executed, just defined!
The following code won't work as we'd expect it to:
<!--Clicking this button won't make an alert-->
<button onclick="function() { alert('Button clicked') }">Click me</button>
Make sure to keep this in mind.
Although, these HTML attributes can sometimes help us quickly set up event handlers for a given element, their usage is NOT recommended at all. It's always better to keep HTML, i.e. the content, separate from the JavaScript, i.e. the logic — a practice referred to as separation of concerns.
As we develop complex applications, this fact would speak of itself.
With modern web apps using numerous scripts to build their end UI, each serving a different purpose, it becomes really difficult to manage code in these scripts with the passage of time, let alone being able to pay attention to the short scripts embedded inside HTML on-
event-handling attributes.
So following this recommendation, throughout this unit, we'll keep ourselves from using event-handling attributes entirely.
If HTML event-handling attributes are bad, then why are they even provided? Why not just remove them?
Good question.
The thing is that event-handling attributes are a remnant of the legacy DOM era, when JavaScript wasn't really deemed by anyone to eventually turn into a humongous technology powering extremely complex frontends. It made sense back then when only simple actions were carried out by the (mostly novice) users of JavaScript.
As far as the question is concerned as to why aren't they removed from JavaScript, this ain't that difficult to reason about.
A lot of legacy code still relies on these somewhat-troublesome attributes. Likewise, if they were to be removed from JavaScript, these legacy apps would cease to work correctly.
In short, HTML event-handling attributes are retained to date in JavaScript simply for backwards-compatibility reasons.