• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

3.24
/projects/igniteui-angular/src/lib/grids/common/crud.service.ts
1
import { Injectable } from '@angular/core';
2
import { first } from 'rxjs/operators';
3
import { IGridEditDoneEventArgs, IGridEditEventArgs, IRowDataCancelableEventArgs, IRowDataEventArgs } from '../common/events';
4
import { GridType, RowType } from './grid.interface';
5
import { Subject } from 'rxjs';
6
import { copyDescriptors, isEqual, isDate } from '../../core/utils';
7
import { FormGroup } from '@angular/forms';
8
import { DateTimeUtil } from '../../date-common/util/date-time.util';
9

10
export class IgxEditRow {
11
    public transactionState: any;
12
    public state: any;
13
    public newData: any;
UNCOV
14
    public rowFormGroup = new FormGroup({});
×
15

UNCOV
16
    constructor(public id: any, public index: number, public data: any, public grid: GridType) {
×
UNCOV
17
        this.rowFormGroup = this.grid.validation.create(id, data);
×
18
    }
19

20
    public createRowEditEventArgs(includeNewValue = true, event?: Event): IGridEditEventArgs {
×
UNCOV
21
        const args: IGridEditEventArgs = {
×
22
            primaryKey: this.id,
23
            rowID: this.id,
24
            rowKey: this.id,
25
            rowData: this.data,
26
            oldValue: this.data,
27
            cancel: false,
28
            owner: this.grid,
29
            isAddRow: false,
30
            valid: this.rowFormGroup.valid,
31
            event
32
        };
UNCOV
33
        if (includeNewValue) {
×
UNCOV
34
            args.newValue = this.newData ?? this.data;
×
35
        }
UNCOV
36
        return args;
×
37
    }
38

39
    public createRowDataEventArgs(event?: Event): IRowDataCancelableEventArgs {
UNCOV
40
        const args: IRowDataCancelableEventArgs = {
×
41
            rowID: this.id,
42
            primaryKey: this.id,
43
            rowKey: this.id,
44
            rowData: this.newData ?? this.data,
×
45
            data: this.newData ?? this.data,
×
46
            oldValue: this.data,
47
            cancel: false,
48
            owner: this.grid,
49
            isAddRow: true,
50
            valid: this.rowFormGroup.valid,
51
            event
52
        };
UNCOV
53
        return args;
×
54
    }
55

56
    public createRowEditDoneEventArgs(cachedRowData: any, event?: Event): IGridEditDoneEventArgs {
UNCOV
57
        const updatedData = this.grid.transactions.enabled ?
×
58
            this.grid.transactions.getAggregatedValue(this.id, true) : this.grid.gridAPI.getRowData(this.id);
UNCOV
59
        const rowData = updatedData ?? this.grid.gridAPI.getRowData(this.id);
×
UNCOV
60
        const args: IGridEditDoneEventArgs = {
×
61
            primaryKey: this.id,
62
            rowID: this.id,
63
            rowKey: this.id,
64
            rowData,
65
            oldValue: cachedRowData,
66
            newValue: updatedData,
67
            owner: this.grid,
68
            isAddRow: false,
69
            valid: true,
70
            event
71
        };
72

UNCOV
73
        return args;
×
74
    }
75

76
    public getClassName() {
UNCOV
77
        return this.constructor.name;
×
78
    }
79
}
80

81
export class IgxAddRow extends IgxEditRow {
UNCOV
82
    public isAddRow = true;
×
83

84
    constructor(id: any,
85
        index: number,
86
        data: any,
UNCOV
87
        public recordRef: any,
×
88
        grid: GridType) {
UNCOV
89
        super(id, index, data, grid);
×
90
    }
91

92
    public override createRowEditEventArgs(includeNewValue = true, event?: Event): IGridEditEventArgs {
×
UNCOV
93
        const args = super.createRowEditEventArgs(includeNewValue, event);
×
UNCOV
94
        args.oldValue = null;
×
UNCOV
95
        args.isAddRow = true;
×
UNCOV
96
        args.rowData = this.newData ?? this.data;
×
UNCOV
97
        return args;
×
98
    }
99

100
    public override createRowEditDoneEventArgs(cachedRowData: any, event?: Event): IGridEditDoneEventArgs {
UNCOV
101
        const args = super.createRowEditDoneEventArgs(null, event);
×
UNCOV
102
        args.isAddRow = true;
×
UNCOV
103
        return args;
×
104
    }
105
}
106

