Tutorial hero image
Lesson icon

GSWF #4: Dealing with Events in Famo.us

3 min read

Originally published December 07, 2014

This is the fourth post in my Getting Started with Famo.us (GSWF) series. This series will cover my attempt at learning the Famo.us framework and hopefully provide useful information to anybody else trying to learn. You can follow along from the start by clicking the link below:

GSWF #1: Introduction

Last time we focused on the Positioning Elements and Creating Animations tutorials. These tutorials dealt with how to position surfaces on the screen and how to animate their movement or transformations. In this post we will be looking at completing the last introductory tutorial in the Famo.us University: Handling Events.

Handling Events

The first thing we learn is that there are three types of events in Famo.us:

  1. Surface events
  2. Engine events
  3. Program events

A Surface Event fires standard DOM (Document Object Model) events that you would be familiar with if you have played around with Javascript before, these are:

  • click
  • mousedown
  • mousemove
  • mouseup
  • mouseover
  • mouseout
  • touchstart
  • touchmove
  • touchend
  • touchcancel
  • keydown
  • keyup
  • keypress

These events can be registered by using the .on() method, i.e:

surface.on('click', function () {
  surface.setProperties({
    backgroundColor: '#878785',
  });
});

Listening for Engine Events is a 'catch-all'. When an event is fired it can first be intercepted at the surface level, then by using .on() method on the context containing that surface and then at the engine level. For example:

Engine.on('keydown', function (e) {
  surface.setContent(e.which);
});

So if a keydown event is fired anywhere within the application and it is not intercepted at the surface or context level, then the engine event will capture it. Typically, events should be handled at a lower level (surface or context) and not at the engine level. A useful example of an engine event is provided in the tutorial though, by listening to the Engines 'resize' event, i.e:

Engine.on('resize', function () {
  surface.setContent('resized');
});

We can perform some action whenever the screen is resized.

A Program Event is a way to move information between program modules. We can emit, transmit and listen to events using Event Handler objects. For example we can create an event handler and tell it to emit (or broadcast, fire, whatever terminology you prefer) a 'hello' event when a surface is clicked on like this:

var eventHandler = new EventHandler();

surface.on('click', function () {
  eventHandler.emit('hello');
});

Now somewhere else in our code we can listen for that event and trigger some action when it is detected like this:

eventHandler.on('hello', function () {
  surface.setContent('heard hello');
});

A more realistic, real world, example is provided in the next section where there is two separate event handlers. Separate modules would have separate event handlers, so in a real world application we might trigger and listen for events like this:

var eventHandlerA = new EventHandler();
var eventHandlerB = new EventHandler();

surfaceA.on('click', function () {
  eventHandlerA.emit('hello');
  surfaceA.setContent('said hello');
});

eventHandlerB.subscribe(eventHandlerA);

eventHandlerB.on('hello', function () {
  surfaceB.setContent('heard hello');
});

Notice the difference in logic for this code compared to the last example? In this case you have to call the subscribe() method. This basically tells eventHandlerB that it should be interested in events coming from eventHandlerA. If the subscribe method was not called then even though A is emitting an event, B won't hear it.

The pipe() method is an alternative to subscribe(). It achieves basically the same thing, just backwards. Rather than saying "eventHandlerB you should listen to events coming from eventHandlerA" we are saying "eventHandlerA you should send your events to eventHandlerB":

surfaceA.on('click', function () {
  eventHandlerA.emit('hello');
  surfaceA.setContent('said hello');
});

eventHandlerA.pipe(eventHandlerB);

eventHandlerB.on('hello', function () {
  surfaceB.setContent('heard hello');
});

Using Events with Views

Up until this point there hasn't really been any discussion on how code is organised, what structure it uses (MVC? MVVM?) etc. Now we are introduced to Views in Famo.us. Views allow code to be organiser and modularized and we learn about how to use events in the context of a view.

Each view has an input event handler called view._eventInput. Any events that are sent to a view (either by using pipe() or subscribe()) can be listened to on this input event handler. Take a look at the provided code for an example:

var myView = new View();
mainContext.add(myView);

myView.add(surface);

surface.pipe(myView);
// alternatively, myView.subscribe(surface);

// normally inside view module's code
myView._eventInput.on('click', function () {
  surface.setContent('hello');
});

You can see here that we are creating a view and then adding a surface to it. Then we are saying that any events from that surface should be sent to the view, that is, the views input event handler. Then we are listening specifically for the 'click' event in the input event handler. As is pointed out by the comment, usually the listener on the input event handler would be set inside the view's module code the tutorial states that this pattern will be covered in the Timbre Menu project (which hopefully we will get to soon!)

We just covered how to listen to an event within a view, now we are going to look at how to emit an event from a view and listen for the event outside of that view. We can broadcast an event from a view by using the views output handler. Adding onto the previous code example:

// normally inside view module's code
myView._eventInput.on('click', function () {
  myView._eventOutput.emit('hello');
});

// listening to view's events from the outside
myView.on('hello', function () {
  surface.setContent('hello');
});

When we detect that 'click' event inside the view, we are now broadcasting an event back out of the view as a response. Then we can listen for that event outside of the view by using the on() method on a reference to that view.

That's it for now! In the next post we'll take a look at the Famo.us Starter Kit.

If you enjoyed this article, feel free to share it with others!