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

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.

Related

Backbone View instance not working as expected

I think I am missing something very trivial here. I have created a Backbone View as follows(without extending Backbone.View):
var PlayersView = new Backbone.View({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
But it doesn't log anything when I run this code. It doesn't work when I explicitly do: PlayersView.render(); as well.
But the following code works :
var PlayersView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
var playersview = new PlayersView();
The View constructor does not accept properties to add to the constructed object. It accepts a few special options like 'model', 'tagName', and so on. But the initialize(...) and render(...) properties in your first code snippet are effectively ignored.
The proper way to provide initialize, render, is to use Backbone.View.extend({...}).
From http://backbonejs.org/#View-extend
extend
Backbone.View.extend(properties, [classProperties]) Get started with
views by creating a custom view class. You'll want to override the
render function, specify your declarative events, and perhaps the
tagName, className, or id of the View's root element.
In other words, your first view's render method was not overridden by your custom render/initialize function.
When using extend you actually let Backbone understand you wish to use your own methods instead of the "default" ones.

Backbone Marionette ItemView return html

I want to render marionette ItemView and append result html() to my div.
My code:
var ProjectItemView = Backbone.Marionette.ItemView.extend({
template: "#ProjectItem",
tagName: 'div',
initialize: function () {
this.model.on('change', this.life, this);
this.model.on('change', this.render, this);
},
life: function () {
alert(JSON.stringify(this.model));
}
});
var tmp = new Project({project_id: 1});
tmp.fetch();
$('#container').append(new ProjectItemView({model: tmp}).$el);
alert in life: function shows model right. It means fetch works fine.
The question is - how to get html as result of view.
I also tried $('#container').append(new ProjectItemView({model: tmp}).render().el);
The problem was with the REST service that I use to populate collections/models. It returns array that contains one element - not plain object directly.
You have to react to render event from marionette.
...
onRender : function() {
$('#container').append(this.$el);
}
...
new ProjectItemView({model: tmp});
tmp.fetch();
If you want to get uncoupled, fire a distinct app event from within your view handler to the outside world. Radio might be worth considering if you're not already.
I think your problem is only the order of operations. Try this:
$('#container').append((new ProjectItemView({model: tmp})).render().el);
The way you had it before, you were invoking .render() on the constructor. With the extra parenthesis above, .render() is called on the instance.
pass element to view:
new ProjectItemView({model: tmp, el:'#container'}).render();

Best method for rendering collection in Backbone

I'm using Backbone and I have the following model and collection
App.Models.Person = Backbone.Model.extend({
});
App.Collections.People = Backbone.Collection.extend({
model: App.Models.Person,
url: 'api/people',
});
However what I'm struggling on is the best way to render this collection. Here's how I've done it so far which works but doesn't seem to be the most elegant solution
App.Views.PeopleView = Backbone.View.extend({
el: $(".people"),
initialize: function () {
this.collection = new App.Collections.People();
//This seems like a bad way to do it?
var that = this;
this.collection.fetch({success: function(){
that.render();
}});
},
render: function () {
var that = this;
_.each(this.collection.models, function (item) {
that.renderPerson(item);
}, this);
},
I'm fairly new to Backbone but have to assign this to a different variable to I use it inside of the success function just seems like a bad way of doing things? Any help on best practices would be appreciated.
Backbone allows you to register for events that you can react to. When the collection is synchronized with the server, it will always fire the sync event. You can choose to listen for that event and call any given method. For instance ...
initialize: function () {
this.collection = new App.Collections.People();
this.listenTo(this.collection, "sync", this.render);
// Fetch the initial state of the collection
this.collection.fetch();
}
... will set up your collection so that it would always call this.render() whenever sync occurs.
The docs on Backbone Events are succinct but pretty good. Keep in mind a few things:
The method you use to register event listeners (i.e. listenTo or on) changes how you provide the context of the called function. listenTo, for instance, will use the current context automatically; on will not. This piece of the docs explains it pretty well.
If you need to remove a view, you will need to disconnect event listeners. The easiest way to do that is to use listenTo to connect them in the first place; then when destroying the view you can just call view.stopListening().
For rendering, there are a lots of suggestions for how to do it. Generally having a view to render each individual model is one way. You can also use Backbone.Collection#each to iterate over the models and control the scope of the iterating function. For instance:
render: function() {
this.collection.each(function(model) {
var view = new App.Collections.PersonView({ model: model });
view.render();
this.$el.append(view.$el);
}, this);
}
Note the second argument to .each specifies the scope of the iterator. (Again, have a look at the docs on controlling scope. If you'd rather have a framework help out with the rendering, check out Marionette's CollectionView and ItemView.
If your view is supposed to just render the collection you can send the collection to temnplate and iterate through in template, otherwise you can create another subView for that purpose or send the individual models of the collection to another subview and append to the container, hope it was helpful.

Cannot understand backbone.js tutorial example

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.

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