Skip to content

Commit e9843b4

Browse files
ViktorSlavovbazal4o
authored andcommitted
feat(igxTreeGrid): Row Editing (#2908)
1 parent adf3ce4 commit e9843b4

33 files changed

+1321
-250
lines changed

projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -900,12 +900,18 @@
900900
}
901901

902902
%igx-grid__td--edited {
903-
font-style: italic;
904-
color: --var($theme, 'cell-edited-value-color');
903+
%grid-cell-text {
904+
font-style: italic;
905+
color: --var($theme, 'cell-edited-value-color');
906+
}
905907
}
906908

907909
%igx-grid__tr--deleted {
908-
text-decoration-line: line-through;
910+
%grid-cell-text {
911+
font-style: italic;
912+
color: igx-color(map-get($theme, 'palette'), 'error');
913+
text-decoration-line: line-through;
914+
}
909915
}
910916

911917
%igx-grid__td--editing {

projects/igniteui-angular/src/lib/core/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,28 @@ export function cloneArray(array, deep?: boolean) {
1313
return arr;
1414
}
1515

16+
/**
17+
* Doesn't clone leaf items
18+
* @hidden
19+
*/
20+
export function cloneHierarchicalArray(array: any[], childDataKey: any): any[] {
21+
const result: any[] = [];
22+
if (!array) {
23+
return result;
24+
}
25+
26+
for (const item of array) {
27+
if (Array.isArray(item[childDataKey])) {
28+
const clonedItem = cloneValue(item);
29+
clonedItem[childDataKey] = cloneHierarchicalArray(clonedItem[childDataKey], childDataKey);
30+
result.push(clonedItem);
31+
} else {
32+
result.push(item);
33+
}
34+
}
35+
return result;
36+
}
37+
1638
/**
1739
* Deep clones all first level keys of Obj2 and merges them to Obj1
1840
* @param obj1 Object to merge into

projects/igniteui-angular/src/lib/data-operations/data-util.ts

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,13 @@
1-
import {
2-
IgxFilteringOperand,
3-
IgxBooleanFilteringOperand,
4-
IgxDateFilteringOperand,
5-
IgxNumberFilteringOperand,
6-
IgxStringFilteringOperand
7-
} from './filtering-condition';
8-
import { FilteringLogic, IFilteringExpression } from './filtering-expression.interface';
91
import { filteringStateDefaults, IFilteringState } from './filtering-state.interface';
10-
import { FilteringStrategy, IFilteringStrategy } from './filtering-strategy';
11-
12-
import { ISortingExpression, SortingDirection } from './sorting-expression.interface';
132
import { ISortingState, SortingStateDefaults } from './sorting-state.interface';
14-
import { ISortingStrategy, SortingStrategy, IGroupByResult, TreeGridSortingStrategy } from './sorting-strategy';
15-
3+
import { IGroupByResult, TreeGridSortingStrategy } from './sorting-strategy';
164
import { IPagingState, PagingError } from './paging-state.interface';
17-
185
import { IDataState } from './data-state.interface';
196
import { IGroupByExpandState, IGroupByKey } from './groupby-expand-state.interface';
207
import { IGroupByRecord } from './groupby-record.interface';
218
import { IGroupingState } from './groupby-state.interface';
22-
import { Transaction, TransactionType } from '../services';
9+
import { Transaction, TransactionType, HierarchicalTransaction, IgxHierarchicalTransactionService, HierarchicalState } from '../services';
10+
import { mergeObjects, cloneValue } from '../core/utils';
2311
import { ITreeGridRecord } from '../grids/tree-grid/tree-grid.interfaces';
2412

2513
export enum DataType {
@@ -79,7 +67,8 @@ export class DataUtil {
7967
children: hierarchicalRecord.children,
8068
isFilteredOutParent: hierarchicalRecord.isFilteredOutParent,
8169
level: hierarchicalRecord.level,
82-
expanded: hierarchicalRecord.expanded
70+
expanded: hierarchicalRecord.expanded,
71+
path: [...hierarchicalRecord.path]
8372
};
8473
return rec;
8574
}
@@ -224,9 +213,12 @@ export class DataUtil {
224213
* @param primaryKey Primary key of the collection, if any
225214
*/
226215
public static mergeTransactions<T>(data: T[], transactions: Transaction[], primaryKey?: any): T[] {
227-
data.forEach((value, index) => {
228-
const rowId = primaryKey ? value[primaryKey] : value;
216+
data.forEach((item: any, index: number) => {
217+
const rowId = primaryKey ? item[primaryKey] : item;
229218
const transaction = transactions.find(t => t.id === rowId);
219+
if (Array.isArray(item.children)) {
220+
this.mergeTransactions(item.children, transactions, primaryKey);
221+
}
230222
if (transaction && transaction.type === TransactionType.UPDATE) {
231223
data[index] = transaction.newValue;
232224
}
@@ -237,4 +229,42 @@ export class DataUtil {
237229
.map(t => t.newValue));
238230
return data;
239231
}
232+
233+
// TODO: optimize addition of added rows. Should not filter transaction in each recursion!!!
234+
/** @experimental @hidden */
235+
public static mergeHierarchicalTransactions(
236+
data: any[],
237+
transactions: HierarchicalTransaction[],
238+
childDataKey: any,
239+
primaryKey?: any,
240+
parentKey?: any): any[] {
241+
242+
for (let index = 0; index < data.length; index++) {
243+
const dataItem = data[index];
244+
const rowId = primaryKey ? dataItem[primaryKey] : dataItem;
245+
const updateTransaction = transactions.filter(t => t.type === TransactionType.UPDATE).find(t => t.id === rowId);
246+
const addedTransactions = transactions.filter(t => t.type === TransactionType.ADD).filter(t => t.parentId === rowId);
247+
if (updateTransaction || addedTransactions.length > 0) {
248+
data[index] = mergeObjects(cloneValue(dataItem), updateTransaction && updateTransaction.newValue);
249+
}
250+
if (addedTransactions.length > 0) {
251+
if (!data[index][childDataKey]) {
252+
data[index][childDataKey] = [];
253+
}
254+
for (const addedTransaction of addedTransactions) {
255+
data[index][childDataKey].push(addedTransaction.newValue);
256+
}
257+
}
258+
if (data[index][childDataKey]) {
259+
data[index][childDataKey] = this.mergeHierarchicalTransactions(
260+
data[index][childDataKey],
261+
transactions,
262+
childDataKey,
263+
primaryKey,
264+
rowId
265+
);
266+
}
267+
}
268+
return data;
269+
}
240270
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent> {
307307
dataWithTransactions.map((record) => record[grid.primaryKey]).indexOf(rowID) :
308308
dataWithTransactions.indexOf(rowID);
309309
if (rowIndex !== -1) {
310+
// Check if below change will work on added rows with transactions
311+
// oldValue = this.get_all_data(id, true)[rowIndex][column.field];
312+
// rowData = this.get_all_data(id, true)[rowIndex];
310313
oldValue = columnID !== null ? dataWithTransactions[rowIndex][column.field] : null;
311314
rowData = dataWithTransactions[rowIndex];
312315
}
@@ -596,9 +599,10 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent> {
596599
return column.dataType === DataType.Number;
597600
}
598601

599-
public get_all_data(id: string): any[] {
602+
public get_all_data(id: string, transactions?: boolean): any[] {
600603
const grid = this.get(id);
601-
return grid.data;
604+
const data = transactions ? grid.dataWithAddedInTransactionRows : grid.data;
605+
return data ? data : [];
602606
}
603607

604608
protected getSortStrategyPerColumn(id: string, fieldName: string) {

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

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
import { Subject } from 'rxjs';
3333
import { takeUntil, first } from 'rxjs/operators';
3434
import { IgxSelectionAPIService } from '../core/selection';
35-
import { cloneArray, isNavigationKey, CancelableEventArgs } from '../core/utils';
35+
import { cloneArray, isNavigationKey, mergeObjects, CancelableEventArgs } from '../core/utils';
3636
import { DataType, DataUtil } from '../data-operations/data-util';
3737
import { FilteringLogic, IFilteringExpression } from '../data-operations/filtering-expression.interface';
3838
import { IGroupByExpandState } from '../data-operations/groupby-expand-state.interface';
@@ -1633,7 +1633,7 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
16331633
/**
16341634
* Get transactions service for the grid.
16351635
*/
1636-
get transactions() {
1636+
get transactions(): TransactionService<Transaction, State> {
16371637
return this._transactions;
16381638
}
16391639

@@ -2062,7 +2062,9 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
20622062
this.zone.run(() => {
20632063
this.cdr.detectChanges();
20642064
this.verticalScrollContainer.onChunkLoad.emit(this.verticalScrollContainer.state);
2065-
this.changeRowEditingOverlayStateOnScroll(this.rowInEditMode);
2065+
if (this.rowEditable) {
2066+
this.changeRowEditingOverlayStateOnScroll(this.rowInEditMode);
2067+
}
20662068
});
20672069
}
20682070

@@ -2097,7 +2099,7 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
20972099
constructor(
20982100
private gridAPI: GridBaseAPIService<IgxGridBaseComponent>,
20992101
public selection: IgxSelectionAPIService,
2100-
@Inject(IgxGridTransaction) private _transactions: TransactionService,
2102+
@Inject(IgxGridTransaction) protected _transactions: TransactionService<Transaction, State>,
21012103
private elementRef: ElementRef,
21022104
private zone: NgZone,
21032105
@Inject(DOCUMENT) public document,
@@ -2859,26 +2861,17 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
28592861
return;
28602862
}
28612863

2864+
// TODO: should we emit this when cascadeOnDelete is true for each row?!?!
28622865
this.onRowDeleted.emit({ data: data[index] });
28632866

2864-
// if there is a row (index !== 0) delete it
2865-
// if there is a row in ADD or UPDATE state change it's state to DELETE
2866-
if (index !== -1) {
2867-
if (this.transactions.enabled) {
2868-
const transaction: Transaction = { id: rowId, type: TransactionType.DELETE, newValue: null };
2869-
this.transactions.add(transaction, data[index]);
2870-
} else {
2871-
this.deleteRowFromData(rowId, index);
2872-
}
2873-
} else {
2874-
this.transactions.add({ id: rowId, type: TransactionType.DELETE, newValue: null }, state.recordRef);
2875-
}
2876-
2877-
if (this.rowSelectable === true && this.selection.is_item_selected(this.id, rowId)) {
2867+
// first deselect row then delete it
2868+
if (this.rowSelectable && this.selection.is_item_selected(this.id, rowId)) {
28782869
this.deselectRows([rowId]);
28792870
} else {
28802871
this.checkHeaderCheckboxStatus();
28812872
}
2873+
2874+
this.deleteRowFromData(rowId, index);
28822875
this._pipeTrigger++;
28832876
this.cdr.markForCheck();
28842877

@@ -2892,7 +2885,19 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
28922885
* @hidden
28932886
*/
28942887
protected deleteRowFromData(rowID: any, index: number) {
2895-
this.data.splice(index, 1);
2888+
// if there is a row (index !== 0) delete it
2889+
// if there is a row in ADD or UPDATE state change it's state to DELETE
2890+
if (index !== -1) {
2891+
if (this.transactions.enabled) {
2892+
const transaction: Transaction = { id: rowID, type: TransactionType.DELETE, newValue: null };
2893+
this.transactions.add(transaction, this.data[index]);
2894+
} else {
2895+
this.data.splice(index, 1);
2896+
}
2897+
} else {
2898+
const state: State = this.transactions.getState(rowID);
2899+
this.transactions.add({ id: rowID, type: TransactionType.DELETE, newValue: null }, state && state.recordRef);
2900+
}
28962901
}
28972902

28982903
/**
@@ -3542,7 +3547,7 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
35423547
if (this.rowSelectable) {
35433548
this.calcRowCheckboxWidth = this.headerCheckboxContainer.nativeElement.clientWidth;
35443549
}
3545-
if (this.rowEditable && !this.rowEditingOverlay.collapsed) {
3550+
if (this.rowEditable) {
35463551
this.repositionRowEditingOverlay(this.rowInEditMode);
35473552
}
35483553
this.cdr.detectChanges();
@@ -3842,7 +3847,7 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
38423847
* this.grid.selectRows([1,2,5], true);
38433848
* ```
38443849
* @param rowIDs
3845-
* @param clearCurrentSelection if true clears the curren selection
3850+
* @param clearCurrentSelection if true clears the current selection
38463851
* @memberof IgxGridBaseComponent
38473852
*/
38483853
public selectRows(rowIDs: any[], clearCurrentSelection?: boolean) {
@@ -4319,18 +4324,18 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
43194324
this.nativeElement.focus();
43204325
} */
43214326

4322-
private changeRowEditingOverlayStateOnScroll(row: IgxRowComponent<IgxGridBaseComponent>) {
4323-
if (!this.rowEditable || this.rowEditingOverlay.collapsed) {
4324-
return;
4325-
}
4326-
if (!row) {
4327-
this.toggleRowEditingOverlay(false);
4328-
} else {
4329-
this.repositionRowEditingOverlay(row);
4327+
private changeRowEditingOverlayStateOnScroll(row: IgxRowComponent<IgxGridBaseComponent>) {
4328+
if (!this.rowEditable || this.rowEditingOverlay.collapsed) {
4329+
return;
4330+
}
4331+
if (!row) {
4332+
this.toggleRowEditingOverlay(false);
4333+
} else {
4334+
this.repositionRowEditingOverlay(row);
4335+
}
43304336
}
4331-
}
43324337

4333-
/**
4338+
/**
43344339
* @hidden
43354340
*/
43364341
public startRowEdit(cell: {
@@ -4365,6 +4370,7 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
43654370
this.rowEditingOverlay.element.removeEventListener('wheel', this.rowEditingWheelHandler);
43664371
this.rowEditPositioningStrategy.isTopInitialPosition = null;
43674372
this.rowEditingOverlay.close();
4373+
this.rowEditingOverlay.element.parentElement.style.display = '';
43684374
}
43694375

43704376
/**
@@ -4383,9 +4389,15 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
43834389
* @hidden
43844390
*/
43854391
public repositionRowEditingOverlay(row: IgxRowComponent<IgxGridBaseComponent>) {
4386-
this.configureRowEditingOverlay(row.rowID);
43874392
if (!this.rowEditingOverlay.collapsed) {
4388-
this.rowEditingOverlay.reposition();
4393+
const rowStyle = this.rowEditingOverlay.element.parentElement.style;
4394+
if (row) {
4395+
rowStyle.display = '';
4396+
this.configureRowEditingOverlay(row.rowID);
4397+
this.rowEditingOverlay.reposition();
4398+
} else {
4399+
rowStyle.display = 'none';
4400+
}
43894401
}
43904402
}
43914403

@@ -4412,6 +4424,9 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
44124424
return rowChanges ? Object.keys(rowChanges).length : 0;
44134425
}
44144426

4427+
protected writeToData(rowIndex: number, value: any) {
4428+
mergeObjects(this.data[rowIndex], value);
4429+
}
44154430
/**
44164431
* TODO: Refactor
44174432
* @hidden

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,7 @@ export class IgxGridAPIService extends GridBaseAPIService<IgxGridComponent> {
111111
}
112112
this.get(id).groupingExpansionState = expansionState;
113113
if (grid.rowEditable) {
114-
if (toggleRowEditingOverlay !== undefined) {
115-
grid.toggleRowEditingOverlay(toggleRowEditingOverlay);
116-
}
117-
118-
// If row overlay is opened in a group and another group is expanded/collapsed,
119-
// then the row in edit will move down/up and therefore the row edit overlay should move down/up.
120-
if (grid.rowInEditMode && !grid.rowEditingOverlay.collapsed) {
121-
grid.repositionRowEditingOverlay(grid.rowInEditMode);
122-
}
114+
grid.repositionRowEditingOverlay(grid.rowInEditMode);
123115
}
124116
}
125117

0 commit comments

Comments
 (0)