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

IgniteUI / igniteui-angular / 6653488239

26 Oct 2023 11:33AM UTC coverage: 92.101% (-0.1%) from 92.206%
6653488239

push

github

web-flow
Merge pull request #13451 from IgniteUI/bundle-test-extended

refactor(i18n, util): tree shaking i18n

15273 of 17962 branches covered (0.0%)

45 of 45 new or added lines in 24 files covered. (100.0%)

26410 of 28675 relevant lines covered (92.1%)

30213.01 hits per line

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

89.21
/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts
1
import { AfterViewInit, ContentChild, EventEmitter, LOCALE_ID, Optional, Output, Pipe, PipeTransform } from '@angular/core';
2
import { getLocaleFirstDayOfWeek, NgIf, NgFor, NgTemplateOutlet, NgClass, DatePipe } from '@angular/common';
3
import { Inject } from '@angular/core';
4
import {
5
    Component, Input, ViewChild, ChangeDetectorRef, ViewChildren, QueryList, ElementRef, OnDestroy, HostBinding
6
} from '@angular/core';
7
import { FormsModule } from '@angular/forms';
8
import { Subject } from 'rxjs';
9
import { editor } from '@igniteui/material-icons-extended';
10
import { IButtonGroupEventArgs, IgxButtonGroupComponent } from '../buttonGroup/buttonGroup.component';
11
import { IgxChipComponent } from '../chips/chip.component';
12
import { DisplayDensityBase, DisplayDensityToken, IDisplayDensityOptions } from '../core/density';
13
import { IQueryBuilderResourceStrings, QueryBuilderResourceStringsEN } from '../core/i18n/query-builder-resources';
14
import { PlatformUtil } from '../core/utils';
15
import { DataType, DataUtil } from '../data-operations/data-util';
16
import { IgxBooleanFilteringOperand, IgxDateFilteringOperand, IgxDateTimeFilteringOperand, IgxNumberFilteringOperand, IgxStringFilteringOperand, IgxTimeFilteringOperand } from '../data-operations/filtering-condition';
17
import { FilteringLogic, IFilteringExpression } from '../data-operations/filtering-expression.interface';
18
import { FilteringExpressionsTree, IExpressionTree } from '../data-operations/filtering-expressions-tree';
19
import { IgxDatePickerComponent } from '../date-picker/date-picker.component';
20

21
import { IgxButtonDirective } from '../directives/button/button.directive';
22
import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/date-time-editor.directive';
23

24
import { IgxOverlayOutletDirective, IgxToggleDirective } from '../directives/toggle/toggle.directive';
25
import { FieldType } from '../grids/common/grid.interface';
26
import { IgxIconService } from '../icon/icon.service';
27
import { IgxSelectComponent } from '../select/select.component';
28
import { HorizontalAlignment, OverlaySettings, Point, VerticalAlignment } from '../services/overlay/utilities';
29
import { AbsoluteScrollStrategy, AutoPositionStrategy, CloseScrollStrategy, ConnectedPositioningStrategy } from '../services/public_api';
30
import { IgxTimePickerComponent } from '../time-picker/time-picker.component';
31
import { IgxQueryBuilderHeaderComponent } from './query-builder-header.component';
32
import { IgxPickerToggleComponent, IgxPickerClearComponent } from '../date-common/picker-icons.common';
33
import { IgxInputDirective } from '../directives/input/input.directive';
34
import { IgxInputGroupComponent } from '../input-group/input-group.component';
35
import { IgxSelectItemComponent } from '../select/select-item.component';
36
import { IgxSuffixDirective } from '../directives/suffix/suffix.directive';
37
import { IgxPrefixDirective } from '../directives/prefix/prefix.directive';
2✔
38
import { IgxIconComponent } from '../icon/icon.component';
2✔
39
import { getCurrentResourceStrings } from '../core/i18n/resources';
2✔
40

2✔
41
const DEFAULT_PIPE_DATE_FORMAT = 'mediumDate';
2✔
42
const DEFAULT_PIPE_TIME_FORMAT = 'mediumTime';
2✔
43
const DEFAULT_PIPE_DATE_TIME_FORMAT = 'medium';
2✔
44
const DEFAULT_PIPE_DIGITS_INFO = '1.0-3';
45
const DEFAULT_DATE_TIME_FORMAT = 'dd/MM/yyyy HH:mm:ss tt';
×
46
const DEFAULT_TIME_FORMAT = 'hh:mm:ss tt';
47

48
@Pipe({
2✔
49
    name: 'fieldFormatter',
50
    standalone: true
51
})
52
export class IgxFieldFormatterPipe implements PipeTransform {
53

54
    public transform(value: any, formatter: (v: any, data: any, fieldData?: any) => any, rowData: any, fieldData?: any) {
55
        return formatter(value, rowData, fieldData);
56
    }
57
}
58

59
/**
60
 * @hidden @internal
61
 *
62
 * Internal class usage
367✔
63
 */
64
class ExpressionItem {
65
    public parent: ExpressionGroupItem;
66
    public selected: boolean;
67
    constructor(parent?: ExpressionGroupItem) {
68
        this.parent = parent;
69
    }
70
}
71

72
/**
113✔
73
 * @hidden @internal
113✔
74
 *
113✔
75
 * Internal class usage
76
 */
77
class ExpressionGroupItem extends ExpressionItem {
78
    public operator: FilteringLogic;
79
    public children: ExpressionItem[];
80
    constructor(operator: FilteringLogic, parent?: ExpressionGroupItem) {
81
        super(parent);
82
        this.operator = operator;
83
        this.children = [];
84
    }
254✔
85
}
254✔
86

87
/**
88
 * @hidden @internal
89
 *
90
 * Internal class usage
91
 */
