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:
- https://bitbucket.org/atlassian/backbone-brace
- See Rich Manalang’s lightning talk from JSConf – http://atlassian.nodejitsu.com/brace/#/title