9
votes

I've been struggling for a while to create nested data tables using VueJs and Vuetify data tables.

Here's what I'm trying to achieve:

I would like to have one main data-table, with filters, search, and everything else (I got that part right I think, using computed properties and reusing my filtered data in the data table), and some other data tables nested under the main one.

To achieve that, I used the 'expand' ability of Vuetify's data tables and created child tables under this expand scope.

While I've been able to achieve the nesting part, at least visually talking, I can't seem to wrap my head around a problem: I would like to add a checkbox on the parent row (that is a row of the parent data table) and be able to select everything that's in its expand slot. It certainly 'works', as it effectively selects the data binded to the row, but it doesn't mark the data under the expand scope as selected.

Here's a screenshot of what I would like to happen when I select the 'Desserts' row. enter image description here

And what happens instead is that the row is selected, but not the children elements under the expand scope.

In summary: I would like a checkbox that acts like the 'select all' checkbox that's on the headers of the child data table. While keeping in mind I would need to have 3 nested data tables (here there's only two), where the parent always selects all children elements and where children can be unselected or reselected at will.

I made a pen for you guys to understand what I'm trying to do, maybe you can help out? (https://codepen.io/Synkied/pen/qGLNBv?editors=1010)

And here's the code:

<div id="app">
  <v-app id="inspire">
    <v-data-table
      v-model="selected"
      :headers="headers"
      :items="treats"
      :pagination.sync="pagination"
      expand="true"
      select-all
      item-key="category"
      class="elevation-1"
      hide-actions
>
      
      <template v-slot:items="props">
        <tr @click="props.expanded = !props.expanded">
          <td>
            <!-- This one should select every items under the expand slot -->
            <v-checkbox
                @click.stop="props.selected = !props.selected"
                :input-value="props.selected"
                primary
                hide-details
             ></v-checkbox>
          </td>
          <td class="text-xs-right">{{ props.item.category }}</td>
        </tr>
      </template>

      <template v-slot:expand="props">
        <v-data-table
                      v-model="selected"
                      :headers="headers"
                      :items="props.item.food"
                      :pagination.sync="pagination"
                      expand="true"
                      select-all
                      item-key="category"
                      class="elevation-1"
                      hide-actions
                      >
          <!-- I should be able to hide headers and have the parent row checkbox act like the select all headers' checkbox -->
              <template v-slot:items="props">
                <tr @click="props.expanded = !props.expanded">
                  <td>
                    <v-checkbox
                      :input-value="props.selected"
                      primary
                      hide-details
                    ></v-checkbox>
                  </td>
                  <td>{{ props.item.name }}</td>
                  <td class="text-xs-right">{{ props.item.calories }}</td>
                  <td class="text-xs-right">{{ props.item.fat }}</td>
                  <td class="text-xs-right">{{ props.item.carbs }}</td>
                  <td class="text-xs-right">{{ props.item.protein }}</td>
                  <td class="text-xs-right">{{ props.item.iron }}</td>
                </tr>
              </template>
          </v-data-table>
      </template>
    </v-data-table>
    {{ selected }}
  </v-app>
</div>
new Vue({
  el: '#app',
  data: () => ({
    pagination: {
      sortBy: 'name'
    },
    selected: [],
    headers: [
      {
        text: 'Dessert (100g serving)',
        align: 'left',
        value: 'name'
      },
      { text: 'Calories', value: 'calories' },
      { text: 'Fat (g)', value: 'fat' },
      { text: 'Carbs (g)', value: 'carbs' },
      { text: 'Protein (g)', value: 'protein' },
      { text: 'Iron (%)', value: 'iron' }
    ],
    treats: [
      {
        category: 'Desserts',
        food: [
          {
            name: 'Frozen Yogurt',
            calories: 159,
            fat: 6.0,
            carbs: 24,
            protein: 4.0,
            iron: '1%'
          },
          {
            name: 'Ice cream sandwich',
            calories: 237,
            fat: 9.0,
            carbs: 37,
            protein: 4.3,
            iron: '1%'
          },
          {
            name: 'Cupcake',
            calories: 305,
            fat: 3.7,
            carbs: 67,
            protein: 4.3,
            iron: '8%'
          }
        ]
      },
      {
        category: 'Entries',
        food: [
          {
            name: 'Melon',
            calories: 159,
            fat: 6.0,
            carbs: 24,
            protein: 4.0,
            iron: '1%'
          },
          {
            name: 'Hummus',
            calories: 237,
            fat: 9.0,
            carbs: 37,
            protein: 4.3,
            iron: '1%'
          }
        ]
      }
    ]
  })
})

