Skip to content

Commit

Permalink
[DataGridPro] Filtering on Column Header (#7760)
Browse files Browse the repository at this point in the history
  • Loading branch information
MBilalShafi authored May 18, 2023
1 parent d199f47 commit bb3a96c
Show file tree
Hide file tree
Showing 121 changed files with 3,744 additions and 208 deletions.
124 changes: 124 additions & 0 deletions docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import {
DataGridPro,
useGridSelector,
gridFilterModelSelector,
useGridApiContext,
} from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';

function CustomHeaderFilter(props) {
const { colDef, width, height, hasFocus, colIndex, tabIndex } = props;
const apiRef = useGridApiContext();
const cellRef = React.useRef(null);

React.useLayoutEffect(() => {
if (hasFocus && cellRef.current) {
const focusableElement = cellRef.current.querySelector('[tabindex="0"]');
const elementToFocus = focusableElement || cellRef.current;
elementToFocus?.focus();
}
}, [apiRef, hasFocus]);

const publish = React.useCallback(
(eventName, propHandler) => (event) => {
apiRef.current.publishEvent(
eventName,
apiRef.current.getColumnHeaderParams(colDef.field),
event,
);

if (propHandler) {
propHandler(event);
}
},
[apiRef, colDef.field],
);

const onMouseDown = React.useCallback(
(event) => {
if (!hasFocus) {
cellRef.current?.focus();
apiRef.current.setColumnHeaderFilterFocus(colDef.field, event);
}
},
[apiRef, colDef.field, hasFocus],
);

const mouseEventsHandlers = React.useMemo(
() => ({
onKeyDown: publish('headerFilterKeyDown'),
onClick: publish('headerFilterClick'),
onMouseDown: publish('headerFilterMouseDown', onMouseDown),
}),
[publish, onMouseDown],
);

const filterModel = useGridSelector(apiRef, gridFilterModelSelector);
const activeFiltersCount =
filterModel.items?.filter(({ field }) => field === colDef.field).length ?? 0;

return (
<Stack
sx={{ outline: hasFocus ? 'solid #1976d2 1px' : '' }}
tabIndex={tabIndex}
ref={cellRef}
data-field={colDef.field}
width={width}
height={height}
justifyContent="center"
alignItems="center"
role="columnheader"
aria-colindex={colIndex + 1}
aria-label={colDef.headerName ?? colDef.field}
{...mouseEventsHandlers}
>
<Button
centerRipple={false}
onClick={() => apiRef.current.showFilterPanel(colDef.field)}
>
{activeFiltersCount > 0 ? `${activeFiltersCount} active` : 'Add'} filters
</Button>
</Stack>
);
}

CustomHeaderFilter.propTypes = {
colDef: PropTypes.object.isRequired,
colIndex: PropTypes.number.isRequired,
hasFocus: PropTypes.bool,
height: PropTypes.number.isRequired,
tabIndex: PropTypes.oneOf([-1, 0]).isRequired,
width: PropTypes.number.isRequired,
};

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

return (
<div style={{ height: 400, width: '100%' }}>
<DataGridPro
{...data}
initialState={{
...data.initialState,
columns: {
columnVisibilityModel: {
avatar: false,
id: false,
},
},
}}
slots={{
headerFilterCell: CustomHeaderFilter,
}}
unstable_headerFilters
/>
</div>
);
}
121 changes: 121 additions & 0 deletions docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import * as React from 'react';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import {
DataGridPro,
useGridSelector,
gridFilterModelSelector,
useGridApiContext,
GridHeaderFilterCellProps,
GridHeaderFilterEventLookup,
} from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';