92
class ExpressionOperandItem extends ExpressionItem {
93
    public expression: IFilteringExpression;
94
    public inEditMode: boolean;
95
    public inAddMode: boolean;
96
    public hovered: boolean;
97
    public fieldLabel: string;
98
    constructor(expression: IFilteringExpression, parent: ExpressionGroupItem) {
99
        super(parent);
2✔
100
        this.expression = expression;
101
    }
102
}
103

104
/**
738✔
105
 * A component used for operating with complex filters by creating or editing conditions
106
 * and grouping them using AND/OR logic.
107
 * It is used internally in the Advanced Filtering of the Grid.
480✔
108
 *
480!
109
 * @example
480✔
110
 * ```html
480✔
111
 * <igx-query-builder [fields]="this.fields">
2,844✔
112
 * </igx-query-builder>
2,844✔
113
 * ```
114
 */
115
@Component({
116
    selector: 'igx-query-builder',
117
    templateUrl: './query-builder.component.html',
118
    standalone: true,
119
    imports: [NgIf, IgxQueryBuilderHeaderComponent, IgxButtonDirective, IgxIconComponent, IgxChipComponent, IgxPrefixDirective, IgxSuffixDirective, IgxSelectComponent, FormsModule, NgFor, IgxSelectItemComponent, IgxInputGroupComponent, IgxInputDirective, IgxDatePickerComponent, IgxPickerToggleComponent, IgxPickerClearComponent, IgxTimePickerComponent, IgxDateTimeEditorDirective, NgTemplateOutlet, NgClass, IgxToggleDirective, IgxButtonGroupComponent, IgxOverlayOutletDirective, DatePipe, IgxFieldFormatterPipe]
120
})
130✔
121
export class IgxQueryBuilderComponent extends DisplayDensityBase implements AfterViewInit, OnDestroy {
122
    /**
123
     * @hidden @internal
102✔
124
     */
102✔
125
    @HostBinding('class.igx-query-builder')
126
    public cssClass = 'igx-query-builder';
127

112✔
128
    /**
129
     * @hidden @internal
130
     */
131
    @HostBinding('style.display')
132
    public display = 'block';
133

134
    /**
164✔
135
    * Returns the fields.
136
    */
164✔
137
    public get fields(): FieldType[] {
164✔
138
        return this._fields;
139
    }
140

×
141
    /**
142
     * An @Input property that sets the fields.
143
     */
144
    @Input()
×
145
    public set fields(fields: FieldType[]) {
146
        this._fields = fields;
147

148
        if (this._fields) {
149
            this.registerSVGIcons();
150

15,002✔
151
            this._fields.forEach(field => {
152
                this.setFilters(field);
153
                this.setFormat(field);
237✔
154
            });
155
        }
46✔
156
    }
42✔
157

158
    /**
159
    * Returns the expression tree.
237✔
160
    */
161
     public get expressionTree(): IExpressionTree {
162
        return this._expressionTree;
163
    }
×
164

165
    /**
166
     * An @Input property that sets the expression tree.
237!
167
     */
168
    @Input()
4✔
169
    public set expressionTree(expressionTree: IExpressionTree) {
1✔
170
        this._expressionTree = expressionTree;
171

172
        this.init();
237✔
173
    }
174

175
    /**
176
     * Gets the `locale` of the query builder.
×
177
     * If not set, defaults to application's locale.
178
     */
179
    @Input()
237✔
180
    public get locale(): string {
181
        return this._locale;
76✔
182
    }
47✔
183

184
    /**
185
     * Sets the `locale` of the query builder.
237✔
186
     * Expects a valid BCP 47 language tag.
187
     */
188
    public set locale(value: string) {
189
        this._locale = value;
×
190
        // if value is invalid, set it back to _localeId
191
        try {
192
            getLocaleFirstDayOfWeek(this._locale);
82✔
193
        } catch (e) {
82✔
194
            this._locale = this._localeId;
82✔
195
        }
82✔
196
    }
82✔
197

82✔
198
    /**
82✔
199
     * Sets the resource strings.
82✔
200
     * By default it uses EN resources.
82✔
201
     */
82✔
202
    @Input()
203
    public set resourceStrings(value: IQueryBuilderResourceStrings) {
204
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
205
    }
82✔
206

207
    /**
208
     * Returns the resource strings.
209
     */
82✔
210
    public get resourceStrings(): IQueryBuilderResourceStrings {
211
        return this._resourceStrings;
212
    }
213

214
    /**
215
     * Event fired as the expression tree is changed.
216
     *
217
     * ```html
82✔
218
     *  <igx-query-builder (expressionTreeChange)='onExpressionTreeChange()'></igx-query-builder>
219
     * ```
220
     */
221
    @Output()
222
    public expressionTreeChange = new EventEmitter();
82✔
223

82✔
224
    @ViewChild('fieldSelect', { read: IgxSelectComponent })
82✔
225
    private fieldSelect: IgxSelectComponent;
82✔
226

82✔
227
    @ViewChild('conditionSelect', { read: IgxSelectComponent })
82✔
228
    private conditionSelect: IgxSelectComponent;
229

230
    @ViewChild('searchValueInput', { read: ElementRef })
231
    private searchValueInput: ElementRef;
82✔
232

233
    @ViewChild('picker')
234
    private picker: IgxDatePickerComponent | IgxTimePickerComponent;
235

236
    @ViewChild('addRootAndGroupButton', { read: ElementRef })
237
    private addRootAndGroupButton: ElementRef;
82✔
238

239
    @ViewChild('addConditionButton', { read: ElementRef })
240
    private addConditionButton: ElementRef;
241

242
    /**
243
     * @hidden @internal
82✔
244
     */
82✔
245
    @ContentChild(IgxQueryBuilderHeaderComponent)
82✔
246
    public headerContent: IgxQueryBuilderHeaderComponent;
247

248
    @ViewChild('editingInputsContainer', { read: ElementRef })
249
    protected set editingInputsContainer(value: ElementRef) {
250
        if ((value && !this._editingInputsContainer) ||
251
            (value && this._editingInputsContainer && this._editingInputsContainer.nativeElement !== value.nativeElement)) {
82✔
252
            requestAnimationFrame(() => {
82✔
253
                this.scrollElementIntoView(value.nativeElement);
254
            });
255
        }
256

257
        this._editingInputsContainer = value;
258
    }
79✔
259

79✔
260
    /** @hidden */
78✔
261
    protected get editingInputsContainer(): ElementRef {
78✔
262
        return this._editingInputsContainer;
2✔
263
    }
2✔
264

2✔
265
    @ViewChild('addModeContainer', { read: ElementRef })
266
    protected set addModeContainer(value: ElementRef) {
267
        if ((value && !this._addModeContainer) ||
268
            (value && this._addModeContainer && this._addModeContainer.nativeElement !== value.nativeElement)) {
269
            requestAnimationFrame(() => {
270
                this.scrollElementIntoView(value.nativeElement);
271
            });
272
        }
11,608✔
273

274
        this._addModeContainer = value;
275
    }
276

277
    /** @hidden */
278
    protected get addModeContainer(): ElementRef {
279
        return this._addModeContainer;
280
    }
82✔
281

282
    @ViewChild('currentGroupButtonsContainer', { read: ElementRef })
283
    protected set currentGroupButtonsContainer(value: ElementRef) {
284
        if ((value && !this._currentGroupButtonsContainer) ||
285
            (value && this._currentGroupButtonsContainer && this._currentGroupButtonsContainer.nativeElement !== value.nativeElement)) {
286
            requestAnimationFrame(() => {
287
                this.scrollElementIntoView(value.nativeElement);
288
            });
3✔
289
        }
290

291
        this._currentGroupButtonsContainer = value;
292
    }
293

294
    /** @hidden */
2,428✔
295
    protected get currentGroupButtonsContainer(): ElementRef {
296
        return this._currentGroupButtonsContainer;
297
    }
298

299
    @ViewChild(IgxToggleDirective)
300
    private contextMenuToggle: IgxToggleDirective;
41✔
301

41✔
302
    @ViewChildren(IgxChipComponent)
303
    private chips: QueryList<IgxChipComponent>;
304

305
    @ViewChild('expressionsContainer')
306
    private expressionsContainer: ElementRef;
307

41✔
308
    @ViewChild('overlayOutlet', { read: IgxOverlayOutletDirective, static: true })
1✔
309
    private overlayOutlet: IgxOverlayOutletDirective;
1✔
310

311
    /**
312
     * @hidden @internal
40✔
313
     */
314
    public rootGroup: ExpressionGroupItem;
41✔
315

316
    /**
317
     * @hidden @internal
318
     */
319
    public selectedExpressions: ExpressionOperandItem[] = [];
320

30✔
321
    /**
322
     * @hidden @internal
323
     */
324
    public currentGroup: ExpressionGroupItem;
325

326
    /**
4✔
327
     * @hidden @internal
328
     */
329
    public contextualGroup: ExpressionGroupItem;
330

331
    /**
332
     * @hidden @internal
1✔
333
     */
334
    public filteringLogics;
335

336
    /**
337
     * @hidden @internal
338
     */
33✔
339
    public selectedCondition: string;
28✔
340

28✔
341
    /**
28✔
342
     * @hidden @internal
28!
343
     */
344
    public searchValue: any;
28✔
345

346
    /**
347
     * @hidden @internal
28✔
348
     */
28✔
349
    public pickerOutlet: IgxOverlayOutletDirective | ElementRef;
350

33✔
351
    /**
33✔
352
     * @hidden @internal
353
     */
354
    public fieldSelectOverlaySettings: OverlaySettings = {
355
        scrollStrategy: new AbsoluteScrollStrategy(),
356
        modal: false,
357
        closeOnOutsideClick: false
224✔
358
    };
4✔
359

4✔
360
    /**
361
     * @hidden @internal
362
     */
363
    public conditionSelectOverlaySettings: OverlaySettings = {
364
        scrollStrategy: new AbsoluteScrollStrategy(),
365
        modal: false,
366
        closeOnOutsideClick: false
109✔
367
    };
7✔
368

7✔
369
    private destroy$ = new Subject<any>();
6✔
370
    private _selectedField: FieldType;
371
    private _clickTimer;
7✔
372
    private _dblClickDelay = 200;
373
    private _preventChipClick = false;
374
    private _editingInputsContainer: ElementRef;
375
    private _addModeContainer: ElementRef;
376
    private _currentGroupButtonsContainer: ElementRef;
377
    private _addModeExpression: ExpressionOperandItem;
378
    private _editedExpression: ExpressionOperandItem;
517✔
379
    private _selectedGroups: ExpressionGroupItem[] = [];
380
    private _fields: FieldType[];
381
    private _expressionTree: IExpressionTree;
382
    private _locale;
383
    private _resourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN);