107
export interface IgxAddRowParent {
108
    /**
109
     * @deprecated since version 17.1.0. Use `rowKey` instead
110
     */
111
    rowID: string;
112
    rowKey: any;
113
    index: number;
114
    asChild: boolean;
115
    isPinned: boolean;
116
}
117

118
export class IgxCell {
119
    public primaryKey: any;
120
    public state: any;
121
    public pendingValue: any;
122

123
    constructor(
UNCOV
124
        public id,
×
UNCOV
125
        public rowIndex: number,
×
UNCOV
126
        public column,
×
UNCOV
127
        public value: any,
×
UNCOV
128
        public _editValue: any,
×
UNCOV
129
        public rowData: any,
×
UNCOV
130
        public grid: GridType) {
×
UNCOV
131
        this.grid.validation.create(id.rowID, rowData);
×
132
    }
133

134
    public get editValue() {
UNCOV
135
        const formControl = this.grid.validation.getFormControl(this.id.rowID, this.column.field);
×
UNCOV
136
        if (formControl) {
×
UNCOV
137
            return formControl.value;
×
138
        }
139
    }
140

141
    public set editValue(value) {
UNCOV
142
        const formControl = this.grid.validation.getFormControl(this.id.rowID, this.column.field);
×
143

UNCOV
144
        if (this.grid.validationTrigger === 'change') {
×
145
            // in case trigger is change, mark as touched.
UNCOV
146
            formControl.setValue(value);
×
UNCOV
147
            formControl.markAsTouched();
×
148
        } else {
UNCOV
149
            this.pendingValue = value;
×
150
        }
151
    }
152

153
    public castToNumber(value: any): any {
UNCOV
154
        if (this.column.dataType === 'number' && !this.column.inlineEditorTemplate) {
×
UNCOV
155
            const v = parseFloat(value);
×
UNCOV
156
            return !isNaN(v) && isFinite(v) ? v : 0;
×
157
        }
UNCOV
158
        return value;
×
159
    }
160

161
    public createCellEditEventArgs(includeNewValue = true, event?: Event): IGridEditEventArgs {
×
UNCOV
162
        const formControl = this.grid.validation.getFormControl(this.id.rowID, this.column.field);
×
UNCOV
163
        const args: IGridEditEventArgs = {
×
164
            primaryKey: this.id.rowID,
165
            rowID: this.id.rowID,
166
            rowKey: this.id.rowID,
167
            cellID: this.id,
168
            rowData: this.rowData,
169
            oldValue: this.value,
170
            cancel: false,
171
            column: this.column,
172
            owner: this.grid,
173
            valid: formControl ? formControl.valid : true,
×
174
            event
175
        };
UNCOV
176
        if (includeNewValue) {
×
UNCOV
177
            args.newValue = this.castToNumber(this.editValue);
×
178
        }
UNCOV
179
        return args;
×
180
    }
181

182
    public createCellEditDoneEventArgs(value: any, event?: Event): IGridEditDoneEventArgs {
UNCOV
183
        const updatedData = this.grid.transactions.enabled ?
×
184
            this.grid.transactions.getAggregatedValue(this.id.rowID, true) : this.rowData;
UNCOV
185
        const rowData = updatedData === null ? this.grid.gridAPI.getRowData(this.id.rowID) : updatedData;
×
UNCOV
186
        const formControl = this.grid.validation.getFormControl(this.id.rowID, this.column.field);
×
UNCOV
187
        const args: IGridEditDoneEventArgs = {
×
188
            primaryKey: this.id.rowID,
189
            rowID: this.id.rowID,
190
            rowKey: this.id.rowID,
191
            cellID: this.id,
192
            // rowData - should be the updated/committed rowData - this effectively should be the newValue
193
            // the only case we use this.rowData directly, is when there is no rowEditing or transactions enabled
194
            rowData,
195
            oldValue: this.value,
196
            valid: formControl ? formControl.valid : true,
×
197
            newValue: value,
198
            column: this.column,
199
            owner: this.grid,
200
            event
201
        };
UNCOV
202
        return args;
×
203
    }
204
}
205

