In Jira we’ve been getting pretty excited about Backbone.js. There are a lot of things we love, too numerous to mention here. But there are a couple of things I want that Backbone doesn’t give us. First, a backbone model might look like this:

[cc lang=’javascript’ line_numbers=’false’]
var model = new Backbone.Model();
[/cc]

That’s it. What attributes does it expose? Does it have any custom events? I probably don’t know because there are 8 people on my team, so it’s about a 12.5% probability that I actually wrote it. In order to find out what this thing actually does, I have to go digging through the class – or worse, whatever else is calling this class. I could add comments, but comments quickly get out of date and lie to you. I also don’t like silent failures. I’m a pretty average typist / developer / human being and I am frequently doing things like:

[cc lang=’javascript’ line_numbers=’false’]
model.set(“selected”);
[/cc]

then later on:

[cc lang=’javascript’ line_numbers=’false’]
model.get(“selection”);
[/cc]

In backbone, this is a silent failure. I lose too much time digging trying to find out why model.get(“selection”) is undefined when it’s a just a typo. I would like a loud failure. And when I want to refactor, this pain gets even worse.

Enter Backbone Brace

We wrote Brace (https://bitbucket.org/atlassian/backbone-brace, Apache 2.0 license) to address this. It has these principles:

  • A model should always define what attributes it exposes.
  • A model / collection / view should always define what custom events it exposes.
  • Setting or getting an attribute that doesn’t exist should fail fast and loudly.

(Note: we don’t verify event names in trigger /  on / off as we’d have to write exceptions for backbone events such as change, add, remove … we wouldn’t forward-compatible if any more are added).

[cc lang=’javascript’ line_numbers=’false’]
var MyModel = new Brace.Model({
namedAttributes: [“selected”, “key”, “name”],
namedEvents: [“select”]
});

var myModel = new MyModel();

myModel.get(“selected”);
myModel.set(“selected”, “z”);
myModel.get(“selection”); // throws “Attribute ‘selection’ not found”
myModel.set(“selection”, “z”); // throws “Attribute ‘selection’ not found”

[/cc]

We’ve also added some syntactic sugar for get / set / trigger / on / off:

[cc lang=’javascript’ line_numbers=’false’]

myModel.getSelected();
myModel.setSelected(“z”);
myModel.triggerSelect(“the”, “args”);
myModel.onSelect(fn);

[/cc]

Brace gives us confidence to refactor mercilessly and fearlessly. If I remove or rename an attribute, I know that anyone using this will fail quickly. If I type like an idiot, which is frequent, I will be reminded all the time.

Mixins

The third thing we’ve added is mixins.

[cc lang=’javascript’ line_numbers=’false’]

var SelectedMixin = {
namedAttributes: [“selected”],
namedEvents: [“select”],

defaults: {
selected: false
},

initialize: function() {
// do something
},

validate: function() {
// do something
},

select: function() {
this.setSelected(true);
this.triggerSelect();
}
};

var Issue = new Brace.Model({
mixins: [SelectedMixin]
});

[/cc]

Our mixins are designed for backbone objects. They copy:

  • All the features of Brace namedAttributes and namedEvents
  • Backbone properties like defaults
  • Backbone methods like initialize() and validate() are mixed into sequential calls with the base model and other mixin’s initialize / validate.

A perennial problem with mixins (or any multiple inheritance structure) is name conflicts – when the mixin brings in properties that exist on the base class or other mixins. Brace resolves these in the following way:

  • Known methods (eg initialize, validate, defaults), are composed into sequential calls
  • Everything else fails loudly and violently at class declaration time (as opposed to object instantiation time)

An example:

[cc lang=’javascript’ line_numbers=’false’]
var Logger = {
log: function(x) { console.log(x); }
};

var MyModel = Brace.Model.extend({
mixins: [Logger],
log: function() {}
}); // fails during the execution of Brace.Model.extend()
[/cc]

That’s it

For more info:

Add some strength to your Backbone