5
votes

The Starting Problem

I have a CompositeView (a table) for which each model in the collection is represented as two table rows, with a template like:

<tr class="row-parent">
    <td>parent info here</td>
</tr>
<tr class="row-child">
    <td>child info here</td>
</tr>

With an ItemView like this:

var ItemView = Backbone.Marionette.ItemView.extend({
    template: ItemTmpl
});

Even though they are named 'parent' and 'child', they are actually peer members of the same model. If I don't specify a tagName, Backbone will wrap each view in a <div> which is both invalid HTML and also breaks the layout.

The First Attempt at a Solution

So I figured, why not remove the outer <tr> tags and let Backbone add them in. So I updated my template to be like:

    <td>parent info here</td>
</tr>
<tr class="row-child">
    <td>child info here</td>

And updated the view to:

var ItemView = Backbone.Marionette.ItemView.extend({
    template: ItemTmpl,
    tagName: 'tr',
    className: 'row-parent'
});

I was hoping that an outer tag would combine with the inner tag fragments, but Marionette didn't like that. It only showed the row-child. So I'm not sure where to go from here. I'm considering two strategies but haven't gone into much details yet.

Moving Forward: Plan A

Override whatever part of Backbone creates the extra div to not create it, or override the part of Marionette which appends the view to remove the div just before appending.

Moving Forward: Plan B

Create a new type of view called CompositeMultiView which, naturally, would extend off CompositeView and allow you two specify a second ItemView, or maybe just an array of views, all of which would be rendered for each model given. This plan seems like a lot more work but less hacked.


Does anyone have any better suggestions, workarounds or concrete pointers as to how I would go about implementing either of the two above plans?

Here is a mockup of what the table should look like: enter image description here

3
On first glance I would definitely go with Plan B. Also, could you give a rough example of what sort of data structure you're dealing with? I want to confirm that a CompositeView is best here.Will M
Hmmm, I don't think the data structure is particularly unusual or complex. It's simply an array of models. I think a CompositeView is appropriate because I want to display the models (leaf) in a table (branch) and this is pretty much what CompositeView was invented for. The only unusual part is that there is too much data to be represented in one table row, so I wanted to show it as two table rows.T Nguyen
Hear me out, @T Nguyen. If it's a flat array of models and shown across two rows, I see no need for the CompositeView. You could just have an ItemView with a template that spreads the data across two table rows. CompositeView makes more sense if you were listing nested data, like a folder structure or a list of items via category.Will M
CMIIW, but if I do it that way, I will only get one giant model. I won't be able to add, update or delete individual models in the collection, without writing my own custom code to do that. If possible, I would like to do this all "The Backbone Way".T Nguyen
Then I'm misunderstanding the way your data is set up. Is it one model = two <tr>s? If so, you would have an ItemView representing one model, then assign the collection of models to a CollectionView.Will M

3 Answers

4
votes

I struggled with that same problem until I finally discovered today, that a table can have multiple tbody tags, each one containing multiple tr tags.

This is actually the answer provided to a similar backbone question.

So your ItemView would become:

var ItemView = Backbone.Marionette.ItemView.extend({
    template: ItemTmpl,
    tagName: 'tbody'
});

And the generated html:

<table>
  <!-- first item -->
  <tbody>
    <tr class="row-parent">
      <td>parent info here</td>
    </tr>
    <tr class="row-child">
      <td>child info here</td>
    </tr>
  </tbody>
  <!-- second item -->
  <tbody>
    <tr class="row-parent">
      <td>parent info here</td>
    </tr>
    <tr class="row-child">
      <td>child info here</td>
    </tr>
  </tbody>
  ...
</table>
1
votes

You could try modifying the CompositeView as follows:

  1. Specify itemView as an array of views
  2. Override addChildView to render each view for each model

This solution ends up looking a lot like your "Plan B". Give it a shot:

itemView: [My.ParentView, My.ChildView],

addChildView: function(item, collection, options){
  this.closeEmptyView();
  var itemViews = this.getItemView(item);
  var index = this.collection.indexOf(item);

  _.each(itemViews, function(ItemView) {
    this.addItemView(item, ItemView, index);
  });
}

I haven't thought through whether this would handle model events such as destroy, but I believe it should handle them gracefully.

0
votes

Dirty solution: Add a custom render function to your ItemView

// Or whatever template you use
var template = Handlebars.compile(datepickerTemplate);

var ItemView = Backbone.Marionette.ItemView.extend({
    render: function(){
        var html = template(this.model.toJSON());
        var newElement = $(html);
        this.$el.replaceWith(newElement);
        this.setElement(newElement);
        return this;
    }
});

This should remove the extra div wrapping