Javascript Events
One of the ways to run Javascript code is in response to actions taken
by the user on the page. Javascript supports a number of event handlers
that are triggered by user interaction. Examples are the click
event
handler which can be added to any element to trigger a fragment of code
to run when the user clicks on that element.
The easiest way to add a handler for an event is to use a HTML attribute
inline in the page. The attributes for each event are prefixed by the
word 'on', so to set a click
event handler I would add an onClick
attribute to an element:
<img src='foo.jpg' onClick="alert('this is a test');">
In this case the fragment of javascript code would run if the user clicked on this image. Any javascript can be included in an attribute value but it is best to keep this brief and just call a handler function that is defined in a script block elsewhere on the page.
Another way to add handlers is via the addEventListener
method for a
DOM element. This has the advantage that it can be done from within a
code block running elsewhere in the page and that it can add multiple
handlers for a single event to an element. Here's an example from the
Mozilla Developer Network
on using this method:
// Function to change the content of t2
function modifyText(new_text) {
let t2 = document.getElementById("t2");
t2.firstChild.nodeValue = new_text;
}
// Function to add event listener to table
let el = document.getElementById("outside");
el.addEventListener("click", function(){modifyText("four")}, false);
The sample code defines a handler function modifyText
which is then
attached to the element with id "outside". Note that the second argument
given to addEventListener
is an anonymous function that calls the
handler with a given argument and returns false.
There's a good list of the different events and the browsers that support them on this MDN page.
this
in Event Listeners
The variable this
in Javascript is used in object methods to refer to the
object instance that the method was called on. It behaves like the
self
argument used in Python but it is defined implicitly rather than
having to be included as a parameter. We can also use this
in the context
of an event handler because of the way that events are managed in the DOM.
In an event handler this
refers to the element that captured or triggered
the event that the handler is responding to. Generally this is the element
that the event listener has been added to. Here's an example:
let el = document.getElementById("outside")
el.addEventListener("click", function(){
this.css("color", "red")
});
In the click
handler defined here as an anonymous function, this
will refer to
whatever element received the click event. As the handler was bound to the element
with id outside
, then this
will refer to that element.
One note of caution here. You may have seen the alternate form of functions (arrow functions)
mentioned in the Javacript chapter used in some examples. This is a
more compact way of writing functions and is often useful in callback functions like this.
However, one significant difference between these and regular functions is that arrow
functions do not get a special this
variable. There's a reason for this which we'll discuss
below, but it means you need to be aware of this when you write a callback. So the above example
would not work as an arrow function:
let el = document.getElementById("outside")
el.addEventListener("click", () => { this.css("color", "red") }); // bad - there is no this variable
If you try this, you'll get an error message saying this.css
is not a function.
The reason for this behaviour is because of a common pattern of use where we create an
eventhandler within another eventhandler (there are also other places where this is useful). Here's
an example. We want to create an event handler for the button primary
which, as part of
it's action, creates a new event handler on another element secondary
. The job of this
new event handler is to change the color of both the primary and secondary elements. Here's
the example:
function clickAction() {
// here `this` refers to the element that was clicked
// what ee do is add a new event handler onto another
// element
this.css('color', 'green');
let sec = document.getElementById("secondary");
sec.css('color', 'green');
sec.addEventListener("click", function() {
sec.css('color', 'red')
this.css('color', 'red');
});
}
let prim = document.getElementById("primary");
prim.addEventListener("click", clickAction);
Inside
the main handler (clickAction
) the value of this
will be the primary element
that was clicked to trigger the action. We change the color of the element,
then find another element with id secondary
and change it's color to green
too. We then add a new event listener to the secondary element so that when
it is clicked, the color of both elements are changed to red. However, the
value of this
inside the new event handler will refer to the secondary element,
not the primary one. Remember, this function won't be run until later when
the secondary button is clicked. So at that point, there's no way to find
the primary element - the event handler won't work.
Instead, we use an arrow function for the second event handler:
function clickAction() {
// here `this` refers to the element that was clicked
// what ee do is add a new event handler onto another
// element
this.css('color', 'green');
let sec = document.getElementById("secondary");
sec.css('color', 'green');
sec.addEventListener("click", () => {
sec.css('color', 'red')
this.css('color', 'red');
});
}
Since we've used an arrow function there is no new this
created when it runs. This
means that Javascript needs to find a value for this
by looking outside the function.
Here we're creating what is called a closure
- the function we create here is closed
over the values of the variables that exist at the time it was created. So, the value
of
this
is the value it had insideclickAction
when the arrow function was defined.
Closures are a useful tool in building event handlers in Javascript. They can be confusing if you are new to this idea. Understanding it involves thinking about the time that the function is created and the time it is executed.