Search code examples
javascriptmarionettebackbone-relational

how can I have CollectionView of CollectionView in marionette.js?


I have a use case where I want to have a collection of collectionViews (or composite views) rendered. What is the best way to do this using marionette.js?

My model structure is as follows-

A 
|
|-- Collection<B>
               |
               |--Collection<C>

I want to render as follows -

A1
|
|--B1
|  |
|  C1
|  |
|  C2
|  |
|  C3
|
|--B2
   |
   C4
   |
   C5
   |
   C6

What is the best way to do this? I don't want it to be like this

A1
|
|--B1
|  |
|  |--C1
|  |
|  |--C2
|  |
|  |--C3
|
|--B2
   |
   |--C4
   |
   |--C5
   |
   |--C6

Solution

  • So you could achieve this by creating a collection view that can deiced if the child it is trying to display has a collection or if is just a model. So for this example i have set up a model which has a name and an optional collection, if the collection is used then it will contain a collection of models which in turn can have an optional collection.

    Then define a collection view, the collection view will check if the child has a collection and it if does will use the collection view as the child.

    //collection view that can choose to display another collection view or a model
    myApp.Views.View1 = Marionette.CollectionView.extend({
        tagName: "ul",
        //overridden getChildView to return either another collection view or an item view
        getChildView: function (child) {
            if (child.get("collection")) {
                return myApp.Views.View1;
            }
            return myApp.Views.ItemView;
        },
    
        //set either the collection or the model depending which view we are using
        childViewOptions: function (model, index) {
            if (model.get("collection")) {
                return {
                    collection: model.get("collection"),
                }
            } else {
                return {
                    model: model
                }
            }
        }
    });
    

    if it doesn't then it will just use an item view to display the model

    //item view to display final model
    myApp.Views.ItemView = Marionette.ItemView.extend({
        tagName: "li",
        template: _.template('<%= name %>'),
    })
    

    here it is running - http://jsfiddle.net/leighking2/r5ogoL5h/

    If you wanted to display the names of the models being render above the collection then you can use a composite view, this also allows us more control on hooking into the view to display the collection

    //collection view that can choose to display another collection view or a model
    myApp.Views.View1 = Marionette.CompositeView.extend({
        tagName: "ul",
        template: _.template('<li><%= name %></li><li><ul class="collectionHook"></ul></li>'),
        childViewContainer: ".collectionHook",
        //overridden getChildView to return either another collection view or an item view
        getChildView: function (child) {
            if (child.get("collection")) {
                return myApp.Views.View1;
            }
            return myApp.Views.ItemView;
        },
    
        //set either the collection or the model depending which view we are using
        childViewOptions: function (model, index) {
            if (model.get("collection")) {
                return {
                    model: model,
                    collection: model.get("collection"),
                }
            } else {
                return {
                    model: model
                }
            }
        }
    });
    

    http://jsfiddle.net/leighking2/kx9kuup0/

    only issue is the html isn't 100% valid as you end up with a double <ul> when you display a composite view inside a composite view but with some tweaking that could be fixed, it still renders correctly though.