Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[added] Pagination component #786

Merged
merged 1 commit into from
Jun 8, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/examples/.eslintrc
Expand Up @@ -39,6 +39,7 @@
"PageHeader",
"PageItem",
"Pager",
"Pagination",
"Panel",
"PanelGroup",
"Popover",
Expand Down
30 changes: 30 additions & 0 deletions docs/examples/PaginationAdvanced.js
@@ -0,0 +1,30 @@
const PaginationAdvanced = React.createClass({
getInitialState() {
return {
activePage: 1
};
},

handleSelect(event, selectedEvent) {
this.setState({
activePage: selectedEvent.eventKey
});
},

render() {
return (
<Pagination
prev={true}
next={true}
first={true}
last={true}
ellipsis={true}
items={20}
maxButtons={5}
activePage={this.state.activePage}
onSelect={this.handleSelect} />
);
}
});

React.render(<PaginationAdvanced />, mountNode);
41 changes: 41 additions & 0 deletions docs/examples/PaginationBasic.js
@@ -0,0 +1,41 @@
const PaginationBasic = React.createClass({
getInitialState() {
return {
activePage: 1
};
},

handleSelect(event, selectedEvent){
this.setState({
activePage: selectedEvent.eventKey
});
},

render() {
return (
<div>
<Pagination
bsSize='large'
items={10}
activePage={this.state.activePage}
onSelect={this.handleSelect} />
<br />

<Pagination
bsSize='medium'
items={10}
activePage={this.state.activePage}
onSelect={this.handleSelect} />
<br />

<Pagination
bsSize='small'
items={10}
activePage={this.state.activePage}
onSelect={this.handleSelect} />
</div>
);
}
});

React.render(<PaginationBasic />, mountNode);
40 changes: 27 additions & 13 deletions docs/src/ComponentsPage.js
Expand Up @@ -445,6 +445,19 @@ const ComponentsPage = React.createClass({
<ReactPlayground codeText={Samples.PagerDisabled} />
</div>

{/* Pagination */}
<div className='bs-docs-section'>
<h1 id='pagination' className='page-header'>Pagination <small>Pagination</small></h1>
<h2 id='pagination-examples'>Example pagination</h2>

<p>Provide pagination links for your site or app with the multi-page pagination component. Set <code>items</code> to the number of pages. <code>activePage</code> prop dictates which page is active</p>
<ReactPlayground codeText={Samples.PaginationBasic} />

<p>More options such as <code>first</code>, <code>last</code>, <code>previous</code>, <code>next</code> and <code>ellipsis</code>.</p>
<ReactPlayground codeText={Samples.PaginationAdvanced} />

</div>

{/* Alerts */}
<div className='bs-docs-section'>
<h1 id='alerts' className='page-header'>Alert messages <small>Alert</small></h1>
Expand Down Expand Up @@ -654,19 +667,20 @@ const ComponentsPage = React.createClass({
<NavItem href='#navbars' key={10}>Navbars</NavItem>
<NavItem href='#tabs' key={11}>Togglable tabs</NavItem>
<NavItem href='#pager' key={12}>Pager</NavItem>
<NavItem href='#alerts' key={13}>Alerts</NavItem>
<NavItem href='#carousels' key={14}>Carousels</NavItem>
<NavItem href='#grids' key={15}>Grids</NavItem>
<NavItem href='#thumbnail' key={16}>Thumbnail</NavItem>
<NavItem href='#listgroup' key={17}>List group</NavItem>
<NavItem href='#labels' key={18}>Labels</NavItem>
<NavItem href='#badges' key={19}>Badges</NavItem>
<NavItem href='#jumbotron' key={20}>Jumbotron</NavItem>
<NavItem href='#page-header' key={21}>Page Header</NavItem>
<NavItem href='#wells' key={22}>Wells</NavItem>
<NavItem href='#glyphicons' key={23}>Glyphicons</NavItem>
<NavItem href='#tables' key={24}>Tables</NavItem>
<NavItem href='#input' key={25}>Input</NavItem>
<NavItem href='#pagination' key={13}>Pagination</NavItem>
<NavItem href='#alerts' key={14}>Alerts</NavItem>
<NavItem href='#carousels' key={15}>Carousels</NavItem>
<NavItem href='#grids' key={16}>Grids</NavItem>
<NavItem href='#thumbnail' key={17}>Thumbnail</NavItem>
<NavItem href='#listgroup' key={18}>List group</NavItem>
<NavItem href='#labels' key={19}>Labels</NavItem>
<NavItem href='#badges' key={20}>Badges</NavItem>
<NavItem href='#jumbotron' key={21}>Jumbotron</NavItem>
<NavItem href='#page-header' key={22}>Page Header</NavItem>
<NavItem href='#wells' key={23}>Wells</NavItem>
<NavItem href='#glyphicons' key={24}>Glyphicons</NavItem>
<NavItem href='#tables' key={25}>Tables</NavItem>
<NavItem href='#input' key={26}>Input</NavItem>
</Nav>
<a className='back-to-top' href='#top'>
Back to top
Expand Down
2 changes: 2 additions & 0 deletions docs/src/ReactPlayground.js
Expand Up @@ -32,6 +32,7 @@ import * as modOverlayMixin from '../../src/OverlayMixin';
import * as modPageHeader from '../../src/PageHeader';
import * as modPageItem from '../../src/PageItem';
import * as modPager from '../../src/Pager';
import * as modPagination from '../../src/Pagination';
import * as modPanel from '../../src/Panel';
import * as modPanelGroup from '../../src/PanelGroup';
import * as modPopover from '../../src/Popover';
Expand Down Expand Up @@ -83,6 +84,7 @@ const OverlayTrigger = modOverlayTrigger.default;
const OverlayMixin = modOverlayMixin.default;
const PageHeader = modPageHeader.default;
const PageItem = modPageItem.default;
const Pagination = modPagination.default;
const Pager = modPager.default;
const Panel = modPanel.default;
const PanelGroup = modPanelGroup.default;
Expand Down
4 changes: 3 additions & 1 deletion docs/src/Samples.js
Expand Up @@ -14,7 +14,7 @@ export default {
ButtonGroupNested: require('fs').readFileSync(__dirname + '/../examples/ButtonGroupNested.js', 'utf8'),
ButtonGroupVertical: require('fs').readFileSync(__dirname + '/../examples/ButtonGroupVertical.js', 'utf8'),
ButtonGroupJustified: require('fs').readFileSync(__dirname + '/../examples/ButtonGroupJustified.js', 'utf8'),
ButtonGroupBlock: require('fs').readFileSync(__dirname + '/../examples/ButtonGroupBlock.js', 'utf8'),
ButtonGroupBlock: require('fs').readFileSync(__dirname + '/../examples/ButtonGroupBlock.js', 'utf8'),
DropdownButtonBasic: require('fs').readFileSync(__dirname + '/../examples/DropdownButtonBasic.js', 'utf8'),
SplitButtonBasic: require('fs').readFileSync(__dirname + '/../examples/SplitButtonBasic.js', 'utf8'),
DropdownButtonSizes: require('fs').readFileSync(__dirname + '/../examples/DropdownButtonSizes.js', 'utf8'),
Expand Down Expand Up @@ -65,6 +65,8 @@ export default {
PagerDefault: require('fs').readFileSync(__dirname + '/../examples/PagerDefault.js', 'utf8'),
PagerAligned: require('fs').readFileSync(__dirname + '/../examples/PagerAligned.js', 'utf8'),
PagerDisabled: require('fs').readFileSync(__dirname + '/../examples/PagerDisabled.js', 'utf8'),
PaginationBasic: require('fs').readFileSync(__dirname + '/../examples/PaginationBasic.js', 'utf8'),
PaginationAdvanced: require('fs').readFileSync(__dirname + '/../examples/PaginationAdvanced.js', 'utf8'),
AlertBasic: require('fs').readFileSync(__dirname + '/../examples/AlertBasic.js', 'utf8'),
AlertDismissable: require('fs').readFileSync(__dirname + '/../examples/AlertDismissable.js', 'utf8'),
AlertAutoDismissable: require('fs').readFileSync(__dirname + '/../examples/AlertAutoDismissable.js', 'utf8'),
Expand Down
167 changes: 167 additions & 0 deletions src/Pagination.js
@@ -0,0 +1,167 @@
import React from 'react';
import classNames from 'classnames';
import BootstrapMixin from './BootstrapMixin';
import PaginationButton from './PaginationButton';

const Pagination = React.createClass({
mixins: [BootstrapMixin],

propTypes: {
activePage: React.PropTypes.number,
items: React.PropTypes.number,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for this to be an array of links?

Or possibly include a prop for hrefTemplate were the page number can be inserted. Thus allowing this to work with non-SPA sites.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking something like:

React.PropTypes.oneOfType([
  React.PropTypes.number,
  React.PropTypes.arrayOf(React.PropTypes.string)
]);

maxButtons: React.PropTypes.number,
ellipsis: React.PropTypes.bool,
first: React.PropTypes.bool,
last: React.PropTypes.bool,
prev: React.PropTypes.bool,
next: React.PropTypes.bool,
onSelect: React.PropTypes.func
},

getDefaultProps() {
return {
activePage: 1,
items: 1,
maxButtons: 0,
first: false,
last: false,
prev: false,
next: false,
ellipsis: true,
bsClass: 'pagination'
};
},

renderPageButtons() {
let pageButtons = [];
let startPage, endPage, hasHiddenPagesBefore, hasHiddenPagesAfter;
let {
maxButtons,
activePage,
items,
onSelect,
ellipsis
} = this.props;

if(maxButtons){
let hiddenPagesBefore = activePage - parseInt(maxButtons / 2);
startPage = hiddenPagesBefore > 1 ? hiddenPagesBefore : 1;
hasHiddenPagesAfter = startPage + maxButtons <= items;

if(!hasHiddenPagesAfter){
endPage = items;
startPage = items - maxButtons + 1;
} else {
endPage = startPage + maxButtons - 1;
}
} else {
startPage = 1;
endPage = items;
}

for(let pagenumber = startPage; pagenumber <= endPage; pagenumber++){
pageButtons.push(
<PaginationButton
key={pagenumber}
eventKey={pagenumber}
active={pagenumber === activePage}
onSelect={onSelect}>
{pagenumber}
</PaginationButton>
);
}

if(maxButtons && hasHiddenPagesAfter && ellipsis){
pageButtons.push(
<PaginationButton
key='ellipsis'
disabled>
<span aria-label='More'>...</span>
</PaginationButton>
);
}

return pageButtons;
},

renderPrev() {
if(!this.props.prev){
return null;
}

return (
<PaginationButton
key='prev'
eventKey={this.props.activePage - 1}
disabled={this.props.activePage === 1}
onSelect={this.props.onSelect}>
<span aria-label='Previous'>&lsaquo;</span>
</PaginationButton>
);
},

renderNext() {
if(!this.props.next){
return null;
}

return (
<PaginationButton
key='next'
eventKey={this.props.activePage + 1}
disabled={this.props.activePage === this.props.items}
onSelect={this.props.onSelect}>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, when button is disabled it is still possible to click on it and OnSelect is handled
#1102

<span aria-label='Next'>&rsaquo;</span>
</PaginationButton>
);
},

renderFirst() {
if(!this.props.first){
return null;
}

return (
<PaginationButton
key='first'
eventKey={1}
disabled={this.props.activePage === 1 }
onSelect={this.props.onSelect}>
<span aria-label='First'>&laquo;</span>
</PaginationButton>
);
},

renderLast() {
if(!this.props.last){
return null;
}

return (
<PaginationButton
key='last'
eventKey={this.props.items}
disabled={this.props.activePage === this.props.items}
onSelect={this.props.onSelect}>
<span aria-label='Last'>&raquo;</span>
</PaginationButton>
);
},

render() {
let classes = this.getBsClassSet();
return (
<ul
{...this.props}
className={classNames(this.props.className, classes)}>
{this.renderFirst()}
{this.renderPrev()}
{this.renderPageButtons()}
{this.renderNext()}
{this.renderLast()}
</ul>
);
}
});

export default Pagination;
51 changes: 51 additions & 0 deletions src/PaginationButton.js
@@ -0,0 +1,51 @@
import React from 'react';
import classNames from 'classnames';
import BootstrapMixin from './BootstrapMixin';
import createSelectedEvent from './utils/createSelectedEvent';

const PaginationButton = React.createClass({
mixins: [BootstrapMixin],

propTypes: {
className: React.PropTypes.string,
eventKey: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]),
onSelect: React.PropTypes.func,
disabled: React.PropTypes.bool,
active: React.PropTypes.bool
},

getDefaultProps() {
return {
active: false,
disabled: false
};
},

handleClick(event) {
// This would go away once SafeAnchor is available
event.preventDefault();

if (this.props.onSelect) {
let selectedEvent = createSelectedEvent(this.props.eventKey);
this.props.onSelect(event, selectedEvent);
}
},

render() {
let classes = this.getBsClassSet();

classes.active = this.props.active;
classes.disabled = this.props.disabled;

return (
<li className={classNames(this.props.className, classes)}>
<a href='#' onClick={this.handleClick}>{this.props.children}</a>
</li>
);
}
});

export default PaginationButton;