Managing Layouts And Nested Views With Backbone.Marionette

I’ve received numerous questions about managing layouts and nested views in Backbone over the last few months. Until recently, though, I never had a great answer. Many of the applications that I’ve built had a lot of custom code to handle the specific needs of that application’s layout and generally avoiding deeply nested views.

All of that changed recently, though, and I found myself needing the ability to manage a larger set of views as siblings. Specifically, I needed a way to render an application layout, have it behave like a view in that I can send it over to one of my region manager objects, and have its own region managers that can show views within the layout.

Marionette.Layout

The answer that I found is something that several Marionette users had suggested and tried on their own: render the layout as a view, directly, and then create some regions from the generated HTML of that view. After trying this out on one of my projects, I quickly realized that this was the answer I needed so I put it together formally, in the Marionette.Layout object.

The core of Layout is an ItemView + Regions. In fact, that’s exactly what the Layout is… it extends from Marionette’s ItemView and it adds the ability to specify a set of regions that will be instantiated with the Layout.

MyLayout = Backbone.Marionette.Layout.extend({
  template: "#layout-template",

  regions: {
    myRegion: "#some-div",
    anotherRegion: ".another-element"
  }
});

When you create a new instance of MyLayout, you’ll get access to “myRegion” and “anotherRegion” immediately. They won’t really be able to do anything until you call the layout’s “render” method, though. Once the layout has been rendered, you can call the “show” method on the regions to show whatever content you want in the region.

var myLayout = new MyLayout();
MyApp.someRegion.show(myLayout);

// somewhere else in the code
myLayout.myRegion.show(anotherView);
myLayout.anotherRegion.show(moreView);

Nested Views And Layouts

One of the benefits of using an ItemView as the base object for my Layout is that I can send the Layout to an existing Region’s “show” method. This means I can nest a layout within an existing part of the application – whether it’s hard coded HTML, another ItemView, or even another Layout. Anywhere that a Backbone.View can be used, a Layout can be put in it’s place, allowing a nearly unlimited nesting structure (limited by a JavaScript runtime engine and it’s stack size, memory usage, etc).

In addition, if you’re using Marionette’s various view types (ItemView, CollectionView, CompositeView and Layout) in combination with Regions, all of the cleanup that is needed, is built right in to the views. When you close a view by either calling `.close()` on it, or by replacing it within a region using the region’s `.show()` method, the view and it’s children will be closed and cleaned up. This means you don’t have to worry quite as much about memory leaks and other issues, though you still have to make sure you de-reference the view entirely.

Layout vs Application

A client recently asked me why I had both a Layout and an Application object in Marionette. He specifically wanted to know what the difference was and when to use which. The core difference comes down to semantics, but there are some technical differences as well.

A Layout has a visual representation. It renders a template and it can handle DOM events and update the DOM when needed. An Application on the other hand has no visual representation. It’s a light-weight object that provides some key functionality for an application to get up and running in JavaScript, including the standard use as a namespace object. Both Layout and Application provide a `.vent` attribute as an event aggregator, though. In that, they are similar. The last of the major difference, though, is that an Application object has initializers. You can register initializer callbacks that are fired when the application’s `start()` method is called. There are no initializers with a Layout, though you could get some of the same effect using events that the Layout triggers.

An Example: BBCloneMail

I’ve updated my BBCloneMail application to use a Layout, specifically to show what it can do. The new version of this example app renders the entire page as one Layout object, and includes the app switching drop down list as part of the layout. Now when you switch between Mail and Contacts in the app, it’s the Layout for the over-all app that handles this.

  // from BBCloneMail.Layout.js

  var Layout = Backbone.Marionette.Layout.extend({
    template: "#layout-template",

    // These are my visual regions: the "navigation" or
    // left hand list of categories, and the "main"
    // content area where the email list or contact list
    // is displayed.
    regions: {
      navigation: "#navigation",
      main: "#main"
    },

    // more stuff here, including DOM event handlers
  });

There is one small oddity in this, though. Since I’m lazy-loading templates from the server – including the layout template – the call to start Backbone.history has to be delayed until after the layout is rendered. If I don’t put this delay in place, then the mail and categories won’t show up after they are rendered. To fix this issue, I moved the `Backbone.history.start()` in to the BBCloneMail.Layout file and took advantage of the view “render” method returning a jQuery deferred object.

  BBCloneMail.addInitializer(function(){
    
    // Render the layout and get it on the screen, first
    BBCloneMail.layout = new Layout();
    var layoutRender = BBCloneMail.layout.render()
    $("body").prepend(BBCloneMail.layout.el);

    // This kicks off the rest of the app, through the router
    layoutRender.done(function(){
      Backbone.history.start();
    });

  });

When this deferred object is “done”, I know it’s been rendered. That means I can start the history object and kick off my routers, which in term render the mail and categories on to the screen.

Some Better Examples?

I know there are some better use cases for a Layout than what I’ve shown, here. In fact, I’ve used it for better purposes already – but on a few projects that I can’t talk about (NDA and all that). One of the ways that I have thought about using a Layout, though, is to represent a sub-application. For example, in a tab-oriented application, each open tab may represent a separate sub-application and have it’s own layout needs. A Layout object would be a great way to render the tab’s contents and allow different parts of the tab’s contents to be swapped out as needed.

