Vuetify 2 grouped data table with customized group header and item rows - vue.js

My goal is to create a Vuetify 2 data table from a list of car models. Data needs to be grouped by vendor with a customized group header row and also the item rows for each car model needs to be customized. Below is a very reduced example to show my main problem which is that Vuetify fully ignores my template for the item-slot and uses the default behaviour instead.
How can I make Vuetify use that template as well with avoiding to use a single template for each item column? ... because in my real world example there are a lot of columns which needs to be customized.
Vue code:
<div id="app">
<v-app>
<v-data-table
dense
disable-sort
:headers="headers"
hide-default-footer
:items="cars"
item-key="id"
group-by="vendor"
>
<template v-slot:group.header="{items, isOpen, toggle}">
<th colspan="2">
<v-icon #click="toggle"
>{{ isOpen ? 'mdi-minus' : 'mdi-plus' }}
</v-icon>
{{ items[0].vendor }}
</th>
</template>
<template v-slot:item="{ item }">
<tr>
<td><strong>{{ item.name }}</strong></td>
<td>{{ item.power }} HP</td>
</tr>
</template>
</v-data-table>
</v-app>
</div>
Javascript code:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
headers: [
{ text: 'Model name', value: 'name' },
{ text: 'Power', value: 'power' }
],
cars: [
{
id: 1,
name: '320i',
vendor: 'BMW',
power: 170
},
{
id: 2,
name: 'M5',
vendor: 'BMW',
power: 350
},
{
id: 3,
name: 'Golf GTD',
vendor: 'Volkswagen',
power: 184
},
{
id: 4,
name: 'Polo GTI',
vendor: 'Volkswagen',
power: 190
}
]
}
}
})
Codepen demo can be found here.

The following behavior is a bug in previous Vuetify versions. Using Vuetify 2.4.9+ (or even earlier) will fix your problem.
Check this codepen: https://codepen.io/seaskyways/pen/VwPbyER
It's the same as yours just with updated Vuetify.
// and this is some code cuz stackoverflow doesn't allow codepens without code

Related

Can Vue.Draggable be used with Vuetify v-data-table and allow utilisation of table v-slot:item.<name>?

Vuetify v-data-table supports several types of slots: v-slot:body, v-slot:item and v-slot:item.<name>.
We have been using v-slot:item.<name> extensively, as these provides a flexible way to style and process content in individual columns AND allow the table headers to be programmatically changed.
I'd like to add draggability to my v-data-table rows and have got this working using Vue.Draggable.
However the draggable component requires use of the v-data-table v-slot:body i.e. taking control of the full body of the table and thereby losing the flexibility of v-slot:item.<name>.
Is there a way these two components can be used together and provide v-slot:item.<name> support?
I have created a DataTableRowHandler component which allows v-slot:item.<name> support.
This is placed inside the draggable component, inserts the table <tr> element and feeds off the same "headers" array to insert <td> elements and v-slot:item.<name> entries. If no v-slot:item.<name> is defined then the cell value is output, in the same way that v-data-table works.
Here is the example component usage:
<v-data-table
ref="myTable"
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
class="elevation-1"
>
<template v-slot:body="props">
<draggable
:list="props.items"
tag="tbody"
:disabled="!allowDrag"
:move="onMoveCallback"
:clone="onCloneCallback"
#end="onDropCallback"
>
<data-table-row-handler
v-for="(item, index) in props.items"
:key="index"
:item="item"
:headers="headers"
:item-class="getClass(item)"
>
<template v-slot:item.lock="{ item }">
<v-icon #click="item.locked = item.locked ? false : true">{{
item.locked ? "mdi-pin-outline" : "mdi-pin-off-outline"
}}</v-icon>
</template>
<template v-slot:item.carbs="{ item }">
{{ item.carbs }}
<v-icon>{{
item.carbs > 80
? "mdi-speedometer"
: item.carbs > 45
? "mdi-speedometer-medium"
: "mdi-speedometer-slow"
}}</v-icon>
</template>
</data-table-row-handler>
</draggable>
</template>
</v-data-table>
Here is the DataTableRowHandler component code
<template>
<tr :class="getClass">
<td v-for="(header, index) in headers" :key="index">
<slot :item="item" :name="columnName(header)">
<div :style="getAlignment(header)">
{{ getNonSlotValue(item, header) }}
</div>
</slot>
</td>
</tr>
</template>
<script>
export default {
name: "DataTableRowHandler",
components: {},
props: {
itemClass: {
type: String,
default: "",
},
item: {
type: Object,
default: () => {
return {};
},
},
headers: {
type: Array,
default: () => {
return [];
},
},
},
data() {
return {};
},
computed: {
getClass() {
return this.itemClass;
}
},
methods: {
columnName(header) {
return `item.${header.value}`;
},
getAlignment(header) {
const align = header.align ? header.align : "right";
return `text-align: ${align}`;
},
getNonSlotValue(item, header) {
const val = item[header.value];
if (val) {
return val;
}
return "";
},
},
};
</script>
An example of it's use is in this codesandbox link
I checked the source code of VDataTable and found that the content in tbody is generated by genItems().
The following example is implemented with functional components and is fully compatible with v-slot:item*:
<v-data-table ref="table" ...>
<template #body="props">
<draggable
v-if="$refs.table"
tag="tbody"
:list="props.items"
>
<v-nodes :vnodes="$refs.table.genItems(props.items, props)" />
</draggable>
</template>
<template #item.name="{ item }">
...
</template>
</v-data-table>
Here is the definition of the VNodes component:
components: {
VNodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes,
}
}

