Skip to content

Commit 1ad1df3

Browse files
authored
Merge pull request #5651 from IgniteUI/ddincheva/gridSelection
Grid Selection --- 8.2.x
2 parents fd40ed4 + d23c272 commit 1ad1df3

File tree

59 files changed

+3761
-1851
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+3761
-1851
lines changed

CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ All notable changes for each version of this project will be documented in this
88
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
99
- `uniqueColumnValuesStrategy` input is added. This property provides a callback for loading unique column values on demand. If this property is provided, the unique values it generates will be used by the Excel Style Filtering (instead of using the unique values from the data that is bound to the grid).
1010
- `igxExcelStyleLoading` directive is added, which can be used to provide a custom loading template for the Excel Style Filtering. If this property is not provided, a default loading template will be used instead.
11+
- introduced new properties `cellSelection` and `rowSelection` which accept GridSelection mode enumeration. Grid selection mode could be none, single or multiple. Also `hideRowSelectors` property is added, which allows you to show and hide row selectors when row selection is enabled.
1112
- `IgxHierarchicalGrid`
1213
- Row Islands now emit child grid events with an additional argument - `owner`, which holds reference to the related child grid component instance.
1314
- `IgxDrag`
@@ -24,9 +25,13 @@ All notable changes for each version of this project will be documented in this
2425

2526
### General
2627
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
28+
- `isCellSelected` method has been deprecated. Now you can use `selected` property.
29+
- `rowSelectable` property has been deprecated. Now you can use `rowSelection` property to enable row selection and also you can show and hide the row selectors by setting `hideRowSelectors` property to true or false (which is the default value).
30+
- Removed deprecated event `OnFocusChange`
2731
- **Breaking Change** `igxExcelStyleSortingTemplate` directive is renamed to `igxExcelStyleSorting`.
2832
- **Breaking Change** `igxExcelStyleMovingTemplate` directive is renamed to `igxExcelStyleMoving`.
2933
- **Breaking Change** `igxExcelStyleHidingTemplate` directive is renamed to `igxExcelStyleHiding`.
34+
- **Breaking Change** `onRowSelectionChange` event arguments are changed. The `row` property has been removed and the properties `added`, `removed` and `cancel` are newly added.
3035
- **Breaking Change** `igxExcelStylePinningTemplate` directive is renamed to `igxExcelStylePinning`.
3136
- **Breaking Change** `onRowDragEnd` and `onRowDragStart` event arguments are changed - `owner` now holds reference to the grid component instance, while `dragDirective` hold reference to the drag directive.
3237
- `IgxCombo`
@@ -50,7 +55,7 @@ All notable changes for each version of this project will be documented in this
5055
- For more information, visit the component's [readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/combo/README.md)
5156

5257
## 8.1.4
53-
- `IgxDialog` new @Input `positionSettings` is now available. It provides the ability to get/set both position and animation settings of the Dialog component.
58+
- `IgxDialog` new @Input `positionSettings` is now available. It provides the ability to get/set both position and animation settings of the Dialog component.
5459

5560
- `IgxDrag`
5661
- Deprecated inputs - `hideBaseOnDrag`, `animateOnRelease`, `visible`.

projects/igniteui-angular/src/lib/core/grid-selection.ts

+152-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Injectable, EventEmitter, NgZone } from '@angular/core';
22
import { IGridEditEventArgs } from '../grids/grid-base.component';
33

