From 7016e55472c356b201c176fa61897f58732e3d80 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Wed, 13 Dec 2017 21:45:41 +0800 Subject: [PATCH] implement filter --- .../package.json | 11 +++ .../src/comparison.js | 2 + .../src/components/text.js | 93 +++++++++++++++++++ .../src/const.js | 5 + .../src/filter.js | 36 +++++++ .../src/index.js | 15 +++ .../src/wrapper.js | 48 ++++++++++ .../react-bootstrap-table2/src/store/index.js | 32 ++++++- .../src/table-factory.js | 5 +- 9 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 packages/react-bootstrap-table2-filter/package.json create mode 100644 packages/react-bootstrap-table2-filter/src/comparison.js create mode 100644 packages/react-bootstrap-table2-filter/src/components/text.js create mode 100644 packages/react-bootstrap-table2-filter/src/const.js create mode 100644 packages/react-bootstrap-table2-filter/src/filter.js create mode 100644 packages/react-bootstrap-table2-filter/src/index.js create mode 100644 packages/react-bootstrap-table2-filter/src/wrapper.js diff --git a/packages/react-bootstrap-table2-filter/package.json b/packages/react-bootstrap-table2-filter/package.json new file mode 100644 index 0000000..826efa7 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/package.json @@ -0,0 +1,11 @@ +{ + "name": "react-bootstrap-table2-filter", + "version": "0.0.1", + "description": "it's the column filter addon for react-bootstrap-table2", + "main": "src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/packages/react-bootstrap-table2-filter/src/comparison.js b/packages/react-bootstrap-table2-filter/src/comparison.js new file mode 100644 index 0000000..cc24214 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/comparison.js @@ -0,0 +1,2 @@ +export const LIKE = 'LIKE'; +export const EQ = '='; diff --git a/packages/react-bootstrap-table2-filter/src/components/text.js b/packages/react-bootstrap-table2-filter/src/components/text.js new file mode 100644 index 0000000..48ab658 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/components/text.js @@ -0,0 +1,93 @@ +/* eslint react/require-default-props: 0 */ +/* eslint react/no-unused-prop-types: 0 */ +/* eslint no-return-assign: 0 */ +import React, { Component } from 'react'; +import { PropTypes } from 'prop-types'; + +import { LIKE, EQ } from '../comparison'; +import { FILTER_TYPE, FILTER_DELAY } from '../const'; + +class TextFilter extends Component { + constructor(props) { + super(props); + this.filter = this.filter.bind(this); + this.timeout = null; + this.state = { + value: props.defaultValue + }; + } + componentDidMount() { + const defaultValue = this.input.value; + if (defaultValue) { + this.props.onFilter(this.props.column, defaultValue, FILTER_TYPE.TEXT); + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.defaultValue !== this.props.defaultValue) { + this.applyFilter(nextProps.defaultValue); + } + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + filter(e) { + e.stopPropagation(); + if (this.timeout) { + clearTimeout(this.timeout); + } + const filterValue = e.target.value; + this.setState(() => ({ value: filterValue })); + this.timeout = setTimeout(() => { + this.props.onFilter(this.props.column, filterValue, FILTER_TYPE.TEXT); + }, this.props.delay); + } + + cleanFiltered() { + const value = this.props.defaultValue; + this.setState(() => ({ value })); + this.props.onFilter(this.props.column, value, FILTER_TYPE.TEXT); + } + + applyFilter(filterText) { + this.setState(() => ({ value: filterText })); + this.props.onFilter(this.props.column, filterText, FILTER_TYPE.TEXT); + } + + render() { + const { placeholder, column: { text }, style } = this.props; + // stopPropagation for onClick event is try to prevent sort was triggered. + return ( + this.input = n } + type="text" + className="filter text-filter form-control" + style={ style } + onChange={ this.filter } + onClick={ e => e.stopPropagation() } + placeholder={ placeholder || `Enter ${text}...` } + value={ this.state.value } + /> + ); + } +} + +TextFilter.propTypes = { + onFilter: PropTypes.func.isRequired, + comparator: PropTypes.oneOf([LIKE, EQ]), + defaultValue: PropTypes.string, + delay: PropTypes.number, + placeholder: PropTypes.string, + column: PropTypes.object, + style: PropTypes.object +}; + +TextFilter.defaultProps = { + delay: FILTER_DELAY, + defaultValue: '' +}; + + +export default TextFilter; diff --git a/packages/react-bootstrap-table2-filter/src/const.js b/packages/react-bootstrap-table2-filter/src/const.js new file mode 100644 index 0000000..25faae0 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/const.js @@ -0,0 +1,5 @@ +export const FILTER_TYPE = { + TEXT: 'TEXT' +}; + +export const FILTER_DELAY = 500; diff --git a/packages/react-bootstrap-table2-filter/src/filter.js b/packages/react-bootstrap-table2-filter/src/filter.js new file mode 100644 index 0000000..6ca025d --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/filter.js @@ -0,0 +1,36 @@ +import { FILTER_TYPE } from './const'; +import { LIKE, EQ } from './comparison'; + +export const filterByText = _ => (data, dataField, { filterVal, comparator = LIKE }) => + data.filter((row) => { + const cell = _.get(row, dataField); + const cellStr = _.isDefined(cell) ? cell.toString() : ''; + if (comparator === EQ) { + return cellStr === filterVal; + } + return cellStr.indexOf(filterVal) > -1; + }); + +export const filterFactory = _ => (filterType) => { + let filterFn; + switch (filterType) { + case FILTER_TYPE.TEXT: + filterFn = filterByText(_); + break; + default: + filterFn = filterByText(_); + } + return filterFn; +}; + +export const filters = (store, _) => (currFilters) => { + const factory = filterFactory(_); + let result = store.getAllData(); + let filterFn; + Object.keys(currFilters).forEach((dataField) => { + const filterObj = currFilters[dataField]; + filterFn = factory(filterObj.filterType); + result = filterFn(result, dataField, filterObj); + }); + return result; +}; diff --git a/packages/react-bootstrap-table2-filter/src/index.js b/packages/react-bootstrap-table2-filter/src/index.js new file mode 100644 index 0000000..4bba49d --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/index.js @@ -0,0 +1,15 @@ +import TextFilter from './components/text'; +import FilterWrapper from './wrapper'; +import * as Comparison from './comparison'; + +export default (options = {}) => ({ + FilterWrapper, + options +}); + +export const Comparator = Comparison; + +export const textFilter = (props = {}) => ({ + Filter: TextFilter, + props +}); diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js new file mode 100644 index 0000000..3f9363b --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/wrapper.js @@ -0,0 +1,48 @@ +import { Component } from 'react'; +import PropTypes from 'prop-types'; +import { filters } from './filter'; + +export default class FilterWrapper extends Component { + static propTypes = { + store: PropTypes.object.isRequired, + baseElement: PropTypes.func.isRequired, + _: PropTypes.object.isRequired + } + + constructor(props) { + super(props); + this.state = { currFilters: {}, isDataChanged: false }; + this.onFilter = this.onFilter.bind(this); + } + + componentWillReceiveProps() { + this.setState(() => ({ isDataChanged: false })); + } + + onFilter(column, filterVal, filterType) { + const { store, _ } = this.props; + const { currFilters } = this.state; + const { dataField, filter } = column; + + if (!_.isDefined(filterVal) || filterVal === '') { + delete currFilters[dataField]; + } else { + const { comparator } = filter.props; + currFilters[dataField] = { filterVal, filterType, comparator }; + } + + store.filteredData = filters(store, _)(currFilters); + store.filtering = Object.keys(currFilters).length > 0; + + this.setState(() => ({ currFilters, isDataChanged: true })); + } + + render() { + return this.props.baseElement({ + ...this.props, + key: 'table', + onFilter: this.onFilter, + isDataChanged: this.state.isDataChanged + }); + } +} diff --git a/packages/react-bootstrap-table2/src/store/index.js b/packages/react-bootstrap-table2/src/store/index.js index c39e47d..eaca367 100644 --- a/packages/react-bootstrap-table2/src/store/index.js +++ b/packages/react-bootstrap-table2/src/store/index.js @@ -6,11 +6,13 @@ import { getRowByRowId } from './rows'; export default class Store { constructor(keyField) { this._data = []; + this._filteredData = []; this._keyField = keyField; - this._sortOrder = undefined; this._sortField = undefined; this._selected = []; + this._filtering = false; + this._isDataChanged = false; } edit(rowId, dataField, newValue) { @@ -24,12 +26,33 @@ export default class Store { this.data = sort(this)(sortFunc); } - get data() { return this._data; } - set data(data) { this._data = (data ? JSON.parse(JSON.stringify(data)) : []); } + getAllData() { + return this._data; + } + + get data() { + if (this._filtering) { + return this._filteredData; + } + return this._data; + } + set data(data) { + if (this._filtering) { + this._filteredData = data; + } else { + this._data = (data ? JSON.parse(JSON.stringify(data)) : []); + } + } + + get filteredData() { return this._filteredData; } + set filteredData(filteredData) { this._filteredData = filteredData; } get keyField() { return this._keyField; } set keyField(keyField) { this._keyField = keyField; } + get isDataChanged() { return this._isDataChanged; } + set isDataChanged(isDataChanged) { this._isDataChanged = isDataChanged; } + get sortOrder() { return this._sortOrder; } set sortOrder(sortOrder) { this._sortOrder = sortOrder; } @@ -38,4 +61,7 @@ export default class Store { get selected() { return this._selected; } set selected(selected) { this._selected = selected; } + + get filtering() { return this._filtering; } + set filtering(filtering) { this._filtering = filtering; } } diff --git a/packages/react-bootstrap-table2/src/table-factory.js b/packages/react-bootstrap-table2/src/table-factory.js index 90bcf24..10ff86e 100644 --- a/packages/react-bootstrap-table2/src/table-factory.js +++ b/packages/react-bootstrap-table2/src/table-factory.js @@ -22,9 +22,8 @@ export const pureTable = props => export const wrapWithFilter = (props) => { if (props.filter) { - const { wrapper } = props.filter; - const FilterBase = wrapper(wrapWithSort, _); - return React.createElement(FilterBase, { ...props }); + const { FilterWrapper } = props.filter; + return React.createElement(FilterWrapper, { ...props, baseElement: wrapWithSort, _ }); } return wrapWithSort(props); };