Functional Mixins and Backbone.js

Engineering

Learn from our challenges and triumphs as our talented engineering team offers insights for discussion and sharing.

Functional Mixins and Backbone.js

Engineering

We’ve been using Backbone.js to organize our JavaScript code for a little over a year now. Backbone packages our client-side code into an MVC-like structure and exposes a well documented set of APIs to interact with the DOM. We picked it from amongst many such frameworks because of its low-level, and therefore flexible, nature. Unfortunately, this means that we often need to modify native Backbone objects to implement somewhat basic functionality. Instead of extending the base Backbone classes to include these extra features, we use functional mixins to package them into small, selectively reusable pieces.

What are Functional Mixins?

A functional mixin is just a plain javascript function that modifies the properties of its context (the value referenced by the this variable). By using the native call function to provide a context of our choosing, we can invoke this function on the object we want to modify, thereby mixing in the desired functionality.  Let’s work through an example to see what that means.

Say we have a Cat class:

var Cat = function() {
  this.meow = function() {
    console.log(‘Meow’);
  };
}; 

var myCat = new Cat();

myCat.meow();
=> Meow

Unfortunately, my cat has a penchant for dying and is making his way through his nine lives at an alarming pace. To better prepare myself for his inevitable demise, I’d like to keep track of how long I have left with him. Here is a functional mixin that does that. 

var withLives = function() {
  this.numLives = 9;
  this.numDaysPerLife = 30;

  this.die = function() {
    if (this.numLives > 0) {
      this.numLives = this.numLives - 1;
    }
  };

  this.daysLeft = function() {
    return this.numLives * this.numDaysPerLife;
  };
}

Now to give my cat nine lives, all I need to do is call our mixin with my cat as the context.

withLives.call(myCat);

myCat.daysLeft();
=> 270

myCat.die();

myCat.daysLeft();
=> 240

The mixin function is written assuming that the object being modified is referenced by the this variable (also known as the context of the function). Notice that we use call to invoke our mixin function, instead of invoking it directly (i.e., withLives(myCat)). This allows us to control the context of the function. When a function is called directly, the value of this is set to the owner of the function which, for an arbitrary anonymous function, might be the browser’s window object (more on how to determine that). By invoking it via call, and passing my cat in as the first argument, we make my cat the context of the function, and thereby the subject of the mixin.

var withLives = function() {
 this.numLives = 9;
 this.numDaysPerLife = 30;

 this.die = function() {
   if (this.numLives > 0) {
     this.numLives = this.numLives - 1;
   }
 };

 this.daysLeft = function() {
   return this.numLives * this.numDaysPerLife;
 };
}

Customizing Mixins

Notice that our magical life-giving mixin doesn’t depend on the object being a cat. In fact, we can use it to give my dog nine lives, too!

withLives.call(myDog);

myDog.daysLeft(); => 270
myDog.die();
myDog.daysLeft(); => 240

Unfortunately, my dog has just one life (and is incredibly protective of it) so this mixin doesn’t quite apply to her. As it turns out, functional mixins are quite flexible. Let’s modify our withLives mixin to accept the number of lives and the average length of a life as parameters.

var withLives = function(numLives, numDaysPerLife) {
  this.numLives = numLives;
  this.numDaysPerLife = numDaysPerLife;

  this.die = function() {
    if (this.numLives > 0) {
      this.numLives = this.numLives - 1;
    }
  };

  this.daysLeft = function() {
    return this.numLives * this.numDaysPerLife;
  };
};

To apply the mixin, we just need to call it with values for these new arguments. This is made trivial by the call function that treats all arguments after the first one (which, as we know, is used as the context) as arguments to the function being invoked. For our example:

withLives.call(myCat, 9, 30);
withLives.call(myDog, 1, 3000);

And there you have it – an audacious cat and a dog that seems to live forever (until he suddenly dies).

Mixing into the Prototype

Of course, its painful to call this on every cat or dog in our codebase. However, if we’re prepared for all our cats to be uniformly reckless, we can just apply the mixin to the class’s prototype object, and have it magically work for every instance.

withLives.call(Cat.prototype, 9, 30);

var anotherRecklessCat = new Cat();

anotherRecklessCat.daysLeft(); => 270
anotherRecklessCat.die();
anotherRecklessCat.die();
anotherRecklessCat.daysLeft(); => 210

Using Functional Mixins with Backbone

Fortunately, our codebase doesn’t have to deal with suicidal cats. Instead, we toss around relatively apathetic Backbone objects and use functional mixins to give them special powers. For example, withRevert is a mixin for Backbone models that gives them the ability to revert to a previous state.

The withRevert mixin allows us to save the state of a model and revert to that saved state at some later point. We most commonly use it to revert all changes made since the last time a model was synced with the server. This backs the ‘cancel’ action on pages where a user can edit the attributes of a model. Here’s a basic implementation of this mixin.

var withRevert = function() {
 this.savedAttributes = {};

 this.saveState = function() {
   // keep a copy of the current attributes
   this.savedAttributes = $.extend({}, this.attributes);
 };

 this.revert = function() {
   this.set(this.savedAttributes);
 };
};

And here is an example of it in action.

var Comment = Backbone.Model.extend({
 defaults: {
   author: ‘Anonymous’,
   message: ‘No comment.’
 };
});

withRevert.call(Comment.prototype);

var comment = new Comment();
comment.get(‘author’); => ‘Anonymous’

comment.saveState();

comment.set(‘author’, ‘Armaan’);
comment.get(‘author’);
=> 'Armaan'

comment.revert();

comment.get(‘author’);
=> ‘Anonymous’

And that’s all there is to it! We have a fully functioning mixin that can be easily (and selectively) applied to a model of our choosing.

We use this functional mixin pattern pretty extensively – modifying models, views, and collections. The pattern itself doesn’t add any additional dependencies and is fairly easy to follow. It helps DRY up our code and increase clarity, providing a powerful mechanism for code reuse and decomposition.