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/
Related
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
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()
I have a div#game.
In here I want some data from the server to appear.
So when I use the initialize function I request data from my server in my game model with an ajax call.
Game = Backbone.View.extend(
{
id: 'game',
initialize: function ()
{
this.model.on('getGame', function() {});
this.model.getGame();
}
}
Because a callback doesn't seem to work in backbone a trigger has to be made.
So the app listens to the trigger getGame which is triggerd when the data from the server has been returned and saved into a variable in the model.
So far so good, this all works. The only problem now is that I want my div#game to fadeIn when it's done appending all data from the getGame function.
But I think because off the model.on(trigger) the initialize function 'thinks' it's ready after running the getGame() function, without actually having the data from the server appended yet.
So the parent div#all running the following:
this.$el.append(new Game().el);
also 'thinks' the div#game is ready and appends the div#game to itself and gives it a fadeIn.
div#game doesn't contain the server data yet, so when it actually does come back the images and text pop into existence instead of fadeIn in nicely...
Does some one know how to resolve this problem?
Thanks in advance!
======================================================
SOLVED
The problem was in the asynchronous property of the $.get and $.post functions. This is why the initialize was ready before the results had come back from the server. I changed it into a $.ajax and made the async: false. Now it all loads in the order I programmed/want.
Thank you for your detaild explaination Tallmaris, it will come in handy for sure since I'll be using a lot of triggers!
Rather than having the div#all render and append the view to itself, you can pass it as a parent to the View:
this.gameView = new Game({parent: this});
Then in your Game view:
initialize: function (options)
this.parent = options.parent
{
this.model.on('getGame', function() { this.$el.appendTo(this.parent); });
this.model.getGame();
}
Alternatively you could use a global Backbone event object like this:
window.vent = $.extend({}, Backbone.Events);
Now you have a global object (which you can name and put in any namespace as you like, don't just do it as in the code above), that you can use like this:
MAIN VIEW:
initialize: function () {
vent.on("GameReady", function ()
{
// fade in game view
});
}
GAME VIEW:
initialize: function (options)
this.parent = options.parent
{
this.model.on('getGame', function() { vent.trigger("GameReady"); });
this.model.getGame();
}
You can also pass parameters around. Read here for more documentation.
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
I have a number of backbone models, organized in collections and connected to their corresponding views and/or collections of views. Some of these models that do not belong to the same collection need to trigger an event which is of interest to another model (and maybe more than one).
The recommended way to deal with this is, I think, the "global event dispatcher/aggregator" as described here and other places.
However, I also happen to be using require.js, which seems to go against the idea of attaching the dispatcher/aggregator to the application's namespace object -- or am I wrong here?
So my question is: using require.js how can I have a number of different backbone models trigger an event that will be handled by another model?
A similar solution to what #Andreas proposed but without Backbone.Marionette (heavily inspired nonetheless, see the article linked in the question).
All you have to do is to define a module that creates a singleton of an event listener and require this object in the modules where you want to trigger an event or listen to this event.
Let's say you have app/channel.js defining your channel
define(['backbone', 'underscore'], function (Backbone, _) {
var channel = _.extend({}, Backbone.Events);
return channel;
});
You can then use it as a listener via
require(['app/channel'], function (channel) {
channel.on('app.event', function () {
console.log('app.event');
});
});
and you can trigger an event on this channel via
require(['app/channel'], function (channel) {
channel.trigger('app.event');
});
We using Marionettes app.vent (which is the global event transmitter for our application), allong with require js and it works really well.
app
define(, function(){
return new Backbone.Marionette.Application();
})
Model1
define(['app'], function(app){
return Backbone.Marionette.Model.extend({
initialize: function(){
this.bindTo('app.vent', 'create:model2', this.toSomething, this);
}
})
})
Model2
define(['app'], function(app){
return Backbone.Marionette.Model.extend({
initialize: function(){
app.vent.trigger('create:model2', this);
}
})
})