• 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

0.32
/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts
1
import {
2
    AfterViewInit,
3
    ChangeDetectorRef,
4
    Component,
5
    Input,
6
    TemplateRef,
7
    ViewChild,
8
    ViewChildren,
9
    QueryList,
10
    ElementRef,
11
    HostBinding,
12
    ChangeDetectionStrategy,
13
    ViewRef,
14
    HostListener,
15
    OnDestroy
16
} from '@angular/core';
17
import { GridColumnDataType, DataUtil } from '../../../data-operations/data-util';
18
import { IgxDropDownComponent } from '../../../drop-down/drop-down.component';
19
import { IFilteringOperation } from '../../../data-operations/filtering-condition';
20
import { FilteringLogic, IFilteringExpression } from '../../../data-operations/filtering-expression.interface';
21
import { HorizontalAlignment, VerticalAlignment, OverlaySettings } from '../../../services/overlay/utilities';
22
import { ConnectedPositioningStrategy } from '../../../services/overlay/position/connected-positioning-strategy';
23
import { IgxDropDownItemComponent } from '../../../drop-down/drop-down-item.component';
24
import { ISelectionEventArgs } from '../../../drop-down/drop-down.common';
25
import { IgxFilteringService } from '../grid-filtering.service';
26
import { AbsoluteScrollStrategy } from '../../../services/overlay/scroll';
27
import { IgxDatePickerComponent } from '../../../date-picker/date-picker.component';
28
import { IgxTimePickerComponent } from '../../../time-picker/time-picker.component';
29
import { isEqual, PlatformUtil } from '../../../core/utils';
30
import { Subject } from 'rxjs';
31
import { takeUntil } from 'rxjs/operators';
32
import { ExpressionUI } from '../excel-style/common';
33
import { ColumnType } from '../../common/grid.interface';
34
import { IgxRippleDirective } from '../../../directives/ripple/ripple.directive';
35
import { IgxChipComponent, IBaseChipEventArgs } from '../../../chips/chip.component';
36
import { IgxChipsAreaComponent } from '../../../chips/chips-area.component';
37
import { IgxButtonDirective } from '../../../directives/button/button.directive';
38
import { IgxDateTimeEditorDirective } from '../../../directives/date-time-editor/date-time-editor.directive';
39
import { IgxPickerToggleComponent, IgxPickerClearComponent } from '../../../date-common/picker-icons.common';
40
import { IgxSuffixDirective } from '../../../directives/suffix/suffix.directive';
41
import { IgxInputDirective } from '../../../directives/input/input.directive';
42
import { IgxDropDownItemNavigationDirective } from '../../../drop-down/drop-down-navigation.directive';
43
import { IgxPrefixDirective } from '../../../directives/prefix/prefix.directive';
44
import { IgxInputGroupComponent } from '../../../input-group/input-group.component';
45
import { IgxIconComponent } from '../../../icon/icon.component';
46
import { NgFor, NgIf, NgTemplateOutlet, NgClass } from '@angular/common';
47
import { IgxIconButtonDirective } from '../../../directives/button/icon-button.directive';
48
import { Size } from '../../common/enums';
49

50
/**
51
 * @hidden
52
 */
