Requirement Analysis#
- Customizable number of displayed page buttons
- Customizable color theme for the pagination component
- Option to display text content or icons for pagination operations
Implementation#
- The number of page buttons to be displayed by the pagination component can be defined through the computed property
pagesToDisplay()
. - The color theme can be implemented by binding a class
paginationClass
that returns the colortype
. - The choice between text or icons can be implemented through nested conditional rendering in the template.
Pagination Principle#
Pagination mainly relies on these two parameters: total (total number of items) and perPage (number of items displayed per page). The backend can return the corresponding data to the frontend using these two parameters.
The relatively complicated part of the entire pagination component is the logic for displaying the page number list. The page number list is an array that returns [minimum page number, maximum page number]. Thus, the problem is divided into how to determine the maximum and minimum values of the page numbers in the page number list array. Here, two computed properties maxPage()
and minPage()
are defined to return the maximum and minimum values of the page numbers in the array.
Page Component#
<template>
<ul class="pagination" :class="paginationClass">
<!-- Previous Page -->
<li
class="page-item prev-page"
:class="{ disabled: value === 1,'no-arrows': noArrows }"
>
<a class="page-link" arial-label="Previous" @click="prevPage">
<!-- Text Content -->
<template v-if="withText">
prev
</template>
<!-- Arrow Icon -->
<i class="arrow-left" v-else></i>
</a>
</li>
<!-- Currently Displayed Pagination List -->
<li
class="page-item"
v-for="item in range(minPage, maxPage)"
:key="item"
:class="{active: value === item}"
>
<a class="page-link" @click="changePage(item)">{{item}}</a>
</li>
<!-- Next Page -->
<li
class="page-item next-page"
:class="{disabled: value === totalPage, 'no-arrows': noArrows}"
>
<a class="page-link" arial-label="Next" @click="nextPage">
<!-- Text Content -->
<template v-if="withText">
Next
</template>
<i class="arrow-right" v-else></i>
</a>
</li>
</ul>
</template>
Props#
props: {
// Color theme
type: {
type: String,
default: "primary",
// Validate the value of `type`
validator: value => {
return [
"default",
"primary",
"info",
"success",
"danger"
].includes(value);
}
},
// Whether it includes text content
withText: Boolean,
// Arrow icons
noArrows: Boolean,
// Number of pages
pageCount: {
type: Number,
default: 0
},
// Number of items displayed per page
perPage: {
type: Number,
default: 0
},
// Total number of items
total: {
type: Number,
default: 0
},
// Current page number value
value: {
type: Number,
default: 1
}
}
Data#
data: {
// Number of page numbers displayed in the list
defaultPagesToDisplay: 5
}
Computed#
computed: {
// Color theme
paginationClass() {
return `pagination-${this.type}`
},
// Total number of pages
totalPages() {
if (this.pageCount > 0) return this.pageCount;
if (this.total > 0 ) {
return Math.ceil(this.total / this.perPage);
}
},
// Number of displayed page buttons
pagesToDisplay() {
if (this.totalPages > 0 && this.totalPages < this.defaultPagesToDisplay) {
return this.totalPages;
} else {
return this.defaultPagesToDisplay;
}
},
// Minimum page number in the list
minPage() {
if (this.value >= this.pagesToDisplay) {
// Define an offset
const pagesToAdd = Math.floor(this.pagesToDisplay / 2);
// Assumed maximum page number = current page value + offset
const newMaxPage = this.value + pagesToAdd;
if (newMaxPage > this.totalPages) {
return totalPages - this.pagesToDisplay + 1;
} else {
// Current page value is positioned in the middle of the page number list
return this.value - pagesToAdd;
}
} else {
// When the current page value is less than the default number of page buttons, return 1
return 1;
}
},
// Maximum page number in the list
maxPage() {
if (this.value >= this.pagesToDisplay) {
const pagesToAdd = Math.floor(this.pagesToDisplay / 2);
const newMaxPage = this.value + pagesToAdd;
if (newMaxPage < this.totalPages) {
return newMaxPage;
} else {
return totalPages;
}
} else {
// When the current page value is less than the default number of page buttons, return the number of displayed page buttons
return this.pagesToDisplay;
}
}
}
Methods#
methods: {
// Page number list array
range(min, max) {
let arr =[];
for (let i = min; i <= max; i++) {
arr.push(i);
}
return arr;
},
// Jump to page
changePage(item) {
this.$emit("input", item);
},
// Next page
nextPage() {
if (this.value < this.totalPages) {
this.$emit("input", this.value + 1);
}
},
// Previous page
prevPage() {
if (this.value > 1) {
this.$emit("input", this.value - 1);
}
}
}
Conclusion#
To continuously improve one's technical skills, one cannot be satisfied with just writing business components. Writing independent components like pagination has also made me think about API design
and functional complexity
. Regardless of how complex a component is, it generally consists of three parts: prop, event, and slot. The specific implementation mainly relies on basic JavaScript capabilities.
Previously, I also wrote radio and input components imitating Element. I hope to persist in writing these independent components and discover a larger world. The source code for this pagination component can be found here.