The main parent component for pagination. Also requires the nested PaginationTrigger component (on Cacher). The loadPage function will be custom to each project dependant on the API being used to pull content. Example usage below:
<Pagination
:currentPage="(currentPage) ? parseInt(currentPage) : 1"
:pageCount="totalPages"
@nextPage="loadPage((currentPage) ? parseInt(currentPage) + 1 : 2)"
@previousPage="loadPage((currentPage) ? parseInt(currentPage) - 1 : 0)"
@loadPage="loadPage" />
<template>
<div class="c-Pagination">
<div class="c-Pagination__inner">
<button
class="c-Pagination__btn prev"
:disabled="isPreviousButtonDisabled"
@click="previousPage"></button>
<div class="c-Pagination__numbers">
<div
v-for="paginationTrigger in paginationTriggers"
:key="`page-${paginationTrigger}`"
class="c-Pagination__number-wrap">
<span v-if="lastEllipse && paginationTrigger === pageCount" class="c-Pagination__ellipse last">...</span>
<PaginationTrigger
:class="['c-Pagination__number', {'current': paginationTrigger === currentPage}]"
:pageNumber="parseInt(paginationTrigger)"
@loadPage="onLoadPage" />
<span v-if="firstEllipse && paginationTrigger === 1" class="c-Pagination__ellipse first">...</span>
</div>
</div>
<button
class="c-Pagination__btn next"
:disabled="isNextButtonDisabled"
@click="nextPage"></button>
</div>
</div>
</template>
<script>
import PaginationTrigger from '~/components/pagination/PaginationTrigger.vue'
export default {
data() {
return {
firstEllipse: false,
lastEllipse: false
}
},
components: {
PaginationTrigger
},
props: {
currentPage: {
type: Number,
required: true
},
pageCount: {
type: Number,
required: true
},
visiblePagesCount: {
type: Number,
default: 5
}
},
created() {
const self = this
this.$nuxt.$on('recalculatePaginationTriggers', () => {
// Must be emmited in the function that renders the new list of items to be paginated i.e loadPages()
self.paginationTriggers
})
},
computed: {
isPreviousButtonDisabled() {
return this.currentPage === 1
},
isNextButtonDisabled() {
return this.currentPage === this.pageCount
},
paginationTriggers() {
const currentPage = this.currentPage
const pageCount = this.pageCount
const visiblePagesCount = this.visiblePagesCount
const visiblePagesThreshold = (visiblePagesCount - 1) / 2
let pagintationTriggersArray = Array(this.visiblePagesCount - 1).fill(0)
if (pageCount < visiblePagesCount) {
// Numbers should be displayed as a flat list
pagintationTriggersArray = Array(this.visiblePagesCount - 2).fill(0)
pagintationTriggersArray[0] = 1
const pagintationTriggers = pagintationTriggersArray.map(
(paginationTrigger, index) => {
return pagintationTriggersArray[0] + index
}
)
this.firstEllipse = false
this.lastEllipse = false
return pagintationTriggers
} else {
// Numbers should be displayed as a dynamic list that includes ellipse
if (currentPage <= visiblePagesThreshold + 1) {
// The selected page number is smaller than half of the list width
pagintationTriggersArray[0] = 1
const pagintationTriggers = pagintationTriggersArray.map(
(paginationTrigger, index) => {
return pagintationTriggersArray[0] + index
}
)
pagintationTriggers.push(pageCount)
this.firstEllipse = false
this.lastEllipse = true
return pagintationTriggers
} else if (currentPage >= pageCount - visiblePagesThreshold + 1) {
// The selected page number is bigger than half of the list width counting from the end of the list
const pagintationTriggers = pagintationTriggersArray.map(
(paginationTrigger, index) => {
return pageCount - index
}
)
pagintationTriggers.reverse().unshift(1)
this.firstEllipse = true
this.lastEllipse = false
return pagintationTriggers
} else {
// All other cases
pagintationTriggersArray[0] = currentPage - visiblePagesThreshold + 1
const pagintationTriggers = pagintationTriggersArray.map(
(paginationTrigger, index) => {
return pagintationTriggersArray[0] + index
}
)
pagintationTriggers.unshift(1);
pagintationTriggers[pagintationTriggers.length - 1] = pageCount
this.firstEllipse = true
this.lastEllipse = true
return pagintationTriggers
}
}
}
},
methods: {
nextPage() {
this.$emit('nextPage')
},
previousPage() {
this.$emit('previousPage')
},
onLoadPage(value) {
this.$emit('loadPage', value)
}
}
}
</script>
<style lang="scss">
/*--------------------
Block
--------------------*/
.c-Pagination {
display: flex;
justify-content: center;
align-items: center;
}
/*--------------------
Elements
--------------------*/
.c-Pagination__inner {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.c-Pagination__numbers {
display: flex;
justify-content: center;
align-items: center;
}
.c-Pagination__number {
text-align: center;
height: 30px;
line-height: 30px;
border-radius: 50%;
margin: 0 8px;
cursor: pointer;
transition: opacity .15s linear;
&:hover:not(.current) {
opacity: 0.6;
}
&.current {
display: inline-block;
width: 30px;
margin: 0;
background-color: blue;
border: 1px solid green;
}
}
.c-Pagination__btn {
border: 0;
width: 30px;
height: 30px;
background-color: transparent;
background: red;
cursor: pointer;
transition: opacity .15s linear;
&:hover {
opacity: 0.6;
}
&:disabled {
opacity: 0.3;
}
&.prev {
margin-right: 8px;
}
&.next {
margin-left: 8px;
}
}
.c-Pagination__ellipse {
&.first {
margin-right: 6px;
}
&.last {
margin-left: 6px;
}
}
</style>