DevExpress / devextreme-reactive

Business React components for Bootstrap and Material-UI
https://devexpress.github.io/devextreme-reactive/
Other
2.08k stars 383 forks source link

"between" filter operation for filtering items within a range of values #1399

Open TaiHovanky opened 6 years ago

TaiHovanky commented 6 years ago

I'm using ...

Is there a way to filter for items between a defined range for the DevExtreme Reactive grid? I noticed in the documentation for the jQuery version (https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/Filtering/jQuery/Light/) when you filter by Sales Amount, the "between" operation is available, allowing you to filter items within a range. In the docs for the React grid, "contains", "notContains", "startsWith", "endsWith", "equal", "notEqual", "greaterThan", "graterThenOrEqual", "lessThan", and "lessThanOrEqual" are the only built-in operations. Are there any plans to add "between" as an operation? Or is there a known workaround if I'm trying to implement filtering by range (for numeric values)?

SergeyAlexeev commented 6 years ago

Hi,

Currently, the 'between' filter operation isn't supported out of the box. However, our React Grid has quite flexible customization possibilities that allow you to implement the required functionality manually.

The thing is that you need to implement custom filter editors for the 'between' filter operation. You can use the cellComponent property of the TableFilterRow plugin.

The second issue is custom filter logic. Use the columnExtensions property of the IntegratedFiltering plugin to define a custom filter predicate.

I've created a sample that shows this approach in action. You can use it as a starting point.

Anyway, we'll discuss the possibility of supporting the 'between' filter operation in the future.

TaiHovanky commented 6 years ago

Hi Sergey,

I ended up using the cellComponent property of the TableFilterRow plugin, and creating a custom filter cell. Then I created a custom filter predicate as you said, and it worked.

For the custom filter cell, I used the TableFilterRow.FilterSelector plugin and added 'between' to the array of availableValues. I set it so that if the user selects the 'between' option, the filter cell displays 2 inputs.

`export const LESS_THAN = 'lessThan'; export const LESS_THAN_EQUAL = 'lessThanOrEqual'; export const GREATER_THAN = 'greaterThan'; export const GREATER_THAN_EQUAL = 'greaterThanOrEqual'; export const NOT_EQUAL = 'notEqual'; export const BETWEEN = 'between'; export const EQUAL = 'equal';

const FilterIcon = (filterSelector) => { let icon = 'equals'; switch (filterSelector.type) { case LESS_THAN: icon = 'less-than'; break; case LESS_THAN_EQUAL: icon = 'less-than-equal'; break; case GREATER_THAN: icon = 'greater-than'; break; case GREATER_THAN_EQUAL: icon = 'greater-than-equal'; break; case NOT_EQUAL: icon = 'not-equal'; break; case BETWEEN: icon = 'arrows-alt-h'; break; default: icon = 'equals'; } return (

);

};

class NumericFilterInput extends React.Component { constructor(props) { super(props);

    this.state = {
        filterValues: {
            filter1: '',
            filter2: ''
        },
        filterOperation: 'equal'
    };
    this.handleFilterChange = this.handleFilterChange.bind(this);
    this.handleFilterOperationChange = this.handleFilterOperationChange.bind(this);
    this.renderFilterInput = this.renderFilterInput.bind(this);
}

handleFilterOperationChange(filterOperation) {
    this.setState({
        filterOperation
    });
}

handleFilterChange(value, filter) {
    this.setState({
        ...this.state,
        filterValues: {
            ...this.state.filterValues,
            [filter]: value
        }
    }, () => {
        this.props.onFilter(this.state);
    });
}

renderFilterInput() {
    if (this.state.filterOperation === 'between') {
        return (
            <span className="numeric-filter-between">
                <Input
                    inputProps={{
                        min: 0,
                        placeholder: 'Filter...'
                    }}
                    onChange={e => this.handleFilterChange(e.target.value, 'filter1')}
                    type="number"
                    value={this.state.filterValues.filter1}
                />
                <Input
                    inputProps={{
                        min: 0,
                        placeholder: 'Filter...'
                    }}
                    onChange={e => this.handleFilterChange(e.target.value, 'filter2')}
                    type="number"
                    value={this.state.filterValues.filter2}
                />
            </span>
        );
    }
    return (
        <Input
            inputProps={{
                min: 0,
                placeholder: 'Filter...'
            }}
            onChange={e => this.handleFilterChange(e.target.value, 'filter1')}
            type="number"
            value={this.state.filterValues.filter1}
        />
    );
}

render() {
    return (
        <div className="numeric-filter__div">
            <TableFilterRow.FilterSelector
                availableValues={[
                    'equal',
                    'notEqual',
                    'greaterThan',
                    'greaterThanOrEqual',
                    'lessThan',
                    'lessThanOrEqual',
                    'between'
                ]}
                getMessage={message => message}
                iconComponent={FilterIcon}
                onChange={this.handleFilterOperationChange}
                value={this.state.filterOperation}
            />
            {this.renderFilterInput()}
        </div>
    );
}

}`

The NumericFilterInput is then wrapped by the FilterCell component shown below:

`const FilterCell = (props) => { const { column } = props;

if (column.name === 'Miles') {
    return (
        <TableFilterRow.Cell {...props}>
            <NumericFilterInput {...props} />
        </TableFilterRow.Cell>
    );
}
return <TableFilterRow.Cell {...props} />;

};`

Lastly, in the grid component file, I created the custom filter predicates and put it into the columnExtensions property of the IntegratedFiltering plugin. I made a filterPredicate function that handles all of the cases (equal, not-equal, less than, etc.) and filters rows accordingly.

this.filteringColumnExtensions = [ { columnName: 'Miles', predicate: (value, filter, row) => filterPredicate(value, filter, row, 'Miles') } ];

...and in the IntegratedFiltering and TableFilterRow plugins:

`

<TableFilterRow cellComponent={FilterCell} showFilterSelector />`

sli commented 4 years ago

Just a heads up that the above example is missing code due to formatting issues (FilterIcon has no return value) and no longer seems to work with the current version of the datagrid, or never worked as it was posted due to similar formatting issues.

I feel like I need to mention this because other, similar issues are referencing the above comment, despite major issues preventing its use.