jsutils/emitter.js

151 lines
5.4 KiB
JavaScript

mimicry.module([
"./base"
], function emitterModule(global, [Base]) {
class Emitter extends Base {
constructor () {
super();
this._events = Object.create(null);
this._accumulatedEvents = Object.create(null);
}
destructor () {
this.off();
super.destructor();
}
/**
* Subscribes to event
*
* @param {String} name - name of the event you want to subscribe
* @param {Function} handler - a handler you want to perform on the event
* @param {Object} [context] - a context you want to perform your handler with
* */
on (name, handler, context) {
let handlers = this._events[name];
if (typeof name !== "string")
throw new Error("Name of event is mandatory");
if (!(handler instanceof Function))
throw new Error("Handler of event is mandatory");
if (!handlers) {
handlers = [];
this._events[name] = handlers;
}
handlers.push({
handler: handler,
context: context || this,
once: false
});
}
/**
* Subscribes to event, but the handler is called only once
*
* @param {String} name - name of the event you want to subscribe
* @param {Function} handler - a handler you want to perform on the event
* @param {Object} [context] - a context you want to perform your handler with
* */
one (name, handler, context) {
this.on(name, handler, context);
this._events[name][this._events[name].length - 1].once = true;
}
/**
* Unsubscribes from event
*
* @param {String} [name] - name of the event you want to unsubscribe from.
* If not passed unsubscribes every subscribed handler from all events
* @param {Function} [handler] - a handler you want to unsubscribe.
* If not passed unsubscribes all handlers from event defined by "name" parameter
* @param {Object} [context] - a context you want to unsubscribe.
* If not passed unsubscribes all matching handlers from
* event defined by "name" parameter.
* */
off (name, handler, context) {
if (typeof name !== "string") {
this._events = {};
return;
}
if (!this._events[name])
return;
if (!(handler instanceof Function)) {
delete this._events[name];
return;
}
const handlers = this._events[name];
for (let i = handlers.length - 1; i >= 0; --i) {
if (handlers[i].handler === handler) {
if (context || context === null) {
if (handlers[i].context === context)
handlers.splice(i, 1);
} else {
handlers.splice(i, 1);
}
}
}
}
/**
* Emits event name with given parameters
*
* @param {String} name - name of the emitting event
* @param {...Array} [params] - list of parameters separated by comma
* */
emit (...[name, ...params]) {
if (this._accumulatedEvents[name]) {
this._accumulatedEvents[name] = params;
return;
}
const handlers = this._events[name];
if (!handlers)
return;
for (let i = 0; i < handlers.length; ++i) {
const handle = handlers[i];
handle.handler.apply(handle.context, params);
if (handle.once)
handlers.splice(i--, 1);
}
}
/**
* Counts how many handlers are subscribed to given event name
*
* @param {String} name - name of the event
*
* @returns {Number} - how many handlers are subscribed to given event
* */
countHandlers (name) {
return this._events[name] && this._events[name].length || 0;
}
/**
* You can call this method and emit(name) will stop notifying subscribers
* It will keep the last event parameters event was called with instead
* This way you can avoid multiple notifications of the same event
*
* @param {String} name - event name
* */
accumulateEvent (name) {
this._accumulatedEvents[name] = true;
}
/**
* This method stops accumulation of the event and emits it
* if there were emissions during accumulation period
* If there were several emissions - event is emitted with the last set of parameters
*
* @param {String} name - event name
* */
releaseEvent (name) {
const params = this._accumulatedEvents[name];
delete this._accumulatedEvents[name];
if (params instanceof Array) {
params.unshift(name);
this.emit.apply(this, params);
}
}
}
return Emitter;
});