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-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', () => )
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
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);
+ });
+ });
});