Giraffe.Contrib.CollectionView
This example details how to use
Giraffe.Contrib
to implement views for a collection of fruits with the Contrib.CollectionView
and ModelView
design pattern.
Live Example
Here is the live example. Each fruit is rendered using an
item view named FruitView
which inherits directly from Giraffe.View
.
The collection of fruits are children of a
single collection view named FruitsView
.
clone
button creates a duplicate of the fruitdelete
button deletes the fruit from the collectionsort
button toggles ascending/descending sortreset
button resets the collection to its original state
var Fruit = Giraffe.Model.extend({
defaults: {
name: null,
color: null
}
});
var Fruits = Giraffe.Collection.extend({
model: Fruit,
comparator: 'name'
});
Fruits.prototype.toggleSort = function() {
var comparator = this.comparator;
// Reverse string order isn't as simple as prefixing with '-'. See
// http://stackoverflow.com/a/5639070. Collection.reverse() is not a
// good idea as the collection would not sort properly on add/remove.
if (typeof comparator === 'string') {
comparator = function(fruit) {
return String.fromCharCode.apply(String, _.map(fruit.get("name").split(""),
function(c) {
return 0xffff - c.charCodeAt();
}
));
}
} else {
comparator = 'name';
}
this.comparator = comparator;
this.sort();
};
var FruitView = Giraffe.View.extend({
template: '#fruit-template',
initialize: function() {
this.$el.css('background-color', this.model.get('color'));
},
serialize: function() {
return this.model.toJSON()
},
onDelete: function() {
// Giraffe method which also removes it from the collection
this.model.dispose();
},
onClone: function() {
this.model.collection.add(this.model.clone());
}
});
var FruitsView = Giraffe.Contrib.CollectionView.extend({
modelView: FruitView,
modelViewEl: '#fruits-list',
modelViewArgs: [{
optional: 'arguments passed to modelView constructor'
}],
template: '#fruits-template'
});
var savoryFruits = [{
name: 'Orange',
color: '#FF7F00'
}, {
name: 'Pink Grapefruit',
color: '#C5363A'
}, {
name: 'Apple',
color: '#0F0'
}, {
name: 'Banana',
color: '#FF0'
}, ];
var fruits = new Fruits(savoryFruits);
var fruitsView = new FruitsView({
collection: fruits
});
var MainView = Giraffe.View.extend({
template: '#main-template',
onClickReset: function() {
fruitsView.collection.reset(savoryFruits);
},
onClickSort: function() {
fruitsView.collection.toggleSort();
},
afterRender: function() {
this.attach(fruitsView);
}
});
var mainView = new MainView();
mainView.attachTo('body');
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' type='text/css' href='../css/reset.css' />
<link rel='stylesheet' type='text/css' href='collectionview0-style.css' />
</head>
<body>
<script id='fruit-template' type='text/template'>
<div class='fruit-view'>
<h2><%= name %></h2>
<button data-gf-click='onDelete'>delete</button>
<button data-gf-click='onClone'>clone</button>
</div>
</script>
<script id='fruits-template' type='text/template'>
<h1>Fruits</h1>
<ul id='fruits-list'>
<!-- FruitView instances are inserted here -->
</ul>
</script>
<script id='main-template' type='text/template'>
<button data-gf-click='onClickReset'>reset</button>
<button data-gf-click='onClickSort'>sort</button>
<hr />
<!-- FruitsView is appended here in afterRender -->
</script>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script src="../backbone.giraffe.js" type="text/javascript"></script>
<script src="../backbone.giraffe.contrib.js" type="text/javascript"></script>
<script type='text/javascript' src='collectionview0-script.js'></script>
</body>
</html>
h1 {
font-size: 36px;
}
h2 {
font-size: 24px;
}
.fruit-view {
position: relative;
padding: 10px;
margin: 10px;
}
hr {
border: 0;
height: 0;
border-top: 1px dashed #ccc;
}
Collection and Model
First define the model and collection representing the fruits. The advantage
of using Giraffe.Model is the addition of a few methods such as
Model#dispose
which is used later. This is not that different from using
Backbone.Model.
var Fruit = Giraffe.Model.extend({
defaults: {
name: null,
color: null
}
});
var Fruits = Giraffe.Collection.extend({
model: Fruit,
comparator: 'name'
});
Item View
Instead of generating markup for each fruit in a single view, it is more maintainable to create an item view representing each item in the collection. The item view encapsulates operations around the specific item.
var FruitView = Giraffe.View.extend({
template: '#fruit-template',
initialize: function() {
this.$el.css('background-color', this.model.get('color'));
},
serialize: function() {
return this.model.toJSON()
},
We could cheat and call this.dispose()
here. By modifying the collection
instead, any view observing the collection is notified. Contrib.CollectionView
observes collection changes and modifies its item views accordingly.
onDelete: function() {
// Giraffe method which also removes it from the collection
this.model.dispose();
},
onClone: function() {
this.model.collection.add(this.model.clone());
}
});
Let's add a delete and clone button to allow users to visually modify the collection.
<script id='fruit-template' type='text/template'>
<div class='fruit-view'>
<h2><%= name %></h2>
<button data-gf-click='onDelete'>delete</button>
<button data-gf-click='onClone'>clone</button>
</div>
</script>
Collection View
Giraffe.Contrib
contains a CollectionView
class that syncs a view per
model in its collection
by reacting to add/remove/sort/reset events.
It is good practice to always modify the collection
instead of trying to add views manually.
var FruitsView = Giraffe.Contrib.CollectionView.extend({
modelView: FruitView,
modelViewEl: '#fruits-list',
modelViewArgs: [{optional: 'arguments passed to modelView constructor'}],
template: '#fruits-template'
});
The CollectionView
property modelViewEl
can be used to specify
where to insert model views.
<script id='fruits-template' type='text/template'>
<h1>Fruits</h1>
<ul id='fruits-list'>
<!-- FruitView instances are inserted here -->
</ul>
</script>
Now create some tasty fruits and create the collection to assign to FruitsView
.
var savoryFruits = [
{name: 'Orange', color: '#FF7F00'},
{name: 'Pink Grapefruit', color: '#C5363A'},
{name: 'Apple', color: '#0F0'},
{name: 'Banana', color: '#FF0'},
];
var fruits = new Fruits(savoryFruits);
var fruitsView = new FruitsView({
collection: fruits
});
Resetting and Sorting
Let's also give the user the ability to reset
and sort
fruits at
any time. The buttons need to be outside of the collection view otherwise
they would be disposed when the view resets its children.
Create a main view to contain the button just to keep things tidy and easy one-way click binding.
<script id='main-template' type='text/template'>
<button data-gf-click='onClickReset'>reset</button>
<button data-gf-click='onClickSort'>sort</button>
<hr />
<!-- FruitsView is appended here in afterRender -->
</script>
var MainView = Giraffe.View.extend({
template: '#main-template',
onClickReset: function() {
fruitsView.collection.reset(savoryFruits);
},
onClickSort: function() {
fruitsView.collection.toggleSort();
},
afterRender: function() {
this.attach(fruitsView);
}
});
var mainView = new MainView();
mainView.attachTo('body');
We need to source the Backbone.Giraffe.Contrib
library which defines
Giraffe.Contrib.CollectionView
.
Backbone.Giraffe.Contrib
,
short for contributions.
<script src="../backbone.giraffe.contrib.js" type="text/javascript"></script>