Skip to content

Instantly share code, notes, and snippets.

@bergie
Created May 30, 2012 12:47
Show Gist options
  • Star 43 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save bergie/2836052 to your computer and use it in GitHub Desktop.
Save bergie/2836052 to your computer and use it in GitHub Desktop.
Backbone.js Collection View example

This is an example of using a Collection view with Backbone.

<!DOCTYPE html>
<html>
<head>
<title>Backbone.js Collection View example</title>
<!-- Dependencies -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<!-- The actual code -->
<script src="models.js"></script>
<script src="views.js"></script>
<script src="router.js"></script>
</head>
<body>
<h1>Backbone.js Collection View example</h1>
<!-- The empty table we'll use as the example -->
<table>
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
</tr>
</thead>
<!-- We'll attach the PeopleView to this element -->
<tbody>
</tbody>
</table>
</body>
</html>
// Models are where actual data is kept. They can also be used
// for communicating between the server and the client through
// methods like save() and fetch().
//
// Models are the abstract data and do not know how they are
// supposed to be visualized. But they can perform validations
// to ensure the data is correct.
var models = {};
// Our base model is "person"
models.Person = Backbone.Model.extend({
// Example of how to do a validation in a model
validate: function(attributes) {
if (typeof attributes.firstname !== 'string') {
// Return a failed validation
return 'Firstname is mandatory';
}
if (typeof attributes.lastname !== 'string') {
// Return a failed validation
return 'Lastname is mandatory';
}
// All validations passed, don't return anything
}
});
// People collection
models.People = Backbone.Collection.extend({
model: models.Person
});
// Router is responsible for driving the application. Usually
// this means populating the necessary data into models and
// collections, and then passing those to be displayed by
// appropriate views.
var Router = Backbone.Router.extend({
routes: {
'': 'index' // At first we display the index route
},
index: function() {
// Initialize a list of people
// In this case we provide an array, but normally you'd
// instantiate an empty list and call people.fetch()
// to get the data from your backend
var people = new models.People([
{
firstname: 'Arthur',
lastname: 'Dent'
},
{
firstname: 'Ford',
lastname: 'Prefect'
}
]);
// Pass the collection of people to the view
var view = new views.People({
collection: people
});
// And render it
view.render();
// Example of adding a new person afterwards
// This will fire the 'add' event in the collection
// which causes the view to re-render
people.add([
{
firstname: 'Zaphod',
lastname: 'Beeblebrox'
}
]);
}
});
jQuery(document).ready(function() {
// When the document is ready we instantiate the router
var router = new Router();
// And tell Backbone to start routing
Backbone.history.start();
});
// Views are responsible for rendering stuff on the screen (well,
// into the DOM).
//
// Typically views are instantiated for a model or a collection,
// and they watch for change events in those in order to automatically
// update the data shown on the screen.
var views = {};
views.PeopleItem = Backbone.View.extend({
// Each person will be shown as a table row
tagName: 'tr',
initialize: function(options) {
// Ensure our methods keep the `this` reference to the view itself
_.bindAll(this, 'render');
// If the model changes we need to re-render
this.model.bind('change', this.render);
},
render: function() {
// Clear existing row data if needed
jQuery(this.el).empty();
// Write the table columns
jQuery(this.el).append(jQuery('<td>' + this.model.get('firstname') + '</td>'));
jQuery(this.el).append(jQuery('<td>' + this.model.get('lastname') + '</td>'));
return this;
}
});
views.People = Backbone.View.extend({
// The collection will be kept here
collection: null,
// The people list view is attached to the table body
el: 'tbody',
initialize: function(options) {
this.collection = options.collection;
// Ensure our methods keep the `this` reference to the view itself
_.bindAll(this, 'render');
// Bind collection changes to re-rendering
this.collection.bind('reset', this.render);
this.collection.bind('add', this.render);
this.collection.bind('remove', this.render);
},
render: function() {
var element = jQuery(this.el);
// Clear potential old entries first
element.empty();
// Go through the collection items
this.collection.forEach(function(item) {
// Instantiate a PeopleItem view for each
var itemView = new views.PeopleItem({
model: item
});
// Render the PeopleView, and append its element
// to the table
element.append(itemView.render().el);
});
return this;
}
});
@dexity
Copy link

dexity commented Feb 25, 2014

Clean and concise solution! Thanks, man. Table rendering drives me nuts.

@htatche
Copy link

htatche commented Mar 21, 2014

Thanks for those snippets, the switch from Ember to Backbone is not so straight-forward sometimes !

@yattias
Copy link

yattias commented Mar 19, 2015

@bergie - very nice & clean dude. One note though, rendering the whole collection when a person is added/removed may be overkill.

@vitalyisaev2
Copy link

@yattias could you please tell whether there are opportunities to avoid the problem you've mentioned (with Backbone of course)?

@okovpashko
Copy link

@vitalyisaev2 you can create your own listener for collection's 'add' event, which will insert new PeopleItem view in correct place:

onCollectionItemAdd: function ( model, collection ) {
    var  list = this.$( 'list selector' ),
        listItems = list.children(),
        newItem = self._renderListItem( model ),
        newItemPosition = collection.models.indexOf( model );

    if ( newItemPosition > listItems.length - 1 ) {
        newItem.$el.appendTo( list );
    } else {
        newItem.$el.insertBefore( listItems.eq( newItemPosition ) );
    }
}

Then you can add listener for model's 'remove' event in PeopleItem view:

onModelRemove: function() {
    this.remove();
}

@TimothyWrightSoftware
Copy link

Just what I needed. Thanks!

@mveer-agarwal
Copy link

Doesn't this solution have DOM Reflow? Here you are attaching child view html with parent view html, which causes DOM Reflow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment