Forwarding events from one event emitter to another - javascript

I have one event emitter (a transform stream) and that event emitter basically has a bunch of child emitters.
I want to forward events from all the child emitters to the parent emitter, something like this:
const EE = require('events');
const exportEvents = new EE();
const sumanEvents = Transform(); // create new transform stream (an event emitter)
sumanEvents.on('test', function () {
exportEvents.emit.bind(exportEvents, 'test').apply(exportEvents, arguments);
});
sumanEvents.on('error', function () {
exportEvents.emit.bind(exportEvents, 'error').apply(exportEvents, arguments);
});
sumanEvents.on('suman-test-file-complete', function () {
exportEvents.emit.bind(exportEvents, 'suman-test-file-complete').apply(exportEvents, arguments);
});
Basically, from what I can tell, I have forwarded the error, test, and suman-test-file-complete events to the parent, but this seems pretty ugly.
Is there at least a more sophisticated way of doing it? I assume there is not a way to directly inherit events with Node.js 'events' package so I am not asking about that.

You could override sumanEvents.emit() so you could see any event that was emitted and then grab it and echo it to exportEvents:
(function(origEmitter, forwardEmitter) {
// save original .emit method
let oldEmit = origEmitter.emit;
// assign override
origEmitter.emit = function() {
// allow the event to be normally emitted
oldEmit.apply(origEmitter, arguments);
// then forward it to the forwardEmitter
forwardEmitter.emit.apply(forwardEmitter, arguments);
};
})(sumanEvents, exportEvents);
Or, put into a reusable function form so you can use it on more than one emitter without copying the code:
function forwardEmitter(origEmitter, forwardEmitter) {
// save original .emit method
let oldEmit = origEmitter.emit;
// assign override
origEmitter.emit = function() {
// allow the event to be normally emitted
oldEmit.apply(origEmitter, arguments);
// then forward it to the forwardEmitter
forwardEmitter.emit.apply(forwardEmitter, arguments);
};
}
forwardEmitter(sumanEvents, exportEvents);
This functionality could be encapsulated in a new object that derives from EventEmitter if you wanted to more easily be able to reuse it, but that would only work if you control the creation of the first EventEmitter object so you could make it your special derived object that supported forwarding of all messages. The example above can be "added on" to any existing regular EventEmitter object.
There's also an EventEmitter2 object described here that allows you to use wildcards to register event handlers so you could register an interest in all events or a subset of events that way without naming each one individually.
And, there's a discussion of this concept on a Google mailing list: https://groups.google.com/forum/#!topic/nodejs-dev/TzRPxsHf0FA

but this seems pretty ugly.
Is there a reason you're binding and applying? It seems like these are being used incorrectly. Lines like exportEvents.emit.bind(exportEvents, 'test').apply(exportEvents, arguments) can be simplified to EE.emit.apply(exportEvents, 'test', arguments) or, using spread syntax, exportEvents.emit('test', ...arguments)
That being said, a utility function might be helpful here. jfriend00's above solution is ok, but I don't really like that it overrides a method on the original object (even if the original method is being called).
The other solution actually works out to be simpler (fewer lines of code):
function forwardEvent(emitterToListenOn, emitterToEmitOn, event) {
emitterToListenOn.on(event, function() { //can't use arrow here, as we need 'arguments'
emitterToEmitOn.emit(event, arguments);
});
}
function forwardEvents(emitterToListenOn, emitterToEmitOn, events) {
events.forEach(event => forwardEvent(emitterToListenOn, emitterToEmitOn, event));
}
function forwardEventsToMultipleEmitters(emitterToListenOn, emittersToEmitOn, events) {
emittersToEmitOn.forEach(emitterToEmitOn => forwardEvents(emitterToListenOn, emitterToEmitOn, events));
}
For those that aren't comfortable with arrow functions, here's the same code without:
function forwardEvent(emitterToListenOn, emitterToEmitOn, event) {
emitterToListenOn.on(event, function() { //can't use arrow here, as we need 'arguments'
emitterToEmitOn.emit(event, arguments);
});
}
function forwardEvents(emitterToListenOn, emitterToEmitOn, events) {
events.forEach(function(event) {
forwardEvent(emitterToListenOn, emitterToEmitOn, event);
});
}
function forwardEventsToMultipleEmitters(emitterToListenOn, emittersToEmitOn, events) {
emittersToEmitOn.forEach(function(emitterToEmitOn) {
forwardEvents(emitterToListenOn, emitterToEmitOn, events);
});
}
To use these in your original code,
const EE = require('events');
const exportEvents = new EE();
const sumanEvents = Transform(); // create new transform stream (an event emitter)
forwardEvents(sumanEvents, exportEvents, ['test', 'error', 'suman-test-file-complete']);
This forwardEvent is fewer lines of code than jfriend00's forwardEmitter and doesn't override any functions and the final solution looks pretty clean to me.

