Vuetify - bottom align text field - vuetify.js

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

Related

Create a dynamic v-data-table in vuetify

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
}
}

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>

How to wire up external data in Vuetify datatables

I have just started with Vue and found Vuetify ( and very impressed ) . I'm a bit of a newbie with node.js as well but some experience.
I am trying to find some examples on loading data from external API's into the vuetify datagrid - CRUD type stuff, reasonably large amounts of data paginated. The documentation in Vuetify is a little lacking in this regard. Should I be using Vuex?
If you want to call external API using REST, you'll need to use axios, which is a NPM package allowing you to make GET, POST and all that kind.
Let's use this online working API for our example. First, you need to get your data by calling this API. A good tutorial on Internet will show you more details, but let's use this code.
this.todos = axios.get('https://jsonplaceholder.typicode.com/todos/')
.then(response => { this.todos = response.data })
.catch(error => { console.log(error)});
Then you just have to use the datatable like in the documentation. Here is a CodePen to help you see, briefly, how I made the API call and then displayed it. It all comes from the official documentation, just modified to call a REST API. I'll put the code also here, in order to save it for future readers too.
<div id="app">
<v-app id="inspire">
<div>
<v-toolbar flat color="white">
<v-toolbar-title>Todos CRUD</v-toolbar-title>
<v-divider
class="mx-2"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<v-btn slot="activator" color="primary" dark class="mb-2">New Item</v-btn>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6 md4>
<v-text-field v-model="editedItem.userId" label="User ID"></v-text-field>
</v-flex>
<v-flex xs12 sm6 md4>
<v-text-field v-model="editedItem.id" label="ID"></v-text-field>
</v-flex>
<v-flex xs12 sm6 md4>
<v-text-field v-model="editedItem.title" label="Title"></v-text-field>
</v-flex>
<v-flex xs12 sm6 md4>
<v-checkbox v-model="editedItem.completed" label="Completed?"></v-checkbox>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" flat #click="close">Cancel</v-btn>
<v-btn color="blue darken-1" flat #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
<v-data-table
:headers="headers"
:items="todos"
class="elevation-1"
>
<template slot="items" slot-scope="props">
<td class="text-xs-right">{{ props.item.userId }}</td>
<td class="text-xs-right">{{ props.item.id }}</td>
<td class="text-xs-right">{{ props.item.title }}</td>
<td class="text-xs-right">{{ props.item.completed }}</td>
<td class="justify-center layout px-0">
<v-icon
small
class="mr-2"
#click="editItem(props.item)"
>
edit
</v-icon>
<v-icon
small
#click="deleteItem(props.item)"
>
delete
</v-icon>
</td>
</template>
<template slot="no-data">
<v-btn color="primary" #click="initialize">Reset</v-btn>
</template>
</v-data-table>
</div>
</v-app>
</div>
And then the associated JS.
new Vue({
el: '#app',
data: () => ({
dialog: false,
headers: [
{
text: 'User ID',
align: 'left',
sortable: false,
value: 'userId',
width: '10'
},
{ text: 'ID', value: 'id', width: '10' },
{ text: 'Title', value: 'title' },
{ text: 'Completed', value: 'completed' }
],
todos: [],
editedIndex: -1,
editedItem: {
userId: 0,
id: 0,
title: '',
completed: false
},
defaultItem: {
userId: 0,
id: 0,
title: '',
completed: false
}
}),
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
}
},
watch: {
dialog (val) {
val || this.close()
}
},
created () {
this.initialize()
},
methods: {
initialize () {
this.todos = axios.get('https://jsonplaceholder.typicode.com/todos/')
.then(response => { this.todos = response.data })
.catch(error => { console.log(error)});
},
editItem (item) {
this.editedIndex = this.todos.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
const index = this.todos.indexOf(item)
confirm('Are you sure you want to delete this item?') && this.todos.splice(index, 1)
},
close () {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
save () {
if (this.editedIndex > -1) {
Object.assign(this.todos[this.editedIndex], this.editedItem)
} else {
this.todos.push(this.editedItem)
}
this.close()
}
}
})

Resources