Reputation: 5780
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>
Upvotes: 3
Views: 3126
Reputation: 3063
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 ]
Upvotes: 1
Reputation: 63099
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>
Upvotes: 7