53
@Component({
54
    changeDetection: ChangeDetectionStrategy.OnPush,
55
    selector: 'igx-grid-filtering-row',
56
    templateUrl: './grid-filtering-row.component.html',
57
    imports: [NgFor, IgxDropDownComponent, IgxDropDownItemComponent, IgxChipsAreaComponent, IgxChipComponent, IgxIconComponent, IgxInputGroupComponent, IgxPrefixDirective, IgxDropDownItemNavigationDirective, IgxInputDirective, NgIf, IgxSuffixDirective, IgxDatePickerComponent, IgxPickerToggleComponent, IgxPickerClearComponent, IgxTimePickerComponent, IgxDateTimeEditorDirective, NgTemplateOutlet, IgxButtonDirective, NgClass, IgxRippleDirective, IgxIconButtonDirective]
58
})
59
export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy {
2✔
60
    @Input()
61
    public get column(): ColumnType {
UNCOV
62
        return this._column;
×
63
    }
64

65
    public set column(val) {
UNCOV
66
        if (this._column) {
×
UNCOV
67
            this.expressionsList.forEach(exp => exp.isSelected = false);
×
68
        }
UNCOV
69
        if (val) {
×
UNCOV
70
            this._column = val;
×
71

UNCOV
72
            this.expressionsList = this.filteringService.getExpressions(this._column.field);
×
UNCOV
73
            this.resetExpression();
×
74

UNCOV
75
            this.chipAreaScrollOffset = 0;
×
UNCOV
76
            this.transform(this.chipAreaScrollOffset);
×
77
        }
78
    }
79

80
    @Input()
81
    public get value(): any {
UNCOV
82
        return this._value;
×
83
    }
84

85
    public set value(val) {
UNCOV
86
        if (!val && val !== 0 && (this.expression.searchVal || this.expression.searchVal === 0)) {
×
UNCOV
87
            this.expression.searchVal = null;
×
UNCOV
88
            this._value = null;
×
UNCOV
89
            const index = this.expressionsList.findIndex(item => item.expression === this.expression);
×
UNCOV
90
            if (index === 0 && this.expressionsList.length === 1) {
×
UNCOV
91
                this.filteringService.clearFilter(this.column.field);
×
92

UNCOV
93
                if (this.expression.condition.isUnary) {
×
UNCOV
94
                    this.resetExpression(this.expression.condition.name);
×
95
                }
96

UNCOV
97
                return;
×
98
            }
99
        } else {
UNCOV
100
            if (val === '') {
×
101
                return;
×
102
            }
UNCOV
103
            const oldValue = this.expression.searchVal;
×
UNCOV
104
            if (isEqual(oldValue, val)) {
×
UNCOV
105
                return;
×
106
            }
107

UNCOV
108
            this._value = val;
×
UNCOV
109
            this.expression.searchVal = DataUtil.parseValue(this.column.dataType, val);
×
UNCOV
110
            if (this.expressionsList.find(item => item.expression === this.expression) === undefined) {
×
UNCOV
111
                this.addExpression(true);
×
112
            }
UNCOV
113
            this.filter();
×
114
        }
115
    }
116

117
    protected get filteringElementsSize(): Size {
118
        // needed because we want the size of the chips to be either Medium or Small
UNCOV
119
        return this.column.grid.gridSize === Size.Large ? Size.Medium : this.column.grid.gridSize;
×
120
    }
121

122
    @HostBinding('class.igx-grid__filtering-row')
UNCOV
123
    public defaultCSSClass = true;
×
124

125
    @ViewChild('defaultFilterUI', { read: TemplateRef, static: true })
126
    protected defaultFilterUI: TemplateRef<any>;
127

128
    @ViewChild('defaultDateUI', { read: TemplateRef, static: true })
129
    protected defaultDateUI: TemplateRef<any>;
130

131
    @ViewChild('defaultTimeUI', { read: TemplateRef, static: true })
132
    protected defaultTimeUI: TemplateRef<any>;
133

134
    @ViewChild('defaultDateTimeUI', { read: TemplateRef, static: true })
135
    protected defaultDateTimeUI: TemplateRef<any>;
136

137
    @ViewChild('input', { read: ElementRef })
138
    protected input: ElementRef<HTMLInputElement>;
139

140
    @ViewChild('inputGroupConditions', { read: IgxDropDownComponent, static: true })
141
    protected dropDownConditions: IgxDropDownComponent;
142

143
    @ViewChild('chipsArea', { read: IgxChipsAreaComponent, static: true })
144
    protected chipsArea: IgxChipsAreaComponent;
145

146
    @ViewChildren('operators', { read: IgxDropDownComponent })
147
    protected dropDownOperators: QueryList<IgxDropDownComponent>;
148

149
    @ViewChild('inputGroup', { read: ElementRef })
150
    protected inputGroup: ElementRef<HTMLElement>;
151

152
    @ViewChild('picker')
153
    protected picker: IgxDatePickerComponent | IgxTimePickerComponent;
154

155
    @ViewChild('inputGroupPrefix', { read: ElementRef })
156
    protected inputGroupPrefix: ElementRef<HTMLElement>;
157

158
    @ViewChild('container', { static: true })
159
    protected container: ElementRef<HTMLElement>;
160

161
    @ViewChild('operand')
162
    protected operand: ElementRef<HTMLElement>;
163

164
    @ViewChild('closeButton', { static: true })
165
    protected closeButton: ElementRef<HTMLElement>;
166

167
    public get nativeElement() {
UNCOV
168
        return this.ref.nativeElement;
×
169
    }
170

171
    public showArrows: boolean;
172
    public expression: IFilteringExpression;
173
    public expressionsList: Array<ExpressionUI>;
174

UNCOV
175
    private _positionSettings = {
×
176
        horizontalStartPoint: HorizontalAlignment.Left,
177
        verticalStartPoint: VerticalAlignment.Bottom
178
    };
179

UNCOV
180
    private _conditionsOverlaySettings: OverlaySettings = {
×
181
        closeOnOutsideClick: true,
182
        modal: false,
183
        scrollStrategy: new AbsoluteScrollStrategy(),
184
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings)
185
    };
186

UNCOV
187
    private _operatorsOverlaySettings: OverlaySettings = {
×
188
        closeOnOutsideClick: true,
189
        modal: false,
190
        scrollStrategy: new AbsoluteScrollStrategy(),
191
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings)
192
    };
193

194
    private chipsAreaWidth: number;
UNCOV
195
    private chipAreaScrollOffset = 0;
×
UNCOV
196
    private _column = null;
×
UNCOV
197
    private isKeyPressed = false;
×
UNCOV
198
    private isComposing = false;
