-
Notifications
You must be signed in to change notification settings - Fork 378
Fixes #123 - add PaginationRow component #162
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| import React from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import ClassNames from 'classnames'; | ||
|
|
||
| import { MenuItem, DropdownButton, Icon } from '../../index'; | ||
|
|
||
| const ArrowIcon = props => { | ||
| const name = `angle-${props.name}`; | ||
| return <Icon type="fa" name={name} className="i" />; | ||
| }; | ||
|
|
||
| ArrowIcon.propTypes = { | ||
| name: PropTypes.oneOf(['left', 'double-left', 'right', 'double-right']) | ||
| }; | ||
|
|
||
| class PaginationRow extends React.Component { | ||
| constructor(props) { | ||
| super(props); | ||
|
|
||
| this.initPagination(props); | ||
| this.state = { | ||
| pageChangeValue: Number(props.currentPage) | ||
| }; | ||
| } | ||
|
|
||
| componentWillReceiveProps(nextProps) { | ||
| if (this.props.currentPage !== nextProps.currentPage) { | ||
| this.setState({ pageChangeValue: Number(nextProps.currentPage) }); | ||
| } | ||
|
|
||
| this.initPagination(nextProps); | ||
| } | ||
|
|
||
| initPagination(props) { | ||
| this.perPage = Number(props.perPage); | ||
| this.totalCount = Number(props.totalCount); | ||
| this.currentPage = Number(props.currentPage); | ||
| } | ||
|
|
||
| msg(key) { | ||
| return this.props.messages[key] || PaginationRow.defaultMessages[key]; | ||
| } | ||
|
|
||
| totalPages() { | ||
| return Math.ceil(this.props.totalCount / this.perPage); | ||
| } | ||
|
|
||
| setPageRelative(diff) { | ||
| this.setPage(Number(this.props.currentPage) + diff); | ||
| } | ||
|
|
||
| setPage(page) { | ||
| if (page !== '') { | ||
| this.props.onPageSet(Number(page)); | ||
| } else { | ||
| console.error("Page can't be blank"); | ||
| } | ||
| } | ||
|
|
||
| handlePageChange(e) { | ||
| this.setState({ pageChangeValue: e.target.value }); | ||
| } | ||
|
|
||
| handleFormSubmit(e) { | ||
| this.setPage(this.state.pageChangeValue); | ||
| e.preventDefault(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dont think it matters much (or at all), but I would make sure the browser doesn't submit first :)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also, shouldn't we allow to execute a custom action? e.g. actually go fetch new data or something?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ya i don't think we need to handle form submit... just handle the callback when the values change...and have the server go fetch it...and the send the props back down.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It actually prevents the form from being submitted. I didn't find any way how to use
|
||
| } | ||
|
|
||
| renderPerPageDropdown() { | ||
| const { perPageOptions, onPerPageSet } = this.props; | ||
|
|
||
| return ( | ||
| <DropdownButton | ||
| dropup | ||
| bsStyle="default" | ||
| title={this.perPage} | ||
| id="per-page" | ||
| > | ||
| {perPageOptions.map(opt => { | ||
| return ( | ||
| <MenuItem | ||
| active={opt === this.perPage} | ||
| onSelect={() => onPerPageSet(opt)} | ||
| key={opt} | ||
| > | ||
| {opt} | ||
| </MenuItem> | ||
| ); | ||
| })} | ||
| </DropdownButton> | ||
| ); | ||
| } | ||
|
|
||
| render() { | ||
| const perPageDropdown = this.renderPerPageDropdown(); | ||
|
|
||
| const displayedRangeStart = (this.currentPage - 1) * this.perPage + 1; | ||
| const displayedRangeEnd = Math.min( | ||
| displayedRangeStart + this.perPage - 1, | ||
| this.totalCount | ||
| ); | ||
| const displayedRange = `${displayedRangeStart}-${displayedRangeEnd}`; | ||
|
|
||
| const backButtonsClass = this.currentPage === 1 ? 'disabled' : ''; | ||
| const nextButtonsClass = | ||
| this.currentPage * this.perPage >= this.totalCount ? 'disabled' : ''; | ||
|
|
||
| const totalPages = this.totalPages(); | ||
|
|
||
| const classes = ClassNames(this.props.className, 'clearfix'); | ||
|
|
||
| return ( | ||
| <div> | ||
| <form className={classes} onSubmit={e => this.handleFormSubmit(e)}> | ||
| <div className="form-group"> | ||
| <div>{perPageDropdown}</div> | ||
| | ||
| <span>{this.msg('perPage')}</span> | ||
| </div> | ||
| <div className="form-group"> | ||
| <span> | ||
| <span className="pagination-pf-items-current"> | ||
| {displayedRange} | ||
| </span> | ||
| {this.msg('of')} | ||
| <span className="pagination-pf-items-total"> | ||
| {this.totalCount} | ||
| </span> | ||
| </span> | ||
|
|
||
| <ul className="pagination pagination-pf-back"> | ||
| <li className={backButtonsClass}> | ||
| <a | ||
| title={this.msg('firstPage')} | ||
| onClick={() => this.setPage(1)} | ||
| > | ||
| <ArrowIcon name="double-left" /> | ||
| </a> | ||
| </li> | ||
| <li className={backButtonsClass}> | ||
| <a | ||
| title={this.msg('previousPage')} | ||
| onClick={() => this.setPageRelative(-1)} | ||
| > | ||
| <ArrowIcon name="left" /> | ||
| </a> | ||
| </li> | ||
| </ul> | ||
|
|
||
| <label className="sr-only">Current Page</label> | ||
| <input | ||
| className="pagination-pf-page" | ||
| value={this.state.pageChangeValue} | ||
| onChange={e => this.handlePageChange(e)} | ||
| type="text" | ||
| /> | ||
| <span> | ||
| {this.msg('of')} | ||
| <span className="pagination-pf-pages">{totalPages}</span> | ||
| </span> | ||
|
|
||
| <ul className="pagination pagination-pf-forward"> | ||
| <li className={nextButtonsClass}> | ||
| <a | ||
| title={this.msg('nextPage')} | ||
| onClick={() => this.setPageRelative(1)} | ||
| > | ||
| <ArrowIcon name="right" /> | ||
| </a> | ||
| </li> | ||
| <li className={nextButtonsClass}> | ||
| <a | ||
| title={this.msg('lastPage')} | ||
| onClick={() => this.setPage(totalPages)} | ||
| > | ||
| <ArrowIcon name="double-right" /> | ||
| </a> | ||
| </li> | ||
| </ul> | ||
| </div> | ||
| </form> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| PaginationRow.propTypes = { | ||
| /** Options for the per page dropdown */ | ||
| perPageOptions: PropTypes.array, | ||
| /** Current per page setting */ | ||
| perPage: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types | ||
| /** Total number of items to paginate */ | ||
| totalCount: PropTypes.number.isRequired, | ||
| /** Index of page that is currently shown, starting from 1 */ | ||
| currentPage: PropTypes.number.isRequired, | ||
| /** A callback triggered when the per page dropdown value is selected */ | ||
| onPerPageSet: PropTypes.func, | ||
| /** A callback triggered when a page is switched */ | ||
| onPageSet: PropTypes.func, | ||
| /** Strings in the component, see PaginationRow.defaultMessages for details */ | ||
| messages: PropTypes.object, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you use
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
| /** Class name for the form element */ | ||
| className: PropTypes.string | ||
| }; | ||
|
|
||
| PaginationRow.defaultProps = { | ||
| perPageOptions: [], | ||
| onPageSet: p => {}, | ||
| onPerPageSet: pp => {}, | ||
| messages: {}, | ||
| className: 'content-view-pf-pagination' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing a You can add another prop called
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
| }; | ||
|
|
||
| PaginationRow.defaultMessages = { | ||
| firstPage: 'First Page', | ||
| previousPage: 'Previous Page', | ||
| nextPage: 'Next Page', | ||
| lastPage: 'Last Page', | ||
| perPage: 'per page', | ||
| of: 'of' | ||
| }; | ||
|
|
||
| export default PaginationRow; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import React from 'react'; | ||
| import { storiesOf } from '@storybook/react'; | ||
| import { action } from '@storybook/addon-actions'; | ||
| import { withKnobs, select, text } from '@storybook/addon-knobs'; | ||
| import { defaultTemplate } from '../../../storybook/decorators/storyTemplates'; | ||
| import { PaginationRow } from './index'; | ||
|
|
||
| const stories = storiesOf('PaginationRow', module); | ||
|
|
||
| stories.addDecorator( | ||
| defaultTemplate({ | ||
| title: 'Pagination Row', | ||
| documentationLink: | ||
| 'http://www.patternfly.org/pattern-library/navigation/pagination/' | ||
| }) | ||
| ); | ||
| stories.addDecorator(withKnobs); | ||
| stories.addWithInfo('Basic example', '', () => { | ||
| const page = select('Page', [1, 3, 8], 1); | ||
| const totalCount = select('Total items', [75, 80, 81], 75); | ||
|
|
||
| return ( | ||
| <PaginationRow | ||
| totalCount={Number(totalCount)} | ||
| perPage={10} | ||
| currentPage={Number(page)} | ||
| perPageOptions={[5, 10, 15]} | ||
| onPageSet={action('page set')} | ||
| onPerPageSet={action('per page value set')} | ||
| /> | ||
| ); | ||
| }); | ||
|
|
||
| stories.addWithInfo('With translations', '', () => { | ||
| var messages = {}; | ||
| for (var key in PaginationRow.defaultMessages) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In today javascript, we should never use
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
| messages[key] = text(key, PaginationRow.defaultMessages[key]); | ||
| } | ||
|
|
||
| return ( | ||
| <PaginationRow | ||
| totalCount={75} | ||
| perPage={10} | ||
| currentPage={1} | ||
| perPageOptions={[5, 10, 15]} | ||
| onPageSet={action('page set')} | ||
| onPerPageSet={action('per page value set')} | ||
| messages={messages} | ||
| /> | ||
| ); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /* eslint-env jest */ | ||
|
|
||
| import React from 'react'; | ||
| import renderer from 'react-test-renderer'; | ||
|
|
||
| import PaginationRow from './PaginationRow'; | ||
|
|
||
| test('PaginationRow renders properly the first page', () => { | ||
| const component = renderer.create( | ||
| <PaginationRow | ||
| totalCount={75} | ||
| perPage={10} | ||
| currentPage={1} | ||
| perPageOptions={[5, 10, 15]} | ||
| /> | ||
| ); | ||
|
|
||
| let tree = component.toJSON(); | ||
| expect(tree).toMatchSnapshot(); | ||
| }); | ||
|
|
||
| test('PaginationRow renders properly a middle page', () => { | ||
| const component = renderer.create( | ||
| <PaginationRow | ||
| totalCount={75} | ||
| perPage={10} | ||
| currentPage={4} | ||
| perPageOptions={[5, 10, 15]} | ||
| /> | ||
| ); | ||
|
|
||
| let tree = component.toJSON(); | ||
| expect(tree).toMatchSnapshot(); | ||
| }); | ||
|
|
||
| test('PaginationRow renders properly the last page', () => { | ||
| const component = renderer.create( | ||
| <PaginationRow | ||
| totalCount={75} | ||
| perPage={10} | ||
| currentPage={8} | ||
| perPageOptions={[5, 10, 15]} | ||
| /> | ||
| ); | ||
|
|
||
| let tree = component.toJSON(); | ||
| expect(tree).toMatchSnapshot(); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move the
ArrowIconintosrc/components/PaginationRow/InnerComponents/ArrowIcon.jsas described here:https://github.com/patternfly/patternfly-react/blob/master/CONTRIBUTING.md#code-consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we can expose it ;)