Backbone Marionette Nested Composite View
Asked Answered
B

2

8

So I am stuck. I got the great Backbone.Marionette to handle my nested childs/parents relationships and rendering(doing it with the bare backbone was a nightmare), but now i'm facing problems with my nested composite view,

I'm always getting a The specified itemViewContainer was not found: .tab-content from the parent composite view - CategoryCollectionView, although the itemViewContainer is available on the template, here is what I'm trying to do, I have a restaurant menu i need to present, so I have several categories and in each category I have several menu items, so my final html would be like this:

    <div id="order-summary">Order Summary Goes here</div>
    <div id="categories-content">
         <ul class="nav nav-tabs" id="categories-tabs">
              <li><a href="#category-1">Appetizers</a></li>
         </ul>
         <div class="tab-content" >
               <div class="tab-pane" id="category-1">
                    <div class="category-title">...</div>
                    <div class="category-content">..the category items goes here.</div>
         </div>
     </div>

Here is what I have so far:

First the templates

template-skeleton

<div id="order-summary"></div>
<div id="categories-content"></div>

template-menu-core

<ul class="nav nav-tabs" id="categories-tabs"></ul>
<div class="tab-content" ></div>

template-category

<div class="category-title">
    <h2><%=name%></h2>
    <%=desc%>
</div>
<div class="category-content">
    The menu items goes here
    <ul class="menu-items"></ul>
</div>

template-menu-item

Item <%= name%>
<strong>Price is <%= price%></strong>
<input type="text" value="<%= quantity %>" />
<a href="javascript:void()" class="add">Add</a>

Now the script

var ItemModel = Backbone.Model.extend({
    defaults: {
        name: '',
        price: 0,
        quantity: 0
    }
});

var ItemView = Backbone.Marionette.ItemView.extend({
    template: '#template-menuitem',
    modelEvents: {
        "change": "update_quantity"
    },
    ui: {
        "quantity" : "input" 
    },
    events: {
        "click .add": "addtoBasket"
    },
    addtoBasket: function (e) {
        this.model.set({"quantity": this.ui.quantity.val() });
    },
    update_quantity: function () {
        //@todo should we do a re-render here instead or is it too costy
        this.ui.quantity.val(this.model.get("quantity"));
    }
});

var ItemCollection = Backbone.Collection.extend({
    model: ItemModel
});



var CategoryModel = Backbone.Model.extend({
    defaults: {
        name: ''
    }
});

var CategoryView = Backbone.Marionette.CompositeView.extend({

    template: '#template-category',

    itemViewContainer: ".menu-items",
    itemView: ItemView,

    className: "tab-pane",
    id: function(){
        return "category-" + this.model.get("id");
    },

    initialize: function () {

        this.collection = new ItemCollection();
        var that = this;
        _(this.model.get("menu_items")).each(function (menu_item) {
            that.collection.add(new ItemModel({
                id: menu_item.id,
                name: menu_item.name,
                price: menu_item.price,
                desc: menu_item.desc
            }));
        });


    }
});


var CategoryCollection = Backbone.Collection.extend({
    url: '/api/categories',
    model: CategoryModel
});

var CategoryCollectionView = Backbone.Marionette.CompositeView.extend({

    el_tabs: '#categories-tabs',

    template: '#template-menu-core',
    itemViewContainer: ".tab-content", // This is where I'm getting the error
    itemView: CategoryView,

    onItemAdded: function (itemView) {
        alert("halalouya");
        //this.$el.append("<li><a href=\"#cateogry-" + tab.get("id") + "\">" + tab.get("name") + "</a></li>");

        //$(this.el_tabs).append("<li><a href='#category-" + itemView.model.get("id") + "'>"
            //+ itemView.model.get("name") + "</a></li>")
    }
});

I know It's a bit hard to follow but you guys are my last resort. There is no problems with the templates and the cateogry fetching and the other stuff(it was already working before converting the CategoryCollectionView from a Marionette collection to a composite view.)

Edit 1

Added App initalizer on request:

            AllegroWidget = new Backbone.Marionette.Application();

            AllegroWidget.addInitializer(function (options) {

                // load templates and append them as scripts
                inject_template([
                        { id: "template-menuitem", path: "/js/templates/ordering-widget-menuitem.html" },
                        { id: "template-category", path: "/js/templates/ordering-widget-category.html" },
                        { id: "template-menu-core", path: "/js/templates/ordering-widget-menu-core.html" },
                        { id: "template-skeleton", path: "/js/templates/ordering-widget-skeleton.html" }
                    ]);

                // create app layout using the skeleton
                var AppLayout = Backbone.Marionette.Layout.extend({
                    template: "#template-skeleton",

                    regions: {
                        order_summary: "#order-summary",
                        categories: "#categories-content"
                    }
                });

                AllegroWidget.layout = new AppLayout();
                var layoutRender = AllegroWidget.layout.render();
                jQuery("#allegro-ordering-widget").html(AllegroWidget.layout.el);

                // Initialize the collection and views
                var _category_collection = new CategoryCollection();

                var _cateogories_view = new CategoryCollectionView({ api_key: window.XApiKey, collection: _category_collection });


                _category_collection.fetch({
                    beforeSend: function (xhr) {
                        xhr.setRequestHeader("X-ApiKey", window.XApiKey);
                    },
                    async: false
                });


                //AllegroWidget.addRegions({
                 ///   mainRegion: "#allegro-ordering-widget"
                //});
                AllegroWidget.layout.categories.show(_cateogories_view);

            });



            AllegroWidget.start({api_key: window.XApiKey});
Bigamous answered 26/4, 2013 at 11:32 Comment(3)
unrelated note. I noticed you are using onItemAdded in your code. It was changed recently, you will want to use onBeforeItemAdded. See my answer in this post #16180659Paulettapaulette
Can you share the code that creates an instance of CategoryCollectionView and calls region.show()?Paulettapaulette
@ScottPuleo thanks for the tip, was just struggling with that, i attached the app initializer to the questionBigamous
P
6

You are adding to the collection via fetch before you call show on the region.

Marionette.CompositeView is wired by default to append ItemViews when models are added to it's collection. This is a problem as the itemViewContainer .tab-content has not been added to the dom since show has not been called on the region.

Easy to fix, rework you code as below and it should work without overloading appendHtml.

// Initialize the collection and views
var _category_collection = new CategoryCollection();

// grab a promise from fetch, async is okay
var p = _category_collection.fetch({headers: {'X-ApiKey': window.XApiKey});

// setup a callback when fetch is done
p.done(function(data) {
   var _cateogories_view = new CategoryCollectionView({ api_key: window.XApiKey, collection: _category_collection });
   AllegroWidget.layout.categories.show(_cateogories_view);
}); 
Paulettapaulette answered 26/4, 2013 at 16:57 Comment(1)
This helped me clear up my marionette architecture tremendously..everything is really working now. THANKS.Chinchilla
B
0

okay this is pretty weird but adding this in the CategoryCollectionView class:

appendHtml: function (collectionView, itemView, index) {
    //@todo very weird stuff, assigning '.tab-content' to itemViewContainer should have been enough 
    collectionView.$(".tab-content").append(itemView.el);
}

solved the problem, however i have no idea why it works, asssigning '.tab-content' to the itemViewContainer should have been enough, any idea?

Bigamous answered 26/4, 2013 at 12:19 Comment(2)
This works because here 'tab-content' is hard-coded in the view method.Selfevident
The problem seems to be in the getItemViewContainer method that seems to be called before template is rendered. I've overridden that method and removed the error, after which it worked.Selfevident

© 2022 - 2024 — McMap. All rights reserved.