In this tutorial, we will show how to add and remove event listeners in JavaScript using the two methods addEventListener
and removeEventListeners
. We'll also share some tips on organizing listeners using the handleEvent
function, with practical examples to illustrate applications of this method in real-world scenarios.
Handling events in JavaScript - addEventListener and removeEventListener methods
The addEventListener
method lets you specify a function that should be called each time a specific event is delivered to the target element:
var element = document.getElementsByClassName('my-class');
if(element.length > 0) {
element[0].addEventListener('click', function(event) {
// function you want to trigger on click
});
}
The function accepts three arguments:
- the event that should trigger a function; in the example above, the 'click' event;
- the function to be executed when the event is triggered;
- options to customize the listener (optional).
These are the possible options that you can pass as the third parameter:
1 - addEventListener - once option
Boolean value. If this is set to true, the listener function will be invoked only once; it will then be automatically removed from the target. Default value is false.
element[0].addEventListener('click', function(event) {
// this function will be called only once; the listener will then be removed automatically
}, {once: true});
2 - addEventListener - capture option
Boolean value. If set to true, this type of event will be dispatched to the this listener before any other target elements beneath it in the DOM tree. Default value is false.
For example, consider the following code:
var parent = document.getElementById('parent'),
child = document.getElementById('child');
parent.addEventListener('click', function(){
console.log('click on parent element');
});
child.addEventListener('click', function(){
console.log('click on child element');
});
where the child
element is a child of the parent
element. By defult, if you click the child element, the first message to be printed in the console will be 'click on child element' (event on child is triggered before parent).
If we now modify the parent listener and set the capture
option to true:
parent.addEventListener('click', function(){
console.log('click on parent element');
}, {capture: true});
the first event to be triggered will be the one on the parent (first message printed in the console will be 'click on parent element').
3 - addEventListener - passive option
Boolean value. If set to true, it specifies that the listener function will never call the preventDefault
method.
element[0].addEventListener('click', function(event) {
// this function cannot use the preventDefault method
}, {passive: true});
4 - addEventListener - signal option
This is used to remove event listeners. You can create an AbortSignal
object; calling the abort
method on that object will result in the removal of the listener.
Consider the following example:
var parent = document.getElementById('parent');
// create an AbortSignal object
const abortSignal = new AbortController();
parent.addEventListener('click', function(event) {
if(event.target.closest('#child')) {
console.log('click');
} else {
abortSignal.abort(); // this will remove the listener
}
}, {signal: abortSignal.signal});
When clicking on the parent
element, the listener function checks if the click was on its child element #child
. If it was, then a message is printed to the console, otherwise, the listener is removed from the parent.
As seen above, the addEventListener
method comes with two options to automatically remove event listeners: once
and signal
.
For scenarios not addressed by these options, you can use the removeEventListener
function:
function clickHandler(event) {
// function you want to trigger on click
};
// add event listener
element[0].addEventListener('click', clickHandler);
// remove event listener
element[0].removeEventListener('click', clickHandler);
Note how this time, we are not using, as a listener, an anonymous function (function(event) {}
), but a named function (clickHandler
).
This is required when using the removeEventListener
method; otherwise, the event won't be successfully removed.
Keep events organized with the handleEvent method
In the first part of this article, we said that the second parameter of the addEventListener
method should be a JavaScript function (either an anonymous or named function). This parameter could also be an object that should have a handleEvent
method; this method automatically takes care of handling all the event listeners.
Let's consider this example:
var Modal = function(element) {
this.element = element;
// other Modal properties defined here
// ...
// use the initEvents method to add event listeners
this.initEvents();
};
Modal.prototype.initEvents = function() {
// we are passing, as the listener, the object 'this' (Modal object), so events will be automatically handled by its handleEvent method
this.element.addEventListener('click', this);
this.element.addEventListener('mouseenter', this);
};
Modal.prototype.handleEvent = function(event) {
switch(event.type) {
case 'click': {
// code to be executed on click
console.log('element clicked');
break;
}
case 'mouseenter': {
//...
}
// all other events here
}
};
In the above example, we define a Modal
object, and use the initEvents
method to add all the listeners.
For all the events, we use the same listener, the this
object (which is the Modal object itself).
Since we are passing an object as the listener, the handleEvent
method of that object will automatically take care of handling the events. In the example above, we use a switch statement to check the event type, and execute a function accordingly.
This way, we can keep all our listeners organized in one place; this not only improves the maintainability of our code (we know where to look if we need to modify a listener function), but it also avoids adding listeners multiple times (we may forget we have already used that event somewhere in our code).
Additionally, this technique makes it super easy to remove events: we do not have to remember the named function to use for each event; we can simply pass the same object as the listener:
Modal.prototype.removeEvents = function() {
this.element.removeEventListener('click', this);
this.element.removeEventListener('mouseenter', this);
};
Real-life examples
Let's see these methods in action in some real-life examples.
Dialog component
Take a look at this Dialog component.
It is used to open a dialog element when clicking on a trigger button. When the dialog is open, we want to add two event listeners: click and keydown.
The click event is used to close the dialog with the 'Cancel' button element.
The keydown event is used to trap the focus while the dialog is open: if user presses the Tab key, we want the focus to move only among the focusable children of the dialog; this way we are sure the focus is not moved to an element not visible (e.g., under the dialog), improving accessibility for keyboard users.
We can define a Dialog
object and an initDialogEvents
function to handle the two events:
var Dialog = function(element) {
this.element = element;
// all Dialog properties here
//...
this.binding = false; // we will use this to store a reference to the event listener
initDialog(this);
};
function initDialog(dialog) {
// dialog.trigger is the <button> element used to open the dialog
dialog.trigger.addEventListener('click', function(event) {
// when opening the dialog -> add event listeners
initDialogEvents(dialog);
});
};
function initDialogEvents(dialog) {
// using dialog.binding to save a refernce to the event listener
dialog.binding = handleEvent.bind(dialog);
dialog.element.addEventListener('keydown', dialog.binding);
dialog.element.addEventListener('click', dialog.binding);
};
function handleEvent(event) {
// handle events
switch(event.type) {
case 'click': {
initClick(this, event); // 'this' -> 'dialog' element
break;
}
case 'keydown': {
initKeyDown(this, event); // 'this' -> 'dialog' element
break;
}
}
};
Here's what we did:
- We created a
Dialog
object; - When opening the dialog (click on the
dialog.trigger
element), we use theinitDialogEvents
function to listen to two events (click and keydown); - We use the
handleEvent
function to handle both events.
Two additional notes about the initDialogEvents
function:
- we use the
dialog.binding
property to store a reference to the event listeners; this way, we can remove the listeners using the same property:
function cancelDialogEvents(dialog) {
//remove event listeners
dialog.element.removeEventListener('keydown', dialog.binding);
dialog.element.removeEventListener('click', dialog.binding);
};
- as the listener, we use the
handleEvent
function, but we also bind the dialog object:
dialog.binding = handleEvent.bind(dialog);
This way, inside the handleEvent
function, we can access the Dialog object using the this
keyword:
function handleEvent(event) {
// handle events
switch(event.type) {
case 'click': {
initClick(this, event); // 'this' -> Dialog object
break;
}
// ...
}
};
Image magnifier component
Take a look at this Image magnifier component.
When the user hovers over the image, the plugin shows a magnified version of that image and modifies its position based on the cursor position.
We need to add two event listeners (mousemove
and mouseleave
) when the mouse enters the image and remove them when it leaves.
As we did for the dialog, we can define an ImageMagnifier
object, then use two methods (initImageMove
/cancelImageMove
) to add and remove event listeners, and the handleEvent
function to handle them:
var ImageMagnifier = function(element) {
this.element = element;
// other ImageMagnifier properties here
// ...
this.binding = false; // we will use this to store a reference to the event listeners
initImageMagnifier(this);
};
function initImageMagnifier(imgMag) {
// listen to 'mouseenter' event
imgMag.binding = handleEvent.bind(imgMag);
imgMag.element.addEventListener('mouseenter', imgMag.binding);
};
function initImageMove(imgMag) {
// listen to 'mousemove' and 'mouseleave' events
imgMag.element.addEventListener('mousemove', imgMag.binding);
imgMag.element.addEventListener('mouseleave', imgMag.binding);
};
function cancelImageMove(imgMag) {
// remove 'mousemove' and 'mouseleave' events
imgMag.element.removeEventListener('mousemove', imgMag.binding);
imgMag.element.removeEventListener('mouseleave', imgMag.binding);
};
function handleEvent(event) {
switch(event.type) {
case 'mouseenter':
// on 'mouseenter', use the initImageMove function to add 'mousemove' and 'mouseleave' event listeners
initImageMove(this);
break;
case 'mousemove':
// on 'mousemove', magnify and update the position of the image
move(this, event);
break;
case 'mouseleave':
// on 'mouseleave', remove the 'mousemove' and 'mouseleave' listeners
cancelImageMove(this);
break;
}
};
Here's what we did:
- We created an
ImageMagnifier
object; - We used the
initImageMagnifier
function to detect themouseenter
event; - Once the mouse enters the image, we use the
initImageMove
function to add listeners for themousemove
andmouseleave
events; - We remove the
mousemove
andmouseleave
listeners when leaving the image, using thecancelImageMove
function; - We use the
handleEvent
function to handle all listeners.