avocado-old/packages/mixins/event-emitter.js
2019-04-07 13:00:11 -04:00

244 lines
5.7 KiB
JavaScript

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