3
votes

Basically, I'm trying to render a CompositeView as a simple four-column list with a table header, where each model in a collection is rendered into a and appended to the . I'm following an example of Derick's quite closely with only a little variation, but unfortunately have gotten some pretty strange results.

Instead of rendering each itemView, the view instead references itself and re-renders for each item in the collection, producing a new table and table head. Before that, It was rendering the compositeView in itself.

I have an itemView, the template of which is a group of items, and a compositeView that references it which is a table

The CompositeView:

class App.module('Views.Parts').Index extends Backbone.Marionette.CompositeView
   template: 'parts/index'
   itemView: App.Views.Parts.Part
   tagName: 'table'
   itemViewContainer: 'tbody'
   appendHtml: (collectionView, itemView, index)->
           collectionView.$el.append(itemView.el)

The ItemView:

class App.module('Views.Parts').Part extends Backbone.App.ItemView
       tagName: 'tr'
       template: 'parts/part'
       events:
               "click .destroy": "destroy"
       destroy: (e) ->
               e.preventDefault()
               @model.destroy()
       onRender: ->
               @stickIt()

The Controller

class App.Controllers.Parts
       constructor: ->
               @parts = new App.Collections.Parts
               @parts.reset(App.parts)
               App.parts = null

       showView: (view)->
               App.mainRegion.show view

       index: ->
               view = new App.Views.Parts.Index
                       collection: @parts
               @showView view

I have also heard that declaring an ItemView before the CompositeView is necessary--however since it is a Marionette Rails project, the views are actually living in different directories. Would I have to declare their order or bind them to each other accordingly in another way?

2

2 Answers

2
votes

You don't want to have itemViewContainer AND appendHtml. Try removing the latter and your views should render properly.

2
votes

TL;DR Marionette.CompositeView uses itself as the item view type if none defined. Set the itemView property on prototype to make it use the correct item view

Problem

In our case issue was that Marionette gets the ItemView of a CompositeView by the following logic:

getItemView: function(item){
  var itemView = Marionette.getOption(this, "itemView") || this.constructor;

  if (!itemView){
    throwError("An `itemView` must be specified", "NoItemViewError");
  }

  return itemView;
}

We had defined the CompositeView as:

class sm.Views.GreetingPicker extends Marionette.CompositeView
  template: 'greeting_picker'
  el: '.message-choice'
  itemView: sm.Views.WelcomeMessage

When the CompositeView, ItemView & Layout that tied them together were in one file (served by Rails using Sprockets), the file loaded all at once and there was no problem.

After I split up the Layout, CompositeView & ItemView, the CompositeView's appendHtml's both arguments were of the same type as CompositeView. This was caused by the Marionette's logic mentioned in the beginning.

Solution

In the initializer set the itemView property on constructor manually. Like this:

class sm.Views.GreetingPicker extends Marionette.CompositeView
  initialize: ->
    @constructor::['itemView'] = sm.Views.WelcomeMessage