VulpesOS/gaia/shared/js/event_dispatcher.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

231 lines
6.9 KiB
JavaScript
Executable file

/*global Map, Set */
/* exported EventDispatcher */
/*
* This file provides an helper to add custom events to any object.
*
* In order to use this functionality wrap your object using the
* 'EventDispatcher.mixin' function:
*
* var obj = EventDispatcher.mixin(new SomeObj());
*
* A list of events can be optionally provided and it is recommended to do so.
* If a list is provided then only the events present in the list will be
* allowed. Using events not present in the list will cause other functions to
* throw an error:
*
* var obj = EventDispatcher.mixin(new SomeObj(), [
* 'somethinghappened',
* 'somethingelsehappened'
* ]);
*
* The wrapped object will have five new methods: 'on', 'once', 'off', 'offAll'
* and 'emit'. Use 'on' to register a new event-handler:
*
* obj.on("somethinghappened", function onSomethingHappened() { ... });
*
* If the same event-handler is added multiple times then only one will be
* registered, e.g.:
*
* function onSomethingHappened() { ... }
* obj.on("somethinghappened", onSomethingHappened);
* obj.on("somethinghappened", onSomethingHappened); // Does nothing
*
* Use 'off' to remove a registered listener:
*
* obj.off("somethinghappened", onSomethingHappened);
*
* Use 'once' to register a one-time event-handler: it will be automatically
* unregistered after being called.
*
* obj.once("somethinghappened", function onSomethingHappened() { ... });
*
* And use 'offAll' to remove all registered event listeners for the specified
* event:
*
* obj.offAll("somethinghappened");
*
* When used without parameters 'offAll' removes all registered event handlers,
* this can be useful when writing unit-tests.
*
* Finally use 'emit' to send an event to the registered handlers:
*
* obj.emit("somethinghappened");
*
* An optional parameter can be passed to 'emit' to be passed to the registered
* handlers:
*
* obj.emit("somethinghappened", 123);
*/
(function(exports) {
'use strict';
function ensureValidEventName(eventName) {
if (!eventName || typeof eventName !== 'string') {
throw new Error('Event name should be a valid non-empty string!');
}
}
function ensureValidHandler(handler) {
if (typeof handler !== 'function') {
throw new Error('Handler should be a function!');
}
}
function ensureAllowedEventName(allowedEvents, eventName) {
if (allowedEvents && allowedEvents.indexOf(eventName) < 0) {
throw new Error('Event "' + eventName + '" is not allowed!');
}
}
// Implements publish/subscribe behaviour that can be applied to any object,
// so that object can be listened for custom events. "this" context is the
// object with Map "listeners" property used to store handlers.
var eventDispatcher = {
/**
* Registers listener function to be executed once event occurs.
* @param {string} eventName Name of the event to listen for.
* @param {function} handler Handler to be executed once event occurs.
*/
on: function(eventName, handler) {
ensureValidEventName(eventName);
ensureAllowedEventName(this.allowedEvents, eventName);
ensureValidHandler(handler);
var handlers = this.listeners.get(eventName);
if (!handlers) {
handlers = new Set();
this.listeners.set(eventName, handlers);
}
// Set.add ignores handler if it has been already registered
handlers.add(handler);
},
/**
* Registers listener function to be executed only first time when event
* occurs.
* @param {string} eventName Name of the event to listen for.
* @param {function} handler Handler to be executed once event occurs.
*/
once: function(eventName, handler) {
ensureValidHandler(handler);
var once = (parameters) => {
eventDispatcher.off.call(this, eventName, once);
handler(parameters);
};
eventDispatcher.on.call(this, eventName, once);
},
/**
* Removes registered listener for the specified event.
* @param {string} eventName Name of the event to remove listener for.
* @param {function} handler Handler to remove, so it won't be executed
* next time event occurs.
*/
off: function(eventName, handler) {
ensureValidEventName(eventName);
ensureAllowedEventName(this.allowedEvents, eventName);
ensureValidHandler(handler);
var handlers = this.listeners.get(eventName);
if (!handlers) {
return;
}
handlers.delete(handler);
if (!handlers.size) {
this.listeners.delete(eventName);
}
},
/**
* Removes all registered listeners for the specified event.
* @param {string} eventName Name of the event to remove all listeners for.
*/
offAll: function(eventName) {
if (typeof eventName === 'undefined') {
this.listeners.clear();
return;
}
ensureValidEventName(eventName);
ensureAllowedEventName(this.allowedEvents, eventName);
var handlers = this.listeners.get(eventName);
if (!handlers) {
return;
}
handlers.clear();
this.listeners.delete(eventName);
},
/**
* Emits specified event so that all registered handlers will be called
* with the specified parameters.
* @param {string} eventName Name of the event to call handlers for.
* @param {Object} parameters Optional parameters that will be passed to
* every registered handler.
*/
emit: function(eventName, parameters) {
ensureValidEventName(eventName);
ensureAllowedEventName(this.allowedEvents, eventName);
var handlers = this.listeners.get(eventName);
if (!handlers) {
return;
}
handlers.forEach(function(handler) {
try {
handler(parameters);
} catch (e) {
console.error(e);
}
});
}
};
exports.EventDispatcher = {
/**
* Mixes dispatcher methods into target object.
* @param {Object} target Object to mix dispatcher methods into.
* @param {Array.<string>} allowedEvents Optional list of the allowed event
* names that can be emitted and listened for.
* @returns {Object} Target object with added dispatcher methods.
*/
mixin: function(target, allowedEvents) {
if (!target || typeof target !== 'object') {
throw new Error('Object to mix into should be valid object!');
}
if (typeof allowedEvents !== 'undefined' &&
!Array.isArray(allowedEvents)) {
throw new Error('Allowed events should be a valid array of strings!');
}
Object.keys(eventDispatcher).forEach(function(method) {
if (typeof target[method] !== 'undefined') {
throw new Error(
'Object to mix into already has "' + method + '" property defined!'
);
}
target[method] = eventDispatcher[method].bind(this);
}, { listeners: new Map(), allowedEvents: allowedEvents });
return target;
}
};
})(self);