BAREBONE Events

Posts in this series:

Next let’s implement the eventing system…

We will create an Events module with two methods (on and trigger) that we will add to the prototypes of all our classes using the Underscore.js extend method. Remember that Underscore.js is a dependency of Backbone.js (in fact, Underscore.js was born out of the creation of Backbone.js).

/* BAREBONE.js code */
BAREBONE.Events = {
on: function(name, callback, context) { ... };
trigger: function(name) { ... };
};
_.extend(BAREBONE.Model.prototype, BAREBONE.Events);
_.extend(BAREBONE.Collection.prototype, BAREBONE.Events);
_.extend(BAREBONE.View.prototype, BAREBONE.Events);

Event systems allow you to do two things:

  1. bind custom named events to a callback function
  2. trigger an event, invoking all the associated callbacks

Despite seeming complicated, the eventing system is composed of two fairly simple methods. First we’ll take a look at the on method.

.on Method

The most common use case for this is when you want a certain callback to get invoked when there is a ‘change’ event on a model.

For example, when there is a change on the ‘kalev’ model, we can re-render the view to reflect the change:

kalev.on('change', renderView, kalev);
function renderView () {...}

Here’s the BAREBONE.js source code for the .on method…

/* BAREBONE.js code */
BAREBONE.Model.prototype.on = function(name, callback, context) {
this._events || (this._events = {});
actions = this._events[name] || (this._events[name] = []);
actions.push({callback: callback, ctx: context || this});
return this;
};

Here we are storing the event name (‘change’) and callback (renderView) in an object stored at the _events property on the object. Like so…

kalev._events = {
change: [
{callback: renderView, ctx: kalev},
{callback: otherCB, ctx: kalev}],
{callback: anotherCB, ctx: kalev}
]
};

Let’s take a look at it line by line:

We have three parameters in our .on method:

  1. name — the event name (ex: ‘change’)
  2. callback — the callback to invoke when the event gets triggered (ex: ‘renderView’)
  3. context — the object to be bound to the ‘this’ keyword within the callback
  • If there isn’t an _events object, create one:
this._events || (this._events = {});
// kalev._events = {};
  • If there isn’t an array to store the callback objects for the named event, create one:
actions = this._events[name] || (this._events[name] = []);
// kalev._events = { change: [] }
  • Add an object with the information for the callback to the event array:
actions.push({callback: callback, ctx: context || this});
// kalev._events = { change: [{callback: renderView, ctx: kalev}] }

.trigger Method

This method is used to trigger an event and kick off the invocation of the callbacks associated with that event.

For example, when an attribute is changed on the ‘kalev’ model…

kalev.set('attending', true);

… then the trigger method on the ‘kalev’ object gets fired in the Model.prototype.set method:

/* BAREBONE.js code */
Model.prototype.set = function(key,val) {
// set property on attributes object for model
this.trigger('change', this);
};

The trigger method then invokes all of the associated callbacks for the ‘change’ event, including ‘renderView’. Here’s the source code for the trigger method:

/* BAREBONE.js code */
trigger: function(name) {
if (!this._events) return this;
var actions = this._events[name] || [];
var args = Array.prototype.slice.call(arguments, 1);
_.each(actions, function(action) {
action.callback.apply(action.ctx, args);
});
return this;
}

Line by line:

  • If there is no _events property, simply exit and return the this object
if (!this._events) return this;
  • Access the ‘actions’ array with the callbacks for the event passed in as ‘name’
var actions = this._events[name] || [];
  • Create an array with all of the arguments passed after the event name. These will be passed on as arguments to all of the callbacks.
var args = Array.prototype.slice.call(arguments, 1);
  • Iterate through the ‘actions’ array and invoke all of the callbacks using .apply with the correct context and pass along all of the arguments in the args array
_.each(actions, function(action) {
action.callback.apply(action.ctx, args);
});

And that’s pretty much it! See, not so complicated! These methods can be used on all Backbone collections and views as well.

Take a look at the BAREBONE.js code again to see how the Events Module is used to add the .on and .trigger properties to the prototype of all model, collection, and view objects:

/* BAREBONE.js code */
BAREBONE.Events = {
on: function(name, callback, context) { ... };
trigger: function(name) { ... };
};
_.extend(BAREBONE.Model.prototype, BAREBONE.Events);
_.extend(BAREBONE.Collection.prototype, BAREBONE.Events);
_.extend(BAREBONE.View.prototype, BAREBONE.Events);

RSVP Sample Application

Now let’s take a look at how we’ll be using the eventing system in our RSVP application!

$('form').on('submit', function(event) {
event.preventDefault();
// 'add' event gets triggered here:
guests.add(new Guest({name: $('#name').val()}));
$('#name').val('');
});
// In ListView
// register listener for 'add' event on guests collection:
this.collection.on('add', this.render, this);
// register listener for 'change' event on guest model (bubbles up to guests collection):
this.collection.on(‘change’, function() {
// update attendee count and re-render view
}, this);
// In ListEntryView
events: {
'click': function(event) {
//on DOM click, model triggers 'change' event
this.model.set('attending', !this.model.get('attending'));
}
}

Awesome! We’ve now covered the implementation details for the Events module and the Model, Collection, and View classes.

Next: Extend method (subclassing)

Now we can cover the wonderful function that makes it all possible… You may recall using the extend method on our Model and View classes.

var Guest = BAREBONE.Model.extend({
defaults: { attending: false }
});
var ListView = BAREBONE.View.extend({
tagName: 'ul',
initialize: function() {...},
render: function() {...}
});

The implementation details for this method are particularly interesting… let’s dive in!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.