Reputation: 34103
I'm new to backbone and I have a collection of objects and a view that displays them as a list. It is set up so that every time an object is added to the collection the view is re-rendered. This is really inefficient because if I add 20 things to the collection all at once, it will be rendered 20 times when it only needs to be rendered one time after the last item has been added. How do I make backbone hold off on rendering until I'm done adding things to the collection?
Here is my code:
<html>
<head>
<meta charset="utf-8" />
<title>Looking At Underscore.js Templates</title>
</head>
<body>
<script type="text/template" class="template">
<ul id="petlist">
<% _.each(model.pets, function(pet) { %>
<li><%- pet.name %> (<%- pet.type %>)</li>
<% }); %>
</ul>
</script>
<div id="container"/>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script type="text/javascript">
var serviceData = [
{
name: "Peaches",
type: "dog"
},
{
name: "Wellington",
type: "cat"
},
{
name: "Beefy",
type: "dog"
}
];
$(document).ready(function() {
var Pet = Backbone.Model.extend({
name: null,
type: null
});
var Pets = Backbone.Collection.extend();
var AppView = Backbone.View.extend({
updateUi: function(model) {
_.templateSettings.variable = "model";
var template = _.template($("script.template").html());
var model = { pets: model.collection.toJSON() };
var html = template(model);
$("#container").html(html);
}
});
var pets = new Pets();
var av = new AppView();
pets.bind("add", av.updateUi);
pets.set(serviceData);
});
</script>
</body>
</html>
Upvotes: 1
Views: 1591
Reputation: 6552
You need to re-factor a couple of things on your code. For your specific case you need to use reset instead of set, to dispatch only one event when data is set. Also you could pass the collection to the view and the view could listen the reset event.
Additionally, to prevent the stress of the browser you could use document.createDocumentFragment that is a DOM holder that speed up the treatment of append. Check point number two for more reference: http://ozkatz.github.io/avoiding-common-backbonejs-pitfalls.html
<html>
<head>
<meta charset="utf-8" />
<title>Looking At Underscore.js Templates</title>
</head>
<body>
<script type="text/template" class="template">
<ul id="petlist">
<% _.each(model.pets, function(pet) { %>
<li><%- pet.name %> (<%- pet.type %>)</li>
<% }); %>
</ul>
</script>
<div id="container"/>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script type="text/javascript">
var serviceData = [
{
name: "Peaches",
type: "dog"
},
{
name: "Wellington",
type: "cat"
},
{
name: "Beefy",
type: "dog"
}
];
$(document).ready(function() {
var Pet = Backbone.Model.extend({
name: null,
type: null
});
var Pets = Backbone.Collection.extend();
var AppView = Backbone.View.extend({
initialize : function(){
this.collection.bind("reset", av.updateUi);
},
updateUi: function() {
$items = document.createDocumentFragment();
this.collection.each(function(model){
var itemView = new ItemView({model : model});
$items.append(itemView.el);
itemView.render();
});
$("#container").html($items);
}
});
var pets = new Pets();
var av = new AppView({collection : pets});
pets.reset(serviceData);
});
</script>
</body>
</html>
Upvotes: 1
Reputation:
You can also do:
pets.set(serviceData, { silent: true });
pets.trigger('add', pets);
And modify av.updateUi to work with whole collection and create the HTML at once, but 'reset' event would be probably more appropriate than 'add' in this case.
Upvotes: 1
Reputation: 1141
You could also create an extra method called, lets say, add. So, if you add a new object, the application just adds a single object instead of rendering the hole collection again.
Somethin like this:
App.Model = Backbone.Model.extend();
App.Collection = Backbone.Collection.extend({
model: App.Model
});
App.CollectionView = Backbone.View.extend({
tagName: 'ul',
render: function(){
this.collection.each(function(model){
var objView = new App.ModelView({ model: model });
this.$el.append( objView.render().el );
});
return this;
},
showAddForm: function(){
// Here you show the form to add another object to the collection
},
save: function(){
// Take form's data into an object/array, then add it to the collection
var formData = {
type: $('select[name=type]').val(),
name $('input[name=name]').val()
};
// Once added to the collection, take the object/array and...
this.addElement(formData);
},
addElement: function(model){
var modView = new App.ModelView({ model: model });
this.$el.append( modView.render().el );
}
});
App.ModelView = Backbone.View.extend({
tagName: 'li',
template: _.template( "<li><%= name %> (<%= type %>)</li>" ),
render: function(){
this.$el.html( this.template( this.model.toJSON() ) );
return this;
}
});
Do you get the idea? When you render the hole collection, the collectionView render method calls a modelView for each object/pet. So, this way, when you get the info of a new pet/object you can just create an instance of ModelView, and append it to the actual rendered view.
Hope it helps.
Upvotes: 4