Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Prepare the tree structure for grouping sorting / filtering + multi field grouping #3301

Merged
merged 8 commits into from
Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/scripts/generateProptypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ async function generateProptypes(program: ttp.ts.Program, sourceFile: string) {
'printOptions',
'column',
'groupingColDef',
'rowNode',
];
if (propsToNotResolve.includes(name)) {
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { DataGridPro, useGridApiContext, GridEvents } from '@mui/x-data-grid-pro';
import {
DataGridPro,
useGridApiContext,
GridEvents,
useGridSelector,
gridFilteredDescendantCountLookupSelector,
} from '@mui/x-data-grid-pro';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';

Expand All @@ -12,8 +18,14 @@ export const isNavigationKey = (key) =>
key === ' ';

const CustomGridTreeDataGroupingCell = (props) => {
const { id, field, value } = props;
const { id, field, rowNode } = props;
const apiRef = useGridApiContext();
const filteredDescendantCountLookup = useGridSelector(
apiRef,
gridFilteredDescendantCountLookupSelector,
);

const filteredDescendantCount = filteredDescendantCountLookup[rowNode.id] ?? 0;

const handleKeyDown = (event) => {
if (event.key === ' ') {
Expand All @@ -25,22 +37,22 @@ const CustomGridTreeDataGroupingCell = (props) => {
};

const handleClick = (event) => {
apiRef.current.setRowChildrenExpansion(id, !value.expanded);
apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded);
apiRef.current.setCellFocus(id, field);
event.stopPropagation();
};

return (
<Box sx={{ ml: value.depth * 4 }}>
<Box sx={{ ml: rowNode.depth * 4 }}>
<div>
{value.filteredDescendantCount > 0 ? (
{filteredDescendantCount > 0 ? (
<Button
onClick={handleClick}
onKeyDown={handleKeyDown}
tabIndex={-1}
size="small"
>
See {value.filteredDescendantCount} employees
See {filteredDescendantCount} employees
</Button>
) : (
<span />
Expand All @@ -60,13 +72,53 @@ CustomGridTreeDataGroupingCell.propTypes = {
*/
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
/**
* The cell value, but if the column has valueGetter, use getValue.
* The node of the row that the current cell belongs to.
*/
value: PropTypes.shape({
rowNode: PropTypes.shape({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@m4theushw those transpiled files do not apply the generateProptypes config but another one stored on formattedTSDemos

I will do, a follow up PR trying to unify both 👍

/**
* The id of the row children.
*/
children: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
),
/**
* Current expansion status of the row.
*/
childrenExpanded: PropTypes.bool,
/**
* 0-based depth of the row in the tree.
*/
depth: PropTypes.number.isRequired,
expanded: PropTypes.bool.isRequired,
filteredDescendantCount: PropTypes.number.isRequired,
label: PropTypes.string.isRequired,
/**
* The field used to group the children of this row.
* Is `null` if no field has been used to group the children of this row.
*/
groupingField: PropTypes.oneOfType([PropTypes.oneOf([null]), PropTypes.string])
.isRequired,
/**
* The key used to group the children of this row.
*/
groupingKey: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
PropTypes.bool,
]).isRequired,
/**
* The grid row id.
*/
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
/**
* If `true`, this node has been automatically added to fill a gap in the tree structure.
*/
isAutoGenerated: PropTypes.bool,
/**
* The row id of the parent (null if this row is a top level row).
*/
parent: PropTypes.oneOfType([
PropTypes.oneOf([null]),
PropTypes.number,
PropTypes.string,
]).isRequired,
}).isRequired,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
GridColumns,
GridRowsProp,
DataGridProProps,
GridTreeDataGroupingCellValue,
useGridSelector,
gridFilteredDescendantCountLookupSelector,
} from '@mui/x-data-grid-pro';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
Expand All @@ -19,11 +20,14 @@ export const isNavigationKey = (key: string) =>
key.indexOf('Page') === 0 ||
key === ' ';

const CustomGridTreeDataGroupingCell = (
props: GridRenderCellParams<GridTreeDataGroupingCellValue>,
) => {
const { id, field, value } = props;
const CustomGridTreeDataGroupingCell = (props: GridRenderCellParams) => {
const { id, field, rowNode } = props;
const apiRef = useGridApiContext();
const filteredDescendantCountLookup = useGridSelector(
apiRef,
gridFilteredDescendantCountLookupSelector,
);
const filteredDescendantCount = filteredDescendantCountLookup[rowNode.id] ?? 0;

const handleKeyDown = (event) => {
if (event.key === ' ') {
Expand All @@ -35,22 +39,22 @@ const CustomGridTreeDataGroupingCell = (
};

const handleClick = (event) => {
apiRef.current.setRowChildrenExpansion(id, !value.expanded);
apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded);
apiRef.current.setCellFocus(id, field);
event.stopPropagation();
};

return (
<Box sx={{ ml: value.depth * 4 }}>
<Box sx={{ ml: rowNode.depth * 4 }}>
<div>
{value.filteredDescendantCount > 0 ? (
{filteredDescendantCount > 0 ? (
<Button
onClick={handleClick}
onKeyDown={handleKeyDown}
tabIndex={-1}
size="small"
>
See {value.filteredDescendantCount} employees
See {filteredDescendantCount} employees
</Button>
) : (
<span />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Box from '@mui/material/Box';
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
import { useGridApiContext } from '../../hooks/utils/useGridApiContext';
import { GridRenderCellParams } from '../../models/params/gridCellParams';
import { useGridSelector } from '../../hooks/utils/useGridSelector';
import { gridFilteredDescendantCountLookupSelector } from '../../hooks/features/filter/gridFilterSelector';
import { isNavigationKey, isSpaceKey } from '../../utils/keyboardUtils';
import { GridEvents } from '../../models/events';
import { getDataGridUtilityClass } from '../../gridClasses';
Expand All @@ -24,22 +26,21 @@ const useUtilityClasses = (ownerState: OwnerState) => {
return composeClasses(slots, getDataGridUtilityClass, classes);
};

export interface GridTreeDataGroupingCellValue {
label: string;
filteredDescendantCount: number;
depth: number;
expanded: boolean;
}

const GridTreeDataGroupingCell = (props: GridRenderCellParams<GridTreeDataGroupingCellValue>) => {
const { id, field, value } = props;
const GridTreeDataGroupingCell = (props: GridRenderCellParams) => {
const { id, field, rowNode } = props;

const rootProps = useGridRootProps();
const apiRef = useGridApiContext();
const ownerState: OwnerState = { classes: rootProps.classes };
const classes = useUtilityClasses(ownerState);
const filteredDescendantCountLookup = useGridSelector(
apiRef,
gridFilteredDescendantCountLookupSelector,
);

const filteredDescendantCount = filteredDescendantCountLookup[rowNode.id] ?? 0;

const Icon = value.expanded
const Icon = rowNode.childrenExpanded
? rootProps.components.TreeDataCollapseIcon
: rootProps.components.TreeDataExpandIcon;

Expand All @@ -53,22 +54,22 @@ const GridTreeDataGroupingCell = (props: GridRenderCellParams<GridTreeDataGroupi
};

const handleClick = (event) => {
apiRef.current.setRowChildrenExpansion(id, !value.expanded);
apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded);
apiRef.current.setCellFocus(id, field);
event.stopPropagation();
};

return (
<Box className={classes.root} sx={{ ml: value.depth * 4 }}>
<Box className={classes.root} sx={{ ml: rowNode.depth * 2 }}>
<div className={classes.toggle}>
{value.filteredDescendantCount > 0 && (
{filteredDescendantCount > 0 && (
<IconButton
size="small"
onClick={handleClick}
onKeyDown={handleKeyDown}
tabIndex={-1}
aria-label={
value.expanded
rowNode.childrenExpanded
? apiRef.current.getLocaleText('treeDataCollapse')
: apiRef.current.getLocaleText('treeDataExpand')
}
Expand All @@ -78,8 +79,8 @@ const GridTreeDataGroupingCell = (props: GridRenderCellParams<GridTreeDataGroupi
)}
</div>
<span>
{value.label}
{value.filteredDescendantCount > 0 ? ` (${value.filteredDescendantCount})` : ''}
{rowNode.groupingKey}
{filteredDescendantCount > 0 ? ` (${filteredDescendantCount})` : ''}
</span>
</Box>
);
Expand Down Expand Up @@ -109,12 +110,7 @@ GridTreeDataGroupingCell.propTypes = {
/**
* The cell value formatted with the column valueFormatter.
*/
formattedValue: PropTypes.shape({
depth: PropTypes.number.isRequired,
expanded: PropTypes.bool.isRequired,
filteredDescendantCount: PropTypes.number.isRequired,
label: PropTypes.string.isRequired,
}).isRequired,
formattedValue: PropTypes.any.isRequired,
/**
* Get the cell value of a row and field.
* @param {GridRowId} id The row id.
Expand All @@ -141,30 +137,15 @@ GridTreeDataGroupingCell.propTypes = {
/**
* The node of the row that the current cell belongs to.
*/
rowNode: PropTypes.shape({
children: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
),
childrenExpanded: PropTypes.bool,
depth: PropTypes.number.isRequired,
groupingValue: PropTypes.string.isRequired,
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
isAutoGenerated: PropTypes.bool,
parent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
}).isRequired,
rowNode: PropTypes.object.isRequired,
/**
* the tabIndex value.
*/
tabIndex: PropTypes.oneOf([-1, 0]).isRequired,
/**
* The cell value, but if the column has valueGetter, use getValue.
*/
value: PropTypes.shape({
depth: PropTypes.number.isRequired,
expanded: PropTypes.bool.isRequired,
filteredDescendantCount: PropTypes.number.isRequired,
label: PropTypes.string.isRequired,
}).isRequired,
value: PropTypes.any.isRequired,
} as any;

export { GridTreeDataGroupingCell };
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,7 @@ GridCellCheckboxForwardRef.propTypes = {
/**
* The node of the row that the current cell belongs to.
*/
rowNode: PropTypes.shape({
children: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
),
childrenExpanded: PropTypes.bool,
depth: PropTypes.number.isRequired,
groupingValue: PropTypes.string.isRequired,
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
isAutoGenerated: PropTypes.bool,
parent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
}).isRequired,
rowNode: PropTypes.object.isRequired,
/**
* the tabIndex value.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const getFlatRowTree: GridRowGroupingPreProcessing = ({ ids, idRowsLookup }) =>
const tree: GridRowTreeConfig = {};
for (let i = 0; i < ids.length; i += 1) {
const rowId = ids[i];
tree[rowId] = { id: rowId, depth: 0, parent: null, groupingValue: '' };
tree[rowId] = { id: rowId, depth: 0, parent: null, groupingKey: '', groupingField: null };
}

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import * as React from 'react';
import { GridColDef } from '../../../models/colDef/gridColDef';
import {
GridTreeDataGroupingCell,
GridTreeDataGroupingCellValue,
} from '../../../components/cell/GridTreeDataGroupingCell';
import { GridTreeDataGroupingCell } from '../../../components/cell/GridTreeDataGroupingCell';
import { GRID_STRING_COL_DEF } from '../../../models/colDef/gridStringColDef';
import { gridFilteredDescendantCountLookupSelector } from '../filter/gridFilterSelector';
import { GridRenderCellParams } from '../../../models';

/**
* TODO: Add sorting and filtering on the value and the filteredDescendantCount
Expand All @@ -20,15 +15,8 @@ export const GRID_TREE_DATA_GROUP_COL_DEF: Omit<GridColDef, 'field' | 'editable'
disableReorder: true,
align: 'left',
width: 200,
valueGetter: ({ rowNode, api }): GridTreeDataGroupingCellValue => ({
label: rowNode.groupingValue,
depth: rowNode.depth,
expanded: rowNode.childrenExpanded ?? false,
filteredDescendantCount: gridFilteredDescendantCountLookupSelector(api.state)[rowNode.id] ?? 0,
}),
renderCell: (params: GridRenderCellParams<GridTreeDataGroupingCellValue>) => (
<GridTreeDataGroupingCell {...params} />
),
valueGetter: ({ rowNode }) => rowNode.groupingKey,
renderCell: (params) => <GridTreeDataGroupingCell {...params} />,
};

export const GRID_TREE_DATA_GROUP_COL_DEF_FORCED_FIELDS: Pick<GridColDef, 'field' | 'editable'> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { GridEvents, GridEventListener } from '../../../models/events';
import { GridColDef, GridColDefOverrideParams, GridColumns } from '../../../models';
import { isSpaceKey } from '../../../utils/keyboardUtils';
import { useFirstRender } from '../../utils/useFirstRender';
import { buildRowTree } from '../../../utils/rowTreeUtils';
import { buildRowTree, BuildRowTreeGroupingCriteria } from '../../../utils/tree/buildRowTree';
import { GridRowGroupingPreProcessing } from '../../core/rowGroupsPerProcessing';
import { gridFilteredDescendantCountLookupSelector } from '../filter';
import { GridPreProcessingGroup, useGridRegisterPreProcessor } from '../../core/preProcessing';
Expand Down Expand Up @@ -79,7 +79,9 @@ export const useGridTreeData = (
const rows = params.ids
.map((rowId) => ({
id: rowId,
path: props.getTreeDataPath!(params.idRowsLookup[rowId]),
path: props.getTreeDataPath!(params.idRowsLookup[rowId]).map(
(key): BuildRowTreeGroupingCriteria => ({ key, field: null }),
),
}))
.sort((a, b) => a.path.length - b.path.length);

Expand Down
6 changes: 6 additions & 0 deletions packages/grid/_modules_/grid/models/colDef/gridColDef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ import { GridEditCellProps } from '../gridEditRowModel';
export type GridAlignment = 'left' | 'right' | 'center';

type ValueOptions = string | number | { value: any; label: string };

/**
* Value that can be used as a key for grouping rows
*/
export type GridKeyValue = string | number | boolean;

/**
* Column Definition interface.
*/
Expand Down
Loading