7

I've been doing a bunch of reading about nested views in backbone.js and I understand a good amount of it, but one thing that is still puzzling me is this...

If my application has a shell view that contains sub-views like page navigation, a footer, etc. that don't change in the course of using the application, do I need to render the shell for every route or do I do some kind of checking in the view to see if it already exists?

It would seem so to me if someone didn't hit the "home" route before moving forward in the app.

I haven't found anything helpful about this in my googling, so any advice is appreciated.

Thanks!

Charles
  • 309
  • 1
  • 16
  • 1
    Charles, have a look at [backbone.marionette](https://github.com/marionettejs/backbone.marionette) before you spend too much time reinventing the wheel. It may or may not meet your needs, but I wish I had known about it when I started my nested views project :) – Kato Mar 07 '13 at 20:27

1 Answers1

16

Since your "shell" or "layout" view never changes, you should render it upon application startup (before triggering any routes), and render further views into the layout view.

Let's say your layout looked something like this:

<body>
  <section id="layout">
    <section id="header"></section>
    <section id="container"></section>
    <section id="footer"></section>
  </section>
</body>

Your layout view might look something like this:

var LayoutView = Backbone.View.extend({
  el:"#layout",
  render: function() {
    this.$("#header").html((this.header = new HeaderView()).render().el);
    this.$("#footer").html((this.footer = new FooterView()).render().el);
    return this;
  },

  renderChild: function(view) {
    if(this.child) 
      this.child.remove();
    this.$("#container").html((this.child = view).render().el); 
  }
});

You would then setup the layout upon application startup:

var layout = new LayoutView().render();
var router = new AppRouter({layout:layout});
Backbone.history.start();

And in your router code:

var AppRouter = Backbone.Router.extend({
  initialize: function(options) {
    this.layout = options.layout;
  },

  home: function() {
    this.layout.renderChild(new HomeView());
  },

  other: function() {
    this.layout.renderChild(new OtherView());
  }
});

There are a number of ways to skin this particular cat, but this is the way I usually handle it. This gives you a single point of control (renderChild) for rendering your "top-level" views, and ensures the the previous element is removed before new one is rendered. This might also come in handy if you ever need to change the way views are rendered.

jevakallio
  • 35,324
  • 3
  • 105
  • 112
  • Sometimes it's the most obvious solutions that are the ones hardest to see! Thank you for shining some light on this. Very nice. – Charles Mar 07 '13 at 19:33
  • fincliff in your example, is the .el on the end of this line required? `this.$("#container").html((this.child = view).render().el);` It's throwing an error "cannot read property 'el' of undefined" – Charles Mar 07 '13 at 20:38
  • @Charles, in order for that to work you need to `return this` from the view's `render` method - quite a common pattern. If you don't want to do that, just split the `render()` call and the `.html(view.el)` on separate lines. – jevakallio Mar 07 '13 at 20:40
  • I liked the solution too.. This is what even I wanted. Cleaner code. – Ambika Dec 03 '14 at 09:06