is `bindAll` outdated? - javascript

Of course i read manual, but as i see in classical example it doesn't really make a difference if i comment the line with binding. Are the methods bound now by default?
(function($){
var ListView = Backbone.View.extend({
el: $('#TheList'), // el attaches to existing element
events: {
'click button#add': 'addItem'
},
initialize: function(){
// _.bindAll(thathis, 'render', 'addItem'); // every function that uses 'this' as the current object should be in here
this.counter = 0; // total number of items added thus far
this.render();
},
render: function(){
$(this.el).append('<button id="add">Add list item</button>');
$(this.el).append('<ul></ul>');
// console.log(this);
// console.log(this.el);
},
addItem: function(){
this.counter++;
$('ul', this.el).append('<li>hello world'+this.counter+'</li>');
}
});
var listView = new ListView();
})(jQuery);

Jax is right that you don't have to manually bind your View methods and events in the latest version of Backbone (Currently 1.1.0). There are earlier versions where this is true too, but I can't remember which ones.
There are cases where you will need to bind your view methods to make them work correctly. These cases are basic Javascript scoping and not related to Backbone JS specifically.
It is best not to use _.bindAll. Instead you should bind on a case-by-case basis using _.bind if you are using Underscore JS.
Actually if you are using Backbone, you won't need to use _.bind either. There are shortcut methods on the Backbone Events class. This means you can use these shortcuts in the Collection and Model classes too since they each have the Events class mixed in essentially.
This what you will need to do in your view class to attach listeners to collections or models.
this.collection.on('add', this.appendItem, this);
or
this.model.on('change', this.render, this);
The third parameter binds the view object scope to the render method. Without this, the render method will be called with the scope of the model I believe.
On a side note you can also do this:
this.collection.bind('add', this.appendItem, this);
The on method is actually an alias to bind, but I think it is clearer to use on. Using bind can be confusing. Looks like the Backbone docs prefer on and off to bind and unbind.
Here are two jsFiddles that illustrates what I'm talking. My examples are modified from this tutorial example: http://arturadib.com/hello-backbonejs/docs/5.html
This tutorial has been around for a long time actually, so may not be the best one to use. It does seem like it may have been updated a bit since it does use Backbone 1.10. I will also note that it uses _.bindAll.
Version 1 uses binding correctly: http://jsfiddle.net/ChTjs/
Relevant methods here:
initialize: function() {
this.collection = new List();
this.collection.on('add', this.appendItem, this);
this.collection.on('add', this.updateCount, this);
this.collection.on('remove', this.updateCount, this);
this.counter = 0;
this.render();
},
and here:
this.model.on('change', this.render, this);
this.model.on('remove', this.unrender, this);
Here is a jsFiddle version that doesn't bind the view scope to the callbacks. http://jsfiddle.net/LpEW8/1/
Try it out and slowly add in the bindings to get the code working again. I just realized this is actually mentioned in the Backbone docs. Search Binding "this"
EDIT 2
Just realized that it would be better to use the listenTo method. The advantage here is that you the callback will always be bound to the view/object that call listenTo. So no need to pass this like when using on. The additional benefit is that the listeners will be removed automatically, whereas that is not the case when using on
So instead of this:
this.collection.on('add', this.appendItem, this);
this.collection.on('add', this.updateCount, this);
this.collection.on('remove', this.updateCount, this);
You do this in your view:
this.listenTo(this.collection, 'add', this.appendItem);
this.listenTo(this.collection, 'add', this.updateCount);
this.listenTo(this.collection, 'remove', this.updateCount);
Here is the anchor link to listenTo in Backbone docs: http://backbonejs.org/#Events-listenTo
Also here is an updated fiddle: http://jsfiddle.net/ChTjs/2/

Yes, 'this' is bound by default to the View for all functions attached to the Backbone View, manually binding is no longer required.

Related

What is the advantage of custom events in backbone?

