From 28a1077badc537bf5a9fcc32f2ed2e2e27d7477a Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 10 Feb 2018 15:43:22 +0800 Subject: [PATCH] implement number filter --- .../react-bootstrap-table2-filter/index.js | 6 + .../src/comparison.js | 5 + .../src/components/number.js | 249 ++++++++++++++++++ .../src/const.js | 3 +- .../src/filter.js | 67 ++++- .../style/react-bootstrap-table2-filter.scss | 19 +- 6 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 packages/react-bootstrap-table2-filter/src/components/number.js diff --git a/packages/react-bootstrap-table2-filter/index.js b/packages/react-bootstrap-table2-filter/index.js index 068b508..a2bc666 100644 --- a/packages/react-bootstrap-table2-filter/index.js +++ b/packages/react-bootstrap-table2-filter/index.js @@ -1,5 +1,6 @@ import TextFilter from './src/components/text'; import SelectFilter from './src/components/select'; +import NumberFilter from './src/components/number'; import wrapperFactory from './src/wrapper'; import * as Comparison from './src/comparison'; @@ -19,3 +20,8 @@ export const selectFilter = (props = {}) => ({ Filter: SelectFilter, props }); + +export const numberFilter = (props = {}) => ({ + Filter: NumberFilter, + props +}); diff --git a/packages/react-bootstrap-table2-filter/src/comparison.js b/packages/react-bootstrap-table2-filter/src/comparison.js index cc24214..7e599e7 100644 --- a/packages/react-bootstrap-table2-filter/src/comparison.js +++ b/packages/react-bootstrap-table2-filter/src/comparison.js @@ -1,2 +1,7 @@ export const LIKE = 'LIKE'; export const EQ = '='; +export const NE = '!='; +export const GT = '>'; +export const GE = '>='; +export const LT = '<'; +export const LE = '<='; diff --git a/packages/react-bootstrap-table2-filter/src/components/number.js b/packages/react-bootstrap-table2-filter/src/components/number.js new file mode 100644 index 0000000..3c2b612 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/components/number.js @@ -0,0 +1,249 @@ +/* eslint no-return-assign: 0 */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import * as Comparator from '../comparison'; +import { FILTER_TYPE, FILTER_DELAY } from '../const'; + +const legalComparators = [ + Comparator.EQ, + Comparator.NE, + Comparator.GT, + Comparator.GE, + Comparator.LT, + Comparator.LE +]; + +class NumberFilter extends Component { + constructor(props) { + super(props); + this.comparators = props.comparators || legalComparators; + this.timeout = null; + let isSelected = props.defaultValue !== undefined && props.defaultValue.number !== undefined; + if (props.options && isSelected) { + isSelected = props.options.indexOf(props.defaultValue.number) > -1; + } + this.state = { isSelected }; + this.onChangeNumber = this.onChangeNumber.bind(this); + this.onChangeNumberSet = this.onChangeNumberSet.bind(this); + this.onChangeComparator = this.onChangeComparator.bind(this); + } + + componentDidMount() { + const { column, onFilter } = this.props; + const comparator = this.numberFilterComparator.value; + const number = this.numberFilter.value; + if (comparator && number) { + onFilter(column, { number, comparator }, FILTER_TYPE.NUMBER); + } + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + onChangeNumber(e) { + const { delay, column, onFilter } = this.props; + const comparator = this.numberFilterComparator.value; + if (comparator === '') { + return; + } + if (this.timeout) { + clearTimeout(this.timeout); + } + const filterValue = e.target.value; + this.timeout = setTimeout(() => { + onFilter(column, { number: filterValue, comparator }, FILTER_TYPE.NUMBER); + }, delay); + } + + onChangeNumberSet(e) { + const { column, onFilter } = this.props; + const comparator = this.numberFilterComparator.value; + const { value } = e.target; + this.setState(() => ({ isSelected: (value !== '') })); + // if (comparator === '') { + // return; + // } + onFilter(column, { number: value, comparator }, FILTER_TYPE.NUMBER); + } + + onChangeComparator(e) { + const { column, onFilter } = this.props; + const value = this.numberFilter.value; + const comparator = e.target.value; + // if (value === '') { + // return; + // } + onFilter(column, { number: value, comparator }, FILTER_TYPE.NUMBER); + } + + getComparatorOptions() { + const optionTags = []; + const { withoutEmptyComparatorOption } = this.props; + if (!withoutEmptyComparatorOption) { + optionTags.push( + ); + } + return optionTags; + } + + getNumberOptions() { + const optionTags = []; + const { options, column, withoutEmptyNumberOption } = this.props; + if (!withoutEmptyNumberOption) { + optionTags.push( + + ); + } + for (let i = 0; i < options.length; i += 1) { + optionTags.push(); + } + return optionTags; + } + + applyFilter(filterObj) { + const { column, onFilter } = this.props; + const { number, comparator } = filterObj; + this.setState(() => ({ isSelected: (number !== '') })); + this.numberFilterComparator.value = comparator; + this.numberFilter.value = number; + onFilter(column, { number, comparator }, FILTER_TYPE.NUMBER); + } + + cleanFiltered() { + const { column, onFilter, defaultValue } = this.props; + const value = defaultValue ? defaultValue.number : ''; + const comparator = defaultValue ? defaultValue.comparator : ''; + this.setState(() => ({ isSelected: (value !== '') })); + this.numberFilterComparator.value = comparator; + this.numberFilter.value = value; + onFilter(column, { number: value, comparator }, FILTER_TYPE.NUMBER); + } + + render() { + const { isSelected } = this.state; + const { + defaultValue, + column, + options, + style, + className, + numberStyle, + numberClassName, + comparatorStyle, + comparatorClassName, + placeholder + } = this.props; + const selectClass = ` + select-filter + number-filter-input + form-control + ${numberClassName} + ${!isSelected ? 'placeholder-selected' : ''} + `; + + return ( +
+ + { + options ? + : + this.numberFilter = n } + type="number" + style={ numberStyle } + className={ `number-filter-input form-control ${numberClassName}` } + placeholder={ placeholder || `Enter ${column.text}...` } + onChange={ this.onChangeNumber } + defaultValue={ defaultValue ? defaultValue.number : '' } + /> + } +
+ ); + } +} + +NumberFilter.propTypes = { + onFilter: PropTypes.func.isRequired, + column: PropTypes.object.isRequired, + options: PropTypes.arrayOf(PropTypes.number), + defaultValue: PropTypes.shape({ + number: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + comparator: PropTypes.oneOf([...legalComparators, '']) + }), + delay: PropTypes.number, + /* eslint consistent-return: 0 */ + comparators: (props, propName) => { + if (!props[propName]) { + return; + } + for (let i = 0; i < props[propName].length; i += 1) { + let comparatorIsValid = false; + for (let j = 0; j < legalComparators.length; j += 1) { + if (legalComparators[j] === props[propName][i] || props[propName][i] === '') { + comparatorIsValid = true; + break; + } + } + if (!comparatorIsValid) { + return new Error(`Number comparator provided is not supported. + Use only ${legalComparators}`); + } + } + }, + placeholder: PropTypes.string, + withoutEmptyComparatorOption: PropTypes.bool, + withoutEmptyNumberOption: PropTypes.bool, + style: PropTypes.object, + className: PropTypes.string, + comparatorStyle: PropTypes.object, + comparatorClassName: PropTypes.string, + numberStyle: PropTypes.object, + numberClassName: PropTypes.string +}; + +NumberFilter.defaultProps = { + delay: FILTER_DELAY, + options: undefined, + defaultValue: { + number: undefined, + comparator: '' + }, + withoutEmptyComparatorOption: false, + withoutEmptyNumberOption: false, + comparators: legalComparators, + placeholder: undefined, + style: undefined, + className: '', + comparatorStyle: undefined, + comparatorClassName: '', + numberStyle: undefined, + numberClassName: '' +}; + +export default NumberFilter; diff --git a/packages/react-bootstrap-table2-filter/src/const.js b/packages/react-bootstrap-table2-filter/src/const.js index 64e8361..a459270 100644 --- a/packages/react-bootstrap-table2-filter/src/const.js +++ b/packages/react-bootstrap-table2-filter/src/const.js @@ -1,6 +1,7 @@ export const FILTER_TYPE = { TEXT: 'TEXT', - SELECT: 'SELECT' + SELECT: 'SELECT', + NUMBER: 'NUMBER' }; 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 index 03914a8..95d0200 100644 --- a/packages/react-bootstrap-table2-filter/src/filter.js +++ b/packages/react-bootstrap-table2-filter/src/filter.js @@ -1,5 +1,7 @@ +/* eslint eqeqeq: 0 */ +/* eslint no-console: 0 */ import { FILTER_TYPE } from './const'; -import { LIKE, EQ } from './comparison'; +import { LIKE, EQ, NE, GT, GE, LT, LE } from './comparison'; export const filterByText = _ => ( data, @@ -19,12 +21,75 @@ export const filterByText = _ => ( return cellStr.indexOf(filterVal) > -1; }); +export const filterByNumber = _ => ( + data, + dataField, + { filterVal: { comparator, number } }, + customFilterValue +) => + data.filter((row) => { + if (number === '' || !comparator) return true; + let valid = true; + let cell = _.get(row, dataField); + if (customFilterValue) { + cell = customFilterValue(cell, row); + } + + switch (comparator) { + case EQ: { + if (cell != number) { + valid = false; + } + break; + } + case GT: { + if (cell <= number) { + valid = false; + } + break; + } + case GE: { + if (cell < number) { + valid = false; + } + break; + } + case LT: { + if (cell >= number) { + valid = false; + } + break; + } + case LE: { + if (cell > number) { + valid = false; + } + break; + } + case NE: { + if (cell == number) { + valid = false; + } + break; + } + default: { + console.error('Number comparator provided is not supported'); + break; + } + } + return valid; + }); + export const filterFactory = _ => (filterType) => { let filterFn; switch (filterType) { case FILTER_TYPE.TEXT: + case FILTER_TYPE.SELECT: filterFn = filterByText(_); break; + case FILTER_TYPE.NUMBER: + filterFn = filterByNumber(_); + break; default: filterFn = filterByText(_); } 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 index 16aa6dd..70d4a03 100644 --- a/packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss +++ b/packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss @@ -3,7 +3,9 @@ } .react-bootstrap-table > table > thead > tr > th .select-filter option[value=''], -.react-bootstrap-table > table > thead > tr > th .select-filter.placeholder-selected { +.react-bootstrap-table > table > thead > tr > th .select-filter.placeholder-selected, +.react-bootstrap-table > table > thead > tr > th .filter::-webkit-input-placeholder, +.react-bootstrap-table > table > thead > tr > th .number-filter-input::-webkit-input-placeholder { color: lightgrey; font-style: italic; } @@ -11,4 +13,19 @@ .react-bootstrap-table > table > thead > tr > th .select-filter.placeholder-selected option:not([value='']) { color: initial; font-style: initial; +} + +.react-bootstrap-table > table > thead > tr > th .number-filter { + display: flex; +} + +.react-bootstrap-table > table > thead > tr > th .number-filter-input { + margin-left: 5px; + float: left; + width: calc(100% - 67px - 5px); +} + +.react-bootstrap-table > table > thead > tr > th .number-filter-comparator { + width: 67px; + float: left; } \ No newline at end of file