bootstrap-vue icon indicating copy to clipboard operation
bootstrap-vue copied to clipboard

pagination show wrong page of table

Open mediaessenz opened this issue 4 years ago • 12 comments

Describe the bug

I have a problem with the pagination of a table who get it's content from a provider function, which uses ctx.currentPage and ctx.perPage to limit the data.

I store the currentPage, perPage and totalRows in a localstorage whenever it changes using a watch function:

watch: {
  perPage(newValue, oldValue) {
    localStorage.setItem('perPage', JSON.stringify(newValue));
  },
  currentPage(newValue, oldValue) {
    localStorage.setItem('currentPage', JSON.stringify(newValue));
  },
  totalRows(newValue, oldValue) {
    localStorage.setItem('totalRows', JSON.stringify(newValue));
  },
}

And restore it like this:

mounted() {
   this.perPage = JSON.parse(localStorage.getItem('perPage')) || 10;
   this.totalRows = JSON.parse(localStorage.getItem('totalRows')) || 0;
   this.currentPage = JSON.parse(localStorage.getItem('currentPage')) || 1;
}

My provider function:

async provider(ctx) {
      this.isBusy = true;
      try {
        return await itemsApi.fetchItems('https://domain.xyz/entrypoint', {
          currentPage: ctx.currentPage,
          perPage: ctx.perPage
        }).then((result) => {
          this.isBusy = false;
          this.totalRows = result.totalRows;
          return result.data;
        });
      } catch (error) {
        this.isBusy = false;
        this.totalRows = 0;
        this.currentPage = 1;
        return [];
      }
    },

The data section:

data() {
    return {
      isBusy: false,
      perPage: 10,
      currentPage: 1,
      totalRows: 0,
   }
}

The table / pagination markup:

<b-table
        :items="provider"
        :fields="filteredFields"
        primary-key="uid"
        :busy.sync="isBusy"
        :current-page="currentPage"
        :per-page="perPage"
    />
<b-pagination
        first-number
        last-number
        v-model="currentPage"
        :total-rows="totalRows"
        :per-page="perPage"
    />

