Save the current page in v-data-table - javascript

I am using VueJS and Vuetify.
In v-data-table UI Component of vuetify, I want to save the current page that the current user is. For example, if the user is on the page 3 of 10, and he reloads the page, it will automatically go back to page 3 of 10. The current function is when the user reloads, it goes back to page 1 of 10.
footerOptions: {
showFirstLastPage: true,
showCurrentPage: true,
itemsPerPageOptions: [20, 50, 100],
itemsPerPageText: 'Data per Page',
},
getPagination(data) {
sessionStorage.setItem('currentPage', data.page)
},
<v-data-table
:headers="tableHeaders"
:items="users"
item-key="username"
:loading="isLoading"
#item-selected="itemSelect"
#toggle-select-all="selectAll"
#click:row="singleClick"
#dblclick:row="doubleClick"
#pagination="getPagination"
:footer-props="footerOptions"
show-select
multi-sort
dense>
</v-data-table>
Edit: I am already getting the current page and save it to session storage. Now, all I need to make that currentPage binded in the v-data-table. I tried to used the page prop of v-data-table but nothing happens. Still on page 1 even the currentPage is 2/3/4 and so on.

Here is what you should try:
add :page prop on v-data-table to bind the page number to a data property pageNum.
listen for update:page event which fires from v-data-table any time the table's page value changes, then call a method named pageChange
in the pageChange method you should save the page value (passed in for you by the event) to localStorage: localStorage.setItem("page", newPage);
on component creation, set pageNum to the value saved in localStorage: this.pageNum = localStorage.getItem("page");
simplified component code shown below:
<template>
<div>
<v-data-table
:headers="tableHeaders"
:items="users"
:page="pageNum"
#update:page="pageChange"
>
</v-data-table>
</div>
</template>
<script>
export default {
data() {
return {
pageNum: 1,
tableHeaders: [],
users: [],
};
},
methods: {
pageChange(newPage) {
localStorage.setItem("page", newPage);
},
},
created() {
this.pageNum = localStorage.getItem("page");
},
};
</script>
This will persist the page number value that v-data-table holds across page refreshes. You might want to think about clearing localStorage, localStorage.clear();, when the user navigates to a different page so if they come back to the page with v-data-table they are on page 1 again.

I think the issue may be that your users is requested from backend?
You should change the value of pageNum binded to v-data-table after users are got from remote.
<template>
<v-data-table
:items="users"
:page="pageNum"
>
</v-data-table>
</template>
<script>
export default {
data() {
return {
pageNum: 1,
users: [],
};
},
created() {
getUsers().then((promise)=>{
this.users = promise.data
this.pageNum = sessionStorage.getItem('currentPage');
})
},
};
</script>
The key point is that you should set the value of pageNum when v-data-table already has multiple pages.

Here's a super simple solution using the power of setup function and VueUse! 🚀
Check this codesandbox I made: https://codesandbox.io/s/stack-73536361-pagination-currentpage-vueuse-3yycx0?file=/src/components/Example.vue
VueUse is a collection of utility functions based on Composition API. With support for Vue 2 & 3. In this example I'll use the useStorage function that simplifies the process to create a reactive LocalStorage/SessionStorage.
First, install the VueUse npm package: npm i #vueuse/core
Then in your vue component import useStorage from #vueuse/core. And configure your reactive variable in the setup function like this:
<script>
import { useStorage } from '#vueuse/core'
export default {
name: 'Example',
setup() {
const state = useStorage('my-page', {
page: 1
})
return { state }
},
data: () => ({
...
})
}
</script>
Then in your v-data-table get/set the current page with the page prop and make sure to use the .sync modifier and link it to your reactive variable. In this example state.page.
<v-data-table
:headers="headers"
:items="desserts"
:page.sync="state.page"
:footer-props="{
showFirstLastPage: true,
showCurrentPage: true,
:items-per-page="3"
itemsPerPageOptions: [3, 50, 10],
itemsPerPageText: 'Data per Page',
}"
class="elevation-1"
></v-data-table>
That's it! VueUse is awesome, you can add more variables to your reactive state to save the items per page or anything you want.

Related

Vue 2 not setting component data when page is refreshed