Related

Dispatch event from custom objects in JavaScript

Is it possible to create custom events in objects? Something like this
var myCustomClass = function (param) {
this.param = param;
};
myCustomClass.prototype.start = function(){
//Do staff
//fire event started
let event = new Event("started", {isStarted: true});
this.dispatchEvent(event);
}
And in mai.js create an instance of myCustomClass
myCustomClass = new myCustomClassr(param);
myCustomClass.addEventListener('started', function () {
console.log("started");
});
myCustomClass.start();
With this code I am getting an error telling me that dispatchEvent is not a function
dispatchEvent is not available on all JavaScript objects. This is very doable, but you need to inherit from an Event Emitter, so your class has event functionality.
There are zillions of Event Emitter things in JS, and you can also write your own pretty easily.
https://netbasal.com/javascript-the-magic-behind-event-emitter-cce3abcbcef9

How should I use JSDoc to document DOM events in my design pattern?

I have a custom-built JavaScript framework that I inherited from the person that worked here before me. What I can say is that it's a web app of sorts that leverages jQuery.
There are a lot of events being fired by various objects that I'd like to document, but I'm unsure of how to do this. I've seen JSDoc 3's examples on the #listens page, but they seem to be oriented more towards a module development pattern, so I'm unsure of how to apply them to the pattern here:
ClassA.js:
var ClassA = function (args) {
//constructor stuff goes here... usually punctuated with:
this.listenForEvents();
};
ClassA.prototype.listenForEvents = function () {
$(document).on("someEvent", function (e, args) {
//Event handlers are almost always anonymous and call class methods,
//usually to determine if the event needs to be handled by a specific
//instance of the class, or meeting a criteria. e.g.:
if (this.identifier === args.identifier) {
this.someClassMethod(args);
}
}.bind(this));
};
ClassA.prototype.someClassMethod = function (args) {
//handle the event
};
ClassB.js:
var ClassB = function (args) {
//constructor stuff goes here...
};
ClassB.prototype.doAThing = function () {
//code goes here
$(document).trigger('someEvent', { identifier: 'someIdentifier', aValue: 1, anotherValue: 'two' });
};
How/where do I use the #event, #listens, and #fires doclets with this design pattern?

Why define event handler member functions (inline) inside the constructor function in order to work with 'unbind'?

