Backbone.js Collections Explained
Collections in Backbone are set of Models created by extending Backbone.Collection
.
When creating a collection, we also want to define a property specifying the type of model that the collection will contain and the instance properties required.
var Todo = Backbone.Model.extend({
defaults: {
title: '',
completed: false
}
});
var TodosCollection = Backbone.Collection.extend({
model: Todo
});
var myTodo = new Todo({ title: 'Sample todo', id: 1});
var todos = new TodosCollection({ myTodo });
console.log('The collection has ' + todos.length + ' item.') // will log "The collection has 1 item"
Adding and removing Models
In the last example, the collection was populated using an array of models when it was instantiaded. After the collection has been created, models can be added and removed using add()
and remove()
methods.
var Todo = Backbone.Model.extend({
defaults: {
title: '',
completed: false
}
});
var TodoCollection = Backbone.Collection.extend({
model: Todo
});
var todo1 = new Todo({ title: 'Write a blog post' });
var todo2 = new Todo({ title: 'Exercise' });
var todo3 = new Todo({ title: 'Create a simple Todo App '});
var todos = new TodoCollection([todo1, todo2]);
console.log('Collection size: ' + todos.length); // Will log Collection size: 2
todos.remove([todo1, todo2]);
console.log('Collection size: ' + todos.length); // Will log Collection size: 0
todo.add(todo3);
console.log('Collection size: ' + todos.length); // Will log Collection size: 1
Note that add()
and remove()
accept individual models and array of models.
Also, when using add()
, passing {merge: true}
causes duplicate models to have their attributes merged in to the existing models, instead of being ignored. By default the merge option is set to false
.
var items = new Backbone.Collection();
items.add([ {id: 1, name: 'Dog', age: 3 }, { id: 2, name: 'Cat', age: 2 }]);
items.add([ {id: 1, name: 'Bear' } ], {merge: true});
items.add({id: 2, name: 'Lion'}); // { merge: false }
console.log(JSON.stringify(items.toJSON()));
// Will log { {"id": 1, "name": "Bear", "age 3" }, {"id": 2, "name": "Cat", "age": 2} }
Retrieving Models
There are a few ways of retrieving a Model from a Collection. The most straight-forward is to use Collection.get()
, which accepts a single id as follows:
var myTodo = new Todo({ title: 'Read a book', id: 2});
var todos = new TodoCollection([myTodo]);
var todo2 = todos.get(2);
console.log(todo2 === myTodo) // will return true
Each model in Backbone has an id
, which is unique identifier that is either an integer or string. Models also have a cid
(client id) which is automatically generated by Backbone when the model is created. Either identifier can be used to retrieve a model from collection.
The main difference between them is that cid
is generated by Backbone.
The idAttribute
is the identifying attribute name of the model returned from the server (i.e the id
in your database). This tells Backbone which data field from a server should be used to populate the id
property.
The value of a model’s idAttribute
should be set by the server when the model is saved. After this point you shouldn’t need to set it manually, unless further control is required.
Backbone.Collection
contains an array of models enumerated by their id
property, if the model instances happen to have one. When collection.get(id)
is called, the array is checked for existence of the model instance with the corresponding id
.
var todoCid = todos.get(todo2.cid);
console.log(myTodo === todoCid) // will return true
Listening for Events
Collections represent a group of items, so we can listen for add
or remove
events. Here is an example:
var TodoCollection = new Backbone.Collection();
TodoCollection.on('add', function(todo) {
console.log('I should ' + todo.get('title') + '. Have you done it before? ' + (todo.get('completed') ? 'Yes!' : 'No');
});
TodosCollection.add([
{ title: 'go to Australia', completedA: false },
{ title: 'go to London', completed: false },
{ title: 'go to Paris', completed: true }
]);
// Will log:
//I should go to Australia. Have I done it before: No
// I should go to London. Have I done it before: No
// I should go to Paris. Have I done it before: Yes
We can also bind a change
event to listen for changes to any of the models in the collection.
TodoCollection.on('change:title', function(todo){
console.log('Changed my mind. I should ' + todo.get('title'));
});
jQuery-style event maps of the form object.on({event: action})
can also be used.
var myTodo = new Todo(); // instantiate a new Backbone Model
myTodo.on({
'change:title': titleChanged,
'change: completed': stateChanged
});
function titleChanged() {
console.log('Title Changed');
}
function stateChanged() {
console.log('State Changed');
}
myTodo.set({ title: 'Get to the market' }) // Will log "Title Changed"
Resetting/Refreshing Collections
Rather than adding or removing models individually, we can update an entire collection at once. Collection.set()
takes an array of models and performs the necessary add, remove or change operations.
var TodoCollection = new Backbone.Collection();
TodoCollections.add([
{ id: 1, title: 'go to Jamaica.', completed: false },
{ id: 2, title: 'go to China.', completed: false },
{ id: 3, title: 'go to Disneyland.', completed: true }
]);
TodoCollection.on('add', function(model) {
console.log('New Model Added');
});
TodoCollection.on('remove', function(model) {
console.log('Model Removed');
});
Todos.on('change:completed', function(model) {
console.log('Completed ' + model.get('title'));
})
If we want to simply replace the entire content of the collection we can use Collection.reset()
.
Using reset
with no arguments will clear out the collection completely. This is useful then dynamically loading a new page of results where you want to blank out the current page of results.
myCollection.reset()
Underscore utility functions
Backbone takes full advantage of its hard dependency on Underscore by making many of its utilities directly available on collections:
forEach
: itarate over collections
var todos = new Backbone.Collection();
todos.add([
{title: 'clean the room', completed: false},
{title: 'make breakfast', completed: false},
{title: 'read a book', completed: true}
]);
todo.forEach(function(model) {
console.log(model.get('title'))
});
// Will log:
// "clean the room"
// "make breakfast"
// "read a book"
sortBy()
: sort a collection on a specific attributemap()
: iterate through a collection, mapping each value through a transformation functionmin()/ max()
: retrieve item with the max or min value of an attributepluck()
: extract a specific attribute
var captions = todos.pluck('caption');
console.log(captions) // will return a list of captions
filter()
: filter a collectionindexOf()
: return the index of a particular item within a collectionany()
: confirm if any of the values in a collection pass an iterator truth testsize()
: return the size of a collectionisEmpty()
: determine whether a collection is emptygroupBy()
: group a collection into groups of like itemspick()
: extract a set of attributes from a modelomit()
: extract all attributes from a model except those listedkeys()
,values()
: get list of attribute names and valuespairs()
: get list of attributes as [key, value] pairsinvert()
: create object in which the values are keys and the attributes are values
Chainable API
Another great feature in Backbone is its support for Underscore’s chain()
method. Chaining is common idiom on object-oriented languages.
A chain is a sequence of method calls on the same objects that are performed is a single statement.
The chain()
method returns an object that has all of the Underscore array operations attached as methods which return that object. The chain ends with a call to the value()
method which simply returns the resulting array value.
var collection = new Backbone.Collection({
{ name: 'Ivan', age: 24 },
{ name: 'George', age: 18 },
{ name: 'Chris', age: 28 }
});
var filteredNames = collection.chain() // start chain
.filter(function(model) { return model.get('age') > 20); })
.map(function(model) { return item.get('name') })
.value();
console.log(filteredNames) // Will log ['Ivan', 'Chris']