Group rows in Vuetify Data Table body - vue.js

In the body section of a v-data-table, where Vuetify puts the items, it wraps all of the rows in a tbody tag. My slot template consists of multiple rows all which I need to group so that I can apply transitions or other effects.
How can I make v-data-table stop wrapping the body in a tbody tag, resulting in nested tbody tags?
<v-data-table
ref="dataTable"
:headers="headers"
fixed-header
:height="height"
:items="invoices"
item-key="id"
:search="search"
class="elevation-1"
disable-pagination
:loading="loading"
loading-text="Loading invoices... please wait..."
:no-data-text="this.noDataText"
no-results-text="No invoices found for your search."
:hide-default-footer="true"
>
<template v-slot:headers="props">
<thead>
<tr>
<th
v-for="header in props.headers"
:key="header.text"
ref="dataTableHdr"
:class="['column sortable', pagination.descending ? 'desc' : 'asc', header.value === pagination.sortBy ? 'active' : '', header.class === '']"
#click="changeSort(header.value)"
>
<v-icon small>arrow_upward</v-icon>
{{ header.text }}
</th>
</tr>
</thead>
</template>
<template v-slot:item="props">
<tbody>
<tr>
<td>{{ props.item.vendorCode }}</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
</tr>
<tr>
<td>{{ props.item.vendorName }}</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
</tr>
</tbody>
</template>
</v-data-table>

You can omit the tbody from the template since v-data-table puts the tbody around the results already when using v-slot:item.
<template v-slot:item="props">
<tr>
<td>{{ props.item.vendorCode }}</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
</tr>
<tr>
<td>{{ props.item.vendorName }}</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
</tr>
</template>

Related

Using v-for in a table

I have a table is populated with some info and I would like to format the table like the picture
Unfortunately the excel sheet which I have no control over is formatted so:
I want any row that has only a Equipment type to span whole row. All other rows should appear as normal table row.
I am using following vue template:
<table>
<caption>
SHS Scrap Table
</caption>
<thead>
<tr>
<th>Make</th>
<th>Model #</th>
<th>Bar Code</th>
<th>Serial #</th>
<th>Location</th>
<th>Condition</th>
</tr>
</thead>
<tbody v-for="item in scrapDataEmptyRowsRemoved" :key="item">
<tr v-if="item['Equipment Type']">
<td class="equipt-type" colspan="6">
Equipment Type - {{ item["Equipment Type"] }}
</td>
</tr>
<tr v-else>
<td>{{ item["Make"] }}</td>
<td>{{ item["Model #"] }}</td>
<td>{{ item["Bar Code"] }}</td>
<td>{{ item["Serial #"] }}</td>
<td>{{ item["Location"] }}</td>
<td>{{ item["Condition"] }}</td>
</tr>
</tbody>
</table>
The only problem is that looking in Devtools I see that every row has a Tbody which is not semantically correct. Any idea's on how to correct this. If I use a container around the v-if v-else all formatting breaks down.Thanks...
Update the only problem is Vite is objecting to moving :key attribute to the v-else:
I dont what other unique key they want.
Update II - Ok apparently if I use different object keys Vite is ok with that ie :key="item['Equipment Type'] and on v-else :key="item['Make']. Does that seem correct?
You can move the v-for in a template tag, that won't be rendered in the DOM.
<tbody>
<template v-for="item in scrapDataEmptyRowsRemoved" :key="item">
<tr v-if="item['Equipment Type']">
<td class="equipt-type" colspan="6">
Equipment Type - {{ item["Equipment Type"] }}
</td>
</tr>
<tr v-else>
<td>{{ item["Make"] }}</td>
<td>{{ item["Model #"] }}</td>
<td>{{ item["Bar Code"] }}</td>
<td>{{ item["Serial #"] }}</td>
<td>{{ item["Location"] }}</td>
<td>{{ item["Condition"] }}</td>
</tr>
</template>
</tbody>

Adding class to <td> and <tr> in Vuetify data-table?

