@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
JSX
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;