I am thinking of writing some online notes/book. Amongst other things, I want to use Backbone to show different sections within a chapter as separate views. In other words, I want each chapter to behave like a single page application; different chapters will behave as separate single page applications. Within a chapter, as the user navigates from one section to another, I don't want the page to reload and am thinking of using the Backbone router to show these section views.
Please take a quick look at the code below and let me know if you see anything problematic with how I am using Backbone routing to render views.
The code works, but I want to know if I am doing anything inefficiently and if there are any "good practice" principles that I am violating. For example, I create new view instance every time the route changes. Is there a way to do this better?
(function() {
var bApp = {
model: {},
view: {},
collection: {},
router: {}
};
window.bApp = bApp;
bApp.view.section = Backbone.View.extend({
el: 'div#chapter2',
template: nunjucks.render('./client/views/client-templates/ch2_sec2.html'),
render: function() {
this.$el.html(this.template);
return this;
}
});
bApp.router = Backbone.Router.extend({
routes: {
'': 'showroute_ch2sec1',
'ch2sec1': 'showroute_ch2sec1',
'ch2sec2': 'showroute_ch2sec2'
},
showroute_ch2sec1: function() {
section1 = new bApp.view.section;
section1.template = nunjucks.render('./client/views/client-templates/ch2_sec1.html');
section1.render();
},
showroute_ch2sec2: function() {
section2 = new bApp.view.section;
section2.template = nunjucks.render('./client/views/client-templates/ch2_sec2.html');
section2.render();
}
});
var r = new bApp.router;
Backbone.history.start();
})();
1 Answer 1
A few minor tweaks could help you out:
- Add a
close
method to all of your views, which cleans up your DOM, and unbinds any bound events (eg, withthis.stopListening()
). This will prevent 'zombie events' -- events bound to views which are no longer rendered. - Move the logic for switching pages to a high-level application contoller. This will keep your router from getting too logic-heavy, and will allow the application controller to handle view creation and cleanup.
Here's how I might approach it.
// If all of your section views behave similarly
// why not create a single base class
bApp.view.Section = Backbone.View.extend({
initialize: function(options) {
this.template = options.template;
},
render: function() {
var html = nunjucks.render(this.template);
this.$el.html(html);
return this;
},
close: function() {
this.$el.empty();
this.$el.off();
this.stopListening();
// Any other cleanup can go here...
}
});
bApp.Router = Backbone.Route.extend({
routes: {
// Use route parameters, to simplify routing
'book/:ch/:sec': 'navigateToSection'
},
navigateToSection: function(ch, sec) {
// Delegate view-switching logic to application
bApp.show(ch, sec);
}
});
bApp.router = new bApp.Router();
// Give the bApp controller
// power over switching views
bApp.show = function(ch, sec) {
var templatePath = './client/views/client-templates/ch' + ch + '_sec' + sec + '.html';
if (this.currentView) {
// Clean up your old view
this.currentView.close();
}
// render your new view
this.currentView = new bApp.view.Section({
el: 'div#chapter' + ch,
template: templatePath
});
this.currentView.render()
// Update the route
// This may seem redundant, but this will allow
// you to call bApp.show() directly, and keep your
// route up to date.
bApp.route.navigate('book/' + ch + '/' + sec);
}
Here's a good article by Derick Bailey about common pitfalls with Backbone routers.
I hope this is helpful!
Edit: Clarify App/Router Separation
Think of the router just as one of many ways to change the state of your application. Just like you could click a "first page" button to go to the first page, you could enter in /page/first
in your browser to go to the first page. The only difference is whether your application state is bound to a button, or to a route.
This is the reason to keep the logic that changes the application state (ie. changing the rendered page) out of your router.
Consider this:
bApp.view.Section = Backbone.View.extend({
events: {
'change input.ch]': this.handlePageChange_
'change input.sec]': this.handlePageChange_
}
//...
handlePageChange_ = function() {
var ch = this.$('input.ch').val();
var sec = this.$('input.sec').val();
// Let your application handle state change
bApp.show(ch, sec)
}
});
In this example, you've bound a input element to your application state, just as you bound a route to your application state. Either way, the state-changing logic belongs in your application controller.
-
\$\begingroup\$ edan, thanks a lot. This is really helpful. Being new to backbone there are a few things I need to figure out, but overall I understand what you are doing. The only part that is not clear is what you mean when you say that I will be able to call bApp.show() directly and about route being up to date. Is the explicit call to route.navigate an alternative to Backbone.history.start()? \$\endgroup\$Curious2learn– Curious2learn2013年10月29日 01:58:19 +00:00Commented Oct 29, 2013 at 1:58
-
\$\begingroup\$ This is pretty much what I was going to say but I usually override
remove
rather than adding aclose
. @Curious2learn You'll still need theBackbone.history.start()
call to crank up the routing machinery. \$\endgroup\$mu is too short– mu is too short2013年10月29日 05:44:14 +00:00Commented Oct 29, 2013 at 5:44
<div id="chapter2">
in the DOM? Are you sure you want to render both views into the same element? \$\endgroup\$