I would like to use the built-in short button not just to sort the items but also to call one of my own methods. Is there any solution to this?
Let's give that a try...
For your <v-data-table, add :options.sync="options"
<v-data-table
:options.sync="options"
Declare options as options: {}, within your data block
data: () => ({
options: {},
Now do what you need to do in watch like
watch: {
options: {
immediate: true,
deep: true,
handler() {
if (Object.keys(this.options).length !== 0) {
if (this.options.sortBy?.length ?? 0 > 0) {
if (this.options.sortBy[0]) {
this.sort = this.options.sortBy[0];
if (this.options.sortDesc[0]) this.sort = this.sort + ":desc";
} else {
this.sort = "";
}
}
this.loadAccounts();
}
},
},
},
Your this.loadAccounts() could look like
methods: {
...mapActions("accounts", ["getAccounts"]),
loadAccounts() {
let params = {
page: this.options.page,
limit: this.options.itemsPerPageOptions,
sort: this.options.sort,
};
this.getAccounts(params);
},
},
You can use :custom-sort directive inside <v-data-table> to achieve the requirement you have.
Working Demo :
new Vue({
el: '#app',
data() {
return {
food: [
{ name: 'Frozen Yogurt', calories: 159, fat: 6, carbs: 24 },
{ name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37 }
],
headers: [
{ text: 'Dessert (100g serving)', align: 'left', value: 'name' },
{ text: 'Calories', align: 'left', value: 'calories' },
{ text: 'Fat (g)', align: 'left', value: 'fat' },
{ text: 'Carbs (g)', align: 'left', value: 'carbs' }
],
search: '',
};
},
methods: {
customSort(items, index, isDescending) {
// You can call your method here and below is the sorting code for that column.
// You can do any chnages here as per your requirement
items.sort((a, b) => {
if (index === 'calories') {
if (isDescending) {
return b.calories - a.calories;
} else {
return a.calories - b.calories;
}
}
});
return items;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue#2.4.2/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#0.15.2/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#0.15.2/dist/vuetify.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons">
<div id="app">
<v-app>
<v-data-table
:headers="headers"
:items="food"
:custom-sort="customSort"
hide-actions
>
<template slot="items" scope="{ item }">
<td>{{ item.name }}</td>
<td>{{ item.calories }}</td>
<td>{{ item.fat }}</td>
<td>{{ item.carbs }}</td>
</template>
</v-data-table>
</v-app>
</div>
Related
I am using Vue and Vuetify to generate a table which I do.
I want to make the headers of the table bold. Hence, I am thinking of passing a class to the headers and customize the headers from the CSS. Despite the fact that when I inspect the page(F12-> inspector) on the header my custom class has been passed but it don't do what I want it to do I feel like the code is overridden somehow. Can you please help ?
HTML:
<v-data-table
:headers="headers"
:items="myDataPSUTwo"
:search="search"
></v-data-table>
the script:
data() {
return {
search: "",
headers: [
{
text: "Name",
align: "center",
filterable: true,
value: "name",
class:"header-of-chart"
},
{ text: "Value", value: "value" },
{ text: "Unit", value: "units" },
],
};
},
the CSS:
<style scoped>
.header-of-chart {
font-weight: bold;
}
</style
v-data-table have a header slot. You can use it to modify header style.
<template>
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
:sort-by="['calories', 'fat']"
:sort-desc="[false, true]"
multi-sort
hide-default-header
class="elevation-1"
>
<template v-slot:header="{ props }">
<th v-for="head in props.headers">{{ head.text.toUpperCase() }}
</template>
</v-data-table>
</v-app>
</div>
</template>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
headers: [
{
text: 'Dessert (100g serving)',
align: 'start',
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: 200,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: '1%',
}
],
}
},
})
v-data-table already have header text in bold that's the reason it is not impact anything. You want to increase the font size ? You can use it like this :
.header-of-chart {
font-size: 25px !important;
}
I have created a b-table that stores all the data from the API that has been hit from Swagger UI, but since the data has a lot of characters in string, My questions are how to make the data in each row be hovered on click to show the real data from API that hasn't been truncated? I've tried using v-b-tooltip but it seems doesn't work. If I may, I also wanted to know more about how to make the b-pagination works to load another data as I navigate page further.
Here's my current code:
<template>
<base-header>
<template>
<b-card body>
<b-card-header class="border-0">
<h3 class="mb-0">Stock List</h3>
</b-card-header>
<template>
<div class="text-center">
<b-table responsive dark striped hover:true :items="items" :fields="fields">
<template #cell()="data">
<span v-b-tooltip.hover :title="data.value">
{{ data.value }}
</span>
</template>
</b-table>
</div>
</template>
<div class="overflow-auto">
<b-card-footer class="py-4 d-flex justify-content-end">
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
aria-controls="my-table"
></b-pagination>
</b-card-footer>
</div>
</b-card>
</template>
</base-header>
</template>
and then here's the script
<script>
// eslint-disable-next-line no-unused-vars
import { getAllProvinces } from '~/api/delivery'
export default {
// components: {
// },
data() {
return {
perPage: 10,
currentPage: 1,
allStock: 0,
text: '',
rows: 100,
// ubah rows dan perPage biar paginationnya ada value
items: [],
fields: [
{
key: 'id',
sortable: true,
label: 'ID',
class: 'truncate',
},
{
key: 'requestId',
sortable: true,
label: 'Request ID',
class: 'truncate',
},
{
key: 'storeCode',
sortable: true,
label: 'Store Code',
class: 'truncate',
},
{
key: 'branchCode',
sortable: true,
label: 'Branch Code',
class: 'truncate',
},
{
key: 'b2bId',
sortable: true,
label: 'B2B ID',
class: 'truncate',
},
{
key: 'request',
sortable: true,
label: 'Request',
class: 'truncate',
},
{
key: 'response',
sortable: true,
label: 'Response',
class: 'truncate',
},
{
key: 'createDate',
sortable: true,
label: 'Create Date',
class: 'truncate',
},
{
key: 'errorClassification',
sortable: true,
label: 'Error Classification',
class: 'truncate',
},
],
}
},
mounted() {
this.getAllStock()
},
methods: {
getAllStock() {
this.$axios
.get(
'API Link'
)
.then((res) => {
// eslint-disable-next-line no-console
console.log(res.data)
this.items = res.data.stocks
this.allStock = res.data
// eslint-disable-next-line no-console
// console.log('cek res stock:', JSON.stringify(res.data))
})
},
computed: {
rows() {
return this.items.length
},
},
},
}
</script>
<style>
.truncate {
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
The documentation is pretty self-explanatory: https://bootstrap-vue.org/docs/components/pagination
But I added several comments on the code below (the template is therefore invalid!)
<template>
<div class="overflow-auto">
<b-pagination
v-model="currentPage" // this is the most important, bind the currentPage state to the pagination component, two-way data binding
:total-rows="rows" // display how much total data there is
:per-page="perPage" // this one will tell how much data per page you want to display
aria-controls="my-table" // this is for a11y
></b-pagination>
<p class="mt-3">Current Page: {{ currentPage }}</p>
<b-table
id="my-table"
:items="items" // where to look for the data
:per-page="perPage"
:current-page="currentPage" // re-use the current value of the pagination component
small
></b-table>
</div>
</template>
<script>
export default {
data() {
return {
perPage: 3,
currentPage: 1,
items: [ // your items, that you may replace with a new array if fetching an API in between each pagination page change
{ id: 1, first_name: 'Fred', last_name: 'Flintstone' },
{ id: 2, first_name: 'Wilma', last_name: 'Flintstone' },
{ id: 3, first_name: 'Barney', last_name: 'Rubble' },
{ id: 4, first_name: 'Betty', last_name: 'Rubble' },
{ id: 5, first_name: 'Pebbles', last_name: 'Flintstone' },
{ id: 6, first_name: 'Bamm Bamm', last_name: 'Rubble' },
{ id: 7, first_name: 'The Great', last_name: 'Gazzoo' },
{ id: 8, first_name: 'Rockhead', last_name: 'Slate' },
{ id: 9, first_name: 'Pearl', last_name: 'Slaghoople' }
]
}
},
computed: {
rows() {
return this.items.length
}
}
}
</script>
Of course, depending of the amount of data, you may want to watch for the currentPage value and make a new API call, fetching the next elements.
This totally depends on the API implementation but it's essentially passing 2 rather than 1 in URL's query params or somewhere into the headers.
As you can see in Github's API here: https://docs.github.com/en/rest/reference/repos#list-repositories-for-the-authenticated-user--parameters
Theirs is awaiting for a page and per_page params, hence the values of our VueJS state that we will send a new API call with.
I have an strange behaviour when I try to detect changes in selected rows for v-data-table (vuetify).
Here is a example code:
<template>
<v-data-table
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
show-select
>
</v-data-table>
</template>
<script>
export default {
data () {
return {
selected: [],
headers: [
{ text: 'Dessert (100g serving)', value: 'name' },
{ text: 'Calories', value: 'calories' },
],
desserts: [
{ name: 'Frozen Yogurt', calories: 159 },
{ name: 'Ice cream sandwich', calories: 237 },
{ name: 'Eclair', calories: 262 },
],
}
},
watch: {
selected: function(newSelected, oldSelected) {
console.log("watch", this.selected.length);
}
},
computed: {
selectedSize() {
console.log("computed", this.selected.length);
return this.selected.length;
}
},
}
</script>
When I select a row in the table then console shows:
watch 1
But no shows "computed 1". Why no prints the computed property?
I am attempting to make a custom component in Vue 2.0 that extends the existing functionality of the Bootstrap Vue library <b-table>. It mostly works how I would like it to except that the removeRow and resetData functions defined in the index.jsp don't work how I'd like them to.
removeRow does visually remove the row, and removes it from the data prop but the row reappears after the next interaction (sort, filter, etc.). So it's not actually updating the right thing. I'm trying to use a v-model as a shallow copy of items so that I can make deletions to it without affecting the original set of data but I'm just missing something.
resetData does set the data in the table back correctly, but doesn't re-render the table so you can't see the re-added rows, until you do a separate interaction (sort, filter, etc.), in which case they reappear.
So I know I'm somewhat close but would really appreciate any insight on how to get this working correctly and ways I could improve any part of this component.
OreillyTable.vue.js
const OreillyTable = {
inheritAttrs: false,
data: function () {
return {
filter: null,
sortDesc: false,
hideEmpty: false,
isBusy: false,
currentPage: 1,
data: null
}
},
mounted: function () {
let filtered = this.slots.filter(function(value, index, arr){
return value.customRender;
});
this.slots = filtered;
},
methods: {
oreillyTableSort (a, b, key) {
if (a[key] === null || b[key] === null) {
return a[key] === null && b[key] !== null ? -1 : (a[key] !== null && b[key] === null ? 1 : 0);
} else if (typeof a[key] === 'number' && typeof b[key] === 'number') {
// If both compared fields are native numbers
return a[key] < b[key] ? -1 : (a[key] > b[key] ? 1 : 0)
} else {
// Stringify the field data and use String.localeCompare
return this.toString(a[key]).localeCompare(this.toString(b[key]), undefined, {
numeric: true
});
}
},
toString (val) {
return typeof val !== "undefined" && val != null ? val.toString() : '';
},
oreillyFilter (filteredItems) {
this.totalRows = filteredItems.length;
this.currentPage = 1;
}
},
props: {
fields: {
type: Array
},
items: {
type: Array,
required: true
},
hideEmpty: {
type: Boolean
},
filterPlaceholder: {
type: String,
default: "Filter"
},
sortFunction: {
type: Function,
default: null
},
filterFunction: {
type: Function,
default: null
},
slots: {
type: Array
},
sortBy: {
type: String,
default: null
},
perPage: {
type: Number,
default: 10
},
value: {
}
},
template: `<div :class="{ 'no-events' : isBusy }">
<b-row>
<b-col md="12">
<b-form-group class="mb-2 col-md-3 float-right pr-0">
<b-input-group>
<b-form-input v-model="filter" :placeholder="filterPlaceholder" class="form-control" />
</b-input-group>
</b-form-group>
</b-col>
</b-row>
<div class="position-relative">
<div v-if="isBusy" class="loader"></div>
<b-table stacked="md" outlined responsive striped hover
v-bind="$attrs"
v-model="data"
:show-empty="!hideEmpty"
:items="items"
:fields="fields"
:no-provider-sorting="true"
:no-sort-reset="true"
:filter="filter"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:sort-compare="sortFunction === null ? this.oreillyTableSort : sortFunction"
:busy.sync="isBusy"
:current-page="currentPage"
:per-page="perPage"
#filtered="filterFunction === null ? this.oreillyFilter : filterFunction">
<template :slot="slot.key" slot-scope="row" v-for="slot in slots">
<slot :name="slot.key" :data="row"></slot>
</template>
</b-table>
<b-row v-if="items.length > perPage">
<b-col sm="12">
<b-pagination size="md" :total-rows="items.length" v-model="currentPage" :per-page="perPage"></b-pagination>
</b-col>
</b-row>
</div>
</div>`
};
index.jsp
<script>
Vue.use(window.vuelidate.default);
Vue.component('oreilly-table', OreillyTable);
const dashboardItems = [
{ id: 12, firstName: "John", lastName: "Adams", tmNumber: "588999", team: "Corporate", flapjackCount: 4, enrollDate: "2018-11-05" },
{ id: 13, firstName: "George", lastName: "Washington", tmNumber: "422111", team: "America", flapjackCount: 28, enrollDate: "2018-10-01" },
{ id: 14, firstName: "Abraham", lastName: "Lincoln", tmNumber: "358789", team: "America", flapjackCount: 16, enrollDate: "2017-09-02" },
{ id: 15, firstName: "Jimmy", lastName: "Carter", tmNumber: "225763", team: "Core", flapjackCount: 9, enrollDate: "2018-03-02" },
{ id: 16, firstName: "Thomas", lastName: "Jefferson", tmNumber: "169796", team: "Core", flapjackCount: 14, enrollDate: "2018-05-01" }
];
const Dashboard = {
template: `<jsp:include page="dashboard.jsp"/>`,
data: function(){
return {
notification: {
text: "The Great Flapjack Contest will be held on December 25, 2018.",
variant: "primary",
timer: true
},
fields: [
{ key: "name", label: "Name", sortable: true, customRender: true },
{ key: "team", label: "Team", sortable: true },
{ key: "enrollDate", label: "Enrollment Date", sortable: true, formatter: (value) => {return new Date(value).toLocaleDateString();} },
{ key: "flapjackCount", sortable: true },
{ key: "id", label: "", 'class': 'text-center', customRender: true }
]
}
},
methods: {
removeRow: function(id) {
this.$refs.table.isBusy = true;
setTimeout(() => { console.log("Ajax Request Here"); this.$refs.table.isBusy = false; }, 1000);
const index = this.$refs.table.data.findIndex(item => item.id === id)
if (~index)
this.$refs.table.data.splice(index, 1)
},
resetData: function() {
this.$refs.table.data = dashboardItems;
}
}
};
const router = new VueRouter({
mode: 'history',
base: "/ProjectTemplate/flapjack",
routes: [
{ path: '/enroll', component: Enroll },
{ path: '/', component: Dashboard },
{ path: '/404', component: NotFound },
{ path: '*', redirect: '/404' }
]
});
new Vue({router}).$mount('#app');
dashboard.jsp
<compress:html>
<div>
<oreilly-table ref="table" :items="dashboardItems" :slots="fields" :fields="fields">
<template slot="name" slot-scope="row">
{{ row.data.item.firstName }} {{ row.data.item.lastName }} ({{ row.data.item.tmNumber }})
</template>
<template slot="id" slot-scope="row">
Remove
</template>
</oreilly-table>
<footer class="footer position-sticky fixed-bottom bg-light">
<div class="container text-center">
<router-link tag="button" class="btn btn-outline-secondary" id="button" to="/enroll">Enroll</router-link>
<b-button #click.prevent="resetData" size="md" variant="outline-danger">Reset</b-button>
</div>
</footer>
</div>
I tried to reproduce your problem with a simple example (see: https://codesandbox.io/s/m30wqm0xk8?module=%2Fsrc%2Fcomponents%2FGridTest.vue) and I came across the same problem you have. Just like the others already said, I agree that the easiest way to reset original data is to make a copy. I wrote two methods to remove and reset data.
methods: {
removeRow(id) {
const index = this.records.findIndex(item => item.index === id);
this.records.splice(index, 1);
},
resetData() {
this.records = this.copyOfOrigin.slice(0);
}
}
On mount I execute a function that makes a copy of the data. This is done with slice because otherwise it makes only a reference to the original array (note that normally JS is pass-by-value, but as stated in the vue documentation with objects, and thus internally in vue it is pass by reference (see: Vue docs scroll to the red marked text)).
mounted: function() {
this.copyOfOrigin = this.records.slice(0);
}
Now you can simple remove a record but also reset all the data.
SEE FULL DEMO
I hope this fixes your issue and if you have any questions, feel free to ask.
Component's template:
<v-layout>
<v-flex>
<v-data-table :headers="headers" :items="items" :search="search" :pagination.sync="pagination" :total-items="totalItems"
:loading="loading" class="elevation-1" :rows-per-page-items="sizes">
<template slot="items" slot-scope="props">
<td>{{ props.item.itemWebCode }}</td>
<td>{{ props.item.description }}</td>
<td>{{ props.item.sequence }}</td>
</template>
</v-data-table>
</v-flex>
Component's js code:
import itemHttpService from './../../../services/itemsHttpService.js'
export default {
name: 'items',
data: () => ({
items: [],
loading: true,
pagination: {},
totalItems: 0,
sizes: [10, 30, 60],
search: '',
headers: [
{ text: 'ItemWebCode', align: 'left', sortable: false, value: 'itemWebCode' },
{ text: 'Description', align: 'left', value: 'description', sortable: false },
{ text: 'Sequence', align: 'left', value: 'sequence', sortable: false }
],
}),
async mounted() {
await this.getItems();
},
watch: {
pagination: {
async handler() {
await this.getItems();
},
deep: true
}
},
methods: {
async getItems() {
this.loading = true;
var resp = await itemHttpService.getItems(this.pagination.page, this.pagination.rowsPerPage);
this.items = resp.data.items;
this.totalItems = resp.data.totalItems;
this.loading = false;
}
}
}
itemHttpService file:
import HTTP from './httpBase.js';
const service = {
getItems: async (page, size) => HTTP.get('items', {
params:{
page: page,
size: size
}
}),
};
export default service;
httpBase file:
import axios from 'axios';
const http = axios.create({
baseURL: 'http://localhost:53403/api/'
});
export default http;
I must say the data table rendering and working well. But found thing that looks
a bit strange for me and I'm pretty sure I did stupid mistake. When component initialization server receives 2 additional GET requests: http://prntscr.com/juo7yu Does anyone have an idea what I did wrong?
That bug was fixed in v1.1.0-alpha.6