ollie-stevenson of Six
4/30/2019 - 9:34 AM

Pagination (Nuxt)

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>