I have released a number of Backbone plugins over the last few months. They are available over at Github, but I think I should at least briefly introduce them here.
Let's have a closer look.
Backbone.Cycle is a set of mixins for Backbone models and collections. Models gain the ability to be selected, and collections handle those selections.
Collections either allow only a single model to be selected (Backbone.Select.One mixin), or cater for multiple selections (Backbone.Select.Many). Events communicate selections and deselections to other parts of your application. In model-sharing mode, models can be part of more than one collection. Backbone.Select will keep selections of shared models in sync and guarantee consistency in even the most complex scenarios.
Backbone.Select is compatible with Backbone.Picky.
Backbone.Cycle is a superset of Backbone.Select. Backbone.Cycle adds options and navigation methods, such as model.ahead(3)
, collection.selectNext()
, collection.prev()
, collection.prevNoLoop()
.
With Backbone.Cycle options, you enable predefined, common behaviours: always selecting the first item in a new collection, or selecting the next model when a selected model is removed. Again, models can be shared across multiple collections, and selections are synced among them.
Perhaps the best way to explain what Backbone.Select and Backbone.Cycle do, is a basic example.
First, Backbone.Select.
var Model = Backbone.Model.extend({
initialize: function () {
// Applies the mixin:
Backbone.Select.Me.applyTo( this );
}
});
// A collection type allowing only one selection at a time
var Collection = Backbone.Collection.extend({
initialize: function ( models, options ) {
// Applies the mixin:
Backbone.Select.One.applyTo( this, models, options );
}
});
var m1 = new Model( {id: "m1"} ),
m2 = new Model( {id: "m2"} ),
m3 = new Model( {id: "m3"} );
var collection = new Collection( [m1, m2, m3] );
collection.select( m1 );
console.log( collection.selected.id ); // prints "m1"
console.log( m1.selected ); // prints true
collection.on( "select:one", function ( model ) {
console.log( model.id + " has been selected." );
});
collection.on( "deselect:one", function ( model ) {
console.log( model.id + " has been deselected." );
});
m2.select();
// prints "m1 has been deselected."
// prints "m2 has been selected."
Backbone.Select is designed with a minimal surface area. As few methods as possible are added to your objects. Basically, all you can do is select
and deselect
. The idea is that you should be able to use the mixin pretty much everywhere, with a near-zero risk of conflicts.
By contrast, if you use Backbone.Cycle instead of Backbone.Select, you get a little more in terms of methods and behaviour.
var Model = Backbone.Model.extend( {
initialize: function () {
// Applies the mixin:
Backbone.Cycle.SelectableModel.applyTo( this );
}
} );
var Collection = Backbone.Collection.extend( {
initialize: function ( models, options ) {
// Applies the mixin:
Backbone.Cycle.SelectableCollection.applyTo( this, models, options );
}
} );
var m1 = new Model( {id: "m1"} ),
m2 = new Model( {id: "m2"} ),
m3 = new Model( {id: "m3"} );
var collection = new Collection(
[m1, m2, m3],
{ initialSelection: "first", selectIfRemoved: "next" }
);
console.log( collection.selected.id ); // prints "m1" because of initialSelection: "first"
console.log( m2.next().id ); // prints "m3"
console.log( m1.ahead( 2 ).id ); // prints "m3"
collection.selectNext(); // selects m2
collection.remove( m2 );
console.log( collection.selected.id ); // prints "m3" because of selectIfRemoved: "next"
Backbone.Marionette.Export is a plugin for Backbone, and specifically targeted at Marionette. It makes the methods of models and collections available to templates.
The name of the plugin has turned out to be a bit of a misnomer, though. Backbone.Marionette.Export does not depend on Marionette and works great without it. In fact, Backbone.Marionette.Export is just as useful in projects based on plain Backbone.
Out of the box, templates handled by Marionette views have access to all properties of a model, and to the array of models represented by a collection. But templates can't use the output of methods.
To work around that, you could override the toJSON
method in a model or collection, but that creates its own set of problems. Specifically, anything you change in toJSON
will also get written back to the server on save.
Backbone.Marionette.Export does not cause any such side effects. After dropping it in, this is what you can do:
onExport
handler.onBeforeExport
handler.onAfterExport
handler.Here is how it works, in its simplest form:
<script id="item-view-template" type="text/x-handlebars-template">
<p>Model method returns: {{foo}} </p>
</script>
...
var Model = Backbone.Model.extend ({
exportable: "foo", // <-- this is the one line you have to add
foo: function () {
return "some calculated result of calling foo";
}
});
var view = new Marionette.ItemView ({
model: new Model(),
template: "#item-view-template"
});
view.render();
In the model definition, you declare which methods are available to a template. Just provide the method name to exportable
, or an array of them.
That works fine for simple method signatures. But what about methods which take arguments?
<script id="item-view-template" type="text/x-handlebars-template">
<p>Model method returns: {{foo}} </p>
</script>
...
var Model = Backbone.Model.extend ({
foo: function ( arg ) {
return "some calculated result of calling foo with " + arg;
},
onExport: function ( modelHash ) {
modelHash.foo = this.foo( someArg );
return modelHash;
}
});
var view = new Marionette.ItemView ({
model: new Model(),
template: "#item-view-template"
});
view.render();
In this scenario, there is no need to declare the method as exportable
. In fact, you can't: the method takes arguments, so it can't be called automatically. Instead, modify the exported model data in an onExport
handler. You can do pretty much anything there. Just remember to return the data at the end.
So far, it has all been about models. But when you deal with a collection, the process is no different. For more about that, as well as recursion, complex data wrangling, and more, head over to the documentation.
There is plenty of documentation for the plugins at Github. Or just install them with Bower and play with them.
bower install backbone.select
bower install backbone.cycle
bower install backbone.marionette.export
Enjoy!