Reputation: 1196
Trying to get Bootstrap Vue to play with a REST api that returns data for one page and the total number of records (based on this):
<template>
</div>
<b-pagination
v-on:change="onPageChange"
:total-rows="totalRows"
:per-page="perPage"
v-model="currentPage" />
<b-table responsive striped hover show-empty
stacked="md"
:items="items"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:sort-direction="sortDirection"
@filtered="onFiltered">
</b-table>
</div>
</template>
<script>
...
export default {
name: 'TableList',
data() {
return {
module: null,
title: 'Table',
items: [],
fields: [],
errors: [],
currentPage: 1,
perPage: 15,
totalRows: 0,
pageOptions: [ 5, 10, 15 ],
sortBy: null,
sortDesc: false,
sortDirection: 'asc',
filter: null,
}
},
created() {
...
this.fetch();
},
methods: {
fetch() {
var me = this;
var requestParams = {
page: this.currentPage,
per_page: this.perPage
};
if(this.sortBy) {
requestParams = Object.assign({ sort_by: this.sortBy }, requestParams);
}
Rest('GET', '/table/' + this.table, requestParams, this.$root.user.token)
.then(response => {
me.items = response.data[1]
me.totalRows = response.data[0].total_entries
})
.catch(error => {
this.errors.push('Error: ' + error.response.status + ': ' + error.response.statusText)
})
.finally(() => {
//alert('turn off loader!');
});
}
}
</script>
This Vue works if I fetch the entire table. However, when I use the REST api to return one page at a time, the number of pages is calculated to be 1, and the forward and end links are inactive. Thus, I am unable to trigger a request for e.g. page 2.
The REST api correctly returns the total number of rows in the table, and the number of rows requested, but Bootstrap Vue does not appear to be watching/reacting to changes to this.totalRows.
What have I missed?
Upvotes: 20
Views: 25909
Reputation: 739
My approach was to inject an items provider function into the items prop for B-Table (:items="fetchData"
) so that it can run this function anytime the table state changes.
// using TypeScript
{
methods: {
public fetchData(
context: { [key: string]: any },
callback: (items: Inspection[]) => void
): null {
const params = new URLSearchParams()
params.append("page", context.currentPage)
params.append("count", context.perPage)
params.append("sort", context.sortBy)
params.append("sort_order", context.sortDesc ? "DESC" : "ASC")
this.$store
.dispatch(Actions.FETCH_ITEMS)
.then(success => success && callback(
this.$store.state.items ?? []
))
return null
}
}
In this case, I'm using Vuex, and the dispatched makes an HTTP call, to which it saves it in the store via a mutation. So after that I just access the items via the store and pass them to the callback function to trigger the rerender. The success
variable that's returned is just a flag to specify that the HTTP call was made successfully.
The context object passed in provides the table state, such as the current page, rows on display, sort column and order, so that should allow you to create a query string that you can attach to your API endpoint.
If for some reason you need to manually trigger a rerender, just use a ref on the table and call the refresh method:
{
methods: {
rerender() {
this.$refs.table.refresh()
}
}
}
Upvotes: 0
Reputation: 5435
You can also disabled local pagination in the table, so that your items provider becomes responsible for controlling the pagination.
Upvotes: 1
Reputation: 18187
You need to set the per-page
prop to 0 on the b-table
component to disable the local pagination and allow b-pagination
to handle the data. Here's an example:
new Vue({
el: '#app',
data() {
return {
items: [],
fields: [{
key: 'postId',
label: 'Post ID'
},
{
key: 'id',
label: 'ID'
},
{
key: 'name',
label: 'Name'
},
{
key: 'email',
label: 'Email'
},
{
key: 'body',
label: 'Body'
}
],
currentPage: 0,
perPage: 10,
totalItems: 0
}
},
mounted() {
this.fetchData().catch(error => {
console.error(error)
})
},
methods: {
async fetchData() {
this.items = await fetch(`https://jsonplaceholder.typicode.com/comments?_page=${this.currentPage}&_limit=${this.perPage}`)
.then(res => {
this.totalItems = parseInt(res.headers.get('x-total-count'), 10)
return res.json()
})
.then(items => items)
}
},
watch: {
currentPage: {
handler: function(value) {
this.fetchData().catch(error => {
console.error(error)
})
}
}
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue.js"></script>
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-table show-empty :items="items" :fields="fields" :current-page="currentPage" :per-page="0"></b-table>
<b-pagination size="md" :total-rows="totalItems" v-model="currentPage" :per-page="perPage"></b-pagination>
</div>
Upvotes: 36