Vuetify 2.x: v-data-table expand row on click - vuetify.js

I am using Vuetify.js and I am creating a a v-data-table
I cannot figure out how to expand a row by clicking anywhere on it, it seems that the only possibility is by using the show-expand property and clicking the icon
Final solution
As suggested here, the v-data-table can link an array to its expanded property, so if you push any item from the table, to this array, it will expand the corresponding row. Intuitive and smart
<template>
<v-data-table
:headers="headers"
:items="items"
:expanded="expanded"
item-key="id"
#click:row="expandRow"
/>
</template>
<script>
data () {
return {
expanded: [],
}
},
expandRow (item) {
// would be
// this.expanded = [item]
// but if you click an expanded row, you want it to collapse
this.expanded = item === this.expanded[0] ? [] : [item]
},
</script>

It worked for me:
<v-data-table
...
#click:row="(item, slot) => slot.expand(!slot.isExpanded)"
/>
Api docs here

There is a click event which gives you the current row on the v-data-table. This one you can use. In the event you update the expanded value
Like this:
HTML:
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
:expanded="expanded"
item-key="name"
show-expand
class="elevation-1"
#click:row="clicked">
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>Expandable Table</v-toolbar-title>
<div class="flex-grow-1"></div>
<v-switch v-model="singleExpand" label="Single expand" class="mt-2"></v-switch>
</v-toolbar>
</template>
<template v-slot:expanded-item="{ headers }">
<td :colspan="headers.length">Peek-a-boo!</td>
</template>
</v-data-table>
</v-app>
</div>
vue js:
new Vue({
el: '#app',
vuetify: new Vuetify(),
methods: {
clicked(value) {
this.expanded.push(value)
}
},
data () {
return {
expanded: ['Donut'],
singleExpand: false,
headers: [
{
text: 'Dessert (100g serving)',
align: 'left',
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' },
],
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%',
}
],
}
},
})

i am not sure if you still need this :P but i wanted to improve dreijntjens reply for people who see this post
so add a listener attr
<v-data-table
:headers="headers"
:items="desserts"
:expanded="expanded"
item-key="name"
show-expand
class="elevation-1"
#click:row="clicked">
and add this method
clicked (value) {
const index = this.expanded.indexOf(value)
if (index === -1) {
this.expanded.push(value)
} else {
this.expanded.splice(index, 1)
}
},
my method will actually both expand and close on click

If you are using v-slot:item, or you set the prop :hide-default-header="true", then you can implement it this way:
<template v-slot:item="{item, index}">
<tr #click="expandedIndex = expandedIndex==-1?index:-1">
<span> {{item}} </span>
</tr>
<tr :colspan="headers.length" v-if="expandedIndex==index">Expanded content</tr>
</template>
If you want to be able to expand multiple rows at the same time, you can do this:
<v-data-table :headers="headers" :items="items">
<template v-slot:item="{item, index}">
<tr #click="expandRow(index)">
<span> {{item}} </span>
</tr>
<tr v-if="expandedRows.includes(index)">
<td :colspan="headers.length">
Expanded content
</td>
</tr>
</template>
</v-data-table>
<script>
...
...
expandedRows=[];
expandRow(index){
const indexOfRow = this.expandedRows.indexOf(index)
if(indexOfRow>-1){
this.expandedRows.splice(indexOfRow,1);
return;
}
this.expandedRows.push(index);
}
</script>

Related

Implement vue draggable in a table. Laravel

I'm trying to create a survey with the option to order the questions that are displayed in a table when creating the survey.
I'm using vue draggable and the example works but I don't know how to use this with a table and still get the rows to be draggable
Example
<draggable v-model="section" #start="drag=true" #end="drag=false">
<div v-for="section in surveySections" :key="section.id">{{section.title}}</div
</draggable>
This is the table
<el-table
:data="form.question_id"
border>
<draggable v-model="surveyQuestions" #start="drag=true" #end="drag=false">
<el-table-column prop="title" label="Pregunta"></el-table-column>
<el-col :xs="5">
<el-table-column fixed="right" label="Operaciones">
<template slot-scope="scope">
<el-button
#click.native.prevent="deleteRow(scope.$index, form.question_id)"
type="text" size="small">
<span class="icon-create">Eliminar</span>
<i class="el-icon-delete-solid"></i>
</el-button>
</template>
</el-table-column>
</el-col>
</draggable>
</el-table>
How can I get this to work?
I must have 50 reputation to comment!
SO.
you can see elementUI Table组件实现拖拽效果
e.g
npm install sortablejs --save
// Element table must specify row-key . Otherwise, the order will be wrong
import Sortable from 'sortablejs'
<template>
<div style="width:800px">
<el-table :data="tableData"
border
row-key="id"
align="left">
<el-table-column v-for="(item, index) in col"
:key="`col_${index}`"
:prop="dropCol[index].prop"
:label="item.label">
</el-table-column>
</el-table>
<pre style="text-align: left">
{{dropCol}}
</pre>
<hr>
<pre style="text-align: left">
{{tableData}}
</pre>
</div>
</template>
<script>
import Sortable from 'sortablejs'
export default {
data() {
return {
col: [
{
label: '日期',
prop: 'date'
},
{
label: '姓名',
prop: 'name'
},
{
label: '地址',
prop: 'address'
}
],
dropCol: [
{
label: '日期',
prop: 'date'
},
{
label: '姓名',
prop: 'name'
},
{
label: '地址',
prop: 'address'
}
],
tableData: [
{
id: '1',
date: '2016-05-02',
name: '王小虎1',
address: '上海市普陀区金沙江路 100 弄'
},
{
id: '2',
date: '2016-05-04',
name: '王小虎2',
address: '上海市普陀区金沙江路 200 弄'
},
{
id: '3',
date: '2016-05-01',
name: '王小虎3',
address: '上海市普陀区金沙江路 300 弄'
},
{
id: '4',
date: '2016-05-03',
name: '王小虎4',
address: '上海市普陀区金沙江路 400 弄'
}
]
}
},
mounted() {
this.rowDrop()
this.columnDrop()
},
methods: {
//行拖拽
rowDrop() {
const tbody = document.querySelector('.el-table__body-wrapper tbody')
const _this = this
Sortable.create(tbody, {
onEnd({ newIndex, oldIndex }) {
const currRow = _this.tableData.splice(oldIndex, 1)[0]
_this.tableData.splice(newIndex, 0, currRow)
}
})
},
//列拖拽
columnDrop() {
const wrapperTr = document.querySelector('.el-table__header-wrapper tr')
this.sortable = Sortable.create(wrapperTr, {
animation: 180,
delay: 0,
onEnd: evt => {
const oldItem = this.dropCol[evt.oldIndex]
this.dropCol.splice(evt.oldIndex, 1)
this.dropCol.splice(evt.newIndex, 0, oldItem)
}
})
}
}
}
</script>
<style scoped>
</style>
element ui table Sortable.js

Problem with expandable child component in parent v-data-table using Vuetify 2

