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

IgniteUI / igniteui-angular / 24038554872

06 Apr 2026 03:43PM UTC coverage: 90.138% (-0.03%) from 90.172%
24038554872

Pull #17160

github

web-flow
Merge df2ca1a9f into b96824570
Pull Request #17160: Do not use outlet internally

14817 of 17262 branches covered (85.84%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 2 files covered. (100.0%)

8 existing lines in 4 files now uncovered.

29867 of 32311 relevant lines covered (92.44%)

34454.62 hits per line

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

89.2
/projects/igniteui-angular/query-builder/src/query-builder/query-builder-tree.component.ts
1
import { AfterViewInit, EventEmitter, LOCALE_ID, Output, TemplateRef, inject } from '@angular/core';
2
import { NgTemplateOutlet, NgClass } from '@angular/common';
3

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 { IgxChipComponent } from 'igniteui-angular/chips';
10
import {
11
    IQueryBuilderResourceStrings,
12
    QueryBuilderResourceStringsEN,
13
    PlatformUtil,
14
    trackByIdentity,
15
    GridColumnDataType,
16
    DataUtil,
17
    IgxBooleanFilteringOperand,
18
    IgxDateFilteringOperand,
19
    IgxDateTimeFilteringOperand,
20
    IgxNumberFilteringOperand,
21
    IgxStringFilteringOperand,
22
    IgxTimeFilteringOperand,
23
    FilteringLogic,
24
    IFilteringExpression,
25
    FilteringExpressionsTree,
26
    IExpressionTree,
27
    IFilteringExpressionsTree,
28
    FieldType,
29
    EntityType,
30
    HorizontalAlignment,
31
    OverlaySettings,
32
    VerticalAlignment,
33
    AbsoluteScrollStrategy,
34
    AutoPositionStrategy,
35
    ConnectedPositioningStrategy,
36
    IgxPickerToggleComponent,
37
    IgxPickerClearComponent,
38
    getCurrentResourceStrings,
39
    DEFAULT_LOCALE,
40
    onResourceChangeHandle,
41
    IgxDateFormatterPipe,
42
    isTree
43
} from 'igniteui-angular/core';
44
import { IgxDatePickerComponent } from 'igniteui-angular/date-picker';
45

46
import {
47
    IgxButtonDirective,
48
    IgxDateTimeEditorDirective,
49
    IgxIconButtonDirective,
50
    IgxTooltipDirective,
51
    IgxTooltipTargetDirective,
52
    IgxDragIgnoreDirective,
53
    IgxDropDirective
54
} from 'igniteui-angular/directives';
55
import { IgxSelectComponent } from 'igniteui-angular/select';
56
import { IgxTimePickerComponent } from 'igniteui-angular/time-picker';
57
import { IgxInputGroupComponent, IgxInputDirective, IgxPrefixDirective } from 'igniteui-angular/input-group';
58
import { IgxSelectItemComponent } from 'igniteui-angular/select';
59
import { IgxIconComponent } from 'igniteui-angular/icon';
60
import { IComboSelectionChangingEventArgs, IgxComboComponent, IgxComboHeaderDirective } from 'igniteui-angular/combo';
61
import { IgxCheckboxComponent, IChangeCheckboxEventArgs } from 'igniteui-angular/checkbox';
62
import { IgxDialogComponent } from 'igniteui-angular/dialog';
63
import {
64
    ISelectionEventArgs,
65
    IgxDropDownComponent,
66
    IgxDropDownItemComponent,
67
    IgxDropDownItemNavigationDirective
68
} from 'igniteui-angular/drop-down';
69
import { IgxQueryBuilderSearchValueTemplateDirective } from './query-builder.directives';
70
import { IgxQueryBuilderComponent } from './query-builder.component';
71
import { IgxQueryBuilderDragService } from './query-builder-drag.service';
72
import { ExpressionGroupItem, ExpressionItem, ExpressionOperandItem, IgxFieldFormatterPipe, IgxQueryBuilderSearchValueContext } from './query-builder.common';
73
import { getCurrentI18n, IResourceChangeEventArgs } from 'igniteui-i18n-core';
74

75
const DEFAULT_PIPE_DATE_FORMAT = 'mediumDate';
3✔
76
const DEFAULT_PIPE_TIME_FORMAT = 'mediumTime';
3✔
77
const DEFAULT_PIPE_DATE_TIME_FORMAT = 'medium';
3✔
78
const DEFAULT_PIPE_DIGITS_INFO = '1.0-3';
3✔
79
const DEFAULT_CHIP_FOCUS_DELAY = 50;
3✔
80

81
/** @hidden */
82
@Component({
83
    selector: 'igx-query-builder-tree',
84
    templateUrl: './query-builder-tree.component.html',
85
    host: { 'class': 'igx-query-builder-tree' },
86
    imports: [
87
        IgxDateFormatterPipe,
88
        FormsModule,
89
        IgxButtonDirective,
90
        IgxCheckboxComponent,
91
        IgxChipComponent,
92
        IgxComboComponent,
93
        IgxComboHeaderDirective,
94
        IgxDatePickerComponent,
95
        IgxDateTimeEditorDirective,
96
        IgxDialogComponent,
97
        IgxDragIgnoreDirective,
98
        IgxDropDirective,
99
        IgxDropDownComponent,
100
        IgxDropDownItemComponent,
101
        IgxDropDownItemNavigationDirective,
102
        IgxFieldFormatterPipe,
103
        IgxIconButtonDirective,
104
        IgxIconComponent,
105
        IgxInputDirective,
106
        IgxInputGroupComponent,
107
        IgxPickerClearComponent,
108
        IgxPickerToggleComponent,
109
        IgxPrefixDirective,
110
        IgxSelectComponent,
111
        IgxSelectItemComponent,
112
        IgxTimePickerComponent,
113
        IgxTooltipDirective,
114
        IgxTooltipTargetDirective,
115
        NgClass,
116
        NgTemplateOutlet
117
    ],
118
    providers: [
119
        IgxQueryBuilderDragService
120
    ],
121
})
122
export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
3✔
123
    public cdr = inject(ChangeDetectorRef);
304✔
124
    public dragService = inject(IgxQueryBuilderDragService);
304✔
125
    protected platform = inject(PlatformUtil);
304✔
126
    private elRef = inject(ElementRef);
304✔
127
    protected _localeId = inject(LOCALE_ID);
304✔
128

129
    /**
130
     * @hidden @internal
131
     */
132
    public _expressionTree: IExpressionTree;
133

134
    /**
135
     * @hidden @internal
136
     */
137
    public _expressionTreeCopy: IExpressionTree;
138

139
    /**
140
     * @hidden @internal
141
     */
142
    @HostBinding('class') public get getClass() {
143
        return `igx-query-builder-tree--level-${this.level}`;
11,433✔
144
    }
145

146
    /**
147
     * Sets/gets the entities.
148
     */
149
    @Input()
150
    public entities: EntityType[];
151

152
    /**
153
     * Sets/gets the parent query builder component.
154
     */
155
    @Input()
156
    public queryBuilder: IgxQueryBuilderComponent;
157

158
    /**
159
     * Sets/gets the search value template.
160
     */
161
    @Input()
162
    public searchValueTemplate: TemplateRef<IgxQueryBuilderSearchValueContext> = null;
304✔
163

164
    /**
165
    * Returns the parent expression operand.
166
    */
167
    @Input()
168
    public get parentExpression(): ExpressionOperandItem {
169
        return this._parentExpression;
153,950✔
170
    }
171

172
    /**
173
     * Sets the parent expression operand.
174
     */
175
    public set parentExpression(value: ExpressionOperandItem) {
176
        this._parentExpression = value;
143✔
177
    }
178

179
    /**
180
    * Returns the fields.
181
    */
182
    public get fields(): FieldType[] {
183
        if (!this._fields && this.isAdvancedFiltering()) {
58,679✔
184
            this._fields = this.entities[0].fields;
66✔
185
        }
186

187
        return this._fields;
58,679✔
188
    }
189

190
    /**
191
     * Sets the fields.
192
     */
193
    @Input()
194
    public set fields(fields: FieldType[]) {
195
        this._fields = fields;
804✔
196

197
        this._fields = this._fields?.map(f => ({...f, filters: this.getFilters(f), pipeArgs: this.getPipeArgs(f) }));
3,279✔
198

199
        if (!this._fields && this.isAdvancedFiltering()) {
804✔
200
            this._fields = this.entities[0].fields;
1✔
201
        }
202
    }
203

204
    /**
205
    * Returns the expression tree.
206
    */
207
    public get expressionTree(): IExpressionTree {
208
        return this._expressionTree;
6,770✔
209
    }
210

211
    /**
212
     * Sets the expression tree.
213
     */
214
    @Input()
215
    public set expressionTree(expressionTree: IExpressionTree) {
216
        this._expressionTree = expressionTree;
517✔
217
        if (!expressionTree) {
517✔
218
            this._selectedEntity = this.isAdvancedFiltering() && this.entities.length === 1 ? this.entities[0] : null;
166✔
219
            this._selectedReturnFields = this._selectedEntity ? this._selectedEntity.fields?.map(f => f.field) : [];
275✔
220
        }
221

222
        if (!this._preventInit) {
517✔
223
            this.init();
508✔
224
        }
225
    }
226

227
    /**
228
     * Gets the `locale` of the query builder.
229
     * If not set, defaults to application's locale.
230
     */
231
    @Input()
232
    public get locale(): string {
233
        return this._locale || this._defaultLocale;
710✔
234
    }
235

236
    /**
237
     * Sets the `locale` of the query builder.
238
     * Expects a valid BCP 47 language tag.
239
     */
240
    public set locale(value: string) {
241
        this._locale = value;
161✔
242
        this._defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN, false, this._locale);
161✔
243
    }
244

245
    /**
246
     * Sets the resource strings.
247
     * By default it uses EN resources.
248
     */
249
    @Input()
250
    public set resourceStrings(value: IQueryBuilderResourceStrings) {
251
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
309✔
252
    }
253

254
    /**
255
     * Returns the resource strings.
256
     */
257
    public get resourceStrings(): IQueryBuilderResourceStrings {
258
        return this._resourceStrings || this._defaultResourceStrings;
276,641!
259
    }
260

261
    /**
262
     * Gets/sets the expected return field.
263
     */
264
    @Input() public expectedReturnField: string = null;
304✔
265

266
    /**
267
     * Event fired as the expression tree is changed.
268
     */
269
    @Output()
270
    public expressionTreeChange = new EventEmitter<IExpressionTree>();
304✔
271

272
    /**
273
     * Event fired if a nested query builder tree is being edited.
274
     */
275
    @Output()
276
    public inEditModeChange = new EventEmitter<ExpressionOperandItem>();
304✔
277

278
    @ViewChild('entitySelect', { read: IgxSelectComponent })
279
    protected entitySelect: IgxSelectComponent;
280

281
    @ViewChild('editingInputs', { read: ElementRef })
282
    private editingInputs: ElementRef;
283

284
    @ViewChild('returnFieldsCombo', { read: IgxComboComponent })
285
    private returnFieldsCombo: IgxComboComponent;
286

287
    @ViewChild('returnFieldSelect', { read: IgxSelectComponent })
288
    protected returnFieldSelect: IgxSelectComponent;
289

290
    @ViewChild('fieldSelect', { read: IgxSelectComponent })
291
    private fieldSelect: IgxSelectComponent;
292

293
    @ViewChild('conditionSelect', { read: IgxSelectComponent })
294
    private conditionSelect: IgxSelectComponent;
295

296
    @ViewChild('searchValueInput', { read: ElementRef })
297
    private searchValueInput: ElementRef;
298

299
    @ViewChild('picker')
300
    private picker: IgxDatePickerComponent | IgxTimePickerComponent;
301

302
    @ViewChild('addRootAndGroupButton', { read: ElementRef })
303
    private addRootAndGroupButton: ElementRef;
304

305
    @ViewChild('addConditionButton', { read: ElementRef })
306
    private addConditionButton: ElementRef;
307

308
    @ViewChild('entityChangeDialog', { read: IgxDialogComponent })
309
    private entityChangeDialog: IgxDialogComponent;
310

311
    @ViewChild('addOptionsDropDown', { read: IgxDropDownComponent })
312
    private addExpressionItemDropDown: IgxDropDownComponent;
313

314
    @ViewChild('groupContextMenuDropDown', { read: IgxDropDownComponent })
315
    private groupContextMenuDropDown: IgxDropDownComponent;
316

317
    /**
318
     * @hidden @internal
319
     */
320
    @ViewChildren(IgxChipComponent, { read: IgxChipComponent })
321
    public expressionsChips: QueryList<IgxChipComponent>;
322

323
    @ViewChild('editingInputsContainer', { read: ElementRef })
324
    protected set editingInputsContainer(value: ElementRef) {
325
        if ((value && !this._editingInputsContainer) ||
819✔
326
            (value && this._editingInputsContainer && this._editingInputsContainer.nativeElement !== value.nativeElement)) {
327
            requestAnimationFrame(() => {
293✔
328
                this.scrollElementIntoView(value.nativeElement);
293✔
329
            });
330
        }
331

332
        this._editingInputsContainer = value;
819✔
333
    }
334

335
    /** @hidden */
336
    protected get editingInputsContainer(): ElementRef {
337
        return this._editingInputsContainer;
×
338
    }
339

340
    @ViewChild('currentGroupButtonsContainer', { read: ElementRef })
341
    protected set currentGroupButtonsContainer(value: ElementRef) {
342
        if ((value && !this._currentGroupButtonsContainer) ||
819✔
343
            (value && this._currentGroupButtonsContainer && this._currentGroupButtonsContainer.nativeElement !== value.nativeElement)) {
344
            requestAnimationFrame(() => {
375✔
345
                this.scrollElementIntoView(value.nativeElement);
375✔
346
            });
347
        }
348

349
        this._currentGroupButtonsContainer = value;
819✔
350
    }
351

352
    /** @hidden */
353
    protected get currentGroupButtonsContainer(): ElementRef {
354
        return this._currentGroupButtonsContainer;
×
355
    }
356

357
    @ViewChild('expressionsContainer')
358
    private expressionsContainer: ElementRef;
359

360
    @ViewChildren(IgxQueryBuilderTreeComponent)
361
    private innerQueries: QueryList<IgxQueryBuilderTreeComponent>;
362

363
    /**
364
     * @hidden @internal
365
     */
366
    public innerQueryNewExpressionTree: IExpressionTree;
367

368
    /**
369
     * @hidden @internal
370
     */
371
    public rootGroup: ExpressionGroupItem;
372

373
    /**
374
     * @hidden @internal
375
     */
376
    public selectedExpressions: ExpressionOperandItem[] = [];
304✔
377

378
    /**
379
     * @hidden @internal
380
     */
381
    public currentGroup: ExpressionGroupItem;
382

383
    /**
384
     * @hidden @internal
385
     */
386
    public contextualGroup: ExpressionGroupItem;
387

388
    /**
389
     * @hidden @internal
390
     */
391
    public filteringLogics;
392

393
    /**
394
     * @hidden @internal
395
     */
396
    public selectedCondition: string;
397

398
    /**
399
     * @hidden @internal
400
     */
401
    public searchValue: { value: any } = { value: null };
304✔
402

403
    /**
404
     * @hidden @internal
405
     */
406
    public prevFocusedExpression: ExpressionOperandItem;
407

408
    /**
409
     * @hidden @internal
410
     */
411
    public initialOperator = 0;
304✔
412

413
    /**
414
     * @hidden @internal
415
     */
416
    public returnFieldSelectOverlaySettings: OverlaySettings = {
304✔
417
        scrollStrategy: new AbsoluteScrollStrategy(),
418
        modal: false,
419
        closeOnOutsideClick: true
420
    };
421

422
    /**
423
     * @hidden @internal
424
     */
425
    public entitySelectOverlaySettings: OverlaySettings = {
304✔
426
        scrollStrategy: new AbsoluteScrollStrategy(),
427
        modal: false,
428
        closeOnOutsideClick: true
429
    };
430

431
    /**
432
     * @hidden @internal
433
     */
434
    public fieldSelectOverlaySettings: OverlaySettings = {
304✔
435
        scrollStrategy: new AbsoluteScrollStrategy(),
436
        modal: false,
437
        closeOnOutsideClick: true
438
    };
439

440
    /**
441
     * @hidden @internal
442
     */
443
    public conditionSelectOverlaySettings: OverlaySettings = {
304✔
444
        scrollStrategy: new AbsoluteScrollStrategy(),
445
        modal: false,
446
        closeOnOutsideClick: true
447
    };
448

449
    /**
450
     * @hidden @internal
451
     */
452
    public addExpressionDropDownOverlaySettings: OverlaySettings = {
304✔
453
        scrollStrategy: new AbsoluteScrollStrategy(),
454
        modal: false,
455
        closeOnOutsideClick: true
456
    };
457

458
    /**
459
     * @hidden @internal
460
     */
461
    public groupContextMenuDropDownOverlaySettings: OverlaySettings = {
304✔
462
        scrollStrategy: new AbsoluteScrollStrategy(),
463
        modal: false,
464
        closeOnOutsideClick: true
465
    };
