Skip to content

Commit

Permalink
feat(react-grid): Mobile-friendly pager (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
SergeyAlexeev authored Jun 7, 2017
1 parent 31f6b2d commit 23ec7f0
Show file tree
Hide file tree
Showing 18 changed files with 451 additions and 65 deletions.
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

0 comments on commit 23ec7f0

Please sign in to comment.