I upgraded to Vuetify 2 from 1.5. Everything went pretty smoothly except for one thing. I have a parent component with a v-data-table and I want to pass data and expand each row with a child component.
ScanGrid(parent component):
<template>
<v-container>
<v-card>
<v-card-text>
<v-layout row align-center>
<v-data-table
:headers="headers"
:items="items"
:hide-default-footer="true"
item-key="id"
>
<template slot="items" slot-scope="props">
<tr #click="props.expanded = !props.expanded">
<td>{{ props.item.name }}</td>
<td class="text-xs-left large-column">
{{ props.item.scanned }}
</td>
<td class="text-xs-left large-column">
{{ props.item.incoming }}
</td>
<td class="text-xs-left large-column">
{{ props.item.outgoing }}
</td>
<td class="text-xs-left large-column">
{{ props.item.unknown }}
</td>
</tr>
</template>
<template slot="expand" slot-scope="props">
<ScanGridChild :value="props.item"></ScanGridChild>
</template>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</v-container>
</template>
ScanGridChild(child component):
<template>
<v-card>
<v-card-text>{{ value }}</v-card-text>
</v-card>
</template>
<script>
export default {
name: "ScanGridChildComponent",
props: {
value: {
Type: Object,
Required: true
}
},
computed: {},
watch: {
props: function(newVal, oldVal) {
console.log("Prop changed: ", newVal, " | was: ", oldVal);
this.render();
}
}
};
</script>
<style></style>
It worked fine in Vuetify 1.5.19. I'm on Vuetify 2.1.6 and using single file components. Thanks.
Vuetify 2.x has major changes to many components, slot-scopes are replaced with v-slot, and many new properties and slots added to vuetify data table
Here is the working codepen reproduced the same feature with above code
https://codepen.io/chansv/pen/BaaWbKR?editors=1010
You need to make sure that you have vue js 2.x and vuetify 2.x
Parent component code:
<template>
<v-container>
<v-card>
<v-card-text>
<v-layout row align-center>
<v-data-table
:headers="headers"
:items="items"
item-key="name"
single-expand
:expanded="expanded"
hide-default-footer
#click:row="clickedRow"
>
<template v-slot:expanded-item="{ item }">
<td :colspan="headers.length">
<ScanGridChild :value="item"></ScanGridChild>
</td>
</template>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</v-container>
</template>
<script>
export default {
...
data () {
return {
expanded: [],
headers: [
{
text: "Localisation",
sortable: true,
value: "name"
},
{
text: "Paquets scannés",
sortable: true,
value: "scanned"
},
{
text: "Paquets entrants",
sortable: true,
value: "incoming"
},
{
text: "Paquets sortants",
sortable: true,
value: "outgoing"
},
{
text: "Paquets inconnus",
sortable: true,
value: "unknown"
}
],
items: [
{
id: 1,
name: "Location 1",
scanned: 159,
incoming: 6,
outgoing: 24,
unknown: 4,
test: "Test 1"
},
{
id: 2,
name: "Location 2",
scanned: 45,
incoming: 6,
outgoing: 24,
unknown: 4,
test: "Test 2"
}
],
}
},
methods: {
clickedRow(value) {
if (this.expanded.length && this.expanded[0].id == value.id) {
this.expanded = [];
} else {
this.expanded = [];
this.expanded.push(value);
}
}
}
...
}
</script>
In child component
Replace
props: {
value: {
Type: Object,
Required: true
}
},
with( Type and Required change to lower case type and required)
props: {
value: {
type: Object,
required: true
}
},

V-data-table controlling expanded items via v-slot:body