I am having a rather strange problem with vue (v2.6.14) in which I am creating a new array based on one receive as a prop. Here's the relevant code:
props: { employees: Array },
data() {
return {
sortedEmployees: [],
};
},
mounted() {
this.sortedEmployees = this.employees.slice(0);
},
Essencially what I want here is to create a new reference for the employees array so that I can sort it to display later without actually altering the original array. I am not worried about cloning the actual objects inside the array since I will not alter them.
The problem with this is that, when the app 'hot reloads'(due to some change in the code) it works as expected, the hook is called and the component data is set according to what is expected BUT if I actually refresh the page in the browser, even though the hook is called, the component data is not set and I end up with an empty array. I can solve this by setting up a watcher to the prop and then it would set the data there, but what I am interested here is understanding what's happening. If the hook is called when the page is refreshed why doesn't it set the data properly as it does when 'hot reloading'?
I have a minimal setup created with vue-cli, no fancy configurations whatsoever. Any clue what I might be missing?
I guess the employees are loaded async, right?
I don't know your exact application structure, but the problem is usually the following:
The mounted hook gets called, when the component mounts, of course. If the employees are loaded async in the parent component, the mount hook is called, before the async call is resolved. So it will copy an empty array at this time.
With a watcher you solve this problem, because the watcher fires as soon as the async call resolves (as it mutates the employees array).
Same happens to the hotreload. When the hotreload occurs, the mounted hook gets executed again - at this time the employees array is already prefilled with values and therefore the correct array is copied in the mount hook.
Update
If you want to avoid watchers, you could also wrap your component like this:
<your-component
v-if="employees.length > 0"
:employees="employees"
/>
Be aware, that the copied employees array IN your component is still not reactive. It just copies the array on the first time it has more than 1 value. A watcher really makes sense in this case.
If you use watchers, make sure to use the immediate: true option. This ensures, that the watcher is also called the first time on render (and also on hotreload).
Summary
If you really want to copy the array, use watchers (with the immediate: true flag).
If it's just about sorting, go for the computed property solution suggested by #Sebastian Scholl
It sounds like the component is Refreshing (reseting) with the prop change however it's not Re-mounting. This is what would cause the array to reset to it's default state ([]), whereas on hot-reload the actual page is reloading.
Try the following, and if it doesn't resolver the issue I would suggest going the route of using a Watcher.
<template>
<div>
sorted: {{ sortedEmployees }}
<br />
not sorted: {{ employees }}
</div>
</template>
<script>
export default {
props: {
employees: Array,
},
data() {
return {
sortedEmployees: Array.from(this.employees),
};
}
};
</script>
Another approach is to just use a Computed method so and add any filtering/sorting logic inside that method. It would be something like:
<template>
<div>
<input v-model="sortKey" />
sorted: {{ sortedEmployees }}
</div>
</template>
<script>
export default {
props: {
employees: Array,
},
data () {
return {
sortKey: ''
}
},
computed: {
sortedEmployees() {
return Array.from(this.employees).sort(this.sortingFunction);
},
},
methods: {
sortingFunction(a, b) {
// sorting function using this.sortKey
}
}
};
</script>
UPDATED ANSWER
I switched up the code in the example a little and believe to have gotten it to work as I you're describing.
App.js
First off, I made it so that the employees array is updated after 3 seconds.
<template>
<div id="app">
<Dashboard :employees="employees" />
</div>
</template>
<script>
import Dashboard from './components/Dashboard.vue';
export default {
name: 'App',
components: { Dashboard },
data () {
return {
employees: [
{
employeeId: '1',
firstName: 'Leite',
}
]
};
},
methods: {
updateEmployees () {
this.employees = this.employees.concat([
{
employeeId: '2',
firstName: 'Jacinto',
},
{
employeeId: '3',
firstName: 'Capelo',
}
]);
}
},
mounted () {
setTimeout(this.updateEmployees, 3000)
},
};
</script>
Dashboard.js
The updated() lifecycle hook runs whenever data changes are detected (props and data properties). This effectively detected the change in the prop passed by the parent App.js component and re-rendered the data - whereas the mounted hook only ran once per page load.
<template>
<div id="dashbord">
<div
v-for="(employee, index) in sortedEmployees"
:key="employee.employeeId"
>
{{ employee.firstName }}
</div>
</div>
</template>
<script>
export default {
name: 'Dashboard',
props: {
employees: Array
},
data() {
return {
sortedEmployees: Array.from(this.employees)
};
},
updated() {
this.sortedEmployees = Array.from(this.employees)
}
};
</script>