206
export class IgxCellCrudState {
207
    public grid: GridType;
208
    public cell: IgxCell | null = null;
37✔
209
    public row: IgxEditRow | null = null;
37✔
210
    public isInCompositionMode = false;
37✔
211

212
    public createCell(cell): IgxCell {
UNCOV
213
        return this.cell = new IgxCell(cell.cellID || cell.id, cell.row.index, cell.column, cell.value, cell.value,
×
214
            cell.row.data, cell.grid);
215
    }
216

217
    public createRow(cell: IgxCell): IgxEditRow {
UNCOV
218
        return this.row = new IgxEditRow(cell.id.rowID, cell.rowIndex, cell.rowData, cell.grid);
×
219
    }
220

221
    public sameRow(rowID): boolean {
UNCOV
222
        return this.row && this.row.id === rowID;
×
223
    }
224

225
    public sameCell(cell: IgxCell): boolean {
226
        return (this.cell.id.rowID === cell.id.rowID &&
×
227
            this.cell.id.columnID === cell.id.columnID);
228
    }
229

230
    public get cellInEditMode(): boolean {
UNCOV
231
        return !!this.cell;
×
232
    }
233

234
    public beginCellEdit(event?: Event) {
UNCOV
235
        const args = this.cell.createCellEditEventArgs(false, event);
×
UNCOV
236
        this.grid.cellEditEnter.emit(args);
×
237

UNCOV
238
        if (args.cancel) {
×
UNCOV
239
            this.endCellEdit();
×
240
        }
241

242
    }
243

244
    public cellEdit(event?: Event) {
UNCOV
245
        const args = this.cell.createCellEditEventArgs(true, event);
×
UNCOV
246
        this.grid.cellEdit.emit(args);
×
UNCOV
247
        return args;
×
248
    }
249

250
    public updateCell(exit: boolean, event?: Event): IGridEditEventArgs {
UNCOV
251
        if (!this.cell) {
×
UNCOV
252
            return;
×
253
        }
254
        // this is needed when we are not using ngModel to update the editValue
255
        // so that the change event of the inlineEditorTemplate is hit before
256
        // trying to update any cell
UNCOV
257
        const cellNode = this.grid.gridAPI.get_cell_by_index(this.cell.id.rowIndex, this.cell.column.field)?.nativeElement;
×
258
        let activeElement;
UNCOV
259
        if (cellNode) {
×
UNCOV
260
            const document = cellNode.getRootNode() as Document | ShadowRoot;
×
UNCOV
261
            if (cellNode.contains(document.activeElement)) {
×
UNCOV
262
                activeElement = document.activeElement as HTMLElement;
×
UNCOV
263
                this.grid.tbody.nativeElement.focus();
×
264
            }
265
        }
266

UNCOV
267
        const formControl = this.grid.validation.getFormControl(this.cell.id.rowID, this.cell.column.field);
×
UNCOV
268
        if (this.grid.validationTrigger === 'blur' && this.cell.pendingValue !== undefined) {
×
269
            // in case trigger is blur, update value if there's a pending one and mark as touched.
UNCOV
270
            formControl.setValue(this.cell.pendingValue);
×
UNCOV
271
            formControl.markAsTouched();
×
272
        }
273

UNCOV
274
        if (this.grid.validationTrigger === 'blur') {
×
UNCOV
275
            this.grid.tbody.nativeElement.focus({ preventScroll: true });
×
276
        }
277

278
        let doneArgs;
UNCOV
279
        if (this.cell.column.dataType === 'date' && !isDate(this.cell.value)) {
×
280
            if (isEqual(DateTimeUtil.parseIsoDate(this.cell.value), this.cell.editValue)) {
×
281
                doneArgs = this.exitCellEdit(event);
×
282
                return doneArgs;
×
283
            }
284
        }
UNCOV
285
        if (isEqual(this.cell.value, this.cell.editValue)) {
×
UNCOV
286
            doneArgs = this.exitCellEdit(event);
×
UNCOV
287
            return doneArgs;
×
288
        }
289

UNCOV
290
        const args = this.cellEdit(event);
×
UNCOV
291
        if (args.cancel) {
×
292
            // the focus is needed when we cancel the cellEdit so that the activeElement stays on the editor template
UNCOV
293
            activeElement?.focus();
×
UNCOV
294
            return args;
×
295
        }
296

UNCOV
297
        this.grid.gridAPI.update_cell(this.cell);
×
298

UNCOV
299
        doneArgs = this.cellEditDone(event, false);
×
UNCOV
300
        if (exit) {
×
UNCOV
301
            doneArgs = this.exitCellEdit(event);
×
302
        }
303

UNCOV
304
        return { ...args, ...doneArgs };
×
305
    }
306

307
    public cellEditDone(event, addRow: boolean): IGridEditDoneEventArgs {
UNCOV
308
        const newValue = this.cell.castToNumber(this.cell.editValue);
×
UNCOV
309
        const doneArgs = this.cell.createCellEditDoneEventArgs(newValue, event);
×
UNCOV
310
        this.grid.cellEditDone.emit(doneArgs);
×
UNCOV
311
        if (addRow) {
×
312
            doneArgs.rowData = this.row.data;
×
313
        }
UNCOV
314
        return doneArgs;
×
315
    }
316

317
    /** Exit cell edit mode */
318
    public exitCellEdit(event?: Event): IGridEditDoneEventArgs {
UNCOV
319
        if (!this.cell) {
×
UNCOV
320
            return;
×
321
        }
UNCOV
322
        const newValue = this.cell.castToNumber(this.cell.editValue);
×
UNCOV
323
        const args = this.cell?.createCellEditDoneEventArgs(newValue, event);
×
324

UNCOV
325
        this.cell.value = newValue;
×
UNCOV
326
        this.grid.cellEditExit.emit(args);
×
UNCOV
327
        this.endCellEdit();
×
UNCOV
328
        return args;
×
329
    }
330

331

332
    /** Clears cell editing state */
333
    public endCellEdit() {
UNCOV
334
        this.cell = null;
×
335
    }
336

337
    /** Returns whether the targeted cell is in edit mode */
338
    public targetInEdit(rowIndex: number, columnIndex: number): boolean {
UNCOV
339
        if (!this.cell) {
×
UNCOV
340
            return false;
×
341
        }
UNCOV
342
        const res = this.cell.column.index === columnIndex && this.cell.rowIndex === rowIndex;
×
UNCOV
343
        return res;
×
344
    }
345
}
346
export class IgxRowCrudState extends IgxCellCrudState {
347
    public closeRowEditingOverlay = new Subject();
37✔
348

349
    private _rowEditingBlocked = false;
37✔
350
    private _rowEditingStarted = false;
37✔
351

352
    public get primaryKey(): any {
UNCOV
353
        return this.grid.primaryKey;
×
354
    }
355

356
    public get rowInEditMode(): RowType {
UNCOV
357
        const editRowState = this.row;
×
UNCOV
358
        return editRowState !== null ? this.grid.rowList.find(e => e.key === editRowState.id) : null;
×
359
    }
360

361
    public get rowEditing(): boolean {
UNCOV
362
        return this.grid.rowEditable;
×
363
    }
364

365
    public get nonEditable(): boolean {
UNCOV
366
        return this.grid.rowEditable && (this.grid.primaryKey === undefined || this.grid.primaryKey === null);
×
367
    }
368

369
    public get rowEditingBlocked() {
UNCOV
370
        return this._rowEditingBlocked;
×
371
    }
372

373
    public set rowEditingBlocked(val: boolean) {
UNCOV
374
        this._rowEditingBlocked = val;
×
375
    }
376

377
    /** Enters row edit mode */
378
    public beginRowEdit(event?: Event) {
UNCOV
379
        if (!this.row || !(this.row.getClassName() === IgxEditRow.name)) {
×
UNCOV
380
            if (!this.row) {
×
UNCOV
381
                this.createRow(this.cell);
×
382
            }
383

UNCOV
384
            if (!this._rowEditingStarted) {
×
UNCOV
385
                const rowArgs = this.row.createRowEditEventArgs(false, event);
×
386

UNCOV
387
                this.grid.rowEditEnter.emit(rowArgs);
×
UNCOV
388
                if (rowArgs.cancel) {
×
UNCOV
389
                    this.endEditMode();
×
UNCOV
390
                    return true;
×
391
                }
392

UNCOV
393
                this._rowEditingStarted = true;
×
394
            }
395

UNCOV
396
            this.row.transactionState = this.grid.transactions.getAggregatedValue(this.row.id, true);
×
UNCOV
397
            this.grid.transactions.startPending();
×
UNCOV
398
            this.grid.openRowOverlay(this.row.id);
×
399
        }
400
    }
401

402
    public rowEdit(event: Event): IGridEditEventArgs {
UNCOV
403
        const args = this.row.createRowEditEventArgs(true, event);
×
UNCOV
404
        this.grid.rowEdit.emit(args);
×
UNCOV
405
        return args;
×
406
    }
407

408
    public updateRow(commit: boolean, event?: Event): IGridEditEventArgs {
UNCOV
409
        if (!this.grid.rowEditable ||
×
410
            this.grid.rowEditingOverlay &&
411
            this.grid.rowEditingOverlay.collapsed || !this.row) {
UNCOV
412
            return {} as IGridEditEventArgs;
×
413
        }
414

415
        let args;
UNCOV
416
        if (commit) {
×
UNCOV
417
            this.row.newData = this.grid.transactions.getAggregatedValue(this.row.id, true);
×
UNCOV
418
            this.updateRowEditData(this.row, this.row.newData);
×
UNCOV
419
            args = this.rowEdit(event);
×
UNCOV
420
            if (args.cancel) {
×
UNCOV
421
                return args;
×
422
            }
423
        }
424

UNCOV
425
        args = this.endRowTransaction(commit, event);
×
426

UNCOV
427
        return args;
×
428
    }
429

430
    /**
431
     * @hidden @internal
432
     */
433
    public endRowTransaction(commit: boolean, event?: Event): IGridEditEventArgs | IRowDataCancelableEventArgs {
UNCOV
434
        this.row.newData = this.grid.transactions.getAggregatedValue(this.row.id, true);
×
UNCOV
435
        let rowEditArgs = this.row.createRowEditEventArgs(true, event);
×
436

437
        let nonCancelableArgs;
UNCOV
438
        if (!commit) {
×
UNCOV
439
            this.grid.transactions.endPending(false);
×
UNCOV
440
            const isAddRow = this.row && this.row.getClassName() === IgxAddRow.name;
×
UNCOV
441
            const id = this.row ? this.row.id : this.cell.id.rowID;
×
UNCOV
442
            if (isAddRow) {
×
UNCOV
443
                this.grid.validation.clear(id);
×
444
            } else {
UNCOV
445
                this.grid.validation.update(id, rowEditArgs.oldValue);
×
446
            }
UNCOV
447
        } else if (this.row.getClassName() === IgxEditRow.name) {
×
UNCOV
448
            rowEditArgs = this.grid.gridAPI.update_row(this.row, this.row.newData, event);
×
UNCOV
449
            nonCancelableArgs = this.rowEditDone(rowEditArgs.oldValue, event);
×
450
        } else {
UNCOV
451
            const rowAddArgs = this.row.createRowDataEventArgs(event);
×
UNCOV
452
            this.grid.rowAdd.emit(rowAddArgs);
×
UNCOV
453
            if (rowAddArgs.cancel) {
×
454
                return rowAddArgs;
×
455
            }
456

UNCOV
457
            this.grid.transactions.endPending(false);
×
458

UNCOV
459
            const parentId = this.getParentRowId();
×
UNCOV
460
            this.grid.gridAPI.addRowToData(this.row.newData ?? this.row.data, parentId);
×
UNCOV
461
            this.grid.triggerPipes();
×
462

UNCOV
463
            nonCancelableArgs = this.rowEditDone(null, event);
×
464
        }
465

UNCOV
466
        nonCancelableArgs = this.exitRowEdit(rowEditArgs.oldValue, event);
×
467

UNCOV
468
        return { ...nonCancelableArgs, ...rowEditArgs };
×
469
    }
470

471
    public rowEditDone(cachedRowData, event: Event) {
UNCOV
472
        const doneArgs = this.row.createRowEditDoneEventArgs(cachedRowData, event);
×
UNCOV
473
        this.grid.rowEditDone.emit(doneArgs);
×
UNCOV
474
        return doneArgs;
×
475
    }
476

477

478
    /** Exit row edit mode */
479
    public exitRowEdit(cachedRowData, event?: Event): IGridEditDoneEventArgs {
UNCOV
480
        const nonCancelableArgs = this.row.createRowEditDoneEventArgs(cachedRowData, event);
×
UNCOV
481
        this.grid.rowEditExit.emit(nonCancelableArgs);
×
UNCOV
482
        this.grid.closeRowEditingOverlay();
×
483

UNCOV
484
        this.endRowEdit();
×
UNCOV
485
        return nonCancelableArgs;
×
486
    }
487

488
    /** Clears row editing state */
489
    public endRowEdit() {
UNCOV
490
        this.row = null;
×
UNCOV
491
        this.rowEditingBlocked = false;
×
UNCOV
492
        this._rowEditingStarted = false;
×
493
    }
494

495
    /** Clears cell and row editing state and closes row editing template if it is open */
496
    public endEditMode() {
UNCOV
497
        this.endCellEdit();
×
UNCOV
498
        if (this.grid.rowEditable) {
×
UNCOV
499
            this.endRowEdit();
×
UNCOV
500
            this.grid.closeRowEditingOverlay();
×
501
        }
502
    }
503

504
    public updateRowEditData(row: IgxEditRow, value?: any) {
UNCOV
505
        const grid = this.grid;
×
506

UNCOV
507
        const rowInEditMode = grid.gridAPI.crudService.row;
×
UNCOV
508
        row.newData = value ?? rowInEditMode.transactionState;
×
509

510

UNCOV
511
        if (rowInEditMode && row.id === rowInEditMode.id) {
×
512
            // do not use spread operator here as it will copy everything over an empty object with no descriptors
UNCOV
513
            row.data = Object.assign(copyDescriptors(row.data), row.data, rowInEditMode.transactionState);
×
514
            // TODO: Workaround for updating a row in edit mode through the API
UNCOV
515
        } else if (this.grid.transactions.enabled) {
×
UNCOV
516
            const state = grid.transactions.getState(row.id);
×
UNCOV
517
            row.data = state ? Object.assign({}, row.data, state.value) : row.data;
×
518
        }
519
    }
520

521
    protected getParentRowId() {
522
        return null;
×
523
    }
524
}
525