×
UNCOV
199
    private _cancelChipClick = false;
×
UNCOV
200
    private _value = null;
×
201

202
    /** switch to icon buttons when width is below 432px */
UNCOV
203
    private readonly NARROW_WIDTH_THRESHOLD = 432;
×
204

UNCOV
205
    private $destroyer = new Subject<void>();
×
206

207
    constructor(
UNCOV
208
        public filteringService: IgxFilteringService,
×
UNCOV
209
        public ref: ElementRef<HTMLElement>,
×
UNCOV
210
        public cdr: ChangeDetectorRef,
×
UNCOV
211
        protected platform: PlatformUtil,
×
212
    ) { }
213

214
    @HostListener('keydown', ['$event'])
215
    public onKeydownHandler(evt: KeyboardEvent) {
UNCOV
216
        if (this.platform.isFilteringKeyCombo(evt)) {
×
UNCOV
217
                evt.preventDefault();
×
UNCOV
218
                evt.stopPropagation();
×
UNCOV
219
                this.close();
×
220
        }
221
    }
222

223
    public ngAfterViewInit() {
UNCOV
224
        this._conditionsOverlaySettings.outlet = this.column.grid.outlet;
×
UNCOV
225
        this._operatorsOverlaySettings.outlet = this.column.grid.outlet;
×
226

UNCOV
227
        const selectedItem = this.expressionsList.find(expr => expr.isSelected === true);
×
UNCOV
228
        if (selectedItem) {
×
UNCOV
229
            this.expression = selectedItem.expression;
×
UNCOV
230
            this._value = this.expression.searchVal;
×
231
        }
232

UNCOV
233
        this.filteringService.grid.localeChange
×
234
        .pipe(takeUntil(this.$destroyer))
235
        .subscribe(() => {
236
            this.cdr.markForCheck();
×
237
        });
238

UNCOV
239
        requestAnimationFrame(() => this.focusEditElement());
×
240
    }
241

242
    public get disabled(): boolean {
UNCOV
243
        return !(this.column.filteringExpressionsTree && this.column.filteringExpressionsTree.filteringOperands.length > 0);
×
244
    }
245

246
    public get template(): TemplateRef<any> {
UNCOV
247
        if (this.column.dataType === GridColumnDataType.Date) {
×
UNCOV
248
            return this.defaultDateUI;
×
249
        }
UNCOV
250
        if (this.column.dataType === GridColumnDataType.Time) {
×
UNCOV
251
            return this.defaultTimeUI;
×
252
        }
UNCOV
253
        if (this.column.dataType === GridColumnDataType.DateTime) {
×
UNCOV
254
            return this.defaultDateTimeUI;
×
255
        }
UNCOV
256
        return this.defaultFilterUI;
×
257
    }
258

259
    public get type() {
UNCOV
260
        switch (this.column.dataType) {
×
261
            case GridColumnDataType.String:
262
            case GridColumnDataType.Boolean:
UNCOV
263
                return 'text';
×
264
            case GridColumnDataType.Number:
265
            case GridColumnDataType.Currency:
UNCOV
266
                return 'number';
×
267
        }
268
    }
269

270
    public get conditions(): any {
UNCOV
271
        return this.column.filters.conditionList();
×
272
    }
273

274
    public get isUnaryCondition(): boolean {
UNCOV
275
        if (this.expression.condition) {
×
UNCOV
276
            return this.expression.condition.isUnary;
×
277
        } else {
UNCOV
278
            return true;
×
279
        }
280
    }
281

282
    public get placeholder(): string {
UNCOV
283
        if (this.expression.condition && this.expression.condition.isUnary) {
×
UNCOV
284
            return this.filteringService.getChipLabel(this.expression);
×
UNCOV
285
        } else if (this.column.dataType === GridColumnDataType.Date) {
×
UNCOV
286
            return this.filteringService.grid.resourceStrings.igx_grid_filter_row_date_placeholder;
×
UNCOV
287
        } else if (this.column.dataType === GridColumnDataType.Boolean) {
×
UNCOV
288
            return this.filteringService.grid.resourceStrings.igx_grid_filter_row_boolean_placeholder;
×
289
        } else {
UNCOV
290
            return this.filteringService.grid.resourceStrings.igx_grid_filter_row_placeholder;
×
291
        }
292
    }
293

294
    /**
295
     * Event handler for keydown on the input group's prefix.
296
     */
297
    public onPrefixKeyDown(event: KeyboardEvent) {
UNCOV
298
        if (this.platform.isActivationKey(event) && this.dropDownConditions.collapsed) {
×
299
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
300
            event.stopImmediatePropagation();
×
UNCOV
301
        } else if (event.key === this.platform.KEYMAP.TAB && !this.dropDownConditions.collapsed) {
×
UNCOV
302
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
303
        }
304
    }
305

306
    /**
307
     * Event handler for keydown on the input.
308
     */
