Cannot understand backbone.js tutorial example - javascript

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.

Related

_.bindAll isn't working while rendering a Backbone view

I'm trying to understand the backbone bindAll function in addition to handling its contexts. In this case, the following implementation doesn't work.
Create a view to insert data from a list.
_.bindAll (this, 'render'); Could you see the context of this in the function do? $(this.el) is undefined. Shouldn't it work using _bindAll?
The following does not append in the ol list
$(this.el).append('idCurso->', cursoModelo.get('idCurso'),' titulo-> ', cursoModelo.get('titulo'));
$(this.el).append('hola caracola');`.
HTML
<ol id="ListaLibros"/>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
JS
// create a view to print the model data in the html
var ListaLibrosView = Backbone.View.extend({
el: '#ListaLibros',
initialize: function() {
this.collection = cursoCollection;
_.bindAll(this, 'render');
this.render();
},
render: function() {
console.log("funcion render");
var listaLibrosLi = '';
this.collection.each(function(cursoModelo) {
$(this.el).append('idCurso->', cursoModelo.get('idCurso'), ' titulo-> ', cursoModelo.get('titulo'));
$(this.el).append('hola caracola');
});
}
});
You're not using using this.el directly inside render, but inside a _.each() callback, which is another function with different context. You need to make sure it has the same context as well, you can simply achieve it like _.each(function(){}, this) since it accepts the context as second argument.
Or if you're using ES6 you can use an arrow function as callback which does not create a new context like _.each(i => {})
TJ's right, though you're using this.collection.each, it's just a proxy to Underscore's _.each.
Also, this.$el = $(this.el). You should use this.$el directly.
Note that _.bindAll is an Underscore's function and it is unrelated to Backbone. Since the problem comes from the nested each callback context, once fixed, you won't need _.bindAll at all.
In fact, I never needed _.bindAll and when seen in tutorials, most of the time, it's because it's outdated or wrongly used.

'this' inside View.render() being set to the model

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);

In Backbone, how do I have an after_render() on all views?

I am maintaining a javascript application and I would like there to be a jquery function invoked on pretty much every view. It would go something like this:
SomeView = Backbone.Marionette.ItemView.extend
initialize: ->
#on( 'render', #after_render )
after_render: ->
this.$el.fadeOut().fadeIn()
Clearly there is a better way to do this than have an after_render() in each view? What is the better way to do it? If you can give an answer that includes jasmine tests, I'll <3 you ;)
The event you are looking for is onDomRefresh. See here for the documentation:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#view-domrefresh--ondomrefresh-event
Create your own base view class and put your afterRender code in it. When you create a view, inherit from this class.
var MyApp.ItemView = Backbone.Marionette.ItemView.extend({
afterRender: function() {
// This will be called after rendering every inheriting view.
}
});
var SpecificItemView = MyApp.ItemView.extend({
// this view will automatically inherit the afterRender code.
});
In general, it seems to be considered good practice to define your own base views for all 3 view types. It will enable you to easily add global functionality later.
There is a common pattern used across all Backbone frameworks, normally they have a render method which in turn calls beforeRender, renderTemplate and afterRender methods.
render:function(){
this.beforeRender();
this.renderTemplate();// method names are just indicative
this.afterRender();
return this;
}
In your Base view you can have these methods to be empty functions, and implement them wherever you want it. Not sure this answer applies to Marionette
Combining thibaut's and Robert Levy's answer, the correct solution would be:
var baseView = Backbone.Marionette.ItemView.extend({
onDomRefresh: function() {
// This will be triggered after the view has been rendered, has been shown in the DOM via a Marionette.Region, and has been re-rendered
// if you want to manipulate the dom element of the view, access it via this.$el or this.$('#some-child-selector')
}
});
var SpecificItemView = baseView.extend({
// this view will automatically inherit the onDomRefresh code.
});

Backbone and RequireJS conflicts - instances or constructors?

Could someone explain the fundamental difference between:
define(['backbone'], function(Backbone) {
MyModel = Backbone.Model.extend({
});
});
define(['backbone', 'models/mymodel'], function(Backbone){
var app = Backbone.View.extend({
initialize: function() {
var model = new MyModel();
}
});
});
and:
define(['backbone'], function(Backbone) {
var MyModel = Backbone.Model.extend({
});
return MyModel;
});
define(['backbone', 'models/mymodel'], function(Backbone, MyModel){
var app = Backbone.View.extend({
initialize: function() {
var model = new MyModel();
}
});
});
In the former, the first module simply defines MyModel. In the latter, it's created as a variable and returned, and the second module needs to have it put in the parameters when imported.
RequireJS examples I see around seem to vary between the two, but I don't really understand the difference - does one return an instance and the other a constructor?
In my application I didn't even notice that I was actually using both ways in different places, and I think it was causing problems. I was using a lot of
self = this
self.model.doSomething
inside my views and models, and as my app got bigger, I started getting errors because there were conflicts with definitions of self.
Short Version: 1st version == wrong.
Medium Version: The first one bypasses Require entirely by using global variables, while the second one actually uses Require.
Long version:
The way Backbone modules work is that you run "define", pass it a function (and usually an array of dependencies also), and whatever gets returned from that function is defined as that module. So if I do:
// Inside foo.js
define([], function() {
return 1;
});
I've defined the "foo" module to be 1, so if elsewhere I do:
define(['foo'], function(foo) {
alert(foo); // alerts 1
});
Your first version doesn't return anything, so it's not actually creating a Require module at all.
How does it work then? Well, in that version you do:
MyModel = Backbone.Model.extend({
NOT:
var MyModel = Backbone.Model.extend({
So that's really the same as doing:
window.MyModel = Backbone.Model.extend({
Then when the second part of the code runs, it access window.MyModel, and works ... but it's completely bypassing Require.js in the process.
I think the most important thing to takeaway is: ALWAYS DECLARE (ie. var) YOUR JAVASCRIPT VARIABLES. I don't agree with everything Crockford says, but he's dead right on this one. You will get lots of bugs (with Require and without) if you don't make this a habit.
Beyond that, the next most important thing is probably: ALWAYS RETURN SOMETHING FROM THE FUNCTION YOU PASS TO define. There are certain special cases where you don't want to return anything, but unless you are deliberately trying to solve one of those cases you should always return something to define the module.
Finally, if you're using Require, every variable in your code should either:
Come from the define function (ie. it should be an argument variable from the function that you pass to define), or
It should be declared (ie. var-ed ) inside that file
If you use JSLint or 'use strict'; (as Valentin Nemcev suggested), or if you use an editor like Eclipse, your tools can help you ensure this (and in fact make it easy to ensure).
MyModel = Backbone.Model.extend({});
Here you are not returning a constructor, you are defining a global variable and accessing it later in different module.
Actually it is wrong, it works by accident. You should return your modules from define and access them via parameters in other modules.
Like this:
return Backbone.Model.extend({});
You should use strict mode to avoid problems with global variables in JS.
Also, constructor in JS is just a function that is meant to be run with new. Backbone extend always returns a constructor function, and you create a model instance by calling the constructor with new, like you are doing in both examples.

backbone.js collection bind this confusion

Here's an example of my collection view:
mod.AppListView = Backbone.View.extend({
initialize: function() {
var self = this
mod.collection.bind('add', self.addOne);
mod.collection.bind('reset', self.addAll);
_.bindAll(self, 'addOne', 'addAll');
this.addAll();
},
events: {
},
addOne: function(myModel) {
var view = new ListItemView({
model: myModel
});
},
addAll: function() {
mod.collection.each(this.addOne);
},
});
On initial run this works fine. But on subsequent resets addAll's this becomes the collection instead of the view, and therefore addOne wouldn't work.
To fix this I had to do:
mod.collection.bind('reset', self.addAll, this);
But I thought that was the point of _.bindAll? Shouldn't that have set the this to be the view? Could this be explained? Is there a way to always ensure this points to the view and not the collection?
Thanks.
_.bindAll must come before any reference to the method. You have it backwards.
_.bindAll(self, 'addOne', 'addAll');
mod.collection.bind('add', self.addOne);
mod.collection.bind('reset', self.addAll);
When you call _.bindAll, it replaces the methods that you've specified with one that has been wrapped / proxied / decorated, to ensure the context is always set correctly. Since the method is being replaced, any reference that you make to the method must be done after the replacement happens. Otherwise the reference will be pointing to the original method and the _.bindAll will appear to have not worked.
As far as _.bindAll vs the 3rd parameter... pick the one you like. I prefer passing in the 3rd parameter when calling .bind, but that's just me. There are cases when I have to use _.bindAll, though. Both of them do the same thing, they just do it in a different way.

Categories