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; });