309
    public onInputKeyDown(event: KeyboardEvent) {
UNCOV
310
        this.isKeyPressed = true;
×
UNCOV
311
        event.stopPropagation();
×
UNCOV
312
        if (this.column.dataType === GridColumnDataType.Boolean) {
×
UNCOV
313
            if (this.platform.isActivationKey(event)) {
×
UNCOV
314
                this.inputGroupPrefix.nativeElement.focus();
×
UNCOV
315
                this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
UNCOV
316
                return;
×
317
            }
318
        }
UNCOV
319
        if (event.key === this.platform.KEYMAP.ENTER) {
×
UNCOV
320
            if (this.isComposing) {
×
321
                return;
×
322
            }
UNCOV
323
            this.commitInput();
×
UNCOV
324
        } else if (event.altKey && (event.key === this.platform.KEYMAP.ARROW_DOWN)) {
×
UNCOV
325
            this.inputGroupPrefix.nativeElement.focus();
×
UNCOV
326
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
UNCOV
327
        } else if (this.platform.isFilteringKeyCombo(event)) {
×
UNCOV
328
            event.preventDefault();
×
UNCOV
329
            this.close();
×
330
        }
331
    }
332

333
    /**
334
     * Event handler for keyup on the input.
335
     */
336
    public onInputKeyUp() {
UNCOV
337
        this.isKeyPressed = false;
×
338
    }
339

340
    /**
341
     * Event handler for input on the input.
342
     */
343
    public onInput(eventArgs) {
UNCOV
344
        if (!eventArgs) {
×
UNCOV
345
            return;
×
346
        }
347

348
        // The 'iskeyPressed' flag is needed for a case in IE, because the input event is fired on focus and for some reason,
349
        // when you have a japanese character as a placeholder, on init the value here is empty string .
UNCOV
350
        const target = eventArgs.target;
×
UNCOV
351
        if (this.column.dataType === GridColumnDataType.DateTime) {
×
352
            this.value = eventArgs;
×
353
            return;
×
354
        }
UNCOV
355
        if (this.platform.isEdge && target.type !== 'number'
×
356
            || this.isKeyPressed || target.value || target.checkValidity()) {
UNCOV
357
            this.value = target.value;
×
358
        }
359
    }
360

361
    /**
362
     * Event handler for compositionstart on the input.
363
     */
364
    public onCompositionStart() {
365
        this.isComposing = true;
×
366
    }
367

368
    /**
369
     * Event handler for compositionend on the input.
370
     */
371
    public onCompositionEnd() {
372
        this.isComposing = false;
×
373
    }
374

375
    /**
376
     * Event handler for input click event.
377
     */
378
    public onInputClick() {
UNCOV
379
        if (this.column.dataType === GridColumnDataType.Boolean && this.dropDownConditions.collapsed) {
×
UNCOV
380
            this.inputGroupPrefix.nativeElement.focus();
×
UNCOV
381
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
382
        }
383
    }
384

385
    /**
386
     * Returns the filtering operation condition for a given value.
387
     */
388
    public getCondition(value: string): IFilteringOperation {
UNCOV
389
        return this.column.filters.condition(value);
×
390
    }
391

392
    /**
393
     * Returns the translated condition name for a given value.
394
     */
395
    public translateCondition(value: string): string {
UNCOV
396
        return this.filteringService.grid.resourceStrings[`igx_grid_filter_${this.getCondition(value).name}`] || value;
×
397
    }
398

399
    /**
400
     * Returns the icon name of the current condition.
401
     */
402
    public getIconName(): string {
UNCOV
403
        if (this.column.dataType === GridColumnDataType.Boolean && this.expression.condition === null) {
×
UNCOV
404
            return this.getCondition(this.conditions[0]).iconName;
×
405
        } else {
UNCOV
406
            return this.expression.condition.iconName;
×
407
        }
408
    }
409

410
    /**
411
     * Returns whether a given condition is selected in dropdown.
412
     */
413
    public isConditionSelected(conditionName: string): boolean {
UNCOV
414
        if (this.expression.condition) {
×
UNCOV
415
            return this.expression.condition.name === conditionName;
×
416
        } else {
UNCOV
417
            return false;
×
418
        }
419
    }
420

421
    /**
422
     * Clears the current filtering.
423
     */
424
    public clearFiltering() {
UNCOV
425
        this.filteringService.clearFilter(this.column.field);
×
UNCOV
426
        this.resetExpression();
×
UNCOV
427
        if (this.input) {
×
UNCOV
428
            this.input.nativeElement.focus();
×
429
        }
UNCOV
430
        this.cdr.detectChanges();
×
431

UNCOV
432
        this.chipAreaScrollOffset = 0;
×
UNCOV
433
        this.transform(this.chipAreaScrollOffset);
×
434
    }
435

436
    /**
437
     * Commits the value of the input.
438
     */
