mirror of
https://github.com/gosticks/react-bootstrap-table2.git
synced 2025-10-16 11:55:39 +00:00
Merge pull request #443 from react-bootstrap-table/develop
20180801 release
This commit is contained in:
commit
4e7cfdf5ea
@ -88,7 +88,7 @@ Please see [available filter configuration](https://react-bootstrap-table.github
|
||||
- [x] Custom Select Filter
|
||||
- [X] Number Filter
|
||||
- [X] Date Filter
|
||||
- [ ] Array Filter
|
||||
- [X] Array Filter
|
||||
- [X] Programmatically Filter
|
||||
|
||||
Remember to install [`react-bootstrap-table2-filter`](https://www.npmjs.com/package/react-bootstrap-table2-filter) firstly.
|
||||
|
||||
80
packages/react-bootstrap-table2-example/examples/column-filter/custom-multi-select-filter.js
vendored
Normal file
80
packages/react-bootstrap-table2-example/examples/column-filter/custom-multi-select-filter.js
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsQualityGenerator } from 'utils/common';
|
||||
|
||||
const products = productsQualityGenerator(6);
|
||||
|
||||
const selectOptions = {
|
||||
0: 'good',
|
||||
1: 'Bad',
|
||||
2: 'unknown'
|
||||
};
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quailty',
|
||||
formatter: cell => selectOptions[cell],
|
||||
filter: multiSelectFilter({
|
||||
options: selectOptions,
|
||||
withoutEmptyOption: true,
|
||||
style: {
|
||||
backgroundColor: 'pink'
|
||||
},
|
||||
className: 'test-classname',
|
||||
datamycustomattr: 'datamycustomattr'
|
||||
})
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
|
||||
|
||||
const selectOptions = {
|
||||
0: 'good',
|
||||
1: 'Bad',
|
||||
2: 'unknown'
|
||||
};
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quailty',
|
||||
formatter: cell => selectOptions[cell],
|
||||
filter: multiSelectFilter({
|
||||
options: selectOptions,
|
||||
withoutEmptyOption: true,
|
||||
style: {
|
||||
backgroundColor: 'pink'
|
||||
},
|
||||
className: 'test-classname',
|
||||
datamycustomattr: 'datamycustomattr'
|
||||
})
|
||||
}];
|
||||
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
filter={ filterFactory() }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsQualityGenerator } from 'utils/common';
|
||||
|
||||
const products = productsQualityGenerator(6);
|
||||
|
||||
const selectOptions = {
|
||||
0: 'good',
|
||||
1: 'Bad',
|
||||
2: 'unknown'
|
||||
};
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quailty',
|
||||
formatter: cell => selectOptions[cell],
|
||||
filter: multiSelectFilter({
|
||||
options: selectOptions,
|
||||
defaultValue: [0, 2]
|
||||
})
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
|
||||
|
||||
const selectOptions = {
|
||||
0: 'good',
|
||||
1: 'Bad',
|
||||
2: 'unknown'
|
||||
};
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quailty',
|
||||
formatter: cell => selectOptions[cell],
|
||||
filter: multiSelectFilter({
|
||||
options: selectOptions,
|
||||
defaultValue: [0, 2]
|
||||
})
|
||||
}];
|
||||
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
|
||||
`;
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
filter={ filterFactory() }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
67
packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter.js
vendored
Normal file
67
packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter.js
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsQualityGenerator } from 'utils/common';
|
||||
|
||||
const products = productsQualityGenerator(6);
|
||||
|
||||
const selectOptions = {
|
||||
0: 'good',
|
||||
1: 'Bad',
|
||||
2: 'unknown'
|
||||
};
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quailty',
|
||||
formatter: cell => selectOptions[cell],
|
||||
filter: multiSelectFilter({
|
||||
options: selectOptions
|
||||
})
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
|
||||
|
||||
const selectOptions = {
|
||||
0: 'good',
|
||||
1: 'Bad',
|
||||
2: 'unknown'
|
||||
};
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quailty',
|
||||
formatter: cell => selectOptions[cell],
|
||||
filter: multiSelectFilter({
|
||||
options: selectOptions
|
||||
})
|
||||
}];
|
||||
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
|
||||
`;
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
filter={ filterFactory() }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsQualityGenerator } from 'utils/common';
|
||||
|
||||
const products = productsQualityGenerator(6);
|
||||
|
||||
let qualityFilter;
|
||||
|
||||
const selectOptions = {
|
||||
0: 'good',
|
||||
1: 'Bad',
|
||||
2: 'unknown'
|
||||
};
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quality',
|
||||
formatter: cell => selectOptions[cell],
|
||||
filter: multiSelectFilter({
|
||||
options: selectOptions,
|
||||
getFilter: (filter) => {
|
||||
// qualityFilter was assigned once the component has been mounted.
|
||||
qualityFilter = filter;
|
||||
}
|
||||
})
|
||||
}];
|
||||
|
||||
const handleClick = () => {
|
||||
qualityFilter([0, 2]);
|
||||
};
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
|
||||
|
||||
let qualityFilter;
|
||||
|
||||
const selectOptions = {
|
||||
0: 'good',
|
||||
1: 'Bad',
|
||||
2: 'unknown'
|
||||
};
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quality',
|
||||
formatter: cell => selectOptions[cell],
|
||||
filter: multiSelectFilter({
|
||||
options: selectOptions,
|
||||
getFilter: (filter) => {
|
||||
// qualityFilter was assigned once the component has been mounted.
|
||||
qualityFilter = filter;
|
||||
}
|
||||
})
|
||||
}];
|
||||
|
||||
const handleClick = () => {
|
||||
qualityFilter([0, 2]);
|
||||
};
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<button className="btn btn-lg btn-primary" onClick={ handleClick }>{' filter columns by option "good" and "unknow" '}</button>
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
|
||||
</div>
|
||||
);
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<button className="btn btn-lg btn-primary" onClick={ handleClick }>{' filter columns by option "good" and "unknow" '}</button>
|
||||
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
filter={ filterFactory() }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
@ -46,6 +46,9 @@ import SelectFilter from 'examples/column-filter/select-filter';
|
||||
import SelectFilterWithDefaultValue from 'examples/column-filter/select-filter-default-value';
|
||||
import SelectFilterComparator from 'examples/column-filter/select-filter-like-comparator';
|
||||
import CustomSelectFilter from 'examples/column-filter/custom-select-filter';
|
||||
import MultiSelectFilter from 'examples/column-filter/multi-select-filter';
|
||||
import MultiSelectFilterDefaultValue from 'examples/column-filter/multi-select-filter-default-value';
|
||||
import CustomMultiSelectFilter from 'examples/column-filter/custom-multi-select-filter';
|
||||
import NumberFilter from 'examples/column-filter/number-filter';
|
||||
import NumberFilterWithDefaultValue from 'examples/column-filter/number-filter-default-value';
|
||||
import CustomNumberFilter from 'examples/column-filter/custom-number-filter';
|
||||
@ -56,6 +59,7 @@ 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 ProgrammaticallyMultiSelectFilter from 'examples/column-filter/programmatically-multi-select-filter';
|
||||
import CustomFilter from 'examples/column-filter/custom-filter';
|
||||
import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter';
|
||||
|
||||
@ -178,6 +182,8 @@ storiesOf('Column Filter', module)
|
||||
.add('Select Filter', () => <SelectFilter />)
|
||||
.add('Select Filter with Default Value', () => <SelectFilterWithDefaultValue />)
|
||||
.add('Select Filter with Comparator', () => <SelectFilterComparator />)
|
||||
.add('MultiSelect Filter', () => <MultiSelectFilter />)
|
||||
.add('MultiSelect Filter with Default Value', () => <MultiSelectFilterDefaultValue />)
|
||||
.add('Number Filter', () => <NumberFilter />)
|
||||
.add('Number Filter with Default Value', () => <NumberFilterWithDefaultValue />)
|
||||
.add('Date Filter', () => <DateFilter />)
|
||||
@ -186,11 +192,13 @@ storiesOf('Column Filter', module)
|
||||
.add('Custom Select Filter', () => <CustomSelectFilter />)
|
||||
.add('Custom Number Filter', () => <CustomNumberFilter />)
|
||||
.add('Custom Date Filter', () => <CustomDateFilter />)
|
||||
.add('Custom MultiSelect Filter', () => <CustomMultiSelectFilter />)
|
||||
.add('Custom Filter Value', () => <CustomFilterValue />)
|
||||
.add('Programmatically Text Filter', () => <ProgrammaticallyTextFilter />)
|
||||
.add('Programmatically Select Filter', () => <ProgrammaticallySelectFilter />)
|
||||
.add('Programmatically Number Filter', () => <ProgrammaticallyNumberFilter />)
|
||||
.add('Programmatically Date Filter', () => <ProgrammaticallyDateFilter />)
|
||||
.add('Programmatically Multi Select Filter', () => <ProgrammaticallyMultiSelectFilter />)
|
||||
.add('Custom Filter', () => <CustomFilter />)
|
||||
.add('Advance Custom Filter', () => <AdvanceCustomFilter />);
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ You can get all types of filters via import and these filters are a factory func
|
||||
|
||||
* TextFilter
|
||||
* SelectFilter
|
||||
* MultiSelectFilter
|
||||
* NumberFilter
|
||||
* DateFilter
|
||||
* CustomFilter
|
||||
@ -114,6 +115,52 @@ const qualityFilter = selectFilter({
|
||||
// omit...
|
||||
```
|
||||
|
||||
## MultiSelect Filter
|
||||
|
||||
A quick example:
|
||||
|
||||
```js
|
||||
import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
|
||||
|
||||
// omit...
|
||||
const selectOptions = {
|
||||
0: 'good',
|
||||
1: 'Bad',
|
||||
2: 'unknown'
|
||||
};
|
||||
|
||||
const columns = [
|
||||
..., {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quailty',
|
||||
formatter: cell => selectOptions[cell],
|
||||
filter: multiSelectFilter({
|
||||
options: selectOptions
|
||||
})
|
||||
}];
|
||||
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
|
||||
```
|
||||
|
||||
Following is an example for custom select filter:
|
||||
|
||||
```js
|
||||
import filterFactory, { multiSelectFilter, Comparator } from 'react-bootstrap-table2-filter';
|
||||
// omit...
|
||||
|
||||
const qualityFilter = multiSelectFilter({
|
||||
options: selectOptions,
|
||||
placeholder: 'My Custom PlaceHolder', // custom the input placeholder
|
||||
className: 'my-custom-text-filter', // custom classname on input
|
||||
defaultValue: '2', // default filtering value
|
||||
comparator: Comparator.LIKE, // default is Comparator.EQ
|
||||
style: { ... }, // your custom styles on input
|
||||
withoutEmptyOption: true // hide the default select option
|
||||
});
|
||||
|
||||
// omit...
|
||||
```
|
||||
|
||||
## Number Filter
|
||||
|
||||
```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
|
||||
|
||||
152
packages/react-bootstrap-table2-filter/src/components/multiselect.js
vendored
Normal file
152
packages/react-bootstrap-table2-filter/src/components/multiselect.js
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
/* eslint react/require-default-props: 0 */
|
||||
/* eslint no-return-assign: 0 */
|
||||
/* eslint no-param-reassign: 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);
|
||||
this.applyFilter = this.applyFilter.bind(this);
|
||||
const isSelected = props.defaultValue.map(item => props.options[item]).length > 0;
|
||||
this.state = { isSelected };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { getFilter } = this.props;
|
||||
|
||||
const value = getSelections(this.selectInput);
|
||||
if (value && value.length > 0) {
|
||||
this.applyFilter(value);
|
||||
}
|
||||
|
||||
// export onFilter function to allow users to access
|
||||
if (getFilter) {
|
||||
getFilter((filterVal) => {
|
||||
this.selectInput.value = filterVal;
|
||||
this.applyFilter(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) {
|
||||
this.applyFilter(this.selectInput.value);
|
||||
}
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
const optionTags = [];
|
||||
const { options, placeholder, column, withoutEmptyOption } = this.props;
|
||||
if (!withoutEmptyOption) {
|
||||
optionTags.push((
|
||||
<option key="-1" value="">{ placeholder || `Select ${column.text}...` }</option>
|
||||
));
|
||||
}
|
||||
Object.keys(options).forEach(key =>
|
||||
optionTags.push(<option key={ key } value={ key }>{ options[key] }</option>)
|
||||
);
|
||||
return optionTags;
|
||||
}
|
||||
|
||||
cleanFiltered() {
|
||||
const value = (this.props.defaultValue !== undefined) ? this.props.defaultValue : [];
|
||||
this.selectInput.value = value;
|
||||
this.applyFilter(value);
|
||||
}
|
||||
|
||||
applyFilter(value) {
|
||||
if (value.length === 1 && value[0] === '') {
|
||||
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.applyFilter(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 (
|
||||
<select
|
||||
{ ...rest }
|
||||
ref={ n => this.selectInput = n }
|
||||
style={ style }
|
||||
multiple
|
||||
className={ selectClass }
|
||||
onChange={ this.filter }
|
||||
onClick={ e => e.stopPropagation() }
|
||||
defaultValue={ defaultValue !== undefined ? defaultValue : '' }
|
||||
>
|
||||
{ this.getOptions() }
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -1,6 +1,7 @@
|
||||
export const FILTER_TYPE = {
|
||||
TEXT: 'TEXT',
|
||||
SELECT: 'SELECT',
|
||||
MULTISELECT: 'MULTISELECT',
|
||||
NUMBER: 'NUMBER',
|
||||
DATE: 'DATE'
|
||||
};
|
||||
|
||||
@ -187,6 +187,26 @@ export const filterByDate = _ => (
|
||||
});
|
||||
};
|
||||
|
||||
export const filterByArray = _ => (
|
||||
data,
|
||||
dataField,
|
||||
{ filterVal, comparator }
|
||||
) => {
|
||||
if (filterVal.length === 0) return data;
|
||||
const refinedFilterVal = filterVal
|
||||
.filter(x => _.isDefined(x))
|
||||
.map(x => x.toString());
|
||||
return data.filter((row) => {
|
||||
const cell = _.get(row, dataField);
|
||||
let cellStr = _.isDefined(cell) ? cell.toString() : '';
|
||||
if (comparator === EQ) {
|
||||
return refinedFilterVal.indexOf(cellStr) !== -1;
|
||||
}
|
||||
cellStr = cellStr.toLocaleUpperCase();
|
||||
return refinedFilterVal.some(item => cellStr.indexOf(item.toLocaleUpperCase()) !== -1);
|
||||
});
|
||||
};
|
||||
|
||||
export const filterFactory = _ => (filterType) => {
|
||||
let filterFn;
|
||||
switch (filterType) {
|
||||
@ -194,6 +214,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;
|
||||
|
||||
@ -53,12 +53,20 @@ 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;
|
||||
|
||||
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 };
|
||||
|
||||
@ -0,0 +1,354 @@
|
||||
import 'jsdom-global/register';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { mount } from 'enzyme';
|
||||
import MultiSelectFilter from '../../src/components/multiselect';
|
||||
import { FILTER_TYPE } from '../../src/const';
|
||||
|
||||
|
||||
describe('Multi Select Filter', () => {
|
||||
let wrapper;
|
||||
let instance;
|
||||
|
||||
// onFilter(x)(y) = filter result
|
||||
const onFilter = sinon.stub();
|
||||
const onFilterFirstReturn = sinon.stub();
|
||||
|
||||
const column = {
|
||||
dataField: 'quality',
|
||||
text: 'Product Quality'
|
||||
};
|
||||
|
||||
const options = {
|
||||
0: 'Bad',
|
||||
1: 'Good',
|
||||
2: 'Unknown'
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
onFilter.reset();
|
||||
onFilterFirstReturn.reset();
|
||||
|
||||
onFilter.returns(onFilterFirstReturn);
|
||||
});
|
||||
|
||||
describe('initialization', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter onFilter={ onFilter } column={ column } options={ options } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
it('should have correct state', () => {
|
||||
expect(instance.state.isSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should rendering component successfully', () => {
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('select')).toHaveLength(1);
|
||||
expect(wrapper.find('.select-filter')).toHaveLength(1);
|
||||
expect(wrapper.find('.placeholder-selected')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should rendering select options correctly', () => {
|
||||
const select = wrapper.find('select');
|
||||
expect(select.find('option')).toHaveLength(Object.keys(options).length + 1);
|
||||
expect(select.childAt(0).text()).toEqual(`Select ${column.text}...`);
|
||||
|
||||
Object.keys(options).forEach((key, i) => {
|
||||
expect(select.childAt(i + 1).prop('value')).toEqual(key);
|
||||
expect(select.childAt(i + 1).text()).toEqual(options[key]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when defaultValue is defined', () => {
|
||||
let defaultValue;
|
||||
|
||||
describe('and it is valid', () => {
|
||||
beforeEach(() => {
|
||||
defaultValue = ['0'];
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter
|
||||
onFilter={ onFilter }
|
||||
column={ column }
|
||||
options={ options }
|
||||
defaultValue={ defaultValue }
|
||||
/>
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
it('should have correct state', () => {
|
||||
expect(instance.state.isSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should rendering component successfully', () => {
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('.placeholder-selected')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should calling onFilter on componentDidMount', () => {
|
||||
expect(onFilter.calledOnce).toBeTruthy();
|
||||
expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
|
||||
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
|
||||
expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when props.getFilter is defined', () => {
|
||||
let programmaticallyFilter;
|
||||
|
||||
const filterValue = ['foo'];
|
||||
|
||||
const getFilter = (filter) => {
|
||||
programmaticallyFilter = filter;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter
|
||||
onFilter={ onFilter }
|
||||
column={ column }
|
||||
options={ options }
|
||||
getFilter={ getFilter }
|
||||
/>
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
|
||||
programmaticallyFilter(filterValue);
|
||||
});
|
||||
|
||||
it('should do onFilter correctly when exported function was executed', () => {
|
||||
expect(onFilter.calledOnce).toBeTruthy();
|
||||
expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
|
||||
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
|
||||
expect(onFilterFirstReturn.calledWith(filterValue)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should setState correctly when exported function was executed', () => {
|
||||
expect(instance.state.isSelected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when placeholder is defined', () => {
|
||||
const placeholder = 'test';
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter
|
||||
onFilter={ onFilter }
|
||||
column={ column }
|
||||
options={ options }
|
||||
placeholder={ placeholder }
|
||||
/>
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
it('should rendering component successfully', () => {
|
||||
expect(wrapper).toHaveLength(1);
|
||||
const select = wrapper.find('select');
|
||||
expect(select.childAt(0).text()).toEqual(placeholder);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when style is defined', () => {
|
||||
const style = { backgroundColor: 'red' };
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter
|
||||
onFilter={ onFilter }
|
||||
column={ column }
|
||||
options={ options }
|
||||
style={ style }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should rendering component successfully', () => {
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('select').prop('style')).toEqual(style);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when withoutEmptyOption is defined', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter
|
||||
onFilter={ onFilter }
|
||||
column={ column }
|
||||
options={ options }
|
||||
withoutEmptyOption
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should rendering select without default empty option', () => {
|
||||
const select = wrapper.find('select');
|
||||
expect(select.find('option')).toHaveLength(Object.keys(options).length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('componentDidUpdate', () => {
|
||||
let prevProps;
|
||||
|
||||
describe('when props.defaultValue is diff from prevProps.defaultValue', () => {
|
||||
const defaultValue = ['0'];
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter
|
||||
onFilter={ onFilter }
|
||||
column={ column }
|
||||
options={ options }
|
||||
defaultValue={ defaultValue }
|
||||
/>
|
||||
);
|
||||
prevProps = {
|
||||
column,
|
||||
options,
|
||||
defaultValue: ['1']
|
||||
};
|
||||
instance = wrapper.instance();
|
||||
instance.componentDidUpdate(prevProps);
|
||||
});
|
||||
|
||||
it('should update', () => {
|
||||
expect(onFilter.callCount).toBe(2);
|
||||
expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
|
||||
expect(onFilterFirstReturn.callCount).toBe(2);
|
||||
expect(onFilterFirstReturn.calledWith(instance.props.defaultValue)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when props.options is diff from prevProps.options', () => {
|
||||
const defaultValue = ['0'];
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter
|
||||
onFilter={ onFilter }
|
||||
column={ column }
|
||||
options={ {
|
||||
...options,
|
||||
3: 'Best'
|
||||
} }
|
||||
defaultValue={ defaultValue }
|
||||
/>
|
||||
);
|
||||
prevProps = {
|
||||
column,
|
||||
options
|
||||
};
|
||||
instance = wrapper.instance();
|
||||
instance.componentDidUpdate(prevProps);
|
||||
});
|
||||
|
||||
it('should update', () => {
|
||||
expect(onFilter.callCount).toBe(2);
|
||||
expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
|
||||
expect(onFilterFirstReturn.callCount).toBe(2);
|
||||
expect(onFilterFirstReturn.calledWith(instance.props.defaultValue)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanFiltered', () => {
|
||||
describe('when props.defaultValue is defined', () => {
|
||||
const defaultValue = ['0'];
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter
|
||||
onFilter={ onFilter }
|
||||
column={ column }
|
||||
options={ options }
|
||||
defaultValue={ defaultValue }
|
||||
/>
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
instance.cleanFiltered();
|
||||
});
|
||||
|
||||
it('should setting state correctly', () => {
|
||||
expect(instance.state.isSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should calling onFilter correctly', () => {
|
||||
expect(onFilter.callCount).toBe(2);
|
||||
expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
|
||||
expect(onFilterFirstReturn.callCount).toBe(2);
|
||||
expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when props.defaultValue is not defined', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter
|
||||
onFilter={ onFilter }
|
||||
column={ column }
|
||||
options={ options }
|
||||
/>
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
instance.cleanFiltered();
|
||||
});
|
||||
|
||||
it('should setting state correctly', () => {
|
||||
expect(instance.state.isSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should calling onFilter correctly', () => {
|
||||
expect(onFilter.callCount).toBe(1);
|
||||
expect(onFilterFirstReturn.callCount).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyFilter', () => {
|
||||
const values = ['2'];
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter onFilter={ onFilter } column={ column } options={ options } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
instance.applyFilter(values);
|
||||
});
|
||||
|
||||
it('should setting state correctly', () => {
|
||||
expect(instance.state.isSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should calling onFilter correctly', () => {
|
||||
expect(onFilter.calledOnce).toBeTruthy();
|
||||
expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
|
||||
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
|
||||
expect(onFilterFirstReturn.calledWith(values)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('filter', () => {
|
||||
const event = { target: { selectedOptions: [{ value: 'tester' }] } };
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<MultiSelectFilter onFilter={ onFilter } column={ column } options={ options } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
instance.filter(event);
|
||||
});
|
||||
|
||||
it('should setting state correctly', () => {
|
||||
expect(instance.state.isSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should calling onFilter correctly', () => {
|
||||
expect(onFilter.calledOnce).toBeTruthy();
|
||||
expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
|
||||
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
|
||||
expect(onFilterFirstReturn.calledWith(
|
||||
event.target.selectedOptions.map(item => item.value))).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -123,6 +123,55 @@ describe('filter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterByArray', () => {
|
||||
beforeEach(() => {
|
||||
filterFn = filters(store, columns, _);
|
||||
});
|
||||
|
||||
describe('when filter value is empty array', () => {
|
||||
it('should return original data', () => {
|
||||
currFilters.name = {
|
||||
filterVal: [],
|
||||
filterType: FILTER_TYPE.MULTISELECT
|
||||
};
|
||||
|
||||
const result = filterFn(currFilters);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(store.data.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when filter value is not an empty array', () => {
|
||||
describe(`and comparator is ${EQ}`, () => {
|
||||
it('should return data correctly', () => {
|
||||
currFilters.price = {
|
||||
filterVal: [201, 203],
|
||||
filterType: FILTER_TYPE.MULTISELECT,
|
||||
comparator: EQ
|
||||
};
|
||||
|
||||
const result = filterFn(currFilters);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`and comparator is ${LIKE}`, () => {
|
||||
it('should return data correctly', () => {
|
||||
currFilters.name = {
|
||||
filterVal: ['name 3', '5'],
|
||||
filterType: FILTER_TYPE.MULTISELECT,
|
||||
comparator: LIKE
|
||||
};
|
||||
|
||||
const result = filterFn(currFilters);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterByNumber', () => {
|
||||
beforeEach(() => {
|
||||
filterFn = filters(store, columns, _);
|
||||
|
||||
@ -163,7 +163,7 @@ describe('Wrapper', () => {
|
||||
});
|
||||
|
||||
describe('when filterVal is empty or undefined', () => {
|
||||
const filterVals = ['', undefined];
|
||||
const filterVals = ['', undefined, []];
|
||||
|
||||
it('should setting store object correctly', () => {
|
||||
filterVals.forEach((filterVal) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user