And I’m sure there are other scenarios where a Layout will come in handy, as well. This feature of Marionette has been requested numerous times, so I know people are working with applications that need this functionality. I also feel like this finally rounds out the majority of the functionality that Marionette really needed, to be a composite application framework. There’s still more to come, though. I’m constantly learning and finding new patterns that I want to incorporate in to Marionette. One of these days, though, I’ll let it hit v1.0.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, Composite Apps, Javascript, Marionette. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Anonymous

    Awesome post!
    But what if my layout is already on the DOM? And I just want to use it without calling layout.render()?

  • Mikael

    I have 2 layouts, one for my App, and another for my SubApp. In the SubApp I have a region called “list”. When I do the following:

    App.layout.main.show(App.SubApp.layout); 
    App.SubApp.layout.list.show(new MyCollectionView());

    The collectionView is never displayed.  The SubApp.layout template is rendred to to the main region and I can see in the debugger that each ItemView are rendred.
    If I instead show the collectionView on the main region it works. 

    I based my application on your BBCloneMain, so I load the templates using the same method and have replaced _.templates with jquery-tmpl.
    Any ideas?

    • http://mutedsolutions.com Derick Bailey

      off-hand, it’s hard to say… i have some ideas, but none of them really make sense based on what you’ve said. it may be a timing issues, it may be that the jquery selector you specified for the subapp.layout.list is incorrect, or it could be a number of other things. can you post a link to a JSFiddle that reproduces the problem?

  • Vadim

    Everything id useful! But the only class I havnt realize how to use – CompositeView.

    Could you make simple example to build usual treeview with it?
    Thanks

  • Mickey Vashchinsky

    Hi!

    I am learning Backbone and trying to use Marionette.

    The problem I encounter is as follows:

    I have a view, render it – it’s events are behaving correctly. But when I re-render it – the events are gone. Sometimes undelegateEvents() and delegateEvents() in the render() helps, but not always.

    With Marionette I am having the same problem.

    Here is my test: http://jsfiddle.net/mickeyvip/bbPKH/

    Try clicking on sidebar’s button, then header’s to toggle the sidebar. When sidebar is re rendered – the click event is not fired anymore.

    Am I doing something wrong? Or don’t I understand correctly what is going on?

    Appreciate your help!

    • http://mutedsolutions.com Derick Bailey
      • Mickey Vashchinsky

        Thank you, Derick!!!

        This post made an order in my mess.

        But, as I understand, Marionette does not support view reuse?

        For my test I added “hide()” methods to Marionette .Region, Marionette .ItemView and Marionette .View, that basically are copies of “remove()” method, that use “hide” instead of “remove”, and the one in Marionette.View calls “this.$el.detach();” instead of “this.remove();”

        This seems to do the trick, but I’m almost sure there are more changes needed in order for all views/regions/layouts to support view reuse.

        Do you think Marionette can be updated to support view reuse?

        Or, maybe there is a different solution? (like flag that can be passed to “remove(preserveViewData)” method?

        Thanks again!

        • http://learningthehardway.net Robert Levy

          not sure if its the best solution, but i found that adding an onRender method to my itemview and having it call this.delagateEvents() did the trick

  • http://learningthehardway.net Robert Levy

    i stumbled on this for quite a while… it seems that you have to show(layout) before layout.region.show(view), otherwise nothing appears.  by design?

    • http://mutedsolutions.com Derick Bailey

      you have to call .render on the layout before the regions are available, at this point. that can be changed, though, and probably should be. the only caveat to changing it is that the view’s `el` must be populated with the elements that the regions are looking for before you try to use the regions.

      • http://learningthehardway.net Robert Levy

        yeah, the .render thing I understood and was documented so that’s cool.  The .show order is what threw me off… I was trying to call .show on the layout regions before .showing the layout itself because I assumed this would yield the best performance – this doesn’t work at all though.

        • http://mutedsolutions.com Derick Bailey

          oh… that’s not right. it should work without having to show the layout first. i’ve logged a bug for this in the github issues and will try to get to it (and others) soon.

    • anthonysessa

      Very nice.

  • Bjarni Thorisson

    Hi, thanks for super useful posts. Whenever your site comes up when googling for backbone answers I know it’s a hit.

    One question regarding this post: It seems the layout in bbclonemail has changed since you wrote this post. What are the reasons?

    • http://mutedsolutions.com Derick Bailey

      BBCloneMail changes on a regular basis – as new ideas, new features and new implementation details make their way in to Marionette. I also tend to use it as a playground for what the future of Marionette will bring. At this time, BBCloneMail is rather outdated, though. I haven’t had a chance to go back and clean up some of the things that I know are odd / not the way I want them.

      • Bjarni Thorisson

        Could you expand a bit on what part of BBCloneMail is outdated?
        Are there any techniques you are using there that should be replaced by other methods?
        Things seem to be changing very fast in Marionette..
        Thanks

  • http://twitter.com/chiborg Gabriel Birke

    What’s the best/common approach to show another view in a region when an event occurs in a view handler? I could either trigger an event in the event handler of the view (the triggered event will be handled by the layout) or insert the layout object as an option for the view. Any comments on the approaches?

    • http://mutedsolutions.com Derick Bailey

      I prefer the events so that my child view (the one going in to the region) doesn’t have to know about the parent or anything else outside of itself. It keeps things more flexible, in my experience.

  • ed

    Hi there – I’ve been using compositeView to create nested views – a model + a collection. Is it possible to nest composite views inside each other?

    I have an composite view, ‘catchup_videos’, its itemView is a ‘catchup_view’. I want to make another composite view that re-uses the ‘catchup_videos’ view as an Item. Basically to create 5 groups of ‘catchup_videos’ – does anybody know where i can see an explanation of this…if its possible?

  • James

    Maybe I skipped over something but I’m still left wondering why we have the option to define regions within marionette.application and a layout object. Is it just a matter of preference/convenience or are there some more concrete reasons as to why you might choose defining regions in one over the other?

  • waterwu

    Why not just add initializer features to layouts?
    Still confused whether to use Layout or Application, it seems that Layout is a more featured version of Application except the initializer.
    And if I uses both, how should I use it? It seems Layout probably have a more complex initialization to do, and an App is useless for most of works.