How to show CompositeView with multiple children v

2019-03-30 01:56发布

问题:

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:

回答1:

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>


回答2:

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.



回答3:

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