The documentation on vuetify 2.0 v-data-tables doesn't specify how to control the expanded items via the v-slot:body. I have a table i need to specify with the body v-slot and would like to use the expand feature.
Expected behavior is by clicking the button in one column in the table, the row expands below.
I'm using the v-slot:body as i will need to fully customize the column content. I'm migrating the code from vuetify 1.5 where props.expanded enabled this functionality.
Codepen: https://codepen.io/thokkane/pen/PooemJP
<template>
<v-data-table
:headers="headers"
:items="deserts"
:expanded.sync="expanded"
:single-expand="singleExpand"
item-key="name"
hide-default-footer
>
<template v-slot:body="{ items }">
<tbody>
<tr v-for="item in items" :key="item.name">
<td>
<v-btn #click="expanded.includes(item.name) ? expanded = [] : expanded.push(item.name)">Expand</v-btn>
</td>
</tr>
</tbody>
</template>
<template v-slot:expanded-item="{ headers, item }">
<span>item.name</span>
</template>
</v-data-table>
</template>
<script>
export default {
data () {
return {
expanded: [],
singleExpand: false,
headers: [
{ text: 'Dessert (100g serving)', align: 'left', 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%',
},
],
}
},
}
</script>
When you use v-slot:body together with v-slot:expanded-item, you are overriding your changes inside v-slot:expanded-item. This is because expanded-item slot is inside the body slot. If you are going to use body for customization, you unfortunately have to customize everything inside.
Imagine a structure like this:
<div slot="body">
<div slot="item">...</div>
<div slot="expanded-item">...</div>
etc...
<div>
So in this case <template v-slot:body> will replace <div slot="body"> and anything inside. Therefore usage of <template v-slot:expanded-item> will not work with <template v-slot:body>. Besides v-slot:body props does not include item-specific properties and events like select(), isSelected, expand(), isExpanded etc.
I would recommend you to use v-slot:item instead. You can accomplish same thing without needing to customize everything with less code.
Something like this should work:
<template v-slot:item="{ item, expand, isExpanded }">
<tr>
<td>
<v-btn #click="expand(!isExpanded)">Expand</v-btn>
</td>
<td class="d-block d-sm-table-cell" v-for="field in Object.keys(item)">
{{item[field]}}
</td>
</tr>
</template>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length">Expanded Content</td>
</template>
If you want to have access to expanded items in JavaScript, don't forget to add :expanded.sync="expanded" to <v-data-table> . To open unique item on click you need to set item-key="" property on <v-data-table>.
With newer versions you can achieve this using body slot as well, here's my code
https://codepen.io/saurabhtalreja/pen/JjyWxEr
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
headers: [{
name: 'id',
text: 'id',
value: 'id'
},
{
name: 'text',
text: 'text',
value: 'text'
}
],
items: [{
id: 1,
text: "Item 1"
},
{
id: 2,
text: "Item 2"
},
{
id: 3,
text: "Item 3"
},
{
id: 4,
text: "Item 4"
},
{
id: 5,
text: "Item 5"
}
]
})
})
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#3.x/css/materialdesignicons.min.css" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-content>
<v-container grid-list-xl>
<v-data-table class="elevation-1" :headers="headers" :items="items" show-expand hide-default-footer>
<template v-slot:body="{ items, headers, expand, isExpanded }">
<tbody>
<tr v-for="item in items" :key="item.name">
<td>
<v-btn #click="expand(item,!isExpanded(item))">Expand</v-btn>
<div v-if="isExpanded(item)" :style="{width: '250px'}">
Expanded content for {{ item.text }}
</div>
</td>
<td>{{item.id}}</td>
<td>{{item.text}}</td>
</tr>
</tbody>
</template>
</v-data-table>
</v-container>
</v-content>
</v-app>
</div>
<div id="app">
<v-app>
<v-content>
<v-container grid-list-xl>
<v-data-table class="elevation-1" :headers="headers" :items="items" show-expand hide-default-footer>
<template v-slot:expanded-item="{item, headers}">
<td :colspan="headers.length">
expanded content for {{ item.text }}
</td>
</template>
</v-data-table>
</v-container>
</v-content>
</v-app>
</div>

Auto select rows in v-data-table

I would like to programmatically checkmark a row in a v-data-table when an external listener notifies me of a particular value.
As an example, here is a Vuetify selectable table: https://vuetifyjs.com/en/components/data-tables#selectable-rows
In the example, If I were passed the value of 'Gingerbread' after the table and its data have already been instantiated, how would I programmatically select that corresponding row?
You can do this by pushing your values in your model like this:
HTML:
<div id="app">
<v-app id="inspire">
<v-btn #click="select">button</v-btn>
<v-data-table
v-model="selected"
:headers="headers"
:items="desserts"
:single-select="singleSelect"
item-key="name"
show-select
class="elevation-1"
>
<template v-slot:top>
<v-switch v-model="singleSelect" label="Single select" class="pa-3"></v-switch>
</template>
</v-data-table>
</v-app>
</div>
VueJS:
new Vue({
el: '#app',
vuetify: new Vuetify(),
methods: {
select: function() {
let result = this.desserts.find((dessert) => {
return dessert.name == 'Gingerbread'
})
this.selected.push(result)
}
},
data () {
return {
singleSelect: false,
selected: [],
headers: [
{ text: 'Dessert (100g serving)', value: 'name' },
{ text: 'Calories', value: 'calories' },
],
desserts: [
{ name: 'Gingerbread', calories: 356 },
{ name: 'Jelly bean', calories: 375 }
],
}
},
})
The v-data-table component has a property called filteredItems which you can use to add items to the selected array
<v-data-table v-model="selected":items="itemsArray" ref="table"></v-data-table>
function selectAll() {
this.$refs.table.filteredItems.map(item => {
if(...some condition...) {
this.selected.push(item)
}
})
}

