UNPKG

@adaptabletools/adaptable

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

245 lines (244 loc) 8.97 kB
/** based on emittery npm package, which is MIT */ const anyMap = new WeakMap(); const eventsMap = new WeakMap(); const triggeredMap = new WeakMap(); const resolvedPromise = Promise.resolve(); //type EmitterCallback = (data?: any) => any; function assertEventName(eventName) { if (typeof eventName !== 'string') { throw new TypeError('eventName must be a string'); } } function assertListener(listener) { if (typeof listener !== 'function') { throw new TypeError('listener must be a function'); } } function getListeners(instance, eventName) { const events = eventsMap.get(instance); if (!events) { // we're hitting this case only when calling `off` after the instance has been destroyed (so extremely rarely) // so we want to return an empty set in order not to make the code throw an error return new Set(); } if (!events.has(eventName)) { events.set(eventName, new Set()); } return events.get(eventName); } function defaultMethodNamesOrAssert(methodNames) { if (methodNames === undefined) { return allEmitteryMethods; } if (!Array.isArray(methodNames)) { throw new TypeError('`methodNames` must be an array of strings'); } for (const methodName of methodNames) { if (!allEmitteryMethods.includes(methodName)) { if (typeof methodName !== 'string') { throw new TypeError('`methodNames` element must be a string'); } throw new Error(`${methodName} is not Emittery method`); } } return methodNames; } class Emittery { static mixin(emitteryPropertyName, methodNames) { methodNames = defaultMethodNamesOrAssert(methodNames); return (target) => { if (typeof target !== 'function') { throw new TypeError('`target` must be function'); } for (const methodName of methodNames) { if (target.prototype[methodName] !== undefined) { throw new Error(`The property \`${methodName}\` already exists on \`target\``); } } function getEmitteryProperty() { Object.defineProperty(this, emitteryPropertyName, { enumerable: false, value: new Emittery(), }); return this[emitteryPropertyName]; } Object.defineProperty(target.prototype, emitteryPropertyName, { enumerable: false, get: getEmitteryProperty, }); const emitteryMethodCaller = (methodName) => (function(...args) { return this[emitteryPropertyName][methodName](...args); }); for (const methodName of methodNames) { Object.defineProperty(target.prototype, methodName, { enumerable: false, value: emitteryMethodCaller(methodName), }); } return target; }; } constructor() { anyMap.set(this, new Set()); eventsMap.set(this, new Map()); triggeredMap.set(this, new Map()); } destroy() { this.clearListeners(); // should not be needed, since those maps are weak // but let's do it anyway - some of our users suggested this Emitter class // could be a source of memory leaks, so let's clean this up anyMap.delete(this); eventsMap.delete(this); triggeredMap.delete(this); } on(eventName, listener) { assertEventName(eventName); assertListener(listener); getListeners(this, eventName).add(listener); return this.off.bind(this, eventName, listener); } off(eventName, listener) { assertEventName(eventName); assertListener(listener); getListeners(this, eventName).delete(listener); } once(eventName) { return new Promise((resolve) => { assertEventName(eventName); const off = this.on(eventName, (data) => { off(); resolve(data); }); }); } onIncludeFiredOnce(eventName) { const triggeredEventsMap = triggeredMap.get(this); const eventInfo = triggeredEventsMap.get(eventName); if (eventInfo) { return Promise.resolve(eventInfo.data); } return this.once(eventName); } onIncludeFired(eventName, listener) { const triggeredEventsMap = triggeredMap.get(this); const eventInfo = triggeredEventsMap.get(eventName); if (eventInfo) { listener(eventInfo.data); } return this.on(eventName, listener); } async emit(eventName, eventData) { assertEventName(eventName); const listeners = getListeners(this, eventName); const triggeredEventsMap = triggeredMap.get(this); const anyListeners = anyMap.get(this); const staticListeners = [...listeners]; const staticAnyListeners = [...anyListeners]; triggeredEventsMap.set(eventName, { data: eventData }); await resolvedPromise; return Promise.all([ ...staticListeners.map(async (listener) => { if (listeners.has(listener)) { return listener(eventData); } }), ...staticAnyListeners.map(async (listener) => { if (anyListeners.has(listener)) { return listener(eventName, eventData); } }), ]); } emitSync(eventName, eventData) { assertEventName(eventName); const listeners = getListeners(this, eventName); const triggeredEventsMap = triggeredMap.get(this); const anyListeners = anyMap.get(this); const staticListeners = [...listeners]; const staticAnyListeners = [...anyListeners]; triggeredEventsMap.set(eventName, { data: eventData }); return [ ...staticListeners.map((listener) => { if (listeners.has(listener)) { return listener(eventData); } }), ...staticAnyListeners.map((listener) => { if (anyListeners.has(listener)) { return listener(eventName, eventData); } }), ]; } async emitSerial(eventName, eventData) { assertEventName(eventName); const listeners = getListeners(this, eventName); const anyListeners = anyMap.get(this); const staticListeners = [...listeners]; const staticAnyListeners = [...anyListeners]; await resolvedPromise; /* eslint-disable no-await-in-loop */ for (const listener of staticListeners) { if (listeners.has(listener)) { await listener(eventData); } } for (const listener of staticAnyListeners) { if (anyListeners.has(listener)) { await listener(eventName, eventData); } } /* eslint-enable no-await-in-loop */ } onAny(listener) { assertListener(listener); anyMap.get(this).add(listener); return this.offAny.bind(this, listener); } offAny(listener) { assertListener(listener); anyMap.get(this).delete(listener); } clearListeners(eventName) { if (typeof eventName === 'string') { getListeners(this, eventName).clear(); } else { anyMap.get(this).clear(); for (const listeners of eventsMap.get(this).values()) { listeners.clear(); } } } listenerCount(eventName) { if (typeof eventName === 'string') { return anyMap.get(this).size + getListeners(this, eventName).size; } if (typeof eventName !== 'undefined') { assertEventName(eventName); } let count = anyMap.get(this).size; for (const value of eventsMap.get(this).values()) { count += value.size; } return count; } bindMethods(target, methodNames) { if (typeof target !== 'object' || target === null) { throw new TypeError('`target` must be an object'); } methodNames = defaultMethodNamesOrAssert(methodNames); for (const methodName of methodNames) { if (target[methodName] !== undefined) { throw new Error(`The property \`${methodName}\` already exists on \`target\``); } Object.defineProperty(target, methodName, { enumerable: false, value: this[methodName].bind(this), }); } } } const allEmitteryMethods = Object.getOwnPropertyNames(Emittery.prototype).filter((v) => v !== 'constructor'); export default Emittery;