466

467
    private destroy$ = new Subject<any>();
304✔
468
    private _timeoutId: any;
469
    private _lastFocusedChipIndex: number;
470
    private _focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
304✔
471
    private _parentExpression: ExpressionOperandItem;
472
    private _selectedEntity: EntityType;
473
    private _selectedReturnFields: string | string[];
474
    private _selectedField: FieldType;
475
    private _editingInputsContainer: ElementRef;
476
    private _currentGroupButtonsContainer: ElementRef;
477
    private _addModeExpression: ExpressionOperandItem;
478
    private _editedExpression: ExpressionOperandItem;
479
    private _preventInit = false;
304✔
480
    private _prevFocusedContainer: ElementRef;
481
    private _expandedExpressions: IFilteringExpression[] = [];
304✔
482
    private _fields: FieldType[];
483
    private _locale;
484
    private _defaultLocale;
485
    private _entityNewValue: EntityType;
486
    private _resourceStrings = null;
304✔
487
    private _defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN);
304✔
488

489
    /**
490
     * Returns if the select entity dropdown at the root level is disabled after the initial selection.
491
     */
492
    public get disableEntityChange(): boolean {
493

494
        return !this.parentExpression && this.selectedEntity ? this.queryBuilder.disableEntityChange : false;
12,244✔
495
    }
496

497
    /**
498
     * Returns if the fields combo at the root level is disabled.
499
     */
500
    public get disableReturnFieldsChange(): boolean {
501

502
        return !this.selectedEntity || this.queryBuilder.disableReturnFieldsChange;
5,876✔
503
    }
504

505
    /**
506
     * Returns the current level.
507
     */
508
    public get level(): number {
509
        let parent = this.elRef.nativeElement.parentElement;
11,433✔
510
        let _level = 0;
11,433✔
511
        while (parent) {
11,433✔
512
            if (parent.localName === 'igx-query-builder-tree') {
96,407✔
513
                _level++;
6,181✔
514
            }
515
            parent = parent.parentElement;
96,407✔
516
        }
517
        return _level;
11,433✔
518
    }
519

520
    /** @hidden */
521
    protected isAdvancedFiltering(): boolean {
522
        return (this.entities?.length === 1 && !this.entities[0]?.name) ||
20,968✔
523
            this.entities?.find(e => e.childEntities?.length > 0) !== undefined ||
37,038✔
524
            (this.entities?.length > 0 && this.queryBuilder?.entities?.length > 0 && this.entities !== this.queryBuilder?.entities);
525
    }
526

527
    /** @hidden */
528
    protected isHierarchicalNestedQuery(): boolean {
529
        return this.queryBuilder.entities !== this.entities
14,310✔
530
    }
531

532
    /** @hidden */
533
    protected isSearchValueInputDisabled(): boolean {
534
        return !this.selectedField ||
1,699✔
535
            !this.selectedCondition ||
536
            (this.selectedField &&
537
                (this.selectedField.filters.condition(this.selectedCondition).isUnary ||
538
                    this.selectedField.filters.condition(this.selectedCondition).isNestedQuery));
539
    }
540

541
    constructor() {
542
        this.initLocale();
304✔
543
        this.dragService.register(this, this.elRef);
304✔
544
    }
545

546
    /**
547
     * @hidden @internal
548
     */
549
    public ngAfterViewInit(): void {
550
        if (this.isAdvancedFiltering() && this.entities?.length === 1) {
304✔
551
            this.selectedEntity = this.entities[0].name;
65✔
552
            if (this._selectedEntity.fields.find(f => f.field === this.expectedReturnField)) {
346✔
553
                this._selectedReturnFields = [this.expectedReturnField];
5✔
554
            }
555
        }
556

557
        // Trigger additional change detection cycle
558
        this.cdr.detectChanges();
304✔
559
    }
560

561
    /**
562
     * @hidden @internal
563
     */
564
    public ngOnDestroy(): void {
565
        this.destroy$.next(true);
304✔
566
        this.destroy$.complete();
304✔
567
    }
568

569
    /**
570
     * @hidden @internal
571
     */
572
    public set selectedEntity(value: string) {
573
        this._selectedEntity = this.entities?.find(el => el.name === value);
65✔
574
    }
575

576
    /**
577
     * @hidden @internal
578
     */
579
    public get selectedEntity(): EntityType {
580
        return this._selectedEntity;
85,530✔
581
    }
582

583
    /**
584
     * @hidden @internal
585
     */
586
    public onEntitySelectChanging(event: ISelectionEventArgs) {
587
        event.cancel = true;
44✔
588
        this._entityNewValue = event.newSelection.value;
44✔
589
        if (event.oldSelection.value && this.queryBuilder.showEntityChangeDialog) {
44✔
590
            this.entityChangeDialog.open();
7✔
591
        } else {
592
            this.onEntityChangeConfirm();
37✔
593
        }
594
    }
595

596
    /**
597
     * @hidden
598
     */
599
    public onShowEntityChangeDialogChange(eventArgs: IChangeCheckboxEventArgs) {
600
        this.queryBuilder.showEntityChangeDialog = !eventArgs.checked;
1✔
601
    }
602

603
    /**
604
     * @hidden
605
     */
606
    public onEntityChangeCancel() {
607
        this.entityChangeDialog.close();
3✔
608
        this.entitySelect.close();
3✔
609
        this._entityNewValue = null;
3✔
610
    }
611

612
    /**
613
     * @hidden
614
     */
615
    public onEntityChangeConfirm() {
616
        if (this._parentExpression) {
39✔
617
            this._expressionTree = this.createExpressionTreeFromGroupItem(this.createExpressionGroupItem(this._expressionTree));
6✔
618
        }
619

620
        this._selectedEntity = this._entityNewValue;
39✔
621
        if (!this._selectedEntity.fields) {
39!
622
            this._selectedEntity.fields = [];
×
623
        }
624
        this.fields = this._entityNewValue ? this._entityNewValue.fields : [];
39!
625

626
        if (this._selectedEntity.fields.find(f => f.field === this.expectedReturnField)) {
150✔
627
            this._selectedReturnFields = [this.expectedReturnField];
6✔
628
        } else {
629
            this._selectedReturnFields = this.parentExpression ? [] : this._entityNewValue.fields?.map(f => f.field);
132!
630
        }
631

632
        if (this._expressionTree) {
39✔
633
            this._expressionTree.entity = this._entityNewValue.name;
5✔
634

635
            const returnFields = Array.isArray(this._selectedReturnFields) ? this._selectedReturnFields : [this._selectedReturnFields];
5!
636
            this._expressionTree.returnFields = this.fields.length === returnFields.length ? ['*'] : returnFields;
5✔
637

638
            this._expressionTree.filteringOperands = [];
5✔
639

640
            this._editedExpression = null;
5✔
641
            if (!this.parentExpression) {
5✔
642
                this.expressionTreeChange.emit(this._expressionTree);
4✔
643
            }
644

645
            this.rootGroup = null;
5✔
646
            this.currentGroup = this.rootGroup;
5✔
647
        }
648

649
        this._selectedField = null;
39✔
650
        this.selectedCondition = null;
39✔
651
        this.searchValue.value = null;
39✔
652

653
        this.entityChangeDialog.close();
39✔
654
        this.entitySelect.close();
39✔
655

656
        this._entityNewValue = null;
39✔
657
        this.innerQueryNewExpressionTree = null;
39✔
658

659
        this.initExpressionTree(this._selectedEntity.name, this.selectedReturnFields);
39✔
660
    }
661

662
    /**
663
     * @hidden @internal
664
     */
665
    public set selectedReturnFields(value: string[]) {
666
        if (this._selectedReturnFields !== value) {
5✔
667
            this._selectedReturnFields = value;
5✔
668

669
            if (this._expressionTree && !this.parentExpression) {
5✔
670
                this._expressionTree.returnFields = value.length === this.fields.length ? ['*'] : value;
5✔
671
                this.expressionTreeChange.emit(this._expressionTree);
5✔
672
            }
673
        }
674
    }
675

676
    /**
677
     * @hidden @internal
678
     */
679
    public get selectedReturnFields(): string[] {
680
        if (typeof this._selectedReturnFields == 'string') {
58,216!
681
            return [this._selectedReturnFields];
×
682
        }
683
        return this._selectedReturnFields;
58,216✔
684
    }
685

686
    /**
687
     * @hidden @internal
688
     */
