How a javascript pubsub/observer sees the function of another object? - javascript

I have this simple code, while trying to learn the basics of implementing the pubsub method in javascript.
people object emits a simple event and events object is the pubsub, takes the event and emits it to all that listen
Then stats that listens for that event, executes a function, with the data that came from the pubsub.
var events = {
events: {},
on: function(eventName, fn) {
this.events[eventName] = this.events[eventName] || [];
this.events[eventName].push(fn);
},
emit: function(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(function(fn) {
console.log('events this ', this);
fn(data)
});
}
}
}
var stats = (function() {
var counterstats = 0
events.on('peoplechange', setPeople);
function setPeople(a) {
console.log('stats this ', this);
counterstats++;
console.log('stats people ', a)
}
})();
var people = (function() {
console.log('people this ', this);
events.emit('peoplechange', 5);
})();
<h1>hello</h1>
I am asking, how events knows and communicates with setPeople that is inside stats. setPeople is a method of an object, not exposed.
How events sees it? I thought I had to do events.on('peoplechange', events.setPeople); so events knows where this method belongs to.
I print the this of the function in the emit, and the this of the setPeople in the stats and they all point to Window.
I still cannot explain how events sees setPeople inside the stats, without specifying further.
Any explanation would be great. Apologies for asking basic things

Related

Forwarding events from one event emitter to another

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.

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?

Modifying callback parameters in JavaScript

