Create a dynamic v-data-table in vuetify - vuetify.js

I have this v-data-table in which one I load the list of products. Each time I press the "Agregar Productos" button, it adds a row to the table, each row must to have a autocomplete select where it stores the available products in the "Nombre" column, a text-field where I can to input the quantity of the product in the "Cantidad" column, the price product in the "Precio" columns in the respective row when the product is changed in the autocomplete select (the price can be got from productosDisponibles), for the last in the "Total" column it must to show the result of the columns "Precio" x "Cantidad".
What can I to do obtain the data of the selected product and show it in the respective row?
<v-data-table
:headers="headers"
:items="listaProductos"
sort-by="calories"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>Productos</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-btn
color="primary"
dark
class="mb-2"
#click="agregarProductoALaLista"
>
Agregar Producto
</v-btn>
</v-toolbar>
</template>
<template v-slot:[`item.name`]="props">
<v-autocomplete
v-model="props.item.name"
:items="productosDisponibles"
item-value="id"
item-text="name"
outlined
dense
label="Producto"
return-object
#change="getCantidad(props.item.name)"
></v-autocomplete>
</template>
<template v-slot:[`item.cantidad`]="props">
<v-text-field
v-model="props.item.cantidad"
label="Cantidad"
type="number"
placeholder="Cantidad"
></v-text-field>
</template>
<template v-slot:[`item.precio`]="props">
{{ props.item.precio }}
</template>
<template v-slot:[`item.total`]="props">
{{ props.item.cantidad * props.item.precio }}
</template>
<template v-slot:[`item.actions`]="{ item }">
<v-icon small class="mr-2" #click="editItem(item)"> mdi-pencil </v-icon>
<v-icon small #click="deleteItem(item)"> mdi-delete </v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" #click="initialize"> Reset </v-btn>
</template>
</v-data-table>
this is the structure of productosDisponibles:
this.productosDisponibles = [
{
id: 1,
name: "Coca-Cola",
cantidad: 159,
fat: 6.0,
precio: 24,
protein: 4.0,
},
{
id: 2,
name: "Leche",
cantidad: 237,
fat: 9.0,
precio: 37,
protein: 4.3,
},
{
id: 3,
name: "Alfajor",
cantidad: 262,
fat: 16.0,
precio: 23,
protein: 6.0,
},
];

I wasn't able to test it but I believe you can update the getCantidad() method to update the listaProductos variable.
getCantidad(name) {
...
const index = this.listaProductos.findIndex((e) => e.name === name); //find the index of the product you want to update
const productDisponibles = this.listaProductos.find((e) => e.name === name); // find the product price
if (index !== -1 && productDisponibles) {
this.listaProductos[index].precio = productDisponibles.precio; // update list if you found the product information and the index
}
}

Related

Usng Vuetify v-data-table how to set chip color based and value and item

I would like to set the chip color for the value of an item in a v-data-table using both the items value and the items id. So i want to set values under 0.5 to red only if the id is "Cl2". ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[enter image description here][1]
Here's my table and code:
[1]: https://i.stack.imgur.com/Sp8DQ.jpg
<template>
<v-data-table
:headers="headers"
:items="parameters"
class="elevation-1"
hide-default-footer
calculate-widths
dense
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>Parameter Input {{ scanSite }} </v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" persistent>
<v-card
max-width="315px"
max-height="240px"
style="position: absolute; top: 90px; left: 30px; right: 30px;"
>
<v-card-title>
<span class="">{{ editedItem.name }} - Enter/Edit Values </span>
</v-card-title>
<v-card-text class="card-text py-0 my-0">
<v-container>
<v-row class="row py-0">
<v-col cols="5">
<v-text-field
v-model="editedItem.value"
label="Value"
></v-text-field>
</v-col>
<v-col cols="5">
<v-text-field
v-model="editedItem.sid"
label="Save ID"
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="close">Cancel</v-btn>
<v-btn color="blue darken-1" text #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:[`item.actions`]="{ item }">
<v-icon small class="mr-2" #click="editItem(item)">
mdi-pencil
</v-icon>
<v-icon small class="mr-2" #click="addItem(item)">
mdi-plus
</v-icon>
</template>
<template v-slot:[`item.analyse`]="{ item }">
<v-simple-checkbox
color="green"
v-model="item.analyse"
></v-simple-checkbox>
</template>
<template v-slot:[`item.value`]="{ item }">
<v-chip :color="getColor(item.value)" small dark>{{ item.value }}</v-chip>
</template>
</v-data-table>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {
scanSite: "",
dialog: false,
selected: [],
editedItem: {
name: "",
value: 0,
sid: 0
}
};
},
computed: {
...mapState(["scanSite", "headers", "parameters", "sites"])
},
methods: {
editItem(item) {
this.editedIndex = this.parameters.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialog = true;
},
save() {
Object.assign(this.parameters[this.editedIndex], this.editedItem);
this.dialog = false;
},
close() {
this.dialog = false;
},
getColor(value, id) {
console.log(value, id);
if (value > 0.5 && id == "Cl2") return "red";
else if (value > 10) return "orange";
else return "green";
},
addItem(item) {
this.editedIndex = this.parameters.indexOf(item);
this.editedItem = Object.assign({}, item);
this.parameters.push(this.editedItem);
this.dialog = true;
}
}
};
</script>
I think you need to set both paramareters when you call your getColor() function, like so
<v-chip :color="getColor(item.value, item.id)" small dark>{{ item.value }}</v-chip>
or pass the whole item object and manage it in the getColor() function
<v-chip :color="getColor(item)" small dark>{{ item.value }}</v-chip>
...
getColor(item) {
if (item.value > 0.5 && item.id == "Cl2") return "red";
else if (item.value > 10) return "orange";
else return "green";
},

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>

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

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>

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