439
    public commitInput() {
UNCOV
440
        const selectedItem = this.expressionsList.filter(ex => ex.isSelected === true);
×
UNCOV
441
        selectedItem.forEach(e => e.isSelected = false);
×
442

UNCOV
443
        let indexToDeselect = -1;
×
UNCOV
444
        for (let index = 0; index < this.expressionsList.length; index++) {
×
UNCOV
445
            const expression = this.expressionsList[index].expression;
×
UNCOV
446
            if (expression.searchVal === null && !expression.condition.isUnary) {
×
UNCOV
447
                indexToDeselect = index;
×
448
            }
449
        }
UNCOV
450
        if (indexToDeselect !== -1) {
×
UNCOV
451
            this.removeExpression(indexToDeselect, this.expression);
×
452
        }
UNCOV
453
        this.resetExpression();
×
UNCOV
454
        this._value = this.expression.searchVal;
×
UNCOV
455
        this.scrollChipsWhenAddingExpression();
×
456
    }
457

458
    /**
459
     * Clears the value of the input.
460
     */
461
    public clearInput(event?: MouseEvent) {
UNCOV
462
        event?.stopPropagation();
×
UNCOV
463
        this.value = null;
×
464
    }
465

466
    /**
467
     * Event handler for keydown on clear button.
468
     */
469
    public onClearKeyDown(eventArgs: KeyboardEvent) {
UNCOV
470
        if (this.platform.isActivationKey(eventArgs)) {
×
UNCOV
471
            eventArgs.preventDefault();
×
UNCOV
472
            this.clearInput();
×
UNCOV
473
            this.focusEditElement();
×
474
        }
475
    }
476

477
    /**
478
     * Event handler for click on clear button.
479
     */
480
    public onClearClick() {
UNCOV
481
        this.clearInput();
×
UNCOV
482
        this.focusEditElement();
×
483
    }
484

485
    /**
486
     * Event handler for keydown on commit button.
487
     */
488
    public onCommitKeyDown(eventArgs: KeyboardEvent) {
UNCOV
489
        if (this.platform.isActivationKey(eventArgs)) {
×
UNCOV
490
            eventArgs.preventDefault();
×
UNCOV
491
            this.commitInput();
×
UNCOV
492
            this.focusEditElement();
×
493
        }
494
    }
495

496
    /**
497
     * Event handler for click on commit button.
498
     */
499
    public onCommitClick(event?: MouseEvent) {
UNCOV
500
        event?.stopPropagation();
×
UNCOV
501
        this.commitInput();
×
UNCOV
502
        this.focusEditElement();
×
503
    }
504

505
    /**
506
     * Event handler for focusout on the input group.
507
     */
508
    public onInputGroupFocusout() {
UNCOV
509
        if (!this.value && this.value !== 0 &&
×
510
            this.expression.condition && !this.expression.condition.isUnary) {
UNCOV
511
            return;
×
512
        }
UNCOV
513
        requestAnimationFrame(() => {
×
UNCOV
514
            const focusedElement = this.column?.grid.document.activeElement;
×
515

UNCOV
516
            if (focusedElement.classList.contains('igx-chip__remove')) {
×
517
                return;
×
518
            }
519

UNCOV
520
            if (!(focusedElement && this.editorFocused(focusedElement))
×
521
                && this.dropDownConditions.collapsed) {
UNCOV
522
                this.commitInput();
×
523
            }
524
        });
525
    }
526

527
    /**
528
     * Closes the filtering edit row.
529
     */
530
    public close() {
UNCOV
531
        if (this.expressionsList.length === 1 &&
×
532
            this.expressionsList[0].expression.searchVal === null &&
533
            this.expressionsList[0].expression.condition.isUnary === false) {
534
            this.filteringService.getExpressions(this.column.field).pop();
×
535

536
            this.filter();
×
537
        } else {
UNCOV
538
            const condToRemove = this.expressionsList.filter(ex => ex.expression.searchVal === null && !ex.expression.condition.isUnary);
×
UNCOV
539
            if (condToRemove && condToRemove.length > 0) {
×
540
                condToRemove.forEach(c => this.filteringService.removeExpression(this.column.field, this.expressionsList.indexOf(c)));
×
541
                this.filter();
×
542
            }
543
        }
544

UNCOV
545
        this.filteringService.isFilterRowVisible = false;
×
UNCOV
546
        this.filteringService.updateFilteringCell(this.column);
×
UNCOV
547
        this.filteringService.filteredColumn = null;
×
UNCOV
548
        this.filteringService.selectedExpression = null;
×
UNCOV
549
        this.filteringService.grid.theadRow.nativeElement.focus();
×
550

UNCOV
551
        this.chipAreaScrollOffset = 0;
×
UNCOV
552
        this.transform(this.chipAreaScrollOffset);
×
553
    }
554

555
    /**
556
     *  Event handler for date picker's selection.
557
     */
558
    public onDateSelected(value: Date) {
559
        this.value = value;
×
560
    }
561

562
    /** @hidden @internal */