Every record has an edit link, who brings the user to a different url (it's not a single page application).

If the user goes back to the list page with the table, all limit settings will be restored from the localstorage correctly and the table also loads the correct section (e.g. records 11 - 20, if he/she was on page 2 before). Unfortunately the pagination always shows page 1 – indepenent from the value in currentPage. If the user clicks on page 2 of the pagination nothings happens, because he/she is already on page 2.

This problem is very annoying, especially if the table has a lot of pages and the user needs to come back to the previously visited page.

Steps to reproduce the bug

Like described

Expected behavior

The pagination should show the loaded currentPage

Versions

Libraries:

  • BootstrapVue: 2.21.2
  • Bootstrap: 4.6.0
  • Vue: 2.6.14

Environment:

  • Device: Mac
  • OS: macOS Big Sur
  • Browser: Firefox / Chrome / Edge / Safari
  • Version: 89 / 91 / 91 / 14.1.1

mediaessenz avatar Jul 01 '21 18:07 mediaessenz

Can you create a reproduction of the issue? You can export a template to Codesandbox or Codepen from the docs playground

Hiws avatar Jul 01 '21 19:07 Hiws

Here is a sample code, but it doesn't show the problem, because i have no idea how to simulate the edit and back function in a code sandbox: https://codesandbox.io/s/uoxed

Everytime i reload the sandbox, the localstorage content get lost.

mediaessenz avatar Jul 01 '21 21:07 mediaessenz

I found a way: https://mdg5u.csb.app/

To reproduce: open the page, switch to a page higher than one and reload page afterwards. The table will show the previous selected page, but the pagination always show page one.

mediaessenz avatar Jul 02 '21 06:07 mediaessenz

Looks like it's because <b-pagination> hasn't properly gotten to calculate how many pages there should be available yet, so it wont allow a higher number than 1. You can fix that by using $nextTick when setting the current page.

this.$nextTick(() => {
  this.currentPage = JSON.parse(localStorage.getItem("currentPage")) || 1;
});

Hiws avatar Jul 02 '21 06:07 Hiws

This works, but results in two requests to the data entry point.

  1. with currentPage: 1
  2. with currentPage restored from localStorage

I have added a console.log to the provider function to demonstrate this behaviour.

mediaessenz avatar Jul 02 '21 08:07 mediaessenz

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

stale[bot] avatar Apr 16 '22 05:04 stale[bot]

This issue still exists and is not solved.

mediaessenz avatar Apr 16 '22 07:04 mediaessenz

It seems this issue still exists on v2.21.2

trandaison avatar Apr 19 '22 02:04 trandaison

Any solution?

rezaffm avatar Oct 27 '22 14:10 rezaffm

Okay, after a lot of trail & error with the framwork pagination components, I came up with my own.

If you want to disable reseting to 1, uncomment created, otherwise include a v-if to wait for the initial pages to be loaded.

Good luck

<template>
  <nav>
    <ul class="pagination justify-content-center">
      <li class="page-item" :class="{ 'disabled': meta.current_page === 1 }">
        <a class="page-link" href="#" aria-label="Previous" @click.prevent="switched(meta.current_page - 1)">
          <span aria-hidden="true">&laquo;</span>
          <span class="sr-only">Previous</span>
        </a>
      </li>
      <template v-if="section > 1">
        <li class="page-item">
          <a class="page-link" href="#" @click.prevent="switched(1)">1</a>
        </li>
        <li class="page-item">
          <a class="page-link" href="#" @click.prevent="goBackASection">...</a>
        </li>
      </template>


      <li class="page-item" v-for="page in pages" :class="{ 'active': meta.current_page == page }" :key="page">
        <a class="page-link" :href="getLink(page)" @click.prevent="switched(page)">{{ page }}</a>
      </li>

      <template v-if="section < sections">
        <li class="page-item">
          <a class="page-link" href="#" @click.prevent="goForwardASection">...</a>
        </li>
        <li class="page-item">
          <a class="page-link" href="#" @click.prevent="switched(meta.last_page)">{{ meta.last_page }}</a>
        </li>
      </template>

      <li class="page-item" :class="{ 'disabled': meta.current_page === meta.last_page }">
        <a class="page-link" href="#" aria-label="Next" @click.prevent="switched(meta.current_page + 1)">
          <span aria-hidden="true">&raquo;</span>
          <span class="sr-only">Next</span>
        </a>
      </li>
    </ul>
  </nav>
</template>

<script>
  import { range } from 'lodash'

  export default {
    props: [
      'value',
      'numberOfPages',
      'meta',
      'linkGen'
    ],
    data () {
      return {
        numbersPerSection: 3
      }
    },
    computed: {
      section() {
        return Math.ceil(this.meta.current_page / this.numbersPerSection)
      },
      sections() {
        return Math.ceil(this.meta.last_page / this.numbersPerSection)
      },
      lastPage() {
        let lastPage = ((this.section - 1) * this.numbersPerSection) + this.numbersPerSection

        if (this.section == this.sections) {
          lastPage = this.meta.last_page
        }

        return lastPage
      },
      pages() {
        return range(
          (this.section - 1) * this.numbersPerSection + 1,
          this.lastPage + 1
        )
      }
    },
    methods: {
      getLink(page) {
        if (this.linkGen) {
          let resolved = this.$router.resolve(this.linkGen(String(page)))
          return resolved.href
        }

        return `?page=${page}`
      },
      switched(page) {
        if (page <= 0 || page > this.meta.last_page) {
          return
        }

        this.$emit('pagination:switched', page)
      },
      goBackASection() {
        this.switched(
          this.firstPageOfSection(this.section - 1)
        )
      },
      goForwardASection() {
        this.switched(
          this.firstPageOfSection(this.section + 1)
        )
      },
      firstPageOfSection (section) {
        return (section - 1) * this.numbersPerSection + 1
      }
    },
    created() {
      if (this.meta.current_page > this.meta.last_page) {
        this.switched(this.meta.last_page)
      }
    }
  }
</script>



rezaffm avatar Mar 27 '23 09:03 rezaffm

@rezaffm Just add an v-if to the b-pagination component, it will do the trick

<b-pagination
  v-if="!loading && total > 0"
  v-model="currentPage"
/>

trandaison avatar Mar 27 '23 09:03 trandaison

Hi pal, thank you! That was not only my issue, I also wanted to use pagination in a classical (unopinionated way), e.g. without resetting to 1 when I change the number of pages - so I came up with my own. Might help anyone (or not). I will soon migrate to Vuetify.

rezaffm avatar Mar 27 '23 09:03 rezaffm