2
\$\begingroup\$

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


})();
\$\endgroup\$
  • \$\begingroup\$ Do you have a <div id="chapter2"> in the DOM? Are you sure you want to render both views into the same element? \$\endgroup\$ – mu is too short Oct 27 '13 at 21:43
  • \$\begingroup\$ Sorry for the wrong title. The code works. I had chosen the title earlier when it was not working and then I got it to work. When I wrote the question above I forgot to change the title. My question is that since I am so new to Backbone I wanted to know whether there are any clearly "bad approaches" that I am using. The code, however, works. \$\endgroup\$ – Curious2learn Oct 27 '13 at 22:01
  • \$\begingroup\$ @SeanVieira I was not aware of that site. Can you move the question there, or do I have to do it myself? Would appreciate instructions on how to do so, if I am supposed to do move it to that site. \$\endgroup\$ – Curious2learn Oct 28 '13 at 12:39
  • \$\begingroup\$ @Curious2learn - unfortunately, I do not have the power to do this myself - I'll flag it for moderator attention and see if they can do it. \$\endgroup\$ – Sean Vieira Oct 28 '13 at 14:35
  • 1
    \$\begingroup\$ @SeanVieira: I flagged it too. I'll try to keep an eye on it and give it a review once it has moved. \$\endgroup\$ – mu is too short Oct 28 '13 at 19:16
4
\$\begingroup\$

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, with this.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.

\$\endgroup\$
  • \$\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 Oct 29 '13 at 1:58
  • \$\begingroup\$ This is pretty much what I was going to say but I usually override remove rather than adding a close. @Curious2learn You'll still need the Backbone.history.start() call to crank up the routing machinery. \$\endgroup\$ – mu is too short Oct 29 '13 at 5:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.