Stable, unchanged numeration of rows in Vuetify Data Table

I'm learning Vue and Vuetify at the moment and I've faced the problem of indexing rows in data tables.
The only way I can assign the number of a row is relying on indexOf comparing to the raw array of data.
But the problem in my case is that when I sort data in the table index breaks accordingly.
I would like to have it stable and when you sort data it recalculates.
Is there any way to achieve it in Vue?
In Angular it's much easier with tables. There is a build-in functionality
Or maybe you know how to reach the filtered or sorted data (array) where Vue holds it.
<v-data-table
:headers="headers"
:items="orders"
class="elevation-2 mt-4"
:loading="loading"
loading-text="Loading... Please wait"
>
<template v-slot:item.index="{ item }">
{{ orders.indexOf(item) + 1}}
</template>
</v-data-table>
Thanks in advance!
The way I've managed to get it working (thanks to #elushnikova) is like this:
<v-data-table
:headers="headers"
:items="orders"
class="elevation-2 mt-4"
:loading="loading"
loading-text="Loading... Please wait"
>
<template v-slot:item="{item, index}">
<tr>
<td>{{index + 1}}</td>
<td>{{item.client_name}}</td>
<td>{{item.client_id}}</td>
<td>{{item.order_total}}</td>
</tr>
</template>
</v-data-table>
It doesn't break on sorting. But I believe later I'll have another problem with it when I have pagination.
But let's solve problems as they come :)
I found this in this link
<template #item.index="{ item }">
{{ protocolData.indexOf(item) }}
</template>
and its worked for me.
Closest way to get the result is by using a computed property as show in codepen
https://codepen.io/manojkmishra/pen/OJyYzVo
Template Part:
<div id="app">
<v-app>
<h3>Orders</h3>
<v-data-table
:headers="headers"
:items="ordersWithIndex"/>
</v-app>
</div>
Script Part:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
headers: [{ text: 'Num', value: 'index' },
{ text: 'OrderName', value: 'Name' },
{ text: 'OrderValue', value: 'value' }
],
orders: [ {Name: 'order1', value: 100 },
{ Name: 'order2', value: 200 },
{ Name: 'order3', value: 300 },
{ Name: 'order4', value: 400 }
],
}
},
computed: {
ordersWithIndex()
{ return this.orders.map(
(items, index) => ({
...items,
index: index + 1
}))
}
}
})
This can be achieved in vuetify using item. slot, you need to pass a header for index along with other headers (eg. SNO).
now just use this in v-data-table.
<template v-slot:item.SNO = "{ index }">
{{ index + 1 }}
</template>

Vuetify insert action button in data-table and get row data

Environment:
vue#^2.6.10:
vuetify#^2.1.0
I want to use v-data-table to show search results and add evaluate button in each row in the v-data-table.
Unfortunately I have two issues:
Evaluate buttons are not shown
I don't know how to get row data of pushed button
What do I need to change?
Template
<v-data-table
:headers="headers"
:items="search_result"
>
<template slot="items" slot-scope="row">
<td>{{row.item.no}}</td>
<td>{{row.item.result}}</td>
<td>
<v-btn class="mx-2" fab dark small color="pink">
<v-icon dark>mdi-heart</v-icon>
</v-btn>
</td>
</template>
</v-data-table>
Script
data () {
return {
headers: [
{ text: 'no', value: 'no' },
{ text: 'result', value: 'result' },
{ text: 'good', value: 'good'},
],
// in real case initial search_result = [], and methods: search function inject below data
search_result: [{no: 0, result: 'aaa'}, {no:2, result: 'bbb'],
}
},
slot name used to "replace the default rendering of a row" is item, not items
Add wrapping <tr> into slot template
Just add #click="onButtonClick(row.item) to v-btn and create method onButtonClick
<v-data-table :headers="headers" :items="search_result">
<template v-slot:item="row">
<tr>
<td>{{row.item.no}}</td>
<td>{{row.item.result}}</td>
<td>
<v-btn class="mx-2" fab dark small color="pink" #click="onButtonClick(row.item)">
<v-icon dark>mdi-heart</v-icon>
</v-btn>
</td>
</tr>
</template>
</v-data-table>
methods: {
onButtonClick(item) {
console.log('click on ' + item.no)
}
}
Note..
...solution above is replacing default row rendering with your own so expect some of the v-data-table features to not work (didn't try but I expect row selection, grouping, in place editing etc. will be broken). If that's problem for you, here is alternative solution:
Add one more column to your headers definition: { text: "", value: "controls", sortable: false }
Do not override item slot (row rendering). Override item.controls slot instead. Notice "controls" is the same as in column definition - we are overriding just rendering of "controls" column
Everything else is same
<v-data-table :headers="headers" :items="search_result">
<template v-slot:item.controls="props">
<v-btn class="mx-2" fab dark small color="pink" #click="onButtonClick(props.item)">
<v-icon dark>mdi-heart</v-icon>
</v-btn>
</template>
</v-data-table>
In my case the solution of Michal was throwing the following exception
The solution was using slot and slot-scope
<template>
<v-data-table
:headers="headers"
:items="items"
class="elevation-1"
>
<template slot="item.delete" slot-scope="props">
<v-btn class="mx-2" icon #click="() => delete(props.item)">
<v-icon dark>mdi-delete</v-icon>
</v-btn>
</template>
</v-data-table>
</template>
<script>
export default {
data() {
return {
headers: [
// Dynamic headers
{
text: 'Name',
value: 'name'
},
{
text: '',
// Row to replace the original template
value: 'delete'
},
],
items: [
{
id: 1,
name: 'A'
},
{
id: 2,
name: 'B'
}
]
};
},
methods: {
delete(item) {
this.items = this.items.filter((d) => d.id !== item.id);
},
},
};
</script>

How to get item value in v-slot:cell() template of b-table - BootstrapVue

I'm a very new at programming. I'm trying to figure it out how to bind the data to get the link :href work using store, vuex and bootstrap-vue table. I have spent 4 days for this, and now I'm dying. Please help.
books.js(store, vuex)
books: [
{
id: 1,
name: "name 1",
bookTitle: "book1",
thumbnail: '../../assets/img/book01.jpeg',
url: "https://www.google.com",
regDate: '2019-10'
},
{
id: 2,
name: "name2",
bookTitle: "book2",
thumbnail: "book2",
url: "http://www.yahoo.com",
regDate: '2019-10'
},
BookList.vue
<script>
export default {
name: "BookList",
components: {
},
computed: {
fields() {
return this.$store.state.fields
},
books() {
return this.$store.state.books
},
bookUrl() {
return this.$store.state.books.url
}
},
data() {
return {
itemFields: this.$store.state.fields,
items: this.$store.state.books,
//url: this.$store.state.books.url
}
}
};
</script>
<template>
<b-container>
<b-table striped hover :items="items" :fields="itemFields" >
<template v-slot:cell(thumbnail)="items">
<img src="" alt="image">
</template>
<template v-slot:cell(url)="items">
<b-link :href="bookUrl" >link</b-link>
</template>
</b-table>
</b-container>
</template>
The cell slot contains two properties you're generally interested in:
item (the current row, or, to be exact, the current item in items)
value (the cell - or, to be exact, the value of the current column within the item).
Therefore, considering your data, in the case of v-slot:cell(url)="{ value, item }", value is equivalent to item.url
Any of these would work:
<template v-slot:cell(url)="{ value }">
<b-link :href="value">link</b-link>
</template>
<template v-slot:cell(url)="slot">
<b-link :href="slot.value">{{ slot.item.bookTitle }}</b-link>
</template>
<template v-slot:cell(url)="{ item }">
<b-link :href="item.url">{{ item.bookTitle }}</b-link>
</template>
Working example here.
Note your question contains a few minor issues which might prevent your code from working (itemFields is referenced but not defined, not using proper getters, etc...). For details have a look at the working example.
And read the docs!

Why bootstrap table example not working for me?

I have a some project with table like this bootstrap table.
Codesandbox
template:
<b-table small :fields="fields" :items="items">
<template v-slot:cell(index)="data">
{{ data.index + 1 }}
</template>
<!-- A custom formatted column -->
<template v-slot:cell(name)="data">
<b class="text-info">{{ data.value.last.toUpperCase() }}</b>, <b>{{ data.value.first }}</b>
</template>
<!-- A virtual composite column -->
<template v-slot:cell(nameage)="data">
{{ data.item.name.first }} is {{ data.item.age }} years old
</template>
<!-- Optional default data cell scoped slot -->
<template v-slot:cell()="data">
<i>{{ data.value }}</i>
</template>
</b-table>
And script:
fields: [
// A virtual column that doesn't exist in items
'index',
// A column that needs custom formatting
{ key: 'name', label: 'Full Name' },
// A regular column
'age',
// A regular column
'sex',
// A virtual column made up from two fields
{ key: 'nameage', label: 'First name and age' }
],
items: [
{ name: { first: 'John', last: 'Doe' }, sex: 'Male', age: 42 },
{ name: { first: 'Jane', last: 'Doe' }, sex: 'Female', age: 36 },
{ name: { first: 'Rubin', last: 'Kincade' }, sex: 'Male', age: 73 },
{ name: { first: 'Shirley', last: 'Partridge' }, sex: 'Female', age: 62 }
]
Bootstrap table works. I am copy this code and example not work. And I do not understand why.
Question: So, Why bootstrap table example not working?
Helo, i have same problem.
I have some custom fields table, on vue devtools the data has been seen. in vuex bindings.
But on custom table no data can appear, just the amount of data available.
This is my template code
<template>
<div class="table-responsive">
<b-table striped hover :items="todos.data" :fields="fields" show-empty>
<template slot="jadwal" slot-scope="row">
<td class="parent-row">{{ row.item.name }}</td>
</template>
</b-table>
</div>
</template>
and this object vuex binding
{"current_page":1,"data":[{"id":1,"name":"Belajar Vue Laravel","note":"Apa aja boleh deh ini deskripsinnya","due_date":"2019-09-30","status":0,"created_at":"2019-09-29 18:23:51","updated_at":"2019-09-29 18:23:51"},{"id":2,"name":"Belajar mengerti kamu","note":"you are my everythings","due_date":"2019-10-01","status":1,"created_at":"2019-09-29 18:23:51","updated_at":"2019-09-29 18:23:51"}],"first_page_url":"http://localhost:8000/api/todo?page=1","from":1,"last_page":1,"last_page_url":"http://localhost:8000/api/todo?page=1","next_page_url":null,"path":"http://localhost:8000/api/todo","per_page":10,"prev_page_url":null,"to":2,"total":2}