384

385
    private _positionSettings = {
386
        horizontalStartPoint: HorizontalAlignment.Right,
387
        verticalStartPoint: VerticalAlignment.Top
120✔
388
    };
115✔
389

390
    private _overlaySettings: OverlaySettings = {
5✔
391
        closeOnOutsideClick: false,
1✔
392
        modal: false,
393
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings),
394
        scrollStrategy: new CloseScrollStrategy()
4✔
395
    };
396

397
    constructor(public cdr: ChangeDetectorRef,
398
        protected iconService: IgxIconService,
399
        protected platform: PlatformUtil,
400
        protected el: ElementRef,
401
        @Inject(LOCALE_ID) protected _localeId: string,
2,558✔
402
        @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions?: IDisplayDensityOptions) {
403
        super(_displayDensityOptions, el);
404
        this.locale = this.locale || this._localeId;
405
    }
406

407
    /**
3✔
408
     * @hidden @internal
409
     */
410
    public ngAfterViewInit(): void {
411
        this._overlaySettings.outlet = this.overlayOutlet;
412
        this.fieldSelectOverlaySettings.outlet = this.overlayOutlet;
413
        this.conditionSelectOverlaySettings.outlet = this.overlayOutlet;
24✔
414
    }
24!
415

24✔
416
    /**
417
     * @hidden @internal
24✔
418
     */
