Creating an Event Listener: The Usual, The Okay and The Complicated

I'm Kenneth and I am software engineer. Here to share my experiences to the world and to learn from everyone as well.
Search for a command to run...

I'm Kenneth and I am software engineer. Here to share my experiences to the world and to learn from everyone as well.
No comments yet. Be the first to comment.
In this series, I'd be talking about 3 different approaches to some common coding tasks. The usual followed by a stepped-up one. Then lastly, something out of the ordinary. Excited? So am I!
This post is part of a series that explores how flexible programming languages and frameworks can get. We all know that with any given task, there are several approaches available to achieve it. Let's look at this in action! In this post, we will l...
When defining application components, a good strategy is to split by functionality. In a blogging application, we'll have a component to represent the WYSIWYG editor then, another to hold the publish settings. In this particular example, the publish ...

In this post, I'll show you how to implement feature management on your React applications. Feature management is the way to control which features are available based on certain scenarios. Why Do Feature Management? Here are some scenarios where con...

When I create content, I start off with a topic. Then, I create different content pieces revolving around that topic targeting different platforms like Hashnode, Dev.to, or Twitter. In fact, I recently Tweeted about this, that content creation nowada...

In this post, we'll go through the process of creating an API built using Express and MongoDB. We'll cover the ff. steps: Setting up MongoDB Creating an Express application Optimizing your Express routes Handling errors Let's start! Setting up Mong...

In a React + React Router environment, routing configuration is a one-to-one mapping between a route and that route's display elements. Here's a basic example: <Route exact path='/' component={Home} /> <Route exact path='/drafts' component={DraftList...

This post is part of a series that explores how flexible programming languages and frameworks can get. We all know that with any given task, there are several approaches available to achieve it. Let's look at this in action!
In this post, we will look at the different approaches in creating an Event Listener.
Of course, there's probably more than three ways to ways to do this in JavaScript, but, I carefully selected three unique ones that better matches the ff. categories: The Usual, The Okay, and The Complicated.
function EventListener() {
let _callbackA = () => {};
let _callbackB = () => {};
this.registerEventAHandler = function(callback) {
_callbackA = callback;
}
this.registerEventBHandler = function(callback) {
_callbackB = callback;
}
this.onEventA = function() {
_callbackA();
}
this.onEventB = function() {
_callbackB();
}
return this;
}
In the above implementation, we define a class that has two register functions for two event handlers. Additionally, there are two separate event triggers. Here's how it's used:
let listener = new EventListener();
listener.registerEventAHandler(() => {
console.log('Event A Happened!');
});
listener.registerEventBHandler(() => {
console.log('Event B Happened!');
});
listener.onEventA();
Only downside to this approach is that the handlers and the events are too tightly coupled. Every new event will require two sets of functions to be directly implemented in the class. It works but not really very flexible. Let's step it up a bit.
function EventListener() {
let _callbackMap = {};
this.registerEventHandler = function(eventName, callback) {
_callbackMap[eventName] = callback;
}
this.onEvent = function(eventName) {
_callbackMap[eventName]();
}
return this;
}
The above's a more flexible version — the handlers are now decoupled from the event itself and we can now define multiple event handlers without touching the EventListener class.
The biggest change in this approach is now we've added the _callbackMap private variable. This will store the mapping between each event and their respective handler. This is how it's used:
let listener = new EventListener();
listener.registerEventHandler('Event A', () => {
console.log('Event A Happened!');
});
listener.registerEventHandler('Event B', () => {
console.log('Event B Happened!');
});
listener.onEvent('Event A');
But what if you need to attach multiple handlers to one event? Technically, this can still be done using this approach. Check this out:
let listener = new EventListener();
listener.registerEventHandler('Event A', () => {
// First action
console.log('Event A Happened!');
// Second action
console.log('Now, let\'s do another thing for Event A!');
});
listener.onEvent('Event A');
The above solution works but is not really adhering to the Single Responsibility Principle. It's not really a good design to have one event handler to do two separate actions. By right, there should've been two handlers attached to the same event. Let's look at how that can be done in the next part.
We now want to be able to attach multiple handlers to a single event. Therefore, we need to change the data type of the _callbackMap variable since each event will now have a list of handlers. Take a look at the new code:
function EventListener() {
let _callbackMap = {};
this.registerEventHandler = function(eventName, callback) {
if (!_callbackMap[eventName]) {
_callbackMap[eventName] = [];
}
_callbackMap[eventName].push(callback);
}
this.onEvent = function(eventName) {
if (_callbackMap[eventName]) {
_callbackMap[eventName].forEach((handlerData) => {
handlerData();
});
}
}
return this;
}
Here's how it's used:
let listener = new EventListener();
let eventAName = 'Event A';
listener.registerEventHandler(eventAName, () => {
console.log('Event A Happened!');
});
listener.registerEventHandler(eventAName, () => {
console.log('Now, let\'s do another thing for Event A!');
});
listener.onEvent(eventAName);
The output will now be:
Event A Happened!
Now, let's do another thing for Event A!
The above solution now allows multiple handlers for individual events. So, when the event is triggered, all the attached handlers will be executed in the order they were registered. Let's step it up a little bit more. What if we want to control the flow by which the event handlers are executed? Here's how:
function EventListener() {
let _callbackMap = {};
this.registerEventHandler = function(eventName, callback, priority = 1) {
if (!_callbackMap[eventName]) {
_callbackMap[eventName] = [];
}
_callbackMap[eventName].push({
callback: callback,
priority: priority
});
// rearrange the handlers for this event based on priority
_callbackMap[eventName].sort((a, b) => {
if (a.priority < b.priority) {
return -1;
} else if (a.priority > b.priority) {
return 1;
}
return 0;
});
}
this.onEvent = function(eventName) {
if (_callbackMap[eventName]) {
_callbackMap[eventName].forEach((handlerData) => {
handlerData.callback();
});
}
}
return this;
}
let listener = new EventListener();
let eventAName = 'Event A';
listener.registerEventHandler(eventAName, () => {
console.log('Event A Happened!');
}, 2);
listener.registerEventHandler(eventAName, () => {
console.log('Now, let\'s do another thing for Event A!');
}, 1);
listener.onEvent(eventAName);
As for what's changed, the _callbackMap now has a priority field for each of its event-to-handler mapping. When an event is triggered, this priority is used to determine the order of execution of the handlers.
The output will now look like this:
Now, let's do another thing for Event A!
Event A Happened!
Pretty neat, right?
For your reference, all the code for this post can be found here.
Anyway, there's most likely many more ways to implementing an Event Handler. Feel free to share them by commenting on this post.
I hoped you enjoyed exploring these with me. I'll be posting more in this series (covering other programming languages too!) so make sure to stay tuned!