689
    public set selectedField(value: FieldType) {
690
        const oldValue = this._selectedField;
208✔
691

692
        if (this._selectedField !== value) {
208✔
693
            this._selectedField = value;
176✔
694
            if (this._selectedField && !this._selectedField.dataType) {
176!
695
                this._selectedField.filters = this.getFilters(this._selectedField);
×
696
            }
697

698
            this.selectDefaultCondition();
176✔
699
            if (oldValue && this._selectedField && this._selectedField.dataType !== oldValue.dataType) {
176✔
700
                this.searchValue.value = null;
17✔
701
                this.cdr.detectChanges();
17✔
702
            }
703
        }
704
    }
705

706
    /**
707
     * @hidden @internal
708
     */
709
    public get selectedField(): FieldType {
710
        return this._selectedField;
57,604✔
711
    }
712

713
    /**
714
     * @hidden @internal
715
     *
716
     * used by the grid
717
     */
718
    public get isContextMenuVisible(): boolean {
719
        return !this.groupContextMenuDropDown.collapsed;
×
720
    }
721

722
    /**
723
     * @hidden @internal
724
     */
725
    public get hasEditedExpression(): boolean {
726
        return this._editedExpression !== undefined && this._editedExpression !== null;
31,369✔
727
    }
728

729
    /**
730
     * @hidden @internal
731
     */
732
    public addCondition(parent: ExpressionGroupItem, afterExpression?: ExpressionOperandItem, isUIInteraction?: boolean) {
733
        this.cancelOperandAdd();
82✔
734

735
        const operandItem = new ExpressionOperandItem({
82✔
736
            fieldName: null,
737
            condition: null,
738
            conditionName: null,
739
            ignoreCase: true,
740
            searchVal: null
741
        }, parent);
742

743
        const groupItem = new ExpressionGroupItem(this.getOperator(null) ?? FilteringLogic.And, parent);
82!
744
        this.contextualGroup = groupItem;
82✔
745
        this.initialOperator = null;
82✔
746

747
        this._lastFocusedChipIndex = this._lastFocusedChipIndex === undefined ? -1 : this._lastFocusedChipIndex;
82✔
748

749
        if (parent) {
82✔
750
            if (afterExpression) {
11✔
751
                const index = parent.children.indexOf(afterExpression);
1✔
752
                parent.children.splice(index + 1, 0, operandItem);
1✔
753
            } else {
754
                parent.children.push(operandItem);
10✔
755
            }
756
            this._lastFocusedChipIndex++;
11✔
757
        } else {
758
            this.rootGroup = groupItem;
71✔
759
            operandItem.parent = groupItem;
71✔
760
            this.rootGroup.children.push(operandItem);
71✔
761
            this._lastFocusedChipIndex = 0;
71✔
762
        }
763

764
        this._focusDelay = 250;
82✔
765

766
        if (isUIInteraction && !afterExpression) {
82✔
767
            this._lastFocusedChipIndex = this.expressionsChips.length;
78✔
768
            this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
78✔
769
        }
770

771
        this.enterExpressionEdit(operandItem);
82✔
772
    }
773

774
    /**
775
     * @hidden @internal
776
     */
777
    public addReverseGroup(parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
778
        parent = parent ?? this.rootGroup;
3!
779

780
        if (parent.operator === FilteringLogic.And) {
3✔
781
            this.addGroup(FilteringLogic.Or, parent, afterExpression);
2✔
782
        } else {
783
            this.addGroup(FilteringLogic.And, parent, afterExpression);
1✔
784
        }
785
    }
786

787
    /**
788
     * @hidden @internal
789
     */
790
    public endGroup(groupItem: ExpressionGroupItem) {
791
        this.currentGroup = groupItem.parent;
×
792
    }
793

794
    /**
795
     * @hidden @internal
796
     */
797
    public commitExpression() {
798
        this.commitOperandEdit();
45✔
799
        this.focusEditedExpressionChip();
45✔
800
    }
801

802
    /**
803
     * @hidden @internal
804
     */
805
    public discardExpression(expressionItem?: ExpressionOperandItem) {
806
        this.cancelOperandEdit();
11✔
807
        if (expressionItem && expressionItem.expression.fieldName) {
11✔
808
            this.focusEditedExpressionChip();
4✔
809
        }
810
    }
811

812
    /**
813
     * @hidden @internal
814
     */
815
    public commitOperandEdit() {
816
        const actualSearchValue = this.searchValue.value;
60✔
817
        if (this._editedExpression) {
60✔
818
            this._editedExpression.expression.fieldName = this.selectedField.field;
57✔
819
            this._editedExpression.expression.condition = this.selectedField.filters.condition(this.selectedCondition);
57✔
820
            this._editedExpression.expression.conditionName = this.selectedCondition;
57✔
821
            this._editedExpression.expression.searchVal = DataUtil.parseValue(this.selectedField.dataType, actualSearchValue) || actualSearchValue;
57✔
822
            this._editedExpression.fieldLabel = this.selectedField.label
57!
823
                ? this.selectedField.label
824
                : this.selectedField.header
57✔
825
                    ? this.selectedField.header
826
                    : this.selectedField.field;
827

828
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0]
57✔
829
            if (innerQuery && this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) {
57✔
830
                innerQuery.exitEditAddMode();
8✔
831
                this._editedExpression.expression.searchTree = this.getExpressionTreeCopy(innerQuery.expressionTree);
8✔
832
                const returnFields = innerQuery.selectedReturnFields.length > 0 ?
8!
833
                                        innerQuery.selectedReturnFields :
834
                                        [innerQuery.fields[0].field];
835
                this._editedExpression.expression.searchTree.returnFields = returnFields;
8✔
836
            } else {
837
                this._editedExpression.expression.searchTree = null;
49✔
838
            }
839
            this.innerQueryNewExpressionTree = null;
57✔
840

841
            if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary || this.selectedField.filters.condition(this.selectedCondition)?.isNestedQuery) {
57✔
842
                this._editedExpression.expression.searchVal = null;
13✔
843
            }
844

845
            this._editedExpression.inEditMode = false;
57✔
846
            this._editedExpression = null;
57✔
847
        }
848

849
        if (this.selectedReturnFields.length === 0) {
60!
850
            this.selectedReturnFields = this.fields.map(f => f.field);
×
851
        }
852

853
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, this.selectedEntity?.name, this.selectedReturnFields);
60✔
854
        if (!this.parentExpression) {
60✔
855
            this.expressionTreeChange.emit(this._expressionTree);
50✔
856
        }
857
    }
858

859
    /**
860
     * @hidden @internal
861
     */
862
    public cancelOperandAdd() {
863
        if (this._addModeExpression) {
608!
864
            this._addModeExpression.inAddMode = false;
×
865
            this._addModeExpression = null;
×
866
        }
867
    }
868

869
    /**
870
     * @hidden @internal
871
     */
872
    public deleteItem = (expressionItem: ExpressionItem, skipEmit: boolean = false) => {
304✔
873
        if (!expressionItem.parent) {
48✔
874
            this.rootGroup = null;
15✔
875
            this.currentGroup = null;
15✔
876
            //this._expressionTree = null;
877
            return;
15✔
878
        }
879

880
        if (expressionItem === this.currentGroup) {
33!
881
            this.currentGroup = this.currentGroup.parent;
×
882
        }
883

884
        const children = expressionItem.parent.children;
33✔
885
        const index = children.indexOf(expressionItem);
33✔
886
        children.splice(index, 1);
33✔
887
        const entity = this.expressionTree ? this.expressionTree.entity : null;
33✔
888
        const returnFields = this.expressionTree ? this.expressionTree.returnFields : null;
33✔
889
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, entity, returnFields); // TODO: don't recreate if not necessary
33✔
890

891
        if (!children.length) {
33✔
892
            this.deleteItem(expressionItem.parent, true);
16✔
893
        }
894

895
        if (!this.parentExpression && !skipEmit) {
33✔
896
            this.expressionTreeChange.emit(this._expressionTree);
31✔
897
        }
898
    }
899

900
    /**
901
     * @hidden @internal
902
     */
903
    public cancelOperandEdit() {
904
        if (this.innerQueries) {
535✔
905
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
231✔
906
            if (innerQuery) {
231✔
907
                if (innerQuery._editedExpression) {
9✔
908
                    innerQuery.cancelOperandEdit();
1✔
909
                }
910

911
                innerQuery.expressionTree = this.getExpressionTreeCopy(this._editedExpression.expression.searchTree);
9✔
912
                this.innerQueryNewExpressionTree = null;
9✔
913
            }
914
        }
915

916
        if (this._editedExpression) {
535✔
917
            this._editedExpression.inEditMode = false;
30✔
918

919
            if (!this._editedExpression.expression.fieldName) {
30✔
920
                this.deleteItem(this._editedExpression);
16✔
921
            }
922

923
            this._editedExpression = null;
30✔
924
        }
925

926
        if (!this.expressionTree && this.contextualGroup) {
535✔
927
            this.initialOperator = this.contextualGroup.operator;
1✔
928
        }
929
    }