419
    public ngOnDestroy(): void {
420
        this.destroy$.next(true);
421
        this.destroy$.complete();
422
    }
423

424
    /**
4✔
425
     * @hidden @internal
4✔
426
     */
4✔
427
    public set selectedField(value: FieldType) {
428
        const oldValue = this._selectedField;
429

430
        if (this._selectedField !== value) {
431
            this._selectedField = value;
432
            if (oldValue && this._selectedField && this._selectedField.dataType !== oldValue.dataType) {
46✔
433
                this.selectedCondition = null;
46✔
434
                this.searchValue = null;
46✔
435
                this.cdr.detectChanges();
46!
436
            }
×
437
        }
438
    }
46✔
439

46✔
440
    /**
12✔
441
     * @hidden @internal
46✔
442
     */
443
    public get selectedField(): FieldType {
46✔
444
        return this._selectedField;
46✔
445
    }
46✔
446

46✔
447
    /**
46✔
448
     * @hidden @internal
46✔
449
     *
46✔
450
     * used by the grid
46✔
451
     */
46✔
452
    public setPickerOutlet(outlet?: IgxOverlayOutletDirective | ElementRef) {
46✔
453
        this.pickerOutlet = outlet;
46✔
454
    }
41✔
455

456
    /**
5!
457
     * @hidden @internal
×
458
     *
459
     * used by the grid
460
     */
5!
461
    public get isContextMenuVisible(): boolean {
5✔
462
        return !this.contextMenuToggle.collapsed;
463
    }
464

465
    /**
466
     * @hidden @internal
467
     */
468
    public get hasEditedExpression(): boolean {
163✔
469
        return this._editedExpression !== undefined && this._editedExpression !== null;
5✔
470
    }
471

163✔
472
    /**
163✔
473
     * @hidden @internal
22✔
474
     */
475
    public addCondition(parent: ExpressionGroupItem, afterExpression?: ExpressionItem) {
163✔
476
        this.cancelOperandAdd();
163✔
477

478
        const operandItem = new ExpressionOperandItem({
479
            fieldName: null,
480
            condition: null,
481
            ignoreCase: true,
482
            searchVal: null
5✔
483
        }, parent);
5✔
484

5!
485
        if (afterExpression) {
×
486
            const index = parent.children.indexOf(afterExpression);
487
            parent.children.splice(index + 1, 0, operandItem);
5✔
488
        } else {
5✔
489
            parent.children.push(operandItem);
5!
490
        }
×
491

492
        this.enterExpressionEdit(operandItem);
493
    }
494

495
    /**
496
     * @hidden @internal
497
     */
11✔
498
    public addAndGroup(parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
499
        this.addGroup(FilteringLogic.And, parent, afterExpression);
500
    }
501

502
    /**
503
     * @hidden @internal
1✔
504
     */
1✔
505
    public addOrGroup(parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
1!
506
        this.addGroup(FilteringLogic.Or, parent, afterExpression);
1✔
507
    }
508

509
    /**
510
     * @hidden @internal
511
     */
512
    public endGroup(groupItem: ExpressionGroupItem) {
513
        this.currentGroup = groupItem.parent;
1✔
514
    }
515

516
    /**
517
     * @hidden @internal
518
     */
519
    public commitOperandEdit() {
2✔
520
        if (this._editedExpression) {
521
            this._editedExpression.expression.fieldName = this.selectedField.field;
522
            this._editedExpression.expression.condition = this.selectedField.filters.condition(this.selectedCondition);
523
            this._editedExpression.expression.searchVal = DataUtil.parseValue(this.selectedField.dataType, this.searchValue);
524
            this._editedExpression.fieldLabel = this.selectedField.label
525
                ? this.selectedField.label
1✔
526
                : this.selectedField.header
2✔
527
                    ? this.selectedField.header
528
                    : this.selectedField.field;
1✔
529
            this._editedExpression.inEditMode = false;
530
            this._editedExpression = null;
531
        }
532

533
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup);
534
        this.expressionTreeChange.emit();
17✔
535
    }
536

537
    /**
538
     * @hidden @internal
539
     */
540
    public cancelOperandAdd() {
1✔
541
        if (this._addModeExpression) {
1✔
542
            this._addModeExpression.inAddMode = false;
1!
543
            this._addModeExpression = null;
1✔
544
        }
1✔
545
    }
1✔
546

2✔
547
    /**
548
     * @hidden @internal
549
     */
1✔
550
    public cancelOperandEdit() {
1✔
551
        if (this._editedExpression) {
552
            this._editedExpression.inEditMode = false;
553

554
            if (!this._editedExpression.expression.fieldName) {
555
                this.deleteItem(this._editedExpression);
556
            }
1✔
557

1✔
558
            this._editedExpression = null;
1!
559
        }
1✔
560
    }
1✔
561

562
    /**
563
     * @hidden @internal
×
564
     */
565
    public operandCanBeCommitted(): boolean {
1✔
566
        return this.selectedField && this.selectedCondition &&
1✔
567
            (!!this.searchValue || this.selectedField.filters.condition(this.selectedCondition).isUnary);
568
    }
569

570
    /**
571
     * @hidden @internal
572
     *
3✔
573
     * used by the grid
3✔
574
     */
575
    public exitOperandEdit() {
576
        if (!this._editedExpression) {
577
            return;
578
        }
579

7,587!
580
        if (this.operandCanBeCommitted()) {
581
            this.commitOperandEdit();
582
        } else {
583
            this.cancelOperandEdit();
584
        }
585
    }
