I have a Vuetify Datatable
<v-data-table
:headers="headers"
:items="members"
item-key="id"
v-model="selected"
:search="search"
>
<template slot="items" slot-scope="props">
<tr :active="props.selected" #click="select(props.item)">
<td>{{ props.item.name }}</td>
<td class="text-xs-right">{{ props.item.FirstName}}</td>
<td class="text-xs-right">{{ props.item.LastName }}</td>
<td class="text-xs-right">{{ props.item.email }}</td>
<td class="text-xs-right">{{ props.item.department}}</td>
<td class="text-xs-right">{{ props.item.division}}</td>
</tr>
</template>
And when I select a row I want to be able to populate an Item on the same page with some of the data such as the name and email in a v-card. I currently have
{{msg}}
and in my script I have
return {
msg: "",
then
select(selectedItem) {
this.selected = [];
this.members.forEach(item => {
if (item.id == selectedItem.id) {
this.selected.push(item);
this.msg = selectedItem.FirstName;
}
});
},
I need to put name into the msg. I feel that I'm going the long way around to get my data and was wondering if someone has a better solution. Thanks for the support.
<v-data-table #click:row="rowClick" item-key="name" single-select ...
methods: {
rowClick: function (item, row) {
row.select(true);
//item - selected item
}
}
<style>
tr.v-data-table__selected {
background: #7d92f5 !important;
}
</style>
or
<style scoped>
/deep/ tr.v-data-table__selected {
background: #7d92f5 !important;
}
</style>
Example
https://codepen.io/nicolai-nikolai/pen/GRgLpNY
Since there is a binding between the data table and this.selected, you only need to populate msg as a computed property of the component. You don't need to manually add to this.selected by listening to the click event.
computed: {
msg() {
const selectedRow = this.selected[0];
return selectedRow ? `${selectedRow.firstName} ${selectedRow.lastName}` : "no data selected";
}
}
EDIT
I've added a minimal example. Note for the item-key prop of v-data-table, you should use unique values.
<template>
<v-card>
<v-card-text>
<v-data-table :headers="headers" :items="members" v-model="selected" item-key="id">
<template slot="items" slot-scope="props">
<td>
<v-checkbox
v-model="props.selected"
:disabled="!props.selected && selected.length != 0"
:indeterminate="!props.selected && selected.length != 0"
></v-checkbox>
</td>
<td>{{ props.item.firstName}}</td>
<td>{{ props.item.lastName }}</td>
</template>
</v-data-table>
{{ msg }}
</v-card-text>
</v-card>
</template>
<script>
export default {
data() {
return {
headers: [
{ text: "Select", value: "id", sortable: false },
{ text: "First Name", value: "firstName", sortable: true },
{ text: "Last Name", value: "lastName", sortable: true }
],
selected: [],
members: [
{
id: 1,
firstName: "a",
lastName: "b"
},
{
id: 2,
firstName: "x",
lastName: "y"
}
]
};
},
computed: {
msg() {
const selectedRow = this.selected[0];
return selectedRow ? `${selectedRow.firstName} ${selectedRow.lastName}` : "no data selected";
}
}
};
</script>
Related
How do i display data from the database. I have fetched data as an object when displaying it works well but when i convert the data to array it does not display the content of the table.
Am also trying to implement the routing such that when one clicks the button on the Actions column it redirect to another page that has a specific user details.
Am getting the data from the API and in the network fetch xhr am able to see the data.
This is the vue template where i want to display the data received by the API I guess am going wrong somewhere
<v-data-table hide-actions flat :headers="headers" :="doctors">
<template v-slot:items="props">
<td>{{ props.index + 1 }}</td>
<td>{{ props.item.full_name }}</td>
<td>{{ props.item.email }}</td>
<td>{{ props.item.username }}</td>
<td>{{ props.item.username }}</td>
<td>
<v-btn outline small color="indigo" #click="view(props.item)">
<i class="fa fa-eye"></i> make payment
</v-btn>
</td>
</template>
<template v-slot:no-results>
<h6 class="grey--text">No data available</h6>
</template>
</v-data-table>
</div>
</template>
<script>
import {mapActions, mapGetters} from "vuex";
export default {
data() {
return {
page: 1,
dateFormat: "DD MMM, YYYY",
selected: null,
dialog: false,
loading: false,
saveLoader: false,
headers: [
{text: "#", value: ""},
{text: "name", value: "name"},
{text: "email", value: "email"},
{text: "role", value: "role"},
{text: "updated_at", value: "updated_at"},
{text: "Action", value: "action"},
],
};
},
computed: {
...mapGetters({
doctors: "getListDoctors",
}),
},
methods: {
...mapActions({
fetchDoctors: 'setListDoctors'
}),
view(id) {
this.$router.push({ path: `/finance/pay-doctors/${item.id, '_blank'}` });
}
},
mounted() {
this.fetchDoctors();
},
}
</script>
Here is sample of data received from the API
{
"data": [
{
"id": 1,
"name": "TEST JOHN",
"full_name": "Mrs. JOHN",
"mobile": "0700000001",
"email": "TEST#TEST.com",
"active": 0,
"created_at": "Sep 10, 2019 07:01:43 am",
"roles": "testpharm",
"username": "USER1"
},
]
}
This should work fine regarding the doc
<template>
<v-data-table hide-actions flat :headers="headers" :items="doctors" :items-per-page="5">
<template v-slot:body="{ items }">
<tbody>
<tr v-for="item in items" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.full_name }}</td>
<td>{{ item.mobile }}</td>
<td>{{ item.email }}</td>
<td>{{ item.username }}</td>
</tr>
</tbody>
</template>
<template v-slot:no-results>
<h6 class="grey--text">No data available</h6>
</template>
</v-data-table>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
export default {
data() {
return {
headers: [
{ text: "#", value: "" },
{ text: "name", value: "name" },
{ text: "email", value: "email" },
{ text: "role", value: "role" },
{ text: "updated_at", value: "updated_at" },
{ text: "Action", value: "action" },
],
};
},
computed: {
...mapGetters({
doctors: "getListDoctors",
}),
},
async mounted() {
await this.fetchDoctors();
},
}
</script>
Use your Vue devtools to be sure to pass data.data to the final doctors Vuex getter (regarding your JSON example).
Vue devtools will help you see what state you currently have + the usage of the network tab can also help quite a lot.
<template>
<div>
<v-data-table
:items="agents"
hide-default-footer
class="agent-table"
v-bind:pagination.sync="pagination">
<template slot="item" slot-scope="props">
<tr>
<td>{{ props.item.first_name }} {{ props.item.last_name }}</td>
<td>{{ props.item.email }}</td>
<td>{{ props.item.phone }}</td>
</tr>
</template>
</v-data-table>
<div >
<v-pagination v-model="pagination.page" :length="pages" :total-visible="7"></v-pagination>
</div>
</div>
<template>
<script>
export default {
data: function() {
return {
pagination: {
rowsPerPage: 15,
page: 1
},
agents:[]
}
},
computed: {
pages () {
return this.pagination.rowsPerPage && this.agents.length !== 0 ? Math.ceil(this.agents.length / this.pagination.rowsPerPage) : 0
},
},
created() {
this.fetchAgents();
}
methods: {
fetchAgentss() {
var that = this;
this.$axios.get('agents.json')
.then(response => {
that.agents = response.data.agents;
});
}
}
}
</script>
I am upgrading from vuetify version 1.0.5 to 2.3.10 and I am using v-pagination for custom pagination but I am getting this error
[breaking] 'pagination' has been removed, use 'options' instead
Please help me find where I am going wrong
Instead of using v-bind:pagination.sync use v-bind:options.sync
{
page: number,
itemsPerPage: number,
sortBy: string[],
sortDesc: boolean[],
groupBy: string[],
groupDesc: boolean[],
multiSort: boolean,
mustSort: boolean
}
Refer Official API
For Example
I have a v-data-table and I added an expandable div to each of them that I want to use as a component. I'm not sure how to pass the data from the selected v-data-table to the component. I'm using single file components.
ScanGrid component(parent):
<template>
<v-container>
<v-card>
<v-card-text>
<v-layout row align-center>
<v-data-table
:headers="headers"
:items="items"
:hide-actions="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-right pr-5">{{ props.item.scanned }}</td>
<td class="text-xs-right pr-5">{{ props.item.incoming }}</td>
<td class="text-xs-right pr-5">{{ props.item.outgoing }}</td>
<td class="text-xs-right pr-5">{{ props.item.unknown }}</td>
</tr>
</template>
<div :value="items">
<ScanGridChild></ScanGridChild>
</div>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</v-container>
</template>
<script>
import ScanGridChild from "./ScanGridChild";
export default {
name: "ScanGrid",
props: {},
components: {
ScanGridChild
},
data: () => ({
selected: [],
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"
}
],
totalResults: 0,
loading: true,
pagination: {},
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"
}
]
}),
mounted() {},
methods: {},
watch: {}
};
</script>
<style lang="scss" scoped></style>
ScanGridChild component(What I want to use for the expanded div in the v-data-table item):
<template v-slot:expand="props">
<v-card flat>
<v-card-text>{{ props.item.test }}</v-card-text>
</v-card>
</template>
<script>
export default {
name: "ScanGridChild",
props: {
value: {
Type: String,
Required: true
}
},
async mounted() {
await this.render();
},
computed: {},
watch: {
props: function(newVal, oldVal) {
console.log("Prop changed: ", newVal, " | was: ", oldVal);
this.render();
}
}
};
</script>
<style></style>
I'm on Vuetify 1.5.19.
I think my problem is with the slot-scope in ScanGrid but I'm not sure.
Your code is almost correct, You need to add some additional fix to parent and child component
Parent component
<template>
<v-container>
<v-card>
<v-card-text>
<v-layout row align-center>
<v-data-table
:headers="headers"
:items="items"
:hide-actions="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-right pr-5">{{ props.item.scanned }}</td>
<td class="text-xs-right pr-5">{{ props.item.incoming }}</td>
<td class="text-xs-right pr-5">{{ props.item.outgoing }}</td>
<td class="text-xs-right pr-5">{{ props.item.unknown }}</td>
</tr>
</template>
<template slot="expand" slot-scope="props">
<ScanGridChild v-bind:testName="props.item.test"></ScanGridChild>
</template>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</v-container>
</template>
In Child Component
<template>
<v-card flat>
<v-card-text>{{ testName }}</v-card-text>
</v-card>
</template>
<script>
export default {
name: "ScanGridChildComponent",
props: {
testName: {
Type: String,
Required: true
}
}
};
</script>
I have a typical Vue data-table with a template section that displays an alert if no records are found. Problem is, that displays right away, even before my Axios method has a chance to go out and get records.
How can I prevent the flash of red warning message before the actual data loads?
<template>
<div>
<v-card>
<v-card-title>
<h1>Locations</h1>
</v-card-title>
<v-data-table :headers="headers" :items="locations" :search="search" :fixed-header="true" :loading="true" class="elevation-1">
<template v-slot:items="location">
<td>{{ location.item.id }}</td>
<td>{{ location.item.company }}</td>
<td>{{ location.item.category }}</td>
<td>{{ location.item.name }}</td>
<td>{{ location.item.city }}, {{ location.item.state }}</td>
</template>
<template v-slot:no-data>
<v-alert :value="true" color="error" icon="warning">Sorry, no locations found.</v-alert>
</template>
</v-data-table>
</v-card>
</div>
</template>
<script>
import { HTTP } from "#/utils/http-common";
export default {
name: 'LocationsList',
data() {
return {
headers: [
{ text: "Id", value: "id" },
{ text: "Company", value: "company" },
{ text: "Category", value: "category" },
{ text: "Name", value: "name" },
{ text: "City, State", value: "city" },
],
locations: [],
errors: []
};
},
created: function() {
this.getAllLocations();
},
methods: {
getAllLocations() {
HTTP.get("locations")
.then(response => {
this.locations = response.data;
})
.catch(err => {
this.errors.push(err);
});
},
}
};
</script>
Add a loading state to data, and set it to true
Set the loading state when the call is finished (.finally promise)
Set the v-if on in your template to show when it's not anymore loading
See code below.
<template>
<div>
<v-card>
<v-card-title>
<h1>Locations</h1>
</v-card-title>
<v-data-table :headers="headers" :items="locations" :search="search" :fixed-header="true" :loading="true" class="elevation-1">
<template v-slot:items="location">
<td>{{ location.item.id }}</td>
<td>{{ location.item.company }}</td>
<td>{{ location.item.category }}</td>
<td>{{ location.item.name }}</td>
<td>{{ location.item.city }}, {{ location.item.state }}</td>
</template>
<template v-slot:no-data>
<v-alert v-if="!loading" :value="true" color="error" icon="warning">Sorry, no locations found.</v-alert>
</template>
</v-data-table>
</v-card>
</div>
</template>
<script>
import { HTTP } from "#/utils/http-common";
export default {
name: 'LocationsList',
data() {
return {
headers: [
{ text: "Id", value: "id" },
{ text: "Company", value: "company" },
{ text: "Category", value: "category" },
{ text: "Name", value: "name" },
{ text: "City, State", value: "city" },
],
locations: [],
errors: [],
loading: true
};
},
created: function() {
this.getAllLocations();
},
methods: {
getAllLocations() {
HTTP.get("locations")
.then(response => {
this.locations = response.data;
})
.catch(err => {
this.errors.push(err);
})
.finally(() => {
this.loading = false;
})
},
}
};
</script>
The function "change sort" executed on header item click does not trigger watch, which fetches updated data from server. On the other hand, if I try to execute fetchRecords method at the end of changeSort method, watch gets triggered multiple times
I need to use Vuetify datatables with server side pagination and sorting, and templates for header and items. I implemented code similarly to Vuetify examples: "Paginate and sort server-side" and "Slot: items and headers" from documentation https://vuetifyjs.com/en/components/data-tables#api.
<template>
<v-card class="table-container">
<v-card-title>
<v-text-field
v-model="searchField"
#blur="fetchRecords()"
#keyup.enter="fetchRecords()"
/>
</v-card-title>
<v-data-table
:headers="headers"
:items="applications.applications"
:loading="paginationLoading"
:pagination.sync="pagination"
:total-items="pagination.totalItems"
no-data-text="No results"
>
<template v-slot:headers="props">
<tr>
<th colspan="4">Dane kandydata</th>
<th colspan="4">Dane polecającego</th>
<th colspan="2">Inne</th>
</tr>
<tr>
<th
v-for="header in props.headers"
:key="header.value"
:class="['column',
header.sortable !== false ? 'sortable' : '' ,
pagination.descending ? 'desc' : 'asc',
header.value === pagination.sortBy ? 'active' : '']"
#click="changeSort(header.value, header.sortable)"
>
{{ header.text }}
<v-icon v-if="header.sortable !== false" small>fas fa-sort-up</v-icon>
</th>
</tr>
</template>
<template v-slot:items="props">
<td>{{ props.item.candidateName }}</td>
<td>{{ props.item.candidateSurname }}</td>
<td>{{ props.item.candidateEmail }}</td>
<td>{{ props.item.candidatePhone }}</td>
<td>{{ props.item.referrerName }}</td>
<td>{{ props.item.referrerSurname }}</td>
<td>{{ props.item.referrerEmail }}</td>
<td>{{ props.item.referrerPhone }}</td>
<td class="text-md-center">
<div>
<v-icon>check_circle_outline</v-icon>
</div>
</td>
<td class="text-md-center">
<div>
<v-icon>check_circle_outline</v-icon>
</div>
</td>
</template>
<v-alert v-slot:no-results :value="true" color="error" icon="warning">
Your search for "{{ searchField }}" found no results.
</v-alert>
</v-data-table>
</v-card>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
data () {
return {
announcementId: '',
announcementTitle: '',
searchField: '',
headers: [
{ text: 'Imię', value: 'candidateName' },
{ text: 'Nazwisko', value: 'candidateSurname' },
{ text: 'Email', value: 'candidateEmail', sortable: false },
{ text: 'Telefon', value: 'candidatePhone' },
{ text: 'Imię', value: 'referrerName' },
{ text: 'Nazwisko', value: 'referrerSurname' },
{ text: 'Email', value: 'referrerEmail', sortable: false },
{ text: 'Telefon', value: 'referrerPhone' },
{ text: 'Status', value: 'processed' },
{ text: 'Akcje', value: 'actions', sortable: false },
],
pagination: {
page: 1,
rowsPerPage: 10,
totalItems: 10,
sortBy: '',
descending: false,
},
paginationLoading: false
}
},
computed: {
...mapState([
'applications',
])
},
watch: {
pagination: {
handler(newVal, oldVal){
if(newVal != oldVal){
this.fetchRecords()
}
},
deep: true
}
},
methods: {
...mapActions([
'getApplications',
]),
fetchRecords () {
this.paginationLoading = true
this.getApplications({
announcementId: this.announcementId,
pagination: this.pagination,
search: this.searchField
})
.then(
response => {
this.paginationLoading = false
if(this.pagination.totalItems != response.totalItems) this.pagination.totalItems = response.totalItems
}
)
.catch(
err => {
console.log(err);
}
)
},
changeSort (columnValue, sortable) {
if(sortable === false){
return
}
if (this.pagination.sortBy === columnValue) {
this.pagination.descending = !this.pagination.descending
console.log(this.pagination.descending);
} else {
this.pagination.sortBy = columnValue
this.pagination.descending = false
}
},
editTableItem(item){
console.log(item);
},
onClearSearch() {
this.searchField = ''
this.fetchRecords()
},
}
}
</script>
You should create new object when changing pagination object. Here I use ES6 syntax:
changeSort (columnValue, sortable) {
if(sortable === false){
return
}
if (this.pagination.sortBy === columnValue) {
this.pagination = {
...this.pagination,
descending: !this.pagination.descending
}
console.log(this.pagination.descending);
} else {
this.pagination = {
...this.pagination,
sortBy: columnValue,
descending: false
}
}
}