Skip to content

Commit

Permalink
[DataGrid] Allow to filter non-filterable columns programmatically (#…
Browse files Browse the repository at this point in the history
…11538)

Signed-off-by: Bilal Shafi <[email protected]>
Co-authored-by: Andrew Cherniavskii <[email protected]>
  • Loading branch information
MBilalShafi and cherniavskii authored Jan 13, 2024
1 parent 462aba4 commit 1a841bb
Show file tree
Hide file tree
Showing 14 changed files with 404 additions and 47 deletions.
46 changes: 46 additions & 0 deletions docs/data/data-grid/filtering/ReadOnlyFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from 'react';
import { DataGrid, GridToolbar } from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';

const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin'];

export default function ReadOnlyFilters() {
const { data } = useDemoData({
dataSet: 'Employee',
visibleFields: VISIBLE_FIELDS,
rowLength: 100,
});

const columns = React.useMemo(
() =>
data.columns.map((column) => ({
...column,
filterable: column.field !== 'name',
})),
[data.columns],
);

const [filterModel, setFilterModel] = React.useState({
items: [
{
field: 'name',
operator: 'contains',
value: 'a',
},
],
});

return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
{...data}
columns={columns}
slots={{
toolbar: GridToolbar,
}}
filterModel={filterModel}
onFilterModelChange={(newFilterModel) => setFilterModel(newFilterModel)}
/>
</div>
);
}
46 changes: 46 additions & 0 deletions docs/data/data-grid/filtering/ReadOnlyFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from 'react';
import { DataGrid, GridFilterModel, GridToolbar } from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';

const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin'];

export default function ReadOnlyFilters() {
const { data } = useDemoData({
dataSet: 'Employee',
visibleFields: VISIBLE_FIELDS,
rowLength: 100,
});

const columns = React.useMemo(
() =>
data.columns.map((column) => ({
...column,
filterable: column.field !== 'name',
})),
[data.columns],
);

const [filterModel, setFilterModel] = React.useState<GridFilterModel>({
items: [
{
field: 'name',
operator: 'contains',
value: 'a',
},
],
});

return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
{...data}
columns={columns}
slots={{
toolbar: GridToolbar,
}}
filterModel={filterModel}
onFilterModelChange={(newFilterModel) => setFilterModel(newFilterModel)}
/>
</div>
);
}
9 changes: 9 additions & 0 deletions docs/data/data-grid/filtering/ReadOnlyFilters.tsx.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<DataGrid
{...data}
columns={columns}
slots={{
toolbar: GridToolbar,
}}
filterModel={filterModel}
onFilterModelChange={(newFilterModel) => setFilterModel(newFilterModel)}
/>
20 changes: 20 additions & 0 deletions docs/data/data-grid/filtering/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,26 @@ In the example below, the _rating_ column can not be filtered.

{{"demo": "DisableFilteringGridSomeColumns.js", "bg": "inline", "defaultCodeOpen": false}}

### Filter non-filterable columns programmatically

You can initialize the `filterModel`, set the `filterModel` prop, or use the API method `apiRef.current.setFilterModel` to set the filters for non-filterable columns. These filters will be applied but will be read-only on the UI and the user won't be able to change them.

```jsx
const columns = [
{ field: 'name', filterable: false },
...otherColumns,
]

<DataGrid
filterModel={{
items: [{ field: 'name', operator: 'contains', value: 'a' }],
}}
columns={columns}
/>
```

{{"demo": "ReadOnlyFilters.js", "bg": "inline", "defaultCodeOpen": false}}

## Ignore diacritics (accents)

You can ignore diacritics (accents) when filtering the rows. See [Quick filter - Ignore diacritics (accents)](/x/react-data-grid/filtering/quick-filter/#ignore-diacritics-accents).
Expand Down
4 changes: 1 addition & 3 deletions docs/pages/x/api/data-grid/grid-filter-form.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,8 @@
"type": { "name": "arrayOf", "description": "Array&lt;'and'<br>&#124;&nbsp;'or'&gt;" },
"default": "[GridLogicOperator.And, GridLogicOperator.Or]"
},
"multiFilterOperator": {
"type": { "name": "enum", "description": "'and'<br>&#124;&nbsp;'or'" }
},
"operatorInputProps": { "type": { "name": "any" }, "default": "{}" },
"readOnly": { "type": { "name": "bool" }, "default": "false" },
"showMultiFilterOperators": { "type": { "name": "bool" } },
"valueInputProps": { "type": { "name": "any" }, "default": "{}" }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
"description": "Props passed to the logic operator input component."
},
"logicOperators": { "description": "Sets the available logic operators." },
"multiFilterOperator": { "description": "The current logic operator applied." },
"operatorInputProps": { "description": "Props passed to the operator input component." },
"readOnly": {
"description": "<code>true</code> if the filter is disabled/read only. i.e. <code>colDef.fiterable = false</code> but passed in <code>filterModel</code>"
},
"showMultiFilterOperators": {
"description": "If <code>true</code>, the logic operator field is visible."
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
GridColDef,
gridVisibleColumnFieldsSelector,
getDataGridUtilityClass,
useGridSelector,
gridFilterModelSelector,
gridFilterableColumnLookupSelector,
} from '@mui/x-data-grid';
import {
GridStateColDef,
Expand Down Expand Up @@ -82,7 +85,7 @@ const GridHeaderFilterCell = React.forwardRef<HTMLDivElement, GridHeaderFilterCe
} = props;