1,739✔
586

587
    /**
588
     * @hidden @internal
589
     */
590
    public isExpressionGroup(expression: ExpressionItem): boolean {
591
        return expression instanceof ExpressionGroupItem;
×
592
    }
×
593

×
594
    /**
595
     * @hidden @internal
596
     */
597
    public onChipRemove(expressionItem: ExpressionItem) {
598
        this.deleteItem(expressionItem);
599
    }
600

4!
601
    /**
4✔
602
     * @hidden @internal
4✔
603
     */
604
    public onChipClick(expressionItem: ExpressionOperandItem) {
605
        this._clickTimer = setTimeout(() => {
606
            if (!this._preventChipClick) {
607
                this.onToggleExpression(expressionItem);
608
            }
609
            this._preventChipClick = false;
×
610
        }, this._dblClickDelay);
×
611
    }
×
612

613
    /**
614
     * @hidden @internal
615
     */
616
    public onChipDblClick(expressionItem: ExpressionOperandItem) {
617
        clearTimeout(this._clickTimer);
618
        this._preventChipClick = true;
619
        this.enterExpressionEdit(expressionItem);
×
620
    }
621

622
    /**
623
     * @hidden @internal
624
     */
625
    public enterExpressionEdit(expressionItem: ExpressionOperandItem) {
512✔
626
        this.clearSelection();
627
        this.exitOperandEdit();
628
        this.cancelOperandAdd();
629

630
        if (this._editedExpression) {
631
            this._editedExpression.inEditMode = false;
20✔
632
        }
633

634
        expressionItem.hovered = false;
635

636
        this.selectedField = expressionItem.expression.fieldName ?
637
            this.fields.find(field => field.field === expressionItem.expression.fieldName) : null;
20✔
638
        this.selectedCondition = expressionItem.expression.condition ?
639
            expressionItem.expression.condition.name : null;
640
        this.searchValue = expressionItem.expression.searchVal;
641

642
        expressionItem.inEditMode = true;
643
        this._editedExpression = expressionItem;
644

645
        this.cdr.detectChanges();
75✔
646

8✔
647
        this.fieldSelectOverlaySettings.target = this.fieldSelect.element;
648
        this.fieldSelectOverlaySettings.excludeFromOutsideClick = [this.fieldSelect.element as HTMLElement];
67✔
649
        this.fieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
41✔
650
        this.conditionSelectOverlaySettings.target = this.conditionSelect.element;
651
        this.conditionSelectOverlaySettings.excludeFromOutsideClick = [this.conditionSelect.element as HTMLElement];
652
        this.conditionSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
653

654
        if (!this.selectedField) {
655
            this.fieldSelect.input.nativeElement.focus();
656
            } else if (this.selectedField.filters.condition(this.selectedCondition).isUnary) {
4,188✔
657
                this.conditionSelect.input.nativeElement.focus();
658
        } else {
659
            const input = this.searchValueInput?.nativeElement || this.picker?.getEditElement();
660
            input.focus();
661
        }
662
    }
663

664
    /**
665
     * @hidden @internal
66✔
666
     */
66✔
667
    public clearSelection() {
34✔
668
        for (const group of this._selectedGroups) {
34✔
669
            group.selected = false;
34✔
670
        }
15✔
671
        this._selectedGroups = [];
672

673
        for (const expr of this.selectedExpressions) {
19✔
674
            expr.selected = false;
675
        }
676
        this.selectedExpressions = [];
677

678
        this.toggleContextMenu();
2,844!
679
    }
×
680

681
    /**
2,844!
682
     * @hidden @internal
×
683
     */
×
684
    public enterExpressionAdd(expressionItem: ExpressionOperandItem) {
685
        this.clearSelection();
686
        this.exitOperandEdit();
2,844!
687

×
688
        if (this._addModeExpression) {
689
            this._addModeExpression.inAddMode = false;
2,844!
690
        }
×
691

692
        expressionItem.inAddMode = true;
693
        this._addModeExpression = expressionItem;
694
        if (expressionItem.selected) {
2,844!
695
            this.toggleExpression(expressionItem);
×
696
        }
697
    }
×
698

×
699
    /**
700
     * @hidden @internal
701
     */
702
    public contextMenuClosed() {
×
703
        this.contextualGroup = null;
×
704
    }
705

×
706
    /**
×
707
     * @hidden @internal
708
     */
×
709
    public onKeyDown(eventArgs: KeyboardEvent) {
×
710
        eventArgs.stopPropagation();
711
        const key = eventArgs.key;
×
712
        if (!this.contextMenuToggle.collapsed && (key === this.platform.KEYMAP.ESCAPE)) {
×
713
            this.clearSelection();
714
        }
715
    }
×
716

×
717
    /**
718
     * @hidden @internal
719
     */
720
    public createAndGroup() {
721
        this.createGroup(FilteringLogic.And);
24✔
722
    }
24✔
723

24✔
724
    /**
725
     * @hidden @internal
726
     */
66✔
727
    public createOrGroup() {
66✔
728
        this.createGroup(FilteringLogic.Or);
49✔
729
    }
730

731
    /**
17✔
732
     * @hidden @internal
17✔
733
     */
17✔
734
    public deleteFilters() {
735
        for (const expr of this.selectedExpressions) {
736
            this.deleteItem(expr);
737
        }
34✔
738

34✔
739
        this.clearSelection();
34✔
740
    }
3✔
741

1✔
742
    /**
1✔
743
     * @hidden @internal
744
     */
745
    public onGroupClick(groupItem: ExpressionGroupItem) {
2✔
746
        this.toggleGroup(groupItem);
747
    }
748

749
    /**
31✔
750
     * @hidden @internal
751
     */
