DEV Community

Dominic Myers
Dominic Myers

Posted on • Originally published at drmsite.blogspot.com on

VueJS Pagination

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">&larr;</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">&rarr;</div>
    </li>
  </ol>
</div>
Enter fullscreen mode Exit fullscreen mode

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;
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

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; 
      } 
    } 
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)