I have a close function attached to all of my views,
Backbone.View.prototype.close = function()
{
this.remove();
this.unbind();
if (this.closeMe) this.closeMe();
};
And in closeMe() function inside views I call off() function of backbone to remove previously-bound callback function from models and collections.
closeMe: function()
{
if(this.model)
this.model.off(null, null, this);
...
}
Question is, If I have some variables attached to current view in initialize function, Do I need to handle them via closeMe() function?
initialize : function(options)
{
...
this.myVar= options.something;
}
The garbage collector do this work for you.
The garbage collector algorithm reduces the definition of "an object is not needed anymore" to "an object has no other object referencing to it". An object is considered garbage collectable if there is zero reference pointing at this object.
When the reference to the view has zero reference pointing at it, the garbage collector will delete this object. At this moment, if the object myVar has no references pointing at it, the garbage collector will delete it.
By the way, I suggest to use the method listenTo to listen events instead of method on because simplify your code. When you call remove on Backbone View, removes a view from the DOM, and calls stopListening over the view to remove any bound events that the view has listenTo'd.
Related
I have a context problem / design problem for my Backbone view.
Goal
The user selects a user from a list / user collection in a separate view.
The mentioned view passes an global event that the editUserView receives ("edit-contact").
The editUserView should receive this event and extract the (user) model.id attribute. By using this model.id I want to update the view with the corresponding object retrieved from the existing view model Tsms.Collection.Users.
Problem
The context passed to the updateView function is wrong, and thus I do not have access to the parent views .render() function. The debugger states "render() is not a function".
Since the context is not that of the parent view I am also unable to set the this.current variable.
How would I go about solving this problem?
View code
Tsms.Views.editUserView = Backbone.View.extend({
model: Tsms.Collections.Users,
initialize: function(options) {
Tsms.require_template('edituser')
this.template = _.template($('#template_edituser').html());
this.current = -1;
Tsms.vent.on('edit-contact', this.updateView)
},
updateView: function(model) {
this.current = model.id;
this.render();
},
render: function() {
this.$el.html(this.template(this.model.get(this.current).attributes));
return this;
}
});
Backbone's on actually takes three arguments:
on object.on(event, callback, [context])
[...]
To supply a context value for this when the callback is invoked, pass the optional last argument: model.on('change', this.render, this) or model.on({change: this.render}, this).
The easiest and (currently) most idiomatic way to solve your problem would be to use the third context argument:
Tsms.vent.on('edit-contact', this.updateView, this);
While mu is too short is right, you should use Backbone's listenTo to avoid memory leaks (zombie views).
this.listenTo(Tsms.vent, 'edit-contact', this.updateView);
The context is automatically set to this, the calling view.
When remove is called on the view, stopListening is called and any references kept for events are removed.
Another reason to avoid on is that it should be the view that is responsible for the events it wants to handle, the event bus shouldn't have to know.
Consider the following:
SomeView = Backbone.View.extend({
render0: function () {
var view0 = new View0();
view0.setElement("#right-block");
view0.render();
},
render1: function(event) {
var view1 = new View1();
view1.setElement("#right-block");
view1.render();
},
});
If I call render0() and then render1, what will happen to the object view0? Do I have to explicitly destroy the old view?
Your view0 will stay in memory as long as the DOM element #right-block exists. Because the event handlers on the DOM element is pointing to methods of your view, so it won't be garbage collected.
Ideally you should invoke view0.remove(), which will remove the element from DOM and also calls stopListening().
But in your sample code, if you do that, the element #right-block will be removed from DOM, and view1.setElement("#right-block"); won't work as expected.
In this case try invoking view0.undelegateEvents(); view0.stopListening();, and if nothing else is referring to the view instance, it'll be garbage collected
No, you don't have to destroy the old view. Variables in objects that themselves go out of scope do not have to be manually cleared. When they go out of scope or when the parent object is deleted, the data contained within will also be eligible for garbage collection.
I am new to Backbone.js
I must be doing something wrong here. I'm trying to make a little demo to see what I can do with Backbone and basing it off some sample code.
I an get "Uncaught TypeError: Cannot call method toJSON of undefined".
I see why it is doing this, because the .bind("change", taskView.render) call is setting the context to the model (which the alert confirms) but it seems like there should at least be an argument to the render function to get access to the view. Maybe I am just going about it the wrong way? (see the sample code below).
task.bind("change", _.bind(taskView.render, taskView));
On Backbone Views and Models, the 'this' context for 'bind' is the calling object, so for model.bind('change', ...) this with be the model. For view.bind(... this will be the view.
You are getting the error because task.bind("change", _.bind(taskView.render, taskView)); sets this to be task when render runs, but this actually needs to be taskView.
Since you want to bind the model event to the view, as irvani suggests, the best way to do this is to use the .listenTo method (see: http://backbonejs.org/#Events-listenTo)
taskView.listenTo(task, 'change', taskView.render);
Depending on how you want the view lifecycle to work, you could also put the code in the initialize method of the view, which executes when the view is created. Then use stopListening in the remove method, to clear up the listener when the view is no longer in use.
As the task model is passed into the view, you get a fairly neat:
AskView = Backbone.Model.extend({
initialize: function(){
this.listenTo(this.model, 'change', this.render);
},
...
remove: function(){
this.stopListening(this.model);
...
}
});
var askView = new AskView({ model: task });
However, the one line solution to your problem is:
task.on("change", taskView.render, taskView);
bind is just an alias of on (see: http://backbonejs.org/#Events-on). The first argument is the event you are listening to, the second is the method to fire, and the third argument is the context to bind to, in this case, your taskView.
task.listenTo(model, 'change', taskView.render);
This says that listen to the model change and render the taskView on every change.
As irvani suggested, use listenTo instead.
object.listenTo(other, event, callback) Tell an object to listen to a particular event on an other object. The advantage of using this
form, instead of other.on(event, callback, object), is that listenTo
allows the object to keep track of the events, and they can be removed
all at once later on. The callback will always be called with object
as context.
In your case,
taskView.listenTo(task,"change",taskView.render);
assuming taskView is Backbone.View and task is a Backbone.Model.
The chances of memory leaks will be less when you use listenTo compared to using on.
If you must use on, you can specify the context as a third argument as and mu is too short suggested:
To supply a context value for this when the callback is invoked, pass the optional last argument: model.on('change', this.render, this) or model.on({change: this.render}, this).
In your case:
task.on("change", taskView.render, taskView);
I have a ListView and InstanceView defined in my Backbone code. The ListView is associated with a collection and it instantiates an InstanceView like so
render: function () {
this.collection.forEach(function(instance){
var commentHTML = new InstanceView({
model: instance
}).render();
renderedComments.push(commentHTML);
});
}
The new view instance goes out of scope after the render call finishes. What I've noticed is that the view persists in memory, though. I can tell because the events attached to it still fire long after the render method terminates.
So, does the view avoid gc because of its reference to the model object which, in turn, is referenced by the collection?
When the view is created events are registered on the model. The callbacks have references to the view, preventing it from being gc'ed.
Check out the backbone code on github. The events are hooked up in the delegateEventsmethod.
Objects are garbage collected whenever the JS engine feels like collecting them.
Your question, however, is actually different.
Just because you cannot access an object does not mean:
Nothing can, and
Things it attaches also go away.
Also, you explicitly add the object to collection, so it's not eligible for collection.
I am learning backbone.js following this tutorial, but I run into problem understanding the first example:
(function($){
var ListView = Backbone.View.extend({
...
initialize: function(){
_.bindAll(this, 'render'); // fixes loss of context for 'this' within methods
this.render(); // not all views are self-rendering. This one is.
},
...
});
...
})(jQuery);
Q1: Why use (function($){})(jQuery); instead of a perfectly fine working (function(){})();?
Q2: What does _.bindAll(this, 'render') do? How does it fixes loss of context for 'this' within method?
Q1: by passing jquery in as a parameter you allow yourself 2 things:
if the need of using 2 versions of jquery arises - you are prepared
module pattern is probably better thought of as something well encapsulated and with well defined dependencies, so by declaring that jquery is a parameter - you declare clear dependency. Granted there are other ways of doing it (like RequireJS), but this is also a way
Q2: bindAll is a utility method from Underscore.js that binds this for a specific method - thus, when that method is invoked (as a callback for instance) the correct this would be used inside of it.
For example:
(function($){
var ListView = Backbone.View.extend({
...
initialize: function(){
// fixes loss of context for 'this' within methods
_.bindAll(this, 'render', 'makestuff');
this.render(); // not all views are self-rendering. This one is.
},
...
makestuff : function() {
...
this.stuff = ... // some action on the list's instance
}
});
...
})(jQuery);
and in some part of your code you doing something like:
var list = new ListView({...});
$('#button').on('click', list.makestuff);
this in makestuff method is a reference to the above list and not whatever context the on function is in when makestuff is actually invoked inside it.
The actual implementation relies on using apply and call functions to bind the context of function's execution to a specific object.
(function($){})(jQuery) passes jQuery to the self executing which is using it as $. This makes sure you can safely use the $ symbol inside the closure without having to worry about interference with other libraries or even other versions of jQuery. A common example for this practice would also be passing in window and document and then using shorthands inside the closure:
(function(w, d, $){
$(w).resize(function({}); //equals $(window) now
})(window, document, jQuery);
underscore's _.bindAll does the following:
Binds a number of methods on the object, specified by methodNames, to
be run in the context of that object whenever they are invoked. Very
handy for binding functions that are going to be used as event
handlers, which would otherwise be invoked with a fairly useless this.
If no methodNames are provided, all of the object's function
properties will be bound to it.
See the annotated source for the how.