implement remote sorting

This commit is contained in:
AllenFang 2017-12-27 23:11:36 +08:00
parent bdfc4ebcad
commit 01337f50a7
9 changed files with 279 additions and 35 deletions

View File

@ -249,13 +249,17 @@ There's only two arguments will be passed to `onTableChange`: `type` and `newSta
* `filter`
* `pagination`
* `sort`
Following is a shape of `newState`
```js
{
page, // newest page
sizePerPage, //newest sizePerPage
filters // an object which have current filter status per column
sizePerPage, // newest sizePerPage
sortField, // newest sort field
sortOrder, // newest sort order
filters, // an object which have current filter status per column
data // when you enable remote sort, you may need to base on data to sort if data is filtered/searched
}
```

View File

@ -0,0 +1,106 @@
/* eslint no-restricted-syntax: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import BootstrapTable from 'react-bootstrap-table2';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator(5);
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
sort: true
}, {
dataField: 'price',
text: 'Product Price',
sort: true
}];
const sourceCode = `\
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
const columns = [{
dataField: 'id',
text: 'Product ID',
}, {
dataField: 'name',
text: 'Product Name',
filter: textFilter()
}, {
dataField: 'price',
text: 'Product Price',
filter: textFilter()
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
`;
const RemoteSort = props => (
<div>
<BootstrapTable
remote={ { sort: true } }
keyField="id"
data={ props.data }
columns={ columns }
onTableChange={ props.onTableChange }
/>
<Code>{ sourceCode }</Code>
</div>
);
RemoteSort.propTypes = {
data: PropTypes.array.isRequired,
onTableChange: PropTypes.func.isRequired
};
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
data: products
};
}
handleTableChange = (type, { sortField, sortOrder, data }) => {
setTimeout(() => {
let result;
if (sortOrder === 'asc') {
result = data.sort((a, b) => {
if (a[sortField] > b[sortField]) {
return 1;
} else if (b[sortField] > a[sortField]) {
return -1;
}
return 0;
});
} else {
result = data.sort((a, b) => {
if (a[sortField] > b[sortField]) {
return -1;
} else if (b[sortField] > a[sortField]) {
return 1;
}
return 0;
});
}
this.setState(() => ({
data: result
}));
}, 2000);
}
render() {
return (
<RemoteSort
data={ this.state.data }
onTableChange={ this.handleTableChange }
/>
);
}
}
export default Container;

View File

@ -88,6 +88,7 @@ import EmptyTableOverlay from 'examples/loading-overlay/empty-table-overlay';
import TableOverlay from 'examples/loading-overlay/table-overlay';
// remote
import RemoteSort from 'examples/remote/remote-sort';
import RemoteFilter from 'examples/remote/remote-filter';
import RemotePaginationTable from 'examples/remote/remote-pagination';
import RemoteAll from 'examples/remote/remote-all';
@ -190,6 +191,7 @@ storiesOf('EmptyTableOverlay', module)
.add('Table Overlay', () => <TableOverlay />);
storiesOf('Remote', module)
.add('Remote Sort', () => <RemoteSort />)
.add('Remote Filter', () => <RemoteFilter />)
.add('Remote Pagination', () => <RemotePaginationTable />)
.add('Remote All', () => <RemoteAll />);

View File

@ -8,6 +8,9 @@ export default ExtendBase =>
page: store.page,
sizePerPage: store.sizePerPage,
filters: store.filters,
sortField: store.sortField,
sortOrder: store.sortOrder,
data: store.data,
...state
};
}
@ -22,6 +25,11 @@ export default ExtendBase =>
return remote === true || (_.isObject(remote) && remote.filter);
}
isRemoteSort() {
const { remote } = this.props;
return remote === true || (_.isObject(remote) && remote.sort);
}
handleRemotePageChange() {
this.props.onTableChange('pagination', this.getNewestState());
}
@ -34,4 +42,8 @@ export default ExtendBase =>
}
this.props.onTableChange('filter', this.getNewestState(newState));
}
handleSortChange() {
this.props.onTableChange('sort', this.getNewestState());
}
};

View File

@ -1,9 +1,10 @@
/* eslint react/prop-types: 0 */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import remoteResolver from '../props-resolver/remote-resolver';
export default Base =>
class SortWrapper extends Component {
class SortWrapper extends remoteResolver(Component) {
static propTypes = {
store: PropTypes.object.isRequired
}
@ -22,7 +23,13 @@ export default Base =>
const order = defaultSorted[0].order;
const column = columns.filter(col => col.dataField === dataField);
if (column.length > 0) {
store.sortBy(column[0], order);
store.setSort(column[0], order);
if (this.isRemoteSort() || this.isRemotePagination()) {
this.handleSortChange();
} else {
store.sortBy(column[0]);
}
}
}
}
@ -32,15 +39,21 @@ export default Base =>
const sortedColumn = nextProps.columns.find(
column => column.dataField === nextProps.store.sortField);
if (sortedColumn) {
nextProps.store.sortBy(sortedColumn, nextProps.store.sortOrder);
nextProps.store.sortBy(sortedColumn);
}
}
}
handleSort(column) {
const { store } = this.props;
store.sortBy(column);
this.forceUpdate();
store.setSort(column);
if (this.isRemoteSort() || this.isRemotePagination()) {
this.handleSortChange();
} else {
store.sortBy(column);
this.forceUpdate();
}
}
render() {

View File

@ -21,9 +21,12 @@ export default class Store {
if (row) _.set(row, dataField, newValue);
}
sortBy({ dataField, sortFunc }, order) {
setSort({ dataField }, order) {
this.sortOrder = nextOrder(this)(dataField, order);
this.sortField = dataField;
}
sortBy({ sortFunc }) {
this.data = sort(this)(sortFunc);
}

View File

@ -102,6 +102,52 @@ describe('remoteResolver', () => {
});
});
describe('isRemoteSort', () => {
describe('when remote is false', () => {
beforeEach(() => {
shallowContainer();
});
it('should return false', () => {
expect(wrapper.instance().isRemoteSort()).toBeFalsy();
});
});
describe('when remote is true', () => {
beforeEach(() => {
shallowContainer({ remote: true });
});
it('should return true', () => {
expect(wrapper.instance().isRemoteSort()).toBeTruthy();
});
});
describe('when remote.sort is true', () => {
beforeEach(() => {
shallowContainer({ remote: { sort: true } });
});
it('should return true', () => {
expect(wrapper.instance().isRemoteSort()).toBeTruthy();
});
});
});
describe('handleSortChange', () => {
const onTableChangeCB = sinon.stub();
beforeEach(() => {
onTableChangeCB.reset();
shallowContainer({ onTableChange: onTableChangeCB });
wrapper.instance().handleSortChange();
});
it('should calling props.onTableChange correctly', () => {
expect(onTableChangeCB.calledOnce).toBeTruthy();
expect(onTableChangeCB.calledWith('sort', wrapper.instance().getNewestState())).toBeTruthy();
});
});
describe('handleRemotePageChange', () => {
const onTableChangeCB = sinon.stub();
beforeEach(() => {

View File

@ -57,28 +57,78 @@ describe('SortWrapper', () => {
});
describe('call handleSort function', () => {
let sortBySpy;
const sortColumn = columns[0];
beforeEach(() => {
store = new Store(keyField);
store.data = data;
wrapper = shallow(
<SortWrapper
keyField={ keyField }
data={ data }
columns={ columns }
store={ store }
/>
);
wrapper.instance().handleSort(sortColumn);
sortBySpy = sinon.spy(store, 'sortBy');
});
it('should operating on store correctly', () => {
expect(store.sortOrder).toEqual(Const.SORT_DESC);
expect(store.sortField).toEqual(sortColumn.dataField);
describe('when remote.sort is false', () => {
beforeEach(() => {
wrapper = shallow(
<SortWrapper
keyField={ keyField }
data={ data }
columns={ columns }
store={ store }
/>
);
wrapper.instance().handleSort(sortColumn); // sort same column again
expect(store.sortOrder).toEqual(Const.SORT_ASC);
expect(store.sortField).toEqual(sortColumn.dataField);
wrapper.instance().handleSort(sortColumn);
});
it('should operating on store correctly', () => {
expect(store.sortOrder).toEqual(Const.SORT_DESC);
expect(store.sortField).toEqual(sortColumn.dataField);
wrapper.instance().handleSort(sortColumn); // sort same column again
expect(store.sortOrder).toEqual(Const.SORT_ASC);
expect(store.sortField).toEqual(sortColumn.dataField);
});
it('should calling store.sortBy correctly', () => {
expect(sortBySpy.calledOnce).toBeTruthy();
expect(sortBySpy.calledWith(sortColumn)).toBeTruthy();
});
});
describe('when remote.sort is true', () => {
let onTableChangeCB;
beforeEach(() => {
onTableChangeCB = sinon.stub();
wrapper = shallow(
<SortWrapper
remote
keyField={ keyField }
data={ data }
columns={ columns }
store={ store }
onTableChange={ onTableChangeCB }
/>
);
wrapper.instance().handleSort(sortColumn);
});
it('should operating on store correctly', () => {
expect(store.sortOrder).toEqual(Const.SORT_DESC);
expect(store.sortField).toEqual(sortColumn.dataField);
wrapper.instance().handleSort(sortColumn); // sort same column again
expect(store.sortOrder).toEqual(Const.SORT_ASC);
expect(store.sortField).toEqual(sortColumn.dataField);
});
it('should not calling store.sortBy', () => {
expect(sortBySpy.calledOnce).toBeFalsy();
});
it('should calling props.onTableChange', () => {
expect(onTableChangeCB.calledOnce).toBeTruthy();
});
});
});

View File

@ -24,7 +24,7 @@ describe('Store Base', () => {
});
});
describe('sortBy', () => {
describe('setSort', () => {
let dataField;
beforeEach(() => {
@ -32,30 +32,43 @@ describe('Store Base', () => {
});
it('should change sortField by dataField param', () => {
store.sortBy({ dataField });
store.setSort({ dataField });
expect(store.sortField).toEqual(dataField);
});
it('should change sortOrder correctly when sortBy same dataField', () => {
store.sortBy({ dataField });
store.setSort({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
store.sortBy({ dataField });
store.setSort({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_ASC);
});
it('should change sortOrder correctly when sortBy different dataField', () => {
store.sortBy({ dataField });
store.setSort({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
dataField = 'id';
store.sortBy({ dataField });
store.setSort({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
dataField = 'name';
store.sortBy({ dataField });
store.setSort({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
});
it('should force assign sortOrder correctly if second argument is passed', () => {
store.setSort({ dataField }, Const.SORT_DESC);
expect(store.sortOrder).toEqual(Const.SORT_DESC);
});
});
describe('sortBy', () => {
let dataField;
beforeEach(() => {
dataField = 'name';
});
it('should have correct result after sortBy', () => {
store.sortBy({ dataField });
const result = store.data.map(e => e[dataField]).sort((a, b) => b - a);
@ -63,11 +76,6 @@ describe('Store Base', () => {
expect(e[dataField]).toEqual(result[i]);
});
});
it('should force assign sortOrder correctly if second argument is passed', () => {
store.sortBy({ dataField }, Const.SORT_DESC);
expect(store.sortOrder).toEqual(Const.SORT_DESC);
});
});
describe('edit', () => {