I understand how custom events work in Backbone and how to trigger them, but I'm having trouble understanding when exactly to use them and what purpose they serve over just calling the function directly.
e.g.
var MyView = Backbone.View.extend({
tagName: 'div',
className: 'myview',
initialize: function() {
this.model.on("mycustomevent", this.doSomething, this);
},
doSomething: function() {
console.log('you triggered a custom event');
}
});
If I am not mistaken, The doSomething method can be called by using this.model.trigger("mycustomevent") within other methods, but can be also called directly with this.doSomething()
Outside the view, it can be called similarly with
var myview = new MyView({model:somemodel});
myview.model.trigger("customevent");
myview.doSomething();
What I am confused about is why not forgo the the custom event and just call the method directly when you need it? Any example uses would be greatly appreciated!
You might want to add multiple handlers in different places in the code, f.ex:
this.model.on("mycustomevent", this.doSomething, this);
// ... and somewhere else you add an anonymous function
this.model.on("mycustomevent", function() {
console.log('do something');
});
Then when you trigger the event, it will execute all handlers. You can also use off to unbind/manage individual or multiple handlers.
If you are asking about a generic explanation of the event pattern (also called observer pattern, publish/subscribe, etc...), you should probably look for a more in-depth article or book.
With backbone, the perfect example is changing a property of a model. Using a function, you would have to do something like this...
$( '#someUI' ).click( function {
// update the model property
myModel.someProperty = 'somethingDifferent';
// update any ui that depends on this properties value
$( '#uiThatDisplaysModelData' ).find( 'someSelector' ).html( 'somethingDifferent' );
// save the model change to the server
$.ajax( {
url: 'somesaveurl',
data: { someProperty: 'somethingDifferent' }
success: callback
} );
} );
And then repeat those steps all over your code for each property change.
With backbone and a little setup the same things can be accomplished with:
myModel.set( 'property', 'somethingDifferent' );
This is because we have attached handlers to the change and change:property events of this model. These are custom events that are created automatically for models by backbone. So whenever any part of your code manipulates the model, the DOM updates and saving can be done automatically. We can also bind input validation or whatever we want to these custom events.
It's basically just applying the observer pattern to your application, where events belong to an object that is observable, and the handlers belong to its observers.
http://en.wikipedia.org/wiki/Observer_pattern

backbone.js: how to bind render

The following code works but I would like to improve readability and accessibility avoiding to write callbacks.
I need to render my view when fetch is performed on my collection.
Here the working code:
var MyView = Backbone.View.extends({
initialize: function()
{
var that = this;
MyCollection.fetch({
success: function () {
that.render();
}
});
},
....
});
Here my attempt which does not work:
var MyView = Backbone.View.extends({
initialize: function()
{
MyCollection.fetch();
MyCollection.bind('change', this.render);
},
....
});
Looks like you need to set the context for the call to bind. Like this:
MyCollection.bind('change', this.render, this);
One excellent thing about Coffeescript is that it takes care of these things much more cleanly.
ETA: the change event isn’t triggered on fetch, it’s only triggered when one of the models in the collection changes. reset is, though. Also, you’re binding to the event after triggering the fetch, not sure if that’s what you intend.
Aside: seems confusing to me that you’re capitalising the MyCollection member, makes it easily mixed up with a class.

Getting a reference to a model in an event callback

I'm not sure if I'm doing this right, first time playing with Backbone.js.
I have two views with two models and I want to use the event aggregator method to fire events between the two.
The aggregator declaration:
Backbone.View.prototype.eventAggregator = _.extend({}, Backbone.Events);
So in one view I have a line like this that will fire the removeRow method.
this.eventAggregator.trigger("removeRow", this.row);
In another view
MyView = Backbone.View.extend({
initialize: function() {
this.eventAggregator.bind("removeRow", this.removeRow);
this.model.get("rows").each(function(row) {
// Do stuff
});
},
removeRow: function(row) {
// row is passed in fine
// this.model is undefined
this.model.get("rows").remove(row);
}
});
I think I understand why this.model is undefined, but what can I do to maintain a reference so that I can use this.model in the callback? I thought about passing the model to the first view and then passing it back in the trigger call, but that seems to make the entire point of an event aggregator pointless. If I have the model I can just call the .remove method directly and have lost the benefit of my first view being unaware of the model. Any suggestions?
I think you have binding problem.
You have two ways to assure that this will be the View instance:
1. Using bindAll
In your View.initialize() you can add this line:
_.bindAll( this, "removeRow" )
Interesting post of #DerickBailey about this matter
2. Using the optional third argument in your bind declaration
Like this:
this.eventAggregator.bind("removeRow", this.removeRow, this);
Backbone documentation about this matter
Supply your View object as third parameter of the bind method:
this.eventAggregator.bind("removeRow", this.removeRow, this);
The third parameter is the context of calling your callback. See the docs.
Also, you can use .on() instead of .bind() which is shorter...
You need to bind this so scope isn't lost. The blog link on the other answer uses underscore's bindAll
initialize: function() {
_.bindAll(this, 'removeRow');
this.eventAggregator.bind("removeRow", this.removeRow);
this.model.get("rows").each(function(row) {
// Do stuff
});
},