526
export class IgxRowAddCrudState extends IgxRowCrudState {
527
    public addRowParent: IgxAddRowParent = null;
37✔
528

529
    /**
530
     * @hidden @internal
531
     */
532
    public createAddRow(parentRow: RowType, asChild?: boolean) {
UNCOV
533
        this.createAddRowParent(parentRow, asChild);
×
534

UNCOV
535
        const newRec = this.grid.getEmptyRecordObjectFor(parentRow);
×
UNCOV
536
        const addRowIndex = this.addRowParent.index + 1;
×
UNCOV
537
        return this.row = new IgxAddRow(newRec.rowID, addRowIndex, newRec.data, newRec.recordRef, this.grid);
×
538
    }
539

540
    /**
541
     * @hidden @internal
542
     */
543
    public createAddRowParent(row: RowType, newRowAsChild?: boolean) {
UNCOV
544
        const rowIndex = row ? row.index : -1;
×
UNCOV
545
        const rowId = row ? row.key : (rowIndex >= 0 ? this.grid.rowList.last.key : null);
×
546

UNCOV
547
        const isInPinnedArea = this.grid.isRecordPinnedByViewIndex(rowIndex);
×
UNCOV
548
        const pinIndex = this.grid.pinnedRecords.findIndex(x => x[this.primaryKey] === rowId);
×
UNCOV
549
        const unpinIndex = this.grid.getUnpinnedIndexById(rowId);
×
UNCOV
550
        this.addRowParent = {
×
551
            rowID: rowId,
552
            rowKey: rowId,
553
            index: isInPinnedArea ? pinIndex : unpinIndex,
×
554
            asChild: newRowAsChild,
555
            isPinned: isInPinnedArea
556
        };
557
    }
558

559
    /**
560
     * @hidden @internal
561
     */
562
    public override endRowTransaction(commit: boolean, event?: Event): IGridEditEventArgs | IRowDataCancelableEventArgs {
UNCOV
563
        const isAddRow = this.row && this.row.getClassName() === IgxAddRow.name;
×
UNCOV
564
        if (isAddRow) {
×
UNCOV
565
            this.grid.rowAdded.pipe(first()).subscribe((addRowArgs: IRowDataEventArgs) => {
×
UNCOV
566
                const rowData = addRowArgs.data;
×
UNCOV
567
                const pinnedIndex = this.grid.pinnedRecords.findIndex(x => x[this.primaryKey] === rowData[this.primaryKey]);
×
568
                // A check whether the row is in the current view
UNCOV
569
                const viewIndex = pinnedIndex !== -1 ? pinnedIndex : this._findRecordIndexInView(rowData);
×
UNCOV
570
                const dataIndex = this.grid.filteredSortedData.findIndex(data => data[this.primaryKey] === rowData[this.primaryKey]);
×
UNCOV
571
                const isInView = viewIndex !== -1 && !this.grid.navigation.shouldPerformVerticalScroll(viewIndex, 0);
×
UNCOV
572
                const showIndex = isInView ? -1 : dataIndex;
×
UNCOV
573
                this.grid.showSnackbarFor(showIndex);
×
574
            });
575
        }
576

UNCOV
577
        const args = super.endRowTransaction(commit, event);
×
UNCOV
578
        if (args.cancel) {
×
579
            return args;
×
580
        }
581

UNCOV
582
        if (isAddRow) {
×
UNCOV
583
            this.endAddRow();
×
UNCOV
584
            if (commit) {
×
UNCOV
585
                const rowAddedEventArgs: IRowDataEventArgs = {
×
586
                    data: args.rowData,
587
                    rowData: args.rowData,
588
                    owner: this.grid,
589
                    primaryKey: args.rowData[this.grid.primaryKey],
590
                    rowKey: args.rowData[this.grid.primaryKey],
591
                }
UNCOV
592
                this.grid.rowAddedNotifier.next(rowAddedEventArgs);
×
UNCOV
593
                this.grid.rowAdded.emit(rowAddedEventArgs);
×
594
            }
595
        }
596

UNCOV
597
        return args;
×
598
    }
599

600
    /**
601
     * @hidden @internal
602
     */
603
    public endAddRow() {
UNCOV
604
        this.addRowParent = null;
×
UNCOV
605
        this.grid.triggerPipes();
×
606
    }
607

608
    /**
609
     * @hidden
610
     * @internal
611
     * TODO: consider changing modifier
612
     */
613
    public _findRecordIndexInView(rec) {
UNCOV
614
        return this.grid.dataView.findIndex(data => data[this.primaryKey] === rec[this.primaryKey]);
×
615
    }
616

617
    protected override getParentRowId() {
UNCOV
618
        if (this.addRowParent.asChild) {
×
UNCOV
619
            return this.addRowParent.asChild ? this.addRowParent.rowID : undefined;
×
UNCOV
620
        } else if (this.addRowParent.rowID !== null && this.addRowParent.rowID !== undefined) {
×
UNCOV
621
            const spawnedForRecord = this.grid.gridAPI.get_rec_by_id(this.addRowParent.rowID);
×
UNCOV
622
            return spawnedForRecord?.parent?.rowID;
×
623
        }
624
    }
625
}
626

