Efficient ways to remove a nested view in Backbone - javascript

I'm creating a single page app. I want to remove a view when switching to another view. In the current view, I have a collection of photos. How can I remove the view and the views of photo collection efficiently without memory leaks?
I did the following:
destroy method:
destroy: function(){
this.undelegateEvents();
this.stopListening();
this.$el.empty();
collection.reset();
}
event:
this.listenTo(collection, 'reset', this.resetBoard);
event handling:
resetBoard: function(collection, options){
var models = options.previousModels;
_.each(models, function(model){
model.id = null;
model.destroy();
});
}
here I empty the $el first ($el wiil be used by other views), so that DOM operation can be done at one time. Next, I reset the collection and destroy those models and related views.
Is this logic correct? Is there any better solutions?

This is a good article on preventing memory leaks in Backbone Views:
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
I don't think you need to explicitly remove the models and collections from memory. You just need to make sure that the view is no longer listening for changes in them. This a great overview of garbage collection in javascript and backbone specifically:
What does backbone.js do with models that are not used anymore
In any case, the use of model.destroy() seems a little weird/hacky to me because I think of it as a server operation (deleting the model from the server) and I also wonder if resetBoard will ever execute since you have already called stopListening() when the collection is reset.

Related

What is the best way to destroy related models when a model is destroyed in backbone relational?

I have complex nested structure of backbone relational models.
Every time I destroy a model, it is expected that the all models from relation are destroyed.
How do I do it?
Apparantely Backbone-Relational does not take care of it.
I would prefer to overload the Backbone model destroy method of your custom models. So you can destroy your nested models. And after that you can destroy the model with Backbone.Model.prototype.destroy.call(this);
Example:
var MyModel = Backbone.Model.extend({
destroy: function(){
var xhr = this.myNestedModel.destroy();
xhr.always(_.bind(function(){
Backbone.Model.prototype.destroy.call(this);
},this));
//or if you don't want to wait for the response without always
//Backbone.Model.prototype.destroy.call(this);
}
});
A different approach to overloading destroy would be to use events to propagate changes. The exact setup and complexity would depend on the relationships you have defined and also on how you create and destroy your models.

Backbone reusable view (set new model to existing view)

What is the best approach for the following setting: A long list if items (several hundred persons) and after clicking on a list entry a new dialog opens with item details.
There are several options (see also here):
use a "dummy" model and one view and change the attributes of the dummy models -> does not reflect changes to the original model
change the model of one already existing view
everytime a click on the list is made a new view is created which the model -> performance issues?
use the marionette framework -> there is little documentation which makes it hard to understand for me
use JSPerf View -> I tried the online demo but on fast scrolling there were several errors
I tried option 2 but there seem to be memory leakes.
ReusableView = Backbone.View.extend({
setModel: function( model) {
// unbind all events
this.model.stopListening();
this.undelegateEvents();
// set new model
this.model = model;
this.delegateEvents({
"click": "onClicked",
"mouseover": "onMouseOver"
});
this.initialize();
}
});
Here is a fiddle were lots of models can be created showed to the user with a single view. Type in the number of models to be created and click on create models.
Questions: Why are there memory leakes? How can I properly clean up after using a model?
For memory allocation I used chrome and its task manager. Memory consumption around 30M for 70000 views.
This is the solution I figured out after reading and testing a lot:
setModel: function( model) {
// unbind all view related things
this.$el.children().removeData().unbind();
this.$el.children().remove();
this.stopListening();
// clear model
if ( this.model) {
this.model.unbind();
this.model.stopListening();
}
// set new model and call initialize
this.model = model;
this.delegateEvents( this.events); // will call undelegateEvents internally
this.initialize();
}
The overall view stayed the same and all children are changed.
You can find the fiddle here http://jsfiddle.net/stot/H4qPG/20/
I created 1.000.000 models and according to chrome task manager the memory consumption was stable over the loooong time of this test.
Useful information:
Destroy or remove a view in Backbone.js
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/

How to delete a backbone model client-side?

