How does Backbone's events context work? - backbone.js

I'm learning backbone from the docs, can you provide an example of how the context works for Events? I don't really understand what they mean by the below:
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).

This is the event listeners.
For example,
model.on('change', this.render, this)
The first parameter is the event type: For example in this case, "change" if event has happened.
The second parameter is the function which you want to call. Let's say if a model has changed, call this function.
Third is the context. ('this') represents the context that you're clicking. For example if you are clicking in an input box, this.$el will be input box. If you don't mention this. Your 'this' will be window object.
For example:
var SomeModelView = Backbone.View.extend({
initialize: function() {
this.model.on('change', this.render, this);
},
render: function() {
// render a template
console.log("Template Rendered");
}
});
JSFiddle: http://jsfiddle.net/8AH3t/169/
Warning:
It's always recommended to use this.listenTo instead of using this.model.on.
this.listenTo(this.model, 'change', this.render);
Why?
When calling the .remove() method, the "change" event handler (our
render function) is still bound. So while the DOM element may be
removed, the view object itself is never released from memory.
Source: http://ozkatz.github.io/avoiding-common-backbonejs-pitfalls.html

Related

What does the return value do on the render function?

I think this source code speaks for itself, it creates a new view.
Every time I find a tutorial there is this return value on the render function.
But I never understood what it does, my code always worked with or without that return value, what does it exactly do?
// renders individual todo items list (li)
app.TodoView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item-template').html()),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this; // "enable chained calls" whatever that means
}
});
It does what you say, it enables chain calls that means you can invoke multiple method calls.
The most common use I've seen in Backbone it's calling el after render.
var view = new TodoView();
('#todo').append(view.render().el);
But you could create any other method in your view and chain it together.
view.render().method1().method2()

Backbone view Render method default parameter

I have a backbone view which is associated with model. View is listening for change method and it calls render if model is changed.
this.listenTo(this.model, 'change', this.render);
I'm having a problem that my backbone view's render method is getting called multiple times. I'm trying to debug this problem. For this purpose I added console.log statement in the render method:
render: function(data) {
if(this.model){
console.log("Render Method:",data," For model:",this.model.cid);
}
}
Now this data value is getting printed as undefined sometimes or something like model. Does anyone know what is the argument passed to a model change listener?
Note that : I'm not passing anything to render method.
and backbone documentation mentions nothing about this: http://documentcloud.github.io/backbone/#View-render
The change event passes the model and a hash of options
In backbone sources:
this.trigger('change', this, options);
And so in the documentation as mu is too short has commented:
"change" (model, options) — when a model's attributes have changed.
As far as I am aware the render function is not supposed to have any parameters pass to it
Event.Listen=Backbone.View.extend({
model: 'something'
initialize:function(){
this.listenTo(this.model,'change',this.render);
}
render:function(){
//is called when the listen to event is triggered
//if(this.model) does not make a lot of sense?? Does it need to be
//true ornull
//call another object
new Event.DoSomething();
}
});
From the Backbone site "Tell an object to listen to a particular event on an "other" object"
Looks like externally to the View there are other calls to render(additional to the event listener).
if you try this(listen to other method instead of render):
this.listenTo(this.model, 'change', this.customMethod);
And then declare this in the view below render:
customMethod: function() {
console.log(arguments, '<==== is there arguments?');
if(this.model){
console.log("Custom Render Method For model:",this.model.cid);
}
}
So please review in your code, where additional to the model.change listener, you are calling the render method outside the view.
The event callback gets called multiple times if the binding happens multiple times i.e.
this.listenTo(this.model, 'change', this.render);
is being executed multiple times.
It can also happen if the change is triggered multiple times. eg. you setting each of the attributes of a model in a for loop rather than at once.
Any call back receives an event as argument.
In your case
render: function(data) {
if(this.model){
console.log("Render Method:",data," For model:",this.model.cid);
}
}
data will be logged as undefined when view.render() is called.
data will be an event object when triggered by a change event

Render a view for a model on first load, not on every change

