0

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.. :/

Such Much Code
  • 787
  • 1
  • 9
  • 26

2 Answers2

1

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 }
  }
}),
joews
  • 29,767
  • 10
  • 79
  • 91
  • 1
    oh that's useful thanks! however, my `app.Items.length` comes up as 0 and I think this is because I am using fetch() method to get Items from the server. And when the view is rendered, it doesn't have items yet. So should I implement something like `collection:change` event somewhere and then trigger an event that will say 'hey, the collection has been updated, wanna re-render?' sort of thing... ? – Such Much Code Aug 19 '15 at 15:04
  • Yes, you should re-render in response to the add and remove events. – joews Aug 19 '15 at 19:02
  • I've updated the code. Is that a correct way to do it? – Such Much Code Aug 20 '15 at 09:38
0

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

limoragni
  • 2,716
  • 2
  • 32
  • 50