awv3
Version:
⚡ AWV3 embedded CAD
178 lines (156 loc) • 6.15 kB
JavaScript
export default class Events {
constructor() {
this._callbacks = undefined;
this._inspectors = undefined;
}
once(type, callback) {
this.on(type, callback, {remove: true});
}
onFirst(type, callback) {
this.on(type, callback, {priority: 1});
}
onLast(type, callback) {
this.on(type, callback, {priority: -1});
}
on(arg1 = {}, arg2 = undefined, options = {}) {
if (Array.isArray(arg1)) {
// (Array of types) & callback
for (let type of arg1)
this.on(type, arg2, options);
} else if (typeof arg1 === 'object') {
// Object with 'Type: Callback' pairs
if (typeof(arg2) === 'object')
options = arg2;
for (let [key, callback] of Object.entries(arg1))
if (typeof callback === 'function')
this.on(key, callback, options);
} else if (typeof arg1 === 'string' && typeof arg2 === 'function') {
// Type & Callback
let type = arg1, callback = arg2;
let {remove = false, priority = 0, sync = false} = options;
if (!this._callbacks) this._callbacks = {};
let listeners = this._callbacks[type];
if (!listeners) listeners = this._callbacks[type] = [];
// note: do not add duplicate callbacks
if (listeners.indexOf(callback) < 0) {
callback.remove = remove;
callback.sync = sync;
callback.priority = priority;
listeners.push(callback);
listeners.sort((a, b) => b.priority - a.priority);
if (this._inspectors) {
for (let inspector of this._inspectors)
inspector({ action: 'Add', type, callback });
}
}
}
return this;
}
inspect(callback) {
if (!this._inspectors) this._inspectors = [];
this._inspectors.push(callback);
}
removeListener(types, callback) {
if (!Array.isArray(types) && typeof types === 'object' && callback === undefined) {
// Object of 'Type: Callback'
for (let [type, callback] of Object.entries(types))
this.removeListener(type, callback);
return this;
}
if (!this._callbacks) this._callbacks = {};
types = Array.isArray(types) ? types : [types];
for (let type of types) {
let listeners = this._callbacks[type];
if (!listeners) continue;
if (!callback) {
delete this._callbacks[type];
if (this._inspectors) {
for (let inspector of this._inspectors)
inspector({ action: 'Remove', type, callback: undefined });
}
} else {
let index = listeners.indexOf(callback);
index > -1 && listeners.splice(index, 1);
if (this._inspectors) {
for (let inspector of this._inspectors)
inspector({ action: 'Remove', type, callback });
}
}
}
return this;
}
removeListeners() {
this._callbacks = undefined;
}
removeInspectors() {
this._inspectors = undefined;
}
emit(type, ...args) {
if (!this._callbacks) this._callbacks = {};
let listeners = this._callbacks[type];
//clone listeners array to ensure safe callbacks removal
listeners = listeners ? [...listeners] : [];
//call all synchronous events first;
for (let listener of listeners)
if (listener.sync) {
if (listener.remove)
this.removeListener(type, listener);
listener.call(this, ...args);
}
//call all asynchronous events in promise chain
let sequence = Promise.resolve();
for (let listener of listeners)
if (!listener.sync) {
sequence = sequence.then(() => {
if (listener.remove)
this.removeListener(type, listener);
return listener.call(this, ...args);
});
}
return sequence;
}
//TODO: does it work properly?
//what about case when listeners = []
bubble(type, ...args) {
if (!this._callbacks) this._callbacks = {};
let listeners = this._callbacks[type];
if (listeners) {
return this.emit(type, ...args);
} else if (this.parent) {
// No listener found, just bubble up ...
return this.parent.bubble(type, ...args);
}
return Promise.resolve();
}
findListener(type) {
if (!this._callbacks) this._callbacks = {};
let listeners = this._callbacks[type];
if (listeners) {
return this;
} else if (this.parent) {
// No listener found, just bubble up ...
return this.parent.find(type);
}
return undefined;
}
hasListener(type, callback = undefined) {
if (!this._callbacks) this._callbacks = {};
let listener = this._callbacks[type];
return !!listener && (!callback || callback === listener);
}
static mixin(object, handlers = null) {
object.hasListener = Events.prototype.hasListener;
object.on = Events.prototype.on;
object.once = Events.prototype.once;
object.onFirst = Events.prototype.onFirst;
object.onLast = Events.prototype.onLast;
object.inspect = Events.prototype.inspect;
object.removeListener = Events.prototype.removeListener;
object.removeListeners = Events.prototype.removeListeners;
object.removeInspectors = Events.prototype.removeInspectors;
object.emit = Events.prototype.emit;
object.bubble = Events.prototype.bubble;
object.findListener = Events.prototype.findListener;
if (handlers) object.on.bind(object)(handlers);
}
}