I would like to render a view for a model when the model is first fetched but not on every change.
My setup is as follows:
var m = new $.model.Customer({id: customer});
var v = new $.view.GeneralEditView({el: $("#general"), model: m});
m.fetch();
Then in the view initialize I bind the change event to the render method to render when the model is loaded:
this.model.bind('change', this.render);
The problem is that the view then renders on every change. I'd like to only render after the fetch. Unfortunately I'm not aware of any event that's fired after a fetch for a model other than change.
Is there something like 'reset' for collections that I can bind to?
EDIT:
Perhaps to put it more succinctly, for Backbone models is there a way to distinguish when a model is loaded from the server versus changed locally?
There are a bunch of different ways to approach this (these all assume var view = this; somewhere in your view code):
Call .fetch() with a one-time success callback:
m.fetch({
success: function() {
view.render();
}
});
Bind to change but unbind in the handler:
function handle() {
view.render();
view.model.off('change', handle);
}
this.model.bind('change', handle);
Use _.once to limit handler calls:
this.model.bind('change', _.once(function() {
view.render();
}));
Use a .ready() pattern for your models - example here. I like this option in cases where multiple views need to know when the model is loaded, and when you need to want to be able to write the same code without worrying about whether your model is loaded yet. The downside of this is that it requires you to add a model method like .isFullyLoaded() to test every time; the upside is that using a test function, rather than setting a flag, allows models to be loaded in bulk as part of a collection without having to change your code.
Models
You can make the change event specific to a certain key changing, such as the uniqueId (if you have one):
this.model.bind('change:id', this.render, this);
By default, fetch does not fire any event directly, but indirectly fires the change event once new data is loaded using set
If that is not an option, you can always trigger an event in your fetch function:
initialize: function () {
this.model.bind("fetch", this.update, this);
}
fetch: function () {
// do stuff
this.model.trigger("fetch", this);
}
update: function () {
// your refresh stuff here
}
I may have a general solution from https://github.com/documentcloud/backbone/pull/1468#issuecomment-6766096. I overwrote the sync method on Backbone as follows:
Backbone.Model.prototype.sync = function(method, model, options) {
var succ = options.success;
var customSuccess = function(resp, status, xhr) {
//call original
succ(resp, status, xhr);
model.trigger('synced', model, options);
}
options.success = customSuccess;
Backbone.sync(method, model, options);
}
To save the original success method as I don't want to mess that unless I need to, pass the custom success method. When the custom success method is invoked the custom event is triggered as suggested by #Austin and then the original success method in invoked.

Does backbone do _.bindAll by default now?

This tutorial suggests that we need to do _.bindAll to get the correct value of this in our functions. It seems that _.bindAll is no longer required with Backbone. The following code logs the same thing twice:
var TestView = Backbone.View.extend({
initialize: function () { _.bindAll(this, 'func1'); },
func1: function () { console.log(this); },
func2: function () { console.log(this); }
});
var testView = new TestView();
testView.func1();
testView.func2();
Am I correct in assuming that bindAll is no longer required, or am I just making a stupid mistake?
It is still necessary when the method is called out of context of the Class. Since you'recalling it in context, it isn't a mistake that you haven't needed it.
As mentioned in the underscore documentation for _.bindAll (http://documentcloud.github.com/underscore/#bindAll), it's "very handy for binding functions that are going to be used as event handlers, which would otherwise be invoked with a fairly useless this." You would also use it for methods where you need to create a callback.
To see how there are differences for callbacks, look at this fiddle. http://jsfiddle.net/joshvermaire/YQdZu/

How can I bind this.save to a backbone.js model change event?

I want my model to save to the server every time it changes.
I have tried:
initialize: function() {
this.bind('change', this.save());
},
I'm new to Backbone so I'm willing to believe there is a better way to achieve this. Basically I want avoid calling model.save at other points in my code by just automatically saving to the server every time the model changes.
Careful with using this.save directly:
this.bind('change', this.save);
will cause the changed attributes sent with the change event to be passed in to the save method, which will cause a second 'change' event to be fired, and so it will be saved twice.
Instead use:
this.bind('change', function(){ this.save(); });
you are calling this.save immediately instead of passing it through as a callback function.remove the parenthesis on this.save:
initialize: function() {
this.bind('change', this.save);
},
and your model's this.save method will be passed in as a function reference, allowing it to be called when the model changes.

Resources