const apiRef = useGridPrivateApiContext();
const columnFields = gridVisibleColumnFieldsSelector(apiRef);
const columnFields = useGridSelector(apiRef, gridVisibleColumnFieldsSelector);
const rootProps = useGridRootProps();
const cellRef = React.useRef<HTMLDivElement>(null);
const handleRef = useForkRef(ref, cellRef);
Expand All @@ -92,9 +95,21 @@ const GridHeaderFilterCell = React.forwardRef<HTMLDivElement, GridHeaderFilterCe
const isEditing = gridHeaderFilteringEditFieldSelector(apiRef) === colDef.field;
const isMenuOpen = gridHeaderFilteringMenuSelector(apiRef) === colDef.field;

const filterModel = useGridSelector(apiRef, gridFilterModelSelector);
const filterableColumnsLookup = useGridSelector(apiRef, gridFilterableColumnLookupSelector);

const isFilterReadOnly = React.useMemo(() => {
if (!filterModel?.items.length) {
return false;
}
const filterModelItem = filterModel.items.find((it) => it.field === colDef.field);
return filterModelItem ? !filterableColumnsLookup[filterModelItem.field] : false;
}, [colDef.field, filterModel, filterableColumnsLookup]);

const currentOperator = filterOperators![0];

const InputComponent = colDef.filterable ? currentOperator!.InputComponent : null;
const InputComponent =
colDef.filterable || isFilterReadOnly ? currentOperator!.InputComponent : null;

const applyFilterChanges = React.useCallback(
(updatedItem: GridFilterItem) => {
Expand Down Expand Up @@ -130,7 +145,7 @@ const GridHeaderFilterCell = React.forwardRef<HTMLDivElement, GridHeaderFilterCe

const onKeyDown = React.useCallback(
(event: React.KeyboardEvent) => {
if (isMenuOpen || isNavigationKey(event.key)) {
if (isMenuOpen || isNavigationKey(event.key) || isFilterReadOnly) {
return;
}
switch (event.key) {
Expand Down Expand Up @@ -172,7 +187,16 @@ const GridHeaderFilterCell = React.forwardRef<HTMLDivElement, GridHeaderFilterCe
break;
}
},
[apiRef, colDef.field, colIndex, columnFields, headerFilterMenuRef, isEditing, isMenuOpen],
[
apiRef,
colDef.field,
colIndex,
columnFields,
headerFilterMenuRef,
isEditing,
isFilterReadOnly,
isMenuOpen,
],
);

const publish = React.useCallback(
Expand Down Expand Up @@ -277,10 +301,13 @@ const GridHeaderFilterCell = React.forwardRef<HTMLDivElement, GridHeaderFilterCe
isFilterActive={isFilterActive}
clearButton={
showClearIcon && isApplied ? (
<GridHeaderFilterClearButton onClick={clearFilterItem} />
<GridHeaderFilterClearButton
onClick={clearFilterItem}
disabled={isFilterReadOnly}
/>
) : null
}
disabled={isNoInputOperator}
disabled={isFilterReadOnly || isNoInputOperator}
tabIndex={-1}
InputLabelProps={null}
sx={colDef.type === 'date' || colDef.type === 'dateTime' ? dateSx : undefined}
Expand All @@ -292,6 +319,7 @@ const GridHeaderFilterCell = React.forwardRef<HTMLDivElement, GridHeaderFilterCe
operators={filterOperators!}
item={item}
field={colDef.field}
disabled={isFilterReadOnly}
applyFilterChanges={applyFilterChanges}
headerFilterMenuRef={headerFilterMenuRef}
buttonRef={buttonRef}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import * as React from 'react';
import { IconButtonProps } from '@mui/material/IconButton';
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';

interface GridHeaderFilterClearIconProps {
onClick: () => void;
}
interface GridHeaderFilterClearIconProps extends IconButtonProps {}

const sx = { padding: '2px' };

function GridHeaderFilterClearButton({ onClick }: GridHeaderFilterClearIconProps) {
function GridHeaderFilterClearButton(props: GridHeaderFilterClearIconProps) {
const rootProps = useGridRootProps();
return (
<rootProps.slots.baseIconButton
tabIndex={-1}
aria-label="Clear filter"
size="small"
onClick={onClick}
sx={sx}
{...props}
{...rootProps.slotProps?.baseIconButton}
>
<rootProps.slots.columnMenuClearIcon fontSize="inherit" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,17 @@ function GridHeaderFilterMenuContainer(props: {
applyFilterChanges: (item: GridFilterItem) => void;
headerFilterMenuRef: React.MutableRefObject<HTMLButtonElement | null>;
buttonRef: React.Ref<HTMLButtonElement>;
disabled?: boolean;
}) {
const { operators, item, field, buttonRef, headerFilterMenuRef, ...others } = props;
const {
operators,
item,
field,
buttonRef,
headerFilterMenuRef,
disabled = false,
...others
} = props;

const buttonId = useId();
const menuId = useId();
Expand Down Expand Up @@ -58,6 +67,7 @@ function GridHeaderFilterMenuContainer(props: {
size="small"
onClick={handleClick}
sx={sx}
disabled={disabled}
{...rootProps.slotProps?.baseIconButton}
>
<rootProps.slots.openFilterButtonIcon fontSize="small" />
Expand All @@ -83,6 +93,7 @@ GridHeaderFilterMenuContainer.propTypes = {
// ----------------------------------------------------------------------
applyFilterChanges: PropTypes.func.isRequired,
buttonRef: refType,
disabled: PropTypes.bool,
field: PropTypes.string.isRequired,
headerFilterMenuRef: PropTypes.shape({
current: PropTypes.object,
Expand Down
Loading

0 comments on commit 1a841bb

Please sign in to comment.