34✔
752
    public ungroup() {
34✔
753
        const selectedGroup = this.contextualGroup;
754
        const parent = selectedGroup.parent;
755
        if (parent) {
756
            const index = parent.children.indexOf(selectedGroup);
127✔
757
            parent.children.splice(index, 1, ...selectedGroup.children);
76✔
758

76✔
759
            for (const expr of selectedGroup.children) {
238✔
760
                expr.parent = parent;
25✔
761
            }
762
        }
763

213✔
764
        this.clearSelection();
213✔
765
        this.commitOperandEdit();
766
    }
767

768
    /**
769
     * @hidden @internal
770
     */
213✔
771
    public deleteGroup() {
569✔
772
        const selectedGroup = this.contextualGroup;
213✔
773
        const parent = selectedGroup.parent;
213✔
774
        if (parent) {
775
            const index = parent.children.indexOf(selectedGroup);
776
            parent.children.splice(index, 1);
777
        } else {
127✔
778
            this.rootGroup = null;
779
        }
780

76!
781
        this.clearSelection();
×
782
        this.commitOperandEdit();
783
    }
76✔
784

76✔
785
    /**
106✔
786
     * @hidden @internal
26✔
787
     */
26✔
788
    public selectFilteringLogic(event: IButtonGroupEventArgs) {
789
        this.contextualGroup.operator = event.index as FilteringLogic;
790
        this.commitOperandEdit();
80✔
791
    }
792

793
    /**
76✔
794
     * @hidden @internal
795
     */
796
    public getConditionFriendlyName(name: string): string {
203✔
797
        return this.resourceStrings[`igx_query_builder_filter_${name}`] || name;
203✔
798
    }
20✔
799

20✔
800
    /**
12✔
801
     * @hidden @internal
802
     */
803
    public isDate(value: any) {
804
        return value instanceof Date;
805
    }
806

807
    /**
808
     * @hidden @internal
809
     */
810
    public onExpressionsScrolled() {
811
        if (!this.contextMenuToggle.collapsed) {
812
            this.calculateContextMenuTarget();
183✔
813
            this.contextMenuToggle.reposition();
101✔
814
        }
815
    }
816

817
    /**
269✔
818
     * @hidden @internal
108✔
819
     */
40!
820
    public invokeClick(eventArgs: KeyboardEvent) {
40✔
821
        if (this.platform.isActivationKey(eventArgs)) {
822
            eventArgs.preventDefault();
823
            (eventArgs.currentTarget as HTMLElement).click();
229✔
824
        }
825
    }
826

162!
827
    /**
×
828
     * @hidden @internal
829
     */
162✔
830
    public openPicker(args: KeyboardEvent) {
108✔
831
        if (this.platform.isActivationKey(args)) {
832
            args.preventDefault();
54✔
833
            this.picker.open();
834
        }
835
    }
23✔
836

6✔
837
    /**
6✔
838
     * @hidden @internal
6✔
839
     */
6✔
840
    public onOutletPointerDown(event) {
841
        // This prevents closing the select's dropdown when clicking the scroll
17!
842
        event.preventDefault();
×
843
    }
844

17✔
845
    /**
17✔
846
     * @hidden @internal
17✔
847
     */
17✔
848
    public getConditionList(): string[] {
17✔
849
        return this.selectedField ? this.selectedField.filters.conditionList() : [];
6✔
850
    }
851

17✔
852
    /**
853
     * @hidden @internal
854
     */
3✔
855
    public getFormatter(field: string) {
11✔
856
        return this.fields.find(el => el.field === field).formatter;
3✔
857
    }
3✔
858

3✔
859
    /**
3✔
860
     * @hidden @internal
3✔
861
     */
3✔
862
    public getFormat(field: string) {
6✔
863
        return this.fields.find(el => el.field === field).pipeArgs.format;
6✔
864
    }
6✔
865

866
    /**
3✔
867
     * @hidden @internal
868
     *
869
     * used by the grid
17✔
870
     */
17✔
871
    public setAddButtonFocus() {
16✔
872
        if (this.addRootAndGroupButton) {
16✔
873
            this.addRootAndGroupButton.nativeElement.focus();
4✔
874
        } else if (this.addConditionButton) {
875
            this.addConditionButton.nativeElement.focus();
16✔
876
        }
877
    }
878

879
    /**
24!
880
     * @hidden @internal
24✔
881
     */
24✔
882
    public context(expression: ExpressionItem, afterExpression?: ExpressionItem) {
16✔
883
        return {
884
            $implicit: expression,
885
            afterExpression
8✔
886
        };
8✔
887
    }
888

889
    /**
24✔
890
     * @hidden @internal
50✔
891
     */
8✔
892
    public onChipSelectionEnd() {
893
        const contextualGroup = this.findSingleSelectedGroup();
894
        if (contextualGroup || this.selectedExpressions.length > 1) {
42✔
895
            this.contextualGroup = contextualGroup;
42!
896
            this.calculateContextMenuTarget();
42✔
897
            if (this.contextMenuToggle.collapsed) {
898
                this.contextMenuToggle.open(this._overlaySettings);
899
            } else {
900
                this.contextMenuToggle.reposition();
901
            }
902
        }
56✔
903
    }
56✔
904

35!
905
    private setFormat(field: FieldType) {
×
906
        if (!field.pipeArgs) {
×
907
            field.pipeArgs = { digitsInfo: DEFAULT_PIPE_DIGITS_INFO };
×
908
        }
909

35✔
910
        if (!field.pipeArgs.format) {
911
            field.pipeArgs.format = field.dataType === DataType.Time ?
912
                DEFAULT_PIPE_TIME_FORMAT : field.dataType === DataType.DateTime ?
913
                    DEFAULT_PIPE_DATE_TIME_FORMAT : DEFAULT_PIPE_DATE_FORMAT;
34✔
914
        }
144✔
915

92✔
916
        if (!field.defaultDateTimeFormat) {
34✔
917
            field.defaultDateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
34✔
918
        }
92✔
919

34✔
920
        if (!field.defaultTimeFormat) {
34✔
921
            field.defaultTimeFormat = DEFAULT_TIME_FORMAT;
34✔
922
        }
923
    }