930

931
    /**
932
     * @hidden @internal
933
     */
934
    public operandCanBeCommitted(): boolean {
935
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
1,766✔
936
        return this.selectedField && this.selectedCondition &&
1,766✔
937
            (
938
                (
939
                    ((!Array.isArray(this.searchValue.value) && !!this.searchValue.value) || (Array.isArray(this.searchValue.value) && this.searchValue.value.length !== 0)) &&
940
                    !(this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery)
941
                ) ||
942
                (
943
                    this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery && innerQuery && !!innerQuery.expressionTree && innerQuery.selectedReturnFields?.length > 0
944
                ) ||
945
                this.selectedField.filters.condition(this.selectedCondition)?.isUnary
946
            );
947
    }
948

949
    /**
950
     * @hidden @internal
951
     */
952
    public canCommitCurrentState(): boolean {
953
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
36✔
954
        if (innerQuery) {
36✔
955
            return this.selectedReturnFields?.length > 0 && innerQuery.canCommitCurrentState();
8✔
956
        } else {
957
            return this.selectedReturnFields?.length > 0 &&
28✔
958
                (
959
                    (!this._editedExpression) || // no edited expr
960
                    (this._editedExpression && !this.selectedField) || // empty edited expr
961
                    (this._editedExpression && this.operandCanBeCommitted() === true) // valid edited expr
962
                );
963
        }
964
    }
965

966
    /**
967
     * @hidden @internal
968
     */
969
    public commitCurrentState(): void {
970
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
3✔
971
        if (innerQuery) {
3✔
972
            innerQuery.commitCurrentState();
1✔
973
        }
974

975
        if (this._editedExpression) {
3✔
976
            if (this.selectedField) {
3!
977
                this.commitOperandEdit();
3✔
978
            } else {
979
                this.deleteItem(this._editedExpression);
×
980
                this._editedExpression = null;
×
981
            }
982
        }
983
    }
984

985
    /**
986
     * @hidden @internal
987
     */
988
    public exitEditAddMode(shouldPreventInit = false) {
49✔
989
        if (!this._editedExpression) {
244✔
990
            return;
229✔
991
        }
992

993
        this.exitOperandEdit();
15✔
994
        this.cancelOperandAdd();
15✔
995

996
        if (shouldPreventInit) {
15✔
997
            this._preventInit = true;
7✔
998
        }
999
    }
1000

1001
    /**
1002
     * @hidden @internal
1003
     *
1004
     * used by the grid
1005
     */
1006
    public exitOperandEdit() {
1007
        if (!this._editedExpression) {
35✔
1008
            return;
16✔
1009
        }
1010

1011
        if (this.operandCanBeCommitted()) {
19✔
1012
            this.commitOperandEdit();
9✔
1013
        } else {
1014
            this.cancelOperandEdit();
10✔
1015
        }
1016
    }
1017

1018
    /**
1019
     * @hidden @internal
1020
     */
1021
    public isExpressionGroup(expression: ExpressionItem): boolean {
1022
        return expression instanceof ExpressionGroupItem;
34,538✔
1023
    }
1024

1025
    /**
1026
     * @hidden @internal
1027
     */
1028
    public onExpressionFocus(expressionItem: ExpressionOperandItem) {
1029
        if (this.prevFocusedExpression) {
40✔
1030
            this.prevFocusedExpression.focused = false;
4✔
1031
        }
1032
        expressionItem.focused = true;
40✔
1033
        this.prevFocusedExpression = expressionItem;
40✔
1034
    }
1035

1036
    /**
1037
     * @hidden @internal
1038
     */
1039
    public onExpressionBlur(event, expressionItem: ExpressionOperandItem) {
1040
        if (this._prevFocusedContainer && this._prevFocusedContainer !== event.target.closest('.igx-filter-tree__expression-item')) {
2!
1041
            expressionItem.focused = false;
×
1042
        }
1043
        this._prevFocusedContainer = event.target.closest('.igx-filter-tree__expression-item');
2✔
1044
    }
1045

1046
    /**
1047
     * @hidden @internal
1048
     */
1049
    public onChipRemove(expressionItem: ExpressionItem) {
1050
        this.exitEditAddMode();
5✔
1051
        this.deleteItem(expressionItem);
5✔
1052
    }
1053

1054
    /**
1055
     * @hidden @internal
1056
     */
1057
    public focusChipAfterDrag = (index: number) => {
304✔
1058
        this._lastFocusedChipIndex = index;
11✔
1059
        this.focusEditedExpressionChip();
11✔
1060
    }
1061
    /**
1062
     * @hidden @internal
1063
     */
1064
    public addExpressionBlur() {
1065
        if (this.prevFocusedExpression) {
×
1066
            this.prevFocusedExpression.focused = false;
×
1067
        }
1068
        if (this.addExpressionItemDropDown && !this.addExpressionItemDropDown.collapsed) {
×
1069
            this.addExpressionItemDropDown.close();
×
1070
        }
1071
    }
1072

1073
    /**
1074
     * @hidden @internal
1075
     */
1076
    public onChipClick(expressionItem: ExpressionOperandItem, chip: IgxChipComponent) {
1077
        this.enterExpressionEdit(expressionItem, chip);
56✔
1078
    }
1079

1080
    /**
1081
     * @hidden @internal
1082
     */
1083
    public enterExpressionEdit(expressionItem: ExpressionOperandItem, chip?: IgxChipComponent) {
1084
        this.exitEditAddMode(true);
138✔
1085
        this.cdr.detectChanges();
138✔
1086
        this._lastFocusedChipIndex = chip ? this.expressionsChips.toArray().findIndex(expr => expr === chip) : this._lastFocusedChipIndex;
138✔
1087
        this.enterEditMode(expressionItem);
138✔
1088
    }
1089

1090

1091
    /**
1092
     * @hidden @internal
1093
     */
1094
    public clickExpressionAdd(targetButton: HTMLElement, chip: IgxChipComponent) {
1095
        this.exitEditAddMode(true);
5✔
1096
        this.cdr.detectChanges();
5✔
1097
        this._lastFocusedChipIndex = this.expressionsChips.toArray().findIndex(expr => expr === chip);
23✔
1098
        this.openExpressionAddDialog(targetButton);
5✔
1099
    }
1100

1101
    /**
1102
     * @hidden @internal
1103
     */
1104
    public openExpressionAddDialog(targetButton: HTMLElement) {
1105
        this.addExpressionDropDownOverlaySettings.target = targetButton;
5✔
1106
        this.addExpressionDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
5✔
1107
            horizontalDirection: HorizontalAlignment.Right,
1108
            horizontalStartPoint: HorizontalAlignment.Left,
1109
            verticalStartPoint: VerticalAlignment.Bottom
1110
        });
1111

1112
        this.addExpressionItemDropDown.open(this.addExpressionDropDownOverlaySettings);
5✔
1113
    }
1114

1115
    /**
1116
     * @hidden @internal
1117
     */
1118
    public enterExpressionAdd(event: ISelectionEventArgs, expressionItem: ExpressionOperandItem) {
1119
        if (this._addModeExpression) {
1!
1120
            this._addModeExpression.inAddMode = false;
×
1121
        }
1122

1123
        if (this.parentExpression) {
1!
1124
            this.inEditModeChange.emit(this.parentExpression);
×
1125
        }
1126

1127
        const parent = expressionItem.parent ?? this.rootGroup;
1!
1128
        requestAnimationFrame(() => {
1✔
1129
            if (event.newSelection.value === 'addCondition') {
1!
1130
                this.addCondition(parent, expressionItem);
1✔
1131
            } else if (event.newSelection.value === 'addGroup') {
×
1132
                this.addReverseGroup(parent, expressionItem);
×
1133
            }
1134
            expressionItem.inAddMode = true;
1✔
1135
            this._addModeExpression = expressionItem;
1✔
1136
        })
1137
    }
1138

1139
    /**
1140
     * @hidden @internal
1141
     */
