import React, { Component } from 'react' import glue from './glue' import PrevNextButtons from './PrevNextButtons' import Counter from './Counter' import Paginator from './Paginator' import { PaginationIterator } from './types/definitions' interface Props { className?: string // list of items list: any[] // how many items will be shown on one page itemsPerPage: number // index of the current page currentPageIndex: number // handler to change current page index (e.g. in case of using Redux): changePageIndex?: (pageIndex: number) => void // how many(max) paginator buttons with numbers should be shown: maximumVisiblePaginators: number // render callback: renderChildren: (children: T[]) => React.ReactNode // custom arrow element/component (if not specified // Pagimagic will render it's own : arrow?: () => JSX.Element // should counter (e.g.: item 1-10 of 213) be displayed or not showCounter?: boolean // if you don't want to specify/apply your own styles: useDefaultStyles?: boolean }; interface State { currentPage: number lastPageIndex: number | null } class Pagimagic extends Component { constructor (props: Props) { super(props) this.state = { currentPage: this.props.currentPageIndex, lastPageIndex: null } } static getDerivedStateFromProps (props: Props, state: State): State | null { if (props.currentPageIndex !== state.lastPageIndex) { return { currentPage: props.currentPageIndex, lastPageIndex: props.currentPageIndex } } return null } handleChangeCurrentPageIndex = (pageIndex: number): void => { if (this.props.changePageIndex != null) { this.props.changePageIndex(pageIndex) this.setState(() => ({ currentPage: pageIndex })) } else { this.setState(() => ({ currentPage: pageIndex })) } } getTotalPaginators = (): number => { return Math.ceil(this.props.list.length / this.props.itemsPerPage) } getItemsPerPage = (): number => this.props.itemsPerPage getCurrentPage = (): number => this.state.currentPage getMaximumVisiblePaginators = (): number => this.props.maximumVisiblePaginators getList = (): any[] => this.props.list // where should splice starts startList = (): number => this.getCurrentPage() * this.getItemsPerPage() // where should splice ends endList = (): number => this.startList() + this.getItemsPerPage() // elements which should to be shown getVisibleList = (): any[] => this.getList().slice(this.startList(), this.endList()) getMax = (): number => this.getTotalPaginators() > this.getMaximumVisiblePaginators() ? this.getMaximumVisiblePaginators() : this.getTotalPaginators() needToRenderArrows = (): boolean => this.getTotalPaginators() > this.getMax() needToShowCounter = (): boolean => Boolean(this.props.showCounter) goTo = (pageIndex: number): void => { this.handleChangeCurrentPageIndex(pageIndex) } onClickPrev = (event: React.MouseEvent): void => { event.preventDefault() if (this.getCurrentPage() > 0) { this.goTo(this.getCurrentPage() - 1) } } onClickNext = (event: React.MouseEvent): void => { event.preventDefault() if (this.getCurrentPage() + 1 < this.getTotalPaginators()) { this.goTo(this.getCurrentPage() + 1) } } createIterator = (currentPage: number): PaginationIterator => { const HALF = Math.floor(this.getMaximumVisiblePaginators() / 2) const TOTAL = this.getTotalPaginators() const VISIBLE = TOTAL > this.getMaximumVisiblePaginators() ? this.getMaximumVisiblePaginators() : TOTAL const TO_RENDER = VISIBLE - 3 const HALF_TO_RENDER = TO_RENDER / 2 const make = (condition: boolean) => (memo: PaginationIterator) => (el: number): void => { if (condition) memo.push(el) } const makeFirst = (memo: PaginationIterator, i: number): void => { if (i === 0) memo.push(i) } const makeEmpty = (condition: boolean) => (memo: PaginationIterator): void => { if (this.needToRenderArrows() && condition) memo.push('...') } const makeLast = (memo: PaginationIterator, i: number): void => { if (i + 1 === VISIBLE) memo.push(TOTAL - 1) } const result = Array.apply(null, Array(VISIBLE)).reduce((memo: PaginationIterator, _, i): PaginationIterator => { /** * Stage 1 - till the middle */ if (currentPage + HALF < VISIBLE) { make(i < VISIBLE - 1)(memo)(i) makeEmpty(i >= VISIBLE - 1)(memo) makeLast(memo, i) /** * Stage 2 - when pagination starts moving */ } else if (currentPage + HALF === VISIBLE && VISIBLE !== TOTAL) { makeFirst(memo, i) make(i !== 0 && i !== VISIBLE - 1)(memo)(i) makeEmpty(i > HALF && i + 1 === VISIBLE)(memo) makeLast(memo, i) /** * Stage 3 - main part */ } else if (currentPage + HALF < TOTAL) { const el = (i + currentPage + HALF - VISIBLE + 1) makeFirst(memo, i) makeEmpty(el < currentPage - HALF_TO_RENDER)(memo) make(el >= currentPage - HALF_TO_RENDER && el <= currentPage + HALF_TO_RENDER)(memo)(el) makeEmpty(el > currentPage + HALF_TO_RENDER && el !== TOTAL - 1)(memo) makeLast(memo, i) /** * Stage 4 - when last part rendered and we need just move active page indicator */ } else if (currentPage + 1 < TOTAL) { const renderingAmount = TOTAL - VISIBLE const el = renderingAmount + i makeFirst(memo, i) makeEmpty(el <= renderingAmount)(memo) make(el > renderingAmount)(memo)(el) /** * Last Stage - last page of paginating */ } else { const el = TOTAL - VISIBLE + i makeFirst(memo, i) makeEmpty(el < currentPage - VISIBLE + 2)(memo) make(el >= currentPage - VISIBLE + 2)(memo)(el) } return memo }, []) return result } render (): JSX.Element { const { renderChildren } = this.props return (
{renderChildren(this.getVisibleList())} { this.needToShowCounter() && }
) } } export default Pagimagic