Composite Views: Tree Structures, Tables, And More

One of the more recent features that I added to my Backbone.Marionette framework is the CompositeView. It’s actually been in the code for a while now, but in a recent version I extracted it out of the CollectionView and in to it’s own object type directly. This helped to give the functionality better exposure and helped to clean things up quite a bit.

Composite Structures

The CompositeView combines both the ItemView and the CollectionView in to one convenient structure. But what does that really mean? To understand this, you have to step back to the core design patterns outline by the GoF, and look at the Composite structure pattern.

The basic idea behind this pattern is most easily understood as a tree structure where every “leaf” – or end point – may also a “branch” – or collection of leaves and branches. We’re all familiar with this, whether or not we realize it:

Screen Shot 2012 03 23 at 4 41 06 PM

The basic folder structure of your computer is a composite structure. Every folder is itself a leaf and a branch. That is, a folder can be the end of the line with nothing further, or it can be a container for other leaves and branches (files and folders).

TreeViews: Recursive / Hierarchical View Structures

The core idea behind the CompositeView in Marionette is that it represents a visualization of a composite structure. The folder view in a tree structure can easily be rendered with a nested hierarchy of CompositeView and ItemView combinations.

For example:

// The recursive tree view
var TreeView = Backbone.Marionette.CompositeView.extend({
    template: "#node-template",
    
    tagName: "ul",
    
    initialize: function(){
        // grab the child collection from the parent model
        // so that we can render the collection as children
        // of this parent node
        this.collection = this.model.nodes;
    },
    
    appendHtml: function(collectionView, itemView){
        // ensure we nest the child list inside of 
        // the current list item
        collectionView.$("li:first").append(itemView.el);
    }
});

// The tree's root: a simple collection view that renders 
// a recursive tree structure for each item in the collection
var TreeRoot = Backbone.Marionette.CollectionView.extend({
    itemView: TreeView
});

In this example, I’ve defined a CompositeView called `TreeView`. By default, a CompositeView is recursive. For every item in the collection that the composite view is handed, the same CompositeView type will be used to render the item. Of course you can override the item view by specifying an `itemView` attribute. In this case, though, we want the recursive structure.

This recursive reference structure will allow the CompositeView to render a tree structure from top to bottom. The only real limitation in the structure’s depth will be the call stack limitations of your JavaScript runtime.

The other view that I’ve defined is the TreeRoot, which is based on a CollectionView. A CompositeView and CollectionView are very similar in functionality. The difference is that a CompositeView renders a single model and template as a wrapper around the collection, while the CollectionView only renders the collection. In this case, I’m using a CollectionView as the tree’s root because the top level of the tree structure is a collection of nodes. I don’t want any wrapper HTML structure rendered around the top level of the collection – I just want the collection to render.

For the TreeRoot, note that I am specifying an `itemView` and I have it set to the `TreeView` type. This means that every item in the top level of the collection will render as a TreeView type, and since the TreeView type is a recursive composite view, the entire tree structure will be rendered.

Here’s the TreeView example running in a JSFiddle:

But there are more uses for the CompositeView than just nested hierarchies. In fact, I rarely use CompositeView for this purpose, because I rarely need nested hierarchies. The most common use case that I have for the CompositeView is a simple collection rendered within an outer template.

UPDATE: Chris Hoffman pointed out, in the comments, that this version of the code renders a <ul> tag around every <li> in the result. I did some digging and managed to create a version that doesn’t have this problem. But my version left an empty <ul></ul> at the bottom of every branch of the tree. Chris then created a version that looks for empty <ul> tags and removes them.

Grid Views: Collections With Wrapper Templates

It seems fairly common, at least in my applications, to need an area of your application rendered with some information that contains a collection or list. For the most basic implementations, a CollectionView will work fine. But sometimes you need to have more than just the list of items. Sometimes you need the extra wrapper around the list.

For example, you might have a list of users that you want to put in a table or grid. The easiest way to do this is to use a CompositeView where the ‘itemView’ is a row, and the CompositeView is the table / grid itself:

// A Grid Row
var GridRow = Backbone.Marionette.ItemView.extend({
    template: "#row-template",
    tagName: "tr"
});

// The grid view
var GridView = Backbone.Marionette.CompositeView.extend({
    tagName: "table",
    template: "#grid-template",
    itemView: GridRow,
    
    appendHtml: function(collectionView, itemView){
        collectionView.$("tbody").append(itemView.el);
    }
});

In this example, the ‘template’ that I defined on the CompositeView directly contains the raw <thead> and <tbody> definitions. This puts the basic table structure in place. The ‘GridRow’, then, has a template of ‘row-template’ and renders with a ‘tagName’ of “tr”. This produces the needed “<tr>” tag with the “<td>” tags from the template getting stuffed in to the table row.

The last thing to note is the “appendHtml” method in the CompositeView definition. By default, the appendHtml method will do what the name suggests: append the HTML for each item to the end of the template from the composite view. By overriding this method with my own implementation for this structure, though, I can make sure that I am stuffing the rendered HTML for each item / row in to the table’s “<tbody>” tag, and end up with the table structure that I need.

