• 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

37.72
/projects/igniteui-angular/src/lib/grids/cell.component.ts
1
import {
2
    ChangeDetectionStrategy,
3
    ChangeDetectorRef,
4
    Component,
5
    ElementRef,
6
    HostBinding,
7
    HostListener,
8
    Input,
9
    TemplateRef,
10
    ViewChild,
11
    NgZone,
12
    OnInit,
13
    OnDestroy,
14
    OnChanges,
15
    SimpleChanges,
16
    Inject,
17
    ViewChildren,
18
    QueryList,
19
    AfterViewInit,
20
    booleanAttribute
21
} from '@angular/core';
22
import { formatPercent, NgIf, NgClass, NgTemplateOutlet, DecimalPipe, PercentPipe, CurrencyPipe, DatePipe, getLocaleCurrencyCode, getCurrencySymbol } from '@angular/common';
23
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
24

25
import { first, takeUntil, takeWhile } from 'rxjs/operators';
26
import { Subject } from 'rxjs';
27

28
import { IgxTextHighlightDirective } from '../directives/text-highlight/text-highlight.directive';
29
import { formatCurrency, formatDate, PlatformUtil } from '../core/utils';
30
import { IgxGridSelectionService } from './selection/selection.service';
31
import { HammerGesturesManager } from '../core/touch';
32
import { GridSelectionMode } from './common/enums';
33
import { CellType, ColumnType, GridType, IgxCellTemplateContext, IGX_GRID_BASE, RowType } from './common/grid.interface';
34
import { GridColumnDataType } from '../data-operations/data-util';
35
import { IgxRowDirective } from './row.directive';
36
import { ISearchInfo } from './common/events';
37
import { IgxGridCell } from './grid-public-cell';
38
import { ISelectionNode } from './common/types';
39
import { AutoPositionStrategy, HorizontalAlignment, IgxOverlayService } from '../services/public_api';
40
import { IgxIconComponent } from '../icon/icon.component';
41
import { IgxGridCellImageAltPipe, IgxStringReplacePipe, IgxColumnFormatterPipe } from './common/pipes';
42
import { IgxTooltipDirective } from '../directives/tooltip/tooltip.directive';
43
import { IgxTooltipTargetDirective } from '../directives/tooltip/tooltip-target.directive';
44
import { IgxSuffixDirective } from '../directives/suffix/suffix.directive';
45
import { IgxPrefixDirective } from '../directives/prefix/prefix.directive';
46
import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/date-time-editor.directive';
47
import { IgxTimePickerComponent } from '../time-picker/time-picker.component';
48
import { IgxDatePickerComponent } from '../date-picker/date-picker.component';
49
import { IgxCheckboxComponent } from '../checkbox/checkbox.component';
50
import { IgxTextSelectionDirective } from '../directives/text-selection/text-selection.directive';
51
import { IgxFocusDirective } from '../directives/focus/focus.directive';
52
import { IgxInputDirective } from '../directives/input/input.directive';
53
import { IgxInputGroupComponent } from '../input-group/input-group.component';
54
import { IgxChipComponent } from '../chips/chip.component';
55

56
/**
57
 * Providing reference to `IgxGridCellComponent`:
58
 * ```typescript
59
 * @ViewChild('grid', { read: IgxGridComponent })
60
 *  public grid: IgxGridComponent;
61
 * ```
62
 * ```typescript
63
 *  let column = this.grid.columnList.first;
64
 * ```
65
 * ```typescript
66
 *  let cell = column.cells[0];
67
 * ```
68
 */
