'React materil-ui/mui data-grid-pro with muliple select in filter definition
See this codesandbox
i tried to add a custom filter operator "oneOf" to the filter on my data-grid based on the example for "between" searches in the mui data-grid doc.
This should allow the user to select multiple values of a "singleSelect" column and filter base on a JS includes() filter.
Using the "or" and chaining multiple rows of filters for the same column would block using an "and" filter for a different column, as the "and"/"or" only works for all filters. Which is not wanted...
Works somewhat if using native select, with multiple, but then the select options are always shown and doesn't fit in the row of the filter popup for that column.
If using "MenuItem" and mui Select the filter popup and the Select closes as soon as the Select Options are shown. i assume because of lost "focus" of the filter popup.
The single select works using a TextInput with prop "select", so i assume there the focus remains on the input, even if the select option are shown.
How can i fix this?
Best solution would be to use mui Select and stop the filter popup of closing if the Select is open.
Solution 1:[1]
Found one more solution, using Autocompleter as this is an input filed. Needs some hacks, but see code and the updated sandbox The filter box is limited in space, and changing that is ugly, see the sass file in the sandbox.
When using the disablePortal mention in comments i have the issue that the drop down is somewhere and i cant place it below the input, and just floating around looks not nice...
PS: i'm still on v4 and sandbox is v5 so the valueOptions are not copied right now in the sandbox...
/**
* A DataGrid operator to filter a "singleSelect" column by multiple values.
*
* @param props
* @returns {JSX.Element}
* @constructor
*/
function OneOfValues(props) {
const {item, applyValue, type, apiRef, focusElementRef, ...others} = props;
const filterTimeout = React.useRef();
const [inputValue, setInputValue] = useState('');
const [applying, setIsApplying] = React.useState(false);
const [options, setOptions] = React.useState([]);
const [completerState, setCompleterState] = React.useState([]);
const icon = <CheckBoxOutlineBlankIcon fontSize='small' />;
const checkedIcon = <CheckBoxIcon fontSize='small' />;
useEffect(() => {
setOptions(renderAutoCompleteOptions(apiRef.current.getColumn(item.columnField), apiRef.current).filter((v) => !!v.value));
return () => {
clearTimeout(filterTimeout.current);
};
}, []);
const updateFilterValue = (values) => {
setIsApplying(true);
applyValue({...item, value: values});
setIsApplying(false);
};
const handleAutoFilterChange = (event, selected) => {
console.debug('handleAutoFilterChange', selected);
setCompleterState(selected);
updateFilterValue(selected.map((o) => o.value));
};
const renderAutoCompleteOptions = ({valueOptions, valueFormatter, field}, api) => {
const iterableColumnValues = typeof valueOptions === 'function' ? ['', ...valueOptions({field})] : ['', ...(valueOptions || [])];
return iterableColumnValues.map((option) =>
typeof option === 'object'
? {label: option.label, value: option.value}
: {label: valueFormatter && option !== '' ? valueFormatter({value: option, field, api}) : option, value: option}
);
};
const convertListToOptions = (values, options) => {
console.debug('huhu', values, options);
if (!values || !Array.isArray(values)) return [];
const selected = options.filter((o) => values.includes(o.value));
console.log('convertListToOptions', values, options, selected);
return selected;
};
React.useEffect(() => {
const itemValue = item.value ?? [];
const options = renderAutoCompleteOptions(apiRef.current.getColumn(item.columnField), apiRef.current).filter((v) => !!v.value);
const state = convertListToOptions(itemValue, options);
console.log('useEffect', item.value, state);
setCompleterState(state);
}, [apiRef, item.columnField, item.value]);
console.log('completerState', completerState);
return (
<Box
style={{
display: 'inline-flex',
flexDirection: 'row',
alignItems: 'end',
height: 48,
pl: '20px',
}}
>
<Autocomplete
id={`filter-value-autocomplete-${item.columnField}`}
multiple
disableCloseOnSelect
size='small'
limitTags={3}
value={completerState}
inputValue={inputValue}
options={options}
getOptionLabel={(option) => option.label}
getOptionSelected={(option, value) => option.value === value.value}
onChange={handleAutoFilterChange}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
renderOption={(option, {selected}) => (
<>
<Checkbox icon={icon} checkedIcon={checkedIcon} style={{marginRight: 8}} checked={selected} />
{option.label}
</>
)}
renderTags={(tagValue, getTagProps) =>
<Chip size='small' label={tagValue.length + " selected"} key={1} />
}
renderInput={(params) => (
<TextField
{...params}
// InputProps={applying ? {endAdornment: <SyncIcon />} : null}
inputRef={focusElementRef}
variant='standard'
label='Values'
/>
)}
style={{minWidth: '190px', width: '100%', display: 'inline-flex', flexDirection: 'row', alignItems: 'end', height: 48, pl: '20px'}}
/>
</Box>
);
}
OneOfValues.propTypes = {
apiRef: PropTypes.any.isRequired,
applyValue: PropTypes.func.isRequired,
focusElementRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({
current: PropTypes.any.isRequired,
}),
]),
item: PropTypes.shape({
/**
* The column from which we want to filter the rows.
*/
columnField: PropTypes.string.isRequired,
/**
* Must be unique.
* Only useful when the model contains several items.
*/ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* The name of the operator we want to apply.
*/ operatorValue: PropTypes.string,
/**
* The filtering value.
* The operator filtering function will decide for each row if the row values is correct compared to this value.
*/ value: PropTypes.any,
}).isRequired,
};
export const oneOfOperator = [
{
label: 'one of',
value: 'oneOf',
getApplyFilterFn: (filterItem) => {
if (!Array.isArray(filterItem.value) || filterItem.value.length !== 2) {
return null;
}
if (Array.isArray(filterItem.value) && filterItem.value.length > 0) {
return null;
}
return ({value}) => {
return value !== null && filterItem.value.includes(value);
};
},
InputComponent: OneOfValues,
},
...getGridStringOperators(),
];
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | A. Rabus |
