@eccenca/gui-elements
Version:
Collection of low-level GUI elements like Buttons, Icons or Alerts. Also includes core styles for those elements.
340 lines (316 loc) • 11.7 kB
JSX
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import Button from './../../elements/Button/Button';
import SelectBox from './../../elements/SelectBox/SelectBox';
import TextField from './../../elements/TextField/TextField';
/**
* This component provides a pagination for switching through lists of results
*/
class Pagination extends React.Component {
// define property types
static propTypes = {
/**
* contains actual start value which is shown
*/
offset: React.PropTypes.number.isRequired,
/**
* contains number of max shown elements per page
*/
limit: React.PropTypes.number.isRequired,
/**
* contains total number of results
*/
totalResults: React.PropTypes.number.isRequired,
/**
* contains method which is called if offset have to change by user
*/
onChange: React.PropTypes.func.isRequired,
/**
* show element offset numbers as pagination information
*/
showElementOffsetPagination: React.PropTypes.bool,
/**
* define position of page change dropdown/dropup
*/
isTopPagination: React.PropTypes.bool,
/**
* text displayed next to limit changer selectbox
*/
newLimitText: React.PropTypes.string,
/**
* possible page sizes
*/
limitRange: React.PropTypes.array,
/**
* if true all buttons and inputs fields are disabled and visibility is decreased
*/
disabled: React.PropTypes.bool,
/**
* the current page number can be edited to jump directly there, works only with `showElementOffsetPagination===false`
*/
showPageInput: React.PropTypes.bool,
/**
* hide info about number of total results
*/
hideTotalResults: React.PropTypes.bool,
};
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,
};
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,
};
}
calculatePagination = ({limit, offset, totalResults}) => {
const onLastPage = offset + limit >= totalResults;
return {
limit,
offset,
totalResults,
onFirstPage: offset === 0 || totalResults === 0,
onLastPage,
currentPage: Math.min(
_.ceil(totalResults / limit),
_.floor(1 + offset / limit)
),
totalPages: _.ceil(totalResults / limit),
lastItemOnPage: onLastPage ? totalResults : offset + limit,
};
}
// trigger event to show first results
onClickFirst() {
const {limit, totalResults} = this.props;
this.props.onChange(
this.calculatePagination({
limit,
offset: 0,
totalResults,
})
);
}
// trigger event to show previous results (regarding to limit)
onClickBack() {
const {limit, totalResults, offset} = this.props;
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} = this.props;
this.props.onChange(
this.calculatePagination({
limit,
offset: offset + limit,
totalResults,
})
);
}
// trigger event to show last results (regarding to limit)
onClickLast() {
const {limit, totalResults} = this.props;
this.props.onChange(
this.calculatePagination({
limit,
offset: (_.ceil(totalResults / limit) - 1) * limit,
totalResults,
})
);
}
onNewLimit(limit) {
const {offset, totalResults} = this.props;
this.props.onChange(
this.calculatePagination({
limit,
offset: _.floor(offset / limit) * limit,
totalResults,
})
);
}
onChangePage(newPage) {
this.setState({
customPage: parseInt(newPage, 10),
});
}
handleKeyPress(e) {
const newPage = e.target.value;
if (e.charCode === 13) {
const {limit, totalResults} = this.props;
const {totalPages} = this.calculatePagination(this.props);
if (newPage < 1 || newPage > totalPages) {
return;
}
this.props.onChange(
this.calculatePagination({
limit,
offset: limit * parseInt(newPage, 10) - 1,
totalResults,
})
);
this.setState({customPage: undefined});
}
}
// template rendering
render() {
const {
showElementOffsetPagination,
offset,
limit,
totalResults,
newLimitText,
isTopPagination,
disabled,
className,
} = this.props;
const limitRange = _.chain(this.props.limitRange)
.push(limit)
.filter(_.isNumber)
.sortBy()
.sortedUniq()
.value();
const {
currentPage,
totalPages,
lastItemOnPage,
onLastPage,
onFirstPage,
} = this.calculatePagination(this.props);
const pageField = !_.isUndefined(this.state.customPage)
? (isNaN(this.state.customPage) ? 0 : this.state.customPage)
: currentPage;
const valid = pageField > 0 && pageField <= totalPages;
let pageInfo = '';
if (showElementOffsetPagination === false) {
if (this.props.showPageInput) {
pageInfo = [
<span>Page</span>,
<TextField
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>of {totalPages.toLocaleString()}</span>
];
} else {
pageInfo = `Page ${currentPage.toLocaleString()} of ${totalPages.toLocaleString()}`;
}
} else {
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}>
{this.props.hideTotalResults === false && (
<span className="ecc-gui-elements__pagination-summary">
Found {totalResults.toLocaleString()} {totalResults === 1 ? 'result' : 'results'}.
</span>
)}
{newLimitText && !_.isEmpty(limitRange) ? (
<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
value={limit}
options={limitRange}
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}
iconName="arrow_lastpage"
/>
</div>
</div>
);
}
}
export default Pagination;