563
    public inputGroupPrefixClick(event: MouseEvent) {
UNCOV
564
        event.stopPropagation();
×
UNCOV
565
        (event.currentTarget as HTMLElement).focus();
×
UNCOV
566
        this.toggleConditionsDropDown(event.currentTarget);
×
567
    }
568

569
    /**
570
     * Opens the conditions dropdown.
571
     */
572
    public toggleConditionsDropDown(target: any) {
UNCOV
573
        this._conditionsOverlaySettings.target = target;
×
UNCOV
574
        this._conditionsOverlaySettings.excludeFromOutsideClick = [target as HTMLElement];
×
UNCOV
575
        this.dropDownConditions.toggle(this._conditionsOverlaySettings);
×
576
    }
577

578
    /**
579
     * Opens the logic operators dropdown.
580
     */
581
    public toggleOperatorsDropDown(eventArgs, index) {
UNCOV
582
        this._operatorsOverlaySettings.target = eventArgs.target.parentElement;
×
UNCOV
583
        this._operatorsOverlaySettings.excludeFromOutsideClick = [eventArgs.target.parentElement as HTMLElement];
×
UNCOV
584
        this.dropDownOperators.toArray()[index].toggle(this._operatorsOverlaySettings);
×
585
    }
586

587
    /**
588
     * Event handler for change event in conditions dropdown.
589
     */
590
    public onConditionsChanged(eventArgs) {
UNCOV
591
        const value = (eventArgs.newSelection as IgxDropDownItemComponent).value;
×
UNCOV
592
        this.expression.condition = this.getCondition(value);
×
UNCOV
593
        if (this.expression.condition.isUnary) {
×
594
            // update grid's filtering on the next cycle to ensure the drop-down is closed
595
            // if the drop-down is not closed this event handler will be invoked multiple times
UNCOV
596
            requestAnimationFrame(() => this.unaryConditionChangedCallback());
×
597
        } else {
UNCOV
598
            requestAnimationFrame(() => this.conditionChangedCallback());
×
599
        }
600

601
        // Add requestAnimationFrame because of an issue in IE, where you are still able to write in the input,
602
        // if it has been focused and then set to readonly.
UNCOV
603
        requestAnimationFrame(() => this.focusEditElement());
×
604
    }
605

606

607
    public onChipPointerdown(args, chip: IgxChipComponent) {
UNCOV
608
        const activeElement = this.column?.grid.document.activeElement;
×
UNCOV
609
        this._cancelChipClick = chip.selected
×
610
            && activeElement && this.editorFocused(activeElement);
611
    }
612

613
    public onChipClick(args, item: ExpressionUI) {
UNCOV
614
        if (this._cancelChipClick) {
×
615
            this._cancelChipClick = false;
×
616
            return;
×
617
        }
618

UNCOV
619
        this.expressionsList.forEach(ex => ex.isSelected = false);
×
620

UNCOV
621
        this.toggleChip(item);
×
622
    }
623

624
    public toggleChip(item: ExpressionUI) {
UNCOV
625
        item.isSelected = !item.isSelected;
×
UNCOV
626
        if (item.isSelected) {
×
UNCOV
627
            this.expression = item.expression;
×
UNCOV
628
            this._value = this.expression.searchVal;
×
UNCOV
629
            this.focusEditElement();
×
630
        }
631
    }
632

633
    /**
634
     * Event handler for chip keydown event.
635
     */
636
    public onChipKeyDown(eventArgs: KeyboardEvent, item: ExpressionUI) {
UNCOV
637
        if (eventArgs.key === this.platform.KEYMAP.ENTER) {
×
UNCOV
638
            eventArgs.preventDefault();
×
639

UNCOV
640
            this.toggleChip(item);
×
641
        }
642
    }
643

644
    /**
645
     * Scrolls the first chip into view if the tab key is pressed on the left arrow.
646
     */
647
    public onLeftArrowKeyDown(event: KeyboardEvent) {
UNCOV
648
        if (event.key === this.platform.KEYMAP.TAB) {
×
UNCOV
649
            this.chipAreaScrollOffset = 0;
×
UNCOV
650
            this.transform(this.chipAreaScrollOffset);
×
651
        }
652
    }
653

654
    /**
655
     * Event handler for chip removed event.
656
     */
657
    public onChipRemoved(eventArgs: IBaseChipEventArgs, item: ExpressionUI) {
UNCOV
658
        const indexToRemove = this.expressionsList.indexOf(item);
×
UNCOV
659
        this.removeExpression(indexToRemove, item.expression);
×
660

UNCOV
661
        this.scrollChipsOnRemove();
×
662
    }
663

664
    /**
665
     * Event handler for logic operator changed event.
666
     */
