Connect with:
About Me
Alessandro Annini
I'm a freelance developer currently working at Leaff.it/ I really love famo.us framework and its amazing potential and if you're like me, and you want the web to win, please share some snippet of code for the community and grow faster with us
Categories
Meta
Famo.us Code | Event System
718
post-template-default,single,single-post,postid-718,single-format-standard,ajax_updown_fade,page_not_loaded,,qode-theme-ver-6.2.1

Event System

26 Aug Event System

This article is by Mark Marijnissen 

[from a post on Famo.us Google Group]

 


EventEmitter.js
EventEmitter is the source of all events.
  • emit(type,event) (a.k.a. trigger): Emits an event to everybody listening (= invoke callbacks)
  • addListener(type,callback) (a.k.a on): Listen to an event (= add callback)
Chaining
Imagine the following: (an App-Router) is listening to (a Menu-View) is listening to A (a Menu-Item-Surface).
  • So a click event is emitted by A, and listened to by B.
  • Then emits that same click event, and it is listened to by C.
You can see that to chain events, B must both listen to and emit events. This is the EventHandler.

EventHandler.js

“EventHandler forwards received events to a set of provided callback functions”
The easiest way to forward an event would be as follows:
A.addListener(“click”,function(data) { B.emit(“click”,data) })
 
However, now you only listen to click events! What if you wanted to catch all events? Enter subscribe: It listens to all events from A, which is called the source,input or upstream.
B.subscribe(A);
C.subscribe(B);
 
Now instead takings C‘s perspective: (“I’m going to listen to all of B’s events!”), you can also stay in B‘s perspective and say: “I’m going to tell C about all events I’m emitting”: This is pipe. From B‘s perspective, is downstream or an output
 
C.subscribe(B) = B.pipe(C);
 
Mixin!
While it’s great to have standalone classes for handling events, events only make sense in some context.
 
Famo.us has a class called View which encapsulates an object which both listens and emits to events. Like B, the Menu-View.

It emits ‘click’ events to the App-Router (C), and it listens to ‘click’ events from the Menu-Item-Surface (A)

  • setInputHandler(object, handler)Make this object a listener (= input
    • add subscribe method: Listen to upstream events, like the click from Menu-Item-Surface (A)
    • add trigger (emit) method: Listen to manually triggered events.

  • setOutputHandler(object, handler)Make this object an emitter (= output)
    • add pipe method: Emit to downstream handlers, like the App-Router (C)
    • add addListener (on) method: Emit event to (manually) added callback.
View.js
You can see there are TWO handlers used in View.
    this._eventInput = new EventHandler();
    this._eventOutput = new EventHandler();
    EventHandler.setInputHandler(this, this._eventInput);
    EventHandler.setOutputHandler(this, this._eventOutput);
Why is this?

Because you don’t want to automatically forward EVERY event.

  • Some events are just meant for B to react to. (i.e. input / listen)
  • Some events are created by B for others. (i.e. output / emit)
  • Some events are forwarded as-is to others.
  • Some events are first processed/altered/converted, then emitted to others.
Instead of A > B > C you want A > B._eventInput > (do stuff) > B._eventOutput > C.
 
Here are some examples I do with events:
Example 1: MenuView (B) creates & owns the MenuItemSurfaces (A), and emits everything (click, touchstart, etc).
// in MenuView.js
MyMenuItemSurface.pipe(this._eventOutput);
Example 2: MenuView (B) doesn’t know anything about the surfaces it’s going to get. Forwards the click!
// in MenuView.js
this._eventInput.on(‘click’,function(data) { this._eventOutput.emit(‘click’, data); })
 
// in main.js:
myMenuView.subscribe(myMenuItemSurface);

Example 3: MenuView emits converts a DOM event (‘click’) to a semantic event (‘menu-item-select’)
// in MenuView.js
myMenuItemSurface.on(‘click’,function(data) { this._eventOutput.emit(‘select-item’, data.target.id })
My Confusion
I get very much confused by the words input and output.
setInputHandler
  • the object handles input: It listens to others.
  • the object is a handler for input: It emits events.
setOutputHandler
  • the object handles output: It emits events to others.
  • the object is an handler for output: It listens to events.
What would make much more sense to me is:
  • makeListener: The object listens to (upstream or triggered) events
  • makeEmitter: The object emits to downstream handlers (or invokes callbacks from listeners).
My issues with events
Every since bidirectional data-binding is working, I avoid events. Why?

1) Loose coupling = opaque callback spaghetti
Events are great for encapsulation, but you risk an opaque event-and-callback spaghetti. Events are so great for loose coupling stuff, that indeed when debugging, it’s hard to know who is triggering who and how objects/modules are coupled.

Also, as soon as you change event data, you must change all dependent listeners: Your apps are still coupled, only not using methods & object references, but by events and event signature.
2) added a listener too late — retrigger the latest event?
Events are great for time-sensitive functionality, for example: “Show a notification”.
It’s a little more akward for synchronizing data between objects.
In an event-based paradigm, data is synchronized manually by applying a “change of data”. I often need to write code like this:
A.on(‘state’, onDataChange);
onDataChange(A.currentData); // simulate “data change” to initialize initial data.
To prevent this, you could do what Firebase does: trigger the last data immediately after adding a listener.
However, this is akward 50% of the time:
A.on(‘newdata’, function(){})
 
I have no idea if “newdata” is actually “new”, or just because I’m initializing. This leads to even more akward code:
var firstTime = true;
A.on(‘newdata’, function(data) {
if(firstTime) /* init stuff */ firstTime = false;
else /* do other stuff */
 
So the solution is to have actual control over whether events are re-triggered upon adding a listener or not.

Have a look at these pages:

 
3) idempotent event handlers
Events prompt me to think in terms of (time-sensitive) changes rather than absolute truths (states).
To synchronize data/state between components, I need to write idempotent event handlers.
So rather than:
  • on(‘increment’, function(n) { value = value + n })
I need:
  • on(‘value’, function(x) { value = x; })
By having no mechanism for sharig state (or bidirectional data-binding), I notice myself making the mistake of writing non-idempotent callback handlers.
This often causes bugs: What happens when listeners are added later? What happens if you miss an event? What happens if you accidentially trigger an event twice? It often causes bugs where components have stale or incorrect data.
4) once is missing
It’s just a really convenient, often-used function that’s missing in the famous core.