Extend the arrays in javascript such that an event gets dispatched whenever an item is added or removed.
Example
Input:
const arr = [];
arr.addListener('add', (eventName, items, array) => {
console.log('items were added', items);
});
arr.addListener('remove', (eventName, item, array) => {
console.log(item, ' was removed');
});
arr.pushWithEvent('add', [4, 5]);
arr.popWithEvent('remove');
Output:
"items were added" // [object Array] (2)
[4,5]
5 " was removed"
The solution for this is quite straightforward, we will extend the array prototype and a few methods to it like,
- listeners : This will store the list of event listeners associated with the event name.
- addListener(eventName, callback) : This will add a callback to the event.
- pushWithEvent(eventName, items) : Adds all the items in the array and triggers the event with the given name.
- popWithEvent(eventName) : Removes the last items from the array and triggers the event with the given name.
- triggerEvent(eventName, args) : A helper function that triggers all the callbacks associated with the given event name.
- removeListener(eventName, callback) : Removes the callback attached to the eventName. Note: It won’t work for anonymous functions.
// to track the events and their callbacks
Array.prototype.listeners = {};
// to add/assign a new event with listener
Array.prototype.addListener = function(name, callback){
// if there are no listener present
// create a new one
// we will invoke all the callbacks when event is triggered
if (!this.listeners[name]) {
this.listeners[name] = [];
}
this.listeners[name].push(callback);
}
// add a new method that triggers an event on push
// Calls trigger event
Array.prototype.pushWithEvent = function(event, args) {
// push the new values
this.push(...args);
// trigger add event
this.triggerEvent(event, args);
};
// add a new method that triggers an event on pop
// Calls trigger event
Array.prototype.popWithEvent = function(event, args) {
// push the new values
const element = this.pop();
// trigger add event
this.triggerEvent(event, element);
};
Array.prototype.triggerEvent = function(eventName, elements) {
// if the event is present
// trigger all the callbacks with the value
if (this.listeners[eventName]) {
this.listeners[eventName].forEach(callback =>
callback(eventName, elements, this)
);
}
};
Array.prototype.removeListener = function(eventName, callback){
// if event exists
if(this.listeners[eventName]){
// filter out the listener
// note: this won't work for anonymous function.
this.listeners[eventName] = this.listeners[eventName].filter((e) => e !== callback);
}
}
Input:
const arr = [];
const onAdd = (eventName, items, array) => {
console.log('items were added', items);
}
const onAddAgain = (eventName, items, array) => {
console.log('items were added again', items);
}
arr.addListener('add', onAdd);
arr.addListener('add', onAddAgain);
arr.addListener('remove', (eventName, item, array) => {
console.log(item, ' was removed');
});
arr.pushWithEvent('add', [1, 2, 3, 'a', 'b']);
arr.removeListener('add', onAddAgain); // removes the second listener
arr.pushWithEvent('add', [4, 5]);
arr.popWithEvent('remove');
console.log(arr);
Output:
"items were added" // [object Array] (5)
[1,2,3,"a","b"]
"items were added again" // [object Array] (5)
[1,2,3,"a","b"]
"items were added" // [object Array] (2)
[4,5]
5 " was removed"
// [object Array] (6)
[1,2,3,"a","b",4]
You can also add a remove listener at the end for memory cleanup.