Skip to content

Instantly share code, notes, and snippets.

@hartmut-co-uk
Last active October 5, 2021 03:58
Show Gist options
  • Save hartmut-co-uk/0f01f852c3efdf97b3286cff8159b6e7 to your computer and use it in GitHub Desktop.
Save hartmut-co-uk/0f01f852c3efdf97b3286cff8159b6e7 to your computer and use it in GitHub Desktop.
SSR supporting nuxt pagination vue component, pagination as links (`nuxt-link` -> vue-router) & query params (configurable param names). Data/API loading paginated results is done via nuxt `fetch` & `watchQuery`.
<template>
<div class="pagination">
<div class="inner">
<template v-if="showPrev">
<nuxt-link v-if="prev" :to="routeToPage(prev)" class="page previous">Previous</nuxt-link>
<span v-else class="page previous disabled">Previous</span>
</template>
<template v-for="it in prevPages">
<nuxt-link v-if="it !== -1" :key="it" :to="routeToPage(it)" class="page">{{ it }}</nuxt-link>
<span v-else :key="it" class="page more">...</span>
</template>
<span class="page current">{{ page }}</span>
<template v-for="it in nextPages">
<nuxt-link v-if="it !== -1" :key="it" :to="routeToPage(it)" class="page">{{ it }}</nuxt-link>
<span v-else :key="it" class="page more">...</span>
</template>
<template v-if="showNext">
<nuxt-link v-if="next" :to="routeToPage(next)" class="page next">Next</nuxt-link>
<span v-else class="page next disabled">Next</span>
</template>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'nuxt-property-decorator'
@Component({
components: {},
})
export default class RouterPagination extends Vue {
@Prop({ required: true }) readonly page: number
@Prop({ required: true }) readonly size: number
@Prop({ required: true }) readonly totalElements: number
@Prop({ default: 10 }) readonly PAGE_SIZE_DEFAULT: number
@Prop({ default: 100 }) readonly PAGE_SIZE_MAX: number
@Prop({ default: 100 }) readonly PAGE_NUMBER_MAX: number
@Prop({ default: 'page' }) readonly pageParamName: string
@Prop({ default: 'size' }) readonly sizeParamName: string
@Prop({ default: 9 }) readonly maxPagesToList: number
@Prop({ default: true }) readonly showPrev: boolean
@Prop({ default: true }) readonly showNext: boolean
@Prop({ default: null }) readonly pageSizeOptions: number[] | null
get sizeSafe () {
return Math.max(1, Math.min(this.size, this.PAGE_SIZE_MAX))
}
get pageSafe () {
return Math.max(1, Math.min(this.page, this.PAGE_NUMBER_MAX))
}
get totalElementsSafe () {
return Math.max(0, this.totalElements)
}
get prev () {
return this.prevPage(this.pageSafe)
}
get next () {
return this.nextPage(this.pageSafe)
}
get prevPages () {
const pages: number[] = []
let it: number | null = this.pageSafe
let iterations = this.maxPagesToList - 1 - Math.min(Math.floor(this.maxPagesToList / 2), this.noOfPagesAfter) - (this.showMorePrev ? 2 : 0)
while (iterations > 0 && it !== null) {
it = this.prevPage(it)
if (it !== null) {
pages.unshift(it)
}
iterations--
}
if (this.showMorePrev) {
pages.unshift(-1)
pages.unshift(1)
}
return pages
}
get nextPages () {
const pages: number[] = []
let it: number | null = this.pageSafe
let iterations = this.maxPagesToList - 1 - Math.min(Math.floor(this.maxPagesToList / 2), this.noOfPagesBefore) - (this.showMoreNext ? 2 : 0)
while (iterations > 0 && it !== null) {
it = this.nextPage(it)
if (it !== null) {
pages.push(it)
}
iterations--
}
if (this.showMoreNext) {
pages.push(-1)
pages.push(this.lastPage)
}
return pages
}
get lastPage () {
return Math.min(Math.ceil(this.totalElementsSafe / this.sizeSafe), this.PAGE_NUMBER_MAX)
}
get noOfPagesBefore () {
return this.pageSafe - 1
}
get noOfPagesAfter () {
return this.lastPage - this.pageSafe
}
get showMorePrev () {
return this.maxPagesToList - Math.min(Math.floor(this.maxPagesToList / 2), this.noOfPagesAfter) <= this.noOfPagesBefore
}
get showMoreNext () {
return this.maxPagesToList - Math.min(Math.floor(this.maxPagesToList / 2), this.noOfPagesBefore) <= this.noOfPagesAfter
}
prevPage (it: number): number | null {
const prev = it - 1
return prev >= 1 ? prev : null
}
nextPage (it: number): number | null {
const next = it + 1
if (next <= this.PAGE_NUMBER_MAX && it * this.sizeSafe < this.totalElementsSafe) {
return next
}
return null
}
routeToPage (page: number) {
console.debug('routeToPage', page)
const query: any = {
...this.$nuxt.$route.query,
}
if (page <= 1) {
delete query[this.pageParamName]
} else {
query[this.pageParamName] = page
}
console.debug('query', query)
return { query: query }
}
}
</script>
<style lang="stylus" scoped>
.pagination
text-align center
.inner
margin auto 0
display inline-block
a:focus, a:hover, span:focus:not(.current), span:hover:not(.current)
background-color: #edf1f4
border-color: #e1e4e8
text-decoration: none
.page
-webkit-user-select: none
background: #fff
border: 1px solid #e1e4e8
color: #0366d6
cursor: pointer
float: left
font-size: 13px
font-style: normal
font-weight: 600
margin-left: -1px
padding: 7px 12px
position: relative
user-select: none
vertical-align: middle
white-space: nowrap
.page.previous
border-bottom-left-radius: 3px
border-top-left-radius: 3px
margin-left: 0
.page.next
border-bottom-right-radius: 3px
border-top-right-radius: 3px
margin-right: 0
.page.current
background-color: #0366d6
border-color: #0366d6
color: #fff
.page.disabled, .page.more
background-color: #edf1f4
color: #d1d5da
cursor: default
a
text-decoration none
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment