I would like to create a custom event emitter in my client-side programs. I am referencing this (sparse) documentation for EventTarget
My implementation atte
Without taking into consideration browser support where EventTarget can not be instantiated as a constructor and only to enrich this issue with yet another functional example.
According to the compatibility list described by Mozilla itself in this date (October 7, 2018):
EventTarget (constructor):
Extends:
class Emitter extends EventTarget {
constructor() {
super()
}
}
You could create common methods in many event plugins like: on(), off(), .once() and emit() (using CustomEvent):
/**
* Emmiter - Event Emitter
* @license The MIT License (MIT) - [https://github.com/subversivo58/Emitter/blob/master/LICENSE]
* @copyright Copyright (c) 2020 Lauro Moraes - [https://github.com/subversivo58]
* @version 0.1.0 [development stage] - [https://github.com/subversivo58/Emitter/blob/master/VERSIONING.md]
*/
const sticky = Symbol()
class Emitter extends EventTarget {
constructor() {
super()
// store listeners (by callback)
this.listeners = {
'*': [] // pre alocate for all (wildcard)
}
// l = listener, c = callback, e = event
this[sticky] = (l, c, e) => {
// dispatch for same "callback" listed (k)
l in this.listeners ? this.listeners[l].forEach(k => k === c ? k(e.detail) : null) : null
}
}
on(e, cb, once = false) {
// store one-by-one registered listeners
!this.listeners[e] ? this.listeners[e] = [cb] : this.listeners[e].push(cb);
// check `.once()` ... callback `CustomEvent`
once ? this.addEventListener(e, this[sticky].bind(this, e, cb), { once: true }) : this.addEventListener(e, this[sticky].bind(this, e, cb))
}
off(e, Fn = false) {
if ( this.listeners[e] ) {
// remove listener (include ".once()")
let removeListener = target => {
this.removeEventListener(e, target)
}
// use `.filter()` to remove expecific event(s) associated to this callback
const filter = () => {
this.listeners[e] = this.listeners[e].filter(val => val === Fn ? removeListener(val) : val);
// check number of listeners for this target ... remove target if empty
this.listeners[e].length === 0 ? e !== '*' ? delete this.listeners[e] : null : null
}
// use `while()` to iterate all listeners for this target
const iterate = () => {
let len = this.listeners[e].length;
while (len--) {
removeListener(this.listeners[e][len])
}
// remove all listeners references (callbacks) for this target (by target object)
e !== '*' ? delete this.listeners[e] : this.listeners[e] = []
}
Fn && typeof Fn === 'function' ? filter() : iterate()
}
}
emit(e, d) {
this.listeners['*'].length > 0 ? this.dispatchEvent(new CustomEvent('*', {detail: d})) : null;
this.dispatchEvent(new CustomEvent(e, {detail: d}))
}
once(e, cb) {
this.on(e, cb, true)
}
}
const MyEmitter = new Emitter()
// one or more listeners for same target ...
MyEmitter.on('xyz', data => {
console.log('first listener: ', data)
})
MyEmitter.on('xyz', data => {
console.log('second listener: ', data)
})
// fire event for this target
MyEmitter.emit('xyz', 'zzzzzzzzzz...') // see listeners show
// stop all listeners for this target
MyEmitter.off('xyz')
// try new "emit" listener event ?
MyEmitter.emit('xyz', 'bu bu bu') // nothing ;)
// fire a "once" ? Yes, fire
MyEmitter.once('abc', data => {
console.log('fired by "once": ', data)
})
// run
MyEmitter.emit('abc', 'Hello World') // its show listener only once
// test "once" again
MyEmitter.emit('abc', 'Hello World') // nothing