I am refactoring my Backbone.js application to use Marionette.js, and I am trying to wrap my head around a CollectionView
.
Suppose I have several ItemView
s with the model Cow
:
// Declare my models.
var Cow = Backbone.Model.extend({});
var Cows = Backbone.Collection.extend({
model: Cow
});
// Make my views
var GrassPatch = Marionette.ItemView.extend({
tagName: 'div',
template: "<section class='grass'>{{name}}</section>",
})
var Pasture = Marionette.CollectionView.extend({});
// Instantiate the CollectionView,
var blissLand = new Pasture({
itemView: GrassPatch;
});
// Now, add models to the collection.
Cows.add({
name: 'Bessie',
hasSpots: true
});
Cows.add({
name: 'Frank',
hasSpots: false
});
Now here's the trick. I only want cows with spots in my pasture. How, in defining my CollectionView (Pasture), do I tell it to only pay attention to those models whose hasSpots
=== true
?
Ideally I would like to have the CollectionView filter that in all events, but minimally, how do I only render some ItemViews based on their model properties?
UPDATE
I used David Sulc's examples and this worked out to an easy solution. Here's an example implementation:
this.collection = Backbone.filterCollection(this.collection, function(criterion){
var len = String(criterion).length;
var a = criterion.toLowerCase();
return function(model){
var b = String(model.get('name')).substr(0, len).toLowerCase();
if (a === b) {
return model;
}
};
});
this.collection.add({ name: 'foo' });
this.collection.add({ name: 'foosball' });
this.collection.add({ name: 'foo bar' });
this.collection.add({ name: 'goats' });
this.collection.add({ name: 'cows' });
this.collection.filter('foo');
// -> returns the first three models
As suggested by others, the best way to achieve this is to filter the collection to contain only the models you want to display, and pass that fitlered collection to a CollectionView for rendering.
You can see a working example here: http://davidsulc.github.io/marionette-gentle-introduction/#contacts Filter contacts with the input field on the top right to display only models containing that text (e.g. "li").
This is achieved by using a special collection type that handles filtering: https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/entities/common.js
And it gets instantiated here: https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/apps/contacts/list/list_controller.js#L13
This code is from my book on Marionette.
Marionette handles this for you in v2.4.1.
See the CollectionView.filter method.
Following is from the docs:
var cv = new Marionette.CollectionView({
childView: SomeChildView,
emptyView: SomeEmptyView,
collection: new Backbone.Collection([
{ value: 1 },
{ value: 2 },
{ value: 3 },
{ value: 4 }
]),
// Only show views with even values
filter: function (child, index, collection) {
return child.get('value') % 2 === 0;
}
});
// renders the views with values '2' and '4'
cv.render();
// change the filter
cv.filter = function (child, index, collection) {
return child.get('value') % 2 !== 0;
};
// renders the views with values '1' and '3'
cv.render();
// remove the filter
// note that using `delete cv.filter` will cause the prototype's filter to be used
// which may be undesirable
cv.filter = null;
// renders all views
cv.render();
@Will M's suggestion to filter the collection is the appropriate way to do this.
Sometimes you can not filter your collection due to some custom logic and you want those models to be in a collection, but you don't want them to be rendered. To achieve it, you can:
var Pasture = Marionette.CollectionView.extend({
addChild: function(child, ChildView, index) {
if(child.get('hasSpots')) {
return Marionette.CollectionView.prototype.addChild.call(this, child, ChildView, index);
}
}});
Although I agree that filtering a collection is a much better way to do this.