I am currently implementing a Bluetooth Library for Node.js that has support for macOS, Linux and Windows. To achieve cross platform compatibility I am using native, platform specific code (Objective-C, C++ & C) that use Node.js' EventEmitter and will trigger events whenever an asynchronous Bluetooth operation has completed or some other device has triggered an event for some reason.
The data format for these events look very differently and I would like to normalize them so users of my library will get there events in one single format, not matter which platform they are on.
Of course, I can attach event handlers to the native EventEmitters, transform and normalize the data and trigger Events with that normalized data in my own library.
The problem with that is that the users of the library will be able to attach - and - remove events to my library and what that means is that if they attached an event to my library, let's say to "deviceDiscovered" like so:
const myListener = (device) => { /* ... */ };
bluetoothLibrary.on("deviceDiscovered", myListener);
My implementation would have to look a little something like this:
class BluetoothLibrary {
nativeAdapter = getAdapterForCurrentOS();
on(event, callback) {
this.nativeAdapter.on(event, (data) => {
const normalizedData = this.normalize(data)
callback(normalizedData);
});
}
off(event, callback) {
this.nativeAdapter.off(event, callback)
}
normalize(data) {
/* ... */
}
}
The problem with this is if someone wants to remove their event listeners again, like so:
const myListener = (device) => { /* ... */ };
bluetoothLibrary.on("deviceDiscovered", myListener);
bluetoothLibrary.off("deviceDiscovered", myListener);
Because of my implementation, the listener never really would be removed because I never really attach the reference to the callback that has been passed to me to the native adapter's EventEmitter.
I am wondering if there is a way to transform or pipe events through some transformations on the way before passing them on to an event listener.
Related
I have some client side logic (I have a little 3d world where objects interacts), and I would like to add some events listener like this:
window.addEventListener("myAmazingEvent", () => {doSomethingAmazing})
How can I implement this eventListener to my own class? Like:
class Person {
construcor() {
this.listener = new SomeJavascriptEventListener()
}
crouched() {
this.listener.call("onCrunch")
}
const a = new Person()
a.addEventListener("onCrunch", () => {a.startSinging()})
What javascript built in classes can provide this behaviour to me?
JavaScript's standard library doesn't have any direct event emitter support. There are lots of event emitter libraries you can find with a search, but none of them is built-in.
You can:
Use one of the pre-built, pre-tested event emitter libraries that already exist (look for "event emitter" or "publish/subscribe" or "pub/sub").
Use something built into the environment you're running your code in. Node.js has an EventEmitter class. On browsers, you could create a DOM element that you keep in your class instance (no need for it to be in the document) and use it to store event listeners, then use dispatchEvent to send events to it. (Though that's not really simpler than the next option.)
Build your own by maintaining an array or Set of event listeners for each event type that you loop through and call when you need to emit an event.
All three are viable options.
But to give you an idea, a class with very basic event handling looks like this:
class Example {
#eventHandlers = {
"some-event": new Set(),
"another-event": new Set(),
};
on(eventName, handler) {
const handlers = this.#eventHandlers[eventName];
if (!handlers) {
throw new Error(`Invalid event name "${eventName}"`);
}
handlers.add(handler);
}
off(eventName, handler) {
const handlers = this.#eventHandlers[eventName];
if (!handlers) {
throw new Error(`Invalid event name "${eventName}"`);
}
handlers.delete(handler);
}
#trigger(eventName, ...eventArgs) {
const handlers = this.#eventHandlers[eventName];
// assert(!!handlers);
for (const handler of handlers) {
try {
handler(...eventArgs);
} catch { // (Add (e) if your environment doesn't support optional catch bindings)
}
}
}
doSomething() {
// In this example, "doing something" triggers the `some-event` event
this.#trigger("some-event", "something happened");
}
}
const e = new Example();
e.on("some-event", (msg) => console.log(`Handler one: received "${msg}"`));
e.on("some-event", (msg) => console.log(`Handler two: received "${msg}"`));
e.doSomething();
That uses relatively modern things (private fields, optional catch bindings), but you can adapt it as needed.
There are a dozen or more ways to skin this cat. Cross-talk between nandlers, cancelling events, passing in some kind of Event object, etc., etc., etc. But the fundamentals are:
Registering handlers (on in the example)
Unregistering handlers (off in the example)
Triggering events (#trigger in the example)
In the past I've also used a resize listener that bundled requestAnimationFrame with it to be a somewhat optimized version of polling resize events:
/**
* Resize listener
* #return {function}
*/
export const optimizedResize = (function () {
let callbacks = [],
running = false;
// fired on resize event
function resize() {
if (!running) {
running = true;
if (window.requestAnimationFrame) {
window.requestAnimationFrame(runCallbacks);
} else {
setTimeout(runCallbacks, 66);
}
}
}
// run the actual callbacks
function runCallbacks() {
callbacks.forEach((callback) => {
callback();
});
running = false;
}
// adds callback to loop
function addCallback(callback) {
if (callback) {
callbacks.push(callback);
}
}
return {
// public method to add additional callback
add(callback) {
if (!callbacks.length) {
window.addEventListener('resize', resize);
}
addCallback(callback);
},
};
}());
I recently came across addListener() which embarrassingly I must say I'm not familiar with. Although it says it's just an alias for addEventListener() the syntax seems pretty straight forward to listen to changes:
const viewportMediumMin = window.matchMedia(`(min-width: 768px)`);
viewportMediumMin.addListener(checkScreenSize);
But, what I'm trying to figure out is addListener() is the equivalent of:
window.addEventListener('resize', function() {
console.log('addEventListener - resize');
}, true);
or if it's doing something "smarter" behind the scenes that I should rely on it exclusively, compared to the optimizedResize method I mentioned. I'm really only interested in the specific event of the media query changing, not finding out every single pixel width change. Thanks for any help!
This is basically reinventing wheels. CSS3 was made to style the pages content gracefully on different screen sizes, and media queries were added to change the look of a page at a certain breakpoint. Those breakpoints are common in todays web development, and modern CSS engines were heavily optimized to perform changes as fast as possible. Therefore
window.matchMedia(`(min-width: 768px)`).addListener(/*...*/)
is probably very performant as the event is detected by the CSS engine that then gets redirected to the JS engine. Adding a listener to resize is probably slower as every pixel change causes a JS event, and your unoptimized JavaScript has to figure out wether a breakpoint was passed. Your optimizeResize doesn't really change that.
I am curious about current best practices for creating streams from sources that may not conform to an existing stream creation method (https://github.com/cujojs/most/blob/master/docs/api.md)
Example using Firebase's ref.on('child_added', function(snap){}):
most.fromEvent('child_added', ref) //ERROR
I can't use .fromEvent... although ref implements some sort of on, it does not seem to conform to the EventEmitter interface (addEventListener, removeEventListener)
ref.on('child_added', function(snap){ emitter.emit('value', snap) })
most.fromEvent('value', emitter)
Manually emitting events, is the best I can think of at the moment...
// https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/create.md
Rx.Observable.create(function(observer){
ref.on('child_added', function(snap){ observer.next(snap) })
})
Is there a similar mechanism to custom create a stream, a la Rx?
Are there better ways I am missing?
Check on how to use #most/create
https://github.com/mostjs/create
It allows to manually emit events - similar to how you would do it with rxJS
Another option might be to shim the interface that your firebase library exposes to fit most's fromEvent constructor.
Looking at the source code for fromEvent, we can see that it supports two interfaces for event sources:
{ addEventListener, removeEventListener } — the EventTarget interface, commonly implemented by DOM elements
{ addListener, removeListener } — the EventEmitter interface, found often in node libraries
With that knowledge, we can implement a shim function to create a stream from the { on, off } interface:
function fromEvent (eventName, source) {
if (typeof source.on === 'function' && typeof source.off === 'function') {
return most.fromEvent(eventName, {
addListener: source.on,
removeListener: source.off
});
} else {
return most.fromEvent.apply(null, arguments)
}
}
This can be nicer than using create, as the EventEmitterSource performs scheduling of the event on the next tick of the event loop, and handles disposing of the event listener when the stream ends.
Setting:
let's say three events happen in three separate part of some application, the event should be handled by two controllers. The application dispatcher is responsible for sending/receiving events from all parts of the application, and this dispatcher should have an asynchronous event queue.
In some cases, two the of three events are related along some attribute but are not of the same name, and only one should be passed to the controller, the other one may be discarded.
Problem:
Currently, I have a 'queue' that really just bounces the event to the controller, this is unhelpful because I have no way of comparing two events in the queue if only one is ever there at time.
So how do I ensure the events should stay in the queue for a while? I suppose a timeout function could do the trick but is there a better way?
To give credit where it's due, the idea of coalescing events is inspired by Cocoa and I'm basically trying to do something similar.
I don't know much about Cocoa, but I assume that it replaces older events with the latest event until the event is able to be dispatched to the application (i.e. if the application is busy for some reason). I'm not sure what your particular use case is, but if you want to rate-limit the events, I would use setTimeout like this:
function Dispatcher(controllers) {
this.controllers = controllers;
this.events = [];
this.nextController = 0;
}
Dispatcher.prototype = {
_dispatch: function (i) {
var ev = this.events.splice(i, 1);
this.controllers[this.nextController].handle(ev);
this.nextController = (this.nextController + 1) % this.controllers.length;
},
notify: function (ev) {
var index = -1, self = this, replace;
function similer(e, i) {
if (e.type === ev.type) {
index = i;
return true;
}
}
replace = this.events.some(similar);
if (replace) {
this.events[i] = ev;
} else {
// it's unique
index = this.events.push(ev) - 1;
setTimeout(function () {
self._dispatch(index);
}, 100);
}
}
};
Just call notify with the event (make sure there's a type property or similar) and it will handle the magic. Different types of events will be handled uniquely with their own setTimeout.
I haven't tested this code, so there are probably bugs.
I suppose a timeout function could do the trick but is there a better way?
No, there really isn't.
Usually the way to go is using setTimeout(..., 0) if your events are dispatched in the same run loop. So the implementation would look something like this:
var eventQueue = [];
var handlerTimer = -1;
function fireEvent(event, params) {
eventQueue.push([event, params]);
window.clearTimeout(handlerTimer);
handlerTimer = window.setTimeout(resolveQueue, 0);
}
function resolveQueue() {
// process eventQueue and remove unwanted events...
// then dispatch remaining events
eventQueue = [];
}
If you need to handle events from different run loops (for example native events like mouseup and click), you need to use some timeout value other than 0. The exact value depends on how long you want to accumulate events.
Are there any Event Driven Architecture jQuery plugins?
Step 1: Subscribing
The subscribers subscribe to the event handler in the middle, and pass in a callback method, as well as the name of the event they are listening for...
i.e. The two green subscribers will be listening for p0 events. And the blue subscriber will be listening for p1 events.
Step 2: The p0 event is fired by another component to the Event Handler
A p0 event is fired to the Event Handler
The event handler notifies it's subscribers of the event, calling the callback methods they specified when they subscribed in Step 1: Subscribing.
Note that the blue subscriber is not notified because it was not listening for p0 events.
Step 3: The p1 event is fired a component to the Event Handler
The p1 event is fired by another component
Just as before except that now the blue subscriber receives the event through its callback and the other two green subscribers do not receive the event.
Images by leeand00, on Flickr
I can't seem to find one, but my guess is that they just call it something else in Javascript/jquery
Also is there a name for this pattern? Because it isn't just a basic publisher/subscriber, it has to be called something else I would think.
You probably don't need a plugin to do this. First of all, the DOM itself is entirely event driven. You can use event delegation to listen to all events on the root node (a technique that jQuery live uses). To handle custom events as well that may not be DOM related, you can use a plain old JavaScript object to do the job. I wrote a blog post about creating a central event dispatcher in MooTools with just one line of code.
var EventBus = new Class({Implements: Events});
It's just as easy to do in jQuery too. Use a regular JavaScript object that acts as a central broker for all events. Any client object can publish and subscribe to events on this object. See this related question.
var EventManager = {
subscribe: function(event, fn) {
$(this).bind(event, fn);
},
unsubscribe: function(event, fn) {
$(this).unbind(event, fn);
},
publish: function(event) {
$(this).trigger(event);
}
};
// Your code can publish and subscribe to events as:
EventManager.subscribe("tabClicked", function() {
// do something
});
EventManager.publish("tabClicked");
EventManager.unsubscribe("tabClicked");
Or if you don't care about exposing jQuery, then simply use an empty object and call bind and trigger directly on the jQuery wrapped object.
var EventManager = {};
$(EventManager).bind("tabClicked", function() {
// do something
});
$(EventManager).trigger("tabClicked");
$(EventManager).unbind("tabClicked");
The wrappers are simply there to hide the underlying jQuery library so you can replace the implementation later on, if need be.
This is basically the Publish/Subscribe or the Observer pattern, and some good examples would be Cocoa's NSNotificationCenter class, EventBus pattern popularized by Ray Ryan in the GWT community, and several others.
Though not a jQuery plugin, Twitter released a JavaScript framework called Flight which allows you to create component-based architectures, which communicate via events.
Flight is a lightweight, component-based JavaScript framework from Twitter. Unlike other JavaScript frameworks which are based around the MVC pattern, Flight maps behavior directly to DOM nodes.
Flight is agnostic to how requests are routed or which templating library you decide to use. Flight enforces strict separation of concerns. Components in Flight do not engage each other directly.
They broadcast their actions as events and those components subscribed to those events can take actions based on them. To make use of Flight, you will need the ES5-shim and jQuery along with an AMD loader.
Flight - A Lightweight, Component-Based JavaScript Framework From Twitter
There are actually two of them:
Listen (faster): http://plugins.jquery.com/project/Listen
Intercept (more advanced): http://plugins.jquery.com/project/Intercept
Could this serve as a ligthweight message passing framework?
function MyConstructor() {
this.MessageQueues = {};
this.PostMessage = function (Subject) {
var Queue = this.MessageQueues[Subject];
if (Queue) return function() {
var i = Queue.length - 1;
do Queue[i]();
while (i--);
}
}
this.Listen = function (Subject, Listener) {
var Queue = this.MessageQueues[Subject] || [];
(this.MessageQueues[Subject] = Queue).push(Listener);
}
}
then you could do:
var myInstance = new MyConstructor();
myInstance.Listen("some message", callback());
myInstance.Listen("some other message", anotherCallback());
myInstance.Listen("some message", yesAnotherCallback());
and later:
myInstance.PostMessage("some message");
would dispatch the queues
This can easily be accomplished using a dummy jQuery node as a dispatcher:
var someModule = (function ($) {
var dispatcher = $("<div>");
function init () {
_doSomething();
}
/**
#private
*/
function _doSomething () {
dispatcher.triggerHandler("SOME_CUSTOM_EVENT", [{someEventProperty: 1337}]);
}
return {
dispatcher: dispatcher,
init: init
}
}(jQuery));
var someOtherModule = (function ($) {
function init () {
someModule.dispatcher.bind("SOME_CUSTOM_EVENT", _handleSomeEvent)
}
/**
#private
*/
function _handleSomeEvent (e, extra) {
console.log(extra.someEventProperty) //1337
}
return {
init: init
}
}(jQuery));
$(function () {
someOtherModule.init();
someModule.init();
})
A recent development is msgs.js "Message oriented programming for JavaScript. Inspired by Spring Integration". It also supports communication via WebSockets.
msgs.js applies the vocabulary and patterns defined in the 'Enterprise Integration Patterns' book to JavaScript extending messaging oriented programming into the browser and/or server side JavaScript. Messaging patterns originally developed to integrate loosely coupled disparate systems, apply just as well to loosely coupled modules within a single application process.
[...]
Tested environments:
Node.js (0.6, 0.8)
Chrome (stable)
Firefox (stable, ESR, should work in earlier versions)
IE (6-10)
Safari (5, 6, iOS 4-6, should work in earlier versions)
Opera (11, 12, should work in earlier versions)
I have used the OpenAjax Hub for its publish/subscribe services. It's not a jQuery plugin, but a standalone JavaScript module. You can download and use the reference implementation from SourceForge. I like the hierarchical topic naming and the support for subscribing to multiple topics using wildcard notation.