Vuetify - bottom align text field

I'm trying to align the bottom of my search text field with the bottom of my floating action button. I've created a code pen which you can see here:
I've tried everything I know including: setting a negative margin, and vertical-align to both baseline and bottom to no effect. You can see my code in the codepen, but I've attached it below as well:
<template id="app">
<v-container>
<v-layout row wrap align-center class="text-xs-center">
<v-flex xs12 align-center>
<v-flex>
<h1 class="display-1 sans_pro_medium fix-title-height pb-3">Failed Order Report</h1>
</v-flex>
<v-layout row>
<v-flex xs12>
<side_drawer v-show="side_drawer_show"></side_drawer>
</v-flex>
</v-layout>
<!--<v-flex xs12>-->
<!--<v-spacer></v-spacer>-->
<v-layout row>
<v-flex xs1>
<v-btn fab large color="purple darken-4" align-left>
<v-icon x-large color="white">refresh</v-icon>
</v-btn>
</v-flex>
<v-flex xs4 offset-xs7>
<v-text-field
align-right
v-model="search"
append-icon="search"
label="Search"
single-line
hide-details
>
</v-text-field>
</v-flex>
</v-layout>
<!--</v-flex>-->
<v-data-table
:headers="headers"
:items="desserts"
:search="search"
class="elevation-11"
>
<template v-slot:no-data>
<v-alert :value="true" type="success">
Your orders are looking great! No orders have failed.
</v-alert>
</template>
<template slot="items" slot-scope="props">
<td>{{ props.item.name }}</td>
<td class="text-xs-center">{{ props.item.calories }}</td>
<td class="text-xs-center">{{ 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>-->
</template>
</v-data-table>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
/*global localStorage*/
// import side_drawer from '../components/side_drawer.vue';
// import {dataShare} from '../packs/application.js';
import axios from 'axios';
export default {
components: {
side_drawer
},
data () {
return {
search: '',
side_drawer_show: true,
headers: [
{
text: 'Shopify Store URL',
align: 'center',
sortable: true,
value: 'url'
},
{ text: 'Shopify Order Number', value: 'shopify_order_number', align: 'center', sortable: true},
{ text: 'Amazon Order Id', value: 'amazon_order_id', align: 'center', sortable: true },
{ text: 'Shopify Order Status', value: 'shopify_order_status', align: 'center', sortable: true },
{ text: 'Amazon Order Status', value: 'amazon_order_status', align: 'center', sortable: true },
{ text: 'Action Needed', value: 'action' , sortable: true}
],
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%'
}
]
}
}
}
</script>
<style>
.nudge_up {
padding-bottom: 10px !important;
}
</style>
You just need to add align-center to your v-layout So:
<v-layout row align-center>
<v-flex xs1>
<v-btn fab large color="purple darken-4" align-left>
<v-icon x-large color="white">refresh</v-icon>
</v-btn>
</v-flex>
<v-flex xs4 offset-xs7>
<v-text-field
align-right
v-model="search"
append-icon="search"
label="Search"
single-line
hide-details
>
</v-text-field>
</v-flex>
</v-layout>
You can read more about it here

Resources