From e14c596b8cf3fc0bf4b357e6ef4aeb905ff930bd Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 24 Dec 2017 14:18:50 +0800 Subject: [PATCH 1/3] refine remote examples --- .../examples/remote/remote-all.js | 202 ++++++++++++++++++ .../examples/remote/remote-filter.js | 105 +++++++++ .../remote-pagination.js | 2 +- .../stories/index.js | 14 +- 4 files changed, 319 insertions(+), 4 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/examples/remote/remote-all.js create mode 100644 packages/react-bootstrap-table2-example/examples/remote/remote-filter.js rename packages/react-bootstrap-table2-example/examples/{pagination => remote}/remote-pagination.js (98%) diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-all.js b/packages/react-bootstrap-table2-example/examples/remote/remote-all.js new file mode 100644 index 0000000..c23d690 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-all.js @@ -0,0 +1,202 @@ +/* eslint guard-for-in: 0 */ +/* eslint no-restricted-syntax: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table2'; +import paginator from 'react-bootstrap-table2-paginator'; +import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table2'; +import paginator from 'react-bootstrap-table2-paginator'; +import filterFactory, { textFilter, Comparator } 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 RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ + { sourceCode } +
+); + +RemoteAll.propTypes = { + data: PropTypes.array.isRequired, + page: PropTypes.number.isRequired, + totalSize: PropTypes.number.isRequired, + sizePerPage: PropTypes.number.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + totalSize: products.length, + sizePerPage: 10 + }; + this.handleTableChange = this.handleTableChange.bind(this); + } + + handleTableChange = (type, { page, sizePerPage, filters }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + const result = products.filter((row) => { + let valid = true; + for (const dataField in filters) { + const { filterVal, filterType, comparator } = filters[dataField]; + + if (filterType === 'TEXT') { + if (comparator === Comparator.LIKE) { + valid = row[dataField].toString().indexOf(filterVal) > -1; + } else { + valid = row[dataField] === filterVal; + } + } + if (!valid) break; + } + return valid; + }); + this.setState(() => ({ + page, + data: result.slice(currentIndex, currentIndex + sizePerPage), + totalSize: result.length, + sizePerPage + })); + }, 2000); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} +`; + +const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+

When remote.pagination is enabled, the filtering, + sorting and searching will also change to remote mode automatically

+ + { sourceCode } +
+); + +RemoteAll.propTypes = { + data: PropTypes.array.isRequired, + page: PropTypes.number.isRequired, + totalSize: PropTypes.number.isRequired, + sizePerPage: PropTypes.number.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + totalSize: products.length, + sizePerPage: 10 + }; + this.handleTableChange = this.handleTableChange.bind(this); + } + + handleTableChange = (type, { page, sizePerPage, filters }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + const result = products.filter((row) => { + let valid = true; + for (const dataField in filters) { + const { filterVal, filterType, comparator } = filters[dataField]; + + if (filterType === 'TEXT') { + if (comparator === Comparator.LIKE) { + valid = row[dataField].toString().indexOf(filterVal) > -1; + } else { + valid = row[dataField] === filterVal; + } + } + if (!valid) break; + } + return valid; + }); + this.setState(() => ({ + page, + data: result.slice(currentIndex, currentIndex + sizePerPage), + totalSize: result.length, + sizePerPage + })); + }, 2000); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-filter.js b/packages/react-bootstrap-table2-example/examples/remote/remote-filter.js new file mode 100644 index 0000000..d2b37b6 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-filter.js @@ -0,0 +1,105 @@ +/* eslint guard-for-in: 0 */ +/* eslint no-restricted-syntax: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table2'; +import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(17); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +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 RemoteFilter = props => ( +
+ + { sourceCode } +
+); + +RemoteFilter.propTypes = { + data: PropTypes.array.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + data: products + }; + } + + handleTableChange = (type, { filters }) => { + setTimeout(() => { + const result = products.filter((row) => { + let valid = true; + for (const dataField in filters) { + const { filterVal, filterType, comparator } = filters[dataField]; + + if (filterType === 'TEXT') { + if (comparator === Comparator.LIKE) { + valid = row[dataField].toString().indexOf(filterVal) > -1; + } else { + valid = row[dataField] === filterVal; + } + } + if (!valid) break; + } + return valid; + }); + this.setState(() => ({ + data: result + })); + }, 2000); + } + + render() { + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/examples/pagination/remote-pagination.js b/packages/react-bootstrap-table2-example/examples/remote/remote-pagination.js similarity index 98% rename from packages/react-bootstrap-table2-example/examples/pagination/remote-pagination.js rename to packages/react-bootstrap-table2-example/examples/remote/remote-pagination.js index ccae5c7..050dc4d 100644 --- a/packages/react-bootstrap-table2-example/examples/pagination/remote-pagination.js +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-pagination.js @@ -105,7 +105,7 @@ class Container extends React.Component { }; } - handleTableChange = ({ page, sizePerPage }) => { + handleTableChange = (type, { page, sizePerPage }) => { const currentIndex = (page - 1) * sizePerPage; setTimeout(() => { this.setState(() => ({ diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 012cfc8..36abbb0 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -82,12 +82,16 @@ import HideSelectionColumnTable from 'examples/row-selection/hide-selection-colu import PaginationTable from 'examples/pagination'; import PaginationHooksTable from 'examples/pagination/pagination-hooks'; import CustomPaginationTable from 'examples/pagination/custom-pagination'; -import RemotePaginationTable from 'examples/pagination/remote-pagination'; // loading overlay import EmptyTableOverlay from 'examples/loading-overlay/empty-table-overlay'; import TableOverlay from 'examples/loading-overlay/table-overlay'; +// remote +import RemoteFilter from 'examples/remote/remote-filter'; +import RemotePaginationTable from 'examples/remote/remote-pagination'; +import RemoteAll from 'examples/remote/remote-all'; + // css style import 'bootstrap/dist/css/bootstrap.min.css'; import 'stories/stylesheet/tomorrow.min.css'; @@ -179,9 +183,13 @@ storiesOf('Row Selection', module) storiesOf('Pagination', module) .add('Basic Pagination Table', () => ) .add('Pagination Hooks', () => ) - .add('Custom Pagination', () => ) - .add('Remote Pagination', () => ); + .add('Custom Pagination', () => ); storiesOf('EmptyTableOverlay', module) .add('Empty Table Overlay', () => ) .add('Table Overlay', () => ); + +storiesOf('Remote', module) + .add('Remote Filter', () => ) + .add('Remote Pagination', () => ) + .add('Remote All', () => ); From c4eb2f835fb4457282f491c4d1f208add1d734b1 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 24 Dec 2017 16:02:05 +0800 Subject: [PATCH 2/3] refine remote mode on filter and pagination --- .../src/wrapper.js | 41 +++++-- .../test/wrapper.test.js | 69 +++++++++-- .../src/wrapper.js | 30 ++++- .../test/wrapper.test.js | 108 +++++++++++++----- .../react-bootstrap-table2/src/container.js | 35 +++++- .../react-bootstrap-table2/src/store/index.js | 18 ++- .../test/container.test.js | 82 ++++++++++++- 7 files changed, 315 insertions(+), 68 deletions(-) diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js index 0cd8f44..48e4d73 100644 --- a/packages/react-bootstrap-table2-filter/src/wrapper.js +++ b/packages/react-bootstrap-table2-filter/src/wrapper.js @@ -1,12 +1,16 @@ +/* eslint react/prop-types: 0 */ import { Component } from 'react'; import PropTypes from 'prop-types'; import { filters } from './filter'; +import { LIKE } from './comparison'; export default class FilterWrapper extends Component { static propTypes = { store: PropTypes.object.isRequired, columns: PropTypes.array.isRequired, baseElement: PropTypes.func.isRequired, + onRemoteFilterChange: PropTypes.func.isRequired, + // refactoring later _: PropTypes.object.isRequired } @@ -16,28 +20,51 @@ export default class FilterWrapper extends Component { this.onFilter = this.onFilter.bind(this); } - componentWillReceiveProps() { - this.setState(() => ({ isDataChanged: false })); + componentWillReceiveProps(nextProps) { + // consider to use lodash.isEqual + if (JSON.stringify(this.state.currFilters) !== JSON.stringify(nextProps.store.filters)) { + this.setState(() => ({ isDataChanged: true, currFilters: nextProps.store.filters })); + } else { + this.setState(() => ({ isDataChanged: false })); + } } onFilter(column, filterVal, filterType) { - const { store, columns, _ } = this.props; - const { currFilters } = this.state; + const { store, columns, _, onRemoteFilterChange } = this.props; + const currFilters = Object.assign({}, this.state.currFilters); const { dataField, filter } = column; if (!_.isDefined(filterVal) || filterVal === '') { delete currFilters[dataField]; } else { - const { comparator } = filter.props; + const { comparator = LIKE } = filter.props; currFilters[dataField] = { filterVal, filterType, comparator }; } + store.filters = currFilters; + + if (this.isRemote() || this.isPaginationRemote()) { + onRemoteFilterChange(this.isPaginationRemote()); + // when remote filtering is enable, dont set currFilters state + // in the componentWillReceiveProps, it's the key point that we can know the filter is changed + return; + } store.filteredData = filters(store, columns, _)(currFilters); - store.filtering = Object.keys(currFilters).length > 0; - this.setState(() => ({ currFilters, isDataChanged: true })); } + // refactoring later + isRemote() { + const { remote } = this.props; + return remote === true || (typeof remote === 'object' && remote.filter); + } + + // refactoring later + isPaginationRemote() { + const { remote } = this.props; + return remote === true || (typeof remote === 'object' && remote.pagination); + } + render() { return this.props.baseElement({ ...this.props, diff --git a/packages/react-bootstrap-table2-filter/test/wrapper.test.js b/packages/react-bootstrap-table2-filter/test/wrapper.test.js index c877c62..9541c8d 100644 --- a/packages/react-bootstrap-table2-filter/test/wrapper.test.js +++ b/packages/react-bootstrap-table2-filter/test/wrapper.test.js @@ -1,4 +1,5 @@ import React from 'react'; +import sinon from 'sinon'; import { shallow } from 'enzyme'; import _ from 'react-bootstrap-table2/src/utils'; @@ -20,7 +21,11 @@ for (let i = 0; i < 20; i += 1) { describe('Wrapper', () => { let wrapper; let instance; + const onRemoteFilterChangeCB = sinon.stub(); + afterEach(() => { + onRemoteFilterChangeCB.reset(); + }); const createTableProps = () => { const tableProps = { @@ -40,7 +45,8 @@ describe('Wrapper', () => { data, filter: filter(), _, - store: new Store('id') + store: new Store('id'), + onRemoteFilterChange: onRemoteFilterChangeCB }; tableProps.store.data = data; return tableProps; @@ -84,13 +90,28 @@ describe('Wrapper', () => { describe('componentWillReceiveProps', () => { let nextProps; - beforeEach(() => { - nextProps = createTableProps(); - instance.componentWillReceiveProps(nextProps); + describe('when props.store.filters is same as current state.currFilters', () => { + beforeEach(() => { + nextProps = createTableProps(); + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting isDataChanged as false (Temporary solution)', () => { + expect(instance.state.isDataChanged).toBeFalsy(); + }); }); - it('should setting isDataChanged as false always(Temporary solution)', () => { - expect(instance.state.isDataChanged).toBeFalsy(); + describe('when props.store.filters is different from current state.currFilters', () => { + beforeEach(() => { + nextProps = createTableProps(); + nextProps.store.filters = { price: { filterVal: 20, filterType: FILTER_TYPE.TEXT } }; + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting states correctly', () => { + expect(instance.state.isDataChanged).toBeTruthy(); + expect(instance.state.currFilters).toBe(nextProps.store.filters); + }); }); }); @@ -126,7 +147,7 @@ describe('Wrapper', () => { it('should setting store object correctly', () => { instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); - expect(props.store.filtering).toBeTruthy(); + expect(props.store.filters).toEqual(instance.state.currFilters); }); it('should setting state correctly', () => { @@ -136,30 +157,54 @@ describe('Wrapper', () => { }); }); + describe('when remote filter is enabled', () => { + const filterVal = '3'; + + beforeEach(() => { + props = createTableProps(); + props.remote = { filter: true }; + createFilterWrapper(props); + instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); + }); + + it('should not setting store object correctly', () => { + expect(props.store.filters).not.toEqual(instance.state.currFilters); + }); + + it('should not setting state', () => { + expect(instance.state.isDataChanged).toBeFalsy(); + expect(Object.keys(instance.state.currFilters)).toHaveLength(0); + }); + + it('should calling props.onRemoteFilterChange correctly', () => { + expect(onRemoteFilterChangeCB.calledOnce).toBeTruthy(); + }); + }); + describe('combination', () => { it('should setting store object correctly', () => { instance.onFilter(props.columns[1], '3', FILTER_TYPE.TEXT); - expect(props.store.filtering).toBeTruthy(); + expect(props.store.filters).toEqual(instance.state.currFilters); expect(instance.state.isDataChanged).toBeTruthy(); expect(Object.keys(instance.state.currFilters)).toHaveLength(1); instance.onFilter(props.columns[1], '2', FILTER_TYPE.TEXT); - expect(props.store.filtering).toBeTruthy(); + expect(props.store.filters).toEqual(instance.state.currFilters); expect(instance.state.isDataChanged).toBeTruthy(); expect(Object.keys(instance.state.currFilters)).toHaveLength(1); instance.onFilter(props.columns[2], '2', FILTER_TYPE.TEXT); - expect(props.store.filtering).toBeTruthy(); + expect(props.store.filters).toEqual(instance.state.currFilters); expect(instance.state.isDataChanged).toBeTruthy(); expect(Object.keys(instance.state.currFilters)).toHaveLength(2); instance.onFilter(props.columns[2], '', FILTER_TYPE.TEXT); - expect(props.store.filtering).toBeTruthy(); + expect(props.store.filters).toEqual(instance.state.currFilters); expect(instance.state.isDataChanged).toBeTruthy(); expect(Object.keys(instance.state.currFilters)).toHaveLength(1); instance.onFilter(props.columns[1], '', FILTER_TYPE.TEXT); - expect(props.store.filtering).toBeFalsy(); + expect(props.store.filters).toEqual(instance.state.currFilters); expect(instance.state.isDataChanged).toBeTruthy(); expect(Object.keys(instance.state.currFilters)).toHaveLength(0); }); diff --git a/packages/react-bootstrap-table2-paginator/src/wrapper.js b/packages/react-bootstrap-table2-paginator/src/wrapper.js index f54a8d9..e01cd51 100644 --- a/packages/react-bootstrap-table2-paginator/src/wrapper.js +++ b/packages/react-bootstrap-table2-paginator/src/wrapper.js @@ -11,7 +11,8 @@ import { getByCurrPage } from './page'; class PaginationWrapper extends Component { static propTypes = { store: PropTypes.object.isRequired, - baseElement: PropTypes.func.isRequired + baseElement: PropTypes.func.isRequired, + onRemotePageChange: PropTypes.func.isRequired } constructor(props) { @@ -43,14 +44,15 @@ class PaginationWrapper extends Component { } this.state = { currPage, currSizePerPage }; + this.saveToStore(currPage, currSizePerPage); } componentWillReceiveProps(nextProps) { let needNewState = false; let { currPage, currSizePerPage } = this.state; - const { page, sizePerPage, pageStartIndex } = nextProps.pagination.options; + const { page, sizePerPage, pageStartIndex, onPageChange } = nextProps.pagination.options; - if (typeof page !== 'undefined') { // user defined page + if (typeof page !== 'undefined' && currPage !== page) { // user defined page currPage = page; needNewState = true; } else if (nextProps.isDataChanged) { // user didn't defined page but data change @@ -63,7 +65,19 @@ class PaginationWrapper extends Component { needNewState = true; } - if (needNewState) this.setState(() => ({ currPage, currSizePerPage })); + this.saveToStore(currPage, currSizePerPage); + + if (needNewState) { + if (onPageChange) { + onPageChange(currPage, currSizePerPage); + } + this.setState(() => ({ currPage, currSizePerPage })); + } + } + + saveToStore(page, sizePerPage) { + this.props.store.page = page; + this.props.store.sizePerPage = sizePerPage; } isRemote() { @@ -74,11 +88,13 @@ class PaginationWrapper extends Component { handleChangePage(currPage) { const { currSizePerPage } = this.state; const { pagination: { options }, onRemotePageChange } = this.props; + this.saveToStore(currPage, currSizePerPage); + if (options.onPageChange) { options.onPageChange(currPage, currSizePerPage); } if (this.isRemote()) { - onRemotePageChange(currPage, currSizePerPage); + onRemotePageChange(); return; } this.setState(() => { @@ -90,11 +106,13 @@ class PaginationWrapper extends Component { handleChangeSizePerPage(currSizePerPage, currPage) { const { pagination: { options }, onRemotePageChange } = this.props; + this.saveToStore(currPage, currSizePerPage); + if (options.onSizePerPageChange) { options.onSizePerPageChange(currSizePerPage, currPage); } if (this.isRemote()) { - onRemotePageChange(currPage, currSizePerPage); + onRemotePageChange(); return; } this.setState(() => { diff --git a/packages/react-bootstrap-table2-paginator/test/wrapper.test.js b/packages/react-bootstrap-table2-paginator/test/wrapper.test.js index 7254e2a..947971d 100644 --- a/packages/react-bootstrap-table2-paginator/test/wrapper.test.js +++ b/packages/react-bootstrap-table2-paginator/test/wrapper.test.js @@ -21,6 +21,11 @@ for (let i = 0; i < 100; i += 1) { describe('Wrapper', () => { let wrapper; let instance; + const onRemotePageChangeCB = sinon.stub(); + + afterEach(() => { + onRemotePageChangeCB.reset(); + }); const createTableProps = (props = {}) => { const tableProps = { @@ -34,7 +39,8 @@ describe('Wrapper', () => { }], data, pagination: paginator(props.options), - store: new Store('id') + store: new Store('id'), + onRemotePageChange: onRemotePageChangeCB }; tableProps.store.data = data; return tableProps; @@ -69,6 +75,11 @@ describe('Wrapper', () => { expect(instance.state.currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[0]); }); + it('should saving page and sizePerPage to store correctly', () => { + expect(props.store.page).toBe(instance.state.currPage); + expect(props.store.sizePerPage).toBe(instance.state.currSizePerPage); + }); + it('should rendering BootstraTable correctly', () => { const table = wrapper.find(BootstrapTable); expect(table.length).toBe(1); @@ -105,10 +116,19 @@ describe('Wrapper', () => { nextProps = createTableProps(); }); - it('should setting currPage state correctly by options.page', () => { - nextProps.pagination.options.page = 2; - instance.componentWillReceiveProps(nextProps); - expect(instance.state.currPage).toEqual(nextProps.pagination.options.page); + describe('when options.page is existing', () => { + beforeEach(() => { + nextProps.pagination.options.page = 2; + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting currPage state correctly', () => { + expect(instance.state.currPage).toEqual(nextProps.pagination.options.page); + }); + + it('should saving store.page correctly', () => { + expect(props.store.page).toEqual(instance.state.currPage); + }); }); it('should not setting currPage state if options.page not existing', () => { @@ -117,10 +137,19 @@ describe('Wrapper', () => { expect(instance.state.currPage).toBe(currPage); }); - it('should setting currSizePerPage state correctly by options.sizePerPage', () => { - nextProps.pagination.options.sizePerPage = 20; - instance.componentWillReceiveProps(nextProps); - expect(instance.state.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage); + describe('when options.sizePerPage is existing', () => { + beforeEach(() => { + nextProps.pagination.options.sizePerPage = 20; + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting currSizePerPage state correctly', () => { + expect(instance.state.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage); + }); + + it('should saving store.sizePerPage correctly', () => { + expect(props.store.sizePerPage).toEqual(instance.state.currSizePerPage); + }); }); it('should not setting currSizePerPage state if options.sizePerPage not existing', () => { @@ -129,17 +158,35 @@ describe('Wrapper', () => { expect(instance.state.currSizePerPage).toBe(currSizePerPage); }); - it('should setting currPage state when nextProps.isDataChanged is true', () => { - nextProps.isDataChanged = true; - instance.componentWillReceiveProps(nextProps); - expect(instance.state.currPage).toBe(Const.PAGE_START_INDEX); + describe('when nextProps.isDataChanged is true', () => { + beforeEach(() => { + nextProps.isDataChanged = true; + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting currPage state correctly', () => { + expect(instance.state.currPage).toBe(Const.PAGE_START_INDEX); + }); + + it('should saving store.page correctly', () => { + expect(props.store.page).toEqual(instance.state.currPage); + }); }); - it('should setting currPage state when nextProps.isDataChanged is true and options.pageStartIndex is existing', () => { - nextProps.isDataChanged = true; - nextProps.pagination.options.pageStartIndex = 0; - instance.componentWillReceiveProps(nextProps); - expect(instance.state.currPage).toBe(nextProps.pagination.options.pageStartIndex); + describe('when nextProps.isDataChanged is true and options.pageStartIndex is existing', () => { + beforeEach(() => { + nextProps.isDataChanged = true; + nextProps.pagination.options.pageStartIndex = 0; + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting currPage state correctly', () => { + expect(instance.state.currPage).toBe(nextProps.pagination.options.pageStartIndex); + }); + + it('should saving store.page correctly', () => { + expect(props.store.page).toEqual(instance.state.currPage); + }); }); }); }); @@ -448,11 +495,16 @@ describe('Wrapper', () => { expect(onPageChange.calledWith(newPage, instance.state.currSizePerPage)).toBeTruthy(); }); + it('should saving page and sizePerPage to store correctly', () => { + expect(props.store.page).toBe(newPage); + expect(props.store.sizePerPage).toBe(instance.state.currSizePerPage); + }); + describe('when pagination remote is enable', () => { beforeEach(() => { props.remote = true; - props.onRemotePageChange = sinon.stub(); createPaginationWrapper(props, false); + onRemotePageChangeCB.reset(); instance.handleChangePage(newPage); }); @@ -460,10 +512,8 @@ describe('Wrapper', () => { expect(instance.state.currPage).not.toEqual(newPage); }); - it('should calling options.onRemotePageChange correctly', () => { - expect(props.onRemotePageChange.calledOnce).toBeTruthy(); - expect(props.onRemotePageChange.calledWith( - newPage, instance.state.currSizePerPage)).toBeTruthy(); + it('should calling props.onRemotePageChange correctly', () => { + expect(onRemotePageChangeCB.calledOnce).toBeTruthy(); }); }); }); @@ -492,11 +542,16 @@ describe('Wrapper', () => { expect(onSizePerPageChange.calledWith(newSizePerPage, newPage)).toBeTruthy(); }); + it('should saving page and sizePerPage to store correctly', () => { + expect(props.store.page).toBe(newPage); + expect(props.store.sizePerPage).toBe(newSizePerPage); + }); + describe('when pagination remote is enable', () => { beforeEach(() => { props.remote = true; - props.onRemotePageChange = sinon.stub(); createPaginationWrapper(props, false); + onRemotePageChangeCB.reset(); instance.handleChangeSizePerPage(newSizePerPage, newPage); }); @@ -505,9 +560,8 @@ describe('Wrapper', () => { expect(instance.state.currSizePerPage).not.toEqual(newSizePerPage); }); - it('should calling options.onRemotePageChange correctly', () => { - expect(props.onRemotePageChange.calledOnce).toBeTruthy(); - expect(props.onRemotePageChange.calledWith(newPage, newSizePerPage)).toBeTruthy(); + it('should calling props.onRemotePageChange correctly', () => { + expect(onRemotePageChangeCB.calledOnce).toBeTruthy(); }); }); }); diff --git a/packages/react-bootstrap-table2/src/container.js b/packages/react-bootstrap-table2/src/container.js index a0866a7..8fb7959 100644 --- a/packages/react-bootstrap-table2/src/container.js +++ b/packages/react-bootstrap-table2/src/container.js @@ -20,16 +20,35 @@ const withDataStore = Base => this.store = new Store(props.keyField); this.store.data = props.data; this.handleUpdateCell = this.handleUpdateCell.bind(this); - this.onRemotePageChange = this.onRemotePageChange.bind(this); + this.handleRemotePageChange = this.handleRemotePageChange.bind(this); + this.handleRemoteFilterChange = this.handleRemoteFilterChange.bind(this); } componentWillReceiveProps(nextProps) { this.store.data = nextProps.data; } - onRemotePageChange(page, sizePerPage) { - const newState = { page, sizePerPage }; - this.props.onTableChange(newState); + getNewestState(state = {}) { + return { + page: this.store.page, + sizePerPage: this.store.sizePerPage, + filters: this.store.filters, + ...state + }; + } + + handleRemotePageChange() { + this.props.onTableChange('pagination', this.getNewestState()); + } + + // refactoring later for isRemotePagination + handleRemoteFilterChange(isRemotePagination) { + const newState = {}; + if (isRemotePagination) { + const options = this.props.pagination.options || {}; + newState.page = _.isDefined(options.pageStartIndex) ? options.pageStartIndex : 1; + } + this.props.onTableChange('filter', this.getNewestState(newState)); } handleUpdateCell(rowId, dataField, newValue) { @@ -72,13 +91,17 @@ const withDataStore = Base => } else if (this.props.selectRow) { return wrapWithSelection(baseProps); } else if (this.props.filter) { - return wrapWithFilter(baseProps); + return wrapWithFilter({ + ...baseProps, + onRemoteFilterChange: this.handleRemoteFilterChange, + onRemotePageChange: this.handleRemotePageChange + }); } else if (this.props.columns.filter(col => col.sort).length > 0) { return wrapWithSort(baseProps); } else if (this.props.pagination) { return wrapWithPagination({ ...baseProps, - onRemotePageChange: this.onRemotePageChange + onRemotePageChange: this.handleRemotePageChange }); } diff --git a/packages/react-bootstrap-table2/src/store/index.js b/packages/react-bootstrap-table2/src/store/index.js index 97d1aba..b735f86 100644 --- a/packages/react-bootstrap-table2/src/store/index.js +++ b/packages/react-bootstrap-table2/src/store/index.js @@ -11,7 +11,9 @@ export default class Store { this._sortOrder = undefined; this._sortField = undefined; this._selected = []; - this._filtering = false; + this._filters = {}; + this._page = undefined; + this._sizePerPage = undefined; } edit(rowId, dataField, newValue) { @@ -30,13 +32,13 @@ export default class Store { } get data() { - if (this._filtering) { + if (Object.keys(this._filters).length > 0) { return this._filteredData; } return this._data; } set data(data) { - if (this._filtering) { + if (Object.keys(this._filters).length > 0) { this._filteredData = data; } else { this._data = (data ? JSON.parse(JSON.stringify(data)) : []); @@ -52,12 +54,18 @@ export default class Store { get sortOrder() { return this._sortOrder; } set sortOrder(sortOrder) { this._sortOrder = sortOrder; } + get page() { return this._page; } + set page(page) { this._page = page; } + + get sizePerPage() { return this._sizePerPage; } + set sizePerPage(sizePerPage) { this._sizePerPage = sizePerPage; } + get sortField() { return this._sortField; } set sortField(sortField) { this._sortField = sortField; } get selected() { return this._selected; } set selected(selected) { this._selected = selected; } - get filtering() { return this._filtering; } - set filtering(filtering) { this._filtering = filtering; } + get filters() { return this._filters; } + set filters(filters) { this._filters = filters; } } diff --git a/packages/react-bootstrap-table2/test/container.test.js b/packages/react-bootstrap-table2/test/container.test.js index 8d1c614..fd4068a 100644 --- a/packages/react-bootstrap-table2/test/container.test.js +++ b/packages/react-bootstrap-table2/test/container.test.js @@ -202,9 +202,7 @@ describe('withDataStore', () => { }); }); - describe('onRemotePageChange', () => { - const page = 2; - const sizePerPage = 25; + describe('handleRemotePageChange', () => { const onTableChangeCallBack = sinon.stub(); beforeEach(() => { @@ -216,12 +214,86 @@ describe('withDataStore', () => { onTableChange={ onTableChangeCallBack } /> ); - wrapper.instance().onRemotePageChange(page, sizePerPage); + wrapper.instance().handleRemotePageChange(); }); it('should calling onTableChange correctly', () => { expect(onTableChangeCallBack.calledOnce).toBeTruthy(); - expect(onTableChangeCallBack.calledWith({ page, sizePerPage })).toBeTruthy(); + expect(onTableChangeCallBack.calledWith('pagination', wrapper.instance().getNewestState())).toBeTruthy(); + }); + }); + + describe('handleRemoteFilterChange', () => { + const onTableChangeCallBack = sinon.stub(); + + beforeEach(() => { + onTableChangeCallBack.reset(); + wrapper = shallow( + + ); + }); + + describe('when isRemotePagination argument is false', () => { + beforeEach(() => { + wrapper.instance().handleRemoteFilterChange(false); + }); + + it('should calling onTableChange correctly', () => { + expect(onTableChangeCallBack.calledOnce).toBeTruthy(); + expect(onTableChangeCallBack.calledWith('filter', wrapper.instance().getNewestState())).toBeTruthy(); + }); + }); + + describe('when isRemotePagination argument is false', () => { + describe('and pagination.options.pageStartIndex is defined', () => { + const options = { pageStartIndex: 0 }; + beforeEach(() => { + wrapper = shallow( + {} } } + onTableChange={ onTableChangeCallBack } + /> + ); + wrapper.instance().handleRemoteFilterChange(true); + }); + + it('should calling onTableChange correctly', () => { + expect(onTableChangeCallBack.calledOnce).toBeTruthy(); + const newState = wrapper.instance().getNewestState(); + newState.page = options.pageStartIndex; + expect(onTableChangeCallBack.calledWith('filter', newState)).toBeTruthy(); + }); + }); + + describe('and pagination.options.pageStartIndex is not defined', () => { + beforeEach(() => { + wrapper = shallow( + {} } } + onTableChange={ onTableChangeCallBack } + /> + ); + wrapper.instance().handleRemoteFilterChange(true); + }); + + it('should calling onTableChange correctly', () => { + expect(onTableChangeCallBack.calledOnce).toBeTruthy(); + const newState = wrapper.instance().getNewestState(); + newState.page = 1; + expect(onTableChangeCallBack.calledWith('filter', newState)).toBeTruthy(); + }); + }); }); }); }); From 2fbc84e36e3468b77c691161e244fbeb7d5ba96b Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 24 Dec 2017 16:10:04 +0800 Subject: [PATCH 3/3] pathc docs for remote mode --- docs/README.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0766df6..05da321 100644 --- a/docs/README.md +++ b/docs/README.md @@ -40,14 +40,21 @@ This is a chance that you can connect to your remote server or database to manip For flexibility reason, you can control what functionality should be handled on remote via a object return: ```js -remote={ { pagination: true } } +remote={ { filter: true } } ``` -In above case, only pagination will be handled on remote. +In above case, only column filter will be handled on remote. > Note: when remote is enable, you are suppose to give [`onTableChange`](#onTableChange) prop on `BootstrapTable` > It's the only way to communicate to your remote server and update table states. +A special case for remote pagination: +```js +remote={ { pagination: true, filter: false, sort: false } } +``` + +In pagination case, even you only specified the paignation need to handle as remote, `react-bootstrap-table2` will handle all the table changes(`filter`, `sort` etc) as remote mode, because `react-bootstrap-table` only know the data of current page, but filtering, searching or sort need to work on overall datas. + ### loading - [Bool] Telling if table is loading or not, for example: waiting data loading, filtering etc. It's **only** valid when [`remote`](#remote) is enabled. When `loading` is `true`, `react-bootstrap-table` will attend to render a overlay on table via [`overlay`](#overlay) prop, if [`overlay`](#overlay) prop is not given, `react-bootstrap-table` will ignore the overlay rendering. @@ -230,17 +237,25 @@ const columns = [ { This callback function will be called when [`remote`](#remote) enabled only. ```js -const onTableChange = (newState) => { +const onTableChange = (type, newState) => { // handle any data change here } ``` -There's only one argument will be passed to `onTableChange`, `newState`: +There's only two arguments will be passed to `onTableChange`: `type` and `newState`: + +`type` is tell you what kind of functionality to trigger this table's change: available values at the below: + +* `filter` +* `pagination` + +Following is a shape of `newState` ```js { page, // newest page - sizePerPage //newest sizePerPage + sizePerPage, //newest sizePerPage + filters // an object which have current filter status per column } ``` \ No newline at end of file