4-
54
export interface GridSelectionRange {
65
rowStart: number;
76
rowEnd: number;
@@ -202,7 +201,7 @@ export class IgxGridCRUDService {
202201

203202
@Injectable()
204203
export class IgxGridSelectionService {
205-
204+
grid;
206205
dragMode = false;
207206
activeElement: ISelectionNode | null;
208207
keyboardState = {} as ISelectionKeyboardState;
@@ -213,7 +212,8 @@ export class IgxGridSelectionService {
213212
temp = new Map<number, Set<number>>();
214213
_ranges: Set<string> = new Set<string>();
215214
_selectionRange: Range;
216-
215+
rowSelection: Set<any> = new Set<any>();
216+
private allRowsSelected: boolean;
217217

218218
/**
219219
* Returns the current selected ranges in the grid from both
@@ -227,7 +227,7 @@ export class IgxGridSelectionService {
227227
const ranges = Array.from(this._ranges).map(range => JSON.parse(range));
228228

229229
// No ranges but we have a focused cell -> add it
230-
if (!ranges.length && this.activeElement) {
230+
if (!ranges.length && this.activeElement && this.grid.isCellSelectable) {
231231
ranges.push(this.generateRange(this.activeElement));
232232
}
233233

@@ -304,7 +304,7 @@ export class IgxGridSelectionService {
304304
}
305305

306306
selected(node: ISelectionNode): boolean {
307-
return this.isActiveNode(node) || this.isInMap(node);
307+
return (this.isActiveNode(node) && this.grid.isCellSelectable) || this.isInMap(node);
308308
}
309309

310310
isActiveNode(node: ISelectionNode, mrl = false): boolean {
@@ -394,7 +394,6 @@ export class IgxGridSelectionService {
394394
}
395395

396396
pointerDown(node: ISelectionNode, shift: boolean, ctrl: boolean): void {
397-
398397
this.addKeyboardRange();
399398
this.initKeyboardState();
400399
this.pointerState.ctrl = ctrl;
@@ -506,7 +505,8 @@ export class IgxGridSelectionService {
506505
this.selectRange(node, state);
507506
}
508507

509-
clear(): void {
508+
clear(clearAcriveEl = false): void {
509+
if (clearAcriveEl) { this.activeElement = null; }
510510
this.selection.clear();
511511
this.temp.clear();
512512
this._ranges.clear();
@@ -541,6 +541,151 @@ export class IgxGridSelectionService {
541541
range.collapse(true);
542542
selection.addRange(range);
543543
}
544+
545+
/** Returns array of the selected row id's. */
546+
getSelectedRows(): Array<any> {
547+
return this.rowSelection.size ? Array.from(this.rowSelection.keys()) : [];
548+
}
549+
550+
/** Clears row selection, if filtering is applied clears only selected rows from filtered data. */
551+
clearRowSelection(event?): void {
552+
const removedRec = this.isFilteringApplied() ?
553+
this.getRowIDs(this.allData).filter(rID => this.isRowSelected(rID)) : this.getSelectedRows();
554+
const newSelection = this.isFilteringApplied() ? this.getSelectedRows().filter(x => !removedRec.includes(x)) : [];
555+
this.emitRowSelectionEvent(newSelection, [], removedRec, event);
556+
}
557+
558+
/** Select all rows, if filtering is applied select only from filtered data. */
559+
selectAllRows(event?) {
560+
const allRowIDs = this.getRowIDs(this.allData);
561+
const addedRows = allRowIDs.filter((rID) => !this.isRowSelected(rID));
562+
const newSelection = this.rowSelection.size ? allRowIDs.concat(this.getSelectedRows()) : addedRows;
563+
564+
this.emitRowSelectionEvent(newSelection, addedRows, [], event);
565+
}
566+
567+
/** Select the specified row and emit event. */
568+
selectRowById(rowID, clearPrevSelection?, event?): void {
569+
if (!this.grid.isRowSelectable || this.isRowDeleted(rowID)) { return; }
570+
clearPrevSelection = !this.grid.isMultiRowSelectionEnabled || clearPrevSelection;
571+
572+
const newSelection = clearPrevSelection ? [rowID] : this.getSelectedRows().indexOf(rowID) !== -1 ?
573+
this.getSelectedRows() : [...this.getSelectedRows(), rowID];
574+
const removed = clearPrevSelection ? this.getSelectedRows() : [];
575+
this.emitRowSelectionEvent(newSelection, [rowID], removed, event);
576+
}
577+
578+
/** Deselect the specified row and emit event. */
579+
deselectRow(rowID, event?): void {
580+
if (!this.isRowSelected(rowID)) { return; }
581+
const newSelection = this.getSelectedRows().filter(r => r !== rowID);
582+
if (this.rowSelection.size && this.rowSelection.has(rowID)) {
583+
this.emitRowSelectionEvent(newSelection, [], [rowID], event);
584+
}
585+
}
586+
587+
/** Select specified rows. No event is emitted. */
588+
selectRowsWithNoEvent(rowIDs: any[], clearPrevSelection?): void {
589+
if (clearPrevSelection) { this.rowSelection.clear(); }
590+
rowIDs.forEach(rowID => { this.rowSelection.add(rowID); });
591+
this.allRowsSelected = undefined;
592+
}
593+
594+
/** Deselect specified rows. No event is emitted. */
595+
deselectRowsWithNoEvent(rowIDs: any[]): void {
596+
rowIDs.forEach(rowID => this.rowSelection.delete(rowID));
597+
this.allRowsSelected = undefined;
598+
}
599+
600+
isRowSelected(rowID): boolean {
601+
return this.rowSelection.size > 0 && this.rowSelection.has(rowID);
602+
}
603+
604+
/** Select range from last selected row to the current specified row.*/
605+
selectMultipleRows(rowID, rowData, event?): void {
606+
this.allRowsSelected = undefined;
607+
if (!this.rowSelection.size || this.isRowDeleted(rowID)) {
608+
this.selectRowById(rowID);
609+
return;
610+
}
611+
const gridData = this.allData;
612+
const lastRowID = this.getSelectedRows()[this.rowSelection.size - 1];
613+
const currIndex = gridData.indexOf(this.getRowDataById(lastRowID));
614+
const newIndex = gridData.indexOf(rowData);
615+
const rows = gridData.slice(Math.min(currIndex, newIndex), Math.max(currIndex, newIndex) + 1);
616+
617+
const added = this.getRowIDs(rows).filter(rID => !this.isRowSelected(rID));
618+
const newSelection = this.getSelectedRows().concat(added);
619+
620+
this.emitRowSelectionEvent(newSelection, added, [], event);
621+
}
622+
623+
areAllRowSelected(): boolean {
624+
if (!this.grid.data) { return false; }
625+
if (this.allRowsSelected !== undefined) { return this.allRowsSelected; }
626+
627+
const dataItemsID = this.getRowIDs(this.allData);
628+
return this.allRowsSelected = Math.min(this.rowSelection.size, dataItemsID.length) > 0 &&
629+
new Set(Array.from(this.rowSelection.values()).concat(dataItemsID)).size === this.rowSelection.size;
630+
}
631+
632+
hasSomeRowSelected(): boolean {
633+
const filteredData = this.isFilteringApplied() ?
634+
this.getRowIDs(this.grid.filteredData).some(rID => this.isRowSelected(rID)) : true;
635+
return this.rowSelection.size > 0 && filteredData && !this.areAllRowSelected();
636+
}
637+
638+
public emitRowSelectionEvent(newSelection, added, removed, event?): boolean {
639+
const currSelection = this.getSelectedRows();
640+
if (this.areEqualCollections(currSelection, newSelection)) { return; }
641+
642+
const args = {
643+
oldSelection: currSelection, newSelection: newSelection,
644+
added: added, removed: removed, event: event, cancel: false
645+
};
646+
this.grid.onRowSelectionChange.emit(args);
647+
if (args.cancel) { return; }
648+
this.selectRowsWithNoEvent(args.newSelection, true);
649+
}
650+
651+
public getRowDataById(rowID): Object {
652+
if (!this.grid.primaryKey) { return rowID; }
653+
const rowIndex = this.getRowIDs(this.grid.gridAPI.get_all_data(true)).indexOf(rowID);
654+
return rowIndex < 0 ? {} : this.grid.gridAPI.get_all_data(true)[rowIndex];
655+
}
656+
657+
public getRowIDs(data): Array<any> {
658+
return this.grid.primaryKey && data.length ? data.map(rec => rec[this.grid.primaryKey]) : data;
659+
}
660+
661+
public clearHeaderCBState(): void {
662+
this.allRowsSelected = undefined;
663+
}
664+
665+
/**Clear rowSelection and update checkbox state*/
666+
public clearAllSelectedRows(): void {
667+
this.rowSelection.clear();
668+
this.clearHeaderCBState();
669+
}
670+
671+
/** Returns all data in the grid, with applied filtering and sorting and without deleted rows. */
672+
public get allData(): Array<any> {
673+
const allData = this.isFilteringApplied() || this.grid.sortingExpressions.length ?
674+
this.grid.filteredSortedData : this.grid.gridAPI.get_all_data(true);
675+
return allData.filter(rData => !this.isRowDeleted(this.grid.gridAPI.get_row_id(rData)));
676+
}
677+
678+
private areEqualCollections(first, second): boolean {
679+
return first.length === second.length && new Set(first.concat(second)).size === first.length;
680+
}
681+
682+
private isFilteringApplied(): boolean {
683+
return this.grid.filteringExpressionsTree.filteringOperands.length > 0;
684+
}
685+
686+
private isRowDeleted(rowID): boolean {
687+
return this.grid.gridAPI.row_deleted_transaction(rowID);
688+
}
544689
}
545690

546691
export function isChromium(): boolean {

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

+9-15
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent & IGridDataBinda
150150
this.grid.summaryService.clearSummaryCache(args);
151151
this.updateData(this.grid, cell.id.rowID, data[index], cell.rowData, { [cell.column.field ]: args.newValue });
152152
if (this.grid.primaryKey === cell.column.field) {
153-
if (this.grid.selection.is_item_selected(this.grid.id, cell.id.rowID)) {
154-
this.grid.selection.deselect_item(this.grid.id, cell.id.rowID);
155-
this.grid.selection.select_item(this.grid.id, args.newValue);
153+
if (this.grid.selectionService.isRowSelected(cell.id.rowID)) {
154+
this.grid.selectionService.deselectRow(cell.id.rowID);
155+
this.grid.selectionService.selectRowById(args.newValue);
156156
}
157157
if (this.grid.hasSummarizedColumns) {
158158
this.grid.summaryService.removeSummaries(cell.id.rowID);
@@ -207,6 +207,7 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent & IGridDataBinda
207207

208208
update_row(row: IgxRow, value: any) {
209209
const grid = this.grid;
210+
const selected = grid.selectionService.isRowSelected(row.id);
210211
const rowInEditMode = grid.crudService.row;
211212
const data = this.get_all_data(grid.transactions.enabled);
212213
const index = this.get_row_index_in_data(row.id);
@@ -245,10 +246,9 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent & IGridDataBinda
245246

246247
this.updateData(grid, row.id, data[index], args.oldValue, args.newValue);
247248
const newId = grid.primaryKey ? args.newValue[grid.primaryKey] : args.newValue;
248-
const selected = grid.selection.is_item_selected(grid.id, row.id);
249249
if (selected) {
250-
grid.selection.deselect_item(grid.id, row.id);
251-
grid.selection.select_item(grid.id, newId);
250+
grid.selectionService.deselectRow(row.id);
251+
grid.selectionService.selectRowById(newId);
252252
}
253253
if (hasSummarized) {
254254
grid.summaryService.removeSummaries(newId);
@@ -503,14 +503,9 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent & IGridDataBinda
503503
// TODO: should we emit this when cascadeOnDelete is true for each row?!?!
504504
grid.onRowDeleted.emit({ data: data[index] });
505505

506-
// first deselect row then delete it
507-
if (grid.rowSelectable && grid.selection.is_item_selected(grid.id, rowId)) {
508-
grid.deselectRows([rowId]);
509-
} else {
510-
grid.checkHeaderCheckboxStatus();
511-
}
512-
513506
this.deleteRowFromData(rowId, index);
507+
508+
grid.selectionService.isRowSelected(rowId) ? grid.selectionService.deselectRow(rowId) : grid.selectionService.clearHeaderCBState();
514509
(grid as any)._pipeTrigger++;
515510
grid.notifyChanges();
516511
// Data needs to be recalculated if transactions are in place
@@ -523,8 +518,7 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent & IGridDataBinda
523518
}
524519

525520
public get_row_id(rowData) {
526-
const grid = this.grid;
527-
return grid.primaryKey ? rowData[grid.primaryKey] : rowData;
521+
return this.grid.primaryKey ? rowData[this.grid.primaryKey] : rowData;
528522
}
529523

530524
public row_deleted_transaction(rowID: any): boolean {

0 commit comments

Comments
 (0)