924

90✔
925
    private setFilters(field: FieldType) {
90✔
926
        if (!field.filters) {
90✔
927
            switch (field.dataType) {
90✔
928
                case DataType.Boolean:
2✔
929
                    field.filters = IgxBooleanFilteringOperand.instance();
930
                    break;
88✔
931
                case DataType.Number:
3✔
932
                case DataType.Currency:
933
                case DataType.Percent:
934
                    field.filters = IgxNumberFilteringOperand.instance();
935
                    break;
102✔
936
                case DataType.Date:
102✔
937
                    field.filters = IgxDateFilteringOperand.instance();
102✔
938
                    break;
102✔
939
                case DataType.Time:
102✔
940
                    field.filters = IgxTimeFilteringOperand.instance();
941
                    break;
942
                case DataType.DateTime:
480✔
943
                    field.filters = IgxDateTimeFilteringOperand.instance();
28,800✔
944
                    break;
945
                case DataType.String:
2✔
946
                default:
947
                    field.filters = IgxStringFilteringOperand.instance();
948
                    break;
949
            }
950

951
        }
952
    }
953

2✔
954
    private onToggleExpression(expressionItem: ExpressionOperandItem) {
955
        this.exitOperandEdit();
956
        this.toggleExpression(expressionItem);
957

958
        this.toggleContextMenu();
959
    }
960

961
    private toggleExpression(expressionItem: ExpressionOperandItem) {
962
        expressionItem.selected = !expressionItem.selected;
963

964
        if (expressionItem.selected) {
965
            this.selectedExpressions.push(expressionItem);
966
        } else {
967
            const index = this.selectedExpressions.indexOf(expressionItem);
968
            this.selectedExpressions.splice(index, 1);
969
            this.deselectParentRecursive(expressionItem);
970
        }
971
    }
972

973
    private addGroup(operator: FilteringLogic, parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
974
        this.cancelOperandAdd();
975

976
        const groupItem = new ExpressionGroupItem(operator, parent);
977

2✔
978
        if (parent) {
979
            if (afterExpression) {
980
                const index = parent.children.indexOf(afterExpression);
981
                parent.children.splice(index + 1, 0, groupItem);
982
            } else {
983
                parent.children.push(groupItem);
984
            }
985
        } else {
986
            this.rootGroup = groupItem;
987
        }
988

989
        this.addCondition(groupItem);
990
        this.currentGroup = groupItem;
991
    }
992

993
    private createExpressionGroupItem(expressionTree: IExpressionTree, parent?: ExpressionGroupItem): ExpressionGroupItem {
994
        let groupItem: ExpressionGroupItem;
995
        if (expressionTree) {
996
            groupItem = new ExpressionGroupItem(expressionTree.operator, parent);
997

998
            for (const expr of expressionTree.filteringOperands) {
999
                if (expr instanceof FilteringExpressionsTree) {
1000
                    groupItem.children.push(this.createExpressionGroupItem(expr, groupItem));
1001
                } else {
1002
                    const filteringExpr = expr as IFilteringExpression;
1003
                    const exprCopy: IFilteringExpression = {
1004
                        fieldName: filteringExpr.fieldName,
1005
                        condition: filteringExpr.condition,
1006
                        searchVal: filteringExpr.searchVal,
1007
                        ignoreCase: filteringExpr.ignoreCase
1008
                    };
1009
                    const operandItem = new ExpressionOperandItem(exprCopy, groupItem);
1010
                    const field = this.fields.find(el => el.field === filteringExpr.fieldName);
1011
                    operandItem.fieldLabel = field.label || field.header || field.field;
1012
                    groupItem.children.push(operandItem);
1013
                }
1014
            }
1015
        }
1016

1017
        return groupItem;
1018
    }
1019

1020
    private createExpressionTreeFromGroupItem(groupItem: ExpressionGroupItem): FilteringExpressionsTree {
1021
        if (!groupItem) {
1022
            return null;
1023
        }
1024

1025
        const expressionTree = new FilteringExpressionsTree(groupItem.operator);
1026

1027
        for (const item of groupItem.children) {
1028
            if (item instanceof ExpressionGroupItem) {
1029
                const subTree = this.createExpressionTreeFromGroupItem((item as ExpressionGroupItem));
1030
                expressionTree.filteringOperands.push(subTree);
1031
            } else {
1032
                expressionTree.filteringOperands.push((item as ExpressionOperandItem).expression);
1033
            }
1034
        }
1035

1036
        return expressionTree;
1037
    }
1038

1039
    private toggleContextMenu() {
1040
        const contextualGroup = this.findSingleSelectedGroup();
1041

1042
        if (contextualGroup || this.selectedExpressions.length > 1) {
1043
            this.contextualGroup = contextualGroup;
1044

1045
            if (contextualGroup) {
1046
                this.filteringLogics = [
1047
                    {
1048
                        label: this.resourceStrings.igx_query_builder_filter_operator_and,
1049
                        selected: contextualGroup.operator === FilteringLogic.And
1050
                    },
1051
                    {
1052
                        label: this.resourceStrings.igx_query_builder_filter_operator_or,
1053
                        selected: contextualGroup.operator === FilteringLogic.Or
1054
                    }
1055
                ];
1056
            }
1057
        } else if (this.contextMenuToggle) {
1058
            this.contextMenuToggle.close();
1059
        }
1060
    }
1061

1062
    private findSingleSelectedGroup(): ExpressionGroupItem {
1063
        for (const group of this._selectedGroups) {
1064
            const containsAllSelectedExpressions = this.selectedExpressions.every(op => this.isInsideGroup(op, group));
1065

1066
            if (containsAllSelectedExpressions) {
1067
                return group;
1068
            }
1069
        }
1070

1071
        return null;
1072
    }