Laravel & Inertia.js - reactivity problem (vue3)

could it be that Inertia.js page components are blocking the reactivity of vue?
I have a Page component, in this component is a normal single file component.
I have a function that adds items to the ItemsManager.items object.
When I'm running this function the single component below doesnt adds this items in the v-for.
But when I'm reload the Page Component it works and the previously added items appear.
Here the single file component:
<template>
<div>
<div v-for="item in items" :key="item.$key">
test
</div>
</div>
</template>
<script>
import { ItemsManager } from "./utils.js";
export default {
name: "test-component",
data: () => ({
items: ItemsManager.items
}),
};
</script>
utils.js:
export const ItemsManager = {
items: [],
add(item) {
item.$key = this.items.length;
this.items.unshift(item);
},
};
function that adds the items (in page component):
addItem(title, options) {
ItemsManager.add({
name: title,
options: options
});
},
Thanks in advance!
Since you're using Vue2, you need to know that there are some caveats when adding/deleting things to Objects/Arrays. You don't show any code relevant to your actual way of adding stuff to your object, but I can still recommend that you'd check this page to understand and fix your issue.
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

Vuetify trigger a tooltip

I'm using vuetify, and I have a tooltip over a button.
I doesn't want to show the tooltip on hover nor on click, I want to show the tooltip if some event is triggered.
translate.vue
<v-tooltip v-model="item.showTooltip" top>
<template v-slot:activator="{}">
<v-btn #click="translateItem(item)"> Call API to translate</v-btn>
</template>
<span>API quota limit has been reached</span>
</v-tooltip>
<script>
export default(){
props: {
item: { default: Objet}
}
methods: {
translateItem: function (item) {
axios
.post(baseURL + "/translateAPI", {
text: item.originTrad;
})
.then((res) => {
if (apiQuotaLimitReached(res) {
// If limit is reached I want to show the tooltip for some time
item.showTooltip = true;
setTimeout(() => {item.showTooltip = false;}, 3000);
} else { ..... }}}
</script>
itemSelect.vue (where I create object item and then use router push to transmit it to the translation page)
<script>
export default(){
methods: {
createItem: function () {
item.originTrad = "the text to translate"
....
item.showTooltip = false;
this.$router.push({
name: "translate",
params: {
"item": item,
},
}); }}
</script>
As you can see I removed the v-slot:activator="{ on }" and v-on="on" that I found on the exemple:https://vuetifyjs.com/fr-FR/components/tooltips/ , because I don't want to show the tooltip on hover. But It doesn't work as expected, the tooltip is not showing properly.
Some help would be great :)
For starters, you are trying to change a prop in the child component, something that you should not do:
[Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders. Instead, use a
data or computed property based on the prop's value. Prop being
mutated: "item.showTooltip"
So start by making a separate data variable for showTooltip (doesn't need to be a property of item perse) and try setting it to true to see what happens (and of course change v-model="item.showTooltip" to v-model="showTooltip" on v-tooltip)

How to re-render a component (Vue) with its data (and state) intact?

Here's how my code is structured: parent component shuffles through child components via v-if directives, one of the child components is using a state to define its data. Everything works except when I switch between the child components. When I get back, no data can be shown because the state has become null.
Parent component:
<template>
<div>
<Welcome v-if="view==0" />
<Courses v-if="view==1" /> //the component that I'm working on
<Platforms v-if="view==2" />
</div>
</template>
Courses component:
<template>
<div>Content</div>
</template>
<script>
export default {
name: 'Courses',
computed: {
...mapState([
'courses'
])
},
data () {
return {
courseList: [],
len: Number,
}
},
created () {
console.log("state.courses:")
console.log(this.courses)
this.courseList = this.courses
this.len = this.courses.length
},
}
</script>
Let say the default value for "view" is 1, when I load the page, the "Courses" component will be shown (complete with the data). If I click a button to change the value of "view" to 0, the "Welcome" component is shown. However, when I tried to go back to the "Courses" component, the courses component is rendered but is missing all the data.
Upon inspection (via console logging), I found that when the "Courses" component was initially rendered, the state was mapped correctly and I could use it, but if I changed the "view" to another value to render another component and then changed it back to the original value, the "Courses" component still renders but the state became undefined or null.
EDIT: Clarification.
Set courseList to a component name and use <component>
and set some enum
eg.
<template>
<component :is="this.viewState" />
</template>
<script>
export default {
// your stuff
data() {
return {
viewState: 'Welcome',
}
},
methods: {
getComponent(stateNum) {
const States = { '1': 'Welcome', '2': 'Courses', '3': 'Platforms' }
Object.freeze(States)
return States[stateNum]
}
},
created() {
// do your stuff here
const view = someTask() // someTask because I don't get it from where you're getting data
this.viewState = this.getComponent(view)
}
}
</script>
I don't actually understood correctly but here I gave some idea for approaching your problem.

b-table reloads nested components when "unshifting" a new row (but not when "pushing")

I have a vue component which contains this table, which also has a component inside its only row:
<template>
<b-table :items="records">
<template slot="row-details">
<child-component/>
</template>
</b-table>
</template>
I'm keeping the data inside the table very simple, for sample purposes:
data() {
return {
records: [{
name: "Parent Row",
_showDetails: true
}]
};
}
The child-component inside the row is quite simple too:
<template>
<div>
<p>{{number}}</p>
</div>
</template>
<script>
export default {
data() {
return {
number: Math.random(),
isUpdating: console.log("Updating Child Component")
};
},
}
</script>
If I add a new row in the parent table using records.push(newRow) everything works fine, I can see the new row and, in the child component, number does not change.
BUT If I add the new row using records.unshift(newRow) the child component is reloaded, the "Updating child component" message shows and number changes every time.
Is this the expected behaviour? How can I keep the number so it does not change when I unshift a new record?
I have created a working sample here.
To minimize re-renders of child components in a b-table, and if your data has a field that is unique for every row (i.e. an ID or primary key), set the primary-key prop to the name of the field that contains the unique row ID. This unique ID will be used as the Vue key for each <tr> element. Vue will then know if it needs to re-render the children or not.
Otherwise, what is happening is that b-table uses the row's index as the key. By pushing new rows on the table, the previous rows' Vue key stays the same (and hence not re-rendered), but if you shift on the top of the rows, those that previously has indexes of 0, 1, 2, etc have been replaced by new rows... and the entire table and it's contents (children) must be re-rendered.
EDIT/UPDATE:
I've discovered the root cause. The row-details slot was always using the row's index as its :key, and not incorporating the primary key value (if available). PR https://github.com/bootstrap-vue/bootstrap-vue/pull/4025 will fix this issue and will be available in the 2.0.0 stable release (being released today hopefully).
UPDATE 2:
BootstrapVue v2.0.0 stable has just been released (2019-09-06)
I think it has something todo with the child component. If you set _showDetails to true to every element you see what is happening. If you press prepend for the first time the number of the prepend will be the current number of parent and parent gets an new number
<template>
<div>
<input value="Append Row" #click="appendRow" type="button">
<input value="Prepend Row" #click="prependRow" type="button">
<b-table :items="records" :fields="fields">
<template v-slot:row-details="scope">
<child-component :number="scope.item.number"/>
</template>
</b-table>
</div>
</template>
<script>
import ChildComponent from "./ChildComponent";
export default {
data() {
return {
fields: ['name'],
records: [
{
name: "Parent Row",
_showDetails: true,
number: Math.random()
}
]
};
},
components: {
"child-component": ChildComponent
},
methods: {
appendRow() {
let newRow = {
name: "New Row (Append)",
_showDetails: true,
number: Math.random()
};
this.records.push(newRow);
},
prependRow() {
let newRow = {
name: "New Row (Prepend)",
_showDetails: true,
number: Math.random()
};
this.records.unshift(newRow);
}
}
};
</script>
Child-component:
<template>
<div>
<p>test {{number}}</p>
</div>
</template>
<script>
export default {
props: ['number'],
data() {
return {
isUpdating: console.log("Updating Child Component")
};
}
};
</script>

Categories