While studying the source code for a signature pad widget, I find the following code snippet inside the constructor function (note in particular the comment in the following snippet):
var SignaturePad = function (canvas, options) {
...
// MY QUESTION IS ABOUT THE FOLLOWING CODE COMMENT!!!
// v v v
// we need add these inline so they are available to unbind while still having
// access to 'self' we could use _.bind but it's not worth adding a dependency
this._handleMouseDown = function (event) {
if (event.which === 1) {
self._mouseButtonDown = true;
self._strokeBegin(event);
}
};
// ... other event handlers here
...
}
... for completeness in providing context for the above code, later the event handlers are bound as event listeners:
SignaturePad.prototype._handleMouseEvents = function () {
...
this._canvas.addEventListener("mousedown", this._handleMouseDown);
...
};
From the above code snippet, you can see the comment:
we need add these inline so they are available to unbind while still having access to 'self'
we could use _.bind but it's not worth adding a dependency`
I am scratching my head about this. Why is access self required when unbinding (and I assume by 'unbinding' is meant detaching the event listener, but please correct me if I'm wrong)?
In other words, I'd like to understand the above code comment so that I can be certain I understand the JavaScript and/or event binding thoroughly in this code.
The .addEventListener calls in that code receive a function reference when binding the handler. In order to use .removeEventListener to unbind, you need to pass a reference to the same function handler.
Because the SignaturePad constructor creates a new, unique (though identical) function for each instance, and binds that function, they need to keep a reference to that function in order to unbind later on. Therefore they put it directly on the object for later use.
The reason they create these handlers inside the constructor function is that they want them to be able to reference the SignaturePad instance that was created. So they create a var self = this variable, and have the functions created in the constructor reference self. If the handlers were on the .prototype, there would be no way for that shared handler to reference the original object, given their approach.
Here's a truncated version of their code that shows how to use the EventListener interface:
var SignaturePad = function(canvas, options) {
this._handleMouseEvents();
};
// Implements the EventListener interface
SignaturePad.prototype.handleEvent = function(event) {
switch (event.type) {
case "mousedown":
this._handleMouseDown(event)
break
case "mousemove":
this._handleMouseMove(event)
break
case "mouseup":
this._handleMouseUp(event)
break
default:
console.log("Unbound event type:", event.type)
}
}
SignaturePad.prototype._handleMouseDown = function(event) {
if (event.which === 1) {
this._mouseButtonDown = true;
this._strokeBegin(event);
}
};
SignaturePad.prototype._handleMouseMove = function(event) {
if (this._mouseButtonDown) {
this._strokeUpdate(event);
}
};
SignaturePad.prototype._handleMouseUp = function(event) {
if (event.which === 1 && this._mouseButtonDown) {
this._mouseButtonDown = false;
this._strokeEnd(event);
}
};
SignaturePad.prototype._strokeUpdate = function(event) {
console.log("stroke update");
};
SignaturePad.prototype._strokeBegin = function(event) {
console.log("stroke begin");
};
SignaturePad.prototype._strokeEnd = function(event) {
console.log("stroke end");
};
SignaturePad.prototype._handleMouseEvents = function() {
this._mouseButtonDown = false;
this._canvas.addEventListener("mousedown", this);
this._canvas.addEventListener("mousemove", this);
document.addEventListener("mouseup", this);
};
So you can see that the handleEvent method was added, and we don't actually bind any functions using .addEventListener. Instead, we bind a reference to the SignaturePad object itself.
When an event occurs, the handleEvent method is invoked with the value of this pointing our SignaturePad object we bound. We still have access to the element as well via event.currentTarget.
So this lets us reuse functions on the .prototype and gives us all the object references we need. And of course unbinding is done the same way, except that we pass the object we bound to .removeEventListener.

Listen for all events in JavaScript

I'm trying to figure out how to listen for all events on a JavaScript object.
I know that I can add individual events with something like this
element.addEventListener("click", myFunction);
element.addEventListener("mouseover", myFunction);
...
I'm trying to figure out if there is a catch-all, I'd like to do something like this:
// Begin pseudocode
var myObj = document.getElementById('someID');
myObj.addEventListener(/*catch all*/, myFunction);
function myFunction() {
alert(/*event name*/);
}
// End pseudocode
A more modern rewrite of #roman-bekkiev's answer:
Object.keys(window).forEach(key => {
if (/^on/.test(key)) {
window.addEventListener(key.slice(2), event => {
console.log(event);
});
}
});
Note that you can further customize what you want to catch, for example:
/^on(key|mouse)/.test(key)
To pick up standard element's events.
var myObj = document.getElementById('someID');
for(var key in myObj){
if(key.search('on') === 0) {
myObj.addEventListener(key.slice(2), myFunction)
}
}
But as #jeremywoertink mentioned any other events are also possible.
I hate that this problem persists without a native or elegant solution.
A Better Solution?
This allows you to subscribe to a single CustomEvent for any EventTarget using target.addEventListener('*', ...).
clear();
/**
* #param : source := EventTarget
* * EventTarget.prototype
* * Node (Element, Attr, etc)
* #usage : [Node].addEventListener('*', ({ detail: e }) => {...}, false);
*/
function proxyEventTargetSource(source) {
var emit = source.dispatchEvent; // obtain reference
function proxy(event) {
var { type } = event, any = new CustomEvent('*', { detail: event }); // use original event as detail
if (!{ '*': true }[ type ]) emit.call(this, any); // only emit "any" if type is not any.type ('*')
return emit.call(this, event);
}
if ({ 'dispatchEvent': true }[ emit.name ]) source.dispatchEvent = proxy; // attempt overwrite only if not already set (avoid rewrapping)
return (source.dispatchEvent === proxy); // indicate if its set after we try to
}
// proxyEventTargetSource(EventTarget.prototype); // all targets
proxyEventTargetSource(document); // single target
var e = new CustomEvent('any!', { detail: true });
document.addEventListener('*', (e) => console.log('type: %s, original: %s, e: %O', e.type, e.detail.type, e), false);
document.dispatchEvent(e);
Granted, a more native or [perhaps] more elegant way would be to use a native Proxy on apply for the target's dispatchEvent method, but that would maybe convey less for the sake of this post.
Gist: https://gist.github.com/cScarlson/875a9fca7ab7084bb608fb66adff0463
Known Issues
Apparently, this only works while driving event-dispatching through EventTargets's dispatchEvent method. That is, naturally triggering events through mouse events (for instance) does not work. There would need to be a way to wrap the internal method being called by natural event-triggers.
That being said, if you have a way around this, please show what you have in another answer.
As far as I know, it's possible.
For all native events, we can retrieve a list of supported events by iterating over the target.onevent properties and installing our listener for all of them.
for (const key in target) {
if(/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener);
}
}
The only other way that events are emitted which I know of is via EventTarget.dispatchEvent, which every Node and thefore every Element inherits.
To listen for all these manually triggered events, we can proxy the dispatchEvent method globally and install our listener just-in-time for the event whose name we just saw ✨ ^^
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
EventTarget.prototype.dispatchEvent = function (event) {
if (!alreadyListenedEventTypes.has(event.type)) {
target.addEventListener(event.type, listener, ...otherArguments);
alreadyListenedEventTypes.add(event.type);
}
dispatchEvent_original.apply(this, arguments);
};
🔥 function snippet 🔥
function addEventListenerAll(target, listener, ...otherArguments) {
// install listeners for all natively triggered events
for (const key in target) {
if (/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener, ...otherArguments);
}
}
// dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
function dispatchEvent(event) {
target.addEventListener(event.type, listener, ...otherArguments); // multiple identical listeners are automatically discarded
dispatchEvent_original.apply(this, arguments);
}
EventTarget.prototype.dispatchEvent = dispatchEvent;
if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);
}
// usage example
addEventListenerAll(window, (evt) => {
console.log(evt.type);
});
document.body.click();
document.body.dispatchEvent(new Event('omg!', { bubbles: true }));
// usage example with `useCapture`
// (also receives `bubbles: false` events, but in reverse order)
addEventListenerAll(
window,
(evt) => { console.log(evt.type); },
true
);
document.body.dispatchEvent(new Event('omfggg!', { bubbles: false }));
You could use EventEmitter2 which does wildcards. The problem with doing a catchall like you're talking about is that there are so many events, and you can create your own. You'd have to make an array of specifically which events you're talking about, iterate over that, and bind each one individually.
You should probably pick the events you want to listen to, put them into an array and iterate over each:
['click','mouseover'].forEach(function(ev) {
el.addEventListener(ev, function() {
console.log('event:', ev)
})
})
//listening for all click events on the document
document.addEventListener('click', function (event) {
//filtering for only events that happen on elements that contain the class
//view_btn.
if (event.target.classList.contains( 'view_btn' )){
//logging out the id of the element
var id_of_clicked_element = event.target.getAttribute("id"); //
console.log("button clicked has is of " + id_of_clicked_element)
}
});

