I've written about pagination before, and it's something I'm interested in so when the chance came to write a component that dealt with it at work I jumped at the chance. Rather than using any of the other techniques I've used in the past, this component was using VueJS, and I knew I'd get some data via a message bus. The data was in the form of an object with just three numbers: the current page selected, the total number of items and the number of items for display on each page.
The total number of items could range from one to many; the number of pages could also be massive depending upon the page size selected by the user.
As well as wanting to display a sensible number of page numbers to the user, I also wanted to decorate the pagination. The decoration needed to have the ability to jump three pages back and forth as well as a quick way of navigating to the first and last page.
So I did what I usually do and fired up JSFiddle to work up a quick prototype and came up with this. I've not included the message bus, but I'm instead using values within the data of the component. While I know it's not good form to alter the data within the component; I'm doing so here for illustration purposes.
Logic splits between the markup and the JS, but I quite like that for some reason.
HTML
<div id="app">
<ol v-if="totalPages > 0"
aria-label="Pagination navigation">
<li v-on:click="paginationOptions.page = 1"
aria-label="Go to the first page"
title="Go to the first page"
v-bind:class="{
'disabled': paginationOptions.page === 1
}">
<div class="content">←</div>
</li>
<li v-if="totalPages > 3 && paginationOptions.page - 2 >= 1"
v-on:click="paginationOptions.page = (paginationOptions.page - 3 < 1) ? 1 : paginationOptions.page - 3"
aria-label="Jump three pages backward"
title="Jump three pages backward">
<div class="content">…</div>
</li>
<li v-for="page in pages"
v-bind:key="page"
v-on:click="paginationOptions.page = page"
v-bind:aria-label="(page === paginationOptions.page) ? 'Current page, page ' + page : 'Go to page ' + page"
v-bind:aria-current="page === paginationOptions.page"
v-bind:title="(page === paginationOptions.page) ? 'Current page, page ' + page : 'Go to page ' + page"
v-bind:class="{
'active': page === paginationOptions.page
}">
<div class="content">{{page}}</div>
<span v-if="page === paginationOptions.page"
style="display: none">(current)</span>
</li>
<li v-if="totalPages > 3 && paginationOptions.page + 2 <= totalPages"
v-on:click="paginationOptions.page = (paginationOptions.page + 3 <= totalPages) ? paginationOptions.page + 3 : totalPages"
aria-label="Jump three pages forward"
title="Jump three pages forward">
<div class="content">…</div>
</li>
<li v-on:click="paginationOptions.page = totalPages"
aria-label="Go to the last page"
title="Go to the last page"
v-bind:class="{
'disabled': paginationOptions.page === totalPages
}">
<div class="content">→</div>
</li>
</ol>
</div>
JS
new Vue({
el: "#app",
data: {
paginationOptions: {
page: 2,
total: 55,
pageSize: 10
}
},
computed: {
totalPages() {
return Math.ceil(this.paginationOptions.total / this.paginationOptions.pageSize);
},
pages: function () {
const returnArray = [];
if(this.paginationOptions.page === 1) {
for(i = 1, count = 0; i <= this.totalPages && count < 3; i++, count++){
returnArray.push(i)
}
} else {
if(this.paginationOptions.page === this.totalPages) {
for(let i = this.totalPages, count = 0; i >= 1 && count < 3; i--, count++) {
returnArray.push(i)
}
returnArray.reverse();
} else {
returnArray.push(this.paginationOptions.page);
if(this.paginationOptions.page < this.totalPages) {
returnArray.push(this.paginationOptions.page + 1)
}
if(this.paginationOptions.page >= 1){
returnArray.unshift(this.paginationOptions.page - 1)
}
}
}
return returnArray;
}
}
});
SCSS
#app{
margin: 1em;
}
ol {
display: flex;
align-items: center;
justify-content: flex-end;
margin: 0;
padding: 0;
list-style-type: none;
width: 100%;
text-align: right;
li{
display: inline-block;
&.disabled {
cursor: not-allowed;
}
.content {
text-decoration: none;
font-weight: bold;
background-color: #fff;
color: #7f8c8d;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
margin: 0 3px;
border: 1px solid #e5e5e5;
}
&.active {
.content {
border-color: #2c3e50;
background-color: #2c3e50;
color: #fff;
text-decoration: none;
}
}
}
}
Top comments (0)