Thanks so much, awesome community!

1

1 Answers

0
votes
<div id="app">
  <v-app id="inspire">
    <v-data-table
      :headers="dessertHeaders"
      :items="desserts"
      :single-expand="singleExpand"
      :expanded.sync="expanded"
      item-key="name"
      show-expand
      class="elevation-1"
    >
      <template v-slot:top>
        <v-toolbar flat>
          <v-toolbar-title>Expandable Table</v-toolbar-title>
          <v-spacer></v-spacer>
          <v-switch
            v-model="singleExpand"
            label="Single expand"
            class="mt-2"
          ></v-switch>
        </v-toolbar>
      </template>
      <template v-slot:item="{ item, expand, isExpanded }">
        <tr>
          <td
            class="d-block d-sm-table-cell"
            v-for="field in Object.keys(item)"
          >
            {{item[field]}}
          </td>
          <td>
            <v-btn icon @click="expand(!isExpanded)">
              <v-icon
                >{{ isExpanded ? 'mdi-chevron-up' : 'mdi-chevron-down'
                }}</v-icon
              >
            </v-btn>
          </td>
        </tr>
      </template>
      <template v-slot:expanded-item="{ headers, item }">
        <tr
          v-for="(item,index) in desserts"
          :key="index"
          :colspan="headers.length"
        >
          <td v-for="(i,index) in Object.values(item)">{{i}}</td>
        </tr>
      </template>
    </v-data-table>
  </v-app>
</div>
new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data () {
    return {
      expanded: [],
      singleExpand: false,
      dessertHeaders: [
        {
          text: 'Dessert (100g serving)',
          align: 'start',
          sortable: false,
          value: 'name',
        },
        { text: 'Calories', value: 'calories' },
        { text: 'Fat (g)', value: 'fat' },
        { text: 'Carbs (g)', value: 'carbs' },
        { text: 'Protein (g)', value: 'protein' },
        { text: 'Iron (%)', value: 'iron' },
        { text: '', value: 'data-table-expand' },
      ],
      desserts: [
        {
          name: 'Frozen Yogurt',
          calories: 159,
          fat: 6.0,
          carbs: 24,
          protein: 4.0,
          iron: '1%',
        },
        {
          name: 'Ice cream sandwich',
          calories: 237,
          fat: 9.0,
          carbs: 37,
          protein: 4.3,
          iron: '1%',
        },
        {
          name: 'Eclair',
          calories: 262,
          fat: 16.0,
          carbs: 23,
          protein: 6.0,
          iron: '7%',
        },
        {
          name: 'Cupcake',
          calories: 305,
          fat: 3.7,
          carbs: 67,
          protein: 4.3,
          iron: '8%',
        },
        {
          name: 'Gingerbread',
          calories: 356,
          fat: 16.0,
          carbs: 49,
          protein: 3.9,
          iron: '16%',
        },
        {
          name: 'Jelly bean',
          calories: 375,
          fat: 0.0,
          carbs: 94,
          protein: 0.0,
          iron: '0%',
        },
        {
          name: 'Lollipop',
          calories: 392,
          fat: 0.2,
          carbs: 98,
          protein: 0,
          iron: '2%',
        },
        {
          name: 'Honeycomb',
          calories: 408,
          fat: 3.2,
          carbs: 87,
          protein: 6.5,
          iron: '45%',
        },
        {
          name: 'Donut',
          calories: 452,
          fat: 25.0,
          carbs: 51,
          protein: 4.9,
          iron: '22%',
        },
        {
          name: 'KitKat',
          calories: 518,
          fat: 26.0,
          carbs: 65,
          protein: 7,
          iron: '6%',
        },
      ],
    }
  },
})