Search code examples
javascriptbackbone.jsmarionette

CollectionView - collection data not being passed to childView


I'm trying to make the collectionView work , but I'm not really sure whether the CollectionView initiates the fetch method automatically and then populates the ChildViews with the fetched data. Or is it something I need to do manually? I have checked the Marionette doc page but I couldn't find any proper example that would answer my question.

The below example is what I've got so far , the collection is filled up with all the required data, but the data aren't passed to childview for some reason.

The below JSON is returned whenever I call 'http://localhost/GetCategories':

JSON data:

[{"category_id":"Category1","category_name":"Category One"}, 
 {"category_id":"Category2","category_name":"Category Two"}]

collection:

  define([ 'underscore',  'backbone',  'models/MainMenu/MM.model.category'], function(_, Backbone, m_Category){
      var CategoriesCollection = Backbone.Collection.extend({
        model: m_Category,

        url : 'GetCategories';

        initialize: function() {
            console.log('CategoriesCollection initialized...');
            this.fetch()
        }

      });


  return CategoriesCollection;
});

collectionView:

define([ 'backbone', 'underscore', 'marionette', 
    'collections/MainMenu/collection.categories' , 
    'layouts/MainMenu/itemview.category'], 
    function (Backbone, _, Marionette, c_Categories, iv_Category) {
    "use strict";
    var CategoriesCollectionView = Marionette.CollectionView.extend({

        tagName: 'div',

        template: null,

        id: 'categories',

        childView: iv_Category,

        collection : new c_Categories(),

        initialize: function(){
            console.log('Categories Collection: initialized');

            /// or should I call fetch() from within the CV? 
            /// but how do I then pass all the data to ChildView? 
            this.collection.fetch().done(function(){

            })
        },
        onRender: function(){
            console.log(this.collection) ///<-- shows all the JSON data properly
        },
        onShow: function(){
        }
    });

    return CategoriesCollectionView;
});

ItemView:

define([ 'backbone', 'underscore', 'marionette', 'templates/template.mainmenu'], function (Backbone, _, Marionette, template) {
    "use strict";
    var CategoryView = Marionette.ItemView.extend({

        tagName: 'div',

        template: template['category.layout'],

        id: 'category-layout',

        initialize: function(){
            console.log('Category Layout: initialized');
        },
        onRender: function(){
            console.log(this.model) /// < --- doesn't return anything 
        },
        onShow: function(){
            console.log(this.model) ///< --- doesn't return anything 
        }
    });

    return CategoryView;
});

it only works when I create a LayoutView on top of the CollectionView and handle the fetching and assigning the collection from there.

define([ 'backbone', 'underscore', 'marionette', 
            'templates/template.mainmenu', 
            'collections/MainMenu/MM.collection.categories',
            'layouts/MainMenu/collectionview.categories'    ],
    function (Backbone, _, Marionette, template, c_Categories, cv_Categories) {
    "use strict";
    var CategoryLayoutView = Marionette.LayoutView.extend({


        template: template['categories.layout'],

        regions: {
            categories : '#categories'
        },

        id: 'categories-layout',

        initialize: function(options){
            this.collection = new c_Categories();
            console.log('Category Layout: initialized');
        },

        onRender: function(){
            var categories = this.categories
            var collection = this.collection;
            collection.fetch().done(function(cData){
                categories.show(new cv_Categories({collection : collection}))
            })
        },

        onShow: function(){
        }
    });

    return CategoryLayoutView;
});

Any help would be greatly appreciated.

Thanks


Solution

  • Prelimnaries: The primary purpose of Collection/CompositeView is to properly render a Backbone.Collection, i.e. pass the collection models to the CollectionView children, and render those children. There is not involvement on your part necessary other than providing a Collection/CompositeView with―and this is key—a pre-filled collection.

    Now, the problem you're having is that you're doing an asynchronous fetch (by default all AJAX calls are async) on your CategoriesCollection.initialize(). Since that initialize is being called when you define the CategoriesCollectionView (see its collection property) the fetch is likely not coming back in time when you render it. So, the CollectionView renders, but finds no models and just renders an emptyView if one has been provided.

    You have two options: To do what you're doing in your LayoutView (that's not only what I always do in my views, but I've spoken to a few of the top Marionette contributors and they do the same), or fetch synchronously (which I don't recommend for perf reasons).

    A few pointers

    1. Remove the fetch from CategoriesCollection.initialize() (If you want to keep it there you'll have to pass the option { async: false } in the fetch, to guarantee the data will be back by the time you render your view. However, I don't recommend doing this since you'll be blocking the CPU while you're waiting for the fetch).

    2. Remove the CategoriesCollectionView.collection property from within the CategoriesCollectionView definition. Instead we'll pass the collection in from withing the parent LayoutView

    3. I suggest you use .then() rather than .done() for your Promise handler. Two reasons:

      a. .then() handles errors better: It will display a stack trace if errors go unmanaged, and the second parameter can always be used to actually handle errors that result from the Promise. b. .then() allows you to continue chaining. Maybe not in this call, but in some other Promise you may want to invoke a series of callbacks after the call comes in. Like the name implies .done() is the end of the callback chain

    Other than that, your solution is Best Practice.