What's the best way to delete a model client side? I don't need to worry about removing it server-side. How do I make sure it is removed everywhere, avoiding every gotcha, every zombie binding. I'm looking for a guide to removing and destroying everything and ensuring the model is garbage collected.
Thanks!!
It really depends on what is inside this model. If it is binded to events from other instances - View/Collection/Models, you should remove those event listeners manually, since there is no way to remove all of them at once.
Also, Model.destroy() removes the model from any collections ( backbone documents ) :
Destroy model.destroy([options])
... Triggers a "destroy" event on the model, which will bubble up through any collections that contain it ...
The thing that you might want to do is assign a new destroy method which includes the event triggering and the stuff you want to remove.
destroy: function(options) {
// Any events you wish to switch off ( if you have any )
SomeCollection.off('change', this.changeFn);
Backbone.Model.prototype.destroy.apply(this, options);
}
May be you should also be aware of some patterns for making less garbage from Models :
Don't place your initialized model in a variable ( keep it in the collection );
Make sure you write your code in a way that no events are binded from the Model ( Use views/collections for that );
Keep your model code simple, since models in your app will be most numbered.
I think by following those rules you won't need to worry so much about garbage from your Models.

A Backbone.js view that is a simple select list

I've built a Backbone-powered library that allows a user to add/remove items, much like the Todos example.
Every time an item is add or removed - or the entire collection is refreshed - I need two other select elements that are on other areas of the page to re-populate with the latest items as options. How would this be implemented, do I simply re-populate the select element in the render function of the view which holds a reference to the collection?
I'm tempted to create a view just for the select options but this seems like overkill, especially when considering the view doesn't need to re-act to any events. The select options are used by other views to populate form data.
You're correct: create a unique view for each select option. It's not overkill at all; that's what Views are for. They listen for events from their models, in this case the item list, and redraw themselves upon receiving an event. They have container designations, so once you've established those in the parameters for the View subclass, you never need to think about them again. You can style them independently.
That's the whole point of the Views being the way they are.
More importantly, you could also abstract out "view of a list of things," and then each of your specific views can inherit from that view, and add two features: the filter ("latest"), and the renderer. You have to write the renderer anyway; you may as well exploit a little syntatic sugar to make it clear what you're rendering where. It's better than writing comments.
Not to distract from Elf Sternberg's already excellent answer, but to add a little context:
Don't loop over collections in your templates
If you want to do this, you might as well just use HTML partials and
AJAX. Instead, use a Backbone View that renders its own views (the
granularity is what minimizes server syncs and page refreshes). This
is recursive, you can repeat this pattern until there is no more
associated data to loop over.
— Jonathan Otto in A Conceptual Understanding of Backbone.js For The Everyman
Of course, there are a few gotchas when you want to render subviews.
I did a code search to try and find some examples of how to do this. Turns out that the TodoMVC example is a good model for doing this. From the Strider-CD source (MIT License):
var UserView = Backbone.View.extend({
//... is a class. not sure how to put that here
tagName: "option",
// Cache the template function for a single item.
template: _.template($('#user-item-template').html()),
// The DOM events specific to an item.
// maybe could put links here? but then user couldn't see on mouse-over
// The UserView listens for changes to its model, re-rendering. Since there's
// a one-to-one correspondence between a **User** and a **UserView** in this
// app, we set a direct reference on the model for convenience.
initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.model.bind('destroy', this.remove);
},
// Re-render the contents of the User item.
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
// Remove the item, destroy the model.
clear: function() {
this.model.clear();
}
});
// The Application
// ---------------
// Our overall **AppView** is the top-level piece of UI.
var UsersView = Backbone.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: $("#user-form"),
// no events here either at this time
// At initialization we kick things off by
// loading list of Users from the db
initialize: function() {
_.bindAll(this, 'addAll', 'addOne','render');
Users.bind('add', this.addOne);
Users.bind('reset', this.addAll);
Users.bind('all', this.render);
console.log("fetching Users");
Users.fetch();
},
// Re-rendering the App just means refreshing the statistics -- the rest
// of the app doesn't change.
render: function() {
console.log("rendering User AppView");
// might want to put some total stats for the Users somewhere on the page
},
// Add a single todo item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOne: function(User) {
console.log("adding one User: " + User.get("id") + "/" + User.get("email"));
var view = new UserView({model: User});
this.$("#user-list").append(view.render().el);
},
// Add all items in the **Users** collection at once.
addAll: function() {
console.log("adding all Users");
console.log(Users.length + " Users");
Users.each(this.addOne);
}
});
// Finally, we kick things off by creating the **App**.
console.log("starting userapp now");
var UsersApp = new UsersView();
});
Additional examples of a select list view with option sub-views can be found in:
Zipkin source
reviewboard source