I'm creating a service in angular to track events, and I want the controllers to be as dumb as possible about the event tracking. For this service each controller will need to supply its own copy of the event function. My current solution is below.
//tracking module
let tracked_events = ['Event1','Event2']
.map(function(e){
return {
eventName:e,
eventProperties: {}
}
});
let finishingTracking = (event) => {
console.log("prentend this does something fancy...",event);
}
let track = (eventName,fn) => {
var event = tracked_events.filter(e => e.eventName===eventName)[0];
fn(event.eventProperties);
//some other internal tracking function
finishTracking(event);
}
//end tracking module
//called from a controller
track("Event 1",(event) => {
event["User ID"] = 123;
event["Company ID"] = 12
});
//some other controller
track("Event 2",(event) => {
event["Manager ID"] = 345;
event["Time Sent"] = Date.now()
}
This works well because each controller will only have to provide its own way to modify the event object and it won't know anything else about the tracking module. From a design perspective, is this ok? I'm not sure about modifying the callback parameters (V8 optimizations/side effects), but I can't think of another way that doesn't cause more changes to each controller that needs to do its own event tracking. Any suggestions on this?
EDIT (prior to refactor)
var eventProperties = { table: "Machines, has_rows: /*rows...*/ }
some_service.list(data)
.then(function (response) {
tracking_service.track({
eventName: 'Event 1',
eventProperties: eventProperties
});
AFTER
some_service.trackEvent(some_service.events.EVENT1, function (event) {
event["Table"] = "Machines";
event["Has Rows"] = response.data.machines.length > 0;
});
An object of shape {eventName: "Name", eventProperties:{} } is currently being defined in each controller. My solution was to pass in the eventName from a constant defined in the service and the callback function modifies the eventProperties of the object. Each controller will have a different set of properties in eventProperties.
After the callback in version two runs, the service does the actual "tracking". My aim was to have a function that prepared the eventProperties object and added whatever properties it needed before being actually tracked.
self.trackEvent = function (eventName, fn) {
//var event = TRACKED_EVENTS.filter(e => e.eventName === eventName)[0];
var event = {
eventName: self.events[eventName],
eventProperties: { }
}
fn(event.eventProperties); //this is the callback that does the prep
self.track(event); //does the actually tracking
}

How to create and trigger events on objects rather than elements

I'm writing a small library that essentially polls a site for data, and is then supposed to notify a consumer when it matches. In C# I'd use events, which are actually multicast delegates. I've written my own multicast delegate in javascript before, but I figure there has to be a better way.
A consumer should register a callback which should be called when data is available. Something like this:
window.MyLibrary.dataAvailable(function(data) {
// do something with data
});
In the background MyLibrary is polling for data. When it finally has something that matches, it should execute the registered function(s). Multiple functions should be able to be registered and probably unregistered too.
CustomEvent is very very close to what I want. The problem with CustomEvent is that the event has to be raised on an element - it can't be raised on an object. That is, this wouldn't work:
var event = new CustomEvent('dataAvailable', { data: 'dynamic data' });
window.MyLibrary.addEventListener('dataAvailable', function (e) {
// do something with e.data
}, false);
// From somewhere within MyLibrary
this.dispatchEvent(event, data);
How do you register handlers on objects in javascript? I need to support the usual browsers and IE11+. Ideally I wouldn't be pulling in a library to do any of this. jQuery will be available on the page and can be used if that would make things easier.
For reference, this is the Multicast Delegate implementation I've used in the past:
function MulticastDelegate(context) {
var obj = context || window,
handlers = [];
this.event = {
subscribe: function (handler) {
if (typeof (handler) === 'function') {
handlers.push(handler);
}
},
unsubscribe: function (handler) {
if (typeof (handler) === 'function') {
handlers.splice(handlers.indexOf(handler), 1);
}
}
};
this.execute = function () {
var args = Array.prototype.slice.call(arguments);
for (var i = 0; i < handlers.length; i++) {
handlers[i].apply(obj, args);
}
};
}
var myEvent = new MulticastDelegate();
myEvent.event.subscribe(function(data) { ... }); // handler 1
myEvent.event.subscribe(function(data) { ... }); // handler 2
myEvent.execute(some_data);

Object-Oriented Javascript custom addEvent method

I have a question regarding developing object-orientated javascript and parsing variables. Please see this blog by net.tutsplus - The Basics of Object-Oriented JavaScript for more info.
I have the following code which creates an event:
$(document).ready(function() {
tools.addEvent('.someButton', 'click', user.getUserDetails);
)};
var tools = {
addEvent: function(to, type, fn) {
$(to).live(type, fn);
}
}
var user = {
getUserDetails: function(userID) {
console.log(userID);
}
}
As you can see, it calls the addEvent method with three variables; the DOM element to attach the event to, the type of the event and the function to run when the event is triggered.
The issue I am having is parsing a variable to the getUserDetails method and I know of 2 options:
I could obviously have 1 line of code at the start which could check an attribute of the sender. For example, the .someButton could have an attribute userID="12345". However, this is not ideal because the function is run from several different places - meaning this check is not always available (and the code is harder to manage).
A better option could be to have another method like user.willGetUserDetails and use this method to get the attribute userID from the DOM. This could be run from anywhere on the page, and would call getUserDetails after getting the userID. Whenever the user details comes from within another function, we would simply call getUserDetails directly.
What would be ideal, is if I could amend the code above to pass a variable directly - even an undefined one. Does anyone know how this could be achieved?
Add one more argument to your addEvent code that accepts data to pass to the event.
var tools = {
addEvent: function(to, type, data, fn) {
if ($.isFunction(data)) {
fn = data;
data = {};
}
$(to).live(type, data, fn);
}
}
Also, i'd suggest using delegate instead, or .on in 1.7+
var tools = {
addEvent: function(to, type, data, fn) {
if ($.isFunction(data)) {
fn = data;
data = {};
}
$(document).delegate(to, type, data, fn);
}
}
or
var tools = {
addEvent: function(to, type, data, fn) {
if ($.isFunction(data)) {
fn = data;
data = {};
}
$(document).on(type, to, data, fn);
}
}
Now you can use it like this:
$(document).ready(function() {
tools.addEvent('.someButton', 'click', {userID: theuserid}, user.getUserDetails);
)};
var user = {
getUserDetails: function(event) {
console.log(event.data.userID);
}
}

Categories