minisaurus
minisaurus

Reputation: 1196

Getting bootstrap vue pagination to play with REST api

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

Answers (3)

OzzyTheGiant
OzzyTheGiant

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

Troy Morehouse
Troy Morehouse

Reputation: 5435

You can also disabled local pagination in the table, so that your items provider becomes responsible for controlling the pagination.

Upvotes: 1

Brian Lee
Brian Lee

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

Related Questions