mirror of
https://github.com/gosticks/react-bootstrap-table2.git
synced 2025-10-16 11:55:39 +00:00
Merge pull request #156 refine remote filter and pagination
Implement remote filter
This commit is contained in:
commit
90bea38900
@ -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.
|
||||
|
||||
### <a name='loading'>loading - [Bool]</a>
|
||||
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
|
||||
}
|
||||
<BootstrapTable data={ data } columns={ columns } onTableChange={ onTableChange } />
|
||||
```
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
202
packages/react-bootstrap-table2-example/examples/remote/remote-all.js
vendored
Normal file
202
packages/react-bootstrap-table2-example/examples/remote/remote-all.js
vendored
Normal file
@ -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 }) => (
|
||||
<div>
|
||||
<BootstrapTable
|
||||
remote={ { pagination: true } }
|
||||
keyField="id"
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
filter={ filterFactory() }
|
||||
pagination={ paginator({ page, sizePerPage, totalSize }) }
|
||||
onTableChange={ onTableChange }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
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 (
|
||||
<RemoteAll
|
||||
data={ data }
|
||||
page={ page }
|
||||
sizePerPage={ sizePerPage }
|
||||
totalSize={ this.state.totalSize }
|
||||
onTableChange={ this.handleTableChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => (
|
||||
<div>
|
||||
<h3>When <code>remote.pagination</code> is enabled, the filtering,
|
||||
sorting and searching will also change to remote mode automatically</h3>
|
||||
<BootstrapTable
|
||||
remote={ { pagination: true } }
|
||||
keyField="id"
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
filter={ filterFactory() }
|
||||
pagination={ paginator({ page, sizePerPage, totalSize }) }
|
||||
onTableChange={ onTableChange }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
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 (
|
||||
<RemoteAll
|
||||
data={ data }
|
||||
page={ page }
|
||||
sizePerPage={ sizePerPage }
|
||||
totalSize={ this.state.totalSize }
|
||||
onTableChange={ this.handleTableChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container;
|
||||
105
packages/react-bootstrap-table2-example/examples/remote/remote-filter.js
vendored
Normal file
105
packages/react-bootstrap-table2-example/examples/remote/remote-filter.js
vendored
Normal file
@ -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()
|
||||
}];
|
||||
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
|
||||
`;
|
||||
|
||||
const RemoteFilter = props => (
|
||||
<div>
|
||||
<BootstrapTable
|
||||
remote={ { filter: true } }
|
||||
keyField="id"
|
||||
data={ props.data }
|
||||
columns={ columns }
|
||||
filter={ filterFactory() }
|
||||
onTableChange={ props.onTableChange }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
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 (
|
||||
<RemoteFilter
|
||||
data={ this.state.data }
|
||||
onTableChange={ this.handleTableChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container;
|
||||
@ -105,7 +105,7 @@ class Container extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
handleTableChange = ({ page, sizePerPage }) => {
|
||||
handleTableChange = (type, { page, sizePerPage }) => {
|
||||
const currentIndex = (page - 1) * sizePerPage;
|
||||
setTimeout(() => {
|
||||
this.setState(() => ({
|
||||
@ -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', () => <PaginationTable />)
|
||||
.add('Pagination Hooks', () => <PaginationHooksTable />)
|
||||
.add('Custom Pagination', () => <CustomPaginationTable />)
|
||||
.add('Remote Pagination', () => <RemotePaginationTable />);
|
||||
.add('Custom Pagination', () => <CustomPaginationTable />);
|
||||
|
||||
storiesOf('EmptyTableOverlay', module)
|
||||
.add('Empty Table Overlay', () => <EmptyTableOverlay />)
|
||||
.add('Table Overlay', () => <TableOverlay />);
|
||||
|
||||
storiesOf('Remote', module)
|
||||
.add('Remote Filter', () => <RemoteFilter />)
|
||||
.add('Remote Pagination', () => <RemotePaginationTable />)
|
||||
.add('Remote All', () => <RemoteAll />);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
35
packages/react-bootstrap-table2/src/container.js
vendored
35
packages/react-bootstrap-table2/src/container.js
vendored
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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(
|
||||
<BootstrapTable
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
onTableChange={ onTableChangeCallBack }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
<BootstrapTable
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
pagination={ { options, PaginationWrapper: () => {} } }
|
||||
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(
|
||||
<BootstrapTable
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
pagination={ { PaginationWrapper: () => {} } }
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user