From 88befb81362bbe20e531485db9fa33f1e7f24574 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 23 Jun 2018 13:47:53 +0800 Subject: [PATCH 1/6] fix #380 --- .../react-bootstrap-table2-filter/src/components/date.js | 7 ++++++- .../react-bootstrap-table2-filter/src/components/number.js | 7 ++++++- .../react-bootstrap-table2-filter/src/components/select.js | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/react-bootstrap-table2-filter/src/components/date.js b/packages/react-bootstrap-table2-filter/src/components/date.js index adff6c6..da2ed2c 100644 --- a/packages/react-bootstrap-table2-filter/src/components/date.js +++ b/packages/react-bootstrap-table2-filter/src/components/date.js @@ -1,4 +1,5 @@ /* eslint react/require-default-props: 0 */ +/* jsx-a11y/no-static-element-interactions: 0 */ /* eslint no-return-assign: 0 */ /* eslint prefer-template: 0 */ import React, { Component } from 'react'; @@ -121,7 +122,11 @@ class DateFilter extends Component { } = this.props; return ( -
+
e.stopPropagation() } + className={ `filter date-filter ${className}` } + style={ style } + > this.numberFilterComparator = n } style={ comparatorStyle } diff --git a/packages/react-bootstrap-table2-filter/src/components/select.js b/packages/react-bootstrap-table2-filter/src/components/select.js index ba2d306..7c4d285 100644 --- a/packages/react-bootstrap-table2-filter/src/components/select.js +++ b/packages/react-bootstrap-table2-filter/src/components/select.js @@ -116,6 +116,7 @@ class SelectFilter extends Component { style={ style } className={ selectClass } onChange={ this.filter } + onClick={ e => e.stopPropagation() } defaultValue={ defaultValue !== undefined ? defaultValue : '' } > { this.getOptions() } From 1152bb8440a0249feca774e7c5673d88242063c4 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 23 Jun 2018 13:47:53 +0800 Subject: [PATCH 2/6] fix #380 --- .../react-bootstrap-table2-filter/src/components/date.js | 7 ++++++- .../react-bootstrap-table2-filter/src/components/number.js | 7 ++++++- .../react-bootstrap-table2-filter/src/components/select.js | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/react-bootstrap-table2-filter/src/components/date.js b/packages/react-bootstrap-table2-filter/src/components/date.js index adff6c6..da2ed2c 100644 --- a/packages/react-bootstrap-table2-filter/src/components/date.js +++ b/packages/react-bootstrap-table2-filter/src/components/date.js @@ -1,4 +1,5 @@ /* eslint react/require-default-props: 0 */ +/* jsx-a11y/no-static-element-interactions: 0 */ /* eslint no-return-assign: 0 */ /* eslint prefer-template: 0 */ import React, { Component } from 'react'; @@ -121,7 +122,11 @@ class DateFilter extends Component { } = this.props; return ( -
+
e.stopPropagation() } + className={ `filter date-filter ${className}` } + style={ style } + > this.numberFilterComparator = n } style={ comparatorStyle } diff --git a/packages/react-bootstrap-table2-filter/src/components/select.js b/packages/react-bootstrap-table2-filter/src/components/select.js index ba2d306..7c4d285 100644 --- a/packages/react-bootstrap-table2-filter/src/components/select.js +++ b/packages/react-bootstrap-table2-filter/src/components/select.js @@ -116,6 +116,7 @@ class SelectFilter extends Component { style={ style } className={ selectClass } onChange={ this.filter } + onClick={ e => e.stopPropagation() } defaultValue={ defaultValue !== undefined ? defaultValue : '' } > { this.getOptions() } From 7d72002b6e74b2dfaa1efa310c372fdc86b13000 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 24 Jun 2018 13:16:48 +0800 Subject: [PATCH 3/6] fix wrong eslint rule --- packages/react-bootstrap-table2-filter/src/components/date.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-bootstrap-table2-filter/src/components/date.js b/packages/react-bootstrap-table2-filter/src/components/date.js index da2ed2c..02468aa 100644 --- a/packages/react-bootstrap-table2-filter/src/components/date.js +++ b/packages/react-bootstrap-table2-filter/src/components/date.js @@ -1,5 +1,5 @@ /* eslint react/require-default-props: 0 */ -/* jsx-a11y/no-static-element-interactions: 0 */ +/* eslint jsx-a11y/no-static-element-interactions: 0 */ /* eslint no-return-assign: 0 */ /* eslint prefer-template: 0 */ import React, { Component } from 'react'; From 04c21cb63daa84c467e9e71270b8d221565ec850 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 24 Jun 2018 15:03:46 +0800 Subject: [PATCH 4/6] implement custom filter --- .../react-bootstrap-table2-filter/index.js | 7 ++ .../src/wrapper.js | 8 +++ .../src/bootstrap-table.js | 4 +- .../react-bootstrap-table2/src/header-cell.js | 14 +++- packages/react-bootstrap-table2/src/header.js | 7 +- .../test/header-cell.test.js | 70 +++++++++++++++++++ 6 files changed, 104 insertions(+), 6 deletions(-) diff --git a/packages/react-bootstrap-table2-filter/index.js b/packages/react-bootstrap-table2-filter/index.js index 0f17bd3..fe60268 100644 --- a/packages/react-bootstrap-table2-filter/index.js +++ b/packages/react-bootstrap-table2-filter/index.js @@ -4,12 +4,15 @@ import NumberFilter from './src/components/number'; import DateFilter from './src/components/date'; import wrapperFactory from './src/wrapper'; import * as Comparison from './src/comparison'; +import { FILTER_TYPE } from './src/const'; export default (options = {}) => ({ wrapperFactory, options }); +export const FILTER_TYPES = FILTER_TYPE; + export const Comparator = Comparison; export const textFilter = (props = {}) => ({ @@ -31,3 +34,7 @@ export const dateFilter = (props = {}) => ({ Filter: DateFilter, props }); + +export const customFilter = (props = {}) => ({ + props +}); diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js index 33867fd..c2b24e8 100644 --- a/packages/react-bootstrap-table2-filter/src/wrapper.js +++ b/packages/react-bootstrap-table2-filter/src/wrapper.js @@ -20,6 +20,7 @@ export default (Base, { super(props); this.state = { currFilters: {}, isDataChanged: props.isDataChanged || false }; this.onFilter = this.onFilter.bind(this); + this.onExternalFilter = this.onExternalFilter.bind(this); } componentWillReceiveProps({ isDataChanged, store, columns }) { @@ -78,12 +79,19 @@ export default (Base, { }; } + onExternalFilter(column, filterType) { + return (value) => { + this.onFilter(column, filterType)(value); + }; + } + render() { return ( ); diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 723f5bd..fe6865c 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -90,6 +90,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { sortOrder={ store.sortOrder } onSort={ this.props.onSort } onFilter={ this.props.onFilter } + onExternalFilter={ this.props.onExternalFilter } selectRow={ headerCellSelectionInfo } /> { sorting, sortOrder, isLastSorting, - onFilter + onFilter, + onExternalFilter } = props; const { text, sort, filter, + filterRenderer, headerTitle, headerAlign, headerFormatter, @@ -89,7 +91,11 @@ const HeaderCell = (props) => { if (cellClasses) cellAttrs.className = cs(cellAttrs.className, cellClasses); if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle; - if (filter) { + + if (filterRenderer) { + const onCustomFilter = onExternalFilter(column, filter.props.type); + filterElm = filterRenderer(onCustomFilter, column); + } else if (filter) { filterElm = ; } @@ -136,6 +142,7 @@ HeaderCell.propTypes = { editorRenderer: PropTypes.func, validator: PropTypes.func, filter: PropTypes.object, + filterRenderer: PropTypes.func, filterValue: PropTypes.func }).isRequired, index: PropTypes.number.isRequired, @@ -143,7 +150,8 @@ HeaderCell.propTypes = { sorting: PropTypes.bool, sortOrder: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC]), isLastSorting: PropTypes.bool, - onFilter: PropTypes.func + onFilter: PropTypes.func, + onExternalFilter: PropTypes.func }; export default HeaderCell; diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index 38b8c3a..5fd7e57 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -15,7 +15,8 @@ const Header = (props) => { onFilter, sortField, sortOrder, - selectRow + selectRow, + onExternalFilter } = props; return ( @@ -39,6 +40,7 @@ const Header = (props) => { onSort={ onSort } sorting={ currSort } onFilter={ onFilter } + onExternalFilter={ onExternalFilter } sortOrder={ sortOrder } isLastSorting={ isLastSorting } />); @@ -57,7 +59,8 @@ Header.propTypes = { onFilter: PropTypes.func, sortField: PropTypes.string, sortOrder: PropTypes.string, - selectRow: PropTypes.object + selectRow: PropTypes.object, + onExternalFilter: PropTypes.func }; export default Header; diff --git a/packages/react-bootstrap-table2/test/header-cell.test.js b/packages/react-bootstrap-table2/test/header-cell.test.js index 535d222..6814135 100644 --- a/packages/react-bootstrap-table2/test/header-cell.test.js +++ b/packages/react-bootstrap-table2/test/header-cell.test.js @@ -669,4 +669,74 @@ describe('HeaderCell', () => { }); }); }); + + describe('when column.filter is defined', () => { + const onFilter = jest.fn(); + const filterProps = { a: 123 }; + const Filter = () =>
test
; + let column; + + beforeEach(() => { + onFilter.mockClear(); + column = { + dataField: 'id', + text: 'ID', + filter: { + props: filterProps, + Filter + } + }; + wrapper = shallow(); + }); + + it('should render successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('th').length).toBe(1); + }); + + it('should render filter correctly', () => { + expect(wrapper.find(Filter).length).toBe(1); + expect(wrapper.find(Filter).props()).toEqual({ + column, + onFilter, + ...filterProps + }); + }); + }); + + describe('when column.filter and column.filterRenderer is defined', () => { + const onExternalFilter = jest.fn(); + const filterProps = { a: 123 }; + const Filter = () =>
test
; + const filterRenderer = jest.fn().mockReturnValue(); + let column; + + beforeEach(() => { + onExternalFilter.mockClear(); + filterRenderer.mockClear(); + column = { + dataField: 'id', + text: 'ID', + filter: { + props: filterProps + }, + filterRenderer + }; + wrapper = shallow( + ); + }); + + it('should render successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('th').length).toBe(1); + }); + + it('should render filter correctly', () => { + expect(wrapper.find(Filter).length).toBe(1); + }); + + it('should call filterRenderer function correctly', () => { + expect(filterRenderer).toHaveBeenCalledTimes(1); + }); + }); }); From cbaec4c655c1f1d7390260b8df6640add784f929 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 24 Jun 2018 15:04:59 +0800 Subject: [PATCH 5/6] add stories for custom filter --- .../column-filter/advance-custom-filter.js | 184 ++++++++++++++++++ .../examples/column-filter/custom-filter.js | 128 ++++++++++++ .../stories/index.js | 6 +- 3 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 packages/react-bootstrap-table2-example/examples/column-filter/advance-custom-filter.js create mode 100644 packages/react-bootstrap-table2-example/examples/column-filter/custom-filter.js diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/advance-custom-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/advance-custom-filter.js new file mode 100644 index 0000000..3736dea --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/advance-custom-filter.js @@ -0,0 +1,184 @@ +/* eslint no-return-assign: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter, customFilter, Comparator, FILTER_TYPES } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(8); + +class PriceFilter extends React.Component { + static propTypes = { + column: PropTypes.object.isRequired, + onFilter: PropTypes.func.isRequired + } + constructor(props) { + super(props); + this.filter = this.filter.bind(this); + this.getValue = this.getValue.bind(this); + this.onChange = this.onChange.bind(this); + this.state = { value: 2100 }; + } + onChange(e) { + this.setState({ value: e.target.value }); + } + getValue() { + return parseInt(this.range.value, 10); + } + filter() { + this.props.onFilter({ + number: this.getValue(), + comparator: this.select.value + }); + } + render() { + return [ + this.range = node } + type="range" + min="2100" + max="2110" + onChange={ this.onChange } + />, +

this.showValue = node } + style={ { textAlign: 'center' } } + > + { this.state.value } +

, + , + + ]; + } +} + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: customFilter({ + type: FILTER_TYPES.NUMBER // ask react-bootstrap-table to filter data as number + }), + filterRenderer: (onFilter, column) => + +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter, customFilter, Comparator, FILTER_TYPES } from 'react-bootstrap-table2-filter'; + +class PriceFilter extends React.Component { + static propTypes = { + column: PropTypes.object.isRequired, + onFilter: PropTypes.func.isRequired + } + constructor(props) { + super(props); + this.filter = this.filter.bind(this); + this.getValue = this.getValue.bind(this); + this.onChange = this.onChange.bind(this); + this.state = { value: 2100 }; + } + onChange(e) { + this.setState({ value: e.target.value }); + } + getValue() { + return parseInt(this.range.value, 10); + } + filter() { + this.props.onFilter({ + number: this.getValue(), + comparator: this.select.value + }); + } + render() { + return [ + this.range = node } + type="range" + min="2100" + max="2110" + onChange={ this.onChange } + />, +

this.showValue = node } + style={ { textAlign: 'center' } } + > + { this.state.value } +

, + , + + ]; + } +} + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: customFilter({ + type: FILTER_TYPES.NUMBER // ask react-bootstrap-table to filter data as number + }), + filterRenderer: (onFilter, column) => + +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter.js new file mode 100644 index 0000000..97fec6c --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter.js @@ -0,0 +1,128 @@ +/* eslint no-return-assign: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter, customFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(8); + +class PriceFilter extends React.Component { + static propTypes = { + column: PropTypes.object.isRequired, + onFilter: PropTypes.func.isRequired + } + constructor(props) { + super(props); + this.filter = this.filter.bind(this); + this.getValue = this.getValue.bind(this); + } + getValue() { + return this.input.value; + } + filter() { + this.props.onFilter(this.getValue()); + } + render() { + return [ + this.input = node } + type="text" + placeholder="Input price" + />, + + ]; + } +} + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: customFilter(), + filterRenderer: (onFilter, column) => + +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter, customFilter } from 'react-bootstrap-table2-filter'; + +class PriceFilter extends React.Component { + static propTypes = { + column: PropTypes.object.isRequired, + onFilter: PropTypes.func.isRequired + } + constructor(props) { + super(props); + this.filter = this.filter.bind(this); + this.getValue = this.getValue.bind(this); + } + getValue() { + return this.input.value; + } + filter() { + this.props.onFilter(this.getValue()); + } + render() { + return [ + this.input = node } + type="text" + placeholder="Input price" + />, + + ]; + } +} + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: customFilter(), + filterRenderer: (onFilter, column) => + +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 08eaf85..64c4909 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -55,6 +55,8 @@ 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 CustomFilter from 'examples/column-filter/custom-filter'; +import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter'; // work on rows import RowStyleTable from 'examples/rows/row-style'; @@ -186,7 +188,9 @@ storiesOf('Column Filter', module) .add('Programmatically Text Filter', () => ) .add('Programmatically Select Filter', () => ) .add('Programmatically Number Filter', () => ) - .add('Programmatically Date Filter', () => ); + .add('Programmatically Date Filter', () => ) + .add('Custom Filter', () => ) + .add('Advance Custom Filter', () => ); storiesOf('Work on Rows', module) .add('Customize Row Style', () => ) From eb746258352421f7a921069b3bd9786ae69354ed Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 24 Jun 2018 15:15:57 +0800 Subject: [PATCH 6/6] patch docs for custom filter --- docs/migration.md | 2 +- .../react-bootstrap-table2-filter/README.md | 53 ++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/docs/migration.md b/docs/migration.md index 5385172..60edbd2 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -82,7 +82,7 @@ Please see [available filter configuration](https://react-bootstrap-table.github - [x] Text Filter - [x] Custom Text Filter - [x] Remote Filter -- [ ] Custom Filter Component +- [x] Custom Filter Component - [ ] Regex Filter - [x] Select Filter - [x] Custom Select Filter diff --git a/packages/react-bootstrap-table2-filter/README.md b/packages/react-bootstrap-table2-filter/README.md index dc9e621..0af54da 100644 --- a/packages/react-bootstrap-table2-filter/README.md +++ b/packages/react-bootstrap-table2-filter/README.md @@ -20,6 +20,7 @@ You can get all types of filters via import and these filters are a factory func * SelectFilter * NumberFilter * DateFilter +* CustomFilter * **Coming soon!** ## Add CSS @@ -189,4 +190,54 @@ const dateFilter = dateFilter({ }) // omit... -``` \ No newline at end of file +``` + +## Custom Filter + +```js +import filterFactory, { customFilter } from 'react-bootstrap-table2-filter'; + +const columns = [..., { + dataField: 'date', + text: 'Product Name', + filter: customFilter(), + filterRenderer: (onFilter, column) => ..... +}]; + + +``` + +In custom filter case, you are suppose to finish following two steps: +1. Call `customFilter` and pass to `column.filter` +2. Give `column.filterRenderer` as a callback function and return your custom filter element. + +### column.filterRenderer + +This function will pass two argument to you: +1. `onFilter`: call it to trigger filter when you need. +2. `column`: Just the column object! + +In the end, please remember to return your custom filter element! + +### customFilter + +`customFilter` function just same as `textFilter`, `selectFilter` etc, it is for customization reason. However, in the custom filter case, there's only one props is valid: `type` + + +```js +import filterFactory, { FILTER_TYPES } from 'react-bootstrap-table2-filter'; + +const customFilter = customFilter({ + type: FILTER_TYPES.NUMBER, // default is FILTER_TYPES.TEXT +}) +``` + +`type` is a way to ask `react-bootstrap-table` to filter you data as number, select, date or normal text. + +### FILTER_TYPES + +Following properties is valid in `FILTER_TYPES`: +* TEXT +* SELECT +* NUMBER +* DATE \ No newline at end of file