244 lines
5.7 KiB
JavaScript
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() {
|
|
super();
|
|
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_PRIVATE(types, fn, that, false);
|
|
return this;
|
|
}
|
|
|
|
on_PRIVATE(typesOrType, fn, that, once) {
|
|
parseTypes(typesOrType).forEach((typeOrCompositeType) => {
|
|
this.onSingleEvent(typeOrCompositeType, fn, that, once);
|
|
});
|
|
}
|
|
|
|
once(types, fn, that = undefined) {
|
|
this.on_PRIVATE(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();
|
|
|
|
});
|
|
}
|