Skip to content

Commit

Permalink
[DataGrid] Add getFilterState method (#13418)
Browse files Browse the repository at this point in the history
  • Loading branch information
cherniavskii authored Jun 13, 2024
1 parent 722aa31 commit 70c6612
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 11 deletions.
88 changes: 88 additions & 0 deletions docs/data/data-grid/filtering-recipes/FilteredRowCount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { DataGridPro, useGridApiRef } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';

const predefinedFilters = [
{
label: 'All',
filterModel: { items: [] },
},
{
label: 'Filled',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Filled' }] },
},
{
label: 'Open',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Open' }] },
},
{
label: 'Rejected',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Rejected' }] },
},
{
label: 'Partially Filled',
filterModel: {
items: [{ field: 'status', operator: 'is', value: 'PartiallyFilled' }],
},
},
];

export default function FilteredRowCount() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 1000,
maxColumns: 10,
});

const apiRef = useGridApiRef();

const [predefinedFiltersRowCount, setPredefinedFiltersRowCount] = React.useState(
[],
);

const getFilteredRowsCount = React.useCallback(
(filterModel) => {
const { filteredRowsLookup } = apiRef.current.getFilterState(filterModel);
return Object.keys(filteredRowsLookup).filter(
(rowId) => filteredRowsLookup[rowId] === true,
).length;
},
[apiRef],
);

React.useEffect(() => {
// Calculate the row count for predefined filters
if (data.rows.length === 0) {
return;
}

setPredefinedFiltersRowCount(
predefinedFilters.map(({ filterModel }) => getFilteredRowsCount(filterModel)),
);
}, [apiRef, data.rows, getFilteredRowsCount]);

return (
<div style={{ overflow: 'hidden' }}>
<Stack direction="row" gap={1} mb={1} flexWrap="wrap">
{predefinedFilters.map(({ label, filterModel }, index) => {
const count = predefinedFiltersRowCount[index];
return (
<Button
key={label}
onClick={() => apiRef.current.setFilterModel(filterModel)}
variant="outlined"
>
{label} {count !== undefined ? `(${count})` : ''}
</Button>
);
})}
</Stack>
<Box sx={{ height: 520, width: '100%' }}>
<DataGridPro {...data} loading={data.rows.length === 0} apiRef={apiRef} />
</Box>
</div>
);
}
88 changes: 88 additions & 0 deletions docs/data/data-grid/filtering-recipes/FilteredRowCount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { DataGridPro, useGridApiRef, GridFilterModel } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';

const predefinedFilters: { label: string; filterModel: GridFilterModel }[] = [
{
label: 'All',
filterModel: { items: [] },
},
{
label: 'Filled',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Filled' }] },
},
{
label: 'Open',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Open' }] },
},
{
label: 'Rejected',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Rejected' }] },
},
{
label: 'Partially Filled',
filterModel: {
items: [{ field: 'status', operator: 'is', value: 'PartiallyFilled' }],
},
},
];

export default function FilteredRowCount() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 1000,
maxColumns: 10,
});

const apiRef = useGridApiRef();

const [predefinedFiltersRowCount, setPredefinedFiltersRowCount] = React.useState<
number[]
>([]);

const getFilteredRowsCount = React.useCallback(
(filterModel: GridFilterModel) => {
const { filteredRowsLookup } = apiRef.current.getFilterState(filterModel);
return Object.keys(filteredRowsLookup).filter(
(rowId) => filteredRowsLookup[rowId] === true,
).length;
},
[apiRef],
);

React.useEffect(() => {
// Calculate the row count for predefined filters
if (data.rows.length === 0) {
return;
}

setPredefinedFiltersRowCount(
predefinedFilters.map(({ filterModel }) => getFilteredRowsCount(filterModel)),
);
}, [apiRef, data.rows, getFilteredRowsCount]);

return (
<div style={{ overflow: 'hidden' }}>
<Stack direction="row" gap={1} mb={1} flexWrap="wrap">
{predefinedFilters.map(({ label, filterModel }, index) => {
const count = predefinedFiltersRowCount[index];
return (
<Button
key={label}
onClick={() => apiRef.current.setFilterModel(filterModel)}
variant="outlined"
>
{label} {count !== undefined ? `(${count})` : ''}
</Button>
);
})}
</Stack>
<Box sx={{ height: 520, width: '100%' }}>
<DataGridPro {...data} loading={data.rows.length === 0} apiRef={apiRef} />
</Box>
</div>
);
}
6 changes: 6 additions & 0 deletions docs/data/data-grid/filtering-recipes/filtering-recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ This requires certain considerations due to the Grid's context structure.
The following example shows how to accomplish this:

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

## Calculating filtered rows in advance