627
@Injectable()
628
export class IgxGridCRUDService extends IgxRowAddCrudState {
2✔
629

630
    public enterEditMode(cell, event?: Event) {
UNCOV
631
        if (this.isInCompositionMode) {
×
632
            return;
×
633
        }
634

UNCOV
635
        if (this.nonEditable) {
×
UNCOV
636
            console.warn('The grid must have a `primaryKey` specified when using `rowEditable`!');
×
UNCOV
637
            return;
×
638
        }
639

UNCOV
640
        if (this.cellInEditMode) {
×
641
            // TODO: case solely for f2/enter nav that uses enterEditMode as toggle. Refactor.
UNCOV
642
            const canceled = this.endEdit(true, event);
×
643

UNCOV
644
            if (!canceled || !this.cell) {
×
UNCOV
645
                this.grid.tbody.nativeElement.focus();
×
646
            }
647
        } else {
UNCOV
648
            if (this.rowEditing) {
×
649
                // TODO rowData
UNCOV
650
                if (this.row && !this.sameRow(cell?.cellID?.rowID)) {
×
UNCOV
651
                    this.rowEditingBlocked = this.endEdit(true, event);
×
UNCOV
652
                    if (this.rowEditingBlocked) {
×
653
                        return true;
×
654
                    }
655

UNCOV
656
                    this.rowEditingBlocked = false;
×
UNCOV
657
                    this.endRowEdit();
×
658
                }
UNCOV
659
                this.createCell(cell);
×
660

UNCOV
661
                const canceled = this.beginRowEdit(event);
×
UNCOV
662
                if (!canceled) {
×
UNCOV
663
                    this.beginCellEdit(event);
×
664
                }
665

666
            } else {
UNCOV
667
                this.createCell(cell);
×
UNCOV
668
                this.beginCellEdit(event);
×
669
            }
670
        }
671
    }
672

673
    /**
674
     * Enters add row mode by creating temporary dummy so the user can fill in new row cells.
675
     *
676
     * @param parentRow Parent row after which the Add Row UI will be rendered.
677
     *                  If `null` will show it at the bottom after all rows (or top if there are not rows).
678
     * @param asChild Specifies if the new row should be added as a child to a tree row.
679
     * @param event Base event that triggered the add row mode.
680
     */
681
    public enterAddRowMode(parentRow: RowType, asChild?: boolean, event?: Event) {
UNCOV
682
        if (!this.rowEditing && (this.grid.primaryKey === undefined || this.grid.primaryKey === null)) {
×
683
            console.warn('The grid must use row edit mode to perform row adding! Please set rowEditable to true.');
×
684
            return;
×
685
        }
UNCOV
686
        this.endEdit(true, event);
×
687
        // work with copy of original row, since context may change on collapse.
UNCOV
688
        const parentRowCopy = parentRow ? Object.assign(copyDescriptors(parentRow), parentRow) : null;
×
UNCOV
689
        if (parentRowCopy != null && this.grid.expansionStates.get(parentRowCopy.key)) {
×
UNCOV
690
            this.grid.collapseRow(parentRowCopy.key);
×
691
        }
692

UNCOV
693
        this.createAddRow(parentRowCopy, asChild);
×
694

UNCOV
695
        this.grid.transactions.startPending();
×
UNCOV
696
        if (this.addRowParent.isPinned) {
×
697
            // If parent is pinned, add the new row to pinned records
UNCOV
698
            (this.grid as any)._pinnedRecordIDs.splice(this.row.index, 0, this.row.id);
×
699
        }
700

UNCOV
701
        this.grid.triggerPipes();
×
UNCOV
702
        this.grid.notifyChanges(true);
×
703

UNCOV
704
        this.grid.navigateTo(this.row.index, -1);
×
705
        // when selecting the dummy row we need to adjust for top pinned rows
UNCOV
706
        const indexAdjust = this.grid.isRowPinningToTop ?
×
707
            (!this.addRowParent.isPinned ? this.grid.pinnedRows.length : 0) :
×
708
            (!this.addRowParent.isPinned ? 0 : this.grid.unpinnedRecords.length);
×
709

710
        // TODO: Type this without shoving a bunch of internal properties in the row type
UNCOV
711
        const dummyRow = this.grid.gridAPI.get_row_by_index(this.row.index + indexAdjust) as any;
×
UNCOV
712
        dummyRow.triggerAddAnimation();
×
UNCOV
713
        dummyRow.cdr.detectChanges();
×
UNCOV
714
        dummyRow.addAnimationEnd.pipe(first()).subscribe(() => {
×
UNCOV
715
            const cell = dummyRow.cells.find(c => c.editable);
×
UNCOV
716
            if (cell) {
×
UNCOV
717
                this.grid.gridAPI.update_cell(this.cell);
×
UNCOV
718
                this.enterEditMode(cell, event);
×
UNCOV
719
                cell.activate();
×
720
            }
721
        });
722
    }
723

724
    /**
725
     * Finishes the row transactions on the current row and returns whether the grid editing was canceled.
726
     *
727
     * @remarks
728
     * If `commit === true`, passes them from the pending state to the data (or transaction service)
729
     * @example
730
     * ```html
731
     * <button type="button" igxButton (click)="grid.endEdit(true)">Commit Row</button>
732
     * ```
733
     * @param commit
734
     */
735
    // TODO: Implement the same representation of the method without evt emission.
736
    public endEdit(commit = true, event?: Event): boolean {
×
737
        if (!this.row && !this.cell) {
26✔
738
            return;
26✔
739
        }
740

741
        let args;
UNCOV
742
        if (commit) {
×
UNCOV
743
            args = this.updateCell(true, event);
×
UNCOV
744
            if (args && args.cancel) {
×
UNCOV
745
                return args.cancel;
×
746
            }
747
        } else {
748
            // needede because this.cell is null after exitCellEdit
749
            // thus the next if is always false
UNCOV
750
            const cell = this.cell;
×
UNCOV
751
            this.exitCellEdit(event);
×
UNCOV
752
            if (!this.grid.rowEditable && cell) {
×
UNCOV
753
                const value = this.grid.transactions.getAggregatedValue(cell.id.rowID, true) || cell.rowData;
×
UNCOV
754
                this.grid.validation.update(cell.id.rowID, value);
×
755
            }
756
        }
757

UNCOV
758
        args = this.updateRow(commit, event);
×
UNCOV
759
        this.rowEditingBlocked = args.cancel;
×
UNCOV
760
        if (args.cancel) {
×
UNCOV
761
            return true;
×
762
        }
763

UNCOV
764
        const activeCell = this.grid.selectionService.activeElement;
×
UNCOV
765
        if (event && activeCell) {
×
UNCOV
766
            const rowIndex = activeCell.row;
×
UNCOV
767
            const visibleColIndex = activeCell.layout ? activeCell.layout.columnVisibleIndex : activeCell.column;
×
UNCOV
768
            this.grid.navigateTo(rowIndex, visibleColIndex);
×
769
        }
770

UNCOV
771
        return false;
×
772
    }
773
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc