1
votes

I have a pagination component that receives totalPages and currentPage props and then renders buttons that change the currentPage. The problem is that right now if I have many products, I render too many buttons and I want to limit the amount of buttons to 5 - the two previous pages, current page and the next two pages.

So if my current page is 4, I'd see buttons for pages 2, 3, 4, 5, 6 and have the current page always be the middle button ( except when that's not possible ). I'm not quite sure how to handle that though, especially the corner cases when the currentPage is either first/second or second-to-last/last.

<template lang="html">
    <div class="pagination">
        <div v-for="page in totalPages">
            <div class="page" :class="{ active: page == currentPage}"  @click="setCurrentPage(page)">{{ page }}</div>
        </div>
    </div>
</template>

<script>
export default {
    props: ["totalPages", "currentPage"],
    methods: {
        setCurrentPage(page){
            this.$emit("setCurrentPage", page);
        }
    }
}
</script>
2

2 Answers

1
votes

For pagination which also shows a full set in the corner cases, use the following algorithm. The idea is to calculate the proper starting index and then generate an array based upon it:

computed: {
  pages() {
    let numShown = 5;   // This sets the number of page tabs
    numShown = Math.min(numShown, this.totalPages);
    let first = this.currentPage - Math.floor(numShown / 2);
    first = Math.max(first, 1);
    first = Math.min(first, this.totalPages - numShown + 1);
    return [...Array(numShown)].map((k,i) => i + first);
  }
}

Here is a demo (I changed some variable names):

Vue.component('child', {
  props: ["total", "current"],
  template: `
  <div class="pagination">
    <template v-for="page in pages">
      <div
        :class="{ active: page == current }" class="page no-select"
        @click="setCurrent(page)"
      >
        {{ page }}
      </div>
    </template>
  </div>
  `,
  data: () => ({
    numShown: 5
  }),
  computed: {
    pages() {
      const numShown = Math.min(this.numShown, this.total);
      let first = this.current - Math.floor(numShown / 2);
      first = Math.max(first, 1);
      first = Math.min(first, this.total - numShown + 1);
      return [...Array(numShown)].map((k,i) => i + first);
    }
  },
  methods: {
    setCurrent(page){
      this.$emit("set", page);
    }
  }
})

new Vue({
  el: "#app",
  data: () => ({
    total: 8,
    current: 1
  })
});
.page {
  display: inline-block;
  background: #dddddd;
  padding: 6px 16px;
  cursor: pointer;
  font-family: 'Helvetica';
  font-size: 13pt;
}
.active {
  background: #ddeeff;
  font-weight: bold;
}
.no-select {
  user-select: none;
  -o-user-select:none;
  -moz-user-select: none;
  -khtml-user-select: none;
  -webkit-user-select: none;
}
<div id="app">
  <child :total="total" :current="current" @set="current = $event"></child>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
1
votes

make a computed property as following:

<template lang="html">
    <div class="pagination">
        <div v-for="page in displayPages"> <!--change here -->
            <div class="page" :class="{ active: page == currentPage}"  @click="setCurrentPage(page)">{{ page }}</div>
        </div>
    </div>
</template>

<script>
export default {
  props: ["totalPages", "currentPage"],
  computed: {
    /* old
    displayPages() {
      return [-2, -1, 0, 1, 2].map(num => num + this.currentPage).filter(num => num <= totalPages && num > 0);
      // produces array of 5 or less
      // currentPage = 1 -> return [1, 2]
      // current page = 8(last) -> [6, 7, 8]
    },*/
    displayPages() {
      const totalPages = this.totalPages;
      let currentPage = this.currentPage;
      if ([1, 2].includes(currentPage)) currentPage = 3;
      else if ([totalPages -1, totalPages].includes(currentPage)) currentPage = totalPages - Math.trunc(5 / 2);
      
      return [...Array(5).keys()].map(i => i - Math.trunc(5 / 2) + currentPage)
    }
  },
  methods: {
    setCurrentPage(page){
      this.$emit("setCurrentPage", page);
    }
  }
}
</script>

it should limit the buttons and scalable; just change the array to scale.

Update: Answer Updated as per Dans's suggestion produce following output

total pages = 20
cp = current page
cp |   return
---------------------
1   [ 1, 2, 3, 4, 5 ]
2   [ 1, 2, 3, 4, 5 ]
3   [ 1, 2, 3, 4, 5 ]
4   [ 2, 3, 4, 5, 6 ]
5   [ 3, 4, 5, 6, 7 ]
6   [ 4, 5, 6, 7, 8 ]
7   [ 5, 6, 7, 8, 9 ]
8   [ 6, 7, 8, 9, 10 ]
9   [ 7, 8, 9, 10, 11 ]
10  [ 8, 9, 10, 11, 12 ]
11  [ 9, 10, 11, 12, 13 ]
12  [ 10, 11, 12, 13, 14 ]
13  [ 11, 12, 13, 14, 15 ]
14  [ 12, 13, 14, 15, 16 ]
15  [ 13, 14, 15, 16, 17 ]
16  [ 14, 15, 16, 17, 18 ]
17  [ 15, 16, 17, 18, 19 ]
18  [ 16, 17, 18, 19, 20 ]
19  [ 16, 17, 18, 19, 20 ]
20  [ 16, 17, 18, 19, 20 ]