UNPKG

@eccenca/gui-elements

Version:

Collection of low-level GUI elements like Buttons, Icons or Alerts. Also includes core styles for those elements.

404 lines (372 loc) 13.5 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import classNames from 'classnames'; import Button from '../Button/Button'; import SelectBox from '../SelectBox/SelectBox'; import TextField from '../TextField/TextField'; import Spinner from '../Spinner/Spinner'; import Tooltip from '../Tooltip/Tooltip'; /** * This component provides a pagination for switching through lists of results */ class Pagination extends Component { // define property types static propTypes = { /** * contains actual start value which is shown */ offset: PropTypes.number.isRequired, /** * contains number of max shown elements per page */ limit: PropTypes.number.isRequired, /** * contains total number of results. The value must be positive or undefined. */ totalResults: PropTypes.number, /** * contains method which is called if offset have to change by user */ onChange: PropTypes.func.isRequired, /** * show element offset numbers as pagination information */ showElementOffsetPagination: PropTypes.bool, /** * define position of page change dropdown/dropup */ isTopPagination: PropTypes.bool, /** * text displayed next to limit changer selectbox */ newLimitText: PropTypes.string, /** * possible page sizes */ limitRange: PropTypes.array, /** * if true all buttons and inputs fields are disabled and visibility is decreased */ disabled: PropTypes.bool, /** * the current page number can be edited to jump directly there, works only with * `showElementOffsetPagination===false` */ showPageInput: PropTypes.bool, /** * hide info about number of total results */ hideTotalResults: PropTypes.bool, /** * show a spinner if true and totalResults is not set */ pendingTotal: PropTypes.bool, /** * additional class names */ className: PropTypes.string, }; static defaultProps = { /** * sets site information as "numbers of elements" as default */ showElementOffsetPagination: false, /** * predefined available range steps */ limitRange: [5, 10, 25, 50, 100, 200], disabled: false, hideTotalResults: false, showPageInput: false, isTopPagination: false, pendingTotal: false, totalResults: undefined, className: '', newLimitText: '', }; constructor(props) { super(props); this.displayName = 'Pagination'; this.onClickFirst = this.onClickFirst.bind(this); this.onClickBack = this.onClickBack.bind(this); this.onClickForward = this.onClickForward.bind(this); this.onClickLast = this.onClickLast.bind(this); this.onNewLimit = this.onNewLimit.bind(this); this.onChangePage = this.onChangePage.bind(this); this.handleKeyPress = this.handleKeyPress.bind(this); this.state = { customPage: undefined, }; } // trigger event to show first results onClickFirst() { const { limit, totalResults, onChange } = this.props; onChange( this.calculatePagination({ limit, offset: 0, totalResults, }) ); } // trigger event to show previous results (regarding to limit) onClickBack() { const { limit, totalResults, offset, onChange, } = this.props; onChange( this.calculatePagination({ limit, offset: offset < limit ? 0 : offset - limit, totalResults, }) ); } // trigger event to show next results (regarding to limit) onClickForward() { const { limit, totalResults, offset, onChange, } = this.props; onChange( this.calculatePagination({ limit, offset: offset + limit, totalResults, }) ); } // trigger event to show last results (regarding to limit) onClickLast() { const { limit, totalResults, onChange } = this.props; onChange( this.calculatePagination({ limit, offset: (_.ceil(totalResults / limit) - 1) * limit, totalResults, }) ); } onNewLimit(limit) { const { offset, totalResults, onChange } = this.props; onChange( this.calculatePagination({ limit, offset: _.floor(offset / limit) * limit, totalResults, }) ); } onChangePage(newPage) { this.setState({ customPage: parseInt(newPage, 10), }); } calculatePagination = ({ limit, offset, totalResults }) => { const onLastPage = offset + limit >= totalResults; return { limit, offset, totalResults, onFirstPage: offset === 0 || totalResults === 0, onLastPage, currentPage: totalResults === undefined ? _.floor(1 + offset / limit) : Math.min( _.ceil(totalResults / limit), _.floor(1 + offset / limit) ), totalPages: _.ceil(totalResults / limit), lastItemOnPage: onLastPage ? totalResults : offset + limit, }; } handleKeyPress(e) { const newPage = parseInt(e.target.value, 10); if (e.charCode === 13) { const { limit, totalResults, onChange } = this.props; const { totalPages } = this.calculatePagination(this.props); if (newPage < 1 || newPage > totalPages) { return; } onChange( this.calculatePagination({ limit, offset: limit * (newPage - 1), totalResults, }) ); this.setState({ customPage: undefined }); } } // template rendering render() { const { showElementOffsetPagination, offset, limit, limitRange, totalResults, newLimitText, isTopPagination, disabled, className, pendingTotal, showPageInput, hideTotalResults, } = this.props; const { customPage } = this.state; const limitRangeSorted = _.chain(limitRange) .push(limit) .filter(_.isNumber) .sortBy() .sortedUniq() .value(); const { currentPage, totalPages, lastItemOnPage, onLastPage, onFirstPage, } = this.calculatePagination(this.props); const pageField = !_.isUndefined(customPage) ? (isNaN(customPage) ? 0 : customPage) : currentPage; const valid = pageField > 0 && pageField <= totalPages; let pageInfo = ''; if (showElementOffsetPagination === false && showPageInput && !_.isUndefined(totalResults)) { pageInfo = [ <span key="PageText">Page</span>, <TextField key="PageNumber" reducedSize className="ecc-gui-elements__pagination__pagenumber" onKeyPress={this.handleKeyPress} disabled={disabled === true} stretch={false} style={{ // the calculation can be improved width: `calc(${Math.max(1, pageField.toString().length)}ex + 1rem)`, }} min={1} max={totalPages} type="number" value={pageField > 0 ? pageField : ''} error={valid ? '' : 'Invalid page'} onChange={e => { this.onChangePage(e.value); }} />, <span key="PageText2"> {'of '} {totalPages.toLocaleString()} </span>, ]; } else if (showElementOffsetPagination === false && !showPageInput && !_.isUndefined(totalResults)) { pageInfo = `Page ${currentPage.toLocaleString()} of ${totalPages.toLocaleString()}`; } else if (showElementOffsetPagination === false && _.isUndefined(totalResults)) { pageInfo = `Page ${currentPage.toLocaleString()}`; } else if (showElementOffsetPagination === true) { const firstItem = Math.min(totalResults, offset + 1); const lastItem = lastItemOnPage; const start = firstItem === lastItem ? lastItem.toLocaleString() : `${firstItem.toLocaleString()} - ${lastItem.toLocaleString()}`; pageInfo = `${start} of ${totalResults.toLocaleString()}`; } // render actual site information const pageInformation = ( <div className="ecc-gui-elements__pagination-pageInfo"> {pageInfo} </div> ); const paginationClassNames = classNames( 'ecc-gui-elements__pagination', { 'ecc-gui-elements__pagination--disabled': disabled === true, }, className ); return ( <div className={paginationClassNames}> {( hideTotalResults === false && !_.isUndefined(totalResults) ) && ( <span className="ecc-gui-elements__pagination-summary"> Found {' '} {totalResults.toLocaleString()} {' '} {totalResults === 1 ? 'result' : 'results'} . </span> )} {newLimitText && !_.isEmpty(limitRangeSorted) ? ( <div className="ecc-gui-elements__pagination-limit"> <span className="ecc-gui-elements__pagination-limit_text"> {newLimitText} </span> <div className="ecc-gui-elements__pagination-limit_size"> <SelectBox reducedSize value={limit} options={limitRangeSorted} clearable={false} searchable={false} onChange={this.onNewLimit} optionsOnTop={isTopPagination !== true} disabled={disabled === true} /> </div> </div> ) : ( '' )} <div className="ecc-gui-elements__pagination-actions"> <Button className="ecc-gui-elements__pagination-actions__first-page-button" onClick={this.onClickFirst} disabled={onFirstPage || disabled === true} iconName="arrow_firstpage" /> <Button className="ecc-gui-elements__pagination-actions__prev-page-button" onClick={this.onClickBack} disabled={onFirstPage || disabled === true} iconName="arrow_prevpage" /> {pageInformation} <Button className="ecc-gui-elements__pagination-actions__next-page-button" onClick={this.onClickForward} disabled={onLastPage || disabled === true} iconName="arrow_nextpage" /> <Button className="ecc-gui-elements__pagination-actions__last-page-button" onClick={this.onClickLast} disabled={onLastPage || disabled === true || totalResults < 1 || totalResults === undefined} iconName="arrow_lastpage" /> </div> {totalResults === undefined && pendingTotal && ( <div className="ecc-gui-elements__pagination-processinfo"> <Tooltip label="Fetch count of total results." > <Spinner appearInline={true} /> </Tooltip> </div> )} </div> ); } } export default Pagination;