import React, {Component, Fragment} from 'react'; const LEFT_PAGE = 'LEFT'; const RIGHT_PAGE = 'RIGHT'; /** * Helper method for creating a range of numbers * range(1, 5) => [1, 2, 3, 4, 5] */ const range = (from, to, step = 1) => { let i = from; const range = []; while (i <= to) { range.push(i); i += step; } return range; } export default class Paginate extends Component { constructor(props) { super(props); this.loadProps = this.loadProps.bind(this); this.gotoPage = this.gotoPage.bind(this); this.handleClick = this.handleClick.bind(this); this.handleMoveLeft = this.handleMoveLeft.bind(this); this.handleMoveRight = this.handleMoveRight.bind(this); this.makePageLink = this.makePageLink.bind(this); this.fetchPageNumbers = this.fetchPageNumbers.bind(this); this.loadProps(); this.state = {currentPage: this.currentPage}; } loadProps() { const {totalRecords = 0, pageLimit = 30, pageNeighbours = 0, currentPage = 1} = this.props; this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 30; this.totalRecords = typeof totalRecords === 'number' ? totalRecords : 0; this.pageNeighbours = typeof pageNeighbours === 'number' ? Math.max(0, Math.min(pageNeighbours, 2)) : 0; this.totalPages = Math.ceil(this.totalRecords / this.pageLimit); this.currentPage = typeof currentPage === 'number' ? currentPage : 1 } gotoPage(page) { const {onPageChanged = f => f} = this.props; const currentPage = Math.max(0, Math.min(page, this.totalPages)); this.setState({currentPage: currentPage}, () => onPageChanged(page)); } handleClick(page) { return (evt) => { evt.preventDefault(); this.gotoPage(page); } } handleMoveLeft(evt) { evt.preventDefault(); this.gotoPage(this.state.currentPage - 1); } handleMoveRight(evt) { evt.preventDefault(); this.gotoPage(this.state.currentPage + 1); } makePageLink(page) { const {makeLink = f => f} = this.props; return makeLink(page); } /** * Let's say we have 10 pages and we set pageNeighbours to 2 * Given that the current page is 6 * The pagination control will look like the following: * * (1) < {4 5} [6] {7 8} > (10) * * (x) => terminal pages: first and last page(always visible) * [x] => represents current page * {...x} => represents page neighbours */ fetchPageNumbers() { const totalPages = this.totalPages; const currentPage = this.state.currentPage; const pageNeighbours = this.pageNeighbours; /** * totalNumbers: the total page numbers to show on the control * totalBlocks: totalNumbers + 2 to cover for the left(<) and right(>) controls */ const totalNumbers = (this.pageNeighbours * 2) + 3; const totalBlocks = totalNumbers + 2; if (totalPages > totalBlocks) { const startPage = Math.max(2, currentPage - pageNeighbours); const endPage = Math.min(totalPages - 1, currentPage + pageNeighbours); let pages = range(startPage, endPage); /** * hasLeftSpill: has hidden pages to the left * hasRightSpill: has hidden pages to the right * spillOffset: number of hidden pages either to the left or to the right */ const hasLeftSpill = startPage > 2; const hasRightSpill = (totalPages - endPage) > 1; const spillOffset = totalNumbers - (pages.length + 1); // handle: (1) < {5 6} [7] {8 9} (10) if (hasLeftSpill && !hasRightSpill) { const extraPages = range(startPage - spillOffset, startPage - 1); pages = [LEFT_PAGE, ...extraPages, ...pages]; // handle: (1) {2 3} [4] {5 6} > (10) } else if (!hasLeftSpill && hasRightSpill) { const extraPages = range(endPage + 1, endPage + spillOffset); pages = [...pages, ...extraPages, RIGHT_PAGE]; // handle: (1) < {4 5} [6] {7 8} > (10) } else if (hasLeftSpill && hasRightSpill) { pages = [LEFT_PAGE, ...pages, RIGHT_PAGE]; } return [1, ...pages, totalPages]; } return range(1, totalPages); } render() { this.loadProps(); if (!this.totalRecords || this.totalPages === 1) return null; const currentPage = this.state.currentPage; const pages = this.fetchPageNumbers(); const blocks = pages.map((page, index) => { if (page === LEFT_PAGE) return (
  • «
  • ); if (page === RIGHT_PAGE) return (
  • »
  • ); return (
  • {page}
  • ); }) return ( ); } }