667
    public onLogicOperatorChanged(eventArgs: ISelectionEventArgs, expression: ExpressionUI) {
UNCOV
668
        if (eventArgs.oldSelection) {
×
UNCOV
669
            expression.afterOperator = (eventArgs.newSelection as IgxDropDownItemComponent).value;
×
UNCOV
670
            this.expressionsList[this.expressionsList.indexOf(expression) + 1].beforeOperator = expression.afterOperator;
×
671

672
            // update grid's filtering on the next cycle to ensure the drop-down is closed
673
            // if the drop-down is not closed this event handler will be invoked multiple times
UNCOV
674
            requestAnimationFrame(() => this.filter());
×
675
        }
676
    }
677

678
    /**
679
     * Scrolls the chips into the chip area when left or right arrows are pressed.
680
     */
681
    public scrollChipsOnArrowPress(arrowPosition: string) {
UNCOV
682
        let count = 0;
×
UNCOV
683
        const chipAraeChildren = this.chipsArea.element.nativeElement.children;
×
UNCOV
684
        const containerRect = this.container.nativeElement.getBoundingClientRect();
×
685

UNCOV
686
        if (arrowPosition === 'right') {
×
UNCOV
687
            for (const chip of chipAraeChildren) {
×
UNCOV
688
                if (Math.ceil(chip.getBoundingClientRect().right) < Math.ceil(containerRect.right)) {
×
UNCOV
689
                    count++;
×
690
                }
691
            }
692

UNCOV
693
            if (count < chipAraeChildren.length) {
×
UNCOV
694
                this.chipAreaScrollOffset -= Math.ceil(chipAraeChildren[count].getBoundingClientRect().right) -
×
695
                    Math.ceil(containerRect.right) + 1;
UNCOV
696
                this.transform(this.chipAreaScrollOffset);
×
697
            }
698
        }
699

UNCOV
700
        if (arrowPosition === 'left') {
×
UNCOV
701
            for (const chip of chipAraeChildren) {
×
UNCOV
702
                if (Math.ceil(chip.getBoundingClientRect().left) < Math.ceil(containerRect.left)) {
×
UNCOV
703
                    count++;
×
704
                }
705
            }
706

UNCOV
707
            if (count > 0) {
×
UNCOV
708
                this.chipAreaScrollOffset += Math.ceil(containerRect.left) -
×
709
                    Math.ceil(chipAraeChildren[count - 1].getBoundingClientRect().left) + 1;
UNCOV
710
                this.transform(this.chipAreaScrollOffset);
×
711
            }
712
        }
713
    }
714

715
    /**
716
     * @hidden
717
     * Resets the chips area
718
     * @memberof IgxGridFilteringRowComponent
719
     */
720
    public resetChipsArea() {
UNCOV
721
        this.chipAreaScrollOffset = 0;
×
UNCOV
722
        this.transform(this.chipAreaScrollOffset);
×
UNCOV
723
        this.showHideArrowButtons();
×
724
    }
725

726
    /** @hidden @internal */
727
    public focusEditElement() {
UNCOV
728
        if (this.input) {
×
UNCOV
729
            this.input.nativeElement.focus();
×
UNCOV
730
        } else if (this.picker) {
×
UNCOV
731
            this.picker.getEditElement().focus();
×
732
        }
733
    }
734

735
    public ngOnDestroy() {
UNCOV
736
        this.$destroyer.next();
×
737
    }
738

739
    private showHideArrowButtons() {
UNCOV
740
        requestAnimationFrame(() => {
×
UNCOV
741
            if (this.filteringService.isFilterRowVisible) {
×
UNCOV
742
                const containerWidth = this.container.nativeElement.getBoundingClientRect().width;
×
UNCOV
743
                this.chipsAreaWidth = this.chipsArea.element.nativeElement.getBoundingClientRect().width;
×
744

UNCOV
745
                this.showArrows = this.chipsAreaWidth >= containerWidth && this.isColumnFiltered;
×
746

747
                // TODO: revise the cdr.detectChanges() usage here
UNCOV
748
                if (!(this.cdr as ViewRef).destroyed) {
×
UNCOV
749
                    this.cdr.detectChanges();
×
750
                }
751
            }
752
        });
753
    }
754

755
    private addExpression(isSelected: boolean) {
UNCOV
756
        const exprUI = new ExpressionUI();
×
UNCOV
757
        exprUI.expression = this.expression;
×
UNCOV
758
        exprUI.beforeOperator = this.expressionsList.length > 0 ? FilteringLogic.And : null;
×
UNCOV
759
        exprUI.isSelected = isSelected;
×
760

UNCOV
761
        this.expressionsList.push(exprUI);
×
762

UNCOV
763
        const length = this.expressionsList.length;
×
UNCOV
764
        if (this.expressionsList[length - 2]) {
×
UNCOV
765
            this.expressionsList[length - 2].afterOperator = this.expressionsList[length - 1].beforeOperator;
×
766
        }
767

UNCOV
768
        this.showHideArrowButtons();
×
769
    }
770

