From 3f2c6201d95f738b35b3d11987c7a0394cd2857c Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 28 Jan 2018 23:12:54 +0800 Subject: [PATCH 1/6] implement select filter --- .../react-bootstrap-table2-filter/index.js | 6 + .../src/components/select.js | 132 ++++++++++++++++++ .../src/const.js | 3 +- .../src/wrapper.js | 6 +- 4 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 packages/react-bootstrap-table2-filter/src/components/select.js diff --git a/packages/react-bootstrap-table2-filter/index.js b/packages/react-bootstrap-table2-filter/index.js index f6335a8..068b508 100644 --- a/packages/react-bootstrap-table2-filter/index.js +++ b/packages/react-bootstrap-table2-filter/index.js @@ -1,4 +1,5 @@ import TextFilter from './src/components/text'; +import SelectFilter from './src/components/select'; import wrapperFactory from './src/wrapper'; import * as Comparison from './src/comparison'; @@ -13,3 +14,8 @@ export const textFilter = (props = {}) => ({ Filter: TextFilter, props }); + +export const selectFilter = (props = {}) => ({ + Filter: SelectFilter, + props +}); diff --git a/packages/react-bootstrap-table2-filter/src/components/select.js b/packages/react-bootstrap-table2-filter/src/components/select.js new file mode 100644 index 0000000..0374280 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/components/select.js @@ -0,0 +1,132 @@ +/* eslint react/require-default-props: 0 */ +/* eslint no-return-assign: 0 */ +/* eslint react/no-unused-prop-types: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { LIKE, EQ } from '../comparison'; +import { FILTER_TYPE } from '../const'; + +function optionsEquals(currOpts, prevOpts) { + const keys = Object.keys(currOpts); + for (let i = 0; i < keys.length; i += 1) { + if (currOpts[keys[i]] !== prevOpts[keys[i]]) { + return false; + } + } + return Object.keys(currOpts).length === Object.keys(prevOpts).length; +} + +class SelectFilter extends Component { + constructor(props) { + super(props); + this.filter = this.filter.bind(this); + const isSelected = props.options[props.defaultValue] !== undefined; + this.state = { isSelected }; + } + + componentDidMount() { + const value = this.selectInput.value; + if (value && value !== '') { + this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); + } + } + + componentDidUpdate(prevProps) { + let needFilter = false; + if (this.props.defaultValue !== prevProps.defaultValue) { + needFilter = true; + } else if (!optionsEquals(this.props.options, prevProps.options)) { + needFilter = true; + } + if (needFilter) { + const value = this.selectInput.value; + if (value) { + this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); + } + } + } + + getOptions() { + const optionTags = []; + const { options, placeholder, column, withoutEmptyOption } = this.props; + if (!withoutEmptyOption) { + optionTags.push(( + + )); + } + Object.keys(options).forEach(key => + optionTags.push() + ); + return optionTags; + } + + cleanFiltered() { + const value = (this.props.defaultValue !== undefined) ? this.props.defaultValue : ''; + this.setState(() => ({ isSelected: value !== '' })); + this.selectInput.value = value; + this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); + } + + applyFilter(value) { + this.selectInput.value = value; + this.setState(() => ({ isSelected: value !== '' })); + this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); + } + + filter(e) { + const { value } = e.target; + this.setState(() => ({ isSelected: value !== '' })); + this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); + } + + render() { + const { + style, + className, + defaultValue, + onFilter, + column, + options, + comparator, + withoutEmptyOption, + ...rest + } = this.props; + + const selectClass = + `filter select-filter form-control ${className} ${this.state.isSelected ? '' : 'placeholder-selected'}`; + + return ( + + ); + } +} + +SelectFilter.propTypes = { + onFilter: PropTypes.func.isRequired, + column: PropTypes.object.isRequired, + options: PropTypes.object.isRequired, + comparator: PropTypes.oneOf([LIKE, EQ]), + placeholder: PropTypes.string, + style: PropTypes.object, + className: PropTypes.string, + withoutEmptyOption: PropTypes.bool, + defaultValue: PropTypes.any +}; + +SelectFilter.defaultProps = { + defaultValue: '', + className: '', + withoutEmptyOption: false, + comparator: EQ +}; + +export default SelectFilter; diff --git a/packages/react-bootstrap-table2-filter/src/const.js b/packages/react-bootstrap-table2-filter/src/const.js index 25faae0..64e8361 100644 --- a/packages/react-bootstrap-table2-filter/src/const.js +++ b/packages/react-bootstrap-table2-filter/src/const.js @@ -1,5 +1,6 @@ export const FILTER_TYPE = { - TEXT: 'TEXT' + TEXT: 'TEXT', + SELECT: 'SELECT' }; export const FILTER_DELAY = 500; diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js index 7e197fa..3e6822a 100644 --- a/packages/react-bootstrap-table2-filter/src/wrapper.js +++ b/packages/react-bootstrap-table2-filter/src/wrapper.js @@ -3,7 +3,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { filters } from './filter'; -import { LIKE } from './comparison'; +import { LIKE, EQ } from './comparison'; +import { FILTER_TYPE } from './const'; export default (Base, { _, @@ -47,7 +48,8 @@ export default (Base, { if (!_.isDefined(filterVal) || filterVal === '') { delete currFilters[dataField]; } else { - const { comparator = LIKE } = filter.props; + // select default comparator is EQ, others are LIKE + const { comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE) } = filter.props; currFilters[dataField] = { filterVal, filterType, comparator }; } store.filters = currFilters; From 094a0682f16ce6e30fd19759565f37a505e15e67 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 28 Jan 2018 23:13:10 +0800 Subject: [PATCH 2/6] add select filter stories --- .../column-filter/custom-select-filter.js | 80 +++++++++++++++++++ .../select-filter-default-value.js | 70 ++++++++++++++++ .../select-filter-like-comparator.js | 69 ++++++++++++++++ .../examples/column-filter/select-filter.js | 68 ++++++++++++++++ .../src/utils/common.js | 7 ++ .../stories/index.js | 8 ++ 6 files changed, 302 insertions(+) create mode 100644 packages/react-bootstrap-table2-example/examples/column-filter/custom-select-filter.js create mode 100644 packages/react-bootstrap-table2-example/examples/column-filter/select-filter-default-value.js create mode 100644 packages/react-bootstrap-table2-example/examples/column-filter/select-filter-like-comparator.js create mode 100644 packages/react-bootstrap-table2-example/examples/column-filter/select-filter.js diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-select-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-select-filter.js new file mode 100644 index 0000000..be7a366 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-select-filter.js @@ -0,0 +1,80 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { selectFilter } 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: selectFilter({ + options: selectOptions, + withoutEmptyOption: true, + style: { + backgroundColor: 'pink' + }, + className: 'test-classname', + datamycustomattr: 'datamycustomattr' + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { selectFilter } 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: selectFilter({ + 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/select-filter-default-value.js b/packages/react-bootstrap-table2-example/examples/column-filter/select-filter-default-value.js new file mode 100644 index 0000000..848766a --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/select-filter-default-value.js @@ -0,0 +1,70 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { selectFilter } 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: selectFilter({ + options: selectOptions, + defaultValue: 2 + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { selectFilter } 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: selectFilter({ + options: selectOptions, + defaultValue: 2 + }) +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/select-filter-like-comparator.js b/packages/react-bootstrap-table2-example/examples/column-filter/select-filter-like-comparator.js new file mode 100644 index 0000000..306bdc8 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/select-filter-like-comparator.js @@ -0,0 +1,69 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { selectFilter, Comparator } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(6); + +const selectOptions = { + '03': '03', + '04': '04', + '01': '01' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + filter: selectFilter({ + options: selectOptions, + comparator: Comparator.LIKE // default is Comparator.EQ + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { selectFilter } from 'react-bootstrap-table2-filter'; + +const selectOptions = { + '03': '03', + '04': '04', + '01': '01' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + filter: selectFilter({ + options: selectOptions, + comparator: Comparator.LIKE // default is Comparator.EQ + }) +}]; + + +`; + +export default () => ( +
+

Select Filter with LIKE Comparator

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/select-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/select-filter.js new file mode 100644 index 0000000..6acf849 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/select-filter.js @@ -0,0 +1,68 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { selectFilter } 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: selectFilter({ + options: selectOptions + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { selectFilter } 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: selectFilter({ + options: selectOptions + }) +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/src/utils/common.js b/packages/react-bootstrap-table2-example/src/utils/common.js index 0d5ad84..0f1c9f1 100644 --- a/packages/react-bootstrap-table2-example/src/utils/common.js +++ b/packages/react-bootstrap-table2-example/src/utils/common.js @@ -20,6 +20,13 @@ export const productsGenerator = (quantity = 5, callback) => { ); }; +export const productsQualityGenerator = (quantity = 5) => + Array.from({ length: quantity }, (value, index) => ({ + id: index, + name: `Item name ${index}`, + quality: index % 3 + })); + export const jobsGenerator = (quantity = 5) => Array.from({ length: quantity }, (value, index) => ({ id: index, diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 8037edb..8cf4a01 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -39,6 +39,10 @@ import TextFilterWithDefaultValue from 'examples/column-filter/text-filter-defau 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'; +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'; // work on rows import RowStyleTable from 'examples/rows/row-style'; @@ -140,6 +144,10 @@ storiesOf('Column Filter', module) .add('Text Filter with Comparator', () => ) .add('Custom Text Filter', () => ) // add another filter type example right here. + .add('Select Filter', () => ) + .add('Select Filter with Default Value', () => ) + .add('Select Filter with Comparator', () => ) + .add('Custom Select Filter', () => ) .add('Custom Filter Value', () => ); storiesOf('Work on Rows', module) From 81e0080aa681100a1c4e9cd10ad7c34d88caa8e3 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 29 Jan 2018 22:57:08 +0800 Subject: [PATCH 3/6] add styles for filter modules --- gulpfile.babel.js | 1 + .../.storybook/webpack.config.js | 3 ++- .../stories/index.js | 1 + .../style/react-bootstrap-table2-filter.scss | 14 ++++++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss diff --git a/gulpfile.babel.js b/gulpfile.babel.js index f124ae9..77b5c9f 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -24,6 +24,7 @@ const JS_SKIPS = `+(${TEST}|${LIB}|${DIST}|${NODE_MODULES})`; const STYLE_PKGS = [ 'react-bootstrap-table2', + 'react-bootstrap-table2-filter', 'react-bootstrap-table2-paginator' ].reduce((pkg, curr) => `${curr}|${pkg}`, ''); diff --git a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js index 166aef5..a2c12e2 100644 --- a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js +++ b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js @@ -7,6 +7,7 @@ const filterSourcePath = path.join(__dirname, '../../react-bootstrap-table2-filt const editorSourcePath = path.join(__dirname, '../../react-bootstrap-table2-editor/index.js'); const sourceStylePath = path.join(__dirname, '../../react-bootstrap-table2/style'); const paginationStylePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/style'); +const filterStylePath = path.join(__dirname, '../../react-bootstrap-table2-filter/style'); const storyPath = path.join(__dirname, '../stories'); const examplesPath = path.join(__dirname, '../examples'); const srcPath = path.join(__dirname, '../src'); @@ -40,7 +41,7 @@ const loaders = [{ }, { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'], - include: [storyPath, sourceStylePath, paginationStylePath], + include: [storyPath, sourceStylePath, paginationStylePath, filterStylePath], }, { test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000', diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 8cf4a01..f5e3db7 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -102,6 +102,7 @@ import 'stories/stylesheet/tomorrow.min.css'; import 'stories/stylesheet/storybook.scss'; import '../../react-bootstrap-table2/style/react-bootstrap-table2.scss'; import '../../react-bootstrap-table2-paginator/style/react-bootstrap-table2-paginator.scss'; +import '../../react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss'; // import { action } from '@storybook/addon-actions'; diff --git a/packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss b/packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss new file mode 100644 index 0000000..16aa6dd --- /dev/null +++ b/packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss @@ -0,0 +1,14 @@ +.react-bootstrap-table > table > thead > tr > th .filter { + font-weight: normal; +} + +.react-bootstrap-table > table > thead > tr > th .select-filter option[value=''], +.react-bootstrap-table > table > thead > tr > th .select-filter.placeholder-selected { + color: lightgrey; + font-style: italic; +} + +.react-bootstrap-table > table > thead > tr > th .select-filter.placeholder-selected option:not([value='']) { + color: initial; + font-style: initial; +} \ No newline at end of file From 2533a63430f6b85f4541322390c79584c91dda63 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Tue, 30 Jan 2018 23:16:10 +0800 Subject: [PATCH 4/6] patch test for select component --- .../test/components/select.test.js | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 packages/react-bootstrap-table2-filter/test/components/select.test.js diff --git a/packages/react-bootstrap-table2-filter/test/components/select.test.js b/packages/react-bootstrap-table2-filter/test/components/select.test.js new file mode 100644 index 0000000..2ed39a6 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/test/components/select.test.js @@ -0,0 +1,296 @@ +import 'jsdom-global/register'; +import React from 'react'; +import sinon from 'sinon'; +import { mount } from 'enzyme'; +import SelectFilter from '../../src/components/select'; +import { FILTER_TYPE } from '../../src/const'; + + +describe('Select Filter', () => { + let wrapper; + let instance; + const onFilter = sinon.stub(); + const column = { + dataField: 'quality', + text: 'Product Quality' + }; + const options = { + 0: 'Bad', + 1: 'Good', + 2: 'Unknow' + }; + + afterEach(() => { + onFilter.reset(); + }); + + describe('initialization', () => { + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + }); + + it('should have correct state', () => { + expect(instance.state.isSelected).toBeFalsy(); + }); + + it('should rendering component successfully', () => { + expect(wrapper).toHaveLength(1); + expect(wrapper.find('select')).toHaveLength(1); + expect(wrapper.find('.select-filter')).toHaveLength(1); + expect(wrapper.find('.placeholder-selected')).toHaveLength(1); + }); + + it('should rendering select options correctly', () => { + const select = wrapper.find('select'); + expect(select.find('option')).toHaveLength(Object.keys(options).length + 1); + expect(select.childAt(0).text()).toEqual(`Select ${column.text}...`); + + Object.keys(options).forEach((key, i) => { + expect(select.childAt(i + 1).prop('value')).toEqual(key); + expect(select.childAt(i + 1).text()).toEqual(options[key]); + }); + }); + }); + + describe('when defaultValue is defined', () => { + let defaultValue; + + describe('and it is valid', () => { + beforeEach(() => { + defaultValue = '0'; + wrapper = mount( + + ); + instance = wrapper.instance(); + }); + + it('should have correct state', () => { + expect(instance.state.isSelected).toBeTruthy(); + }); + + it('should rendering component successfully', () => { + expect(wrapper).toHaveLength(1); + expect(wrapper.find('.placeholder-selected')).toHaveLength(0); + }); + + it('should calling onFilter on componentDidMount', () => { + expect(onFilter.calledOnce).toBeTruthy(); + expect(onFilter.calledWith(column, defaultValue, FILTER_TYPE.SELECT)).toBeTruthy(); + }); + }); + }); + + describe('when placeholder is defined', () => { + const placeholder = 'test'; + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + }); + + it('should rendering component successfully', () => { + expect(wrapper).toHaveLength(1); + const select = wrapper.find('select'); + expect(select.childAt(0).text()).toEqual(placeholder); + }); + }); + + describe('when style is defined', () => { + const style = { backgroundColor: 'red' }; + beforeEach(() => { + wrapper = mount( + + ); + }); + + it('should rendering component successfully', () => { + expect(wrapper).toHaveLength(1); + expect(wrapper.find('select').prop('style')).toEqual(style); + }); + }); + + describe('when withoutEmptyOption is defined', () => { + beforeEach(() => { + wrapper = mount( + + ); + }); + + it('should rendering select without default empty option', () => { + const select = wrapper.find('select'); + expect(select.find('option')).toHaveLength(Object.keys(options).length); + }); + }); + + describe('componentDidUpdate', () => { + let prevProps; + + describe('when props.defaultValue is diff from prevProps.defaultValue', () => { + beforeEach(() => { + wrapper = mount( + + ); + prevProps = { + column, + options, + defaultValue: '1' + }; + instance = wrapper.instance(); + instance.componentDidUpdate(prevProps); + }); + + it('should update', () => { + expect(onFilter.callCount).toBe(2); + expect(onFilter.calledWith( + column, instance.props.defaultValue, FILTER_TYPE.SELECT)).toBeTruthy(); + }); + }); + + describe('when props.options is diff from prevProps.options', () => { + beforeEach(() => { + wrapper = mount( + + ); + prevProps = { + column, + options + }; + instance = wrapper.instance(); + instance.componentDidUpdate(prevProps); + }); + + it('should update', () => { + expect(onFilter.callCount).toBe(2); + expect(onFilter.calledWith( + column, instance.props.defaultValue, FILTER_TYPE.SELECT)).toBeTruthy(); + }); + }); + }); + + describe('cleanFiltered', () => { + describe('when props.defaultValue is defined', () => { + const defaultValue = '0'; + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + instance.cleanFiltered(); + }); + + it('should setting state correctly', () => { + expect(instance.state.isSelected).toBeTruthy(); + }); + + it('should calling onFilter correctly', () => { + expect(onFilter.callCount).toBe(2); + expect(onFilter.calledWith(column, defaultValue, FILTER_TYPE.SELECT)).toBeTruthy(); + }); + }); + + describe('when props.defaultValue is not defined', () => { + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + instance.cleanFiltered(); + }); + + it('should setting state correctly', () => { + expect(instance.state.isSelected).toBeFalsy(); + }); + + it('should calling onFilter correctly', () => { + expect(onFilter.callCount).toBe(1); + }); + }); + }); + + describe('applyFilter', () => { + const value = '2'; + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + instance.applyFilter(value); + }); + + it('should setting state correctly', () => { + expect(instance.state.isSelected).toBeTruthy(); + }); + + it('should calling onFilter correctly', () => { + expect(onFilter.callCount).toBe(1); + expect(onFilter.calledWith(column, value, FILTER_TYPE.SELECT)).toBeTruthy(); + }); + }); + + describe('filter', () => { + const event = { target: { value: 'tester' } }; + + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + instance.filter(event); + }); + + it('should setting state correctly', () => { + expect(instance.state.isSelected).toBeTruthy(); + }); + + it('should calling onFilter correctly', () => { + expect(onFilter.callCount).toBe(1); + expect(onFilter.calledWith(column, event.target.value, FILTER_TYPE.SELECT)).toBeTruthy(); + }); + }); +}); From 9a354444d0c84e4aac766eb7e7557e8967b0df77 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Tue, 30 Jan 2018 23:25:58 +0800 Subject: [PATCH 5/6] fix bug for wrap not existing method --- .../test/row-selection/selection-cell.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js index bcb5799..c0069e2 100644 --- a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js +++ b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js @@ -34,12 +34,12 @@ describe('', () => { }); }); - describe('handleRowClick', () => { + describe('handleClick', () => { describe('when was been clicked', () => { const rowKey = 1; const selected = true; let mockOnRowSelect; - const spy = sinon.spy(SelectionCell.prototype, 'handleRowClick'); + const spy = sinon.spy(SelectionCell.prototype, 'handleClick'); beforeEach(() => { mockOnRowSelect = sinon.stub(); From 8fa6389c81ad0128ad170360d492551cc672d00b Mon Sep 17 00:00:00 2001 From: AllenFang Date: Tue, 30 Jan 2018 23:39:18 +0800 Subject: [PATCH 6/6] patch docs --- docs/columns.md | 1 + docs/migration.md | 3 +- .../react-bootstrap-table2-filter/README.md | 46 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/columns.md b/docs/columns.md index a18a073..cf546a8 100644 --- a/docs/columns.md +++ b/docs/columns.md @@ -542,6 +542,7 @@ Or take a callback function Configure `column.filter` will able to setup a column level filter on the header column. Currently, `react-bootstrap-table2` support following filters: * Text(`textFilter`) +* Select(`selectFilter`) We have a quick example to show you how to use `column.filter`: diff --git a/docs/migration.md b/docs/migration.md index f66b7f7..fb294c3 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -83,7 +83,8 @@ Please see [available filter configuration](https://react-bootstrap-table.github - [x] Remote Filter - [ ] Custom Filter Component - [ ] Regex Filter -- [ ] Select Filter +- [x] Select Filter +- [x] Custom Select Filter - [ ] Number Filter - [ ] Date Filter - [ ] Array Filter diff --git a/packages/react-bootstrap-table2-filter/README.md b/packages/react-bootstrap-table2-filter/README.md index f8b6788..74192b5 100644 --- a/packages/react-bootstrap-table2-filter/README.md +++ b/packages/react-bootstrap-table2-filter/README.md @@ -17,6 +17,7 @@ $ npm install react-bootstrap-table2-filter --save You can get all types of filters via import and these filters are a factory function to create a individual filter instance. Currently, we support following filters: * TextFilter +* SelectFilter * **Coming soon!** ## Text Filter @@ -51,5 +52,50 @@ const priceFilter = textFilter({ delay: 1000 // how long will trigger filtering after user typing, default is 500 ms }); +// omit... +``` + +## Select Filter +A quick example: + +```js +import filterFactory, { selectFilter } 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: selectFilter({ + options: selectOptions + }) +}]; + + +``` + +Following is an example for custom select filter: + +```js +import filterFactory, { selectFilter, Comparator } from 'react-bootstrap-table2-filter'; +// omit... + +const qualityFilter = selectFilter({ + 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... ``` \ No newline at end of file