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

feat(react-grid): mobile-friendly pager #125

Merged
merged 19 commits into from
Jun 7, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/dx-grid-core/src/plugins/paging-state/computeds.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,12 @@ export const ensurePageHeaders = (rows, pageSize) => {
};

export const totalPageCount = (rows, pageSize) => Math.ceil(rows.length / pageSize);

export const totalCount = rows => rows.length;

export const firstRowOnPage = (currentPage, pageSize) => (currentPage * pageSize) + 1;

export const lastRowOnPage = (currentPage, pageSize, totalRowCount) => {
const index = (currentPage + 1) * pageSize;
return index > totalRowCount ? totalRowCount : index;
};
29 changes: 29 additions & 0 deletions packages/dx-grid-core/src/plugins/paging-state/computeds.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import {
paginate,
ensurePageHeaders,
totalPageCount,
totalCount,
firstRowOnPage,
lastRowOnPage,
} from './computeds';

describe('PagingState computeds', () => {
Expand Down Expand Up @@ -146,4 +149,30 @@ describe('PagingState computeds', () => {
expect(count).toEqual(2);
});
});

describe('#totalCount', () => {
test('should work', () => {
const count = totalCount([1, 2, 3]);
expect(count).toEqual(3);
});
});

describe('#firstRowOnPage', () => {
test('should work', () => {
const count = firstRowOnPage(1, 5);
expect(count).toEqual(6);
});
});