<v-data-table
class="elevation-1 user-table"
:headers="table.headers"
:items="usersList"
:items-per-page="table.options.itemsPerPage"
:no-data-text="table.options.noDataText"
:footer-props="table.options.footerProps"
>
<template slot="items" slot-scope="props">
<td class="text-xs-right pa-4">{{ props.item.id }}</td>
<td class="text-xs-right pa-0">{{ props.item.username }}</td>
<td class="text-xs-right pa-0">{{ props.item.createdAt }}</td>
</template>
</v-data-table>
i try to use the slot="items" to change the class of td in vuetify table ,but it doesnt work?
i use this version of vuetify
"vuetify": "^2.3.17",
refactor item slot to this
<v-data-table
class="elevation-1 user-table"
:headers="table.headers"
:items="usersList"
:items-per-page="table.options.itemsPerPage"
:no-data-text="table.options.noDataText"
:footer-props="table.options.footerProps"
>
<template v-slot:item="{ item }">
<tr>
<td class="text-xs-right pa-4"> {{ item.id }} </td>
<td class="text-xs-right pa-0"> {{ item.username }} </td>
<td class="text-xs-right pa-0"> {{ item.createdAt }} </td>
</tr>
</template>
</v-data-table>
whats wrong with your code:
you used items instead of item => <template slot="item" slot-scope="props">
you missed tr element

transition-group without wrapping in a span

