Such Much Code
Such Much Code

Reputation: 827

Display collection length in a view in Marionette.js

I'm learning Marionette.js and have a scenario, where my app has:

app.addRegions({
    main: '#omen',
    newItem: '#addnewitem',
    counter: '#counter'
});

These regions. I have these Model/Collections:

var Item = Backbone.Model.extend(),
Items = Backbone.Collection.extend({
    model: Item,
    url: 'api/items'
}),

I have an Item view and Items view:

ItemView = Mn.ItemView.extend({
    tagName: 'tr',
    template: '#itemView',
    events: {
        'click #btnDeleteBook' : 'deleteItem'
    },
    deleteItem: function() {
        app.trigger('item:delete', this.model);
    }
}),
ItemsView = Mn.CollectionView.extend({
    tagName: 'table',
    childView: ItemView,
    onShow: function(view) {
        TweenMax.staggerFrom($(view).find('td'), 1, {
            x: 100
        }, 2);
    }
}),

I have an initializer function, that listens for events above and does stuff through app.ItemController. It all works fine.

But now I want to add a region (counter region), that displays the total number of items in my collection. I need this to be a separate view ideally, because I will be displaying it in different places.

So I do this:

DisplayCounter = Mn.ItemView.extend({
    template: _.template('Total: '+ app.Items.length),
}),

app.Items is an instance of Collection declared above. But even before instantiation of DisplayCounter, I get error:

Uncaught TypeError: Cannot read property 'length' of undefined.

Please help... :(

------------------------- E D I T ----------------------

I've achieved it, but it seems to be so complicated to do such a tiny thing.

Changed my collection like so:

Items = Backbone.Collection.extend({
        model: Item,
        url: 'api/items',
        initialize: function() {
            this.listenTo(this, 'add', function() {
                app.trigger('collection:updated', this);
            });
        }
    }),

and changed my DisplayCounter like this:

DisplayCounter = Mn.ItemView.extend({
        template: _.template('Total: <%=length%>'),
        templateHelpers: function() {
            return {
                length: this.lengthVariable || 0
            }
        },
        initialize: function() {
            app.on('collection:updated', function(params){
                this.lengthVariable = params.length;
                this.render();
            }.bind(this));
        }
    }),

I can't believe there's no easier way to do this.. :/

Upvotes: 0

Views: 1134

Answers (2)

limoragni
limoragni

Reputation: 2776

app.Items is not being defined.

In Marionette you can define which collection or model are your views going to use.

ItemsView = Mn.CollectionView.extend({
tagName: 'table',
childView: ItemView,
collection: myItems // An instance of your collection
onShow: function(view) {
    TweenMax.staggerFrom($(view).find('td'), 1, {
        x: 100
    }, 2);
}
}),

So marionette is going to render one itemView per element in your collection. Then inside of your collection view this.collection is going to refer to the collection instance. So this.collection.length will have what you need.

And in your ItemView you can get the corresponding model by using this.model

Upvotes: 0

joews
joews

Reputation: 30330

The code that sets up DisplayCounter is being run before the code that puts an instance of Items into app.Items.

Even if you avoided this problem by assigning app.Items first, you'd still have a problem - the template property is only set once so you'd only ever see the length of app.Items at the time that you define DisplayCounter.

Rather than hard-coding the value directly into the template string, you should supply a value at render time. Mn.View.serializeData allows you to customise the data that is passed into the template function at render time:

DisplayCounter = Mn.ItemView.extend({
  template: _.template('Total:: <%= itemCount %>),

  serializeData: function() {
    return { itemCount: app.Items.length }
  }
}),

Upvotes: 1

Related Questions