Vue - How do you render sub-rows in a table when you are only allowed one root component?

Brian Boyko picture Brian Boyko · Feb 22, 2018 · Viewed 7.6k times · Source

I'm trying to figure out the best way to render sub rows in a table.

I'm developing what's essentially a table+accordion. That is, the table adds more rows when you click on it to show more details for that entry.

My table is located in the Researcher Groups component, which has a sub-component: "app-researcher-group-entry (ResearcherGroupEntry).

I'm using Vue-Material, but the problem would be the same if I was using a plain .

Anyway, my structure is kind of like:

   // ResearcherGroups.vue
<template>
    <md-table>
        <md-table-row>
          <md-table-head></md-table-head>
          <md-table-head>Researcher</md-table-head>
          <md-table-head>Type</md-table-head>
          <md-table-head></md-table-head>
          <md-table-head></md-table-head>
        </md-table-row>
        <app-researcher-group-entry v-for="(group, index) in researcherGroups" :key="group.label" :groupData="group" :indexValue="index"></app-researcher-group-entry>
    </md-table>
</template>

And in ResearcherGroupEntry.vue:

<template>
<md-table-row>
    <md-table-cell>
      <md-button class="md-icon-button md-primary" @click="toggleExpansion">
        <md-icon v-if="expanded">keyboard_arrow_down</md-icon>
        <md-icon v-else>keyboard_arrow_right</md-icon>
      </md-button>
      <md-button @click="toggleExpansion" class="index-icon md-icon-button md-raised md-primary">{{indexValue + 1}}</md-button>
    </md-table-cell>
    <md-table-cell>{{groupData.label}}</md-table-cell>
    <md-table-cell>Group</md-table-cell>
    <md-table-cell>
      <md-button @click="openTab" class="md-primary">
        <md-icon>add_box</md-icon>
        Add Client / Client Type
      </md-button>
    </md-table-cell>
    <md-table-cell>
      <md-button class="md-primary">
        <md-icon>settings</md-icon>
        Settings
      </md-button>
    </md-table-cell>
  <app-add-client-to-group-tab :indexValue="indexValue" :groupData="groupData" :closeTab="closeTab" :addClientToGroupTab="addClientToGroupTab"></app-add-client-to-group-tab>
  </md-table-row>

</template>

Here's where the problem comes in. I want to be able to expand the clients like this from our mockup:

mockup

This would be trivially easy if I could just add another into the ResearcherGroupEntry component. Unfortunately, wrapping with a div or span tag won't work here (it messes up the table).

If I wasnt using Vue Material, I might have considered using JSX for this component, because then I could simply use .map in the parent to create an array of components based on the two variables. I might still be able to do that with v-for directives, but I'm not sure. What I may have to do is create, from props, a crazy pseudo array merging in parents and children, but that's UUUUUGLY code.

Answer

Roy J picture Roy J · Feb 22, 2018

The key here is that the row component doesn't render its own sub-rows. You can use <template> tags to provide entity-less v-for and v-if wrappers. Do your v-for in a template, so that you can output the row tr and also some optional sub-row trs. Simple example:

new Vue({
  el: '#app',
  data: {
    rows: [{
        id: 1,
        name: 'one',
        subrows: ['a', 'b', 'c']
      },
      {
        id: 2,
        name: 'two',
        subrows: ['d', 'e', 'f']
      }
    ],
    expanded: {}
  },
  methods: {
    expand(id) {
      this.$set(this.expanded, id, true);
    }
  },
  components: {
    aRow: {
      template: '<tr><td>{{name}}</td><td><button @click="expand">Expand</button></td></tr>',
      props: ['name', 'id'],
      methods: {
        expand() {
          this.$emit('expand', this.id);
        }
      }
    },
    subRow: {
      template: '<tr><td colspan=2>{{value}}</td></tr>',
      props: ['value']
    }
  }
});
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<table id="app" border=1>
  <tbody>
    <template v-for="r in rows">
      <a-row :id="r.id" :name="r.name" @expand="expand"></a-row>
      <template v-if="r.id in expanded">
        <sub-row v-for="s in r.subrows" :value="s"></sub-row>
      </template>
    </template>
  </tbody>
</table>