I am trying to apply a transition to a Vuetify v-data-table so that when I delete a tbody in my table, it fades out instead of just vanishing off the screen. If I wrap the tbody in a transition-group the tbody fades out as expected. However, it's wrapped in a span tag which means all of the columns in the tbody get shoved into the first column of my table.
I know I can specify a different tag other than a span, but they all cause my table layout to break. How can I apply a fade transition to the tbody and maintain my table layout?
<v-data-table
ref="dataTable"
:headers="headers"
fixed-header
:height="height"
:items="invoices"
item-key="id"
:search="search"
class="elevation-1"
disable-pagination
:loading="loading"
loading-text="Loading invoices... please wait..."
:no-data-text="this.noDataText"
no-results-text="No invoices found for your search."
:hide-default-footer="true"
>
<template v-slot:headers="props">
<thead>
<tr>
<th
v-for="header in props.headers"
:key="header.text"
ref="dataTableHdr"
:class="['column sortable', pagination.descending ? 'desc' : 'asc', header.value === pagination.sortBy ? 'active' : '', header.class === '']"
#click="changeSort(header.value)"
>
<v-icon small>arrow_upward</v-icon>
{{ header.text }}
</th>
</tr>
</thead>
</template>
<template v-slot:body="{ items }">
<transition-group tag="boo" name="invoice">
<tbody v-for="(item, index) in items" :key="item.id" #click="items.splice(index,1)">
<tr>
<td
:rowspan="updateRowCount(item.invoiceSections)"
valign="top"
class="pt-3 text-left font-weight-medium"
>{{ item.vendorCode }}</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
</tr>
<tr>
<td>{{ item.vendorName }}</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
</tr>
</tbody>
</transition-group>
</template>
</v-data-table>
Revised Code:
<template v-slot:body="{ items }">
<tbody v-for="(item, index) in items" :key="item.id">
<transition-group tag="tbody" name="invoice">
<tr key="row1">
<td
:rowspan="updateRowCount(item.invoiceSections)"
valign="top"
class="pt-3 text-left font-weight-medium"
>{{ item.vendorCode }}</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>
<v-btn #click="reject(items, index)" class="mx-2" v-blur fab small>
<v-icon color="red">mdi-cancel</v-icon>
</v-btn>
</td>
</tr>
<tr key="row2">
<td>{{ item.vendorName }}</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
<td>CONTENT</td>
</tr>
</transition-group>
</tbody>
</template>
<style>
.invoice-leave-active tr,
.invoice-leave-active tr:hover {
background-color: yellow !important;
}
.invoice-enter-active,
.invoice-leave-active {
transition: opacity 1s;
}
.invoice-enter, .invoice-leave-to /* .invoice-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>
Here is my final, super complex template, using v-if with changing the item.id to null.
<template v-slot:body="{ items }">
<transition-group tag="tbody" name="invoice" v-for="(item, index) in items" :key="index">
<tr
key="row1"
v-if="item.id"
:class="[item.duplicate ? 'duplicateItemRow' : '', {'success': item.id===selectedId}]"
>
<td
:rowspan="updateRowCount(item.invoiceSections)"
valign="top"
class="pt-3 text-left font-weight-medium"
>{{ item.vendorCode }}</td>
<td
:rowspan="updateRowCount(item.invoiceSections)"
valign="top"
class="pt-3 text-left font-weight-medium"
>{{ item.vendorName }}</td>
<td
:rowspan="updateRowCount(item.invoiceSections)"
valign="top"
class="pt-3 text-left font-weight-medium nowrap"
>{{ item.poNumber }}</td>
<td
:rowspan="updateRowCount(item.invoiceSections)"
valign="top"
class="pt-3 text-left font-weight-medium"
>
<v-tooltip bottom>
<template v-slot:activator="{ on }">
<a
:href="`/rvimain.pgm?rqstyp=senchcall1&isys=a&i7=INV&i4=${item.invoiceNumber}&i6=${item.poNumber}`"
class="pa-0 ma-0"
v-on="on"
target="_blank"
>{{ item.invoiceNumber }}</a>
</template>
<span>Click to view invoice</span>
</v-tooltip>
</td>
<td
:rowspan="updateRowCount(item.invoiceSections)"
valign="top"
class="pt-3 pr-6 text-right font-weight-medium"
>{{ item.invoiceDate | moment("M/D/YYYY") }}</td>
<td :class="item.invoiceAmount !== item.poAmount ? 'amountMismatchItemRow' : ''"></td>
<td
valign="top"
:class="['pt-3', 'font-weight-medium', 'nowrap', item.invoiceAmount !== item.poAmount ? 'amountMismatchItemRow' : '']"
>
<div v-if="item.duplicate">{{ item.duplicate }}</div>
<div
v-if="item.invoiceAmount !== item.poAmount"
>${{ item.poAmount | currency }} PURCHASE ORDER DOES NOT MATCH INVOICE</div>
</td>
<td :class="item.invoiceAmount !== item.poAmount ? 'amountMismatchItemRow' : ''"></td>
<td :class="item.invoiceAmount !== item.poAmount ? 'amountMismatchItemRow' : ''"></td>
<td :class="item.invoiceAmount !== item.poAmount ? 'amountMismatchItemRow' : ''"></td>
<td :class="item.invoiceAmount !== item.poAmount ? 'amountMismatchItemRow' : ''">
<CommentsDialog
v-if="item.invoiceSections[0].comments"
:itemId="item.invoiceSections[0].id"
:comments="item.invoiceSections[0].comments"
/>
</td>
<td
valign="top"
:class="['pt-3', 'text-right', 'font-weight-medium', 'nowrap', item.invoiceAmount !== item.poAmount ? 'amountMismatchItemRow' : '']"
>{{ item.invoiceAmount | currency }}</td>
<td
:rowspan="updateRowCount(item.invoiceSections)"
valign="top"
class="pt-3 text-center"
>
<div v-if="item.approvalDate && !item.rejectedDate">
<v-icon color="green">mdi-check</v-icon>
</div>
<div
v-if="!item.duplicate && !item.approvalDate && !item.rejectedDate && item.invoiceAmount === item.poAmount"
class="mb-4"
>
<v-tooltip bottom>
<template v-slot:activator="{ on }">
<v-btn #click="approveInvoice(item)" v-on="on" v-blur class="mx-2" fab small>
<v-icon color="green">mdi-check</v-icon>
</v-btn>
</template>
<span>Approve invoice</span>
</v-tooltip>
</div>
<RejectButton
v-if="!item.approvalDate && !item.rejectedDate"
:invoice="item"
#removeInvoice="removeInvoice"
/>
<div v-if="!item.approvalDate && item.rejectedDate">
<v-icon color="red" class="mb-4">mdi-cancel</v-icon>
<div>
Rejected
<br />
{{item.rejectedDate | moment("M/D/YYYY")}}
<br />
{{ item.rejectedBy }}
<br />
<RejectReasonDialog
v-if="item.rejectedReason"
:itemId="item.id"
:reason="item.rejectedReason"
/>
</div>
</div>
</td>
</tr>
<!-- SECTIONS -->
<template v-for="(section, index) in item.invoiceSections">
<tr
v-if="item.id && section.length > 0 && section.sectionDescription != ''"
:class="item.duplicate ? 'duplicateItemRow' : 'darkTableRow'"
:key="`${item.id}.${index}`"
>
<td class="text-left" colspan="2">{{ section.sectionDescription }}</td>
<td class="text-left" colspan="3">{{ section.repairReason }}</td>
<td class="text-right nowrap">{{ section.sectionTotal | currency }}</td>
<td>
<CommentsDialog
v-if="section.comments"
:itemId="section.id"
:comments="section.comments"
/>
</td>
</tr>
<!-- ITEMS -->
<tr
:class="[item.duplicate ? 'duplicateItemRow' : '', item.qtyRequested != item.qtyReceived ? 'qtyMismatchItemRow' : '' ]"
v-show="item.id"
v-for="(item, index) in section.invoiceItems"
:key="`${item.id}.${item.id}.${index}`"
>
<td class="text-left">{{ item.partNumber }}</td>
<td class="text-left">{{ item.partDescription }}</td>
<td class="text-right">
<v-tooltip v-if="item.qtyRequested != item.qtyReceived" bottom>
<template v-slot:activator="{ on }">
<span v-on="on" class="help">{{ item.qtyReceived }}</span>
</template>
<span>Requested {{ item.qtyRequested }}</span>
</v-tooltip>
<span v-else>{{ item.qtyReceived }}</span>
</td>
<td
:class="['text-right nowrap nopaddingRight', item.each > item.avgChargeAmount && item.avgChargeAmount != 0 ? 'over help' : '',
item.each < item.avgChargeAmount && item.avgChargeAmount != 0 ? 'under help' : '']"
>
<v-tooltip
v-if="item.each != item.avgChargeAmount && item.avgChargeAmount != 0"
bottom
>
<template v-slot:activator="{ on }">
<span v-on="on">{{ item.each | currency }}</span>
</template>
<span>Avg price: ${{ item.avgChargeAmount | currency }}</span>
</v-tooltip>
<span v-else>{{ item.each | currency }}</span>
</td>
<td
:class="['text-left nowrap lowpaddingLeft', item.each > item.avgChargeAmount && item.avgChargeAmount != 0 ? 'over' : '',
item.each < item.avgChargeAmount && item.avgChargeAmount != 0 ? 'under' : '']"
>
<v-tooltip v-if="item.nationalAccount" bottom>
<template v-slot:activator="{ on }">
<span v-on="on" class="help">N</span>
</template>
<span>
National
<br />Account
</span>
</v-tooltip>
</td>
<td class="text-right nowrap">{{ item.lineTotal | currency }}</td>
<td class="text-left">
<v-btn
outlined
small
color="primary"
dark
v-if="item.comments"
#click.stop="$set(dialogComments, item.id, true)"
>Comments</v-btn>
<CommentsDialog v-if="item.comments" :itemId="item.id" :comments="item.comments" />
</td>
</tr>
</template>
<tr key="row3" v-if="item.id">
<td colspan="13" class="divider"> </td>
</tr>
</transition-group>
</template>
You can use the transition group as your tbody, with the tag attribute like tag="tbody". So the transition group will display as a tbody instead of a span. The immediate children of the transition group also need unique keys and a v-if or v-show condition. The if or show condition also has to be triggered after whatever content you're looping through is ready, in order to se the transition. The click function will work inside of your table, but not on the tbody.
https://v2.vuejs.org/v2/guide/transitions.html#List-Transitions
<transition-group tag="tbody" name="invoice" v-for="(item, index) in items" :key="index">
<tr key="row1" v-if="showRow">
// Your columns
</tr>
<tr key="row2" v-if="showRow">
// Your columns
</tr>
</transition-group>
You can also use a tbody with the is attribute: is="transition-group", but I prefer the above option because the required name attribute of transition-group is not an allowed attribute of tbody. https://v2.vuejs.org/v2/api/#is
<tbody is="transition-group" name="invoice">
...
</tbody>

Get count of multiple nested items

I am displaying a Vuetify datatable and need to span some rows based on the count of nested data in the sample JSON below. The rowspan value should be a count of invoiceSections and invoiceItems.
I can get the count/length of invoiceSections but am having trouble with the count/length of invoiceItems.
[{
"vendorCode": "LOTMWI",
"vendorName": "LOVES TIRE CARE",
"orderId": "944803",
"invoiceSections": [{
"sectionDescription": "Lamps - Tail, Stop, Turn & License, Rear",
"repairReason": "INSPECTION",
"invoiceItems": [{
"partNumber": "",
"partDescription": "REPAIR LABOR",
"qtyReceived": 0,
},
{
"partNumber": "TL44032R",
"partDescription": "44032R S/T/T SUPER MODEL 44LED R",
"qtyReceived": 2,
},
{
"partNumber": "IMBRASS02",
"partDescription": "BRASS FITTINGS 02",
"qtyReceived": 1,
},
],
"id": 1,
}],
"id": 171,
}
]
A snipped of my code (trying to display the count to debug first):
<template v-slot:item="props">
<!-- <tbody> -->
<tr>
<td
:rowspan="rowspan"
valign="top"
class="pt-3 text-left font-weight-medium"
>{{ props.item.vendorCode }} {{ props.item.invoiceSections.length + 1}} - {{ props.item.invoiceSections.invoiceItems.length }}</td>
<td
:rowspan="rowspan"
valign="top"
class="pt-3 text-left font-weight-medium"
>{{ props.item.vendorName }}</td>
<td
:rowspan="rowspan"
valign="top"
class="pt-3 text-left font-weight-medium"
>{{ props.item.poNumber }}</td>
<td :rowspan="rowspan" valign="top" class="pt-3 text-left font-weight-medium">
<a
href="#"
data-tooltip="Click to view invoice"
data-tooltip-position="bottom"
target="_blank"
>{{ props.item.invoiceNumber }}</a>
</td>
<td
:rowspan="rowspan"
valign="top"
class="pt-3 text-left font-weight-medium"
>{{ props.item.invoiceDate | moment("M/D/YY") }}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td
valign="top"
class="pt-3 text-right font-weight-medium nowrap"
>{{ props.item.invoiceAmount | currency }}</td>
<td :rowspan="rowspan" valign="top" class="pt-3 text-center">
<div v-if="props.item.approvalDate">
<v-icon color="green">mdi-check</v-icon>
</div>
<div v-else>
<v-btn #click="markApproved(props.item)" class="mx-2" fab small>
<v-icon color="grey lighten-2">mdi-check</v-icon>
</v-btn>
</div>
</td>
</tr>
<!-- SECTIONS -->
<template v-for="section in props.item.invoiceSections">
<tr class="darkTableRow" :key="section.id">
<td class="text-left" colspan="2">{{ section.sectionDescription }}</td>
<td class="text-left" colspan="3">{{ section.repairReason }}</td>
<td>$ 999</td>
<td>
<v-btn
outlined
small
color="primary"
dark
v-if="section.comments"
#click.stop="$set(dialogComments, section.id, true)"
>Comments</v-btn>
</td>
</tr>
<!-- ITEMS -->
<tr v-for="item in section.invoiceItems" :key="item.id">
<td class="text-left">{{ item.partNumber }}</td>
<td class="text-left">{{ item.partDescription }}</td>
<td class="text-right">{{ item.qtyReceived }}</td>
<td
class="text-right nowrap nopaddingRight"
:class="item.comparison"
>{{ item.each | currency }}</td>
<td class="text-left nowrap nopaddingLeft" :class="item.comparison">
<span
v-if="item.nationalAccount"
data-tooltip="National Account"
data-tooltip-position="right"
>N</span>
</td>
<td class="text-right nowrap">{{ item.secTotal | currency }}</td>
<td class="text-left">
<v-btn
outlined
small
color="primary"
dark
v-if="item.comments"
#click.stop="$set(dialogComments, item.id, true)"
>Comments</v-btn>
<v-dialog
v-model="dialogComments[item.id]"
scrollable
lazy
max-width="600"
:key="item.id"
>
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>Comments</v-card-title>
<v-divider></v-divider>
<v-card-text v-html="item.comments" class="pt-4"></v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="green darken-1"
outlined
#click.stop="$set(dialogComments, item.id, false)"
>Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</td>
</tr>
</template>
<tr>
<td colspan="13" class="divider"> </td>
</tr>
<!-- </tbody> -->
</template>

Vuetify table - custom css isn't applied to first row after page reload

Table:
<v-card :dark="true">
<v-card-title>
<v-btn color="indigo" dark #click="initialize"><v-icon dark>refresh</v-icon></v-btn>
<v-spacer></v-spacer>
<v-text-field append-icon="search" label="Search" single-line hide-details></v-text-field>
</v-card-title>
<v-data-table :dark="true" :headers="headers" :items="expenses" hide-actions class="elevation-1">
<template slot="headers" slot-scope="props">
<tr>
<th v-for="header in props.headers">{{header.text}}</th>
</tr>
</template>
<template slot="items" slot-scope="props">
<tr v-bind:class="getClass(props.item)">
<td class="text-xs-center">{{ props.item.year }}</td>
<td class="text-xs-center">{{ props.item.month }}</td>
<td class="text-xs-center">{{ props.item.day }}</td>
<td class="text-xs-center">{{ props.item.description }}</td>
<td class="text-xs-center">{{ props.item.value }}</td>
<td class="text-xs-center">{{ props.item.category }}</td>
<td class="text-xs-center">{{ props.item.details }}</td>
<td class="justify-center layout px-0">
<v-btn icon class="mx-0" #click="deleteItem(props.item)">
<v-icon color="pink">delete</v-icon>
</v-btn>
</td>
</tr>
</template>
<template slot="no-data">
<v-btn color="primary" #click="initialize">Reset</v-btn>
</template>
</v-data-table>
</v-card>
Css before page reload, after the code is edited in Webstorm and automatically compiled:
And after the reload:
And if I just remove the first row, the same happens no matter which one is first.
I had the same problem.
They problem here is that You override the items slot including <tr> tag. Without that everything will work fine. But for me, that was not a solution so if You want to override the <tr> tag also, add :key to it, like this: <tr :key="props.index">.
Take a look at source of v-data-table here.
To be honest, i don't know why it make that big difference but in my case that resolved the problem (i suspect that it is connected with vue list rendering).
Hope it helps!