69
@Component({
70
    changeDetection: ChangeDetectionStrategy.OnPush,
71
    selector: 'igx-grid-cell',
72
    templateUrl: './cell.component.html',
73
    providers: [HammerGesturesManager],
74
    imports: [
75
        NgIf,
76
        NgClass,
77
        NgTemplateOutlet,
78
        DecimalPipe,
79
        PercentPipe,
80
        CurrencyPipe,
81
        DatePipe,
82
        ReactiveFormsModule,
83
        IgxChipComponent,
84
        IgxTextHighlightDirective,
85
        IgxIconComponent,
86
        IgxInputGroupComponent,
87
        IgxInputDirective,
88
        IgxFocusDirective,
89
        IgxTextSelectionDirective,
90
        IgxCheckboxComponent,
91
        IgxDatePickerComponent,
92
        IgxTimePickerComponent,
93
        IgxDateTimeEditorDirective,
94
        IgxPrefixDirective,
95
        IgxSuffixDirective,
96
        IgxTooltipTargetDirective,
97
        IgxTooltipDirective,
98
        IgxGridCellImageAltPipe,
99
        IgxStringReplacePipe,
100
        IgxColumnFormatterPipe
101
    ]
102
})
103
export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellType, AfterViewInit {
2✔
104
    private _destroy$ = new Subject<void>();
1,763✔
105
    /**
106
     * @hidden
107
     * @internal
108
     */
109
    @HostBinding('class.igx-grid__td--new')
110
    public get isEmptyAddRowCell() {
111
        return this.intRow.addRowUI && (this.value === undefined || this.value === null);
9,938!
112
    }
113

114
    /**
115
     * @hidden
116
     * @internal
117
     */
118
    @ViewChildren('error', { read: IgxTooltipDirective })
119
    public errorTooltip: QueryList<IgxTooltipDirective>;
120

121
    /**
122
     * @hidden
123
     * @internal
124
     */
125
    @ViewChild('errorIcon', { read: IgxIconComponent, static: false })
126
    public errorIcon: IgxIconComponent;
127

128
    /**
129
     * Gets the default error template.
130
     * @hidden @internal
131
     */
132
    @ViewChild('defaultError', { read: TemplateRef, static: true })
133
    public defaultErrorTemplate: TemplateRef<any>;
134

135
    /**
136
     * Gets the column of the cell.
137
     * ```typescript
138
     *  let cellColumn = this.cell.column;
139
     * ```
140
     *
141
     * @memberof IgxGridCellComponent
142
     */
143
    @Input()
144
    public column: ColumnType;
145

146

147
    /**
148
     * @hidden
149
     * @internal
150
     */
151
    protected get formGroup(): FormGroup {
152
        return this.grid.validation.getFormGroup(this.intRow.key);
46,022✔
153
    }
154

155
    /**
156
     * @hidden
157
     * @internal
158
     */
159
    @Input()
160
    public intRow: IgxRowDirective;
161

162
    /**
163
     * Gets the row of the cell.
164
     * ```typescript
165
     * let cellRow = this.cell.row;
166
     * ```
167
     *
168
     * @memberof IgxGridCellComponent
169
     */
170
    @Input()
171
    public get row(): RowType {
UNCOV
172
        return this.grid.createRow(this.intRow.index);
×
173
    }
174

175
    /**
176
     * Gets the data of the row of the cell.
177
     * ```typescript
178
     * let rowData = this.cell.rowData;
179
     * ```
180
     *
181
     * @memberof IgxGridCellComponent
182
     */
183
    @Input()
184
    public rowData: any;
185

186
    /**
187
     * @hidden
188
     * @internal
189
     */
190
    @Input()
191
    public columnData: any;
192

193
    /**
194
     * Sets/gets the template of the cell.
195
     * ```html
196
     * <ng-template #cellTemplate igxCell let-value>
197
     *   <div style="font-style: oblique; color:blueviolet; background:red">
198
     *       <span>{{value}}</span>
199
     *   </div>
200
     * </ng-template>
201
     * ```
202
     * ```typescript
203
     * @ViewChild('cellTemplate',{read: TemplateRef})
204
     * cellTemplate: TemplateRef<any>;
205
     * ```
206
     * ```typescript
207
     * this.cell.cellTemplate = this.cellTemplate;
208
     * ```
209
     * ```typescript
210
     * let template =  this.cell.cellTemplate;
211
     * ```
212
     *
213
     * @memberof IgxGridCellComponent
214
     */
215
    @Input()
216
    public cellTemplate: TemplateRef<any>;
217

218
    @Input()
219
    public cellValidationErrorTemplate: TemplateRef<any>;
220

221
    @Input()
222
    public pinnedIndicator: TemplateRef<any>;
223

224
    /**
225
     * Sets/gets the cell value.
226
     * ```typescript
227
     * this.cell.value = "Cell Value";
228
     * ```
229
     * ```typescript
230
     * let cellValue = this.cell.value;
231
     * ```
232
     *
233
     * @memberof IgxGridCellComponent
234
     */
235
    @Input()
236
    public value: any;
237

238
    /**
239
     * Gets the cell formatter.
240
     * ```typescript
241
     * let cellForamatter = this.cell.formatter;
242
     * ```
243
     *
244
     * @memberof IgxGridCellComponent
245
     */
246
    @Input()
247
    public formatter: (value: any, rowData?: any, columnData?: any) => any;
248

249
    /**
250
     * Gets the cell template context object.
251
     * ```typescript
252
     *  let context = this.cell.context();
253
     * ```
254
     *
255
     * @memberof IgxGridCellComponent
256
     */
257
    public get context(): IgxCellTemplateContext {
258
        const getCellType = () => this.getCellType(true);
4,180✔
259
        const ctx: IgxCellTemplateContext = {
4,180✔
260
            $implicit: this.value,
261
            additionalTemplateContext: this.column.additionalTemplateContext,
262
            get cell() {
263
                /* Turns the `cell` property from the template context object into lazy-evaluated one.
264
                 * Otherwise on each detection cycle the cell template is recreating N cell instances where
265
                 * N = number of visible cells in the grid, leading to massive performance degradation in large grids.
266
                 */
UNCOV
267
                return getCellType();
×
268
            }
269
        };
270
        if (this.editMode) {
4,180!
UNCOV
271
            ctx.formControl = this.formControl;
×
272
        }
273
        if (this.isInvalid) {
4,180!
UNCOV
274
            ctx.defaultErrorTemplate = this.defaultErrorTemplate;
×
275
        }
276
        return ctx;
4,180✔
277
    }
278

279
    /**
280
     * Gets the cell template.
281
     * ```typescript
282
     * let template = this.cell.template;
283
     * ```
284
     *
285
     * @memberof IgxGridCellComponent
286
     */
287
    public get template(): TemplateRef<any> {
288
        if (this.editMode && this.formGroup) {
2,090!
UNCOV
289
            const inlineEditorTemplate = this.column.inlineEditorTemplate;
×
UNCOV
290
            return inlineEditorTemplate ? inlineEditorTemplate : this.inlineEditorTemplate;
×
291
        }
292
        if (this.cellTemplate) {
2,090!
UNCOV
293
            return this.cellTemplate;
×
294
        }
295
        if (this.grid.rowEditable && this.intRow.addRowUI) {
2,090!
UNCOV
296
            return this.addRowCellTemplate;
×
297
        }
298
        return this.defaultCellTemplate;
2,090✔
299
    }
300

301
    /**
302
     * Gets the pinned indicator template.
303
     * ```typescript
304
     * let template = this.cell.pinnedIndicatorTemplate;
305
     * ```
306
     *
307
     * @memberof IgxGridCellComponent
308
     */
309
    public get pinnedIndicatorTemplate() {
310
        if (this.pinnedIndicator) {
2,090!
311
            return this.pinnedIndicator;
×
312
        }
313
        return this.defaultPinnedIndicator;
2,090✔
314
    }
315

316
    /**
317
     * Gets the `id` of the grid in which the cell is stored.
318
     * ```typescript
319
     * let gridId = this.cell.gridID;
320
     * ```
321
     *
322
     * @memberof IgxGridCellComponent
323
     */
324
    public get gridID(): any {
325
        return this.intRow.gridID;
11,725✔
326
    }
327

328

329
    /**
330
     * Gets the `index` of the row where the cell is stored.
331
     * ```typescript
332
     * let rowIndex = this.cell.rowIndex;
333
     * ```
334
     *
335
     * @memberof IgxGridCellComponent
336
     */
337
    @HostBinding('attr.data-rowIndex')
338
    public get rowIndex(): number {
339
        return this.intRow.index;
39,752✔
340
    }
341

342
    /**
343
     * Gets the `index` of the cell column.
344
     * ```typescript
345
     * let columnIndex = this.cell.columnIndex;
346
     * ```
347
     *
348
     * @memberof IgxGridCellComponent
349
     */
350
    public get columnIndex(): number {
UNCOV
351
        return this.column.index;
×
352
    }
353

354
    /**
355
     * Returns the column visible index.
356
     * ```typescript
357
     * let visibleColumnIndex = this.cell.visibleColumnIndex;
358
     * ```
359
     *
360
     * @memberof IgxGridCellComponent
361
     */
362
    @HostBinding('attr.data-visibleIndex')
363
    @Input()
364
    public get visibleColumnIndex() {
365
        return this.column.columnLayoutChild ? this.column.visibleIndex : this._vIndex;
39,752!
366
    }
367

368
    public set visibleColumnIndex(val) {
369
        this._vIndex = val;
1,763✔
370
    }
371

372
    /**
373
     * Gets the ID of the cell.
374
     * ```typescript
375
     * let cellID = this.cell.cellID;
376
     * ```
377
     *
378
     * @memberof IgxGridCellComponent
379
     */
380
    public get cellID() {
UNCOV
381
        const primaryKey = this.grid.primaryKey;
×
UNCOV
382
        const rowID = primaryKey ? this.rowData[primaryKey] : this.rowData;
×
UNCOV
383
        return { rowID, columnID: this.columnIndex, rowIndex: this.rowIndex };
×
384
    }
385

386
    @HostBinding('attr.id')
387
    public get attrCellID() {
388
        return `${this.intRow.gridID}_${this.rowIndex}_${this.visibleColumnIndex}`;
9,938✔
389
    }
390

391
    @HostBinding('attr.title')
392
    public get title() {
393
        if (this.editMode || this.cellTemplate || this.errorShowing) {
9,938!
UNCOV
394
            return '';
×
395
        }
396

397
        if (this.formatter) {
9,938✔
398
            return this.formatter(this.value, this.rowData, this.columnData);
20✔
399
        }
400

401
        const args = this.column.pipeArgs;
9,918✔
402
        const locale = this.grid.locale;
9,918✔
403

404
        switch (this.column.dataType) {
9,918!
405
            case GridColumnDataType.Percent:
UNCOV
406
                return formatPercent(this.value, locale, args.digitsInfo);
×
407
            case GridColumnDataType.Currency:
UNCOV
408
                return formatCurrency(this.value, this.currencyCode, args.display, args.digitsInfo, locale);
×
409
            case GridColumnDataType.Date:
410
            case GridColumnDataType.DateTime:
411
            case GridColumnDataType.Time:
412
                return formatDate(this.value, args.format, locale, args.timezone);
2,643✔
413
        }
414
        return this.value;
7,275✔
415
    }
416

417
    @HostBinding('class.igx-grid__td--bool-true')
418
    public get booleanClass() {
419
        return this.column.dataType === 'boolean' && this.value;
9,938✔
420
    }
421

422
    /**
423
     * Returns a reference to the nativeElement of the cell.
424
     * ```typescript
425
     * let cellNativeElement = this.cell.nativeElement;
426
     * ```
427
     *
428
     * @memberof IgxGridCellComponent
429
     */
430
    public get nativeElement(): HTMLElement {
431
        return this.element.nativeElement;
14,302✔
432
    }
433

434
    /**
435
     * @hidden
436
     * @internal
437
     */
438
    @Input()
439
    public get cellSelectionMode() {
440
        return this._cellSelection;
3,526✔
441
    }
442

443
    public set cellSelectionMode(value) {
444
        if (this._cellSelection === value) {
1,763✔
445
            return;
1,763✔
446
        }
UNCOV
447
        this.zone.runOutsideAngular(() => {
×
UNCOV
448
            if (value === GridSelectionMode.multiple) {
×
UNCOV
449
                this.addPointerListeners(value);
×
450
            } else {
UNCOV
451
                this.removePointerListeners(this._cellSelection);
×
452
            }
453
        });
UNCOV
454
        this._cellSelection = value;
×
455
    }
456

457
    /**
458
     * @hidden
459
     * @internal
460
     */
461
    @Input()
462
    public set lastSearchInfo(value: ISearchInfo) {
463
        this._lastSearchInfo = value;
1,763✔
464
        this.highlightText(this._lastSearchInfo.searchText, this._lastSearchInfo.caseSensitive, this._lastSearchInfo.exactMatch);
1,763✔
465
    }
466

467
    /**
468
     * @hidden
469
     * @internal
470
     */
471
    @Input()
472
    @HostBinding('class.igx-grid__td--pinned-last')
473
    public lastPinned = false;
1,763✔
474

475
    /**
476
     * @hidden
477
     * @internal
478
     */
479
    @Input()
480
    @HostBinding('class.igx-grid__td--pinned-first')
481
    public firstPinned = false;
1,763✔
482

483
    /**
484
     * Returns whether the cell is in edit mode.
485
     */
486
    @Input({ transform: booleanAttribute })
487
    @HostBinding('class.igx-grid__td--editing')
488
    public editMode = false;
1,763✔
489

490
    /**
491
     * Sets/get the `role` property of the cell.
492
     * Default value is `"gridcell"`.
493
     * ```typescript
494
     * this.cell.role = 'grid-cell';
495
     * ```
496
     * ```typescript
497
     * let cellRole = this.cell.role;
498
     * ```
499
     *
500
     * @memberof IgxGridCellComponent
501
     */
502
    @HostBinding('attr.role')
503
    public role = 'gridcell';
1,763✔
504

505
    /**
506
     * Gets whether the cell is editable.
507
     * ```typescript
508
     * let isCellReadonly = this.cell.readonly;
509
     * ```
510
     *
511
     * @memberof IgxGridCellComponent
512
     */
513
    @HostBinding('attr.aria-readonly')
514
    public get readonly(): boolean {
515
        return !this.editable;
9,938✔
516
    }
517

518
    /** @hidden @internal */
519
    @HostBinding('attr.aria-describedby')
520
    public get ariaDescribeBy() {
521
        let describeBy = (this.gridID + '_' + this.column.field).replace('.', '_');
9,938✔
522
        if (this.isInvalid) {
9,938!
UNCOV
523
            describeBy += ' ' + this.ariaErrorMessage;
×
524
        }
525
        return describeBy;
9,938✔
526
    }
527

528
    /** @hidden @internal */
529
    public get ariaErrorMessage() {
UNCOV
530
        return this.grid.id + '_' + this.column.field + '_' + this.intRow.index + '_error';
×
531
    }
532

533
    /**
534
     * @hidden
535
     * @internal
536
     */
537
    @HostBinding('class.igx-grid__td--invalid')
538
    @HostBinding('attr.aria-invalid')
539
    public get isInvalid() {
540
        const isInvalid = this.formGroup?.get(this.column?.field)?.invalid && this.formGroup?.get(this.column?.field)?.touched;
36,084!
541
        return !this.intRow.deleted && isInvalid;
36,084✔
542
    }
543

544
    /**
545
     * @hidden
546
     * @internal
547
     */
548
    @HostBinding('class.igx-grid__td--valid')
549
    public get isValidAfterEdit() {
550
        const formControl = this.formGroup?.get(this.column?.field);
9,938✔
551
        return this.editMode && formControl && !formControl.invalid && formControl.dirty;
9,938!
552
    }
553

554
    /**
555
     * Gets the formControl responsible for value changes and validation for this cell.
556
     */
557
    protected get formControl(): FormControl {
UNCOV
558
        return this.grid.validation.getFormControl(this.intRow.key, this.column.field) as FormControl;
×
559
    }
560

561
    public get gridRowSpan(): number {
UNCOV
562
        return this.column.gridRowSpan;
×
563
    }
564

565
    public get gridColumnSpan(): number {
UNCOV
566
        return this.column.gridColumnSpan;
×
567
    }
568

569
    public get rowEnd(): number {
570
        return this.column.rowEnd;
×
571
    }
572

573
    public get colEnd(): number {
574
        return this.column.colEnd;
×
575
    }
576

577
    public get rowStart(): number {
578
        return this.column.rowStart;
×
579
    }
580

581
    public get colStart(): number {
582
        return this.column.colStart;
×
583
    }
584

585
    /**
586
     * Gets the width of the cell.
587
     * ```typescript
588
     * let cellWidth = this.cell.width;
589
     * ```
590
     *
591
     * @memberof IgxGridCellComponent
592
     */
593
    @Input()
594
    public width = '';
1,763✔
595

596
    /**
597
     * @hidden
598
     */
599
    @Input()
600
    @HostBinding('class.igx-grid__td--active')
601
    public active = false;
1,763✔
602

603
    @HostBinding('attr.aria-selected')
604
    public get ariaSelected() {
605
        return this.selected || this.column.selected || this.intRow.selected;
9,938✔
606
    }
607

608
    /**
609
     * Gets whether the cell is selected.
610
     * ```typescript
611
     * let isSelected = this.cell.selected;
612
     * ```
613
     *
614
     * @memberof IgxGridCellComponent
615
     */
616
    @HostBinding('class.igx-grid__td--selected')
617
    public get selected() {
618
        return this.selectionService.selected(this.selectionNode);
19,876✔
619
    }
620

621
    /**
622
     * Selects/deselects the cell.
623
     * ```typescript
624
     * this.cell.selected = true.
625
     * ```
626
     *
627
     * @memberof IgxGridCellComponent
628
     */
629
    public set selected(val: boolean) {
UNCOV
630
        const node = this.selectionNode;
×
UNCOV
631
        if (val) {
×
UNCOV
632
            this.selectionService.add(node);
×
633
        } else {
634
            this.selectionService.remove(node);
×
635
        }
UNCOV
636
        this.grid.notifyChanges();
×
637
    }
638

639
    /**
640
     * Gets whether the cell column is selected.
641
     * ```typescript
642
     * let isCellColumnSelected = this.cell.columnSelected;
643
     * ```
644
     *
645
     * @memberof IgxGridCellComponent
646
     */
647
    @HostBinding('class.igx-grid__td--column-selected')
648
    public get columnSelected() {
649
        return this.selectionService.isColumnSelected(this.column.field);
9,938✔
650
    }
651

652
    /**
653
     * Sets the current edit value while a cell is in edit mode.
654
     * Only for cell editing mode.
655
     * ```typescript
656
     * this.cell.editValue = value;
657
     * ```
658
     *
659
     * @memberof IgxGridCellComponent
660
     */
661
    public set editValue(value) {
UNCOV
662
        if (this.grid.crudService.cellInEditMode) {
×
UNCOV
663
            this.grid.crudService.cell.editValue = value;
×
664
        }
665
    }
666

667
    /**
668
     * Gets the current edit value while a cell is in edit mode.
669
     * Only for cell editing mode.
670
     * ```typescript
671
     * let editValue = this.cell.editValue;
672
     * ```
673
     *
674
     * @memberof IgxGridCellComponent
675
     */
676
    public get editValue() {
UNCOV
677
        if (this.grid.crudService.cellInEditMode) {
×
UNCOV
678
            return this.grid.crudService.cell.editValue;
×
679
        }
680
    }
681

682
    /**
683
     * Returns whether the cell is editable.
684
     */
685
    public get editable(): boolean {
686
        return this.column.editable && !this.intRow.disabled;
9,938!
687
    }
688

689
    /**
690
     * @hidden
691
     */
692
    @Input()
693
    @HostBinding('class.igx-grid__td--row-pinned-first')
694
    public displayPinnedChip = false;
1,763✔
695

696
    @HostBinding('style.min-height.px')
697
    protected get minHeight() {
698
        if ((this.grid as any).isCustomSetRowHeight) {
9,938!
UNCOV
699
            return this.grid.renderedRowHeight;
×
700
        }
701
    }
702

703
    @ViewChild('defaultCell', { read: TemplateRef, static: true })
704
    protected defaultCellTemplate: TemplateRef<any>;
705

706
    @ViewChild('defaultPinnedIndicator', { read: TemplateRef, static: true })
707
    protected defaultPinnedIndicator: TemplateRef<any>;
708

709
    @ViewChild('inlineEditor', { read: TemplateRef, static: true })
710
    protected inlineEditorTemplate: TemplateRef<any>;
711

712
    @ViewChild('addRowCell', { read: TemplateRef, static: true })
713
    protected addRowCellTemplate: TemplateRef<any>;
714

715
    @ViewChild(IgxTextHighlightDirective, { read: IgxTextHighlightDirective })
716
    protected set highlight(value: IgxTextHighlightDirective) {
717
        this._highlight = value;
1,765✔
718

719
        if (this._highlight && this.grid.lastSearchInfo.searchText) {
1,765!
UNCOV
720
            this._highlight.highlight(this.grid.lastSearchInfo.searchText,
×
721
                this.grid.lastSearchInfo.caseSensitive,
722
                this.grid.lastSearchInfo.exactMatch);
UNCOV
723
            this._highlight.activateIfNecessary();
×
724
        }
725
    }
726

727
    protected get highlight() {
728
        return this._highlight;
2,476✔
729
    }
730

731
    protected get selectionNode(): ISelectionNode {
732
        return {
19,876✔
733
            row: this.rowIndex,
734
            column: this.column.columnLayoutChild ? this.column.parent.visibleIndex : this.visibleColumnIndex,
19,876!
735
            layout: this.column.columnLayoutChild ? {
19,876!
736
                rowStart: this.column.rowStart,
737
                colStart: this.column.colStart,
738
                rowEnd: this.column.rowEnd,
739
                colEnd: this.column.colEnd,
740
                columnVisibleIndex: this.visibleColumnIndex
741
            } : null
742
        };
743
    }
744

745
    /**
746
     * Sets/gets the highlight class of the cell.
747
     * Default value is `"igx-highlight"`.
748
     * ```typescript
749
     * let highlightClass = this.cell.highlightClass;
750
     * ```
751
     * ```typescript
752
     * this.cell.highlightClass = 'igx-cell-highlight';
753
     * ```
754
     *
755
     * @memberof IgxGridCellComponent
756
     */
757
    public highlightClass = 'igx-highlight';
1,763✔
758

759
    /**
760
     * Sets/gets the active highlight class class of the cell.
761
     * Default value is `"igx-highlight__active"`.
762
     * ```typescript
763
     * let activeHighlightClass = this.cell.activeHighlightClass;
764
     * ```
765
     * ```typescript
766
     * this.cell.activeHighlightClass = 'igx-cell-highlight_active';
767
     * ```
768
     *
769
     * @memberof IgxGridCellComponent
770
     */
771
    public activeHighlightClass = 'igx-highlight__active';
1,763✔
772

773
    /** @hidden @internal */
774
    public get step(): number {
UNCOV
775
        const digitsInfo = this.column.pipeArgs.digitsInfo;
×
UNCOV
776
        if (!digitsInfo) {
×
777
            return 1;
×
778
        }
UNCOV
779
        const step = +digitsInfo.substr(digitsInfo.indexOf('.') + 1, 1);
×
UNCOV
780
        return 1 / (Math.pow(10, step));
×
781
    }
782

783
    /** @hidden @internal */
784
    public get currencyCode(): string {
UNCOV
785
        return this.column.pipeArgs.currencyCode ?
×
786
            this.column.pipeArgs.currencyCode : getLocaleCurrencyCode(this.grid.locale);
787
    }
788

789
    /** @hidden @internal */
790
    public get currencyCodeSymbol(): string {
UNCOV
791
        return getCurrencySymbol(this.currencyCode, 'wide', this.grid.locale);
×
792
    }
793

794
    protected _lastSearchInfo: ISearchInfo;
795
    private _highlight: IgxTextHighlightDirective;
796
    private _cellSelection: GridSelectionMode = GridSelectionMode.multiple;
1,763✔
797
    private _vIndex = -1;
1,763✔
798

799
    constructor(
800
        protected selectionService: IgxGridSelectionService,
1,763✔
801
        @Inject(IGX_GRID_BASE) public grid: GridType,
1,763✔
802
        @Inject(IgxOverlayService) protected overlayService: IgxOverlayService,
1,763✔
803
        public cdr: ChangeDetectorRef,
1,763✔
804
        private element: ElementRef<HTMLElement>,
1,763✔
805
        protected zone: NgZone,
1,763✔
806
        private touchManager: HammerGesturesManager,
1,763✔
807
        protected platformUtil: PlatformUtil
1,763✔
808
    ) { }
809

810
    /**
811
     * @hidden
812
     * @internal
813
     */
814
    @HostListener('dblclick', ['$event'])
815
    public onDoubleClick = (event: MouseEvent) => {
1,763✔
UNCOV
816
        if (event.type === 'doubletap') {
×
817
            // prevent double-tap to zoom on iOS
UNCOV
818
            event.preventDefault();
×
819
        }
UNCOV
820
        if (this.editable && !this.editMode && !this.intRow.deleted && !this.grid.crudService.rowEditingBlocked) {
×
UNCOV
821
            this.grid.crudService.enterEditMode(this, event as Event);
×
822
        }
823

UNCOV
824
        this.grid.doubleClick.emit({
×
825
            cell: this.getCellType(),
826
            event
827
        });
828
    };
829

830
    /**
831
     * @hidden
832
     * @internal
833
     */
834
    @HostListener('click', ['$event'])
835
    public onClick(event: MouseEvent) {
UNCOV
836
        this.grid.cellClick.emit({
×
837
            cell: this.getCellType(),
838
            event
839
        });
840
    }
841

842
    /**
843
     * @hidden
844
     * @internal
845
     */
846
    public ngOnInit() {
847
        this.zone.runOutsideAngular(() => {
1,763✔
848
            this.nativeElement.addEventListener('pointerdown', this.pointerdown);
1,763✔
849
            this.addPointerListeners(this.cellSelectionMode);
1,763✔
850
        });
851
        if (this.platformUtil.isIOS) {
1,763!
UNCOV
852
            this.touchManager.addEventListener(this.nativeElement, 'doubletap', this.onDoubleClick, {
×
853
                cssProps: {} /* don't disable user-select, etc */
854
            });
855
        }
856

857
    }
858

859
    public ngAfterViewInit() {
860
        this.errorTooltip.changes.pipe(takeUntil(this._destroy$)).subscribe(() => {
1,763✔
UNCOV
861
            if (this.errorTooltip.length > 0 && this.active) {
×
862
                // error ocurred
UNCOV
863
                this.cdr.detectChanges();
×
UNCOV
864
                this.openErrorTooltip();
×
865
            }
866
        });
867
    }
868

869
    /**
870
     * @hidden
871
     * @internal
872
     */
873
    public errorShowing = false;
1,763✔
874

875
    private openErrorTooltip() {
UNCOV
876
        const tooltip = this.errorTooltip.first;
×
UNCOV
877
        tooltip.open(
×
878
            {
879
                target: this.errorIcon.el.nativeElement,
880
                closeOnOutsideClick: true,
881
                excludeFromOutsideClick: [this.nativeElement],
882
                closeOnEscape: false,
883
                outlet: this.grid.outlet,
884
                modal: false,
885
                positionStrategy: new AutoPositionStrategy({
886
                    horizontalStartPoint: HorizontalAlignment.Center,
887
                    horizontalDirection: HorizontalAlignment.Center
888
                })
889
            }
890
        );
891
    }
892

893
    /**
894
     * @hidden
895
     * @internal
896
     */
897
    public ngOnDestroy() {
898
        this.zone.runOutsideAngular(() => {
1,763✔
899
            this.nativeElement.removeEventListener('pointerdown', this.pointerdown);
1,763✔
900
            this.removePointerListeners(this.cellSelectionMode);
1,763✔
901
        });
902
        this.touchManager.destroy();
1,763✔
903
        this._destroy$.next();
1,763✔
904
        this._destroy$.complete();
1,763✔
905
    }
906

907
    /**
908
     * @hidden
909
     * @internal
910
     */
911
    public ngOnChanges(changes: SimpleChanges): void {
912
        if (changes.editMode && changes.editMode.currentValue && this.formControl) {
2,006!
913
            // ensure when values change, form control is forced to be marked as touche.
UNCOV
914
            this.formControl.valueChanges.pipe(takeWhile(() => this.editMode)).subscribe(() => this.formControl.markAsTouched());
×
915
            // while in edit mode subscribe to value changes on the current form control and set to editValue
UNCOV
916
            this.formControl.statusChanges.pipe(takeWhile(() => this.editMode)).subscribe(status => {
×
UNCOV
917
                if (status === 'INVALID' && this.errorTooltip.length > 0) {
×
918
                    this.cdr.detectChanges();
×
919
                    const tooltip = this.errorTooltip.first;
×
920
                    this.resizeAndRepositionOverlayById(tooltip.overlayId, this.errorTooltip.first.element.offsetWidth);
×
921
                }
922
            });
923
        }
924
        if (changes.value && !changes.value.firstChange) {
2,006✔
925
            if (this.highlight) {
200✔
926
                this.highlight.lastSearchInfo.searchText = this.grid.lastSearchInfo.searchText;
171✔
927
                this.highlight.lastSearchInfo.caseSensitive = this.grid.lastSearchInfo.caseSensitive;
171✔
928
                this.highlight.lastSearchInfo.exactMatch = this.grid.lastSearchInfo.exactMatch;
171✔
929
            }
930
        }
931
    }
932

933

934

935
    /**
936
     * @hidden @internal
937
     */
938
    private resizeAndRepositionOverlayById(overlayId: string, newSize: number) {
939
        const overlay = this.overlayService.getOverlayById(overlayId);
×
940
        if (!overlay) return;
×
941
        overlay.initialSize.width = newSize;
×
942
        overlay.elementRef.nativeElement.parentElement.style.width = newSize + 'px';
×
943
        this.overlayService.reposition(overlayId);
×
944
    }
945

946
    /**
947
     * Starts/ends edit mode for the cell.
948
     *
949
     * ```typescript
950
     * cell.setEditMode(true);
951
     * ```
952
     */
953
    public setEditMode(value: boolean): void {
UNCOV
954
        if (this.intRow.deleted) {
×
955
            return;
×
956
        }
UNCOV
957
        if (this.editable && value) {
×
UNCOV
958
            if (this.grid.crudService.cellInEditMode) {
×
UNCOV
959
                this.grid.gridAPI.update_cell(this.grid.crudService.cell);
×
UNCOV
960
                this.grid.crudService.endCellEdit();
×
961
            }
UNCOV
962
            this.grid.crudService.enterEditMode(this);
×
963
        } else {
UNCOV
964
            this.grid.crudService.endCellEdit();
×
965
        }
UNCOV
966
        this.grid.notifyChanges();
×
967
    }
968

969
    /**
970
     * Sets new value to the cell.
971
     * ```typescript
972
     * this.cell.update('New Value');
973
     * ```
974
     *
975
     * @memberof IgxGridCellComponent
976
     */
977
    // TODO: Refactor
978
    public update(val: any) {
UNCOV
979
        if (this.intRow.deleted) {
×
980
            return;
×
981
        }
982

UNCOV
983
        let cell = this.grid.crudService.cell;
×
UNCOV
984
        if (!cell) {
×
UNCOV
985
            cell = this.grid.crudService.createCell(this);
×
986
        }
UNCOV
987
        cell.editValue = val;
×
UNCOV
988
        this.grid.gridAPI.update_cell(cell);
×
UNCOV
989
        this.grid.crudService.endCellEdit();
×
UNCOV
990
        this.cdr.markForCheck();
×
991
    }
992

993
    /**
994
     *
995
     * @hidden
996
     * @internal
997
     */
998
    public pointerdown = (event: PointerEvent) => {
1,763✔
UNCOV
999
        if (this.cellSelectionMode !== GridSelectionMode.multiple) {
×
UNCOV
1000
            this.activate(event);
×
UNCOV
1001
            return;
×
1002
        }
UNCOV
1003
        if (!this.platformUtil.isLeftClick(event)) {
×
UNCOV
1004
            event.preventDefault();
×
UNCOV
1005
            this.grid.navigation.setActiveNode({ rowIndex: this.rowIndex, colIndex: this.visibleColumnIndex });
×
UNCOV
1006
            this.selectionService.addKeyboardRange();
×
UNCOV
1007
            this.selectionService.initKeyboardState();
×
UNCOV
1008
            this.selectionService.primaryButton = false;
×
1009
            // Ensure RMB Click on edited cell does not end cell editing
UNCOV
1010
            if (!this.selected) {
×
UNCOV
1011
                this.grid.crudService.updateCell(true, event);
×
1012
            }
UNCOV
1013
            return;
×
1014
        } else {
UNCOV
1015
            this.selectionService.primaryButton = true;
×
1016
        }
UNCOV
1017
        this.selectionService.pointerDown(this.selectionNode, event.shiftKey, event.ctrlKey);
×
UNCOV
1018
        this.activate(event);
×
1019
    };
1020

1021
    /**
1022
     *
1023
     * @hidden
1024
     * @internal
1025
     */
1026
    public pointerenter = (event: PointerEvent) => {
1,763✔
1027
        const isHierarchicalGrid = this.grid.type === 'hierarchical';
1✔
1028
        if (isHierarchicalGrid && (!this.grid.navigation?.activeNode?.gridID || this.grid.navigation.activeNode.gridID !== this.gridID)) {
1!
1029
            return;
1✔
1030
        }
UNCOV
1031
        const dragMode = this.selectionService.pointerEnter(this.selectionNode, event);
×
UNCOV
1032
        if (dragMode) {
×
UNCOV
1033
            this.grid.cdr.detectChanges();
×
1034
        }
1035
    };
1036

1037
    /**
1038
     * @hidden
1039
     * @internal
1040
     */
1041
    public focusout = () => {
1,763✔
UNCOV
1042
        this.closeErrorTooltip();
×
1043
    }
1044

1045
    private closeErrorTooltip() {
UNCOV
1046
        const tooltip = this.errorTooltip.first;
×
UNCOV
1047
        if (tooltip) {
×
1048
            tooltip.close();
×
1049
        }
1050
    }
1051

1052
    /**
1053
     * @hidden
1054
     * @internal
1055
     */
1056
    public pointerup = (event: PointerEvent) => {
1,763✔
UNCOV
1057
        const isHierarchicalGrid = this.grid.type === 'hierarchical';
×
UNCOV
1058
        if (!this.platformUtil.isLeftClick(event) || (isHierarchicalGrid && (!this.grid.navigation?.activeNode?.gridID ||
×
1059
            this.grid.navigation.activeNode.gridID !== this.gridID))) {
UNCOV
1060
            return;
×
1061
        }
UNCOV
1062
        if (this.selectionService.pointerUp(this.selectionNode, this.grid.rangeSelected)) {
×
UNCOV
1063
            this.grid.cdr.detectChanges();
×
1064
        }
1065
    };
1066

1067
    /**
1068
     * @hidden
1069
     * @internal
1070
     */
1071
    public activate(event: FocusEvent | KeyboardEvent) {
UNCOV
1072
        const node = this.selectionNode;
×
UNCOV
1073
        let shouldEmitSelection = !this.selectionService.isActiveNode(node);
×
1074

UNCOV
1075
        if (this.selectionService.primaryButton) {
×
UNCOV
1076
            const currentActive = this.selectionService.activeElement;
×
UNCOV
1077
            if (this.cellSelectionMode === GridSelectionMode.single && (event as any)?.ctrlKey && this.selected) {
×
UNCOV
1078
                this.selectionService.activeElement = null;
×
UNCOV
1079
                shouldEmitSelection = true;
×
1080
            } else {
UNCOV
1081
                this.selectionService.activeElement = node;
×
1082
            }
UNCOV
1083
            const cancel = this._updateCRUDStatus(event);
×
UNCOV
1084
            if (cancel) {
×
UNCOV
1085
                this.selectionService.activeElement = currentActive;
×
UNCOV
1086
                return;
×
1087
            }
1088

UNCOV
1089
            const activeElement = this.selectionService.activeElement;
×
UNCOV
1090
            const row = activeElement ? this.grid.gridAPI.get_row_by_index(activeElement.row) : null;
×
UNCOV
1091
            if (this.grid.crudService.rowEditingBlocked && row && this.intRow.key !== row.key) {
×
1092
                return;
×
1093
            }
1094

1095
        } else {
1096
            this.selectionService.activeElement = null;
×
1097
            if (this.grid.crudService.cellInEditMode && !this.editMode) {
×
1098
                this.grid.crudService.updateCell(true, event);
×
1099
            }
1100
        }
1101

UNCOV
1102
        this.grid.navigation.setActiveNode({ row: this.rowIndex, column: this.visibleColumnIndex });
×
1103

UNCOV
1104
        const isTargetErrorIcon = event && event.target && event.target === this.errorIcon?.el.nativeElement
×
UNCOV
1105
        if (this.isInvalid && !isTargetErrorIcon) {
×
UNCOV
1106
            this.cdr.detectChanges();
×
UNCOV
1107
            this.openErrorTooltip();
×
UNCOV
1108
            this.grid.activeNodeChange.pipe(first()).subscribe(() => {
×
1109
                this.closeErrorTooltip();
×
1110
            });
1111
        }
UNCOV
1112
        this.selectionService.primaryButton = true;
×
UNCOV
1113
        if (this.cellSelectionMode === GridSelectionMode.multiple && this.selectionService.activeElement) {
×
UNCOV
1114
            if (this.selectionService.isInMap(this.selectionService.activeElement) && (event as any)?.ctrlKey && !(event as any)?.shiftKey) {
×
UNCOV
1115
                this.selectionService.remove(this.selectionService.activeElement);
×
UNCOV
1116
                shouldEmitSelection = true;
×
1117
            } else {
UNCOV
1118
                this.selectionService.add(this.selectionService.activeElement, false); // pointer events handle range generation
×
UNCOV
1119
                this.selectionService.keyboardStateOnFocus(node, this.grid.rangeSelected, this.nativeElement);
×
1120
            }
1121
        }
UNCOV
1122
        if (this.grid.isCellSelectable && shouldEmitSelection) {
×
UNCOV
1123
            this.zone.run(() => this.grid.selected.emit({ cell: this.getCellType(), event }));
×
1124
        }
1125
    }
1126

1127
    /**
1128
     * If the provided string matches the text in the cell, the text gets highlighted.
1129
     * ```typescript
1130
     * this.cell.highlightText('Cell Value', true);
1131
     * ```
1132
     *
1133
     * @memberof IgxGridCellComponent
1134
     */
1135
    public highlightText(text: string, caseSensitive?: boolean, exactMatch?: boolean): number {
1136
        return this.highlight && this.column.searchable ? this.highlight.highlight(text, caseSensitive, exactMatch) : 0;
1,763!
1137
    }
1138

1139
    /**
1140
     * Clears the highlight of the text in the cell.
1141
     * ```typescript
1142
     * this.cell.clearHighLight();
1143
     * ```
1144
     *
1145
     * @memberof IgxGridCellComponent
1146
     */
1147
    public clearHighlight() {
UNCOV
1148
        if (this.highlight && this.column.searchable) {
×
UNCOV
1149
            this.highlight.clearHighlight();
×
1150
        }
1151
    }
1152

1153
    /**
1154
     * @hidden
1155
     * @internal
1156
     */
1157
    public calculateSizeToFit(range: any): number {
UNCOV
1158
        return this.platformUtil.getNodeSizeViaRange(range, this.nativeElement);
×
1159
    }
1160

1161
    /**
1162
     * @hidden
1163
     * @internal
1164
     */
1165
    public get searchMetadata() {
1166
        const meta = new Map<string, any>();
1,787✔
1167
        meta.set('pinned', this.grid.isRecordPinnedByViewIndex(this.intRow.index));
1,787✔
1168
        return meta;
1,787✔
1169
    }
1170

1171
    /**
1172
     * @hidden
1173
     * @internal
1174
     */
1175
    private _updateCRUDStatus(event?: Event) {
UNCOV
1176
        if (this.editMode) {
×
UNCOV
1177
            return;
×
1178
        }
1179

1180
        let editableArgs;
UNCOV
1181
        const crud = this.grid.crudService;
×
UNCOV
1182
        const editableCell = this.grid.crudService.cell;
×
UNCOV
1183
        const editMode = !!(crud.row || crud.cell);
×
1184

UNCOV
1185
        if (this.editable && editMode && !this.intRow.deleted) {
×
UNCOV
1186
            if (editableCell) {
×
UNCOV
1187
                editableArgs = this.grid.crudService.updateCell(false, event);
×
1188

1189
                /* This check is related with the following issue #6517:
1190
                 * when edit cell that belongs to a column which is sorted and press tab,
1191
                 * the next cell in edit mode is with wrong value /its context is not updated/;
1192
                 * So we reapply sorting before the next cell enters edit mode.
1193
                 * Also we need to keep the notifyChanges below, because of the current
1194
                 * change detection cycle when we have editing with enabled transactions
1195
                 */
UNCOV
1196
                if (this.grid.sortingExpressions.length && this.grid.sortingExpressions.indexOf(editableCell.column.field)) {
×
1197
                    this.grid.cdr.detectChanges();
×
1198
                }
1199

UNCOV
1200
                if (editableArgs && editableArgs.cancel) {
×
UNCOV
1201
                    return true;
×
1202
                }
1203

UNCOV
1204
                crud.exitCellEdit(event);
×
1205
            }
UNCOV
1206
            this.grid.tbody.nativeElement.focus({ preventScroll: true });
×
UNCOV
1207
            this.grid.notifyChanges();
×
UNCOV
1208
            crud.enterEditMode(this, event);
×
UNCOV
1209
            return false;
×
1210
        }
1211

UNCOV
1212
        if (editableCell && crud.sameRow(this.cellID.rowID)) {
×
UNCOV
1213
            this.grid.crudService.updateCell(true, event);
×
UNCOV
1214
        } else if (editMode && !crud.sameRow(this.cellID.rowID)) {
×
UNCOV
1215
            this.grid.crudService.endEdit(true, event);
×
1216
        }
1217
    }
1218

1219
    private addPointerListeners(selection) {
1220
        if (selection !== GridSelectionMode.multiple) {
1,763!
UNCOV
1221
            return;
×
1222
        }
1223
        this.nativeElement.addEventListener('pointerenter', this.pointerenter);
1,763✔
1224
        this.nativeElement.addEventListener('pointerup', this.pointerup);
1,763✔
1225
        this.nativeElement.addEventListener('focusout', this.focusout);
1,763✔
1226
    }
1227

1228
    private removePointerListeners(selection) {
1229
        if (selection !== GridSelectionMode.multiple) {
1,763!
UNCOV
1230
            return;
×
1231
        }
1232
        this.nativeElement.removeEventListener('pointerenter', this.pointerenter);
1,763✔
1233
        this.nativeElement.removeEventListener('pointerup', this.pointerup);
1,763✔
1234
        this.nativeElement.removeEventListener('focusout', this.focusout);
1,763✔
1235
    }
1236

1237
    private getCellType(useRow?: boolean): CellType {
UNCOV
1238
        const rowID = useRow ? this.grid.createRow(this.intRow.index, this.intRow.data) : this.intRow.index;
×
UNCOV
1239
        return new IgxGridCell(this.grid, rowID, this.column);
×
1240
    }
1241
}
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