function CustomHeaderFilter(props: GridHeaderFilterCellProps) {
const { colDef, width, height, hasFocus, colIndex, tabIndex } = props;
const apiRef = useGridApiContext();
const cellRef = React.useRef<HTMLDivElement>(null);

React.useLayoutEffect(() => {
if (hasFocus && cellRef.current) {
const focusableElement =
cellRef.current!.querySelector<HTMLElement>('[tabindex="0"]');
const elementToFocus = focusableElement || cellRef.current;
elementToFocus?.focus();
}
}, [apiRef, hasFocus]);

const publish = React.useCallback(
(
eventName: keyof GridHeaderFilterEventLookup,
propHandler?: React.EventHandler<any>,
) =>
(event: React.SyntheticEvent) => {
apiRef.current.publishEvent(
eventName,
apiRef.current.getColumnHeaderParams(colDef.field),
event as any,
);

if (propHandler) {
propHandler(event);
}
},
[apiRef, colDef.field],
);

const onMouseDown = React.useCallback(
(event: React.MouseEvent) => {
if (!hasFocus) {
cellRef.current?.focus();
apiRef.current.setColumnHeaderFilterFocus(colDef.field, event);
}
},
[apiRef, colDef.field, hasFocus],
);

const mouseEventsHandlers = React.useMemo(
() => ({
onKeyDown: publish('headerFilterKeyDown'),
onClick: publish('headerFilterClick'),
onMouseDown: publish('headerFilterMouseDown', onMouseDown),
}),
[publish, onMouseDown],
);

const filterModel = useGridSelector(apiRef, gridFilterModelSelector);
const activeFiltersCount =
filterModel.items?.filter(({ field }) => field === colDef.field).length ?? 0;

return (
<Stack
sx={{ outline: hasFocus ? 'solid #1976d2 1px' : '' }}
tabIndex={tabIndex}
ref={cellRef}
data-field={colDef.field}
width={width}
height={height}
justifyContent="center"
alignItems="center"
role="columnheader"
aria-colindex={colIndex + 1}
aria-label={colDef.headerName ?? colDef.field}
{...mouseEventsHandlers}
>
<Button
centerRipple={false}
onClick={() => apiRef.current.showFilterPanel(colDef.field)}
>
{activeFiltersCount > 0 ? `${activeFiltersCount} active` : 'Add'} filters
</Button>
</Stack>
);
}

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

return (
<div style={{ height: 400, width: '100%' }}>
<DataGridPro
{...data}
initialState={{
...data.initialState,
columns: {
columnVisibilityModel: {
avatar: false,
id: false,
},
},
}}
slots={{
headerFilterCell: CustomHeaderFilter,
}}
unstable_headerFilters
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<DataGridPro
{...data}
initialState={{
...data.initialState,
columns: {
columnVisibilityModel: {
avatar: false,
id: false,
},
},
}}
slots={{
headerFilterCell: CustomHeaderFilter,
}}
unstable_headerFilters
/>
119 changes: 119 additions & 0 deletions docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Box from '@mui/material/Box';
import Rating from '@mui/material/Rating';
import { DataGridPro, getGridNumericOperators } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';

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

function RatingInputValue(props) {
const { item, applyValue, focusElementRef, headerFilterMenu, clearButton } = props;

const ratingRef = React.useRef(null);
React.useImperativeHandle(focusElementRef, () => ({
focus: () => {
ratingRef.current
.querySelector(`input[value="${Number(item.value) || ''}"]`)
.focus();
},
}));

const handleFilterChange = (event, newValue) => {
applyValue({ ...item, value: newValue });
};

return (
<React.Fragment>
{headerFilterMenu}
<Box
sx={{
display: 'inline-flex',
flexDirection: 'row',
alignItems: 'center',
height: '100%',
pl: '10px',
bl: '1px solid lightgrey',
}}
>
<Rating
name="custom-rating-filter-operator"
placeholder="Filter value"
value={Number(item.value)}
onChange={handleFilterChange}
precision={0.5}
ref={ratingRef}
/>
</Box>
{clearButton}
</React.Fragment>
);
}

RatingInputValue.propTypes = {
applyValue: PropTypes.func.isRequired,
clearButton: PropTypes.node,
focusElementRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({
current: PropTypes.any.isRequired,
}),
]),
headerFilterMenu: PropTypes.node,
item: PropTypes.shape({
/**
* The column from which we want to filter the rows.
*/
field: 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.
*/
operator: PropTypes.string.isRequired,
/**
* 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 default function CustomHeaderFilterOperatorDataGridPro() {
const { data } = useDemoData({
dataSet: 'Employee',
rowLength: 100,
visibleFields: VISIBLE_FIELDS,
});

const columns = React.useMemo(
() =>
data.columns.map((colDef) => {
if (colDef.field === 'rating') {
return {
...colDef,
minWidth: 200,
filterOperators: getGridNumericOperators()
.filter((operator) => operator.value !== 'isAnyOf')
.map((operator) => ({
...operator,
InputComponent: operator.InputComponent
? RatingInputValue
: undefined,
})),
};
}
return colDef;
}),
[data.columns],
);

return (
<div style={{ height: 400, width: '100%' }}>
<DataGridPro {...data} columns={columns} unstable_headerFilters />
</div>
);
}
Loading

0 comments on commit bb3a96c

Please sign in to comment.