Skip to content

Commit 7016e55

Browse files
committed
implement filter
1 parent 861809d commit 7016e55

File tree

9 files changed

+241
-6
lines changed

9 files changed

+241
-6
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "react-bootstrap-table2-filter",
3+
"version": "0.0.1",
4+
"description": "it's the column filter addon for react-bootstrap-table2",
5+
"main": "src/index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "",
10+
"license": "ISC"
11+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const LIKE = 'LIKE';
2+
export const EQ = '=';
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/* eslint react/require-default-props: 0 */
2+
/* eslint react/no-unused-prop-types: 0 */
3+
/* eslint no-return-assign: 0 */
4+
import React, { Component } from 'react';
5+
import { PropTypes } from 'prop-types';
6+
7+
import { LIKE, EQ } from '../comparison';
8+
import { FILTER_TYPE, FILTER_DELAY } from '../const';
9+
10+
class TextFilter extends Component {
11+
constructor(props) {
12+
super(props);
13+
this.filter = this.filter.bind(this);
14+
this.timeout = null;
15+
this.state = {
16+
value: props.defaultValue
17+
};
18+
}
19+
componentDidMount() {
20+
const defaultValue = this.input.value;
21+
if (defaultValue) {
22+
this.props.onFilter(this.props.column, defaultValue, FILTER_TYPE.TEXT);
23+
}
24+
}
25+
26+
componentWillReceiveProps(nextProps) {
27+
if (nextProps.defaultValue !== this.props.defaultValue) {
28+
this.applyFilter(nextProps.defaultValue);
29+
}
30+
}
31+
32+
componentWillUnmount() {
33+
clearTimeout(this.timeout);
34+
}
35+
36+
filter(e) {
37+
e.stopPropagation();
38+
if (this.timeout) {
39+
clearTimeout(this.timeout);
40+
}
41+
const filterValue = e.target.value;
42+
this.setState(() => ({ value: filterValue }));
43+
this.timeout = setTimeout(() => {
44+
this.props.onFilter(this.props.column, filterValue, FILTER_TYPE.TEXT);
45+
}, this.props.delay);
46+
}
47+
48+
cleanFiltered() {
49+
const value = this.props.defaultValue;
50+
this.setState(() => ({ value }));
51+
this.props.onFilter(this.props.column, value, FILTER_TYPE.TEXT);
52+
}
53+
54+
applyFilter(filterText) {
55+
this.setState(() => ({ value: filterText }));
56+
this.props.onFilter(this.props.column, filterText, FILTER_TYPE.TEXT);
57+
}
58+
59+
render() {
60+
const { placeholder, column: { text }, style } = this.props;
61+
// stopPropagation for onClick event is try to prevent sort was triggered.
62+
return (
63+
<input
64+
ref={ n => this.input = n }
65+
type="text"
66+
className="filter text-filter form-control"
67+
style={ style }
68+
onChange={ this.filter }
69+
onClick={ e => e.stopPropagation() }
70+
placeholder={ placeholder || `Enter ${text}...` }
71+
value={ this.state.value }
72+
/>
73+
);
74+
}
75+
}
76+
77+
TextFilter.propTypes = {
78+
onFilter: PropTypes.func.isRequired,
79+
comparator: PropTypes.oneOf([LIKE, EQ]),
80+
defaultValue: PropTypes.string,
81+
delay: PropTypes.number,
82+
placeholder: PropTypes.string,
83+
column: PropTypes.object,
84+
style: PropTypes.object
85+
};
86+
87+
TextFilter.defaultProps = {
88+
delay: FILTER_DELAY,
89+
defaultValue: ''
90+
};
91+
92+
93+
export default TextFilter;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const FILTER_TYPE = {
2+
TEXT: 'TEXT'
3+
};
4+
5+
export const FILTER_DELAY = 500;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { FILTER_TYPE } from './const';
2+
import { LIKE, EQ } from './comparison';
3+
4+
export const filterByText = _ => (data, dataField, { filterVal, comparator = LIKE }) =>
5+
data.filter((row) => {
6+
const cell = _.get(row, dataField);
7+
const cellStr = _.isDefined(cell) ? cell.toString() : '';
8+
if (comparator === EQ) {
9+
return cellStr === filterVal;
10+
}
11+
return cellStr.indexOf(filterVal) > -1;
12+
});
13+
14+
export const filterFactory = _ => (filterType) => {
15+
let filterFn;
16+
switch (filterType) {
17+
case FILTER_TYPE.TEXT:
18+
filterFn = filterByText(_);
19+
break;
20+
default:
21+
filterFn = filterByText(_);
22+
}
23+
return filterFn;
24+
};
25+
26+
export const filters = (store, _) => (currFilters) => {
27+
const factory = filterFactory(_);
28+
let result = store.getAllData();
29+
let filterFn;
30+
Object.keys(currFilters).forEach((dataField) => {
31+
const filterObj = currFilters[dataField];
32+
filterFn = factory(filterObj.filterType);
33+
result = filterFn(result, dataField, filterObj);
34+
});
35+
return result;
36+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import TextFilter from './components/text';
2+
import FilterWrapper from './wrapper';
3+
import * as Comparison from './comparison';
4+
5+
export default (options = {}) => ({
6+
FilterWrapper,
7+
options
8+
});
9+
10+
export const Comparator = Comparison;
11+
12+
export const textFilter = (props = {}) => ({
13+
Filter: TextFilter,
14+
props
15+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Component } from 'react';
2+
import PropTypes from 'prop-types';
3+
import { filters } from './filter';
4+
5+
export default class FilterWrapper extends Component {
6+
static propTypes = {
7+
store: PropTypes.object.isRequired,
8+
baseElement: PropTypes.func.isRequired,
9+
_: PropTypes.object.isRequired
10+
}
11+
12+
constructor(props) {
13+
super(props);
14+
this.state = { currFilters: {}, isDataChanged: false };
15+
this.onFilter = this.onFilter.bind(this);
16+
}
17+
18+
componentWillReceiveProps() {
19+
this.setState(() => ({ isDataChanged: false }));
20+
}
21+
22+
onFilter(column, filterVal, filterType) {
23+
const { store, _ } = this.props;
24+
const { currFilters } = this.state;
25+
const { dataField, filter } = column;
26+
27+
if (!_.isDefined(filterVal) || filterVal === '') {
28+
delete currFilters[dataField];
29+
} else {
30+
const { comparator } = filter.props;
31+
currFilters[dataField] = { filterVal, filterType, comparator };
32+
}
33+
34+
store.filteredData = filters(store, _)(currFilters);
35+
store.filtering = Object.keys(currFilters).length > 0;
36+
37+
this.setState(() => ({ currFilters, isDataChanged: true }));
38+
}
39+
40+
render() {
41+
return this.props.baseElement({
42+
...this.props,
43+
key: 'table',
44+
onFilter: this.onFilter,
45+
isDataChanged: this.state.isDataChanged
46+
});
47+
}
48+
}

packages/react-bootstrap-table2/src/store/index.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import { getRowByRowId } from './rows';
66
export default class Store {
77
constructor(keyField) {
88
this._data = [];
9+
this._filteredData = [];
910
this._keyField = keyField;
10-
1111
this._sortOrder = undefined;
1212
this._sortField = undefined;
1313
this._selected = [];
14+
this._filtering = false;
15+
this._isDataChanged = false;
1416
}
1517

1618
edit(rowId, dataField, newValue) {
@@ -24,12 +26,33 @@ export default class Store {
2426
this.data = sort(this)(sortFunc);
2527
}
2628

27-
get data() { return this._data; }
28-
set data(data) { this._data = (data ? JSON.parse(JSON.stringify(data)) : []); }
29+
getAllData() {
30+
return this._data;
31+
}
32+
33+
get data() {
34+
if (this._filtering) {
35+
return this._filteredData;
36+
}
37+
return this._data;
38+
}
39+
set data(data) {
40+
if (this._filtering) {
41+
this._filteredData = data;
42+
} else {
43+
this._data = (data ? JSON.parse(JSON.stringify(data)) : []);
44+
}
45+
}
46+
47+
get filteredData() { return this._filteredData; }
48+
set filteredData(filteredData) { this._filteredData = filteredData; }
2949

3050
get keyField() { return this._keyField; }
3151
set keyField(keyField) { this._keyField = keyField; }
3252

53+
get isDataChanged() { return this._isDataChanged; }
54+
set isDataChanged(isDataChanged) { this._isDataChanged = isDataChanged; }
55+
3356
get sortOrder() { return this._sortOrder; }
3457
set sortOrder(sortOrder) { this._sortOrder = sortOrder; }
3558

@@ -38,4 +61,7 @@ export default class Store {
3861

3962
get selected() { return this._selected; }
4063
set selected(selected) { this._selected = selected; }
64+
65+
get filtering() { return this._filtering; }
66+
set filtering(filtering) { this._filtering = filtering; }
4167
}

packages/react-bootstrap-table2/src/table-factory.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ export const pureTable = props =>
2222

2323
export const wrapWithFilter = (props) => {
2424
if (props.filter) {
25-
const { wrapper } = props.filter;
26-
const FilterBase = wrapper(wrapWithSort, _);
27-
return React.createElement(FilterBase, { ...props });
25+
const { FilterWrapper } = props.filter;
26+
return React.createElement(FilterWrapper, { ...props, baseElement: wrapWithSort, _ });
2827
}
2928
return wrapWithSort(props);
3029
};

0 commit comments

Comments
 (0)