The [Grid API](/x/react-data-grid/api-object/#how-to-use-the-api-object) provides the [`getFilterState`](/x/api/data-grid/grid-api/#grid-api-prop-getFilterState) method, which allows you to display the row count for predefined filters upfront without applying filters to the Data Grid:

{{"demo": "FilteredRowCount.js", "bg": "inline", "defaultCodeOpen": false}}
6 changes: 6 additions & 0 deletions docs/pages/x/api/data-grid/grid-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@
"required": true,
"isProPlan": true
},
"getFilterState": {
"type": {
"description": "(filterModel: GridFilterModel) =&gt; GridStateCommunity['filter']"
},
"required": true
},
"getLocaleText": {
"type": {
"description": "&lt;T extends GridTranslationKeys&gt;(key: T) =&gt; GridLocaleText[T]"
Expand Down
5 changes: 5 additions & 0 deletions docs/pages/x/api/data-grid/grid-filter-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
"description": "Deletes a <a href=\"/x/api/data-grid/grid-filter-item/\">GridFilterItem</a>.",
"type": "(item: GridFilterItem) => void"
},
{
"name": "getFilterState",
"description": "Returns the filter state for the given filter model without applying it to the data grid.",
"type": "(filterModel: GridFilterModel) => GridStateCommunity['filter']"
},
{ "name": "hideFilterPanel", "description": "Hides the filter panel.", "type": "() => void" },
{
"name": "ignoreDiacritics",
Expand Down
3 changes: 3 additions & 0 deletions docs/translations/api-docs/data-grid/grid-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
"description": "Returns the grid data as an exceljs workbook.<br />This method is used internally by <code>exportDataAsExcel</code>."
},
"getExpandedDetailPanels": { "description": "Returns the rows whose detail panel is open." },
"getFilterState": {
"description": "Returns the filter state for the given filter model without applying it to the data grid."
},
"getLocaleText": { "description": "Returns the translation for the <code>key</code>." },
"getPinnedColumns": { "description": "Returns which columns are pinned." },
"getRootDimensions": { "description": "Returns the dimensions of the grid" },
Expand Down
39 changes: 28 additions & 11 deletions packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,13 @@ export const useGridFilter = (
const updateFilteredRows = React.useCallback(() => {
apiRef.current.setState((state) => {
const filterModel = gridFilterModelSelector(state, apiRef.current.instanceId);
const isRowMatchingFilters =
props.filterMode === 'client'
? buildAggregatedFilterApplier(filterModel, apiRef, props.disableEval)
: null;

const filteringResult = apiRef.current.applyStrategyProcessor('filtering', {
isRowMatchingFilters,
filterModel: filterModel ?? getDefaultGridFilterModel(),
});
const filterState = apiRef.current.getFilterState(filterModel);

const newState = {
...state,
filter: {
...state.filter,
...filteringResult,
...filterState,
},
};

Expand All @@ -133,7 +125,7 @@ export const useGridFilter = (
};
});
apiRef.current.publishEvent('filteredRowsSet');
}, [apiRef, props.filterMode, props.disableEval]);
}, [apiRef]);

const addColumnMenuItem = React.useCallback<GridPipeProcessor<'columnMenu'>>(
(columnMenuItems, colDef) => {
Expand Down Expand Up @@ -321,6 +313,30 @@ export const useGridFilter = (
[apiRef, logger, props.disableMultipleColumnsFiltering],
);

const getFilterState = React.useCallback<GridFilterApi['getFilterState']>(
(inputFilterModel) => {
const filterModel = sanitizeFilterModel(
inputFilterModel,
props.disableMultipleColumnsFiltering,
apiRef,
);
const isRowMatchingFilters =
props.filterMode === 'client'
? buildAggregatedFilterApplier(filterModel, apiRef, props.disableEval)
: null;

const filterResult = apiRef.current.applyStrategyProcessor('filtering', {
isRowMatchingFilters,
filterModel: filterModel ?? getDefaultGridFilterModel(),
});
return {
...filterResult,
filterModel,
};
},
[props.disableMultipleColumnsFiltering, props.filterMode, props.disableEval, apiRef],
);

const filterApi: GridFilterApi = {
setFilterLogicOperator,
unstable_applyFilters: applyFilters,
Expand All @@ -332,6 +348,7 @@ export const useGridFilter = (
hideFilterPanel,
setQuickFilterValues,
ignoreDiacritics: props.ignoreDiacritics,
getFilterState,
};

useGridApiMethod(apiRef, filterApi, 'public');
Expand Down
7 changes: 7 additions & 0 deletions packages/x-data-grid/src/models/api/gridFilterApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GridFilterModel } from '../gridFilterModel';
import { GridFilterItem, GridLogicOperator } from '../gridFilterItem';
import { GridControlledStateReasonLookup } from '../events';
import type { DataGridProcessedProps } from '../props/DataGridProps';
import { GridStateCommunity } from '../gridStateCommunity';

/**
* The filter API interface that is available in the grid [[apiRef]].
Expand Down Expand Up @@ -61,4 +62,10 @@ export interface GridFilterApi {
* Returns the value of the `ignoreDiacritics` prop.
*/
ignoreDiacritics: DataGridProcessedProps['ignoreDiacritics'];
/**
* Returns the filter state for the given filter model without applying it to the data grid.
* @param {GridFilterModel} filterModel The filter model to get the state for.
* @returns {GridStateCommunity['filter']} The filter state.
*/
getFilterState: (filterModel: GridFilterModel) => GridStateCommunity['filter'];
}

0 comments on commit 70c6612

Please sign in to comment.