function createListener(fn, that, type, namespace, once) { return { fn, that, type, namespace, once, bound: that ? fn.bind(that) : fn, } } export function EventEmitterMixin(Superclass) { return class EventEmitter extends Superclass { constructor(...args) { super(...args); this.$$events = {}; this.$$namespaces = {}; } addListener(...args) { return this.on(...args); } // Notify ALL the listeners! emit(...args) { const type = args[0]; const listeners = this.lookupEmitListeners(type); if (0 === listeners.length) { return; } for (const {once, type, namespace, fn, bound, that} of listeners) { const offset = type !== '*' ? 1 : 0; if (once) { this.removeListener(`${type}.${namespace}`, fn); } // Fast path... if (args.length === offset) { bound() } else if (args.length === offset + 1) { bound( args[offset + 0] ); } else if (args.length === offset + 2) { bound( args[offset + 0], args[offset + 1] ) } else if (args.length === offset + 3) { bound( args[offset + 0], args[offset + 1], args[offset + 2] ) } else if (args.length === offset + 4) { bound( args[offset + 0], args[offset + 1], args[offset + 2], args[offset + 3] ) } else if (args.length === offset + 5) { bound( args[offset + 0], args[offset + 1], args[offset + 2], args[offset + 3], args[offset + 4] ) } // Slow path... else { fn.apply(that, args.slice(offset)); } } } lookupEmitListeners(type) { return ['*', type].reduce((r, type) => { if (type in this.$$events) { r.push(...this.$$events[type]); } return r; }, []); } off(typesOrType, fn) { parseTypes(typesOrType).forEach((typeOrCompositeType) => { this.offSingleEvent(typeOrCompositeType, fn); }); return this; } offSingleEvent(typeOrCompositeType, fn) { const [type, namespace] = decomposeType(typeOrCompositeType); // Function. if ('function' === typeof fn) { const lists = []; if ((type in this.$$events)) { lists.push(this.$$events); } if ( (namespace in this.$$namespaces) && (type in this.$$namespaces[namespace]) ) { lists.push(this.$$namespaces[namespace]); } lists.forEach((listeners) => { listeners[type] = listeners[type].filter((listener) => { return listener.fn !== fn; }); }); return; } // Only type. if (0 === namespace.length) { if (type in this.$$events) { delete this.$$events[type]; } for (const namespace in this.$$namespaces) { const namespaceEvents = this.$$namespaces[namespace]; if (type in namespaceEvents) { delete namespaceEvents[type]; } } return; } // Only namespace. if (!(namespace in this.$$namespaces)) { return; } if (0 === type.length) { for (const type in this.$$namespaces[namespace]) { this.removeEventListenersFor(type, namespace); } delete this.$$namespaces[namespace]; } // Type & namespace. else if (type in this.$$namespaces[namespace]) { this.removeEventListenersFor(type, namespace); delete this.$$namespaces[namespace][type]; } } on(types, fn, that = undefined) { this._on(types, fn, that, false); return this; } _on(typesOrType, fn, that, once) { parseTypes(typesOrType).forEach((typeOrCompositeType) => { this.onSingleEvent(typeOrCompositeType, fn, that, once); }); } once(types, fn, that = undefined) { this._on(types, fn, that, true); return this; } onSingleEvent(typeOrCompositeType, fn, that, once) { const [type, namespace] = decomposeType(typeOrCompositeType); const listener = createListener(fn, that, type, namespace, once); if (!(type in this.$$events)) { this.$$events[type] = []; } this.$$events[type].push(listener); if (!(namespace in this.$$namespaces)) { this.$$namespaces[namespace] = {}; } if (!(type in this.$$namespaces[namespace])) { this.$$namespaces[namespace][type] = []; } this.$$namespaces[namespace][type].push(listener); } removeEventListenersFor(type, namespace) { for (const {fn} of this.$$namespaces[namespace][type]) { this.$$events[type] = this.$$events[type].filter((listener) => { return listener.fn !== fn; }); } if (0 === this.$$events[type].length) { delete this.$$events[type]; } } removeListener(...args) { return this.off(...args); } } } export function decomposeType(typeOrCompositeType) { const index = typeOrCompositeType.indexOf('.'); const isCompositeType = -1 !== index; if (isCompositeType) { return [ typeOrCompositeType.substr(0, index), typeOrCompositeType.substr(index + 1), ]; } return [typeOrCompositeType, '']; } // Split, flatten, and trim. export function parseTypes(typesOrType) { const types = Array.isArray(typesOrType) ? typesOrType : [typesOrType]; return types.map((type) => { return type.split(' '); }).reduce((r, types) => { r.push(...types); return r; }, []).map((type) => { return type.trim(); }); }