1073

1074
    private isInsideGroup(item: ExpressionItem, group: ExpressionGroupItem): boolean {
1075
        if (!item) {
1076
            return false;
1077
        }
1078

1079
        if (item.parent === group) {
1080
            return true;
1081
        }
1082

1083
        return this.isInsideGroup(item.parent, group);
1084
    }
1085

1086
    private deleteItem(expressionItem: ExpressionItem) {
1087
        if (!expressionItem.parent) {
1088
            this.rootGroup = null;
1089
            this.currentGroup = null;
1090
            this._expressionTree = null;
1091
            return;
1092
        }
1093

1094
        if (expressionItem === this.currentGroup) {
1095
            this.currentGroup = this.currentGroup.parent;
1096
        }
1097

1098
        const children = expressionItem.parent.children;
1099
        const index = children.indexOf(expressionItem);
1100
        children.splice(index, 1);
1101
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup);
1102

1103
        if (!children.length) {
1104
            this.deleteItem(expressionItem.parent);
1105
        }
1106

1107
        this.expressionTreeChange.emit();
1108
    }
1109

1110
    private createGroup(operator: FilteringLogic) {
1111
        const chips = this.chips.toArray();
1112
        const minIndex = this.selectedExpressions.reduce((i, e) => Math.min(i, chips.findIndex(c => c.data === e)), Number.MAX_VALUE);
1113
        const firstExpression = chips[minIndex].data;
1114

1115
        const parent = firstExpression.parent;
1116
        const groupItem = new ExpressionGroupItem(operator, parent);
1117

1118
        const index = parent.children.indexOf(firstExpression);
1119
        parent.children.splice(index, 0, groupItem);
1120

1121
        for (const expr of this.selectedExpressions) {
1122
            groupItem.children.push(expr);
1123
            this.deleteItem(expr);
1124
            expr.parent = groupItem;
1125
        }
1126

1127
        this.clearSelection();
1128
    }
1129

1130
    private toggleGroup(groupItem: ExpressionGroupItem) {
1131
        this.exitOperandEdit();
1132
        if (groupItem.children && groupItem.children.length) {
1133
            this.toggleGroupRecursive(groupItem, !groupItem.selected);
1134
            if (!groupItem.selected) {
1135
                this.deselectParentRecursive(groupItem);
1136
            }
1137
            this.toggleContextMenu();
1138
        }
1139
    }
1140

1141
    private toggleGroupRecursive(groupItem: ExpressionGroupItem, selected: boolean) {
1142
        if (groupItem.selected !== selected) {
1143
            groupItem.selected = selected;
1144

1145
            if (groupItem.selected) {
1146
                this._selectedGroups.push(groupItem);
1147
            } else {
1148
                const index = this._selectedGroups.indexOf(groupItem);
1149
                this._selectedGroups.splice(index, 1);
1150
            }
1151
        }
1152

1153
        for (const expr of groupItem.children) {
1154
            if (expr instanceof ExpressionGroupItem) {
1155
                this.toggleGroupRecursive(expr, selected);
1156
            } else {
1157
                const operandExpression = expr as ExpressionOperandItem;
1158
                if (operandExpression.selected !== selected) {
1159
                    this.toggleExpression(operandExpression);
1160
                }
1161
            }
1162
        }
1163
    }
1164

1165
    private deselectParentRecursive(expressionItem: ExpressionItem) {
1166
        const parent = expressionItem.parent;
1167
        if (parent) {
1168
            if (parent.selected) {
1169
                parent.selected = false;
1170
                const index = this._selectedGroups.indexOf(parent);
1171
                this._selectedGroups.splice(index, 1);
1172
            }
1173
            this.deselectParentRecursive(parent);
1174
        }
1175
    }
1176

1177
    private calculateContextMenuTarget() {
1178
        const containerRect = this.expressionsContainer.nativeElement.getBoundingClientRect();
1179
        const chips = this.chips.filter(c => this.selectedExpressions.indexOf(c.data) !== -1);
1180
        let minTop = chips.reduce((t, c) =>
1181
            Math.min(t, c.nativeElement.getBoundingClientRect().top), Number.MAX_VALUE);
1182
        minTop = Math.max(containerRect.top, minTop);
1183
        minTop = Math.min(containerRect.bottom, minTop);
1184
        let maxRight = chips.reduce((r, c) =>
1185
            Math.max(r, c.nativeElement.getBoundingClientRect().right), 0);
1186
        maxRight = Math.max(maxRight, containerRect.left);
1187
        maxRight = Math.min(maxRight, containerRect.right);
1188
        this._overlaySettings.target = new Point(maxRight, minTop);
1189
    }
1190

1191
    private scrollElementIntoView(target: HTMLElement) {
1192
        const container = this.expressionsContainer.nativeElement;
1193
        const targetOffset = target.offsetTop - container.offsetTop;
1194
        const delta = 10;
1195

1196
        if (container.scrollTop + delta > targetOffset) {
1197
            container.scrollTop = targetOffset - delta;
1198
        } else if (container.scrollTop + container.clientHeight < targetOffset + target.offsetHeight + delta) {
1199
            container.scrollTop = targetOffset + target.offsetHeight + delta - container.clientHeight;
1200
        }
1201
    }
1202

1203
    private init() {
1204
        this.clearSelection();
1205
        this.cancelOperandAdd();
1206
        this.cancelOperandEdit();
1207
        this.rootGroup = this.createExpressionGroupItem(this.expressionTree);
1208
        this.currentGroup = this.rootGroup;
1209
    }
1210

1211
    private registerSVGIcons(): void {
1212
        const editorIcons = editor as any[];
1213
        editorIcons.forEach(icon => this.iconService.addSvgIconFromText(icon.name, icon.value, 'imx-icons'));
1214
    }
1215
}
1216

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

© 2026 Coveralls, Inc