1142
    public enterEditMode(expressionItem: ExpressionOperandItem) {
1143
        if (this._editedExpression) {
138!
1144
            this._editedExpression.inEditMode = false;
×
1145
        }
1146

1147
        if (this.parentExpression) {
138✔
1148
            this.inEditModeChange.emit(this.parentExpression);
16✔
1149
        }
1150

1151
        expressionItem.hovered = false;
138✔
1152
        this.fields = this.selectedEntity ? this.selectedEntity.fields : null;
138✔
1153
        this.selectedField =
138✔
1154
            expressionItem.expression.fieldName ?
138✔
1155
                this.fields?.find(field => field.field === expressionItem.expression.fieldName)
90✔
1156
                : null;
1157
        this.selectedCondition =
138✔
1158
            expressionItem.expression.condition ?
138✔
1159
                expressionItem.expression.condition.name :
1160
                null;
1161
        this.searchValue.value = expressionItem.expression.searchVal instanceof Set ?
138✔
1162
            Array.from(expressionItem.expression.searchVal) :
1163
            expressionItem.expression.searchVal;
1164

1165
        expressionItem.inEditMode = true;
138✔
1166
        this._editedExpression = expressionItem;
138✔
1167
        this.cdr.detectChanges();
138✔
1168

1169
        this.entitySelectOverlaySettings.target = this.entitySelect.getEditElement();
138✔
1170
        this.entitySelectOverlaySettings.excludeFromOutsideClick = [this.entitySelect.getEditElement() as HTMLElement];
138✔
1171
        this.entitySelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
138✔
1172

1173
        if (this.returnFieldSelect) {
138✔
1174
            this.returnFieldSelectOverlaySettings.target = this.returnFieldSelect.getEditElement();
16✔
1175
            this.returnFieldSelectOverlaySettings.excludeFromOutsideClick = [this.returnFieldSelect.getEditElement() as HTMLElement];
16✔
1176
            this.returnFieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
16✔
1177
        }
1178
        if (this.fieldSelect) {
138✔
1179
            this.fieldSelectOverlaySettings.target = this.fieldSelect.getEditElement();
135✔
1180
            this.fieldSelectOverlaySettings.excludeFromOutsideClick = [this.fieldSelect.getEditElement() as HTMLElement];
135✔
1181
            this.fieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
135✔
1182
        }
1183
        if (this.conditionSelect) {
138✔
1184
            this.conditionSelectOverlaySettings.target = this.conditionSelect.getEditElement();
135✔
1185
            this.conditionSelectOverlaySettings.excludeFromOutsideClick = [this.conditionSelect.getEditElement() as HTMLElement];
135✔
1186
            this.conditionSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
135✔
1187
        }
1188

1189
        if (!this.selectedField) {
138✔
1190
            this.fieldSelect.input.nativeElement.focus();
82✔
1191
        } else if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary) {
56✔
1192
            this.conditionSelect?.input.nativeElement.focus();
5✔
1193
        } else {
1194
            const input = this.searchValueInput?.nativeElement || this.picker?.getEditElement();
51✔
1195
            input?.focus();
51✔
1196
        }
1197

1198
        (this.editingInputs?.nativeElement.parentElement as HTMLElement)?.scrollIntoView({ block: "nearest", inline: "nearest" });
138✔
1199
    }
1200

1201
    /**
1202
     * @hidden @internal
1203
     */
1204
    public onConditionSelectChanging(event: ISelectionEventArgs) {
1205
        event.cancel = true;
67✔
1206
        this.selectedCondition = event.newSelection.value;
67✔
1207
        this.conditionSelect.close();
67✔
1208
        this.cdr.detectChanges();
67✔
1209
    }
1210

1211
    /**
1212
     * @hidden @internal
1213
     */
1214
    public onKeyDown(eventArgs: KeyboardEvent) {
1215
        eventArgs.stopPropagation();
×
1216
    }
1217

1218
    /**
1219
     * @hidden @internal
1220
     */
1221
    public onGroupClick(groupContextMenuDropDown: any, targetButton: HTMLButtonElement, groupItem: ExpressionGroupItem) {
1222
        this.exitEditAddMode();
9✔
1223
        this.cdr.detectChanges();
9✔
1224

1225
        this.groupContextMenuDropDown = groupContextMenuDropDown;
9✔
1226
        this.groupContextMenuDropDownOverlaySettings.target = targetButton;
9✔
1227
        this.groupContextMenuDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
9✔
1228
            horizontalDirection: HorizontalAlignment.Right,
1229
            horizontalStartPoint: HorizontalAlignment.Left,
1230
            verticalStartPoint: VerticalAlignment.Bottom
1231
        });
1232

1233
        if (groupContextMenuDropDown.collapsed) {
9✔
1234
            this.contextualGroup = groupItem;
8✔
1235
            groupContextMenuDropDown.open(this.groupContextMenuDropDownOverlaySettings);
8✔
1236
        } else {
1237
            groupContextMenuDropDown.close();
1✔
1238
        }
1239
    }
1240

1241
    /**
1242
     * @hidden @internal
1243
     */
1244
    public getOperator(expressionItem: any) {
1245
        // if (!expressionItem && !this.expressionTree && !this.initialOperator) {
1246
        //     this.initialOperator = 0;
1247
        // }
1248

1249
        const operator = expressionItem ?
90,064✔
1250
            expressionItem.operator :
1251
            this.expressionTree ?
3,226✔
1252
                this.expressionTree.operator :
1253
                this.initialOperator;
1254
        return operator;
90,064✔
1255
    }
1256

1257
    /**
1258
     * @hidden @internal
1259
     */
1260
    public getSwitchGroupText(expressionItem: any) {
1261
        const operator = this.getOperator(expressionItem);
14,997✔
1262
        const condition = operator === FilteringLogic.Or ? this.resourceStrings.igx_query_builder_and_label : this.resourceStrings.igx_query_builder_or_label
14,997✔
1263
        return this.resourceStrings.igx_query_builder_switch_group.replace('{0}', condition.toUpperCase());
14,997✔
1264
    }
1265

1266
    /**
1267
     * @hidden @internal
1268
     */
1269
    public onGroupContextMenuDropDownSelectionChanging(event: ISelectionEventArgs) {
1270
        event.cancel = true;
3✔
1271

1272
        if (event.newSelection.value === 'switchCondition') {
3✔
1273
            const newOperator = (!this.expressionTree ? this.initialOperator : (this.contextualGroup ?? this._expressionTree).operator) === 0 ? 1 : 0;
2!
1274
            this.selectFilteringLogic(newOperator);
2✔
1275
        } else if (event.newSelection.value === 'ungroup') {
1✔
1276
            this.ungroup();
1✔
1277
        }
1278

1279
        this.groupContextMenuDropDown.close();
3✔
1280
    }
1281

1282
    /**
1283
     * @hidden @internal
1284
     */
1285
    public ungroup() {
1286
        const selectedGroup = this.contextualGroup;
1✔
1287
        const parent = selectedGroup.parent;
1✔
1288
        if (parent) {
1✔
1289
            const index = parent.children.indexOf(selectedGroup);
1✔
1290
            parent.children.splice(index, 1, ...selectedGroup.children);
1✔
1291

1292
            for (const expr of selectedGroup.children) {
1✔
1293
                expr.parent = parent;
2✔
1294
            }
1295
        }
1296
        this.commitOperandEdit();
1✔
1297
    }
1298

1299
    /**
1300
     * @hidden @internal
1301
     */
1302
    public selectFilteringLogic(index: number) {
1303
        if (!this.expressionTree) {
2!
1304
            this.initialOperator = index;
×
1305
            return;
×
1306
        }
1307

1308
        if (this.contextualGroup) {
2!
1309
            this.contextualGroup.operator = index as FilteringLogic;
2✔
1310
            this.commitOperandEdit();
2✔
UNCOV
1311
        } else if (this.expressionTree) {
×
UNCOV
1312
            this._expressionTree.operator = index as FilteringLogic;
×
1313
        }
1314

1315
        this.initialOperator = null;
2✔
1316
    }
1317

1318
    /**
1319
     * @hidden @internal
1320
     */
1321
    public getConditionFriendlyName(name: string): string {
1322
        // As we have an 'In' condition already used in ESF to search in a Set, we add the 'Query' suffix to the newly introduced nested query condition names.
1323
        // So instead of in/notIn we end up with 'inQuery'/'notInQuery', hence removing the suffix from the friendly name.
1324
        return this.resourceStrings[`igx_query_builder_filter_${name?.replace('Query', '')}`] || name;
60,837✔
1325
    }
1326

1327
    /**
1328
     * @hidden @internal
1329
     */
1330
    public isDate(value: any) {
1331
        return value instanceof Date;
20,059✔
1332
    }
1333

1334
    /**
1335
     * @hidden @internal
1336
     */
1337
    public invokeClick(eventArgs: KeyboardEvent) {
1338
        if (!this.dragService.dropGhostExpression && this.platform.isActivationKey(eventArgs)) {
2✔
1339
            eventArgs.preventDefault();
2✔
1340
            (eventArgs.currentTarget as HTMLElement).click();
2✔
1341
        }
1342
    }
1343

1344
    /**
1345
     * @hidden @internal
1346
     */
1347
    public openPicker(args: KeyboardEvent) {
1348
        if (this.platform.isActivationKey(args)) {
×
1349
            args.preventDefault();
×
1350
            this.picker.open();
×
1351
        }
1352
    }
1353

1354
    /**
1355
     * @hidden @internal
1356
     */
1357
    public getConditionList(): string[] {
1358
        if (!this.selectedField) return [];
1,720✔
1359

1360
        if (!this.selectedField.filters) {
1,252!
1361
            this.selectedField.filters = this.getFilters(this.selectedField);
×
1362
        }
1363

1364
        if ((this.isAdvancedFiltering() && !this.entities[0].childEntities) ||
1,252✔
1365
            (this.isHierarchicalNestedQuery() && this.selectedEntity.name && !this.selectedEntity.childEntities)) {
1366
            return this.selectedField.filters.conditionList();
207✔
1367
        }
1368

1369
        return this.selectedField.filters.extendedConditionList();
1,045✔
1370
    }
1371

1372
    /**
1373
     * @hidden @internal
1374
     */
1375
    public getFormatter(field: string) {
1376
        return this.fields?.find(el => el.field === field)?.formatter;
72,298✔
1377
    }
1378

1379
    /**
1380
     * @hidden @internal
1381
     */
1382
    public getFormat(field: string) {
1383
        return this.fields?.find(el => el.field === field).pipeArgs.format;
1,845✔
1384
    }
1385

1386
    /**
1387
     * @hidden @internal
1388
     *
1389
     * used by the grid
1390
     */
1391
    public setAddButtonFocus() {
1392
        if (this.addRootAndGroupButton) {
53!
1393
            this.addRootAndGroupButton.nativeElement.focus();
×
1394
        } else if (this.addConditionButton) {
53✔
1395
            this.addConditionButton.nativeElement.focus();
20✔
1396
        }
1397
    }
1398

1399
    /**
1400
     * @hidden @internal
1401
     */
1402
    public context(expression: ExpressionItem, afterExpression?: ExpressionItem) {
1403
        return {
56,371✔
1404
            $implicit: expression,
1405
            afterExpression
1406
        };
1407
    }
1408

1409
    public formatReturnFields(innerTree: IFilteringExpressionsTree) {
1410
        const returnFields = innerTree.returnFields;
3,642✔
1411
        let text = returnFields.join(', ');
3,642✔
1412
        const innerTreeEntity = this.entities?.find(el => el.name === innerTree.entity);
3,642✔
1413
        if (returnFields.length === innerTreeEntity?.fields.length) {
3,642!
1414
            text = this.resourceStrings.igx_query_builder_all_fields;
×
1415
        } else {
1416
            text = returnFields.join(', ');
3,642✔
1417
            text = text.length > 25 ? text.substring(0, 25) + ' ...' : text;
3,642!
1418
        }
1419
        return text;
3,642✔
1420
    }
1421

1422
    public isInEditMode(): boolean {
1423
        return !this.parentExpression || (this.parentExpression && this.parentExpression.inEditMode);
65,049✔
1424
    }
1425

1426
    public onInEditModeChanged(expressionItem: ExpressionOperandItem) {
1427
        if (!expressionItem.inEditMode) {
16!
1428
            this.enterExpressionEdit(expressionItem);
×
1429
        }
1430
    }
1431

1432
    public getExpressionTreeCopy(expressionTree: IExpressionTree, shouldAssignInnerQueryExprTree?: boolean): IExpressionTree {
1433
        if (!expressionTree) {
322✔
1434
            return null;
290✔
1435
        }
1436

1437
        const exprTreeCopy = new FilteringExpressionsTree(expressionTree.operator, expressionTree.fieldName, expressionTree.entity, expressionTree.returnFields);
32✔
1438
        exprTreeCopy.filteringOperands = [];
32✔
1439

1440
        expressionTree.filteringOperands.forEach(o => isTree(o) ? exprTreeCopy.filteringOperands.push(this.getExpressionTreeCopy(o)) : exprTreeCopy.filteringOperands.push(o));
57!
1441

1442
        if (!this.innerQueryNewExpressionTree && shouldAssignInnerQueryExprTree) {
32✔
1443
            this.innerQueryNewExpressionTree = exprTreeCopy;
19✔
1444
        }
1445

1446
        return exprTreeCopy;
32✔
1447
    }
1448

1449
    public onSelectAllClicked() {
1450
        if (
2✔
1451
            (this._selectedReturnFields.length > 0 && this._selectedReturnFields.length < this._selectedEntity.fields.length) ||
5✔
1452
            this._selectedReturnFields.length == this._selectedEntity.fields.length
1453
        ) {
1454
            this.returnFieldsCombo.deselectAllItems();
1✔
1455
        } else {
1456
            this.returnFieldsCombo.selectAllItems();
1✔
1457
        }
1458
    }
1459

1460
    public onReturnFieldSelectChanging(event: IComboSelectionChangingEventArgs | ISelectionEventArgs) {
1461
        let newSelection = [];
11✔
1462
        if (Array.isArray(event.newSelection)) {
11✔
1463
            newSelection = event.newSelection.map(item => item.field)
11✔
1464
        } else {
1465
            newSelection.push(event.newSelection.value);
6✔
1466
            this._selectedReturnFields = newSelection;
6✔
1467
        }
1468

1469
        this.initExpressionTree(this.selectedEntity.name, newSelection);
11✔
1470
    }
1471

1472
    public initExpressionTree(selectedEntityName: string, selectedReturnFields: string[]) {
1473
        if (!this._expressionTree) {
50✔
1474
            this._expressionTree = this.createExpressionTreeFromGroupItem(new ExpressionGroupItem(FilteringLogic.And, this.rootGroup), selectedEntityName, selectedReturnFields);
34✔
1475
        }
1476

1477
        if (!this.parentExpression) {
50✔
1478
            this.expressionTreeChange.emit(this._expressionTree);
38✔
1479
        }
1480
    }
1481

1482
    public getSearchValueTemplateContext(defaultSearchValueTemplate): IgxQueryBuilderSearchValueContext {
1483
        const ctx = {
1,720✔
1484
            $implicit: this.searchValue,
1485
            selectedField: this.selectedField,
1486
            selectedCondition: this.selectedCondition,
1487
            defaultSearchValueTemplate: defaultSearchValueTemplate
1488
        };
1489
        return ctx;
1,720✔
1490
    }
1491

1492
    private getPipeArgs(field: FieldType) {
1493
        let pipeArgs = {...field.pipeArgs};
3,279✔
1494
        if (!pipeArgs) {
3,279!
1495
            pipeArgs = { digitsInfo: DEFAULT_PIPE_DIGITS_INFO };
×
1496
        }
1497

1498
        if (!pipeArgs.format) {
3,279✔
1499
            pipeArgs.format = field.dataType === GridColumnDataType.Time ?
3,044!
1500
                DEFAULT_PIPE_TIME_FORMAT : field.dataType === GridColumnDataType.DateTime ?
3,044!
1501
                    DEFAULT_PIPE_DATE_TIME_FORMAT : DEFAULT_PIPE_DATE_FORMAT;
1502
        }
1503

1504
        return pipeArgs;
3,279✔
1505
    }
1506

1507
    private selectDefaultCondition() {
1508
        if (this.selectedField && this.selectedField.filters) {
176✔
1509
            this.selectedCondition = this.selectedField.filters.conditionList().indexOf('equals') >= 0 ? 'equals' : this.selectedField.filters.conditionList()[0];
126✔
1510
        }
1511
    }
1512

1513
    private getFilters(field: FieldType) {
1514
        if (!field.filters) {
3,279✔
1515
            switch (field.dataType) {
3,044!
1516
                case GridColumnDataType.Boolean:
1517
                    return IgxBooleanFilteringOperand.instance();
753✔
1518
                case GridColumnDataType.Number:
1519
                case GridColumnDataType.Currency:
1520
                case GridColumnDataType.Percent:
1521
                    return IgxNumberFilteringOperand.instance();
1,131✔
1522
                case GridColumnDataType.Date:
1523
                    return IgxDateFilteringOperand.instance();
380✔
1524
                case GridColumnDataType.Time:
1525
                    return IgxTimeFilteringOperand.instance();
×
1526
                case GridColumnDataType.DateTime:
1527
                    return IgxDateTimeFilteringOperand.instance();
×
1528
                case GridColumnDataType.String:
1529
                default:
1530
                    return IgxStringFilteringOperand.instance();
780✔
1531
            }
1532
        } else {
1533
            return field.filters;
235✔
1534
        }
1535
    }
