ampache_react/app/components/elements/Pagination.jsx

205 lines
8.9 KiB
React
Raw Permalink Normal View History

// NPM imports
2016-07-07 23:23:18 +02:00
import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import CSSModules from "react-css-modules";
2016-07-29 00:50:08 +02:00
import { defineMessages, injectIntl, intlShape, FormattedMessage, FormattedHTMLMessage } from "react-intl";
2016-07-07 23:23:18 +02:00
// Local imports
import { computePaginationBounds, filterInt, messagesMap } from "../../utils";
// Translations
import commonMessages from "../../locales/messagesDescriptors/common";
import messages from "../../locales/messagesDescriptors/elements/Pagination";
// Styles
import css from "../../styles/elements/Pagination.scss";
// Define translations
const paginationMessages = defineMessages(messagesMap(Array.concat([], commonMessages, messages)));
/**
* Pagination button bar
*/
class PaginationCSSIntl extends Component {
constructor(props) {
super (props);
2016-07-07 23:23:18 +02:00
// Bind this
this.goToPage = this.goToPage.bind(this);
this.dotsOnClick = this.dotsOnClick.bind(this);
this.dotsOnKeyDown = this.dotsOnKeyDown.bind(this);
this.cancelModalBox = this.cancelModalBox.bind(this);
2016-07-07 23:23:18 +02:00
}
/**
* Handle click on the "go to page" button in the modal.
*/
goToPage(e) {
e.preventDefault();
// Parse and check page number
const pageNumber = filterInt(this.refs.pageInput.value);
if (pageNumber && !isNaN(pageNumber) && pageNumber > 0 && pageNumber <= this.props.nPages) {
// Remove error class from input form
this.refs.pageFormGroup.classList.remove("has-error");
this.refs.pageFormGroup.classList.add("has-success");
// Hide the modal and go to page
$(this.refs.paginationModal).modal("hide");
this.props.goToPage(pageNumber);
} else {
// Set error class on input form
this.refs.pageFormGroup.classList.add("has-error");
this.refs.pageFormGroup.classList.remove("has-success");
return;
2016-07-26 13:21:37 +02:00
}
}
/**
* Handle click on the ellipsis dots.
*/
2016-07-26 13:21:37 +02:00
dotsOnClick() {
// Show modal
2016-07-26 13:21:37 +02:00
$(this.refs.paginationModal).modal();
}
/**
* Bind key down events on ellipsis dots for a11y.
*/
dotsOnKeyDown(e) {
e.preventDefault;
const code = e.keyCode || e.which;
2016-07-26 13:21:37 +02:00
if (code == 13 || code == 32) { // Enter or Space key
this.dotsOnClick(); // Fire same event as onClick
}
}
/**
* Handle click on "cancel" in the modal box.
*/
2016-07-26 13:21:37 +02:00
cancelModalBox() {
// Hide modal
2016-07-26 13:21:37 +02:00
$(this.refs.paginationModal).modal("hide");
2016-07-07 23:23:18 +02:00
}
render() {
const { formatMessage } = this.props.intl;
// Get bounds
const { lowerLimit, upperLimit } = computePaginationBounds(this.props.currentPage, this.props.nPages);
// Store buttons
let pagesButton = [];
let key = 0; // key increment to ensure correct ordering
// If lower limit is above 1, push 1 and ellipsis
2016-07-07 23:23:18 +02:00
if (lowerLimit > 1) {
pagesButton.push(
<li className="page-item" key={key}>
2016-08-02 13:07:12 +02:00
<Link className="page-link" title={formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: 1})} to={this.props.buildLinkToPage(1)}>
2016-07-29 00:50:08 +02:00
<FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: 1 }} />
</Link>
2016-07-07 23:23:18 +02:00
</li>
);
key++; // Always increment key after a push
2016-07-07 23:23:18 +02:00
if (lowerLimit > 2) {
// Eventually push "…"
pagesButton.push(
<li className="page-item" key={key}>
<span tabIndex="0" role="button" onKeyDown={this.dotsOnKeyDown} onClick={this.dotsOnClick}>&hellip;</span>
2016-07-07 23:23:18 +02:00
</li>
);
key++;
}
}
// Main buttons, between lower and upper limits
for (let i = lowerLimit; i < upperLimit; i++) {
let classNames = ["page-item"];
let currentSpan = null;
2016-07-07 23:23:18 +02:00
if (this.props.currentPage == i) {
classNames.push("active");
currentSpan = <span className="sr-only">(<FormattedMessage {...paginationMessages["app.pagination.current"]} />)</span>;
2016-07-07 23:23:18 +02:00
}
const title = formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: i });
2016-07-07 23:23:18 +02:00
pagesButton.push(
<li className={classNames.join(" ")} key={key}>
2016-08-02 13:07:12 +02:00
<Link className="page-link" title={title} to={this.props.buildLinkToPage(i)}>
2016-07-29 00:50:08 +02:00
<FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: i }} />
{currentSpan}
</Link>
2016-07-07 23:23:18 +02:00
</li>
);
key++;
}
// If upper limit is below the total number of page, show last page button
if (upperLimit < this.props.nPages) {
if (upperLimit < this.props.nPages - 1) {
2016-07-07 23:23:18 +02:00
// Eventually push "…"
pagesButton.push(
<li className="page-item" key={key}>
<span tabIndex="0" role="button" onKeyDown={this.dotsOnKeyDown} onClick={this.dotsOnClick}>&hellip;</span>
2016-07-07 23:23:18 +02:00
</li>
);
key++;
}
const title = formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: this.props.nPages });
2016-07-07 23:23:18 +02:00
// Push last page
pagesButton.push(
<li className="page-item" key={key}>
2016-08-02 13:07:12 +02:00
<Link className="page-link" title={title} to={this.props.buildLinkToPage(this.props.nPages)}>
2016-07-29 00:50:08 +02:00
<FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: this.props.nPages }} />
</Link>
2016-07-07 23:23:18 +02:00
</li>
);
}
// If there are actually some buttons, show them
2016-07-07 23:23:18 +02:00
if (pagesButton.length > 1) {
return (
<div>
<nav className="pagination-nav" styleName="nav" aria-label={formatMessage(paginationMessages["app.pagination.pageNavigation"])} role="navigation" >
<ul className="pagination" styleName="pointer" role="group">
2016-07-07 23:23:18 +02:00
{ pagesButton }
</ul>
</nav>
2016-07-26 13:21:37 +02:00
<div className="modal fade" ref="paginationModal" tabIndex="-1" role="dialog" aria-labelledby="paginationModalLabel">
<div className="modal-dialog" role="document">
2016-07-07 23:23:18 +02:00
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label={formatMessage(paginationMessages["app.common.close"])}>&times;</button>
<h4 className="modal-title" id="paginationModalLabel">
<FormattedMessage {...paginationMessages["app.pagination.pageToGoTo"]} />
</h4>
2016-07-07 23:23:18 +02:00
</div>
<div className="modal-body">
<form onSubmit={this.goToPage}>
<div className="form-group" ref="pageFormGroup">
<input className="form-control" autoComplete="off" type="number" ref="pageInput" aria-label={formatMessage(paginationMessages["app.pagination.pageToGoTo"])} min="1" max={this.props.nPages} step="1" defaultValue={this.props.currentPage} autoFocus />
</div>
2016-07-07 23:23:18 +02:00
</form>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" onClick={this.cancelModalBox}>
<FormattedMessage {...paginationMessages["app.common.cancel"]} />
</button>
<button type="button" className="btn btn-primary" onClick={this.goToPage}>
<FormattedMessage {...paginationMessages["app.common.go"]} />
</button>
2016-07-07 23:23:18 +02:00
</div>
</div>
</div>
</div>
</div>
);
}
return null;
}
}
PaginationCSSIntl.propTypes = {
2016-07-07 23:23:18 +02:00
currentPage: PropTypes.number.isRequired,
goToPage: PropTypes.func.isRequired,
buildLinkToPage: PropTypes.func.isRequired,
nPages: PropTypes.number.isRequired,
intl: intlShape.isRequired,
2016-07-07 23:23:18 +02:00
};
export default injectIntl(CSSModules(PaginationCSSIntl, css));