backbone.js - collections and views

I understand how to get a collection together, or an individual model. And I can usually get a model's data to display. But I'm not clear at all how to take a collection and get the list of models within that collection to display.
Am I supposed to iterate over the collection and render each model individually?
Is that supposed to be part of the collection's render function?
Or does the collection just have it's own view and somehow I populate the entire collection data into a view?
Just speaking generally, what is the normal method to display a list of models?
From my experience, it's the best to keep in your collection view references to each model's view.
This snippet from the project I'm currently working on should help you get the idea better:
var MyElementsViewClass = Backbone.View.extend({
tagName: 'table',
events: {
// only whole collection events (like table sorting)
// each child view has it's own events
},
initialize: function() {
this._MyElementViews = {}; // view chache for further reuse
_(this).bindAll('add');
this.collection.bind('add', this.add);
},
render: function() {
// some collection rendering related stuff
// like appending <table> or <ul> elements
return this;
},
add: function(m) {
var MyElementView = new MyElementViewClass({
model: m
});
// cache the view
this._MyElementViews[m.get('id')] = MyElementView;
// single model rendering
// like appending <tr> or <li> elements
MyElementView.render();
}
});
Taking this approach allows you to update views more efficiently (re-rendering one row in the table instead of the whole table).
I think there are two ways to do it.
Use a view per item, and manipulate the DOM yourself. This is what the Todos example does. It's a nice way to do things because the event handling for a single model item is clearer. You also can do one template per item. On the downside, you don't have a single template for the collection view as a whole.
Use a view for the whole collection. The main advantage here is that you can do more manipulation in your templates. The downside is that you don't have a template per item, so if you've got a heterogeneous collection, you need to switch in the collection view template code -- bletcherous.
For the second strategy, I've done code that works something like this:
var Goose = Backbone.Model.extend({ });
var Gaggle = Backbone.Collection.extend({
model: Goose;
};
var GaggleView = Backbone.View.extend({
el: $('#gaggle'),
template: _.template($('#gaggle-template').html()),
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
}
};
var g = new Gaggle({id: 69);
g.fetch({success: function(g, response) {
gv = new GaggleView({model: g});
gv.render();
}});
The template code would look something like this:
<script type="text/template" id="gaggle-template">
<ul id="gaggle-list">
<% _.each(gaggle, function(goose) { %>
<li><%- goose.name %></li>
<% }); %>
</ul>
</script>
Note that I use the _ functions (useful!) in the template. I also use the "obj" element, which is captured in the template function. It's probably cheating a bit; passing in {gaggle: [...]} might be nicer, and less dependent on the implementation.
I think when it comes down to it the answer is "There are two ways to do it, and neither one is that great."
The idea of backbone is that view rendering is event driven.
Views attach to Model data change events so that when any data in the model changes the view updates itself for you.
What you're meant to do with collections is manipulate a collection of models at the same time.
I would recommend reading the annotated example.
I've also found this a confusing part of the Backbone framework.
The example Todos code is an example here. It uses 4 classes:
Todo (extends Backbone.Model). This represents a single item to be todone.
TodoList (extends Backbone.Collection). Its "model" property is the Todo class.
TodoView (extends Backbone.View). Its tagName is "li". It uses a template to render a single Todo.
AppView (extends Backbone.View). Its element is the "#todoapp" . Instead of having a "model" property, it uses a global TodoList named "Todos" (it's not clear why...). It binds to the important change events on Todos, and when there's a change, it either adds a single TodoView, or loops through all the Todo instances, adding one TodoView at a time. It doesn't have a single template for rendering; it lets each TodoView render itself, and it has a separate template for rendering the stats area.
It's not really the world's best example for first review. In particular, it doesn't use the Router class to route URLs, nor does it map the model classes to REST resources.
But it seems like the "best practice" might be to keep a view for each member of the collection, and manipulate the DOM elements created by those views directly.

Categories