How to render and append sub-views in Backbone.js

I have a nested-View setup which can get somewhat deep in my application. There are a bunch of ways I could think of initializing, rendering and appending the sub-views, but I'm wondering what common practice is.
Here are a couple I've thought of:
initialize : function () {
this.subView1 = new Subview({options});
this.subView2 = new Subview({options});
},
render : function () {
this.$el.html(this.template());
this.subView1.setElement('.some-el').render();
this.subView2.setElement('.some-el').render();
}
Pros: You don't have to worry about maintaining the right DOM order with appending. The views are initialized early on, so there isn't as much to do all at once in the render function.
Cons: You are forced to re-delegateEvents(), which might be costly? The parent view's render function is cluttered with all of the subview rendering that needs to happen? You don't have the ability to set the tagName of the elements, so the template needs to maintain the correct tagNames.
Another way:
initialize : function () {
},
render : function () {
this.$el.empty();
this.subView1 = new Subview({options});
this.subView2 = new Subview({options});
this.$el.append(this.subView1.render().el, this.subView2.render().el);
}
Pros: You don't have to re-delegate events. You don't need a template that just contains empty placeholders and your tagName's are back to being defined by the view.
Cons: You now have to make sure to append things in the right order. The parent view's render is still cluttered by the subview rendering.
With an onRender event:
initialize : function () {
this.on('render', this.onRender);
this.subView1 = new Subview({options});
this.subView2 = new Subview({options});
},
render : function () {
this.$el.html(this.template);
//other stuff
return this.trigger('render');
},
onRender : function () {
this.subView1.setElement('.some-el').render();
this.subView2.setElement('.some-el').render();
}
Pros: The subview logic is now separated from the view's render() method.
With an onRender event:
initialize : function () {
this.on('render', this.onRender);
},
render : function () {
this.$el.html(this.template);
//other stuff
return this.trigger('render');
},
onRender : function () {
this.subView1 = new Subview();
this.subView2 = new Subview();
this.subView1.setElement('.some-el').render();
this.subView2.setElement('.some-el').render();
}
I've kind of mix and matched a bunch of different practices across all of these examples (so sorry about that) but what are the ones that you would keep or add? and what would you not do?
Summary of practices:
Instantiate subviews in initialize or in render?
Perform all sub-view rendering logic in render or in onRender?
Use setElement or append/appendTo?
I have generally seen/used a couple of different solutions:
Solution 1
var OuterView = Backbone.View.extend({
initialize: function() {
this.inner = new InnerView();
},
render: function() {
this.$el.html(template); // or this.$el.empty() if you have no template
this.$el.append(this.inner.$el);
this.inner.render();
}
});
var InnerView = Backbone.View.extend({
render: function() {
this.$el.html(template);
this.delegateEvents();
}
});
This is similar to your first example, with a few changes:
The order in which you append the sub elements matters
The outer view does not contain the html elements to be set on the inner view(s) (meaning you can still specify tagName in the inner view)
render() is called AFTER the inner view's element has been placed into the DOM, which is helpful if your inner view's render() method is placing/sizing itself on the page based on other elements' position/size (which is a common use case, in my experience)
Solution 2
var OuterView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
this.$el.html(template); // or this.$el.empty() if you have no template
this.inner = new InnerView();
this.$el.append(this.inner.$el);
}
});
var InnerView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
this.$el.html(template);
}
});
Solution 2 may look cleaner, but it has caused some strange things in my experience and has affected performance negatively.
I generally use Solution 1, for a couple of reasons:
A lot of my views rely on already being in the DOM in their render() method
When the outer view is re-rendered, views don't have to be re-initialized, which re-initialization can cause memory leaks and also cause freaky issues with existing bindings
Keep in mind that if you are initializing a new View() every time render() is called, that initialization is going to call delegateEvents() anyway. So that shouldn't necessarily be a "con", as you've expressed.
This is a perennial problem with Backbone and, in my experience, there's not really a satisfying answer to this question. I share your frustration, especially since there is so little guidance despite how common this use case is. That said, I usually go with something akin to your second example.
First of all, I would dismiss out of hand anything that requires you to re-delegate events. Backbone's event-driven view model is one of its most crucial components, and to lose that functionality simply because your application is non-trivial would leave a bad taste in any programmer's mouth. So scratch number one.
Regarding your third example, I think it's just an end-run around the conventional rendering practice and doesn't add much meaning. Perhaps if you're doing actual event triggering (i.e., not a contrived "onRender" event), it would be worth just binding those events to render itself. If you find render becoming unwieldy and complex, you have too few subviews.
Back to your second example, which is probably the lesser of the three evils. Here is example code lifted from Recipes With Backbone, found on page 42 of my PDF edition:
...
render: function() {
$(this.el).html(this.template());
this.addAll();
return this;
},
addAll: function() {
this.collection.each(this.addOne);
},
addOne: function(model) {
view = new Views.Appointment({model: model});
view.render();
$(this.el).append(view.el);
model.bind('remove', view.remove);
}
This is only a slightly more sophisticated setup than your second example: they specifiy a set of functions, addAll and addOne, that do the dirty work. I think this approach is workable (and I certainly use it); but it still leaves a bizarre aftertaste. (Pardon all these tongue metaphors.)
To your point on appending in the right order: if you're strictly appending, sure, that's a limitation. But make sure you consider all possible templating schemes. Perhaps you'd actually like a placeholder element (e.g., an empty div or ul) that you can then replaceWith a new (DOM) element that holds the appropriate subviews. Appending isn't the only solution, and you can certainly get around the ordering problem if you care about it that much, but I would imagine you have a design issue if it is tripping you up. Remember, subviews can have subviews, and they should if it's appropriate. That way, you have a rather tree-like structure, which is quite nice: each subview adds all its subviews, in order, before the parent view adds another, and so on.
Unfortunately, solution #2 is probably the best you can hope for using out-of-the-box Backbone. If you're interested in checking out third-party libraries, one that I have looked into (but haven't actually had any time to play with yet) is Backbone.LayoutManager, which seems to have a healthier method of adding subviews. However, even they have had recent debates on similar issues to these.
Surprised this hasn't been mentioned yet, but I'd seriously consider using Marionette.
It enforces a bit more structure to Backbone apps, including specific view types (ListView, ItemView, Region and Layout), adding proper Controllers and a lot more.
Here is the project on Github and a great guide by Addy Osmani in the book Backbone Fundamentals to get you started.
I have, what I believe to be, a quite comprehensive solution to this problem. It allows a model within a collection to change, and have only its view re-rendered (rather than the entire collection). It also handles removal of zombie views through the close() methods.
var SubView = Backbone.View.extend({
// tagName: must be implemented
// className: must be implemented
// template: must be implemented
initialize: function() {
this.model.on("change", this.render, this);
this.model.on("close", this.close, this);
},
render: function(options) {
console.log("rendering subview for",this.model.get("name"));
var defaultOptions = {};
options = typeof options === "object" ? $.extend(true, defaultOptions, options) : defaultOptions;
this.$el.html(this.template({model: this.model.toJSON(), options: options})).fadeIn("fast");
return this;
},
close: function() {
console.log("closing subview for",this.model.get("name"));
this.model.off("change", this.render, this);
this.model.off("close", this.close, this);
this.remove();
}
});
var ViewCollection = Backbone.View.extend({
// el: must be implemented
// subViewClass: must be implemented
initialize: function() {
var self = this;
self.collection.on("add", self.addSubView, self);
self.collection.on("remove", self.removeSubView, self);
self.collection.on("reset", self.reset, self);
self.collection.on("closeAll", self.closeAll, self);
self.collection.reset = function(models, options) {
self.closeAll();
Backbone.Collection.prototype.reset.call(this, models, options);
};
self.reset();
},
reset: function() {
this.$el.empty();
this.render();
},
render: function() {
console.log("rendering viewcollection for",this.collection.models);
var self = this;
self.collection.each(function(model) {
self.addSubView(model);
});
return self;
},
addSubView: function(model) {
var sv = new this.subViewClass({model: model});
this.$el.append(sv.render().el);
},
removeSubView: function(model) {
model.trigger("close");
},
closeAll: function() {
this.collection.each(function(model) {
model.trigger("close");
});
}
});
Usage:
var PartView = SubView.extend({
tagName: "tr",
className: "part",
template: _.template($("#part-row-template").html())
});
var PartListView = ViewCollection.extend({
el: $("table#parts"),
subViewClass: PartView
});
Check out this mixin for creating and rendering subviews:
https://github.com/rotundasoftware/backbone.subviews
It is a minimalist solution that addresses a lot of the issues discussed in this thread, including rendering order, not having to re-delegate events, etc. Note that the case of a collection view (where each model in the collection is represented with one subview) is a different topic. Best general solution I am aware of to that case is the CollectionView in Marionette.
I don't really like any of the above solutions. I prefer for this configuration over each view having to manually do work in the render method.
views can be a function or object returning an object of view definitions
When a parent's .remove is called, the .remove of nested children from the lowest order up should be called (all the way from sub-sub-sub views)
By default the parent view passes it's own model and collection, but options can be added and overridden.
Here's an example:
views: {
'.js-toolbar-left': CancelBtnView, // shorthand
'.js-toolbar-right': {
view: DoneBtnView,
append: true
},
'.js-notification': {
view: Notification.View,
options: function() { // Options passed when instantiating
return {
message: this.state.get('notificationMessage'),
state: 'information'
};
}
}
}
Backbone was intentionally built so that there was no "common" practice in regards to this and many other issues. It is meant to be as unopinionated as possible. Theoretically, you don't even have to use templates with Backbone. You could use javascript/jquery in the render function of a view to manually change all of the data in the view. To make it more extreme, you don't even need one specific render function. You could have a function called renderFirstName which updates the first name in the dom and renderLastName which updates the last name in the dom. If you took this approach, it would be way better in terms of performance and you'd never have to manually delegate events again. The code would also make total sense to someone reading it (although it would be longer/messier code).
However, usually there is no downside to using templates and simply destroying and rebuilding the entire view and it's subviews on each and every render call, as it didn't even occur to the questioner to do anything otherwise. So that's what most people do for pretty much every situation they come across. And that's why opinionated frameworks just make this the default behavior.
You could also inject the rendered subviews as variables into the main template as variables.
first render the subviews and convert them to html like this:
var subview1 = $(subview1.render.el).html();
var subview2 = $(subview2.render.el).html();
(that way you could also dynamically string concatenate the views like subview1 + subview2 when used in loops) and then pass it to the master template which looks like this:
... some header stuff ...
<%= sub1 %>
<%= sub2 %>
... some footer stuff ...
and inject it finally like this:
this.$el.html(_.template(MasterTemplate, { sub1: subview1, sub2: subview2 } ));
Regarding the Events within the subviews: They will be most likely have to be connected in the parent (masterView) with this approach not within the subviews.
I like to use the following approach which also make sure to remove the child views properly. Here is an example from the book by Addy Osmani.
Backbone.View.prototype.close = function() {
if (this.onClose) {
this.onClose();
}
this.remove(); };
NewView = Backbone.View.extend({
initialize: function() {
this.childViews = [];
},
renderChildren: function(item) {
var itemView = new NewChildView({ model: item });
$(this.el).prepend(itemView.render());
this.childViews.push(itemView);
},
onClose: function() {
_(this.childViews).each(function(view) {
view.close();
});
} });
NewChildView = Backbone.View.extend({
tagName: 'li',
render: function() {
} });
There is no need to re-delegate events as it is costly. See below:
var OuterView = Backbone.View.extend({
initialize: function() {
this.inner = new InnerView();
},
render: function() {
// first detach subviews
this.inner.$el.detach();
// now can set html without affecting subview element's events
this.$el.html(template);
// now render and attach subview OR can even replace placeholder
// elements in template with the rendered subview element
this.$el.append(this.inner.render().el);
}
});
var InnerView = Backbone.View.extend({
render: function() {
this.$el.html(template);
}
});