771
    private removeExpression(indexToRemove: number, expression: IFilteringExpression) {
UNCOV
772
        if (indexToRemove === 0 && this.expressionsList.length === 1) {
×
UNCOV
773
            this.clearFiltering();
×
UNCOV
774
            return;
×
775
        }
776

UNCOV
777
        this.filteringService.removeExpression(this.column.field, indexToRemove);
×
778

UNCOV
779
        this.filter();
×
780

UNCOV
781
        if (this.expression === expression) {
×
UNCOV
782
            this.resetExpression();
×
783
        }
784

UNCOV
785
        this.showHideArrowButtons();
×
786
    }
787

788
    private resetExpression(condition?: string) {
UNCOV
789
        this.expression = {
×
790
            fieldName: this.column.field,
791
            condition: null,
792
            conditionName: null,
793
            searchVal: null,
794
            ignoreCase: this.column.filteringIgnoreCase
795
        };
796

UNCOV
797
        if (this.column.dataType !== GridColumnDataType.Boolean) {
×
UNCOV
798
            this.expression.condition = this.getCondition(condition ?? this.conditions[0]);
×
799
        }
800

UNCOV
801
        if (this.column.dataType === GridColumnDataType.Date && this.input) {
×
802
            this.input.nativeElement.value = null;
×
803
        }
804

UNCOV
805
        this.showHideArrowButtons();
×
806
    }
807

808
    private scrollChipsWhenAddingExpression() {
UNCOV
809
        const chipAraeChildren = this.chipsArea.element.nativeElement.children;
×
UNCOV
810
        if (!chipAraeChildren || chipAraeChildren.length === 0) {
×
UNCOV
811
            return;
×
812
        }
813

UNCOV
814
        const chipsContainerWidth = this.container.nativeElement.offsetWidth;
×
UNCOV
815
        const chipsAreaWidth = this.chipsArea.element.nativeElement.offsetWidth;
×
816

UNCOV
817
        if (chipsAreaWidth > chipsContainerWidth) {
×
UNCOV
818
            this.chipAreaScrollOffset = chipsContainerWidth - chipsAreaWidth;
×
UNCOV
819
            this.transform(this.chipAreaScrollOffset);
×
820
        }
821
    }
822

823
    private transform(offset: number) {
UNCOV
824
        requestAnimationFrame(() => {
×
UNCOV
825
            this.chipsArea.element.nativeElement.style.transform = `translate(${offset}px)`;
×
826
        });
827
    }
828

829
    private scrollChipsOnRemove() {
UNCOV
830
        let count = 0;
×
UNCOV
831
        const chipAraeChildren = this.chipsArea.element.nativeElement.children;
×
UNCOV
832
        const containerRect = this.container.nativeElement.getBoundingClientRect();
×
833

UNCOV
834
        for (const chip of chipAraeChildren) {
×
UNCOV
835
            if (Math.ceil(chip.getBoundingClientRect().right) < Math.ceil(containerRect.left)) {
×
836
                count++;
×
837
            }
838
        }
839

UNCOV
840
        if (count <= 2) {
×
UNCOV
841
            this.chipAreaScrollOffset = 0;
×
842
        } else {
843
            const dif = chipAraeChildren[count].id === 'chip' ? count - 2 : count - 1;
×
844
            this.chipAreaScrollOffset += Math.ceil(containerRect.left) - Math.ceil(chipAraeChildren[dif].getBoundingClientRect().left) + 1;
×
845
        }
846

UNCOV
847
        this.transform(this.chipAreaScrollOffset);
×
848
    }
849

850
    private conditionChangedCallback() {
UNCOV
851
        if (!!this.expression.searchVal || this.expression.searchVal === 0) {
×
UNCOV
852
            this.filter();
×
UNCOV
853
        } else if (this.value) {
×
854
            this.value = null;
×
855
        }
856
    }
857

858
    private unaryConditionChangedCallback() {
UNCOV
859
        if (this.value) {
×
UNCOV
860
            this.value = null;
×
861
        }
UNCOV
862
        if (this.expressionsList.find(item => item.expression === this.expression) === undefined) {
×
UNCOV
863
            this.addExpression(true);
×
864
        }
UNCOV
865
        this.filter();
×
866
    }
867

868
    private filter() {
UNCOV
869
        this.filteringService.filterInternal(this.column.field);
×
870
    }
871

872
    private editorFocused(activeElement: Element): boolean {
873
        // if the first check is false and the second is undefined this will return undefined
874
        // make sure it always returns boolean
UNCOV
875
        return !!(this.inputGroup && this.inputGroup.nativeElement.contains(activeElement)
×
876
            || this.picker && this.picker.isFocused);
877
    }
878

879
    private get isColumnFiltered() {
UNCOV
880
        return this.column.filteringExpressionsTree && this.column.filteringExpressionsTree.filteringOperands.length > 0;
×
881
    }
882

883
    public get isNarrowWidth(): boolean {
UNCOV
884
        return this.nativeElement.offsetWidth < this.NARROW_WIDTH_THRESHOLD;
×
885
    }
886
}
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