From 7a2b1d3e5f04c31e1b892882f94c2c0766ef82c9 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 3 Oct 2023 16:29:59 -0300 Subject: [PATCH] Base and Emitter classes --- base.js | 38 ++++++++++++++ emitter.js | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 base.js create mode 100644 emitter.js diff --git a/base.js b/base.js new file mode 100644 index 0000000..3780768 --- /dev/null +++ b/base.js @@ -0,0 +1,38 @@ +mimicry.module(function baseModule () { + let counter = 0; + class Base { + constructor () { + this._id = ++counter; + this._uncyclic = []; + } + get className () { + return this.constructor.name; + } + get id () { + return this._id; + } + destructor () { + for (let i = 0; i < this._uncyclic.length; ++i) { + try { + this._uncyclic[i].call(this); + } catch (e) { + //log probably + } + } + + for (let key in this) { + if (this.hasOwnProperty(key)) { + this[key] = undefined; + delete this[key]; + } + } + + this.destroyed = true; + } + recycle (/*Function*/fn) { + this._uncyclic.push(fn); + } + } + + return Base; +}); \ No newline at end of file diff --git a/emitter.js b/emitter.js new file mode 100644 index 0000000..17f7292 --- /dev/null +++ b/emitter.js @@ -0,0 +1,151 @@ +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; +}); \ No newline at end of file