Understanding better Javascript OOP architecture [duplicate]

This question already has answers here:
JavaScript: Class.method vs. Class.prototype.method
(5 answers)
Closed 9 years ago.
As i read through some examples of Angularjs' UI add-on, i've stumbled over some code that showed me that my knowdledge of Javascript is quite improvable:
The following is a class inside of an Angular provider:
function Dialog(opts) {
var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
this._open = false;
this.backdropEl = createElement(options.backdropClass);
if(options.backdropFade){
// ...
}
this.handleLocationChange = function() {
self.close();
};
// more functions
}
Pretty straightforward. But outside of that class, there are prototype functions, e.g the above invoked close()
Dialog.prototype.open = function(templateUrl, controller){
var self = this, options = this.options;
// .. some code
};
Now i do not understand why that function is declared as a prototype, but handleLocationChange inside the class itself.
How do i decide which method to choose?
The full gist can be found here
Consider these 2 cases:
Dialog.prototype.open = function...
Dialog.open = function....
First case - every object created by calling new Dialog() will have this open function
Second case has nothing to do with dialog objects, consider it as static function.
EDIT
found a great answer here : javascript-class-method-vs-class-prototype-method
function open will be shared by all objects create using new Dialog().. and handleLocationChange will be different for different objects.
I think handleLocationChange is called from event triggering object that registers listeners but doesn't register the this context so when it's triggered you can't use this as it refers to handleLocationChange. To overcome this they have chosen to set a closure reference to this (=the self variable) and call other instance functions using self. Basically it's storing a value known at creation but not known when handleLocationChange is executing.
Here is some code showing the problem:
var eventSystem={
events:{},
add:function(eventname,fnCallback){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push(fnCallback);
},
trigger:function(eventname){
if(!this.events[eventname]){
return;
}
var i=0;
for(i=0;i<this.events[eventname].length;i++){
this.events[eventname][i]();
}
}
};
var person=function(name){
this.name=name;
};
person.prototype.sayName=function(){
console.log("this is now:",this.toString());
// logs this is now: function (){ console.log("this is now:...
// so this is now the sayName function not the person instance
console.log(this.name);//undefined: sayName doesn't have a name property
}
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event
Here is how it's solved setting a closure reference
var eventSystem={
events:{},
add:function(eventname,fnCallback){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push(fnCallback);
},
trigger:function(eventname){
if(!this.events[eventname]){
return;
}
var i=0;
for(i=0;i<this.events[eventname].length;i++){
this.events[eventname][i]();
}
}
};
var person=function(name){
var self=this;// set closure ref to this
this.name=name;
this.sayName=function(){
console.log(self.name);//use closure ref to get this
// logs jon
}
};
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event
Here is a fix to the event system to take care of the this context:
var eventSystem={
events:{},
add:function(eventname,fnCallback,thisRef){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push({
"callback":fnCallback,//store the event handler
"thisRef":thisRef//store the this context
});
},
trigger:function(eventname){
if(!this.events[eventname]){
return;
}
var i=0;
for(i=0;i<this.events[eventname].length;i++){
this.events[eventname][i].callback.call(
this.events[eventname][i].thisRef);
}
}
};
var person=function(name){
this.name=name;
};
person.prototype.sayName=function(){
console.log("this is now:",this);//referring to person instance
// with the name jon
console.log(this.name);//logs jon
console.log(this instanceof person);//true
}
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName,jon);//add extra parameter for this ref
eventSystem.trigger("sayname");//trigger the event
The pattern used above is not an event system (think it's pulisher subscriber) as an event usually get triggered on or invoked from an object (button, input, dialog) but in case of a more event system like implementation it would be easy to get the correct this context since you trigger the event on or from an instance (like myButton or myDialog).
See following code for event system like implementation:
var eventSystem={
add:function(eventname,fnCallback){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push(fnCallback);
},
//change in trigger as it's passing the event object now
trigger:function(event){
if(!this.events[event.type]){
return;
}
var i=0;
for(i=0;i<this.events[event.type].length;i++){
this.events[event.type][i](event);
}
},
initES:function(){//set the instance variables needed
this.events=this.events||{};
}
};
function addProtos(o,protos){
for(item in protos){
o.prototype[item]=protos[item];
}
}
var person=function(name){
this.name=name;
this.initES();//needed to initialeze eventsystem
};
// make person capable of storing event handlers
// and triggering them
addProtos(person,eventSystem);
person.prototype.askQuestion=function(){
//asking a question will trigger an "answer" event
this.trigger({type:"answer",target:this});
}
// handler for when jon will fire an answer event
function answerHandler(event){
console.log("answer from:",event.target);
console.log("name of the person:",event.target.name);
}
var jon=new person("jon");
jon.add("answer",answerHandler);//add event listener
jon.askQuestion();//triggers the answer event from within jon
jon.trigger({type:"answer",target:jon});//trigger the event externally
Not sure why Angular choose to "break" prototype by using closures as the examples show there are other alternatives. Maybe someone can explain that who is more familiar with Angular.

Categories