Testing backbone.js application with jasmine - how to test model bindings on a view?

I had some interesting tribulations in trying to test whether views were correctly bound to events.  In backbone, we typically bind to events in the initialize method, using something along the lines of: something.bind("change", this.render);.  In my test, I want to make sure that this binding is set up, so I did the following: 
this.myView = new MyView();
spyOn(this.myView, "render");;
this.legendView.groupData.trigger("change");
expect(this.legendView.render).toHaveBeenCalled();
But, that won't work.  Because the bind occurs in MyView's initialize function, the event get's bound to myView's render function AT THAT TIME.  So, when you add your spy, it wraps the render function and sets it back into place at myView.render.  But the closure created by the first bind still exists, and we are totally hozed.  So what can we do about it?  What I did, is move my bind call's to a seperate function, something like: 
myView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, "render");
this.initialize_model_bindings();
},
initialize_model_bindings: function(){
something.bind("change", this.render);
},
render: function(){ //... }
});
and my test then looks like:
this.myView = new MyView();
spyOn(this.myView, "render");
this.myView.initialize_model_bindings();
this.legendView.groupData.trigger("change");
expect(this.legendView.render).toHaveBeenCalled();
This works, but I'm looking for a better solution. Thanks
I have managed to achieve this using prototype patching. Before you create the instance of the view, spyOn the constructor's prototype.
spyOn(MyView.prototype, 'changeSelected');
var view = new MyView();
view.selectSomething();
expect(view.changeSelected).toHaveBeenCalled();
Instead of spying on the callback you might try spying on something.bind. Then test that bind was called w/ the appropriate arguments. This is working for me so far. I'm using sinon.js instead of jasmine's built-in spies. sinon.js makes it a bit easier to test for args passed to a method call in a stack of same method calls (eg a bunch of calls to bind in a view init). So I haven't tested this idea w/ jasmine alone but believe it should be possible.
spyOn(this.legendView.groupData, 'bind');
this.myView = new MyView();
expect(this.legendView.groupData.mostRecentCall.args).toEqual('change', this.myView.render); // example!! only works if testing a single call to bind or the last call in a series (ie mostRecentCall)
And w/ sinon.js
sinon.spy(this.legendView.groupData, 'bind');
this.myView = new MyView();
expect(this.legendView.groupData.bind.calledWith('change', this.myView.render); // works w/ any number of calls to bind
I solved this problem by spying on a function called by my render function. So in your example:
myView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, "render");
something.bind("change", this.render);
},
someOtherFunction: function(){}, //this function only called from render
render: function(){ this.someOtherFunction(); /* rest of render function */ }
});
test looks like:
this.myView = new MyView();
spyOn(this.myView, "someOtherFunction");
this.myView.something.trigger("change");
expect(this.myView.someOtherFunction).toHaveBeenCalled();
then I wrote a separate test for whatever someOtherFunction does.
You should consider looking at Sinon.js. You could stub/mock the render() call and not even have to worry about 'someOtherFunction()'.
This may be too closely coupled with Backbone internals, but you can check the callback chain manually:
expect(this.legendView.groupData._callbacks['change']).toContain(this.myView.render)
I ran into the same problem and changed my Views code from:
this.model.on('change', this.render, this);
to:
this.model.on('change', function () {
this.render();
}, this);
And my jasmine tests worked as expected.

Categories

Resources