Search code examples
cssvue.jsvuetify.jsvuetify-datatable

Vuetify nested data table alignments


I have got a vuetify data table with expandable rows. When expanded, I want to show a data table within the expanded section that has the exact same columns as the main one. I am having trouble with aligning the columns, I just can't seem to align them.

I have tried adding another data table within the parent one, adding a full <table> or only <tr>s and/or <td>s but no luck.

The result so far is

nested tables

You can see that the nested table is not aligned with the parent one, the figures are out of place.

Here is where I got so far (CodePen):

<div id="app">
  <v-app id="inspire">
    <v-data-table
      :headers="headers"
      :items="reportData"
      :items-per-page="-1"
      :single-expand="false"
      :expanded.sync="expanded"
      class="elevation-1"
      show-expand
      item-key="id" 
    >
      <template v-slot:top>
        <v-toolbar flat>
          <v-toolbar-title>Report</v-toolbar-title>
          <v-spacer></v-spacer>
        </v-toolbar>
      </template>

      <template v-slot:expanded-item="{ headers, item }">
        <td :colspan="headers.length">
          <table style="width: 100%;">
            
            <tr v-for="row in item.events" :key="row.id">

              <td v-if="key != 'id'" v-for="(event, key) in row">{{row[key]}}</td>
              
            </tr>
          </table>
        </td>
      </template>
    </v-data-table>
  </v-app>
</div>

And

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data () {
    return {
      expanded: [],
      singleExpand: false,
      headers: [
        { text: 'Event Name', value: 'name'},
            { text: 'Total Hits', value: 'totalHits' },
            { text: 'Unique Hits', value: 'uniqueHits' },
            { text: '', value: 'data-table-expand' },
      ],
      reportData: [
    {
        "id": 294,
        "name": "Bounce",
        "totalHits": 40243,
        "uniqueHits": 14569,
        "events": [
            {
                "id": 411,
                "name": "Bounce",
                "totalHits": 40242,
                "uniqueHits": 14568
            },
            {
                "id": 562,
                "name": "",
                "totalHits": 1,
                "uniqueHits": 1
            }
        ]
    },
    {
        "id": 295,
        "name": "Return",
        "totalHits": 1763,
        "uniqueHits": 672,
        "events": [
            {
                "id": 412,
                "name": "Return",
                "totalHits": 1628,
                "uniqueHits": 623
            },
            {
                "id": 417,
                "name": "Push",
                "totalHits": 135,
                "uniqueHits": 49
            }
        ]
    },
    {
        "id": 402,
        "name": "Deadly Virus | Push",
        "totalHits": 69,
        "uniqueHits": 28,
        "events": [
            {
                "id": 561,
                "name": "No Action Needed",
                "totalHits": 69,
                "uniqueHits": 28
            }
        ]
    },
    {
        "id": 404,
        "name": "Returns",
        "totalHits": 17124,
        "uniqueHits": 2762,
        "events": [
            {
                "id": 409,
                "name": "Not a candidate",
                "totalHits": 5613,
                "uniqueHits": 384
            },
            {
                "id": 557,
                "name": "Sold Out",
                "totalHits": 11511,
                "uniqueHits": 2378
            }
        ]
    },
    {
        "id": 405,
        "name": "Deadly Virus | Push < Stage 1",
        "totalHits": 20,
        "uniqueHits": 16,
        "events": [
            {
                "id": 559,
                "name": "Unknow Source Added",
                "totalHits": 20,
                "uniqueHits": 16
            }
        ]
    },
    {
        "id": 411,
        "name": "Bad Test",
        "totalHits": 159,
        "uniqueHits": 103,
        "events": [
            {
                "id": 421,
                "name": "Yes",
                "totalHits": 9,
                "uniqueHits": 7
            },
            {
                "id": 568,
                "name": "No",
                "totalHits": 150,
                "uniqueHits": 96
            }
        ]
    },
    {
        "id": 462,
        "name": "New AMR",
        "totalHits": 1,
        "uniqueHits": 1,
        "events": [
            {
                "id": 623,
                "name": "Completed",
                "totalHits": 1,
                "uniqueHits": 1
            }
        ]
    },
    {
        "id": 472,
        "name": "Deadly Virus  | Push >Stage 1",
        "totalHits": 690,
        "uniqueHits": 159,
        "events": [
            {
                "id": 561,
                "name": "No Action Needed",
                "totalHits": 690,
                "uniqueHits": 159
            }
        ]
    },
    {
        "id": 473,
        "name": "Deadly Virus | Push < Stage 1 | Unknow Source Added",
        "totalHits": 75,
        "uniqueHits": 69,
        "events": [
            {
                "id": 559,
                "name": "Unknow Source Added",
                "totalHits": 75,
                "uniqueHits": 69
            }
        ]
    },
    {
        "id": 488,
        "name": "Inadequate",
        "totalHits": 7,
        "uniqueHits": 7,
        "events": [
            {
                "id": 623,
                "name": "Boom",
                "totalHits": 7,
                "uniqueHits": 7
            }
        ]
    }
],
      
    }
  },
})

EDIT

I got to the solution based on @Oleg's answer. Thanks @Oleg.

<td v-for="header in headers">
    <div class="use-case-breakdown-container">
        <div class="use-case-breakdown" v-for="actionObj in item.actions" :key="actionObj.id">
           <div v-for="prop in Object.keys(actionObj)">
             <span v-if="prop == header.value">
               {{actionObj[header.value]}}
             </span>
           </div>         
        </div>
    </div>
 </td>

Solution

  • Here's one way to do it with CSS Grid and without nesting tables (Codepen):

      <template v-slot:expanded-item="{ headers, item }">
        <td v-for="(header, parent_index) in headers">
          <div class="grid-container">
            <div class="grid-item" v-for="row in item.events" :key="row.id">
              <div v-for="(value, event, child_index) in row">
                <span v-if="parent_index + 1 === child_index"> {{ value }}</span>
              </div>
            </div>
          </div>
        </td>
       </template>
    

    And CSS:

    .grid-container {
      display: inline-grid;
      grid-template-columns: auto;
      width: 100%;
    }