I’ve been seeing a lot of code lately use custom functions to add event handlers to multiple elements, that looks something like this:
var buttons = document.querySelectorAll(".playButton");
buttons.forEach(function togglePlayButton(el) {
el.addEventListener("click", playButtonClickHandler);
});
Put it in a function
That code is remarkably consistent across the many types of code that it’s used with, which makes it a good candidate to be placed in a consistent function to help us add event handlers.
function addPlayButtonEventHandlers() {
var buttons = document.querySelectorAll(".playButton");
buttons.forEach(function togglePlayButton(el) {
el.addEventListener("click", playButtonClickHandler);
});
}
addPlayButtonEventHandlers();
Add a function parameter
I want to modify the function so that it’s more flexible, so that we can call it just addEventHandlers() instead. To achieve that, we can remove from the function things that are likely to change.
The “.playButton” is the first part that gets replaced first, with a selector
parameter that we add to the function:
function addPlayButtonEventHandlers(selector) {
var buttons = document.querySelectorAll(selector);
// ...
}
addPlayButtonEventHandlers(".playButton");
Add more function parameters
I want to pass the "click"
and playButton
in to the function too, so let’s add those to the parameter list of the function.
function addPlayButtonEventHandlers(selector, eventType, eventHandler) {
// ...
}
We can now add "click"
and playButton
to the options being given to the function:
addPlayButtonEventHandlers(".playButton", "click", playButtonClickHandler);
And we can update the addPlayButtonEventHandlers() function to use that information from the options:
function addPlayButtonEventHandlers(selector, eventType, eventHandler) {
// ...
el.addEventListener(eventType, eventHandler);
});
}
Give it a more generic name
We can now rename the function from addPlayButtonEventHandlers() to the more generic name of addEventHandlers() which lets us can use that function in a wide variety of different situations.
function addEventHandlers(selector, eventType, eventHandler) {
// ...
}
addEventHandlers(".playButton", "click", playButtonClickHandler);
But, it still has button-related terminology being used inside of that function, so we should make those names more generic as well, renaming buttons
to els
and togglePlayButton
to addEventHandler`
function addEventHandlers(selector, eventType, eventHandler) {
var els = document.querySelectorAll(selector);
els.forEach(function addEventHandler(el) {
el.addEventListener(eventType, eventHandler);
});
}
Using objects to prepare for multiple events
With that addEventHandlers() function, you only need the following to add an event handler to all elements that match the selector:
addEventHandlers(".playButton", "click", playButtonClickHandler);
But what happens when we want to add handlers for multiple events? A less than pleasing solution is to repeat the line multiple times:
addEventHandlers(".playButton", "click", playButtonClickHandler);
addEventHandlers(".playButton", "mouseover", playButtonMouseoverHandler);
addEventHandlers(".playButton", "mouseout", playButtonMouseoutHandler);
A better solution is to pass the event types and the event handlers to an array. To easily achieve that, it helps if the function first can accept the event info as an object.
We can still can use addEventHandlers to add multiple events by using separate function calls:
function addEventHandlers(selector, eventType, eventHandler) {
var els;
if (typeof eventType === "object") {
return addEventHandlers(selector, eventType.type, eventType.handler);
}
els = document.querySelectorAll(selector);
// ...
}
We can now use the addEventHandlers() function with the event handlers being given in separate objects.
addEventHandlers(".playButton", {type: "click", handler: playButtonClickHandler});
addEventHandlers(".playButton", {type: "mouseover", handler: playButtonMouseoverHandler});
addEventHandlers(".playButton", {type: "mouseout", handler: playButtonMouseoutHandler});
Handling multiple events
We can now easily handle those event objects when they are in an array, by checking if type
is an array. If it is, then it loops through each of them.
function addEventHandlers(selector, eventType, eventHandler) {
var els;
if (Array.isArray(eventType)) {
return eventType.forEach(function addEachHandler(handlerInfo) {
addEventHandlers(selector, handlerInfo);
});
}
if (typeof eventType === "object") {
// ...
}
And we can now pass mutiple sets of events to be added to all elements that match the selector:
addEventHandlers(".playButton", [
{type: "click", handler: playButtonClickHandler},
{type: "mouseover", handler: playButtonMouseoverHandler},
{type: "mouseout", handler: playButtonMouseoutHandler}
]);
Using arrow-notation instead
We can also improve what happens with an array, by using arrow-notation instead:
function addEventHandlers(selector, eventType, eventHandler) {
var els;
if (Array.isArray(eventType)) {
return eventType.forEach((handlerInfo) => addEventHandlers(selector, handlerInfo));
}
if (typeof eventType === "object") {
// ...
}
Adding documentation
Due to the variety of ways of using this addEventHandlers function, it helps to leave some documatation about how it should be used.
It can be possible to figure out how to use the function by inspecting what it does, but it’s more beneficial to make that information available up front, to help reduce the amount of work that people need to do to use the function.
/* Types of usage:
addEventHandlers(selector, eventType, eventHandler);
addEventHandlers(selector, {type: eventType, handler: eventHandler});
addEventHandlers(selector, [{type: eventType, handler: eventHandler}, ...]);
*/
function addEventHandlers(selector, type, handler) {
// ...
}
The final addEventHandlers function
We now have an a very flexible function for adding event handlers:
/* Types of usage:
addEventHandlers(selector, eventType, eventHandler);
addEventHandlers(selector, {type: eventType, handler: eventHandler});
addEventHandlers(selector, [{type: eventType, handler: eventHandler}, ...]);
*/
function addEventHandlers(selector, eventType, eventHandler) {
var els;
if (Array.isArray(eventType) {
return eventType.forEach(function addEachHandler(handlerInfo) {
addEventHandlers(selector, handlerInfo);
});
}
if (typeof eventType === "object") {
return addEventHandlers(selector, eventType.type, eventType.handler);
}
els = document.querySelectorAll(selector);
els.forEach(function addEventHandler(el) {
el.addEventListener(eventType, eventHandler);
});
}
Flexible ways to use the addEventHandlers() function
Here are some code examples of using that function to add event handlers in three different ways:
First, by adding a single event handler at a time, with separate event type and handler info.
addEventHandlers(".playButton", "click", playButtonClickHandler);
addEventHandlers(".playButton", "mouseover", playButtonMouseoverHandler);
addEventHandlers(".playButton", "mouseout", playButtonMouseoutHandler);
We can also give the function an object that contains the event type and handler:
addEventHandlers(".playButton", {type: "click", handler: playButtonClickHandler});
addEventHandlers(".playButton", {type: "mouseover", handler: playButtonMouseoverHandler});
addEventHandlers(".playButton", {type: "mouseout", handler: playButtonMouseoutHandler});
And finally, we can give the event information as an array of objects:
addEventHandlers(".playButton", [
{type: "click", handler: playButtonClickHandler},
{type: "mouseover", handler: playButtonMouseoverHandler},
{type: "mouseout", handler: playButtonMouseoutHandler}
]);