Skip to content

Commit 3029d7f

Browse files
authored
Merge pull request #8772 from IgniteUI/mevtimov/feat-8040
feat(tree-grid): add cascading row selection #8040
2 parents 6363a18 + d956838 commit 3029d7f

20 files changed

+1494
-60
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ All notable changes for each version of this project will be documented in this
88
- The `igx-drop-down-item` now allows for `igxPrefix`, `igxSuffix` and `igx-divider` directives to be passed as `ng-content` and they will be renderer accordingly in the item's content.
99
- `IgxGrid`
1010
- Added support for exporting grouped data.
11+
- `IgxTreeGrid`
12+
- Added `multipleCascade` row selection mode. In this mode, selecting a record results in the selection of all its children recursively. When only some children are selected, their parent's checkbox is in an indeterminate state.
13+
14+
15+
```html
16+
<igx-tree-grid [rowSelection]="'multipleCascade'">
17+
</igx-tree-grid>
18+
```
1119
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
1220
- Support for `currency` type columns is added in the grid.
1321
- Support for `percent` type columns is added in the grid.

projects/igniteui-angular/src/lib/grids/common/enums.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ export type GridKeydownTargetType =
3030
export const GridSelectionMode = mkenum({
3131
none: 'none',
3232
single: 'single',
33-
multiple: 'multiple'
33+
multiple: 'multiple',
34+
multipleCascade: 'multipleCascade'
3435
});
3536
export type GridSelectionMode = (typeof GridSelectionMode)[keyof typeof GridSelectionMode];
3637

projects/igniteui-angular/src/lib/grids/grid-base.directive.ts

+6-9
Original file line numberDiff line numberDiff line change
@@ -2048,11 +2048,7 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
20482048
*/
20492049
@Input()
20502050
public set selectedRows(rowIDs: any[]) {
2051-
if (rowIDs.length > 0) {
2052-
this.selectRows(rowIDs, true);
2053-
} else {
2054-
this.deselectAllRows();
2055-
}
2051+
this.selectRows(rowIDs || [], true);
20562052
}
20572053

20582054
public get selectedRows(): any[] {
@@ -2603,8 +2599,8 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
26032599
* Gets/Sets row selection mode
26042600
*
26052601
* @remarks
2606-
* By default the row selection mode is none
2607-
* @param selectionMode: GridSelectionMode
2602+
* By default the row selection mode is 'none'
2603+
* Note that in IgxGrid and IgxHierarchicalGrid 'multipleCascade' behaves like 'multiple'
26082604
*/
26092605
@WatchChanges()
26102606
@Input()
@@ -2614,7 +2610,7 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
26142610

26152611
public set rowSelection(selectionMode: GridSelectionMode) {
26162612
this._rowSelectionMode = selectionMode;
2617-
if (this.gridAPI.grid && this.columnList) {
2613+
if (!this._init) {
26182614
this.selectionService.clearAllSelectedRows();
26192615
this.notifyChanges(true);
26202616
}
@@ -3057,7 +3053,8 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
30573053
* @hidden @internal
30583054
*/
30593055
public get isMultiRowSelectionEnabled(): boolean {
3060-
return this.rowSelection === GridSelectionMode.multiple;
3056+
return this.rowSelection === GridSelectionMode.multiple
3057+
|| this.rowSelection === GridSelectionMode.multipleCascade;
30613058
}
30623059

30633060
/**

projects/igniteui-angular/src/lib/grids/grid/grid-row-selection.spec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1858,7 +1858,6 @@ describe('IgxGrid - Row Selection #grid', () => {
18581858
it('Should bind selectedRows properly', () => {
18591859
fix.componentInstance.selectedRows = [1, 2, 3];
18601860
fix.detectChanges();
1861-
18621861
expect(grid.getRowByIndex(0).selected).toBeTrue();
18631862
expect(grid.getRowByIndex(4).selected).toBeFalse();
18641863

projects/igniteui-angular/src/lib/grids/grid/grid.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType,
482482
return res;
483483
}
484484
const rList = this._groupsRowList.filter(item => item.element.nativeElement.parentElement !== null)
485-
.sort((item1, item2) => item1.index - item2.index);
485+
.sort((item1, item2) => item1.index - item2.index);
486486
res.reset(rList);
487487
return res;
488488
}

projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts

+15-15
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti
242242
*/
243243
@Input()
244244
public set expandChildren(value: boolean) {
245-
this._defaultExpandState = value;
245+
this._defaultExpandState = value;
246246
this.expansionStates = new Map<any, boolean>();
247247
}
248248

@@ -256,7 +256,7 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti
256256
* @memberof IgxHierarchicalGridComponent
257257
*/
258258
public get expandChildren(): boolean {
259-
return this._defaultExpandState ;
259+
return this._defaultExpandState;
260260
}
261261

262262
/**
@@ -358,16 +358,16 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti
358358
this.dragIndicatorIconTemplate = this.parentIsland ?
359359
this.parentIsland.dragIndicatorIconTemplate :
360360
this.dragIndicatorIconTemplate;
361-
this.rowExpandedIndicatorTemplate = this.rootGrid.rowExpandedIndicatorTemplate;
362-
this.rowCollapsedIndicatorTemplate = this.rootGrid.rowCollapsedIndicatorTemplate;
361+
this.rowExpandedIndicatorTemplate = this.rootGrid.rowExpandedIndicatorTemplate;
362+
this.rowCollapsedIndicatorTemplate = this.rootGrid.rowCollapsedIndicatorTemplate;
363363
this.headerCollapseIndicatorTemplate = this.rootGrid.headerCollapseIndicatorTemplate;
364364
this.headerExpandIndicatorTemplate = this.rootGrid.headerExpandIndicatorTemplate;
365365
this.excelStyleHeaderIconTemplate = this.rootGrid.excelStyleHeaderIconTemplate;
366366
this.hasChildrenKey = this.parentIsland ?
367-
this.parentIsland.hasChildrenKey || this.rootGrid.hasChildrenKey :
368-
this.rootGrid.hasChildrenKey;
369-
this.showExpandAll = this.parentIsland ?
370-
this.parentIsland.showExpandAll : this.rootGrid.showExpandAll;
367+
this.parentIsland.hasChildrenKey || this.rootGrid.hasChildrenKey :
368+
this.rootGrid.hasChildrenKey;
369+
this.showExpandAll = this.parentIsland ?
370+
this.parentIsland.showExpandAll : this.rootGrid.showExpandAll;
371371

372372
this.excelStyleFilteringComponents = this.parentIsland ?
373373
this.parentIsland.excelStyleFilteringComponents :
@@ -572,14 +572,14 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti
572572
* @internal
573573
*/
574574
public hasExpandedRecords() {
575-
if (this.expandChildren) {
575+
if (this.expandChildren) {
576576
return true;
577-
}
578-
let hasExpandedEntry = false;
579-
this.expansionStates.forEach(value => {
580-
if (value) {
581-
hasExpandedEntry = value;
582-
}
577+
}
578+
let hasExpandedEntry = false;
579+
this.expansionStates.forEach(value => {
580+
if (value) {
581+
hasExpandedEntry = value;
582+
}
583583
});
584584
return hasExpandedEntry;
585585
}

projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,7 @@ describe('IgxHierarchicalGrid selection #hGrid', () => {
12681268
rowIsland1.rowSelection = GridSelectionMode.multiple;
12691269
fix.componentInstance.selectedRows = ['0', '2', '3'];
12701270
fix.detectChanges();
1271+
12711272
expect(hierarchicalGrid.getRowByKey('0').selected).toBeTrue();
12721273
expect(hierarchicalGrid.getRowByKey('1').selected).toBeFalse();
12731274

projects/igniteui-angular/src/lib/grids/row.directive.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ export class IgxRowDirective<T extends IgxGridBaseDirective & GridType> implemen
403403
if (this.grid.rowSelection === 'none' || this.deleted || !this.grid.selectRowOnClick) {
404404
return;
405405
}
406-
if (event.shiftKey && this.grid.rowSelection === 'multiple') {
406+
if (event.shiftKey && this.grid.isMultiRowSelectionEnabled) {
407407
this.selectionService.selectMultipleRows(this.rowID, this.rowData, event);
408408
return;
409409
}
@@ -436,7 +436,7 @@ export class IgxRowDirective<T extends IgxGridBaseDirective & GridType> implemen
436436
*/
437437
public onRowSelectorClick(event) {
438438
event.stopPropagation();
439-
if (event.shiftKey && this.grid.rowSelection === 'multiple') {
439+
if (event.shiftKey && this.grid.isMultiRowSelectionEnabled) {
440440
this.selectionService.selectMultipleRows(this.rowID, this.rowData, event);
441441
return;
442442
}

projects/igniteui-angular/src/lib/grids/selection/selection.service.ts

+17-6
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class IgxRow {
7575

7676
createDoneEditEventArgs(cachedRowData: any, event?: Event): IGridEditDoneEventArgs {
7777
const updatedData = this.grid.transactions.enabled ?
78-
this.grid.transactions.getAggregatedValue(this.id, true) : this.grid.gridAPI.getRowData(this.id);
78+
this.grid.transactions.getAggregatedValue(this.id, true) : this.grid.gridAPI.getRowData(this.id);
7979
const rowData = updatedData === null ? this.grid.gridAPI.getRowData(this.id) : updatedData;
8080
const args: IGridEditDoneEventArgs = {
8181
rowID: this.id,
@@ -131,7 +131,7 @@ export class IgxCell {
131131

132132
createDoneEditEventArgs(value: any, event?: Event): IGridEditDoneEventArgs {
133133
const updatedData = this.grid.transactions.enabled ?
134-
this.grid.transactions.getAggregatedValue(this.id.rowID, true) : this.rowData;
134+
this.grid.transactions.getAggregatedValue(this.id.rowID, true) : this.rowData;
135135
const rowData = updatedData === null ? this.grid.gridAPI.getRowData(this.id.rowID) : updatedData;
136136
const args: IGridEditDoneEventArgs = {
137137
rowID: this.id.rowID,
@@ -228,7 +228,7 @@ export class IgxGridCRUDService {
228228
}
229229
/** Changing the reference with the new editable cell */
230230
const newCell = this.createCell(cell);
231-
if (this.rowEditing) {
231+
if (this.rowEditing) {
232232
const canceled = this.beginRowEdit(newCell, event);
233233
if (!canceled) {
234234
this.beginCellEdit(newCell, event);
@@ -394,6 +394,7 @@ export class IgxGridSelectionService {
394394
_ranges: Set<string> = new Set<string>();
395395
_selectionRange: Range;
396396
rowSelection: Set<any> = new Set<any>();
397+
indeterminateRows: Set<any> = new Set<any>();
397398
columnSelection: Set<string> = new Set<string>();
398399
/**
399400
* @hidden @internal
@@ -759,12 +760,16 @@ export class IgxGridSelectionService {
759760
return this.rowSelection.size ? Array.from(this.rowSelection.keys()) : [];
760761
}
761762

763+
/** Returns array of the rows in indeterminate state. */
764+
getIndeterminateRows(): Array<any> {
765+
return this.indeterminateRows.size ? Array.from(this.indeterminateRows.keys()) : [];
766+
}
767+
762768
/** Clears row selection, if filtering is applied clears only selected rows from filtered data. */
763769
clearRowSelection(event?): void {
764770
const removedRec = this.isFilteringApplied() ?
765771
this.getRowIDs(this.allData).filter(rID => this.isRowSelected(rID)) : this.getSelectedRows();
766772
const newSelection = this.isFilteringApplied() ? this.getSelectedRows().filter(x => !removedRec.includes(x)) : [];
767-
768773
this.emitRowSelectionEvent(newSelection, [], removedRec, event);
769774
}
770775

@@ -773,6 +778,7 @@ export class IgxGridSelectionService {
773778
const allRowIDs = this.getRowIDs(this.allData);
774779
const addedRows = allRowIDs.filter((rID) => !this.isRowSelected(rID));
775780
const newSelection = this.rowSelection.size ? this.getSelectedRows().concat(addedRows) : addedRows;
781+
this.indeterminateRows.clear();
776782
this.selectedRowsChange.next();
777783
this.emitRowSelectionEvent(newSelection, addedRows, [], event);
778784
}
@@ -824,6 +830,10 @@ export class IgxGridSelectionService {
824830
return this.rowSelection.size > 0 && this.rowSelection.has(rowID);
825831
}
826832

833+
isRowInIndeterminateState(rowID): boolean {
834+
return this.indeterminateRows.size > 0 && this.indeterminateRows.has(rowID);
835+
}
836+
827837
/** Select range from last selected row to the current specified row. */
828838
selectMultipleRows(rowID, rowData, event?): void {
829839
this.allRowsSelected = undefined;
@@ -904,6 +914,7 @@ export class IgxGridSelectionService {
904914
/** Clear rowSelection and update checkbox state */
905915
public clearAllSelectedRows(): void {
906916
this.rowSelection.clear();
917+
this.indeterminateRows.clear();
907918
this.clearHeaderCBState();
908919
this.selectedRowsChange.next();
909920
}
@@ -1041,7 +1052,7 @@ export class IgxGridSelectionService {
10411052
this.columnSelection.clear();
10421053
}
10431054

1044-
private areEqualCollections(first, second): boolean {
1055+
protected areEqualCollections(first, second): boolean {
10451056
return first.length === second.length && new Set(first.concat(second)).size === first.length;
10461057
}
10471058

@@ -1057,4 +1068,4 @@ export class IgxGridSelectionService {
10571068
}
10581069

10591070
export const isChromium = (): boolean => (/Chrom|e?ium/g.test(navigator.userAgent) ||
1060-
/Google Inc/g.test(navigator.vendor)) && !/Edge/g.test(navigator.userAgent);
1071+
/Google Inc/g.test(navigator.vendor)) && !/Edge/g.test(navigator.userAgent);

projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class IgxTreeGridAPIService extends GridBaseAPIService<IgxTreeGridCompone
4040
const row = grid.records.get(rowID);
4141
if (row.expanded === expanded ||
4242
((!row.children || !row.children.length) && (!grid.loadChildrenOnDemand ||
43-
(grid.hasChildrenKey && !row.data[grid.hasChildrenKey])))) {
43+
(grid.hasChildrenKey && !row.data[grid.hasChildrenKey])))) {
4444
return false;
4545
}
4646
return true;
@@ -84,10 +84,10 @@ export class IgxTreeGridAPIService extends GridBaseAPIService<IgxTreeGridCompone
8484
public deleteRowById(rowID: any) {
8585
const treeGrid = this.grid;
8686
const flatDataWithCascadeOnDeleteAndTransactions =
87-
treeGrid.primaryKey &&
88-
treeGrid.foreignKey &&
89-
treeGrid.cascadeOnDelete &&
90-
treeGrid.transactions.enabled;
87+
treeGrid.primaryKey &&
88+
treeGrid.foreignKey &&
89+
treeGrid.cascadeOnDelete &&
90+
treeGrid.transactions.enabled;
9191

9292
if (flatDataWithCascadeOnDeleteAndTransactions) {
9393
treeGrid.transactions.startPending();
@@ -180,7 +180,7 @@ export class IgxTreeGridAPIService extends GridBaseAPIService<IgxTreeGridCompone
180180
if (!parentRecord) {
181181
throw Error('Invalid parent row ID!');
182182
}
183-
this.grid.summaryService.clearSummaryCache({rowID: parentRecord.rowID});
183+
this.grid.summaryService.clearSummaryCache({ rowID: parentRecord.rowID });
184184
if (this.grid.primaryKey && this.grid.foreignKey) {
185185
data[this.grid.foreignKey] = parentRowID;
186186
super.addRowToData(data);

projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-expanding.spec.ts

+35
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,41 @@ describe('IgxTreeGrid - Expanding / Collapsing #tGrid', () => {
10211021
TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 4, true);
10221022
expect(treeGrid.selectedRows).toEqual([1, 6, 10]);
10231023
});
1024+
1025+
it('check row selection within multipleCascade selection mode when expand a row', fakeAsync(() => {
1026+
treeGrid.rowSelection = GridSelectionMode.multipleCascade;
1027+
fix.detectChanges();
1028+
1029+
treeGrid.selectRows([1]);
1030+
fix.detectChanges();
1031+
1032+
expect(treeGrid.selectedRows).toEqual([1]);
1033+
TreeGridFunctions.verifyRowByIndexSelectionAndCheckboxState(fix, 0, true, true);
1034+
1035+
treeGrid.expandRow(1);
1036+
fix.detectChanges();
1037+
tick(1000);
1038+
fix.detectChanges();
1039+
1040+
expect(treeGrid.rowList.length).toBe(5);
1041+
expect(treeGrid.selectedRows.length).toBe(3);
1042+
TreeGridFunctions.verifyRowByIndexSelectionAndCheckboxState(fix, 0, true, true);
1043+
TreeGridFunctions.verifyRowByIndexSelectionAndCheckboxState(fix, 1, true, true);
1044+
TreeGridFunctions.verifyRowByIndexSelectionAndCheckboxState(fix, 2, true, true);
1045+
1046+
treeGrid.expandRow(2);
1047+
fix.detectChanges();
1048+
tick(1000);
1049+
fix.detectChanges();
1050+
1051+
expect(treeGrid.rowList.length).toBe(7);
1052+
expect(treeGrid.selectedRows.length).toBe(5);
1053+
TreeGridFunctions.verifyRowByIndexSelectionAndCheckboxState(fix, 0, true, true);
1054+
TreeGridFunctions.verifyRowByIndexSelectionAndCheckboxState(fix, 1, true, true);
1055+
TreeGridFunctions.verifyRowByIndexSelectionAndCheckboxState(fix, 2, true, true);
1056+
TreeGridFunctions.verifyRowByIndexSelectionAndCheckboxState(fix, 3, true, true);
1057+
TreeGridFunctions.verifyRowByIndexSelectionAndCheckboxState(fix, 4, true, true);
1058+
}));
10241059
});
10251060

10261061
describe('ChildDataKey', () => {

projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
[tabindex]="-1"
9696
[readonly]="true"
9797
[checked]="selected"
98+
[indeterminate]="indeterminate"
9899
[disabled]="deleted"
99100
disableRipple="true"
100101
[disableTransitions]="grid.disableTransitions"

projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.ts

+7
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ export class IgxTreeGridRowComponent extends IgxRowDirective<IgxTreeGridComponen
131131
this.treeRow.children && this.treeRow.children.length;
132132
}
133133

134+
/**
135+
* @hidden
136+
*/
137+
get indeterminate(): boolean {
138+
return this.selectionService.isRowInIndeterminateState(this.rowID);
139+
}
140+
134141
/**
135142
* @hidden
136143
*/

0 commit comments

Comments
 (0)