I have started working with Backbone.
While learning Backbone, I got to know that the advantage of using .listenTo instead of .on is that listenTo allows "the object to keep track of the events".
I did not got what this sentence is trying to say. Well I can only see the difference between .on and .listenTo is about syntax.
Consider two calls:
this.model.on('change', this.some_method);
this.listenTo(this.model, 'change', this.some_method);
In the first case:
this.model knows that someone is listening for 'change' events but it doesn't know who.
this doesn't know what it is listening to unless you explicitly track what on calls you've made.
In the second case:
this.model knows that someone is listening for 'change' events but it doesn't know who.
this knows that it is listening to events from this.model.
So yes, the syntax is different but the main difference is who knows who is listening: with on, only the thing being listened to knows that there is a listener; with listenTo, the listener also knows what they're listening to.
The difference is important if you want to stop listening to events. If you're using on then you have to maintain your own list of what you're listening to so that you can off them; if you're using listenTo then listenTo keeps track of that and you can simply stopListening.
For example, View#remove looks like this:
remove: function() {
this.$el.remove();
this.stopListening();
return this;
}
so you can call view.remove() and it will automatically clean up any event bindings you have (as long as you used listenTo) and the chance of zombie listeners is drastically reduced.
Related
Note: we are using backbone 1.0.0
I am relatively new to Backbone, and was going to through some of the code a ex co-worker wrote. Rather than copy pasting stuff blindly, I wanted to understand how he did things, and that's when I started wondering about the best way to handle zombie views.
var view = new editItemView({ model: this.model });
this.ui.editItemPopup.html(view.render().el).modal({ modalOverflow: true });
This creates an instance of view and pops it up in a boostrap modal. The model has Save Changes, Cancel & Delete buttons. We will look at the clean work that is done on Save changes and delete.
onDelete: function() {
this.stopListening(this.model);
this.$el.parent().modal('hide');
this.$el.remove();
},
onApplyChangesClick: function () {
this.stopListening(this.model);
this.close();
},
close: function () {
this.$el.parent().modal('hide');
}
As far as I can tell, this code won't discard the view. And if I were to add another listener to the aforementioned view
this.listenTo(this.model.AnotherItem, 'change', this.doSomething);
and then trigger the change event on this.model.AnotherItem, this.doSomething will still fire. Correct?
I did some reading on Zombie views prior to posting this question. http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
And based on that article wouldn't I be better off if I just did
onDelete: function() {
this.close()
},
onApplyChangesClick: function () {
this.close();
},
close: function () {
this.$el.parent().modal('hide');
this.remove();
}
his.remove() will automatically call stopListening and also remove the dom element(Same as this.$el.remove)
The article that I posted also uses this.unbind()
this.unbind()will unbind any events that our view triggers directly – that is, anytime we may have calledthis.trigger(…)` from within our view, in order to have our view raise an event.
Is that still necessary in Backbone 1.0.0 (or latest version)? The article is 3 years old, so I was wondering and I couldn't find any mention of view.unbind in backbone documentation. The documentation mentions that unbind is an alias of off. So should I be doing
this.remove();
this.off();
Ok, first off let me state the obvious: a few zombie views here or there are not going to cause you any problems. All any given zombie view will do is eat up a small amount of memory and then go away when the user hits refresh or navigates away. So, if you're a little sloppy about cleaning up your references in general things will still work fine. Where you will run in to problems is when you have a lot of zombie views, say because you rendered a 20x100 table where every cell has its own View.
Now, to truly understand how to avoid zombie views you have to understand how memory works in Javascript. I encourage you to read more about that elsewhere, but here's the cliff notes version: anything you "stop using" will get cleaned up by the browser's garbage collector, and since the garbage collector can't tell exactly when you "stop using" something it actually goes by whether or not that thing has any references to it on other objects.
This is where event bindings come in to play, because they can create references that prevent a view from being garbage collected. One of the features of Backbone is that it handles cleaning up these bindings if they are made as part of a Backbone.View's initialization (ie. the events you put in an events property of the View class). So if you remove a View's element from the page, it will get garbage collected ...
... unless it has some other reference to it, like another object that uses it, or an event binding that you created using jQuery. So, as long as your Views don't have any other references you are correct: simply removing the element will be enough. But if you do have any other references, you will need to clean them up or else the View won't get garbage collected and will become a zombie view.
I have a view which needs to listenTo the change of a model attribute. When that change event fires, I have to call two different callback functions. I know this can be easily accomplished using .on like
model.on({
'change:title' : updateTitle,
'change:title' : updateSummary
});
But when using listenTo I have to write this in multiple lines
this.listenTo(model, 'change:title', updateTitle);
this.listenTo(model, 'change:title', updateSummary);
Is there a way to avoid having to re-write the listenTo statement multiple times?
Or does this issue mean that my code isn't structured properly and I should rethink my approach?
A simple alternative is that I call updateSummary from updateTitle, but I was just wondering if this can be accomplished using object.listenTo
does this issue mean that my code isn't structured properly and I should rethink my approach?
I don't see any problem having two (or more) listeners registered to the same event. That's a core point of decoupling. You could have different (decoupled) Views that update their contents when the same event is triggered.
I don't know the rest of your code, but I suppose your updateSummary listens to other change events apart from the change:title:
this.listenTo(model, 'change:summary', updateSummary);
In this case you are registering the same listener (updateSummary) to different events (change:title and change:summary). And that's ok, with listenTo you are sort of connecting different view updates to different model events in a decoupled way.
Is there a way to avoid having to re-write the listenTo statement multiple times?
Maybe an overkill, but you could use the functions that underscore provides to have something like this:
_.each([updateTitle, updateSummary], _.partial(this.listenTo, model, 'change:title'), this);
A simple alternative is that I call updateSummary from updateTitle, but I was just wondering if this can be accomplished using object.listenTo
I'm afraid that listenTo only accepts a callback function, not an array of functions.
If you are still worried about having listenTo on the same event multiple times, you can create a more general function, let's say update, that updates both title and summary, and register only this more general update function:
this.listenTo(model, 'change:title', update);
or if you don't want another function, you can use an anonymous one:
this.listenTo(model, 'change:title', function() {updateTitle(); updateSummary();});
As #muistooshort already mentioned in the comment :
var objParam={
'change:title' : updateTitle,
'change:title' : updateSummary
};
model.on(objParam);
See carefully, objParam has 2 keys with the same name, this will give error in strict mode, otherwise will just override the previous value of the key & hence, console.log(objParam) prints
{ 'change:title' : updateSummary }
and this is what passed as argument to the .on call.
Im a bit confused over how to handle events between views in Backbone. Right now i have two partial views rendered at the same time on a website. Now i want one of the views to dispatch an event that the other view can listen to. How would i do that?
In the view that dispatches the event i run:
this.trigger("myEvent")
And in the view listening i run:
this.bind('myEvent', this.myFunc);
But nothing seem to happen at all.
If you're triggering an event on v1 with:
this.trigger('myEvent'); // this is v1
then you'd have to listen to events from v1 with:
v1.on('myEvent', this.myFunc); // this is, say, v2 here.
The events aren't global, they come from specific objects and you have to listen to those specific objects if you want to receive their events.
If you bind the views directly to each other, you'll quickly have a tangled mess where everything is directly tied to everything else. The usual solution is to create your own event bus:
// Put this where ever it makes sense for your application, possibly
// a global, possible something your your app's global namespace, ...
var event_bus = _({}).extend(Backbone.Events);
Then v1 would send events through the event_bus:
event_bus.trigger('myEvent');
and v2 would listen to the event_bus:
this.listenTo(event_bus, 'myEvent', this.myFunc);
I've also switched from bind to listenTo since listenTo makes it easier to prevent zombies.
Demo: http://jsfiddle.net/ambiguous/yb9TY/
What is more efficient, using events to communicate between nested Views, or keeping a reference around to call a method on. The following example shows two Views. The outer View responds to a click event, and could then use either an event, or method call to get the InnerView to respond appropriately.
InnerView = Backbone.View.extend({
initialize: function() {
this.model.bind('doSomethingEvent', this.doSomething);
},
doSomething: function() {
// This could have been called from event/trigger
// or from direction method invocation using reference.
}
});
OuterView = Backbone.View.extend({
events = {
'click' : 'handleOutViewClick'
},
render: function() {
// Create InnerView to render some model
var innerView = new InnerView({model:this.model });
$(this.el).append(innerView.render().el);
// Could store a reference to the View?
this.viewRef = innerView;
},
handleOutViewClick: function(e) {
// Should this function use a reference to the InnerView instance:
this.viewRef.doSomething();
// Or should it trigger an event on this.model that
// the InnerView is bound to?
this.someCollection.trigger('doSomethingEvent');
}
});
Probably a single method call is going to be more efficient than an event dispatch, which will involve at least 2 method calls. But I don't think you need to be concerned about which is more "efficient" here technically. Unless this is happening many times a second, you can concern yourself only with what makes for cleaner, more correct code. I think the cleanest pattern depends on the details being communicated. Here's my preference:
If it is a natural fit for outerview to manipulate models and collections, and have innerview respond via the normal backbone model/collection events, that is the cleanest.
If what's happening isn't really about the models, consider a "View Model" pattern where you model the interesting bits of state of the view as if it was a back end model, even though you have no intention of ever having that model interact with the server. Then bind your two views to events coming off the view model and have them coordinate by altering a common "view model" instance. This is a pattern I use for complicated views with lots of interdependent state not directly associated with the underlying models from the server.
If what's happening doesn't really change the models/collections and is more of a view-specific thing, a direct method dispatch will be more straightforward but also more tightly coupled. It's up to your judgement to decide when the loose coupling afforded by event dispatch merits the extra complexity and harder-to-follow control flow.
What is the best way to bind events to a Backbone boilerplate application? I've been trying to bind my events directly to the models associated with my views, in my views, but it doesn't seem to be working. I see within 'namespace.js', that there is an app key that extends Backbone.Events like so:
// Keep active application instances namespaced under an app object.
app: _.extend({}, Backbone.Events)
I don't fully understand how to use it...
I was able to get things working without the boilerplate, but it does provide some very cool functionality, so I'd love to be able to use it. Thanks!
ADDED
the code I was using was with the underscore bind method like so:
this.module.bind('change', this.render);
But then, I realized that 'this.model' is returning undefined, and so this doesn't work. I really am not sure how the boilerplate wants me to reference my model from the view.
I'm not sure if it is a typo that you copied from your code or a typo you only entered here, but I believe this.module (which IS undefined) should be this.model, which you also must be sure to pass in when you instantiate your view, of course, as so:
myView = new BBView({model: myModel});
then you can say this.model.bind('change', this.render); or this.model.on('change', this.render); for the most recent version of Backbone
I frequently bind my views to change events on my models in this way.
As for extending Backbone.Events, the way I have used it is to create an independent "event aggregator" that will help connect events between different views on your page. Let's say for example you have some action on one view that needs to set off an action on another view. In this case, you can pass your event aggregator object as an option to each of your views when you instantiate them, so that they can both trigger events or bind to events on a common object (i.e. your event aggregator).
whatsUp = _.extend({}, Backbone.Events) // the aggregator
myFirstView = new FirstBBView ({whatsUp: whatsUp});
(the aggregator shows up as this.options.whatsUp inside the view)
mySecondView = new SecondBBView2 ({whatsUp: whatsUp});
inside FirstBBView:
this.options.whatsUp.bind('specialEvent', function(arg1,arg2) {
// do stuff
});
inside SecondBBView, when something important happens:
this.options.whatsUp.trigger('specialEvent', {arg1: 'some data', arg2: 'more data'});
For a great explanation, see this great article by Derick Bailey