/**
* Event Emitter.
*
* @author Alain Pitiot
* @version 2022.2.3
* @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
import * as util from "./Util.js";
/**
* <p>EventEmitter implements the classic observer/observable pattern.</p>
*
* <p>Note: this is heavily inspired by http://www.datchley.name/es6-eventemitter/</p>
*
* @example
* let observable = new EventEmitter();
* let uuid1 = observable.on('change', data => { console.log(data); });
* observable.emit("change", { a: 1 });
* observable.off("change", uuid1);
* observable.emit("change", { a: 1 });
*/
export class EventEmitter
{
/**
* @memberof module:util
*/
constructor()
{
this._listeners = new Map();
this._onceUuids = new Map();
}
/**
* Listener called when this instance emits an event for which it is registered.
*
* @callback module:util.EventEmitter~Listener
* @param {object} data - the data passed to the listener
*/
/**
* Register a new listener for events with the given name emitted by this instance.
*
* @param {String} name - the name of the event
* @param {module:util.EventEmitter~Listener} listener - a listener called upon emission of the event
* @return string - the unique identifier associated with that (event, listener) pair (useful to remove the listener)
*/
on(name, listener)
{
// check that the listener is a function:
if (typeof listener !== "function")
{
throw new TypeError("listener must be a function");
}
// generate a new uuid:
let uuid = util.makeUuid();
// add the listener to the event map:
if (!this._listeners.has(name))
{
this._listeners.set(name, []);
}
this._listeners.get(name).push({ uuid, listener });
return uuid;
}
/**
* Register a new listener for the given event name, and remove it as soon as the event has been emitted.
*
* @param {String} name - the name of the event
* @param {module:util.EventEmitter~Listener} listener - a listener called upon emission of the event
* @return string - the unique identifier associated with that (event, listener) pair (useful to remove the listener)
*/
once(name, listener)
{
let uuid = this.on(name, listener);
if (!this._onceUuids.has(name))
{
this._onceUuids.set(name, []);
}
this._onceUuids.get(name).push(uuid);
return uuid;
}
/**
* Remove the listener with the given uuid associated to the given event name.
*
* @param {String} name - the name of the event
* @param {module:util.EventEmitter~Listener} listener - a listener called upon emission of the event
*/
off(name, uuid)
{
let relevantUuidListeners = this._listeners.get(name);
if (relevantUuidListeners && relevantUuidListeners.length)
{
this._listeners.set(name, relevantUuidListeners.filter((uuidlistener) => (uuidlistener.uuid != uuid)));
return true;
}
return false;
}
/**
* Emit an event with a given name and associated data.
*
* @param {String} name - the name of the event
* @param {object} data - the data of the event
* @return {boolean} true if at least one listener has been registered for that event, and false otherwise
*/
emit(name, data)
{
let relevantUuidListeners = this._listeners.get(name);
if (relevantUuidListeners && relevantUuidListeners.length)
{
let onceUuids = this._onceUuids.get(name);
let self = this;
relevantUuidListeners.forEach(({ uuid, listener }) =>
{
listener(data);
if (typeof onceUuids !== "undefined" && onceUuids.includes(uuid))
{
self.off(name, uuid);
}
});
return true;
}
return false;
}
}