describe('#lastRowOnPage', () => {
test('should work', () => {
const count = lastRowOnPage(1, 5, 15);
expect(count).toEqual(10);
});

test('should not be greater than total count', () => {
const count = lastRowOnPage(1, 5, 9);
expect(count).toEqual(9);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ import {
Grid,
TableView,
TableFilterRow,
} from '@devexpress/dx-react-grid-material-ui';
import {
DropDownMenu,
} from '../components/drop-down-menu';
} from '@devexpress/dx-react-grid-material-ui';
import {
generateRows,
} from '../../demoData';
Expand Down
1 change: 1 addition & 0 deletions packages/dx-react-grid-bootstrap3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"prop-types": "^15.5.8"
},
"peerDependencies": {
"@devexpress/dx-grid-core": "1.0.0-alpha.2",
"@devexpress/dx-react-grid": "1.0.0-alpha.2",
"react": "^15.5.4",
"react-bootstrap": "^0.31.0"
Expand Down
32 changes: 30 additions & 2 deletions packages/dx-react-grid-bootstrap3/src/templates/pager.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Pagination } from 'react-bootstrap';
import { Pagination, Pager as BootstrapPager } from 'react-bootstrap';
import { firstRowOnPage, lastRowOnPage } from '@devexpress/dx-grid-core';
import { PageSizeSelector } from './page-size-selector';

export const Pager = ({
Expand All @@ -10,6 +11,7 @@ export const Pager = ({
pageSize,
onPageSizeChange,
allowedPageSizes,
totalCount,
}) => (
<div className="clearfix">
{!!allowedPageSizes.length && <PageSizeSelector
Expand All @@ -22,13 +24,38 @@ export const Pager = ({
margin: 0,
verticalAlign: 'bottom',
}}
className="pull-right"
className="pull-right hidden-xs"
items={totalPages}
activePage={currentPage + 1}
boundaryLinks
maxButtons={3}
onSelect={page => onCurrentPageChange(page - 1)}
/>
<BootstrapPager
className="pull-right visible-xs"
style={{ margin: 0 }}
>
<BootstrapPager.Item
disabled={currentPage === 0}
onClick={() => onCurrentPageChange(currentPage - 1)}
>
&laquo;
</BootstrapPager.Item>
{' '}
<BootstrapPager.Item
disabled={currentPage === totalPages - 1}
onClick={() => onCurrentPageChange(currentPage + 1)}
>
&raquo;
</BootstrapPager.Item>
</BootstrapPager>
<span className="pull-right visible-xs" style={{ marginRight: '20px' }}>
<span style={{ display: 'inline-block', verticalAlign: 'middle', lineHeight: '32px' }}>
{ String(firstRowOnPage(currentPage, pageSize)) }
-
{ String(lastRowOnPage(currentPage, pageSize, totalCount)) } of {String(totalCount)}
</span>
</span>
</div>
);

Expand All @@ -39,4 +66,5 @@ Pager.propTypes = {
pageSize: PropTypes.number.isRequired,
onPageSizeChange: PropTypes.func.isRequired,
allowedPageSizes: PropTypes.arrayOf(PropTypes.number).isRequired,
totalCount: PropTypes.number.isRequired,
};
100 changes: 100 additions & 0 deletions packages/dx-react-grid-bootstrap3/src/templates/pager.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import { mount } from 'enzyme';
import { Pager } from './pager';

describe('Pager', () => {
describe('#render', () => {
const mountPager = ({
currentPage,
totalPages,
pageSize,
totalCount,
allowedPageSizes = [],
onPageSizeChange = () => {},
onCurrentPageChange = () => {},
}) => mount(<Pager
totalPages={totalPages}
currentPage={currentPage}
totalCount={totalCount}
pageSize={pageSize}
allowedPageSizes={allowedPageSizes}
onCurrentPageChange={onCurrentPageChange}
onPageSizeChange={onPageSizeChange}
/>);

test('can show info about rendered pages', () => {
const tree = mountPager({
totalPages: 10,
currentPage: 1,
totalCount: 96,
pageSize: 10,
});

expect(tree.find('div > span > span').text()).toBe('11-20 of 96');
});

test('can render pagination arrows', () => {
const onCurrentPageChange = jest.fn();
const arrows = mountPager({
totalPages: 10,
currentPage: 2,
totalCount: 96,
pageSize: 10,
onCurrentPageChange,
}).find('.pager li');

const prew = arrows.at(0);
const next = arrows.at(1);

prew.find('a').simulate('click');
next.find('a').simulate('click');

expect(arrows).toHaveLength(2);
expect(prew.hasClass('disabled')).toBeFalsy();
expect(next.hasClass('disabled')).toBeFalsy();
expect(onCurrentPageChange.mock.calls).toHaveLength(2);
});

test('the prev arrow is disabled if current page is 1', () => {
const onCurrentPageChange = jest.fn();
const arrows = mountPager({
totalPages: 10,
currentPage: 0,
totalCount: 96,
pageSize: 10,
onCurrentPageChange,
}).find('.pager li');

const prew = arrows.at(0);
const next = arrows.at(1);

prew.find('a').simulate('click');
next.find('a').simulate('click');

expect(prew.hasClass('disabled')).toBeTruthy();
expect(next.hasClass('disabled')).toBeFalsy();
expect(onCurrentPageChange.mock.calls).toHaveLength(1);
});

test('the next arrow is disabled if current page equals to total page count', () => {
const onCurrentPageChange = jest.fn();
const arrows = mountPager({
totalPages: 10,
currentPage: 9,
totalCount: 96,
pageSize: 5,
onCurrentPageChange,
}).find('.pager li');

const prew = arrows.at(0);
const next = arrows.at(1);

prew.find('a').simulate('click');
next.find('a').simulate('click');

expect(prew.hasClass('disabled')).toBeFalsy();
expect(next.hasClass('disabled')).toBeTruthy();
expect(onCurrentPageChange.mock.calls).toHaveLength(1);
});
});
});
1 change: 1 addition & 0 deletions packages/dx-react-grid-material-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"prop-types": "^15.5.8"
},
"peerDependencies": {
"@devexpress/dx-grid-core": "1.0.0-alpha.2",
"@devexpress/dx-react-grid": "1.0.0-alpha.2",
"material-ui": "^1.0.0-alpha.14",
"material-ui-icons": "^1.0.0-alpha.3",
Expand Down
1 change: 1 addition & 0 deletions packages/dx-react-grid-material-ui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { TableFilterRow } from './plugins/table-filter-row';
export { TableHeaderRow } from './plugins/table-header-row';
export { TableEditColumn } from './plugins/table-edit-column';
export { TableEditRow } from './plugins/table-edit-row';
export { DropDownMenu } from './templates/drop-down-menu';
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Typography, Menu, MenuItem } from 'material-ui';
import { withStyles, createStyleSheet } from 'material-ui/styles';

import ExpandMore from 'material-ui-icons/ExpandMore';
import ExpandLess from 'material-ui-icons/ExpandLess';

Expand Down Expand Up @@ -53,11 +52,11 @@ class DropDownMenuBase extends React.PureComponent {
}

render() {
const { items, classes } = this.props;
const { items, classes, className } = this.props;
const { anchorEl, open, selectedIndex, title } = this.state;

return (
<div>
<div className={className}>
<Typography
type="button"
onClick={this.handleClick}
Expand Down Expand Up @@ -92,10 +91,21 @@ class DropDownMenuBase extends React.PureComponent {
}

DropDownMenuBase.propTypes = {
defaultTitle: PropTypes.string.isRequired,
items: PropTypes.arrayOf(PropTypes.string).isRequired,
defaultTitle: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]).isRequired,
items: PropTypes.arrayOf(PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
])).isRequired,
classes: PropTypes.object.isRequired,
onItemClick: PropTypes.func.isRequired,
className: PropTypes.string,
};

DropDownMenuBase.defaultProps = {
className: null,
};

export const DropDownMenu = withStyles(styleSheet)(DropDownMenuBase);
Original file line number Diff line number Diff line change
@@ -1,30 +1,46 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'material-ui';
import { withStyles, createStyleSheet } from 'material-ui/styles';
import { DropDownMenu } from './drop-down-menu';

const styleSheet = createStyleSheet('PageSizeSelector', theme => ({
button: {
minWidth: theme.spacing.unit * 2,
pageSizeSelector: {
...theme.typography.caption,
float: 'right',
paddingRight: theme.spacing.unit * 5,
},
label: {
paddingRight: theme.spacing.unit * 3,
lineHeight: `${theme.spacing.unit * 5}px`,
},
pageSizes: {
display: 'inline-block',
minWidth: theme.spacing.unit * 4,
},
'@media (max-width: 768px)': {
label: {
display: 'none',
},
pageSizeSelector: {
paddingRight: theme.spacing.unit * 2,
marginTop: 4,
},
},
}));

export const PageSizeSelectorBase = ({ pageSize, onPageSizeChange, allowedPageSizes, classes }) => (
<div style={{ display: 'inline-block' }}>
{allowedPageSizes.map(item => (
<Button
key={item}
accent={item === pageSize}
raised={item === pageSize}
className={classes.button}
onTouchTap={(e) => {
e.preventDefault();
onPageSizeChange(item);
}}
>
{item}
</Button>
))}
const PageSizeSelectorBase = ({ pageSize, onPageSizeChange, allowedPageSizes, classes }) => (
<div className={classes.pageSizeSelector}>
<span className={classes.label}>
Rows per page:
</span>
<DropDownMenu
defaultTitle={String(pageSize)}
items={allowedPageSizes}
onItemClick={(item) => {
onPageSizeChange(item);
}}
className={classes.pageSizes}
/>
</div>
);

Expand Down
14 changes: 9 additions & 5 deletions packages/dx-react-grid-material-ui/src/templates/pager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,21 @@ const PagerBase = ({
classes,
onCurrentPageChange,
onPageSizeChange,
totalCount,
}) => (
<div className={classes.pager}>
<Pagination
totalPages={totalPages}
totalCount={totalCount}
currentPage={currentPage}
onCurrentPageChange={page => onCurrentPageChange(page)}
pageSize={pageSize}
/>
{!!allowedPageSizes.length && <PageSizeSelector
pageSize={pageSize}
onPageSizeChange={onPageSizeChange}
allowedPageSizes={allowedPageSizes}
/>}
<Pagination
totalPages={totalPages}
currentPage={currentPage + 1}
onCurrentPageChange={page => onCurrentPageChange(page - 1)}
/>
</div>
);

Expand All @@ -41,6 +44,7 @@ PagerBase.propTypes = {
classes: PropTypes.object.isRequired,
onCurrentPageChange: PropTypes.func.isRequired,
onPageSizeChange: PropTypes.func.isRequired,
totalCount: PropTypes.number.isRequired,
};

export const Pager = withStyles(styleSheet)(PagerBase);
Loading