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; } } } }