1536

1537

1538
    private addGroup(operator: FilteringLogic, parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
1539
        this.cancelOperandAdd();
3✔
1540

1541
        const groupItem = new ExpressionGroupItem(operator, parent);
3✔
1542

1543
        if (parent) {
3!
1544
            if (afterExpression) {
3!
1545
                const index = parent.children.indexOf(afterExpression);
×
1546
                parent.children.splice(index + 1, 0, groupItem);
×
1547
            } else {
1548
                parent.children.push(groupItem);
3✔
1549
            }
1550
        } else {
1551
            this.rootGroup = groupItem;
×
1552
        }
1553

1554
        this.addCondition(groupItem);
3✔
1555
        this.currentGroup = groupItem;
3✔
1556
    }
1557

1558
    private createExpressionGroupItem(expressionTree: IExpressionTree, parent?: ExpressionGroupItem, entityName?: string): ExpressionGroupItem {
1559
        let groupItem: ExpressionGroupItem;
1560
        if (expressionTree) {
862✔
1561
            groupItem = new ExpressionGroupItem(expressionTree.operator, parent);
688✔
1562
            if (!expressionTree.filteringOperands) {
688!
1563
                return groupItem;
×
1564
            }
1565

1566
            for (let i = 0; i < expressionTree.filteringOperands.length; i++) {
688✔
1567
                const expr = expressionTree.filteringOperands[i];
1,495✔
1568

1569
                if (isTree(expr)) {
1,495✔
1570
                    groupItem.children.push(this.createExpressionGroupItem(expr, groupItem, expressionTree.entity));
88✔
1571
                } else {
1572
                    const filteringExpr = expr as IFilteringExpression;
1,407✔
1573
                    const exprCopy: IFilteringExpression = {
1,407✔
1574
                        fieldName: filteringExpr.fieldName,
1575
                        condition: filteringExpr.condition,
1576
                        conditionName: filteringExpr.condition?.name || filteringExpr.conditionName,
1,407!
1577
                        searchVal: filteringExpr.searchVal,
1578
                        searchTree: filteringExpr.searchTree,
1579
                        ignoreCase: filteringExpr.ignoreCase
1580
                    };
1581
                    const operandItem = new ExpressionOperandItem(exprCopy, groupItem);
1,407✔
1582
                    const field = this.fields?.find(el => el.field === filteringExpr.fieldName);
2,012✔
1583
                    operandItem.fieldLabel = field?.label || field?.header || field?.field;
1,407✔
1584
                    if (this._expandedExpressions.filter(e => e.searchTree == operandItem.expression.searchTree).length > 0) {
1,407!
1585
                        operandItem.expanded = true;
×
1586
                    }
1587
                    groupItem.children.push(operandItem);
1,407✔
1588
                }
1589
            }
1590

1591

1592
            if (expressionTree.entity) {
688✔
1593
                entityName = expressionTree.entity;
629✔
1594
            }
1595
            const entity = this.entities?.find(el => el.name === entityName);
977✔
1596
            if (entity) {
688✔
1597
                this.fields = entity.fields;
627✔
1598
            }
1599

1600
            this._selectedEntity = this.entities?.find(el => el.name === entityName);
977✔
1601
            this._selectedReturnFields =
688✔
1602
                !expressionTree.returnFields || expressionTree.returnFields.includes('*') || expressionTree.returnFields.includes('All') || expressionTree.returnFields.length === 0
2,892✔
1603
                    ? this.fields?.map(f => f.field)
1,120✔
1604
                    : this.fields?.filter(f => expressionTree.returnFields.indexOf(f.field) >= 0).map(f => f.field);
1,778✔
1605
        }
1606
        return groupItem;
862✔
1607
    }
1608

1609
    private createExpressionTreeFromGroupItem(groupItem: ExpressionGroupItem, entity?: string, returnFields?: string[]): FilteringExpressionsTree {
1610
        if (!groupItem) {
152✔
1611
            return null;
5✔
1612
        }
1613

1614
        const expressionTree = new FilteringExpressionsTree(groupItem.operator, undefined, entity, returnFields);
147✔
1615

1616
        for (let i = 0; i < groupItem.children.length; i++) {
147✔
1617
            const item = groupItem.children[i];
200✔
1618

1619
            if (item instanceof ExpressionGroupItem) {
200✔
1620
                const subTree = this.createExpressionTreeFromGroupItem((item as ExpressionGroupItem), entity, returnFields);
19✔
1621
                expressionTree.filteringOperands.push(subTree);
19✔
1622
            } else {
1623
                expressionTree.filteringOperands.push((item as ExpressionOperandItem).expression);
181✔
1624
            }
1625
        }
1626

1627
        return expressionTree;
147✔
1628
    }
1629

1630
    private scrollElementIntoView(target: HTMLElement) {
1631
        const container = this.expressionsContainer.nativeElement;
668✔
1632
        const targetOffset = target.offsetTop - container.offsetTop;
668✔
1633
        const delta = 10;
668✔
1634

1635
        if (container.scrollTop + delta > targetOffset) {
668✔
1636
            container.scrollTop = targetOffset - delta;
447✔
1637
        } else if (container.scrollTop + container.clientHeight < targetOffset + target.offsetHeight + delta) {
221✔
1638
            container.scrollTop = targetOffset + target.offsetHeight + delta - container.clientHeight;
216✔
1639
        }
1640
    }
1641

1642
    private focusEditedExpressionChip() {
1643
        if (this._timeoutId) {
60✔
1644
            clearTimeout(this._timeoutId);
5✔
1645
        }
1646

1647
        this._timeoutId = setTimeout(() => {
60✔
1648
            if (this._lastFocusedChipIndex != -1) {
60✔
1649
                //Sort the expression chip list.
1650
                //If there was a recent drag&drop and the tree hasn't rerendered(child query), they will be unordered
1651
                const sortedChips = this.expressionsChips.toArray().sort(function (a, b) {
58✔
1652
                    if (a === b) return 0;
75!
1653
                    if (a.chipArea.nativeElement.compareDocumentPosition(b.chipArea.nativeElement) & 2) {
75✔
1654
                        // b comes before a
1655
                        return 1;
68✔
1656
                    }
1657
                    return -1;
7✔
1658
                });
1659
                const chipElement = sortedChips[this._lastFocusedChipIndex]?.nativeElement;
58✔
1660
                if (chipElement) {
58✔
1661
                    chipElement.focus();
50✔
1662
                }
1663
                this._lastFocusedChipIndex = -1;
58✔
1664
                this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
58✔
1665
            }
1666
        }, this._focusDelay);
1667
    }
1668

1669
    private init() {
1670
        this.cancelOperandAdd();
508✔
1671
        this.cancelOperandEdit();
508✔
1672

1673
        // Ignore values of certain properties for the comparison
1674
        const propsToIgnore = ['parent', 'hovered', 'ignoreCase', 'inEditMode', 'inAddMode'];
508✔
1675
        const propsReplacer = function replacer(key, value) {
508✔
1676
            if (propsToIgnore.indexOf(key) >= 0) {
19,600✔
1677
                return undefined;
2,611✔
1678
            } else {
1679
                return value;
16,989✔
1680
            }
1681
        };
1682

1683
        // Skip root being recreated if the same
1684
        const newRootGroup = this.createExpressionGroupItem(this.expressionTree);
508✔
1685
        if (JSON.stringify(this.rootGroup, propsReplacer) !== JSON.stringify(newRootGroup, propsReplacer)) {
508✔
1686
            this.rootGroup = this.createExpressionGroupItem(this.expressionTree);
260✔
1687
            this.currentGroup = this.rootGroup;
260✔
1688
        }
1689

1690
        if (this.rootGroup?.children?.length == 0) {
508✔
1691
            this.rootGroup = null;
40✔
1692
            this.currentGroup = null;
40✔
1693
        }
1694
    }
1695

1696
    private initLocale() {
1697
        this._defaultLocale = getCurrentI18n();
304✔
1698
        this._locale = this._localeId !== DEFAULT_LOCALE ? this._localeId : this._locale;
304!
1699
        onResourceChangeHandle(this.destroy$, this.onResourceChange, this);
304✔
1700
    }
1701

1702
    private onResourceChange(args: CustomEvent<IResourceChangeEventArgs>) {
1703
        this._defaultLocale = args.detail.newLocale;
4✔
1704
        if (!this._locale) {
4✔
1705
            this._defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN, false);
1✔
1706
        }
1707
    }
1708

1709
    /** rootGroup is recreated after clicking Apply, which sets new expressionTree and calls init()*/
1710
    protected trackExpressionItem = trackByIdentity;
304✔
1711
}
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