Here’s what that looks like, running in a JSFiddle:

A Drop-Down Menu

A Marionette user created a drop-down menu structure with the CompositeView. I love this example so I wanted to share the JSFiddle along with the others that I built.

An Accordion View

Another Marionette user has created an accordion view in this JSFiddle:

I particularly like the way he separated the base accordion widget implementation from the specific instance being displayed.

A Model For The Composite Wrapper

You can specify a ‘model’ for a CompositeView to render, as well. When the CompositeView is rendering the ‘template’ that you specify, the ‘model’ that you hand to the composite view will be used as the data source for the template. This means you can create a parent / children relationship with data from both the parent and children showing up.

Events For The Composite Wrapper

DOM event can also be handled in the composite view’s wrapper. Specify the standard “events” declaration in the composite view definition and you’re good to go. Just keep in mind that your composite view may pick up events from the nested children if your selectors are not written carefully. This can be used to your advantage at times, but can also cause problems if you’re not careful.

One View, Many Uses

There are more uses for the CompositeView, still. When you start combining this view with other view types, and then throw in the idea of Regions and Layouts to manage the display of views, you can quickly see how this becomes a very flexible tool to use. Of course, it’s not the only tool you should use. There certainly are scenarios where it’s not the right choice, but that’s why I have so many available view types in Marionette, and why I support any object that extends from Backbone.View with my Region objects.


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, Design Patterns, Javascript, JQuery, JSFiddle, Marionette. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Vadim

    Thanks Derick !
    It’s great!

  • Chris Hoffman

    Do you realize that the tree you construct as your first example has a ul wrapped around every single li (even siblings)?

    • http://mutedsolutions.com Derick Bailey

      #facepalm oops :( i’ll see if i can get that fixed :P

  • Chris Hoffman

    Could you explain why you use
    this.unset(“nodes”); 
    after setting
    this.nodes
    to a TreeNodeCollection 

    • http://mutedsolutions.com Derick Bailey

      it has to do with the data that i’m stuffing in to the models, and the hierarchy that i’m creating. I don’t want to keep the child nodes in a given model’s “attributes”. i prefer to keep the child objects as direct properties on the model object instead of in teh attributes. so, after i create the collection from the child data, i remove the child data from the attributes of the model. this keeps me from having duplicate data to deal with and keeps my objects cleaner.

  • http://www.facebook.com/anderson.m.joshua Joshua Anderson

    Well thought out; backbone.js doesn’t consider relationships inside items.  This is great, that you have thought this thoroughly. Thank you Derek for your time.

  • Jacob Kristhammar

    How would you use composite view when some of the elements in the collection might need different views (like the Finder.app picture you provided with different icons etc.). I.e. you only want the to recursively render non-leaf nodes and use special views for the leaves?

  • Nick Gottlieb

    I’m having some trouble figuring out how to finagle my event selectors to be specific to the item in the tree. I’ve got a tree very similar to your’s, with some text for each entry, and a X attached to it.

    I can’t figure out how to put the selector in context — so, all the things I’ve tried so far have made the event fire for the top level nodes, but for each under that, it fires for the one clicked and every parent node up to the root.

    I’m hoping there’s a way I can compose the selector so that I don’t need to give the delete button (or any other action buttons) a unique identifier, but I have a hunch that might be the only solution. Thoughts?

    Aside from that, thanks a lot for this tutorial and Marionette. I was trying to build the tree with straight Backbone for quite a while and boy was it a pain. Took me about an hour once I found this.

    • http://mutedsolutions.com Derick Bailey

      Hi Nick – can you post this question with code and HTML samples of your issue, over at http://stackoverflow.com? Answering questions that need code samples is difficult on blog comments.

    • Pesta

      Try event.stopPropagation

  • NICEFINLY

    Hi Derick – Could you please take a look at this issue on stack? I’ve been struggling with it for days and have looked at all related stack questions, as well as your blog posts already. I suspect I’ve overlooked something extremely minor that others may easily catch.

    http://stackoverflow.com/questions/13633780/backbone-marionette-collection-within-compositeview-which-itself-is-nested-in

  • Dmitri Zaitsev

    I am learning Backbone Marionette and have some very naive questions about the definition of TreeNode in this example:

    1. The “nodes” property is obtained indirectly via this.get(“nodes”) but is set via “this.nodes = …” instead of this.set(“nodes”, …) which seems to be recommended by the Backbone. Is there any reason behind?

    2. The setting “this.nodes = …” seems to override the reference to the array “nodes”, so the reference seems to be lost but the array remains in the memory. Doesn’t it lead to a memory leak?

    3. Now this.nodes points to the collection created. But the next command this.unset(“nodes”) is removing that reference. Then this.nodes becomes undefined and we don’t have any reference at all to the nodes. Is that correct?

    Many thanks in advance!

    • http://mutedsolutions.com Derick Bailey

      Hi Dimitri,

      1: it’s a personal preference. model.set is used to store attributes of a model. i do not consider a child model to be an “attribute” – a data point. additionally, storing child models in a parent model’s attributes causes more complexity and code to be written when working with the model and it’s children.

      2: no memory leaks are created here, and the “nodes” array is no over-written by this statement. since the “nodes” array is stored in model.attributes, assigning model.nodes = … does not overwrite it. I am creating a new property on the modle directly when i make the assignment. i am not yet removing or replacing the model.attributes.nodes array.

      3: that is not correct. calling model.set or model.unset does not change any property on the model object directly. it changes properties stored in model.attributes. when i call model.get(“nodes”) it returns model.attributes.nodes. when i call model.unset(“nodes”), it removes model.attribute.nodes. it never directly reads, writes or modifies model.nodes. I am manually assigning a value to model.nodes.

      hope that helps.

      • Dmitri Zaitsev

        Hi Derick,

        Thank you so much, it is really helpful!

        Seems I confused data points (properties) with Backbone attributes.

        You mention additional complexity when storing child models (or collections like in this examples) in a parent model’s attribute. Is it just the matter of using set and get instead of direct references or is there some sensitive “Backbone magic” around it?

        Thanks again!

  • Dmitri Zaitsev

    It seems the first example can be simplified a bit by using the itemViewContainer attribute instead of the custom appendHtml method:

    http://jsfiddle.net/dmitri14/St2dB/

    • http://mutedsolutions.com Derick Bailey

      yes, there are a lot of things that could simplified in this blog post. Marionette has changed a lot since I wrote this post, 8 months ago :)

      • Dmitri Zaitsev

        If anything else can be simplified or there are any simpler implementation of backbone tree view anywhere on the web, that would be very helpful!

        I could only find longer and more complicated solutions elsewhere.

        • Dmitri Zaitsev

          Seems like I found another simplification, which avoids the painful inserting/removing the “ul” tags:
          http://jsfiddle.net/dmitri14/St2dB/1/

          • http://www.joezimjs.com Joe Zimmerman

            Interesting solution. I like it.

            I ended up removing the UL from the template and using onCompositeModelRendered or onBeforeRender to add a UL in if it was needed. I think your solution may be a bit wiser though because it doesn’t assume as much about what is contained or should be contained in the template. Obviously the `itemViewContainer:’ul’` breaks that idea a little bit, but at least this way it’s just a minor configuration instead of logic.

  • luis manuel ruiz

    Very good article.

    Muchas gracias desde España, lástima que mi inglés no sea suficientemente bueno, y algunas cosas me cueste entenderlas.
    Muchas gracias de nuevo por tu proyecto Marionette.

    Un saludo.

  • Paul Dailly

    Thanks very much! This was just the article I was looking for to help figure out how to manage my composite views!

  • Jesse Drelick

    I found this terrific article that breaks down the different Marionette views visually quite wonderfully. Worthwhile mention: http://blog.artlogic.com/2013/03/26/a-visual-guide-to-marionette-js-views/

  • Peter Caisse

    I’m having an issue trying to pass options from the TreeRoot to the TreeView. Here’s my stackoverflow question about it:

    http://stackoverflow.com/questions/16446061/pass-options-from-collectionview-to-recursive-compositeview

    Would anyone be able to shed some light?

  • Ben

    Thanks for laying the views out like this. The node structure/hierarchical layout is just what I needed!

  • Gabriel

    I have a similar question to Jacob. Suppose you want to nest models of a different type, with their own views, in a composite view? The example you provide only handles continued nesting of elements of the same exact type. No wonder you don’t use this much, I also haven’t come across too many cases where this makes sense in a real-world backbone application. What I do come across all the time, is the need for hierarchical, parent-child relationships where the views can be nested in one another. Can composite views handle this?

  • Joseph Juhnke

    Hi Derick!

    None of the jsfiddles work above. I assume it’s because they did something (update) since you posted your examples. I’m working on a problem that your post directly addresses and I’d love to see the code working and play with it. Do you think you’ll be able to fix the code to work again? Thanks!

    • http://mutedsolutions.com Derick Bailey

      d’oh!

      I fixed the two that I own: http://jsfiddle.net/derickbailey/me4NK/ and http://jsfiddle.net/derickbailey/AdWjU/ but they don’t seem to be working in the blog post, still. don’t know why the other two aren’t working… the same fix didn’t help.

      • Joseph Juhnke

        That’ll work! Thank you!

        I can use the links. The first table view is showing up on your post now, fyi.

        • http://btylerburton.com b.tyler.burton

          Hey great blog post!
          I added a childView attribute to the CollectionView so that the tree would render appropriately.

          http://jsfiddle.net/AdWjU/593/

  • Uchiha Itachi
  • Alexey

    blablabla, what I should to do if I want render one model in two different places?

  • Dennis Dyatlov

    Thank you for your explanations! Helped a lot.

    I`ve remade tree example – http://jsfiddle.net/d6zUK/

    UL and LI elements are now in place.

  • Chris

    Good article. Unfortunately, the JSfiddles are all broken due to incompatible changes in Marionnete v2.0. You should probably use versioned library includes to avoid this in the future.