From 26314254be5be49f9bcb370f4088e216c73b9b07 Mon Sep 17 00:00:00 2001 From: ignalion Date: Fri, 20 Jul 2018 16:39:05 +0300 Subject: [PATCH] Added multiselect filter (mostly copied from default select one) --- .../react-bootstrap-table2-filter/index.js | 6 + .../src/components/multiselect.js | 155 ++++++++++++++++++ .../src/const.js | 1 + .../src/filter.js | 19 +++ .../src/wrapper.js | 10 +- 5 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 packages/react-bootstrap-table2-filter/src/components/multiselect.js diff --git a/packages/react-bootstrap-table2-filter/index.js b/packages/react-bootstrap-table2-filter/index.js index fe60268..6d9368a 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 MultiSelectFilter from './src/components/multiselect'; import NumberFilter from './src/components/number'; import DateFilter from './src/components/date'; import wrapperFactory from './src/wrapper'; @@ -25,6 +26,11 @@ export const selectFilter = (props = {}) => ({ props }); +export const multiSelectFilter = (props = {}) => ({ + Filter: MultiSelectFilter, + props +}); + export const numberFilter = (props = {}) => ({ Filter: NumberFilter, props diff --git a/packages/react-bootstrap-table2-filter/src/components/multiselect.js b/packages/react-bootstrap-table2-filter/src/components/multiselect.js new file mode 100644 index 0000000..9630bbd --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/components/multiselect.js @@ -0,0 +1,155 @@ +/* eslint react/require-default-props: 0 */ +/* eslint no-return-assign: 0 */ +/* eslint react/no-unused-prop-types: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { LIKE, EQ } from '../comparison'; +import { FILTER_TYPE } from '../const'; + + +function optionsEquals(currOpts, prevOpts) { + const keys = Object.keys(currOpts); + for (let i = 0; i < keys.length; i += 1) { + if (currOpts[keys[i]] !== prevOpts[keys[i]]) { + return false; + } + } + return Object.keys(currOpts).length === Object.keys(prevOpts).length; +} + +const getSelections = container => + Array.from(container.selectedOptions).map(item => item.value); + +class MultiSelectFilter extends Component { + constructor(props) { + super(props); + this.filter = this.filter.bind(this); + const isSelected = props.defaultValue.map(item => props.options[item]).length > 0; + this.state = { isSelected }; + } + + componentDidMount() { + const { column, onFilter, getFilter } = this.props; + + const value = getSelections(this.selectInput); + if (value && value.length > 0) { + onFilter(column, FILTER_TYPE.MULTISELECT)(value); + } + + // export onFilter function to allow users to access + if (getFilter) { + getFilter((filterVal) => { + this.setState(() => ({ isSelected: filterVal.length > 0 })); + this.selectInput.value = filterVal; + + onFilter(column, FILTER_TYPE.MULTISELECT)(filterVal); + }); + } + } + + componentDidUpdate(prevProps) { + let needFilter = false; + if (this.props.defaultValue !== prevProps.defaultValue) { + needFilter = true; + } else if (!optionsEquals(this.props.options, prevProps.options)) { + needFilter = true; + } + if (needFilter) { + const value = this.selectInput.value; + if (value) { + this.props.onFilter(this.props.column, FILTER_TYPE.MULTISELECT)(value); + } + } + } + + getOptions() { + const optionTags = []; + const { options, placeholder, column, withoutEmptyOption } = this.props; + if (!withoutEmptyOption) { + optionTags.push(( + + )); + } + Object.keys(options).forEach(key => + optionTags.push() + ); + return optionTags; + } + + cleanFiltered() { + const value = (this.props.defaultValue !== undefined) ? this.props.defaultValue : []; + this.setState(() => ({ isSelected: value.length > 0 })); + this.selectInput.value = value; + this.props.onFilter(this.props.column, FILTER_TYPE.MULTISELECT)(value); + } + + applyFilter(value) { + this.selectInput.value = value; + this.setState(() => ({ isSelected: value.length > 0 })); + this.props.onFilter(this.props.column, FILTER_TYPE.MULTISELECT)(value); + } + + filter(e) { + const value = getSelections(e.target); + this.setState(() => ({ isSelected: value.length > 0 })); + this.props.onFilter(this.props.column, FILTER_TYPE.MULTISELECT)(value); + } + + render() { + const { + style, + className, + defaultValue, + onFilter, + column, + options, + comparator, + withoutEmptyOption, + caseSensitive, + getFilter, + ...rest + } = this.props; + + const selectClass = + `filter select-filter form-control ${className} ${this.state.isSelected ? '' : 'placeholder-selected'}`; + + return ( + + ); + } +} + +MultiSelectFilter.propTypes = { + onFilter: PropTypes.func.isRequired, + column: PropTypes.object.isRequired, + options: PropTypes.object.isRequired, + comparator: PropTypes.oneOf([LIKE, EQ]), + placeholder: PropTypes.string, + style: PropTypes.object, + className: PropTypes.string, + withoutEmptyOption: PropTypes.bool, + defaultValue: PropTypes.array, + caseSensitive: PropTypes.bool, + getFilter: PropTypes.func +}; + +MultiSelectFilter.defaultProps = { + defaultValue: [], + className: '', + withoutEmptyOption: false, + comparator: EQ, + caseSensitive: true +}; + +export default MultiSelectFilter; diff --git a/packages/react-bootstrap-table2-filter/src/const.js b/packages/react-bootstrap-table2-filter/src/const.js index ccb4d78..e685640 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', + MULTISELECT: 'MULTISELECT', NUMBER: 'NUMBER', DATE: 'DATE' }; diff --git a/packages/react-bootstrap-table2-filter/src/filter.js b/packages/react-bootstrap-table2-filter/src/filter.js index a65d52a..73d84c4 100644 --- a/packages/react-bootstrap-table2-filter/src/filter.js +++ b/packages/react-bootstrap-table2-filter/src/filter.js @@ -187,6 +187,22 @@ export const filterByDate = _ => ( }); }; +export const filterByArray = _ => ( + data, + dataField, + { filterVal, comparator } +) => ( + data.filter((row) => { + const cell = _.get(row, dataField); + let cellStr = _.isDefined(cell) ? cell.toString() : ''; + if (comparator === EQ) { + return filterVal.indexOf(cellStr) !== -1; + } + cellStr = cellStr.toLocaleUpperCase(); + return filterVal.some(item => cellStr.indexOf(item.toLocaleUpperCase()) !== -1); + }) +); + export const filterFactory = _ => (filterType) => { let filterFn; switch (filterType) { @@ -194,6 +210,9 @@ export const filterFactory = _ => (filterType) => { case FILTER_TYPE.SELECT: filterFn = filterByText(_); break; + case FILTER_TYPE.MULTISELECT: + filterFn = filterByArray(_); + break; case FILTER_TYPE.NUMBER: filterFn = filterByNumber(_); break; diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js index c2b24e8..8a97979 100644 --- a/packages/react-bootstrap-table2-filter/src/wrapper.js +++ b/packages/react-bootstrap-table2-filter/src/wrapper.js @@ -53,12 +53,18 @@ export default (Base, { const currFilters = Object.assign({}, store.filters); const { dataField, filter } = column; - if (!_.isDefined(filterVal) || filterVal === '') { + const needClearFilters = !_.isDefined(filterVal) || filterVal === '' || + filterVal.length === 0; // || (filterVal.length === 1 && filterVal[0] === ''); + + if (needClearFilters) { delete currFilters[dataField]; } else { // select default comparator is EQ, others are LIKE const { - comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE), + comparator = ( + (filterType === FILTER_TYPE.SELECT) || ( + filterType === FILTER_TYPE.MULTISELECT) ? EQ : LIKE + ), caseSensitive = false } = filter.props; currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive };