Using vue, I display my database in a table.
My table contains a lot of rows. Therefore, I use pagination.
Using the select function, I can select multiple rows and store them in an array.
The problem:
I select some rows in my table. The data from the row is stored in my array called selected. I let them appear next to my table.
Using pagination, I go to the next page. My data is still stored in this array. When I now select another row. My array is emptied,
my data from my section is gone.
How can I solve this? Why does this happen? Is there a way to store this data so it isn't lost?
Did I miss something?
Bellow, you find my code. Thanks for the help.
Vue code
<!-- Main table -->
<b-row align-v="start">
<b-col sm="8">
<b-table class="col-sm-12"
show-empty
striped hover
selectable
stacked="md"
:items="values"
:fields="fields"
:select-mode="selectMode"
:current-page="currentPage"
:filter="valueSearch"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:sort-direction="sortDirection"
#filtered="onFiltered"
#row-selected="rowSelected"
<template slot="name" slot-scope="row">
{{row.item.name}}
</template>
<template slot="id" slot-scope="row">
{{ row.item.id }}
</template>
<template slot="ref" slot-scope="row">
{{row.item.ref}}
</template>
<template slot="specific" slot-scope="row">
{{row.item.specific}}
</template>
<template slot="iso" slot-scope="row">
{{row.item.iso}}
</template>
<template slot="tax" slot-scope="row">
{{row.item.tax}}
</template>
<template slot="amount" slot-scope="row">
<span class='dot largerSize' :class="statusColor[range(row.item.amount)]"></span>
</template>
</b-table>
</b-col>
<b-col sm="4">
Here comes the value selected.
<div v-for="selectedvalue in selected" :key="selectedvalue.id">
{{ selectedvalue.id }} - {{selectedvalue.name}}
{{selected}}
</div>
</b-col>
</b-row>
<!-- How is sorted? & Pagnation -->
<b-row align-v="end" class="mb-2">
<b-col>
Sorting By: <b>{{ sortBy | capitalize }}</b>, Sort Direction:
<b>{{ sortDesc ? 'Descending' : 'Ascending' }}</b>
</b-col>
<b-col>
<b-pagination v-model="currentPage" :total-rows="totalRows" :per-page="perPage" class="my-0"></b-pagination>
</b-col>
<b-col>One of three columns</b-col>
</b-row>
Javascript
<script>
export default {
props: ['valuedata','minamount','maxamount'],
data() {
return {
values: this.valuedata,
statusColor: ["red","orange","green"],
totalRows: 1,
currentPage: 1,
perPage: 10,
pageOptions: [5, 10, 20, 50,100],
sortBy: "id",
sortDesc: false,
sortDirection: 'asc',
filter: null,
selectMode: 'multi',
fixed: true,
selected: [],
fields: {
id: {
label: "Id",
sortable: true
},
name: {
label: "Name",
sortable: true
},
ref: {
label: "Ref",
sortable: true
},
iso: {
label: "Iso",
sortable: true
},
tax: {
label: "Tax",
sortable: true
},
specific: {
label: "specific",
sortable: true
},
amount: {
label: "amount",
sortable: true
}
}
}
},
computed:{
hits: function(){
var hits = this.values.length
return hits
}
},
mounted () {
this.totalRows = this.values.length
},
methods: {
onFiltered(filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.totalRows = filteredItems.length
this.currentPage = 1
},
// Option: enables the storage of selected rows
rowSelected(items){
console.log(items[0].name)
this.selected = items
},
// Function: color code amount
range: function(x){
if (x < this.minamount){
return 0;
}else if(x >= this.minamount && x <= this.maxamount ){
return 1;
}else if(x >= this.maxamount ){
return 2;
}
}
},
filters: {
// Function: captitalize the first letter
capitalize: function (value) {
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
}
From my understanding, you replace all items in selected :
// Option: enables the storage of selected rows
rowSelected(items){
console.log(items[0].name)
this.selected = items
},
What you want to do is adding values, not replacing them, so IMHO what you should is something like :
// Option: enables the storage of selected rows
rowSelected(items){
console.log(items[0].name)
items.forEach(item => {
const selectedIds = this.selected.map(selectedItem => selectedItem.id);
// I guess you don't want twice the same item in your array
if (!selectedIds.includes(item.id) {
this.selected.push(item)
}
})
},
Related
I want to render an icon in a column of a VDataTable. Did anyone figure out, if it is already possible to use slots for VDataTable in the latest Lab release of Vuetify3.
With Vuetify Version 2.x it could be done like that:
<template>
<v-data-table
:headers="headers"
:items="tableData"
:items-per-page="10"
>
<template #[`item.actions`]="{ item }">
<v-icon
icon="$magnifier"
style="color: #0692bc"
class="inline cursor-pointer mr-4"
#click="action(item)"
/>
</template>
</v-data-table>
</template>
<script>
export default {
data: () => ({
tableData: [
{
test_data: 123,
},
],
headers: [
{
title: 'Funktion',
value: 'actions',
sortable: false,
},
],
}),
methods: {
action(item) {
console.log(item.test_data);
},
},
};
</script>
That results in an empty column (no Errors, Warnings as its not ready for production yet).
EDIT:
Just solved it, it is now key instead of value in the header objects. So the headers should be:
headers: [
{
title: 'Funktion',
key: 'actions',
sortable: false,
},
],
It is possible according to the documentation. It can be done like this-
<template v-slot:item.actions="{ item }">
<v-icon
icon="$magnifier"
style="color: #0692bc"
class="inline cursor-pointer mr-4"
#click="action(item)"
/>
</template>
I'm currently struggling with returning the id of a specific field from each row. I need this id to use it as a parameter for a function that will be used in an action button to delete the row.
This is how my table template is looking like:
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>Publicații</v-toolbar-title>
<v-divider class="mx-4" inset vertical> </v-divider>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Caută"
single-line
clearable
class="shrink"
hide-details=""
></v-text-field>
<v-divider class="mx-4" inset vertical> </v-divider>
<v-btn
color="orange"
v-on:click="
changeRoute();
arataDialogEditare = true;
"
v-show="arataBtnAdaugare"
>{{ txtBtnAdaugaNou }}
</v-btn>
</v-toolbar>
</template>
<template v-slot:[`item.actiuni`]>
<v-btn color="primary" fab x-small elevation="2">
<v-icon>edit</v-icon>
</v-btn>
<v-btn
v-on:click="deletePublication()"
color="primary"
fab
x-small
elevation="2"
>
<v-icon>delete_forever</v-icon>
</v-btn>
</template>
</v-data-table>
</template>
This is how my headers are looking like (my headers is dynamically loaded, that's why I have more than one) :
headers: [],
headersInitial: [
{
text: "Id publicatie",
value: "ID",
},
{
text: "Tip publicație",
value: "tipPublicatie",
},
{
text: "Nume publicație",
value: "titluPublicatie",
},
{
text: "An publicație",
value: "an",
},
{
text: "Actions",
value: "actions",
sortable: false,
},
],
headersFaraTipPublicatii: [
{
text: "Id publicatie",
value: "ID",
},
{
text: "Nume publicație",
value: "titluPublicatie",
},
{
text: "An publicație",
value: "an",
},
{
text: "Actions",
value: "actions",
sortable: false,
},
],
publicatii: [],
publicatiiCuFiltru: [],
This is how I get my data into the table:
initialize() {
this.headers = this.headersInitial;
axios.get("https://localhost:44349/api/items/ReturnarePublicatii").then((returnPub) => {
this.publicatii = returnPub.data;
this.publicatiiCuFiltru = returnPub.data
});
},
Here is my delete function:
deletePublication() {
let ID = this.headersInitial.ID
if (confirm('Are you sure you want to delete the record?')) {
axios.get("https://localhost:44349/api/items/StergereItem/" + ID).then((returnPub) => {
this.publicatii = returnPub.data;
this.publicatiiCuFiltru = returnPub.data
});
}
},
Whenever I try to delete a record, this error occurs: "Uncaught (in promise) Error: Request failed with status code 400". How can I make it work?
let ID = this.headersInitial.ID
this.headersInitial is an array - it has no property ID
Your deletePublication() method needs to receive the id of the row as a parameter (because it can be called for different rows always with different id)
That's why Vuetify passes the actual row into a item slot as a prop. Replace v-slot:item.actiuni with v-slot:item.actiuni="{ item }" as shown in the example. The item is the object (current row) and you can use it in your handler as v-on:click="deletePublication(item.ID)
I'm using VueJs and Vuetify. I have Vuetify's table where I present the data. I'm trying to edit the header column of some of the columns so I could add a dropdown search bar to filter the table. Something like:
Where Select Item is actually the header (please see the sort symbol aside of it). I could add it the dropdown above the table, like it's shown in the official docs, but I'm trying to make the dropdown as the header column itself and not just above the table.
The current code:
<v-data-table
class="elevation-1"
:headers="headers"
:items="items"
:items-per-page="tableItemsPerPage"
:footer-props="tableFooterSettings"
:loading="tableLoading"
hide-default-footer
>
where headers is a simple array of the headers, for example:
{ text: 'data', value: 'pretty_data', align: 'center', sortable: true },
Of course I want the column to be still sortable (like it's shown in the image [the arrow]). How can I make it done?
Created this working Demo. Hope it will work as per your expectation.
new Vue({
el: '#app',
data: () => ({
selected: [],
headers: [
{
text: 'Name',
align: 'left',
value: 'name'
}
],
filters: {
name: []
},
desserts: [
{
name: 'Frozen Yogurt'
},
{
name: 'Ice cream sandwich'
},
{
name: 'Eclair'
},
{
name: 'Cupcake'
},
{
name: 'Gingerbread'
},
{
name: 'Jelly bean'
},
{
name: 'Lollipop'
},
{
name: 'Honeycomb'
},
{
name: 'Donut'
},
{
name: 'KitKat'
}
]
}),
computed: {
filteredDesserts() {
return this.desserts.filter(d => {
return Object.keys(this.filters).every(f => {
return this.filters[f].length < 1 || this.filters[f].includes(d[f])
})
})
}
},
methods: {
columnValueList(val) {
return this.desserts.map(d => d[val])
}
}
})
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.1.10/dist/vuetify.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.1.10/dist/vuetify.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<div id="app">
<v-app id="inspire">
<v-data-table
v-model="selected"
:headers="headers"
:items="filteredDesserts"
item-key="name"
class="elevation-1"
>
<template slot="headers" slot-scope="props">
<tr class="grey lighten-3">
<th
v-for="header in props.headers"
:key="header.text"
>
<div v-if="filters.hasOwnProperty(header.value)">
<v-select flat hide-details small multiple clearable :items="columnValueList(header.value)" v-model="filters[header.value]">
</v-select>
</div>
</th>
</tr>
</template>
<template slot="items" slot-scope="props">
<tr>
<td>{{ props.item.name }}</td>
</tr>
</template>
</v-data-table>
</v-app>
</div>
sortable and filterable not work in dynamic data from API in DataTable vuetify
its works for static data.
in laravel api :
$posts = Post::select('id', 'title', 'type', 'active', 'category_id')
->with('category')
->where('author_id', $request['userId'])
->orderBy('created_at', 'desc')
->paginate($size);
in client :
<template>
<main>
<v-app>
<!--breadcrumbs-->
<breadcrumbs :breadcrumbs="breadcrumbs"></breadcrumbs>
<!---->
<!--actions-->
<div class="actions container">
<v-btn class="action__button" depressed color="success" #click="gotoNewCategory">
<v-icon dark> mdi-plus </v-icon>
insert category
</v-btn>
</div>
<!---->
<v-card style="margin: 15px; padding: 10px">
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="search"
single-line
hide-details
></v-text-field>
</v-card>
<!--datatable-->
<v-data-table
:headers="headers"
:items="categorys"
:page="page"
:pageCount="numberOfPages"
:options.sync="options"
:server-items-length="totalCategorys"
:loading="loading"
:search="search"
class="elevation-1"
no-data-text="no data!"
:footer-props="{
showFirstLastPage: true,
'items-per-page-text': 'test',
'items-per-page-all-text': 'all',
'page-text': '',
}"
>
<template v-slot:top>
<v-dialog v-model="dialogDelete" max-width="500px">
<v-card>
<v-card-title class="headline">
</v-card-title
>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="closeDelete">no</v-btn>
<v-btn color="blue darken-1" text #click="deleteCategoryConfirm"
>yes</v-btn
>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<template v-slot:[`item.actions`]="{ item }">
<td>
<v-icon small class="mr-2" #click="editCategory(item)"> mdi-pencil </v-icon>
<v-icon small #click="deleteCategory(item)"> mdi-delete </v-icon>
</td>
</template>
</v-data-table>
<!---->
</v-app>
</main>
</template>
<script>
import breadcrumbs from "~/components/breadcrumbs";
export default {
name: "categorys",
layout: "dashboard",
components: { breadcrumbs },
data() {
return {
search: "",
page: 1,
totalCategorys: 0,
numberOfPages: 0,
dialogDelete: false,
editedIndex: -1,
categoryIdForDelete: "",
loading: true,
options: {},
headers: [
{ text: "ID", value: "id", align: "start" },
{ text: "categoryName", value: "categoryName", align: "start" },
{ text: "actions", value: "actions", sortable: false },
],
categorys: [],
};
},
//this one will populate new data set when user changes current page.
watch: {
options: {
handler() {
this.readDataFromAPI();
},
},
deep: true,
},
methods: {
readDataFromAPI() {
this.loading = true;
const { page, itemsPerPage, sortBy, sortDesc } = this.options;
let pageNumber = page;
this.$axios
.get(
"/api/category/categories?size=" +
itemsPerPage +
"&page=" +
pageNumber +
"&sortby=" +
sortBy +
"&sortDesc=" +
sortDesc,
this.fields
)
.then((response) => {
this.loading = false;
this.categorys = response.data.result.data;
this.totalCategorys = response.data.result.total;
this.numberOfPages = Math.round(response.data.result.total / itemsPerPage);
});
},
filterOnlyCapsText(value, search, item) {
return (
value != null &&
search != null &&
typeof value === "string" &&
value.toString().toLocaleUpperCase().indexOf(search) !== -1
);
},
gotoNewCategory() {
this.$router.push("/dashboard/categorys/new");
},
editCategory(item) {
this.$router.push({ name: "dashboard-categorys-edit", params: item });
},
deleteCategory(category) {
this.categoryIdForDelete = category.id;
this.editedIndex = this.categorys.indexOf(category);
this.dialogDelete = true;
},
deleteCategoryConfirm() {
this.categorys.splice(this.editedIndex, 1);
this.$axios
.delete("api/category/delete?id=" + this.categoryIdForDelete)
.then((response) => {
if (response.data.status === "success") {
this.$toast.show(response.data.message, {
theme: "toasted-primary",
position: "bottom-left",
type: "success",
duration: 2000,
});
} else {
let list = [];
$.each(response.data.message, function (key, value) {
list.push(value);
});
this.$toast.show(list, {
theme: "toasted-primary",
position: "bottom-left",
type: "error",
duration: 4000,
});
}
});
this.closeDelete();
},
closeDelete() {
this.dialogDelete = false;
this.$nextTick(() => {
this.editedIndex = -1;
});
},
},
mounted() {
this.readDataFromAPI();
},
filters: {
striphtmltag: function (value) {
var div = document.createElement("div");
div.innerHTML = value;
var text = div.textContent || div.innerText || "";
return text;
},
},
};
</script>
<style lang="scss" scoped>
* {
font-family: YekanBakh !important;
letter-spacing: normal !important;
}
button {
&:focus {
outline: none !important;
}
}
.elevation-1 {
box-shadow: 0 0.46875rem 2.1875rem rgba(4, 9, 20, 0.03),
0 0.9375rem 1.40625rem rgba(4, 9, 20, 0.03), 0 0.25rem 0.53125rem rgba(4, 9, 20, 0.05),
0 0.125rem 0.1875rem rgba(4, 9, 20, 0.03) !important;
margin: 15px;
}
</style>
filterable not pass to API variable.
but in option for sortable :
groupBy: Array(0)
groupDesc: Array(0)
itemsPerPage: 10
multiSort: false
mustSort: false
page: 1
sortBy: Array(0)
sortDesc: Array(0)
The issue here is that you are using server-items-length. For Vuetify, this means you will be responsible for filtering and sorting the data in the source/back-end (the API call response data will be returning the filtered and sorted result)
As mentioned in Vuetify Data Table documentation, you must implement your own filtration and sorting logic when using server-items-length.
Here is what you can do
Update your readDataFromAPI method to include the "search" data property in the request parameters to your API.
You can add another watcher for the data property "search", and call the method readDataFromAPI in it (I don't like this implementation, as it will be calling the back-end with each character, which will be an expensive operation. Still depends on your use case)
search(val) {
this.readDataFromAPI()
},
A better implementation is to add an event listener to the append icon on the search text-field to call the readDataFromAPI (Make sure you are not calling it with an empty search value)
<v-text-field
v-model="search"
append-icon="mdi-magnify"
#click:append="readDataFromAPI()"
placeholder="Type your search and press Enter"
#keydown.enter="readDataFromAPI()"
label="search"
single-line
hide-details
></v-text-field>
In your API, do the filtration and sorting and return the result to the front-end, which should be what you are expecting.
Good luck.
I am trying to make a custom list filter on input using computed property. In one file I create a widget, in another I use it. Here is the code from the widget creation file:
<template>
<input value="Гарантийный случай"
v-model="searchText"
:class="{'w-autocomplete__input_completed': completed}"
ref="input"
#click="areOptionsVisible = !areOptionsVisible"/>
<div v-if="areOptionsVisible"
:style="{maxHeight: maxHeight, overflow: 'auto', zIndex: zIndex}"
class="w-autocomplete__items">
<div v-for="option in options" class="w-autocomplete__item_first" >
{{ option.name }}
<div v-for="item in option.children" class="w-autocomplete__item"
:class="{'w-autocomplete__item_active': currentIndex === item}"
#mouseenter="setActive(item)"
#keyup.up="changeCurrent('up', item)"
#keyup.down="changeCurrent('down', item)"
#click="doChoose(item)">
{{ item.name }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'dropdown',
props: {
placeholder: {
type: String,
required: false,
default: '',
},
options: {
type: Array,
default(){
return []
}
},
},
data: function () {
return {
counter: 0,
currentIndex: null,
completed: false,
chosenItem: null,
areOptionsVisible: false,
searchText: '',
data: [],
}
},
computed: {
options(){
return this.props.options.filter(elem => {
return elem.name.toLowerCase().includes(this.searchText.toLowerCase());
});
},
},
.......
}
</script>
This is how I pass the array to this list in another file:
<template>
........
<div class="complaint-form__line-item">
<div class="form-group">
<label>Гарантийный случай</label>
<dropdown :options="options" />
</div>
</div>
........
</template>
<script>
........
export default {
name: 'complaint-form',
components: {LockedImport, UploadFiles, CarNumberInput, Autocomplete, Dropdown},
props: {
......
}
},
data() {
const complaint = new Complaint();
return {
........
options: [
{name: 'Выход детали из строя в процессе эксплуатации', value: null,
children: [{name: 'Увеличение зазора, люфт (дробь/стуки)', value: 53},
{name: 'Обрыв детали', value: 54}]},
{name: 'Поломка при установке', value: null},
{name: 'Брак до установки', value: null,
children: [{name: 'Недокомплект', value: 55},
{name: 'Заводской брак (замятия, отсутствие резьбы, пробой пыльника и т.д.)',
value: 56}]},
],
}
},
Tell me please why computed doesn't work? Only I add computed and the list is not displayed at all when clicking on the field, but should. That is, it breaks down completely. I want to be filtered when entering text in input
Vue.js cannot have more than one element inside a <template> tag, so I would suggest that you enclose the whole code of the dropdown component within a <div> tag or something of the sort.
Also, and this is just a comment, I would suggest that you use the focus event for the input because with click it will still be showing even if you aren't focusing on the input.