diff --git a/docs/migration.md b/docs/migration.md index 60edbd2..32d137d 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -88,7 +88,7 @@ Please see [available filter configuration](https://react-bootstrap-table.github - [x] Custom Select Filter - [X] Number Filter - [X] Date Filter -- [ ] Array Filter +- [X] Array Filter - [X] Programmatically Filter Remember to install [`react-bootstrap-table2-filter`](https://www.npmjs.com/package/react-bootstrap-table2-filter) firstly. diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-multi-select-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-multi-select-filter.js new file mode 100644 index 0000000..0f90ac9 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-multi-select-filter.js @@ -0,0 +1,80 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsQualityGenerator } from 'utils/common'; + +const products = productsQualityGenerator(6); + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quailty', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + withoutEmptyOption: true, + style: { + backgroundColor: 'pink' + }, + className: 'test-classname', + datamycustomattr: 'datamycustomattr' + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quailty', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + withoutEmptyOption: true, + style: { + backgroundColor: 'pink' + }, + className: 'test-classname', + datamycustomattr: 'datamycustomattr' + }) +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter-default-value.js b/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter-default-value.js index 2e2b0e9..d2b4e19 100644 --- a/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter-default-value.js +++ b/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter-default-value.js @@ -30,7 +30,7 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; -import filterFactory, { selectFilter } from 'react-bootstrap-table2-filter'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; const selectOptions = { 0: 'good', @@ -48,7 +48,7 @@ const columns = [{ dataField: 'quality', text: 'Product Quailty', formatter: cell => selectOptions[cell], - filter: selectFilter({ + filter: multiSelectFilter({ options: selectOptions, defaultValue: [0, 2] }) diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter.js index ac67a69..dce63cd 100644 --- a/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter.js +++ b/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter.js @@ -29,7 +29,7 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; -import filterFactory, { selectFilter } from 'react-bootstrap-table2-filter'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; const selectOptions = { 0: 'good', @@ -47,7 +47,7 @@ const columns = [{ dataField: 'quality', text: 'Product Quailty', formatter: cell => selectOptions[cell], - filter: selectFilter({ + filter: multiSelectFilter({ options: selectOptions }) }]; diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-multi-select-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-multi-select-filter.js new file mode 100644 index 0000000..e6c20e3 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-multi-select-filter.js @@ -0,0 +1,95 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsQualityGenerator } from 'utils/common'; + +const products = productsQualityGenerator(6); + +let qualityFilter; + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quality', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + getFilter: (filter) => { + // qualityFilter was assigned once the component has been mounted. + qualityFilter = filter; + } + }) +}]; + +const handleClick = () => { + qualityFilter([0, 2]); +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; + +let qualityFilter; + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quality', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + getFilter: (filter) => { + // qualityFilter was assigned once the component has been mounted. + qualityFilter = filter; + } + }) +}]; + +const handleClick = () => { + qualityFilter([0, 2]); +}; + +export default () => ( +
+ + +
+); +`; + +export default () => ( +
+ + + + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index c48dbe9..0617922 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -45,9 +45,10 @@ import CustomFilterValue from 'examples/column-filter/custom-filter-value'; import SelectFilter from 'examples/column-filter/select-filter'; import SelectFilterWithDefaultValue from 'examples/column-filter/select-filter-default-value'; import SelectFilterComparator from 'examples/column-filter/select-filter-like-comparator'; +import CustomSelectFilter from 'examples/column-filter/custom-select-filter'; import MultiSelectFilter from 'examples/column-filter/multi-select-filter'; import MultiSelectFilterDefaultValue from 'examples/column-filter/multi-select-filter-default-value'; -import CustomSelectFilter from 'examples/column-filter/custom-select-filter'; +import CustomMultiSelectFilter from 'examples/column-filter/custom-multi-select-filter'; import NumberFilter from 'examples/column-filter/number-filter'; import NumberFilterWithDefaultValue from 'examples/column-filter/number-filter-default-value'; import CustomNumberFilter from 'examples/column-filter/custom-number-filter'; @@ -58,6 +59,7 @@ import ProgrammaticallyTextFilter from 'examples/column-filter/programmatically- import ProgrammaticallySelectFilter from 'examples/column-filter/programmatically-select-filter'; import ProgrammaticallyNumberFilter from 'examples/column-filter/programmatically-number-filter'; import ProgrammaticallyDateFilter from 'examples/column-filter/programmatically-date-filter'; +import ProgrammaticallyMultiSelectFilter from 'examples/column-filter/programmatically-multi-select-filter'; import CustomFilter from 'examples/column-filter/custom-filter'; import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter'; @@ -190,11 +192,13 @@ storiesOf('Column Filter', module) .add('Custom Select Filter', () => ) .add('Custom Number Filter', () => ) .add('Custom Date Filter', () => ) + .add('Custom MultiSelect Filter', () => ) .add('Custom Filter Value', () => ) .add('Programmatically Text Filter', () => ) .add('Programmatically Select Filter', () => ) .add('Programmatically Number Filter', () => ) .add('Programmatically Date Filter', () => ) + .add('Programmatically Multi Select Filter', () => ) .add('Custom Filter', () => ) .add('Advance Custom Filter', () => ); diff --git a/packages/react-bootstrap-table2-filter/README.md b/packages/react-bootstrap-table2-filter/README.md index 6cf47a0..38c278e 100644 --- a/packages/react-bootstrap-table2-filter/README.md +++ b/packages/react-bootstrap-table2-filter/README.md @@ -18,6 +18,7 @@ You can get all types of filters via import and these filters are a factory func * TextFilter * SelectFilter +* MultiSelectFilter * NumberFilter * DateFilter * CustomFilter @@ -114,6 +115,52 @@ const qualityFilter = selectFilter({ // omit... ``` +## MultiSelect Filter + +A quick example: + +```js +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; + +// omit... +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [ + ..., { + dataField: 'quality', + text: 'Product Quailty', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions + }) +}]; + + +``` + +Following is an example for custom select filter: + +```js +import filterFactory, { multiSelectFilter, Comparator } from 'react-bootstrap-table2-filter'; +// omit... + +const qualityFilter = multiSelectFilter({ + options: selectOptions, + placeholder: 'My Custom PlaceHolder', // custom the input placeholder + className: 'my-custom-text-filter', // custom classname on input + defaultValue: '2', // default filtering value + comparator: Comparator.LIKE, // default is Comparator.EQ + style: { ... }, // your custom styles on input + withoutEmptyOption: true // hide the default select option +}); + +// omit... +``` + ## Number Filter ```js diff --git a/packages/react-bootstrap-table2-filter/src/components/multiselect.js b/packages/react-bootstrap-table2-filter/src/components/multiselect.js index 9630bbd..5f4623b 100644 --- a/packages/react-bootstrap-table2-filter/src/components/multiselect.js +++ b/packages/react-bootstrap-table2-filter/src/components/multiselect.js @@ -1,5 +1,6 @@ /* eslint react/require-default-props: 0 */ /* eslint no-return-assign: 0 */ +/* eslint no-param-reassign: 0 */ /* eslint react/no-unused-prop-types: 0 */ import React, { Component } from 'react'; import PropTypes from 'prop-types'; @@ -24,25 +25,24 @@ class MultiSelectFilter extends Component { constructor(props) { super(props); this.filter = this.filter.bind(this); + this.applyFilter = this.applyFilter.bind(this); const isSelected = props.defaultValue.map(item => props.options[item]).length > 0; this.state = { isSelected }; } componentDidMount() { - const { column, onFilter, getFilter } = this.props; + const { getFilter } = this.props; const value = getSelections(this.selectInput); if (value && value.length > 0) { - onFilter(column, FILTER_TYPE.MULTISELECT)(value); + this.applyFilter(value); } // export onFilter function to allow users to access if (getFilter) { getFilter((filterVal) => { - this.setState(() => ({ isSelected: filterVal.length > 0 })); this.selectInput.value = filterVal; - - onFilter(column, FILTER_TYPE.MULTISELECT)(filterVal); + this.applyFilter(filterVal); }); } } @@ -55,10 +55,7 @@ class MultiSelectFilter extends Component { needFilter = true; } if (needFilter) { - const value = this.selectInput.value; - if (value) { - this.props.onFilter(this.props.column, FILTER_TYPE.MULTISELECT)(value); - } + this.applyFilter(this.selectInput.value); } } @@ -78,21 +75,21 @@ class MultiSelectFilter extends Component { cleanFiltered() { const value = (this.props.defaultValue !== undefined) ? this.props.defaultValue : []; - this.setState(() => ({ isSelected: value.length > 0 })); this.selectInput.value = value; - this.props.onFilter(this.props.column, FILTER_TYPE.MULTISELECT)(value); + this.applyFilter(value); } applyFilter(value) { - this.selectInput.value = value; + if (value.length === 1 && value[0] === '') { + value = []; + } this.setState(() => ({ isSelected: value.length > 0 })); this.props.onFilter(this.props.column, FILTER_TYPE.MULTISELECT)(value); } filter(e) { const value = getSelections(e.target); - this.setState(() => ({ isSelected: value.length > 0 })); - this.props.onFilter(this.props.column, FILTER_TYPE.MULTISELECT)(value); + this.applyFilter(value); } render() { diff --git a/packages/react-bootstrap-table2-filter/src/filter.js b/packages/react-bootstrap-table2-filter/src/filter.js index 73d84c4..c1a8666 100644 --- a/packages/react-bootstrap-table2-filter/src/filter.js +++ b/packages/react-bootstrap-table2-filter/src/filter.js @@ -191,17 +191,21 @@ export const filterByArray = _ => ( data, dataField, { filterVal, comparator } -) => ( - data.filter((row) => { +) => { + if (filterVal.length === 0) return data; + const refinedFilterVal = filterVal + .filter(x => _.isDefined(x)) + .map(x => x.toString()); + return data.filter((row) => { const cell = _.get(row, dataField); let cellStr = _.isDefined(cell) ? cell.toString() : ''; if (comparator === EQ) { - return filterVal.indexOf(cellStr) !== -1; + return refinedFilterVal.indexOf(cellStr) !== -1; } cellStr = cellStr.toLocaleUpperCase(); - return filterVal.some(item => cellStr.indexOf(item.toLocaleUpperCase()) !== -1); - }) -); + return refinedFilterVal.some(item => cellStr.indexOf(item.toLocaleUpperCase()) !== -1); + }); +}; export const filterFactory = _ => (filterType) => { let filterFn; diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js index 0c8fab7..0602ee8 100644 --- a/packages/react-bootstrap-table2-filter/src/wrapper.js +++ b/packages/react-bootstrap-table2-filter/src/wrapper.js @@ -53,8 +53,10 @@ export default (Base, { const currFilters = Object.assign({}, store.filters); const { dataField, filter } = column; - const needClearFilters = !_.isDefined(filterVal) || filterVal === '' || - filterVal.length === 0 || (filterVal.length === 1 && filterVal[0] === ''); + const needClearFilters = + !_.isDefined(filterVal) || + filterVal === '' || + filterVal.length === 0; if (needClearFilters) { delete currFilters[dataField]; diff --git a/packages/react-bootstrap-table2-filter/test/filter.test.js b/packages/react-bootstrap-table2-filter/test/filter.test.js index 25f629d..79d0407 100644 --- a/packages/react-bootstrap-table2-filter/test/filter.test.js +++ b/packages/react-bootstrap-table2-filter/test/filter.test.js @@ -123,6 +123,55 @@ describe('filter', () => { }); }); + describe('filterByArray', () => { + beforeEach(() => { + filterFn = filters(store, columns, _); + }); + + describe('when filter value is empty array', () => { + it('should return original data', () => { + currFilters.name = { + filterVal: [], + filterType: FILTER_TYPE.MULTISELECT + }; + + const result = filterFn(currFilters); + expect(result).toBeDefined(); + expect(result).toHaveLength(store.data.length); + }); + }); + + describe('when filter value is not an empty array', () => { + describe(`and comparator is ${EQ}`, () => { + it('should return data correctly', () => { + currFilters.price = { + filterVal: [201, 203], + filterType: FILTER_TYPE.MULTISELECT, + comparator: EQ + }; + + const result = filterFn(currFilters); + expect(result).toBeDefined(); + expect(result).toHaveLength(2); + }); + }); + + describe(`and comparator is ${LIKE}`, () => { + it('should return data correctly', () => { + currFilters.name = { + filterVal: ['name 3', '5'], + filterType: FILTER_TYPE.MULTISELECT, + comparator: LIKE + }; + + const result = filterFn(currFilters); + expect(result).toBeDefined(); + expect(result).toHaveLength(3); + }); + }); + }); + }); + describe('filterByNumber', () => { beforeEach(() => { filterFn = filters(store, columns, _); diff --git a/packages/react-bootstrap-table2-filter/test/wrapper.test.js b/packages/react-bootstrap-table2-filter/test/wrapper.test.js index 1442658..751fc2e 100644 --- a/packages/react-bootstrap-table2-filter/test/wrapper.test.js +++ b/packages/react-bootstrap-table2-filter/test/wrapper.test.js @@ -163,7 +163,7 @@ describe('Wrapper', () => { }); describe('when filterVal is empty or undefined', () => { - const filterVals = ['', undefined]; + const filterVals = ['', undefined, []]; it('should setting store object correctly', () => { filterVals.forEach((filterVal) => {