diff --git a/docs/README.md b/docs/README.md
index 2a334c1..0766df6 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -22,6 +22,7 @@
* [rowEvents](#rowEvents)
* [defaultSorted](#defaultSorted)
* [pagination](#pagination)
+* [filter](#filter)
* [onTableChange](#onTableChange)
### keyField(**required**) - [String]
@@ -198,6 +199,33 @@ paginator({
})
```
+### filter - [Object]
+`filter` allow user to filter data by column. However, filter funcitonality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-filter` firstly.
+
+```sh
+$ npm install react-bootstrap-table2-filter --save
+```
+
+After installation of `react-bootstrap-table2-filter`, you can configure filter on `react-bootstrap-table2` easily:
+
+```js
+import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
+
+// omit...
+const columns = [ {
+ dataField: 'id',
+ text: 'Production ID'
+}, {
+ dataField: 'name',
+ text: 'Production Name',
+ filter: textFilter() // apply text filter
+}, {
+ dataField: 'price',
+ text: 'Production Price'
+} ];
+
+```
+
### onTableChange - [Function]
This callback function will be called when [`remote`](#remote) enabled only.
diff --git a/docs/columns.md b/docs/columns.md
index 52aaa17..0f13a82 100644
--- a/docs/columns.md
+++ b/docs/columns.md
@@ -31,20 +31,22 @@ Available properties in a column object:
* [validator](#validator)
* [editCellStyle](#editCellStyle)
* [editCellClasses](#editCellClasses)
+* [filter](#filter)
+* [filterValue](#filterValue)
Following is a most simplest and basic usage:
```js
const rows = [ { id: 1, name: '...', price: '102' } ];
const columns = [ {
- dataField: id,
- text: Production ID
+ dataField: 'id',
+ text: 'Production ID'
}, {
- dataField: name,
- text: Production Name
+ dataField: 'name',
+ text: 'Production Name'
}, {
- dataField: price,
- text: Production Price
+ dataField: 'price',
+ text: 'Production Price'
}
];
```
@@ -525,4 +527,24 @@ Or take a callback function
// it is suppose to return a string
}
}
-```
\ No newline at end of file
+```
+
+## column.filter - [Object]
+Configure `column.filter` will able to setup a column level filter on the header column. Currently, `react-bootstrap-table2` support following filters:
+
+* Text(`textFilter`)
+
+We have a quick example to show you how to use `column.filter`:
+
+```
+import { textFilter } from 'react-bootstrap-table2-filter';
+
+// omit...
+{
+ dataField: 'price',
+ text: 'Product Price',
+ filter: textFilter()
+}
+```
+
+For some reason of simple customization, `react-bootstrap-table2` allow you to pass some props to filter factory function. Please check [here](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/master/packages/react-bootstrap-table2-filter/README.md) for more detail tutorial.
\ No newline at end of file
diff --git a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js
index 8ebc72a..25f09d8 100644
--- a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js
+++ b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js
@@ -3,6 +3,7 @@ const path = require('path');
const sourcePath = path.join(__dirname, '../../react-bootstrap-table2/src');
const paginationSourcePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/src');
const overlaySourcePath = path.join(__dirname, '../../react-bootstrap-table2-overlay/src');
+const filterSourcePath = path.join(__dirname, '../../react-bootstrap-table2-filter/src');
const sourceStylePath = path.join(__dirname, '../../react-bootstrap-table2/style');
const paginationStylePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/style');
const storyPath = path.join(__dirname, '../stories');
@@ -26,7 +27,7 @@ const loaders = [{
test: /\.js?$/,
use: ['babel-loader'],
exclude: /node_modules/,
- include: [sourcePath, paginationSourcePath, overlaySourcePath, storyPath],
+ include: [sourcePath, paginationSourcePath, overlaySourcePath, filterSourcePath, storyPath],
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader'],
diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js
new file mode 100644
index 0000000..55158e0
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js
@@ -0,0 +1,74 @@
+/* eslint no-unused-vars: 0 */
+import React from 'react';
+import BootstrapTable from 'react-bootstrap-table2';
+import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
+import Code from 'components/common/code-block';
+import { jobsGenerator } from 'utils/common';
+
+const jobs = jobsGenerator(5);
+
+const owners = ['Allen', 'Bob', 'Cat'];
+const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money'];
+
+const columns = [{
+ dataField: 'id',
+ text: 'Job ID'
+}, {
+ dataField: 'name',
+ text: 'Job Name',
+ filter: textFilter()
+}, {
+ dataField: 'owner',
+ text: 'Job Owner',
+ filter: textFilter(),
+ formatter: (cell, row) => owners[cell],
+ filterValue: (cell, row) => owners[cell]
+}, {
+ dataField: 'type',
+ text: 'Job Type',
+ filter: textFilter(),
+ formatter: (cell, row) => types[cell],
+ filterValue: (cell, row) => types[cell]
+}];
+
+const sourceCode = `\
+import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
+
+const owners = ['Allen', 'Bob', 'Cat'];
+const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money'];
+const columns = [{
+ dataField: 'id',
+ text: 'Job ID'
+}, {
+ dataField: 'name',
+ text: 'Job Name',
+ filter: textFilter()
+}, {
+ dataField: 'owner',
+ text: 'Job Owner',
+ filter: textFilter(),
+ formatter: (cell, row) => owners[cell],
+ filterValue: (cell, row) => owners[cell]
+}, {
+ dataField: 'type',
+ text: 'Job Type',
+ filter: textFilter(),
+ filterValue: (cell, row) => types[cell]
+}];
+
+// shape of job: { id: 0, name: 'Job name 0', owner: 1, type: 3 }
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-text-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-text-filter.js
new file mode 100644
index 0000000..84edd0b
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-text-filter.js
@@ -0,0 +1,68 @@
+/* eslint no-console: 0 */
+import React from 'react';
+import BootstrapTable from 'react-bootstrap-table2';
+import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const products = productsGenerator(8);
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name',
+ filter: textFilter()
+}, {
+ dataField: 'price',
+ text: 'Product Price',
+ filter: textFilter({
+ delay: 1000, // default is 500ms
+ style: {
+ backgroundColor: 'yellow'
+ },
+ className: 'test-classname',
+ placeholder: 'Custom PlaceHolder',
+ onClick: e => console.log(e)
+ })
+}];
+
+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({
+ delay: 1000, // default is 500ms
+ style: {
+ backgroundColor: 'yellow'
+ },
+ className: 'test-classname',
+ placeholder: 'Custom PlaceHolder',
+ onClick: e => console.log(e)
+ })
+}];
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-default-value.js b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-default-value.js
new file mode 100644
index 0000000..edbf0b7
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-default-value.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import BootstrapTable from 'react-bootstrap-table2';
+import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const products = productsGenerator(8);
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name',
+ filter: textFilter()
+}, {
+ dataField: 'price',
+ text: 'Product Price',
+ filter: textFilter({
+ defaultValue: '2103'
+ })
+}];
+
+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({
+ defaultValue: '2103'
+ })
+}];
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-eq-comparator.js b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-eq-comparator.js
new file mode 100644
index 0000000..d062178
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-eq-comparator.js
@@ -0,0 +1,56 @@
+import React from 'react';
+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(8);
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name',
+ filter: textFilter({
+ comparator: Comparator.EQ // default is Comparator.LIKE
+ })
+}, {
+ dataField: 'price',
+ text: 'Product Price',
+ filter: textFilter()
+}];
+
+const sourceCode = `\
+import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter';
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name',
+ filter: textFilter({
+ comparator: Comparator.EQ
+ })
+}, {
+ dataField: 'price',
+ text: 'Product Price',
+ filter: textFilter()
+}];
+
+
+`;
+
+export default () => (
+
+
Product Name filter apply Equal Comparator
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/text-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter.js
new file mode 100644
index 0000000..03de391
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import BootstrapTable from 'react-bootstrap-table2';
+import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const products = productsGenerator(8);
+
+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()
+}];
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/package.json b/packages/react-bootstrap-table2-example/package.json
index a726ef5..419f884 100644
--- a/packages/react-bootstrap-table2-example/package.json
+++ b/packages/react-bootstrap-table2-example/package.json
@@ -19,7 +19,8 @@
"bootstrap": "^3.3.7",
"react-bootstrap-table2": "0.0.1",
"react-bootstrap-table2-paginator": "0.0.1",
- "react-bootstrap-table2-overlay": "0.0.1"
+ "react-bootstrap-table2-overlay": "0.0.1",
+ "react-bootstrap-table2-filter": "0.0.1"
},
"devDependencies": {
"@storybook/addon-console": "^1.0.0",
diff --git a/packages/react-bootstrap-table2-example/src/utils/common.js b/packages/react-bootstrap-table2-example/src/utils/common.js
index 724e042..0d5ad84 100644
--- a/packages/react-bootstrap-table2-example/src/utils/common.js
+++ b/packages/react-bootstrap-table2-example/src/utils/common.js
@@ -20,4 +20,12 @@ export const productsGenerator = (quantity = 5, callback) => {
);
};
+export const jobsGenerator = (quantity = 5) =>
+ Array.from({ length: quantity }, (value, index) => ({
+ id: index,
+ name: `Job name ${index}`,
+ owner: Math.floor(Math.random() * 3),
+ type: Math.floor(Math.random() * 5)
+ }));
+
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js
index 738cfc6..b2f0f15 100644
--- a/packages/react-bootstrap-table2-example/stories/index.js
+++ b/packages/react-bootstrap-table2-example/stories/index.js
@@ -32,6 +32,13 @@ import HeaderColumnClassTable from 'examples/header-columns/column-class-table';
import HeaderColumnStyleTable from 'examples/header-columns/column-style-table';
import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table';
+// column filter
+import TextFilter from 'examples/column-filter/text-filter';
+import TextFilterWithDefaultValue from 'examples/column-filter/text-filter-default-value';
+import TextFilterComparator from 'examples/column-filter/text-filter-eq-comparator';
+import CustomTextFilter from 'examples/column-filter/custom-text-filter';
+import CustomFilterValue from 'examples/column-filter/custom-filter-value';
+
// work on rows
import RowStyleTable from 'examples/rows/row-style';
import RowClassTable from 'examples/rows/row-class';
@@ -121,6 +128,14 @@ storiesOf('Work on Header Columns', module)
.add('Customize Column Style', () => )
.add('Customize Column HTML attribute', () => );
+storiesOf('Column Filter', module)
+ .add('Text Filter', () => )
+ .add('Text Filter with Default Value', () => )
+ .add('Text Filter with Comparator', () => )
+ .add('Custom Text Filter', () => )
+ // add another filter type example right here.
+ .add('Custom Filter Value', () => );
+
storiesOf('Work on Rows', module)
.add('Customize Row Style', () => )
.add('Customize Row Class', () => )
diff --git a/packages/react-bootstrap-table2-filter/README.md b/packages/react-bootstrap-table2-filter/README.md
new file mode 100644
index 0000000..8cef968
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/README.md
@@ -0,0 +1,40 @@
+# react-bootstrap-table2-filter
+
+## Filters
+
+* Text (`textFilter`)
+
+You can get all of above filters via import and these filters are a factory function to create a individual filter instance.
+In addition, for some simple customization reasons, these factory function allow to pass some props.
+
+### Text Filter
+
+```js
+import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
+
+// omit...
+const columns = [
+ ..., {
+ dataField: 'price',
+ text: 'Product Price',
+ filter: textFilter()
+}];
+
+
+```
+
+Following we list all the availabe props for `textFilter` function:
+
+```js
+import { Comparator } from 'react-bootstrap-table2-filter';
+// omit...
+
+const customTextFilter = textFilter({
+ placeholder: 'My Custom PlaceHolder', // custom the input placeholder
+ style: { ... }, // your custom styles on input
+ className: 'my-custom-text-filter', // custom classname on input
+ defaultValue: 'test', // default filtering value
+ delay: 1000, // how long will trigger filtering after user typing, default is 500 ms
+ comparator: Comparator.EQ // default is Comparator.LIKE
+});
+```
\ No newline at end of file
diff --git a/packages/react-bootstrap-table2-filter/package.json b/packages/react-bootstrap-table2-filter/package.json
new file mode 100644
index 0000000..826efa7
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "react-bootstrap-table2-filter",
+ "version": "0.0.1",
+ "description": "it's the column filter addon for react-bootstrap-table2",
+ "main": "src/index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
diff --git a/packages/react-bootstrap-table2-filter/src/comparison.js b/packages/react-bootstrap-table2-filter/src/comparison.js
new file mode 100644
index 0000000..cc24214
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/src/comparison.js
@@ -0,0 +1,2 @@
+export const LIKE = 'LIKE';
+export const EQ = '=';
diff --git a/packages/react-bootstrap-table2-filter/src/components/text.js b/packages/react-bootstrap-table2-filter/src/components/text.js
new file mode 100644
index 0000000..1c102c3
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/src/components/text.js
@@ -0,0 +1,107 @@
+/* eslint react/require-default-props: 0 */
+/* eslint react/prop-types: 0 */
+/* eslint no-return-assign: 0 */
+import React, { Component } from 'react';
+import { PropTypes } from 'prop-types';
+
+import { LIKE, EQ } from '../comparison';
+import { FILTER_TYPE, FILTER_DELAY } from '../const';
+
+class TextFilter extends Component {
+ constructor(props) {
+ super(props);
+ this.filter = this.filter.bind(this);
+ this.handleClick = this.handleClick.bind(this);
+ this.timeout = null;
+ this.state = {
+ value: props.defaultValue
+ };
+ }
+ componentDidMount() {
+ const defaultValue = this.input.value;
+ if (defaultValue) {
+ this.props.onFilter(this.props.column, defaultValue, FILTER_TYPE.TEXT);
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.defaultValue !== this.props.defaultValue) {
+ this.applyFilter(nextProps.defaultValue);
+ }
+ }
+
+ componentWillUnmount() {
+ this.cleanTimer();
+ }
+
+ filter(e) {
+ e.stopPropagation();
+ this.cleanTimer();
+ const filterValue = e.target.value;
+ this.setState(() => ({ value: filterValue }));
+ this.timeout = setTimeout(() => {
+ this.props.onFilter(this.props.column, filterValue, FILTER_TYPE.TEXT);
+ }, this.props.delay);
+ }
+
+ cleanTimer() {
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ }
+ }
+
+ cleanFiltered() {
+ const value = this.props.defaultValue;
+ this.setState(() => ({ value }));
+ this.props.onFilter(this.props.column, value, FILTER_TYPE.TEXT);
+ }
+
+ applyFilter(filterText) {
+ this.setState(() => ({ value: filterText }));
+ this.props.onFilter(this.props.column, filterText, FILTER_TYPE.TEXT);
+ }
+
+ handleClick(e) {
+ e.stopPropagation();
+ if (this.props.onClick) {
+ this.props.onClick(e);
+ }
+ }
+
+ render() {
+ const { placeholder, column: { text }, style, className, onFilter, ...rest } = this.props;
+ // stopPropagation for onClick event is try to prevent sort was triggered.
+ return (
+ this.input = n }
+ type="text"
+ className={ `filter text-filter form-control ${className}` }
+ style={ style }
+ onChange={ this.filter }
+ onClick={ this.handleClick }
+ placeholder={ placeholder || `Enter ${text}...` }
+ value={ this.state.value }
+ />
+ );
+ }
+}
+
+TextFilter.propTypes = {
+ onFilter: PropTypes.func.isRequired,
+ column: PropTypes.object.isRequired,
+ comparator: PropTypes.oneOf([LIKE, EQ]),
+ defaultValue: PropTypes.string,
+ delay: PropTypes.number,
+ placeholder: PropTypes.string,
+ style: PropTypes.object,
+ className: PropTypes.string
+};
+
+TextFilter.defaultProps = {
+ delay: FILTER_DELAY,
+ defaultValue: ''
+};
+
+
+export default TextFilter;
diff --git a/packages/react-bootstrap-table2-filter/src/const.js b/packages/react-bootstrap-table2-filter/src/const.js
new file mode 100644
index 0000000..25faae0
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/src/const.js
@@ -0,0 +1,5 @@
+export const FILTER_TYPE = {
+ TEXT: 'TEXT'
+};
+
+export const FILTER_DELAY = 500;
diff --git a/packages/react-bootstrap-table2-filter/src/filter.js b/packages/react-bootstrap-table2-filter/src/filter.js
new file mode 100644
index 0000000..03914a8
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/src/filter.js
@@ -0,0 +1,45 @@
+import { FILTER_TYPE } from './const';
+import { LIKE, EQ } from './comparison';
+
+export const filterByText = _ => (
+ data,
+ dataField,
+ { filterVal, comparator = LIKE },
+ customFilterValue
+) =>
+ data.filter((row) => {
+ let cell = _.get(row, dataField);
+ if (customFilterValue) {
+ cell = customFilterValue(cell, row);
+ }
+ const cellStr = _.isDefined(cell) ? cell.toString() : '';
+ if (comparator === EQ) {
+ return cellStr === filterVal;
+ }
+ return cellStr.indexOf(filterVal) > -1;
+ });
+
+export const filterFactory = _ => (filterType) => {
+ let filterFn;
+ switch (filterType) {
+ case FILTER_TYPE.TEXT:
+ filterFn = filterByText(_);
+ break;
+ default:
+ filterFn = filterByText(_);
+ }
+ return filterFn;
+};
+
+export const filters = (store, columns, _) => (currFilters) => {
+ const factory = filterFactory(_);
+ let result = store.getAllData();
+ let filterFn;
+ Object.keys(currFilters).forEach((dataField) => {
+ const filterObj = currFilters[dataField];
+ filterFn = factory(filterObj.filterType);
+ const { filterValue } = columns.find(col => col.dataField === dataField);
+ result = filterFn(result, dataField, filterObj, filterValue);
+ });
+ return result;
+};
diff --git a/packages/react-bootstrap-table2-filter/src/index.js b/packages/react-bootstrap-table2-filter/src/index.js
new file mode 100644
index 0000000..4bba49d
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/src/index.js
@@ -0,0 +1,15 @@
+import TextFilter from './components/text';
+import FilterWrapper from './wrapper';
+import * as Comparison from './comparison';
+
+export default (options = {}) => ({
+ FilterWrapper,
+ options
+});
+
+export const Comparator = Comparison;
+
+export const textFilter = (props = {}) => ({
+ Filter: TextFilter,
+ props
+});
diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js
new file mode 100644
index 0000000..0cd8f44
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/src/wrapper.js
@@ -0,0 +1,49 @@
+import { Component } from 'react';
+import PropTypes from 'prop-types';
+import { filters } from './filter';
+
+export default class FilterWrapper extends Component {
+ static propTypes = {
+ store: PropTypes.object.isRequired,
+ columns: PropTypes.array.isRequired,
+ baseElement: PropTypes.func.isRequired,
+ _: PropTypes.object.isRequired
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = { currFilters: {}, isDataChanged: false };
+ this.onFilter = this.onFilter.bind(this);
+ }
+
+ componentWillReceiveProps() {
+ this.setState(() => ({ isDataChanged: false }));
+ }
+
+ onFilter(column, filterVal, filterType) {
+ const { store, columns, _ } = this.props;
+ const { currFilters } = this.state;
+ const { dataField, filter } = column;
+
+ if (!_.isDefined(filterVal) || filterVal === '') {
+ delete currFilters[dataField];
+ } else {
+ const { comparator } = filter.props;
+ currFilters[dataField] = { filterVal, filterType, comparator };
+ }
+
+ store.filteredData = filters(store, columns, _)(currFilters);
+ store.filtering = Object.keys(currFilters).length > 0;
+
+ this.setState(() => ({ currFilters, isDataChanged: true }));
+ }
+
+ render() {
+ return this.props.baseElement({
+ ...this.props,
+ key: 'table',
+ onFilter: this.onFilter,
+ isDataChanged: this.state.isDataChanged
+ });
+ }
+}
diff --git a/packages/react-bootstrap-table2-filter/test/components/text.test.js b/packages/react-bootstrap-table2-filter/test/components/text.test.js
new file mode 100644
index 0000000..4a9c282
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/test/components/text.test.js
@@ -0,0 +1,190 @@
+import 'jsdom-global/register';
+import React from 'react';
+import sinon from 'sinon';
+import { mount } from 'enzyme';
+import TextFilter from '../../src/components/text';
+import { FILTER_TYPE } from '../../src/const';
+
+jest.useFakeTimers();
+describe('Text Filter', () => {
+ let wrapper;
+ let instance;
+ const onFilter = sinon.stub();
+ const column = {
+ dataField: 'price',
+ text: 'Price'
+ };
+
+ afterEach(() => {
+ onFilter.reset();
+ });
+
+ describe('initialization', () => {
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ });
+
+ it('should have correct state', () => {
+ expect(instance.state.value).toEqual(instance.props.defaultValue);
+ });
+
+ it('should rendering component successfully', () => {
+ expect(wrapper).toHaveLength(1);
+ expect(wrapper.find('input[type="text"]')).toHaveLength(1);
+ expect(instance.input.getAttribute('placeholder')).toEqual(`Enter ${column.text}...`);
+ });
+ });
+
+ describe('when defaultValue is defined', () => {
+ const defaultValue = '123';
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ });
+
+ it('should have correct state', () => {
+ expect(instance.state.value).toEqual(defaultValue);
+ });
+
+ it('should rendering component successfully', () => {
+ expect(wrapper).toHaveLength(1);
+ expect(instance.input.value).toEqual(defaultValue);
+ });
+
+ it('should calling onFilter on componentDidMount', () => {
+ expect(onFilter.calledOnce).toBeTruthy();
+ expect(onFilter.calledWith(column, defaultValue, FILTER_TYPE.TEXT)).toBeTruthy();
+ });
+ });
+
+ describe('when placeholder is defined', () => {
+ const placeholder = 'test';
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ });
+
+ it('should rendering component successfully', () => {
+ expect(wrapper).toHaveLength(1);
+ expect(instance.input.getAttribute('placeholder')).toEqual(placeholder);
+ });
+ });
+
+ describe('when style is defined', () => {
+ const style = { backgroundColor: 'red' };
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ });
+
+ it('should rendering component successfully', () => {
+ expect(wrapper).toHaveLength(1);
+ expect(wrapper.find('input').prop('style')).toEqual(style);
+ });
+ });
+
+ describe('componentWillReceiveProps', () => {
+ const nextDefaultValue = 'tester';
+ const nextProps = {
+ onFilter,
+ column,
+ defaultValue: nextDefaultValue
+ };
+
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ instance.componentWillReceiveProps(nextProps);
+ });
+
+ it('should setting state correctly when props.defaultValue is changed', () => {
+ expect(instance.state.value).toEqual(nextDefaultValue);
+ });
+
+ it('should calling onFilter correctly when props.defaultValue is changed', () => {
+ expect(onFilter.calledOnce).toBeTruthy();
+ expect(onFilter.calledWith(column, nextDefaultValue, FILTER_TYPE.TEXT)).toBeTruthy();
+ });
+ });
+
+ describe('cleanFiltered', () => {
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ instance.cleanFiltered();
+ });
+
+ it('should setting state correctly', () => {
+ expect(instance.state.value).toEqual(instance.props.defaultValue);
+ });
+
+ it('should calling onFilter correctly', () => {
+ expect(onFilter.calledOnce).toBeTruthy();
+ expect(onFilter.calledWith(
+ column, instance.props.defaultValue, FILTER_TYPE.TEXT)).toBeTruthy();
+ });
+ });
+
+ describe('applyFilter', () => {
+ const filterText = 'test';
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ instance.applyFilter(filterText);
+ });
+
+ it('should setting state correctly', () => {
+ expect(instance.state.value).toEqual(filterText);
+ });
+
+ it('should calling onFilter correctly', () => {
+ expect(onFilter.calledOnce).toBeTruthy();
+ expect(onFilter.calledWith(column, filterText, FILTER_TYPE.TEXT)).toBeTruthy();
+ });
+ });
+
+ describe('filter', () => {
+ const event = { stopPropagation: sinon.stub(), target: { value: 'tester' } };
+
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ instance.filter(event);
+ });
+
+ afterEach(() => {
+ setTimeout.mockClear();
+ });
+
+ it('should calling e.stopPropagation', () => {
+ expect(event.stopPropagation.calledOnce).toBeTruthy();
+ });
+
+ it('should setting state correctly', () => {
+ expect(instance.state.value).toEqual(event.target.value);
+ });
+
+ it('should calling setTimeout correctly', () => {
+ expect(setTimeout.mock.calls).toHaveLength(1);
+ expect(setTimeout.mock.calls[0]).toHaveLength(2);
+ expect(setTimeout.mock.calls[0][1]).toEqual(instance.props.delay);
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2-filter/test/filter.test.js b/packages/react-bootstrap-table2-filter/test/filter.test.js
new file mode 100644
index 0000000..508c25f
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/test/filter.test.js
@@ -0,0 +1,94 @@
+import sinon from 'sinon';
+import _ from 'react-bootstrap-table2/src/utils';
+import Store from 'react-bootstrap-table2/src/store';
+
+import { filters } from '../src/filter';
+import { FILTER_TYPE } from '../src/const';
+import { LIKE, EQ } from '../src/comparison';
+
+const data = [];
+for (let i = 0; i < 20; i += 1) {
+ data.push({
+ id: i,
+ name: `itme name ${i}`,
+ price: 200 + i
+ });
+}
+
+describe('filter', () => {
+ let store;
+ let filterFn;
+ let currFilters;
+ let columns;
+
+ beforeEach(() => {
+ store = new Store('id');
+ store.data = data;
+ currFilters = {};
+ columns = [{
+ dataField: 'id',
+ text: 'ID'
+ }, {
+ dataField: 'name',
+ text: 'Name'
+ }, {
+ dataField: 'price',
+ text: 'Price'
+ }];
+ });
+
+ describe('text filter', () => {
+ beforeEach(() => {
+ filterFn = filters(store, columns, _);
+ });
+
+ describe(`when default comparator is ${LIKE}`, () => {
+ it('should returning correct result', () => {
+ currFilters.name = {
+ filterVal: '3',
+ filterType: FILTER_TYPE.TEXT
+ };
+
+ const result = filterFn(currFilters);
+ expect(result).toBeDefined();
+ expect(result).toHaveLength(2);
+ });
+ });
+
+ describe(`when default comparator is ${EQ}`, () => {
+ it('should returning correct result', () => {
+ currFilters.name = {
+ filterVal: 'itme name 3',
+ filterType: FILTER_TYPE.TEXT,
+ comparator: EQ
+ };
+
+ const result = filterFn(currFilters);
+ expect(result).toBeDefined();
+ expect(result).toHaveLength(1);
+ });
+ });
+
+ describe('column.filterValue is defined', () => {
+ beforeEach(() => {
+ columns[1].filterValue = sinon.stub();
+ filterFn = filters(store, columns, _);
+ });
+
+ it('should calling custom filterValue callback correctly', () => {
+ currFilters.name = {
+ filterVal: '3',
+ filterType: FILTER_TYPE.TEXT
+ };
+
+ const result = filterFn(currFilters);
+ expect(result).toBeDefined();
+ expect(columns[1].filterValue.callCount).toBe(data.length);
+ const calls = columns[1].filterValue.getCalls();
+ calls.forEach((call, i) => {
+ expect(call.calledWith(data[i].name, data[i])).toBeTruthy();
+ });
+ });
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2-filter/test/wrapper.test.js b/packages/react-bootstrap-table2-filter/test/wrapper.test.js
new file mode 100644
index 0000000..c877c62
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/test/wrapper.test.js
@@ -0,0 +1,168 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import _ from 'react-bootstrap-table2/src/utils';
+import BootstrapTable from 'react-bootstrap-table2/src/bootstrap-table';
+import Store from 'react-bootstrap-table2/src/store';
+import filter, { textFilter } from '../src';
+import FilterWrapper from '../src/wrapper';
+import { FILTER_TYPE } from '../src/const';
+
+const data = [];
+for (let i = 0; i < 20; i += 1) {
+ data.push({
+ id: i,
+ name: `itme name ${i}`,
+ price: 200 + i
+ });
+}
+
+describe('Wrapper', () => {
+ let wrapper;
+ let instance;
+
+
+ const createTableProps = () => {
+ const tableProps = {
+ keyField: 'id',
+ columns: [{
+ dataField: 'id',
+ text: 'ID'
+ }, {
+ dataField: 'name',
+ text: 'Name',
+ filter: textFilter()
+ }, {
+ dataField: 'price',
+ text: 'Price',
+ filter: textFilter()
+ }],
+ data,
+ filter: filter(),
+ _,
+ store: new Store('id')
+ };
+ tableProps.store.data = data;
+ return tableProps;
+ };
+
+ const pureTable = props => ();
+
+ const createFilterWrapper = (props, renderFragment = true) => {
+ wrapper = shallow();
+ instance = wrapper.instance();
+ if (renderFragment) {
+ const fragment = instance.render();
+ wrapper = shallow({ fragment }
);
+ }
+ };
+
+ describe('default filter wrapper', () => {
+ const props = createTableProps();
+
+ beforeEach(() => {
+ createFilterWrapper(props);
+ });
+
+ it('should rendering correctly', () => {
+ expect(wrapper.length).toBe(1);
+ });
+
+ it('should initializing state correctly', () => {
+ expect(instance.state.isDataChanged).toBeFalsy();
+ expect(instance.state.currFilters).toEqual({});
+ });
+
+ it('should rendering BootstraTable correctly', () => {
+ const table = wrapper.find(BootstrapTable);
+ expect(table.length).toBe(1);
+ expect(table.prop('onFilter')).toBeDefined();
+ expect(table.prop('isDataChanged')).toEqual(instance.state.isDataChanged);
+ });
+ });
+
+ describe('componentWillReceiveProps', () => {
+ let nextProps;
+
+ beforeEach(() => {
+ nextProps = createTableProps();
+ instance.componentWillReceiveProps(nextProps);
+ });
+
+ it('should setting isDataChanged as false always(Temporary solution)', () => {
+ expect(instance.state.isDataChanged).toBeFalsy();
+ });
+ });
+
+ describe('onFilter', () => {
+ let props;
+
+ beforeEach(() => {
+ props = createTableProps();
+ createFilterWrapper(props);
+ });
+
+ describe('when filterVal is empty or undefined', () => {
+ const filterVals = ['', undefined];
+
+ it('should setting store object correctly', () => {
+ filterVals.forEach((filterVal) => {
+ instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT);
+ expect(props.store.filtering).toBeFalsy();
+ });
+ });
+
+ it('should setting state correctly', () => {
+ filterVals.forEach((filterVal) => {
+ instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT);
+ expect(instance.state.isDataChanged).toBeTruthy();
+ expect(Object.keys(instance.state.currFilters)).toHaveLength(0);
+ });
+ });
+ });
+
+ describe('when filterVal is existing', () => {
+ const filterVal = '3';
+
+ it('should setting store object correctly', () => {
+ instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT);
+ expect(props.store.filtering).toBeTruthy();
+ });
+
+ it('should setting state correctly', () => {
+ instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT);
+ expect(instance.state.isDataChanged).toBeTruthy();
+ expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
+ });
+ });
+
+ describe('combination', () => {
+ it('should setting store object correctly', () => {
+ instance.onFilter(props.columns[1], '3', FILTER_TYPE.TEXT);
+ expect(props.store.filtering).toBeTruthy();
+ 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(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(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(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(instance.state.isDataChanged).toBeTruthy();
+ expect(Object.keys(instance.state.currFilters)).toHaveLength(0);
+ });
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2-paginator/src/page-resolver.js b/packages/react-bootstrap-table2-paginator/src/page-resolver.js
index ef2e6d2..931595f 100644
--- a/packages/react-bootstrap-table2-paginator/src/page-resolver.js
+++ b/packages/react-bootstrap-table2-paginator/src/page-resolver.js
@@ -18,8 +18,7 @@ export default ExtendBase =>
return { totalPages, lastPage, dropdownOpen: false };
}
- calculateTotalPage(sizePerPage = this.props.currSizePerPage) {
- const { dataSize } = this.props;
+ calculateTotalPage(sizePerPage = this.props.currSizePerPage, dataSize = this.props.dataSize) {
return Math.ceil(dataSize / sizePerPage);
}
diff --git a/packages/react-bootstrap-table2-paginator/src/page.js b/packages/react-bootstrap-table2-paginator/src/page.js
index c18ede9..1ea54e5 100644
--- a/packages/react-bootstrap-table2-paginator/src/page.js
+++ b/packages/react-bootstrap-table2-paginator/src/page.js
@@ -1,11 +1,12 @@
export const getByCurrPage = store => (page, sizePerPage, pageStartIndex) => {
+ const dataSize = store.data.length;
+ if (!dataSize) return [];
const getNormalizedPage = () => {
const offset = Math.abs(1 - pageStartIndex);
return page + offset;
};
const end = (getNormalizedPage() * sizePerPage) - 1;
const start = end - (sizePerPage - 1);
- const dataSize = store.data.length;
const result = [];
for (let i = start; i <= end; i += 1) {
diff --git a/packages/react-bootstrap-table2-paginator/src/pagination.js b/packages/react-bootstrap-table2-paginator/src/pagination.js
index ee685c7..d103e4b 100644
--- a/packages/react-bootstrap-table2-paginator/src/pagination.js
+++ b/packages/react-bootstrap-table2-paginator/src/pagination.js
@@ -20,9 +20,8 @@ class Pagination extends pageResolver(Component) {
componentWillReceiveProps(nextProps) {
const { dataSize, currSizePerPage } = nextProps;
-
if (currSizePerPage !== this.props.currSizePerPage || dataSize !== this.props.dataSize) {
- const totalPages = this.calculateTotalPage(currSizePerPage);
+ const totalPages = this.calculateTotalPage(currSizePerPage, dataSize);
const lastPage = this.calculateLastPage(totalPages);
this.setState({ totalPages, lastPage });
}
diff --git a/packages/react-bootstrap-table2-paginator/src/wrapper.js b/packages/react-bootstrap-table2-paginator/src/wrapper.js
index 94995cd..f54a8d9 100644
--- a/packages/react-bootstrap-table2-paginator/src/wrapper.js
+++ b/packages/react-bootstrap-table2-paginator/src/wrapper.js
@@ -48,11 +48,16 @@ class PaginationWrapper extends Component {
componentWillReceiveProps(nextProps) {
let needNewState = false;
let { currPage, currSizePerPage } = this.state;
- const { page, sizePerPage } = nextProps.pagination.options;
- if (typeof page !== 'undefined') {
+ const { page, sizePerPage, pageStartIndex } = nextProps.pagination.options;
+
+ if (typeof page !== 'undefined') { // user defined page
currPage = page;
needNewState = true;
+ } else if (nextProps.isDataChanged) { // user didn't defined page but data change
+ currPage = typeof pageStartIndex !== 'undefined' ? pageStartIndex : Const.PAGE_START_INDEX;
+ needNewState = true;
}
+
if (typeof sizePerPage !== 'undefined') {
currSizePerPage = sizePerPage;
needNewState = true;
diff --git a/packages/react-bootstrap-table2-paginator/test/page.test.js b/packages/react-bootstrap-table2-paginator/test/page.test.js
index e8ccafa..f073c86 100644
--- a/packages/react-bootstrap-table2-paginator/test/page.test.js
+++ b/packages/react-bootstrap-table2-paginator/test/page.test.js
@@ -4,6 +4,18 @@ import { getByCurrPage } from '../src/page';
describe('Page Functions', () => {
let data;
let store;
+ const params = [
+ // [page, sizePerPage, pageStartIndex]
+ [1, 10, 1],
+ [1, 25, 1],
+ [1, 30, 1],
+ [3, 30, 1],
+ [4, 30, 1],
+ [10, 10, 1],
+ [0, 10, 0],
+ [1, 10, 0],
+ [9, 10, 0]
+ ];
describe('getByCurrPage', () => {
beforeEach(() => {
@@ -16,23 +28,20 @@ describe('Page Functions', () => {
});
it('should always return correct data', () => {
- [
- // [page, sizePerPage, pageStartIndex]
- [1, 10, 1],
- [1, 25, 1],
- [1, 30, 1],
- [3, 30, 1],
- [4, 30, 1],
- [10, 10, 1],
- [0, 10, 0],
- [1, 10, 0],
- [9, 10, 0]
- ].forEach(([page, sizePerPage, pageStartIndex]) => {
+ params.forEach(([page, sizePerPage, pageStartIndex]) => {
const rows = getByCurrPage(store)(page, sizePerPage, pageStartIndex);
expect(rows).toBeDefined();
expect(Array.isArray(rows)).toBeTruthy();
expect(rows.every(row => !!row)).toBeTruthy();
});
});
+
+ it('should return empty array when store.data is empty', () => {
+ store.data = [];
+ params.forEach(([page, sizePerPage, pageStartIndex]) => {
+ const rows = getByCurrPage(store)(page, sizePerPage, pageStartIndex);
+ expect(rows).toHaveLength(0);
+ });
+ });
});
});
diff --git a/packages/react-bootstrap-table2-paginator/test/pagination.test.js b/packages/react-bootstrap-table2-paginator/test/pagination.test.js
index cf8e44d..a9e5d35 100644
--- a/packages/react-bootstrap-table2-paginator/test/pagination.test.js
+++ b/packages/react-bootstrap-table2-paginator/test/pagination.test.js
@@ -143,12 +143,13 @@ describe('Pagination', () => {
it('should setting correct state.totalPages', () => {
instance.componentWillReceiveProps(nextProps);
expect(instance.state.totalPages).toEqual(
- instance.calculateTotalPage(nextProps.currSizePerPage));
+ instance.calculateTotalPage(nextProps.currSizePerPage, nextProps.dataSize));
});
it('should setting correct state.lastPage', () => {
instance.componentWillReceiveProps(nextProps);
- const totalPages = instance.calculateTotalPage(nextProps.currSizePerPage);
+ const totalPages = instance.calculateTotalPage(
+ nextProps.currSizePerPage, nextProps.dataSize);
expect(instance.state.lastPage).toEqual(
instance.calculateLastPage(totalPages));
});
diff --git a/packages/react-bootstrap-table2-paginator/test/wrapper.test.js b/packages/react-bootstrap-table2-paginator/test/wrapper.test.js
index 16852d0..7254e2a 100644
--- a/packages/react-bootstrap-table2-paginator/test/wrapper.test.js
+++ b/packages/react-bootstrap-table2-paginator/test/wrapper.test.js
@@ -100,29 +100,47 @@ describe('Wrapper', () => {
});
describe('componentWillReceiveProps', () => {
- it('should setting currPage state correclt by options.page', () => {
- props.pagination.options.page = 2;
- instance.componentWillReceiveProps(props);
- expect(instance.state.currPage).toEqual(props.pagination.options.page);
+ let nextProps;
+ beforeEach(() => {
+ 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);
});
it('should not setting currPage state if options.page not existing', () => {
const { currPage } = instance.state;
- instance.componentWillReceiveProps(props);
+ instance.componentWillReceiveProps(nextProps);
expect(instance.state.currPage).toBe(currPage);
});
- it('should setting currSizePerPage state correclt by options.sizePerPage', () => {
- props.pagination.options.sizePerPage = 20;
- instance.componentWillReceiveProps(props);
- expect(instance.state.currSizePerPage).toEqual(props.pagination.options.sizePerPage);
+ 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);
});
it('should not setting currSizePerPage state if options.sizePerPage not existing', () => {
const { currSizePerPage } = instance.state;
- instance.componentWillReceiveProps(props);
+ instance.componentWillReceiveProps(nextProps);
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);
+ });
+
+ 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);
+ });
});
});
diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js
index ca7000c..19e7afa 100644
--- a/packages/react-bootstrap-table2/src/bootstrap-table.js
+++ b/packages/react-bootstrap-table2/src/bootstrap-table.js
@@ -86,6 +86,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
sortField={ store.sortField }
sortOrder={ store.sortOrder }
onSort={ this.props.onSort }
+ onFilter={ this.props.onFilter }
selectRow={ headerCellSelectionInfo }
/>
});
} else if (this.props.selectRow) {
return wrapWithSelection(baseProps);
+ } else if (this.props.filter) {
+ return wrapWithFilter(baseProps);
} else if (this.props.columns.filter(col => col.sort).length > 0) {
return wrapWithSort(baseProps);
} else if (this.props.pagination) {
diff --git a/packages/react-bootstrap-table2/src/header-cell.js b/packages/react-bootstrap-table2/src/header-cell.js
index fb57ade..80f819c 100644
--- a/packages/react-bootstrap-table2/src/header-cell.js
+++ b/packages/react-bootstrap-table2/src/header-cell.js
@@ -16,12 +16,14 @@ const HeaderCell = (props) => {
onSort,
sorting,
sortOrder,
- isLastSorting
+ isLastSorting,
+ onFilter
} = props;
const {
text,
sort,
+ filter,
hidden,
headerTitle,
headerAlign,
@@ -38,10 +40,13 @@ const HeaderCell = (props) => {
..._.isFunction(headerAttrs) ? headerAttrs(column, index) : headerAttrs,
...headerEvents
};
+ // we are suppose to pass sortSymbol and filerElm
+ // the headerFormatter is not only header text but also the all of header cell customization
const children = headerFormatter ? headerFormatter(column, index) : text;
- let cellStyle = {};
let sortSymbol;
+ let filterElm;
+ let cellStyle = {};
let cellClasses = _.isFunction(headerClasses) ? headerClasses(column, index) : headerClasses;
if (headerStyle) {
@@ -91,12 +96,14 @@ const HeaderCell = (props) => {
}
if (cellClasses) cellAttrs.className = cs(cellAttrs.className, cellClasses);
-
if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
+ if (filter) {
+ filterElm = ;
+ }
return (
- { children }{ sortSymbol }
+ { children }{ sortSymbol }{ filterElm }
|
);
};
@@ -126,13 +133,16 @@ HeaderCell.propTypes = {
editable: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
editCellStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
editCellClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
- validator: PropTypes.func
+ validator: PropTypes.func,
+ filter: PropTypes.object,
+ filterValue: PropTypes.func
}).isRequired,
index: PropTypes.number.isRequired,
onSort: PropTypes.func,
sorting: PropTypes.bool,
sortOrder: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC]),
- isLastSorting: PropTypes.bool
+ isLastSorting: PropTypes.bool,
+ onFilter: PropTypes.func
};
export default HeaderCell;
diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js
index da77cb9..ddbc7bf 100644
--- a/packages/react-bootstrap-table2/src/header.js
+++ b/packages/react-bootstrap-table2/src/header.js
@@ -12,6 +12,7 @@ const Header = (props) => {
const {
columns,
onSort,
+ onFilter,
sortField,
sortOrder,
selectRow
@@ -36,6 +37,7 @@ const Header = (props) => {
column={ column }
onSort={ onSort }
sorting={ currSort }
+ onFilter={ onFilter }
sortOrder={ sortOrder }
isLastSorting={ isLastSorting }
/>);
@@ -49,6 +51,7 @@ const Header = (props) => {
Header.propTypes = {
columns: PropTypes.array.isRequired,
onSort: PropTypes.func,
+ onFilter: PropTypes.func,
sortField: PropTypes.string,
sortOrder: PropTypes.string,
selectRow: PropTypes.object
diff --git a/packages/react-bootstrap-table2/src/sort/wrapper.js b/packages/react-bootstrap-table2/src/sort/wrapper.js
index fcb644c..82f00b2 100644
--- a/packages/react-bootstrap-table2/src/sort/wrapper.js
+++ b/packages/react-bootstrap-table2/src/sort/wrapper.js
@@ -26,6 +26,16 @@ class SortWrapper extends Component {
}
}
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.isDataChanged) {
+ const sortedColumn = nextProps.columns.find(
+ column => column.dataField === nextProps.store.sortField);
+ if (sortedColumn) {
+ nextProps.store.sortBy(sortedColumn, nextProps.store.sortOrder);
+ }
+ }
+ }
+
handleSort(column) {
const { store } = this.props;
store.sortBy(column);
diff --git a/packages/react-bootstrap-table2/src/store/index.js b/packages/react-bootstrap-table2/src/store/index.js
index c39e47d..97d1aba 100644
--- a/packages/react-bootstrap-table2/src/store/index.js
+++ b/packages/react-bootstrap-table2/src/store/index.js
@@ -6,11 +6,12 @@ import { getRowByRowId } from './rows';
export default class Store {
constructor(keyField) {
this._data = [];
+ this._filteredData = [];
this._keyField = keyField;
-
this._sortOrder = undefined;
this._sortField = undefined;
this._selected = [];
+ this._filtering = false;
}
edit(rowId, dataField, newValue) {
@@ -24,8 +25,26 @@ export default class Store {
this.data = sort(this)(sortFunc);
}
- get data() { return this._data; }
- set data(data) { this._data = (data ? JSON.parse(JSON.stringify(data)) : []); }
+ getAllData() {
+ return this._data;
+ }
+
+ get data() {
+ if (this._filtering) {
+ return this._filteredData;
+ }
+ return this._data;
+ }
+ set data(data) {
+ if (this._filtering) {
+ this._filteredData = data;
+ } else {
+ this._data = (data ? JSON.parse(JSON.stringify(data)) : []);
+ }
+ }
+
+ get filteredData() { return this._filteredData; }
+ set filteredData(filteredData) { this._filteredData = filteredData; }
get keyField() { return this._keyField; }
set keyField(keyField) { this._keyField = keyField; }
@@ -38,4 +57,7 @@ export default class Store {
get selected() { return this._selected; }
set selected(selected) { this._selected = selected; }
+
+ get filtering() { return this._filtering; }
+ set filtering(filtering) { this._filtering = filtering; }
}
diff --git a/packages/react-bootstrap-table2/src/table-factory.js b/packages/react-bootstrap-table2/src/table-factory.js
index d90453d..10ff86e 100644
--- a/packages/react-bootstrap-table2/src/table-factory.js
+++ b/packages/react-bootstrap-table2/src/table-factory.js
@@ -1,6 +1,7 @@
/* eslint react/prop-types: 0 */
import React from 'react';
+import _ from './utils';
import BootstrapTable from './bootstrap-table';
import SortWrapper from './sort/wrapper';
import RowSelectionWrapper from './row-selection/wrapper';
@@ -19,6 +20,14 @@ export const wrapWithSort = props =>
export const pureTable = props =>
React.createElement(BootstrapTable, { ...props });
+export const wrapWithFilter = (props) => {
+ if (props.filter) {
+ const { FilterWrapper } = props.filter;
+ return React.createElement(FilterWrapper, { ...props, baseElement: wrapWithSort, _ });
+ }
+ return wrapWithSort(props);
+};
+
export const wrapWithPagination = (props) => {
if (props.pagination) {
const { PaginationWrapper } = props.pagination;
@@ -29,6 +38,6 @@ export const wrapWithPagination = (props) => {
export const sortableElement = props => wrapWithPagination(props);
-export const selectionElement = props => wrapWithSort(props);
+export const selectionElement = props => wrapWithFilter(props);
export const cellEditElement = props => wrapWithSelection(props);
diff --git a/packages/react-bootstrap-table2/test/sort/wrapper.test.js b/packages/react-bootstrap-table2/test/sort/wrapper.test.js
index 6618192..733f67d 100644
--- a/packages/react-bootstrap-table2/test/sort/wrapper.test.js
+++ b/packages/react-bootstrap-table2/test/sort/wrapper.test.js
@@ -1,5 +1,6 @@
import 'jsdom-global/register';
import React from 'react';
+import sinon from 'sinon';
import { shallow, mount } from 'enzyme';
import Const from '../../src/const';
@@ -114,4 +115,38 @@ describe('SortWrapper', () => {
expect(store.sortOrder).toEqual(defaultSorted[0].order);
});
});
+
+ describe('componentWillReceiveProps', () => {
+ let nextProps;
+
+ beforeEach(() => {
+ nextProps = { columns, store };
+ store.sortField = columns[1].dataField;
+ store.sortOrder = Const.SORT_DESC;
+ });
+
+ describe('if nextProps.isDataChanged is true', () => {
+ beforeEach(() => {
+ nextProps.isDataChanged = true;
+ store.sortBy = sinon.stub();
+ });
+
+ it('should sorting again', () => {
+ wrapper.instance().componentWillReceiveProps(nextProps);
+ expect(store.sortBy.calledOnce).toBeTruthy();
+ });
+ });
+
+ describe('if nextProps.isDataChanged is false', () => {
+ beforeEach(() => {
+ nextProps.isDataChanged = false;
+ store.sortBy = sinon.stub();
+ });
+
+ it('should not sorting', () => {
+ wrapper.instance().componentWillReceiveProps(nextProps);
+ expect(store.sortBy.calledOnce).toBeFalsy();
+ });
+ });
+ });
});