diff --git a/docs/README.md b/docs/README.md index 05da321..5023f08 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 } ``` \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-sort.js b/packages/react-bootstrap-table2-example/examples/remote/remote-sort.js new file mode 100644 index 0000000..0ca4377 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-sort.js @@ -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() +}]; + + +`; + +const RemoteSort = props => ( +
+ + { sourceCode } +
+); + +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 ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 36abbb0..909f5b8 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -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', () => ); storiesOf('Remote', module) + .add('Remote Sort', () => ) .add('Remote Filter', () => ) .add('Remote Pagination', () => ) .add('Remote All', () => ); diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js index ddadafc..b59c9e3 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js @@ -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()); + } }; diff --git a/packages/react-bootstrap-table2/src/sort/wrapper.js b/packages/react-bootstrap-table2/src/sort/wrapper.js index 40486fb..45414e3 100644 --- a/packages/react-bootstrap-table2/src/sort/wrapper.js +++ b/packages/react-bootstrap-table2/src/sort/wrapper.js @@ -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() { diff --git a/packages/react-bootstrap-table2/src/store/index.js b/packages/react-bootstrap-table2/src/store/index.js index b735f86..9c6e108 100644 --- a/packages/react-bootstrap-table2/src/store/index.js +++ b/packages/react-bootstrap-table2/src/store/index.js @@ -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); } diff --git a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js index fa7d8b3..a6e260d 100644 --- a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js +++ b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js @@ -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(() => { diff --git a/packages/react-bootstrap-table2/test/sort/wrapper.test.js b/packages/react-bootstrap-table2/test/sort/wrapper.test.js index 208058d..d8a3ce7 100644 --- a/packages/react-bootstrap-table2/test/sort/wrapper.test.js +++ b/packages/react-bootstrap-table2/test/sort/wrapper.test.js @@ -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( - - ); - 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( + + ); - 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( + + ); + 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(); + }); }); }); diff --git a/packages/react-bootstrap-table2/test/store/index.test.js b/packages/react-bootstrap-table2/test/store/index.test.js index f6f9901..dc8b28e 100644 --- a/packages/react-bootstrap-table2/test/store/index.test.js +++ b/packages/react-bootstrap-table2/test/store/index.test.js @@ -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', () => {