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

[DataGridPremium] Automatic parents and children selection #13757

Merged
merged 51 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4c4ecf8
[DataGridPro] Add option to propagate row selection for nested rows
MBilalShafi Jul 8, 2024
899df0f
Merge branch 'master' into propagate-row-selection
MBilalShafi Jul 11, 2024
7fd93e7
Fix autogenerated parents not being affected by nested selection
MBilalShafi Jul 11, 2024
124ddbc
Update docs/data/data-grid/row-grouping/row-grouping.md
MBilalShafi Jul 11, 2024
3e9ad6e
Fix the id 0 not being picked properly for selection
MBilalShafi Jul 11, 2024
c581f04
Update demo to use multi-level
MBilalShafi Jul 11, 2024
4af394b
Reset selection on filtering
MBilalShafi Jul 16, 2024
e9146ce
Add tests
MBilalShafi Jul 16, 2024
361658b
Lint
MBilalShafi Jul 16, 2024
f3124b0
Prettier
MBilalShafi Jul 16, 2024
7e0f475
Tweak the performance a bit
MBilalShafi Jul 16, 2024
c4f4d0c
Lint
MBilalShafi Jul 16, 2024
fbd2d44
Michel's review addressed
MBilalShafi Jul 16, 2024
1f50a41
Minor docs update
MBilalShafi Jul 16, 2024
a328ea6
useDemoData: Temporarily allow higher row size for testing
MBilalShafi Jul 16, 2024
4f03788
Revert a328ea62893b99909a3573c585366b93c6e7a75a
MBilalShafi Jul 16, 2024
0c9fbaa
Make row group fetch use loop instead of recursion
MBilalShafi Jul 16, 2024
27904e4
Desc update
MBilalShafi Jul 18, 2024
42f5728
Romain's suggestion
MBilalShafi Jul 18, 2024
ff2bea1
Use specific selectors and update the row propagation updation logic
MBilalShafi Jul 18, 2024
8d2d059
Fix failing tests
MBilalShafi Jul 18, 2024
b6ca567
Updates
MBilalShafi Jul 18, 2024
a1cf083
Merge branch 'master' into propagate-row-selection
MBilalShafi Jul 24, 2024
5facfd3
Make the auto selection respect the isRowSelectable prop
MBilalShafi Jul 24, 2024
49ed4e0
Make select all checkbox in indeterminate state select all the rows
MBilalShafi Jul 24, 2024
fdab7f0
Merge branch 'master' into propagate-row-selection
MBilalShafi Jul 30, 2024
8e5238e
Introduce selectors with arguments
MBilalShafi Aug 1, 2024
a987418
Revert 8e5238ecfcacf2489899426aa46c19aa36c196ae
MBilalShafi Aug 17, 2024
5adfbec
Merge branch 'master' into propagate-row-selection
MBilalShafi Aug 17, 2024
1cc312d
Revert "Make select all checkbox in indeterminate state select all th…
MBilalShafi Aug 17, 2024
1babd7e
Merge branch 'master' into propagate-row-selection
MBilalShafi Sep 17, 2024
70feadc
propagateRowSelection -> rowSelectionPropagation
MBilalShafi Sep 18, 2024
d0f1c8b
Update
MBilalShafi Sep 24, 2024
c147230
Fix some tests
MBilalShafi Sep 24, 2024
9f789ac
Support indeterminateCheckboxAction prop on group header checkbox
MBilalShafi Sep 25, 2024
ac85050
Recompute selected parents on filtering
MBilalShafi Sep 25, 2024
1aac47d
Update signature of rowSelectionPropagation prop
MBilalShafi Sep 26, 2024
79e1b91
Handle faulty usecase with only auto select parents turned on
MBilalShafi Sep 26, 2024
87d9aef
Some small updates and docs improvement
MBilalShafi Sep 26, 2024
0ccad5b
Housekeeping
MBilalShafi Sep 26, 2024
9ffba58
Fix lint-check
MBilalShafi Sep 26, 2024
4d841c9
Merge branch 'master' into propagate-row-selection
MBilalShafi Sep 28, 2024
0427cf5
Cleanup
MBilalShafi Sep 28, 2024
4cb493a
Add tests
MBilalShafi Sep 28, 2024
c16e7f4
Improve check for id = 0
MBilalShafi Oct 1, 2024
3548c1f
Resolve a few comments
MBilalShafi Oct 2, 2024
201be14
Optimization
MBilalShafi Oct 2, 2024
b044d6f
Fix the failing test
MBilalShafi Oct 2, 2024
e5ee1b2
Update condition
MBilalShafi Oct 2, 2024
6ea605f
Improve documentation as per suggestions
MBilalShafi Oct 3, 2024
87f71d4
Update the docs
MBilalShafi Oct 4, 2024
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
33 changes: 33 additions & 0 deletions docs/data/data-grid/row-grouping/RowGroupingPropagateSelection.js
MBilalShafi marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import {
DataGridPremium,
useGridApiRef,
useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingPropagateSelection() {
const data = useMovieData();
const apiRef = useGridApiRef();

const initialState = useKeepGroupedColumnsHidden({
apiRef,
initialState: {
rowGrouping: {
model: ['company', 'director'],
},
},
});

return (
<div style={{ height: 400, width: '100%' }}>
<DataGridPremium
{...data}
apiRef={apiRef}
initialState={initialState}
checkboxSelection
propagateRowSelection
/>
</div>
);
}
33 changes: 33 additions & 0 deletions docs/data/data-grid/row-grouping/RowGroupingPropagateSelection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import {
DataGridPremium,
useGridApiRef,
useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingPropagateSelection() {
const data = useMovieData();
const apiRef = useGridApiRef();

const initialState = useKeepGroupedColumnsHidden({
apiRef,
initialState: {
rowGrouping: {
model: ['company', 'director'],
},
},
});

return (
<div style={{ height: 400, width: '100%' }}>
<DataGridPremium
{...data}
apiRef={apiRef}
initialState={initialState}
checkboxSelection
propagateRowSelection
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<DataGridPremium
{...data}
apiRef={apiRef}
initialState={initialState}
checkboxSelection
propagateRowSelection
/>
22 changes: 22 additions & 0 deletions docs/data/data-grid/row-grouping/row-grouping.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,28 @@ In the example below:
If you are dynamically switching the `leafField` or `mainGroupingCriteria`, the sorting and filtering models will not be cleaned up automatically, and the sorting/filtering will not be re-applied.
:::

## Automatic parents and children selection

By default, selecting a parent row will not select its children.
Set the `propagateRowSelection` prop to `true` to achieve the following behavior.
MBilalShafi marked this conversation as resolved.
Show resolved Hide resolved

1. Selecting/deselecting a parent row would select/deselect all the children rows.
2. When all the child rows are selected, the parent row will be auto selected.
3. When a child row is deselected, if one or more parent rows are already selected, they will be moved to an indeterminate state.
4. "Select All" checkbox in the header row would select/deselect all the rows including child rows.
5. After applying the filtering, the previously selected rows which are not in the newly filtered rows will be auto deselected.

{{"demo": "RowGroupingPropagateSelection.js", "bg": "inline", "defaultCodeOpen": false}}
cherniavskii marked this conversation as resolved.
Show resolved Hide resolved

:::info
When the row selection propagation feature is enabled, only the filtered rows will be kept selected.
If some rows were selected before filtering, they will be auto deselected if they are not among the newly filtered rows.
:::

:::warning
The row selection propagation is a client-side feature and not recommended to be used with the [server-side data source](/x/react-data-grid/server-side-data/), since it will only work on the partially loaded data.
:::

arminmeh marked this conversation as resolved.
Show resolved Hide resolved
## Get the rows in a group

You can use the `apiRef.current.getRowGroupChildren` method to get the id of all rows contained in a group.
Expand Down
4 changes: 4 additions & 0 deletions docs/data/data-grid/tree-data/tree-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ If you want to access the grouping column field, for instance, to use it with co

Same behavior as for the [Row grouping](/x/react-data-grid/row-grouping/#group-expansion).

## Automatic parents and children selection

Same behavior as for the [Row grouping](/x/react-data-grid/row-grouping/#propagate-row-selection).

## Gaps in the tree

If some entries are missing to build the full tree, the `DataGridPro` will automatically create rows to fill those gaps.
Expand Down
1 change: 1 addition & 0 deletions docs/pages/x/api/data-grid/data-grid-premium.json
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@
"returned": "Promise<R> | R"
}
},
"propagateRowSelection": { "type": { "name": "bool" }, "default": "false" },
"resizeThrottleMs": { "type": { "name": "number" }, "default": "60" },
"rowBufferPx": { "type": { "name": "number" }, "default": "150" },
"rowCount": { "type": { "name": "number" } },
Expand Down
1 change: 1 addition & 0 deletions docs/pages/x/api/data-grid/data-grid-pro.json
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@
"returned": "Promise<R> | R"
}
},
"propagateRowSelection": { "type": { "name": "bool" }, "default": "false" },
"resizeThrottleMs": { "type": { "name": "number" }, "default": "60" },
"rowBufferPx": { "type": { "name": "number" }, "default": "150" },
"rowCount": { "type": { "name": "number" } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@
"groupingColDef": { "description": "The grouping column used by the tree data." },
"headerFilterHeight": { "description": "Override the height of the header filters." },
"headerFilters": {
"description": "If <code>true</code>, enables the data grid filtering on header feature."
"description": "If <code>true</code>, the header filters feature is enabled."
},
"hideFooter": { "description": "If <code>true</code>, the footer component is hidden." },
"hideFooterPagination": {
Expand Down Expand Up @@ -586,6 +586,9 @@
"Promise<R> | R": "The final values to update the row."
}
},
"propagateRowSelection": {
"description": "If <code>true</code>, following behavior happens with nested data: 1. Selecting/deselecting a parent row would select/deselect all the children rows. 2. When all the child rows are selected, the parent row will be auto selected. 3. When a child row is deselected, if one or more parent rows are already selected, they will be moved to an indeterminate state. 4. Select All checkbox in the header row would select/deselect all the rows including child rows. Works with tree data and row grouping on the client-side only."
},
"resizeThrottleMs": { "description": "The milliseconds throttle delay for resizing the grid." },
"rowBufferPx": { "description": "Row region in pixels to render before/after the viewport" },
"rowCount": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
"groupingColDef": { "description": "The grouping column used by the tree data." },
"headerFilterHeight": { "description": "Override the height of the header filters." },
"headerFilters": {
"description": "If <code>true</code>, enables the data grid filtering on header feature."
"description": "If <code>true</code>, the header filters feature is enabled."
},
"hideFooter": { "description": "If <code>true</code>, the footer component is hidden." },
"hideFooterPagination": {
Expand Down Expand Up @@ -532,6 +532,9 @@
"Promise<R> | R": "The final values to update the row."
}
},
"propagateRowSelection": {
"description": "If <code>true</code>, following behavior happens with nested data: 1. Selecting/deselecting a parent row would select/deselect all the children rows. 2. When all the child rows are selected, the parent row will be auto selected. 3. When a child row is deselected, if one or more parent rows are already selected, they will be moved to an indeterminate state. 4. Select All checkbox in the header row would select/deselect all the rows including child rows. Works with tree data and row grouping on the client-side only."
},
"resizeThrottleMs": { "description": "The milliseconds throttle delay for resizing the grid." },
"rowBufferPx": { "description": "Row region in pixels to render before/after the viewport" },
"rowCount": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ DataGridPremiumRaw.propTypes = {
*/
headerFilterHeight: PropTypes.number,
/**
* If `true`, enables the data grid filtering on header feature.
* If `true`, the header filters feature is enabled.
* @default false
*/
headerFilters: PropTypes.bool,
Expand Down Expand Up @@ -894,6 +894,16 @@ DataGridPremiumRaw.propTypes = {
* @returns {Promise<R> | R} The final values to update the row.
*/
processRowUpdate: PropTypes.func,
/**
* If `true`, following behavior happens with nested data:
* 1. Selecting/deselecting a parent row would select/deselect all the children rows.
* 2. When all the child rows are selected, the parent row will be auto selected.
* 3. When a child row is deselected, if one or more parent rows are already selected, they will be moved to an indeterminate state.
* 4. Select All checkbox in the header row would select/deselect all the rows including child rows.
* Works with tree data and row grouping on the client-side only.
* @default false
*/
propagateRowSelection: PropTypes.bool,
/**
* The milliseconds throttle delay for resizing the grid.
* @default 60
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import * as React from 'react';
import { createRenderer, fireEvent, act } from '@mui/internal-test-utils';
import { getCell } from 'test/utils/helperFn';
import { expect } from 'chai';
import {
DataGridPremium,
DataGridPremiumProps,
GridApi,
GridRowsProp,
useGridApiRef,
} from '@mui/x-data-grid-premium';

const isJSDOM = /jsdom/.test(window.navigator.userAgent);

interface BaselineProps extends DataGridPremiumProps {
rows: GridRowsProp;
}

const rows: GridRowsProp = [
{ id: 0, category1: 'Cat A', category2: 'Cat 1' },
{ id: 1, category1: 'Cat A', category2: 'Cat 2' },
{ id: 2, category1: 'Cat A', category2: 'Cat 2' },
{ id: 3, category1: 'Cat B', category2: 'Cat 2' },
{ id: 4, category1: 'Cat B', category2: 'Cat 1' },
];

const baselineProps: BaselineProps = {
autoHeight: isJSDOM,
disableVirtualization: true,
rows,
columns: [
{
field: 'id',
type: 'number',
},
{
field: 'category1',
},
{
field: 'category2',
},
],
};

describe('<DataGridPremium /> - Row selection', () => {
const { render } = createRenderer();

let apiRef: React.MutableRefObject<GridApi>;

function Test(props: Partial<DataGridPremiumProps>) {
apiRef = useGridApiRef();

return (
<div style={{ width: 300, height: 300 }}>
<DataGridPremium {...baselineProps} checkboxSelection apiRef={apiRef} {...props} />
</div>
);
}

describe('prop: propagateRowSelection', () => {
it('should select all the children when selecting a parent', () => {
render(
<Test propagateRowSelection initialState={{ rowGrouping: { model: ['category1'] } }} />,
);

fireEvent.click(getCell(1, 0).querySelector('input')!);
expect(apiRef.current.getSelectedRows()).to.have.keys([
'auto-generated-row-category1/Cat B',
3,
4,
]);
});

it('should deselect all the children when deselecting a parent', () => {
render(
<Test propagateRowSelection initialState={{ rowGrouping: { model: ['category1'] } }} />,
);

fireEvent.click(getCell(1, 0).querySelector('input')!);
expect(apiRef.current.getSelectedRows()).to.have.keys([
'auto-generated-row-category1/Cat B',
3,
4,
]);
fireEvent.click(getCell(1, 0).querySelector('input')!);
expect(apiRef.current.getSelectedRows().size).to.equal(0);
});

it('should put the parent into indeterminate if some but not all the children are selected', () => {
render(
<Test
propagateRowSelection
defaultGroupingExpansionDepth={-1}
initialState={{ rowGrouping: { model: ['category1'] } }}
density="compact"
/>,
);

fireEvent.click(getCell(1, 0).querySelector('input')!);
expect(getCell(0, 0).querySelector('input')!).to.have.attr('data-indeterminate', 'true');
});

it('should auto select the parent if all the children are selected', () => {
render(
<Test
propagateRowSelection
defaultGroupingExpansionDepth={-1}
density="compact"
initialState={{ rowGrouping: { model: ['category1'] } }}
/>,
);

fireEvent.click(getCell(1, 0).querySelector('input')!);
fireEvent.click(getCell(2, 0).querySelector('input')!);
fireEvent.click(getCell(3, 0).querySelector('input')!);
expect(apiRef.current.getSelectedRows()).to.have.keys([
0,
1,
2,
'auto-generated-row-category1/Cat A',
]);
});

it('should deselect auto selected parent if one of the children is deselected', () => {
render(
<Test
propagateRowSelection
defaultGroupingExpansionDepth={-1}
density="compact"
initialState={{ rowGrouping: { model: ['category1'] } }}
/>,
);

fireEvent.click(getCell(1, 0).querySelector('input')!);
fireEvent.click(getCell(2, 0).querySelector('input')!);
fireEvent.click(getCell(3, 0).querySelector('input')!);
expect(apiRef.current.getSelectedRows()).to.have.keys([
0,
1,
2,
'auto-generated-row-category1/Cat A',
]);
fireEvent.click(getCell(2, 0).querySelector('input')!);
expect(apiRef.current.getSelectedRows()).to.have.keys([0, 2]);
});

it('should deselect unfiltered rows after filtering', () => {
render(
<Test
propagateRowSelection
defaultGroupingExpansionDepth={-1}
density="compact"
initialState={{ rowGrouping: { model: ['category1'] } }}
/>,
);

fireEvent.click(getCell(1, 0).querySelector('input')!);
fireEvent.click(getCell(2, 0).querySelector('input')!);
fireEvent.click(getCell(3, 0).querySelector('input')!);
fireEvent.click(getCell(5, 0).querySelector('input')!);

expect(apiRef.current.getSelectedRows()).to.have.keys([
0,
1,
2,
'auto-generated-row-category1/Cat A',
3,
]);
act(() => {
apiRef.current.setFilterModel({
items: [],
quickFilterValues: ['Cat B'],
});
});
expect(apiRef.current.getSelectedRows()).to.have.keys([3]);
});
});
});
Loading