I have been looking at some code for a while now and can't decide on the best practice to apply in this situation:
Let's say that we have a view that has n-subviews. I have come across two practices for initializing them
1 Inside intitialize
initialize: function() {
this.subViews = [];
this.subViewModelCollection.each(function(model) {
var view = new SubView({model: model});
this.subViews.push(view);
this.$el.append(view.el);
}, this);
},
render: function() {
_.invoke(this.subViews, 'render');
}
2 Inside render
initialize: function() {
... // render handles the subviews
},
render: function() {
this.subViews = [];
this.subViewModelCollection.each(function(model) {
var view = new SubView({model: model}).render(); // render optional
this.subViews.push(view);
this.$el.append(view.el);
}, this);
}
now these are just crude samples, but they demonstrate my dilemma. Should the initialize or the render -function be responsible for initializing the subviews? Personally I have been a strong advocate for the latter version, but some code I saw made me sway towards the former.
So, WHERE do you initialize your subviews, WHY do you do it there and WHY is it better than the other option?
You should maximize the amount of work you do in initialize, as that will only be done once, and minimize the amount of work you do in your render function which may typically be called many times (for instance in a single page application or responsive webpage).
If you know your render method for your subviews will never change the generated html, then you can probably also call the render method for the subviews in the initialize method as well, and simply appending the rendered element in the "main view".
If the render methods for both your subviews and main view is only called once anyway (as part of loading a page or whatever) it probably does not matter how you do it, but generally minimizing the amount of work to be done in the render functions is probably good advice.
I would put the actual sub view rendering functionality into a new function completely, say renderSubViews().
This will allow for greater flexibility.
You can call this function from initialize or render, and change it easily as your application grows/changes.
You can bind this new function to events, i.e. the reset event for the view.
This is more logical. Does it make sense to auto-render sub-views when you initialize or render the main view? Should one be directly tied to the other?
Related
I have a Backbone view whose render method creates and caches some JavaScript objects then renders them out to SVG nodes in the DOM. So (very simplistically), I'm doing something like this:
render: function() {
// Create objects and cache them to the view...
this.someObjects = [obj1, obj2, ...];
// Render objects...
this.someObjects.forEach(function(obj) {
obj.render(); // Each object knows how to render itself...
})
return this;
}
My view events hash is returned from a function, and contains event handlers which close over certain variables which depend on the objects cached when rendering. So (again simplistically, just for illustration purposes), I'm doing something like this:
events: function() {
var getRenderedObjects = function() {
return this.someObjects;
};
var renderedObjects = getRenderedObjects();
return {
'click #someDiv .someClass' : function() {
console.log(renderedObjects);
}
}
}
The problem is that Backbone sets up the bindings for the events hash before the view initialization function runs, which is where I need to do an explicit render. So this.someObjects will be undefined. Before moving to Backbone all my event handlers were defined and bound after rendering the view, which seemed natural anyway. I have found a workaround, which is to set the events hash manually on initialization, after rendering, and then call delegateEvents() to ensure they're bound. This works, although I found that within the handlers this no longer refers to the view object, but rather to the global window object again. It all seems rather cumbersome and a bit hackish, however.
I guess I could leave renderedObjects as null and set it conditionally in the handler which requires it (so if it's not set, set it, else do nothing). That seems ugly, though. Is there a better way?
I'm still confused by this. The common paradigm I see emerge is...
view.render()
With render set to
function () { this.$el.html( this.template() ) }
How does this behavior mesh with remove? This renders the view.. but then logically you'll call .remove() to unbind the events and ditch it.. If you use that though, you've ditched the parent container ($el), and can't re-render. The docs need to be explicit then that the convention is to not this.$el.html( this.template() ), but to render the template by calling .html() on some non-el parent element. You can either call .render() using the views own $el and also use .remove().
How does these two work together?
The best practice I see is slightly different. Leave the el property off entirely -- let backbone create its own element. The render method should look like this:
render: function() {
this.$el.html(this.template(some_data_from_somewhere));
return this;
}
and then you call render from the router, thusly:
some_container.append(view.render().el)
If you do it this way, then the default remove implementation:
remove: function() {
this.$el.remove();
this.stopListening();
return this;
}
starts to make more sense. The view created and has sole control of its el so it makes perfect sense for the view to destroy the el when it is being destroyed. The view's owner creates the view and adds it to the page; then later, the view's owner destroys the view (by calling remove) when the owner is done with it. This approach makes the view nicely self-contained.
Of course, if your view is expecting to bind to an existing el or have someone give it an el when it is created, then you're going to have to provide a custom remove to unbind DOM events (by calling this.undelegateEvents() presumably), remove the contents of this.el (but not el itself), etc. Similarly, if your view has child views then you'd provide a remove that would call remove on all the children before cleaning itself up.
I don't think I fully understand your questions; but I think if you look at the sections titled:
Decouple Views from other DOM elements
and
Cleaning Up: Avoiding Memory Leaks and Ghost Views
form the following link, then you might just find the answer that you were looking for.
http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/
It was only until recently that I started adding my view to the DOM from outside of the view itself meaning I don't pass in an el: when creating the view. I just have he view render the DOM element from within the view to memory and call render().$el from the calling code to render the view just like the article states.
I am building an app using backbone.js and find myself with a view with a lot of conditional logic in the templating of the Model's View. The type property of the model is used to determine which html to render. I would like to avoid this logic if possible, because it is hard to read. There are a couple of ways that I think I could deal with this (Note that the actual templates I've got here are very much simplified):
1. Conditional Logic in Collection View rather than in Model View - Multiple Subviews
I could put the conditional logic that acts on each model's type into the Collection View:
var CollectionView = Backbone.View.extend({
....
render: function() {
this.collection.each(function(thing) {
if(thing.get("type") === "wotsit") {
this.$el.append(new WotsitView({ model: thing });
} else if(thing.get("type") === "oojamaflip") {
this.$el.append(new OojamaflipView({ model: thing });
}
}, this);
},
....
}
Pros
This way I could have each subview with a template method that had no logic in it, but rather just builds html.
var WotsitView = new Backbone.View.extend({
....
template: _.template('<h2>{{ title }}</h2>');
});
var OojamaflipView = new Backbone.View.extend({
....
template: _.template('<h3>{{ title }}</h3>');
});
Cons
The thing is, the things in the collection are all very similar. The events for each thing are likely to be the same or very similar and I can see there being a lot of code duplication. I really only want the actual template for these subviews to be different with everything else the same.
2. Conditional Logic in Model View - Multiple Template Methods
var ModelView = Backbone.View.extend({
....
render: function() {
if(this.model.get("type") ==== "wotsit") {
this.$el.html(this.wotsitTemplate(this.model.attributes));
} else if(this.model.get("type") === "oojamaflip") {
this.$el.html(this.oojamaflipTemplate(this.model.attributes));
}
},
wotsitTemplate: _.template('<h2>{{ title }}</h2>'),
oojamaflipTemplate: _.template('<h3>{{ title }}</h3>')
});
Pros
There is only one view for the model.
All of the events etc are handles in one view rather that being duplicated.
Cons
I actually quite like this way, but I would be very interested to hear some other peoples options on it.
Option #1 = polymorphism
Option #2 = switch statement
Switch statements are useful if you never (or rarely) have to add new types, but you might want to add new methods. Imagine you want to add a validate method to ModelView, which checks view input for that kind of model for correctness and reports the results to the user. With option #2, you'd just add one new method that switches on the model type (just like the render method) to handle validation.
Now let's assume we already have a render method and a validate method, and we want to handle a new type of model, a thingamajig. With option #2, you'd have to add logic to both render and validate. Now imagine we don't have just 2 methods, but 10 — option #2 gets real complicated real quick when you have to handle new types. But if we followed option #1, then no matter how many methods there were, we'd only have to create a new view in one place, and update CollectionView to map the new type to the new view.
Most of the time polymorphism (option #1) is the clean way to go. It lets you separate all logic specific to a given type and put it one place, making it easy to handle new types.
Keeping DRY
If you're worried with option #1 that you'll end up with a lot of duplicate code between all the views, don't forget that it's easy to make Views that inherit from other Backbone Views:
var ItemView = Backbone.View.extend({
initialize: function() {
/* some common logic for all views */
}
});
var WotsitView = ItemView.extend({
template: _.template('<h2>{{ title }}</h2>')
});
var OojamaflipView = ItemView.extend({
template: _.template('<h3>{{ title }}</h3>')
});
What is more efficient, using events to communicate between nested Views, or keeping a reference around to call a method on. The following example shows two Views. The outer View responds to a click event, and could then use either an event, or method call to get the InnerView to respond appropriately.
InnerView = Backbone.View.extend({
initialize: function() {
this.model.bind('doSomethingEvent', this.doSomething);
},
doSomething: function() {
// This could have been called from event/trigger
// or from direction method invocation using reference.
}
});
OuterView = Backbone.View.extend({
events = {
'click' : 'handleOutViewClick'
},
render: function() {
// Create InnerView to render some model
var innerView = new InnerView({model:this.model });
$(this.el).append(innerView.render().el);
// Could store a reference to the View?
this.viewRef = innerView;
},
handleOutViewClick: function(e) {
// Should this function use a reference to the InnerView instance:
this.viewRef.doSomething();
// Or should it trigger an event on this.model that
// the InnerView is bound to?
this.someCollection.trigger('doSomethingEvent');
}
});
Probably a single method call is going to be more efficient than an event dispatch, which will involve at least 2 method calls. But I don't think you need to be concerned about which is more "efficient" here technically. Unless this is happening many times a second, you can concern yourself only with what makes for cleaner, more correct code. I think the cleanest pattern depends on the details being communicated. Here's my preference:
If it is a natural fit for outerview to manipulate models and collections, and have innerview respond via the normal backbone model/collection events, that is the cleanest.
If what's happening isn't really about the models, consider a "View Model" pattern where you model the interesting bits of state of the view as if it was a back end model, even though you have no intention of ever having that model interact with the server. Then bind your two views to events coming off the view model and have them coordinate by altering a common "view model" instance. This is a pattern I use for complicated views with lots of interdependent state not directly associated with the underlying models from the server.
If what's happening doesn't really change the models/collections and is more of a view-specific thing, a direct method dispatch will be more straightforward but also more tightly coupled. It's up to your judgement to decide when the loose coupling afforded by event dispatch merits the extra complexity and harder-to-follow control flow.
I'm just getting started with Backbone. I went through the first two PeepCode screencasts which were great and now I'm digging in on a quick detached (no server side) mock-up of a future app.
Here's what I'm looking to build (roughly). A series of five text boxes - lets call these Widgets. Each Widget input, when selected, will display a pane that shows Tasks associated with the Widget and allow the user to create a new Task or destroy existing Tasks.
At this point, I'm thinking I have the following models:
Widget
Task
The following collections:
Tasks
Widgets
The following views (this is where it gets hairy!)
WidgetListView
- Presents a collection of Widgets
WidgetView
- sub-view of WidgetListView to render a specific Widget
TaskPaneView
- Presented when the user selects a Widget input
TaskCreateView
- Ability to create a new Task associated with selected Widget
TaskListView
- Presents a collection of Tasks for the given widget
TaskView
- Displays Task detail - sub-view of TaskListView
Assuming that's reasonable, the trick becomes how to display a TaskPaneView when a WidgetView is selected. And futhermore, how that TaskPaneView should in turn render TaskCreateViews and TaskListViews.
The real question here is: Does one cascade render events across Views? Is it permissible for a Root view to know of sub-views and render them explicitly? Should this be event-driven?
Apologies if this is an open-ended question, just hoping someone will have seen something similar before and be able to point me in the right direction.
Thanks!
Definitely make it event driven. Also, try not to create views that are closely coupled. Loose coupling will make your code more maintainable as well as flexible.
Check out this post on the event aggregator model and backbone:
http://lostechies.com/derickbailey/2011/07/19/references-routing-and-the-event-aggregator-coordinating-views-in-backbone-js/
The short version is you can do this:
var vent = _.extend({}, Backbone.Events);
and use vent.trigger and vent.bind to control your app.
Pre p.s.: I have made a gist for you with the code that I wrote below:
https://gist.github.com/2863979
I agree with the pub/sub ('Observer pattern') that is suggested by the other answers. I would however also use the power of Require.js along with Backbone.
Addy Osmani has written a few GREAT! resources about Javascript design patterns and about building Backbone applications:
http://addyosmani.com/resources/essentialjsdesignpatterns/book/
http://addyosmani.com/writing-modular-js/
http://addyosmani.github.com/backbone-fundamentals/
The cool thing about using AMD (implemented in Require.js) along with Backbone is that you solve a few problems that you'd normally have.
Normally you will define classes and store these in some sort of namespaced way, e.g.:
MyApp.controllers.Tasks = Backbone.Controller.extend({})
This is fine, as long as you define most things in one file, when you start adding more and more different files to the mix it gets less robust and you have to start paying attention to how you load in different files, controllers\Tasks.js after models\Task.js etc. You could of course compile all the files in proper order, etc, but it is far from perfect.
On top of this, the problem with the non AMD way is that you have to nest Views inside of each other more tightly. Lets say:
MyApp.classes.views.TaskList = Backbone.View.extend({
// do stuff
});
MyApp.views.App = Backbone.View.extend({
el: '#app',
initialize: function(){
_.bindAll(this, 'render');
this.task_list = new MyApp.classes.views.TaskList();
},
render: function(){
this.task_list.render();
}
});
window.app = new MyApp.views.App();
All good and well, but this can become a nightmare.
With AMD you can define a module and give it a few dependencies, if you are interested in how this works read the above links, but the above example would look like this:
// file: views/TaskList.js
define([], function(){
var TaskList = Backbone.View.extend({
//do stuff
});
return new TaskList();
});
// file: views/App.js
define(['views/TaskList'], function(TaskListView){
var App = Backbone.View.extend({
el: '#app',
initialize: function(){
_.bindAll(this, 'render');
},
render: function(){
TaskListView.render();
}
});
return new App();
});
// called in index.html
Require(['views/App'], function(AppView){
window.app = AppView;
});
Notice that in the case of views you'd return instances, I do this for collections too, but for models I'd return classes:
// file: models/Task.js
define([], function(){
var Task = Backbone.Model.extend({
//do stuff
});
return Task;
});
This may seem a bit much at first, and people may think 'wow this is overkill'. But the true power becomes clear when you have to use the same objects in many different modules, for example collections:
// file: models/Task.js
define([], function(){
var Task = Backbone.Model.extend({
//do stuff
});
return Task;
});
// file: collections/Tasks.js
define(['models/Task'], function(TaskModel){
var Tasks = Backbone.Collection.extend({
model: TaskModel
});
return new Tasks();
});
// file: views/TaskList.js
define(['collections/Tasks'], function(Tasks){
var TaskList = Backbone.View.extend({
render: function(){
_.each(Tasks.models, function(task, index){
// do something with each task
});
}
});
return new TaskList();
});
// file: views/statistics.js
define(['collections/Tasks'], function(Tasks){
var TaskStats = Backbone.View.extend({
el: document.createElement('div'),
// Note that you'd have this function in your collection normally (demo)
getStats: function(){
totals = {
all: Tasks.models.length
done: _.filter(Tasks, function(task){ return task.get('done'); });
};
return totals;
},
render: function(){
var stats = this.getStats();
// do something in a view with the stats.
}
});
return new TaskStats();
});
Note that the 'Tasks' object is exactly the same in both views, so the same models, state, etc. This is a lot nicer than having to create instances of the Tasks collection at one point and then reference it through the whole application all the time.
At least for me using Require.js with Backbone has taken away a gigantic piece of the puzzling with where to instantiate what. Using modules for this is very very helpful. I hope this is applicable to your question as well.
p.s. please also note that you'd include Backbone, Underscore and jQuery as modules to your app too, although you don't have to, you can just load them in using the normal script tags.
For something with this kind of complexity, I might recommend using Backbone Aura, which has not yet had a stable release version. Aura essentially allows you to have multiple fully self-contained Backbone apps, called "widgets," running on a single page, which might help disentangle and smooth over some of your model/view logic.
From a classical MVC perspective, your views respond to changes in their associated models.
//initialize for view
initialize : function() {
this.model.on("change", this.render(), this);
}
The idea here is that anytime a view's model is changed, it'll render itself.
Alternatively or additionally, if you change something on a view, you can trigger an event that the controller listens to. If the controller also created the other models, it can modify them in some meaningful way, then if you're listening for changes to the models the views will change as well.
Similar answer to Chris Biscardi's. Here's what I have:
You create a global var Dispatcher (doesn't have to be global as long as it can be accessed from the scope of Backbone app):
Dispatcher = _.extend({}, Backbone.Events);
Dispatcher will help you execute subscribed callbacks with events that are not particularly triggered by changes in models or collections. And cool thing is that Dispatcher can execute any function, inside or outside Backbone app.
You subscribe to events using bind() in a view or any part of the app:
Dispatcher.bind('editor_keypress', this.validate_summary);
Then in another view or part of the app you trigger new event using trigger():
Dispatcher.trigger('redactor_keypress');
The beauty of using a dispatcher is its simplicity and ability to subscribe multiple listeners (e.g. callbacks in different Backbone views) to the same event.