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

IgniteUI / igniteui-angular / 20191011486

13 Dec 2025 10:50AM UTC coverage: 91.504% (+0.003%) from 91.501%
20191011486

push

github

web-flow
refactor(*): New localization implementation. (#16034)

14236 of 16778 branches covered (84.85%)

378 of 419 new or added lines in 61 files covered. (90.21%)

1 existing line in 1 file now uncovered.

28649 of 31309 relevant lines covered (91.5%)

34896.51 hits per line

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

92.31
/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
    CloseScrollStrategy,
36
    ConnectedPositioningStrategy,
37
    IgxPickerToggleComponent,
38
    IgxPickerClearComponent,
39
    getCurrentResourceStrings,
40
    DEFAULT_LOCALE,
41
    onResourceChangeHandle,
42
    IgxDateFormatterPipe,
43
    isTree,
44
    IgxOverlayOutletDirective
45
} from 'igniteui-angular/core';
46
import { IgxDatePickerComponent } from 'igniteui-angular/date-picker';
47

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

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

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

132
    /**
133
     * @hidden @internal
134
     */
135
    public _expressionTree: IExpressionTree;
136

137
    /**
138
     * @hidden @internal
139
     */
140
    public _expressionTreeCopy: IExpressionTree;
141

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

149
    /**
150
     * Sets/gets the entities.
151
     */
152
    @Input()
153
    public entities: EntityType[];
154

155
    /**
156
     * Sets/gets the parent query builder component.
157
     */
158
    @Input()
159
    public queryBuilder: IgxQueryBuilderComponent;
160

161
    /**
162
     * Sets/gets the search value template.
163
     */
164
    @Input()
165
    public searchValueTemplate: TemplateRef<IgxQueryBuilderSearchValueTemplateDirective> = null;
304✔
166

167
    /**
168
    * Returns the parent expression operand.
169
    */
170
    @Input()
171
    public get parentExpression(): ExpressionOperandItem {
172
        return this._parentExpression;
153,946✔
173
    }
174

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

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

190
        return this._fields;
58,679✔
191
    }
192

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

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

202
        if (!this._fields && this.isAdvancedFiltering()) {
804✔
203
            this._fields = this.entities[0].fields;
1✔
204
        }
205
    }
206

207
    /**
208
    * Returns the expression tree.
209
    */
210
    public get expressionTree(): IExpressionTree {
211
        return this._expressionTree;
6,773✔
212
    }
213

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

225
        if (!this._preventInit) {
517✔
226
            this.init();
508✔
227
        }
228
    }
229

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

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

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

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

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

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

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

281
    @ViewChild('entitySelect', { read: IgxSelectComponent })
282
    protected entitySelect: IgxSelectComponent;
283

284
    @ViewChild('editingInputs', { read: ElementRef })
285
    private editingInputs: ElementRef;
286

287
    @ViewChild('returnFieldsCombo', { read: IgxComboComponent })
288
    private returnFieldsCombo: IgxComboComponent;
289

290
    @ViewChild('returnFieldSelect', { read: IgxSelectComponent })
291
    protected returnFieldSelect: IgxSelectComponent;
292

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

296
    @ViewChild('conditionSelect', { read: IgxSelectComponent })
297
    private conditionSelect: IgxSelectComponent;
298

299
    @ViewChild('searchValueInput', { read: ElementRef })
300
    private searchValueInput: ElementRef;
301

302
    @ViewChild('picker')
303
    private picker: IgxDatePickerComponent | IgxTimePickerComponent;
304

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

308
    @ViewChild('addConditionButton', { read: ElementRef })
309
    private addConditionButton: ElementRef;
310

311
    @ViewChild('entityChangeDialog', { read: IgxDialogComponent })
312
    private entityChangeDialog: IgxDialogComponent;
313

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

317
    @ViewChild('groupContextMenuDropDown', { read: IgxDropDownComponent })
318
    private groupContextMenuDropDown: IgxDropDownComponent;
319

320
    /**
321
     * @hidden @internal
322
     */
323
    @ViewChildren(IgxChipComponent, { read: IgxChipComponent })
324
    public expressionsChips: QueryList<IgxChipComponent>;
325

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

335
        this._editingInputsContainer = value;
819✔
336
    }
337

338
    /** @hidden */
339
    protected get editingInputsContainer(): ElementRef {
340
        return this._editingInputsContainer;
×
341
    }
342

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

352
        this._currentGroupButtonsContainer = value;
819✔
353
    }
354

355
    /** @hidden */
356
    protected get currentGroupButtonsContainer(): ElementRef {
357
        return this._currentGroupButtonsContainer;
×
358
    }
359

360
    @ViewChild('expressionsContainer')
361
    private expressionsContainer: ElementRef;
362

363
    @ViewChild('overlayOutlet', { read: IgxOverlayOutletDirective, static: true })
364
    private overlayOutlet: IgxOverlayOutletDirective;
365

366
    @ViewChildren(IgxQueryBuilderTreeComponent)
367
    private innerQueries: QueryList<IgxQueryBuilderTreeComponent>;
368

369
    /**
370
     * @hidden @internal
371
     */
372
    public innerQueryNewExpressionTree: IExpressionTree;
373

374
    /**
375
     * @hidden @internal
376
     */
377
    public rootGroup: ExpressionGroupItem;
378

379
    /**
380
     * @hidden @internal
381
     */
382
    public selectedExpressions: ExpressionOperandItem[] = [];
304✔
383

384
    /**
385
     * @hidden @internal
386
     */
387
    public currentGroup: ExpressionGroupItem;
388

389
    /**
390
     * @hidden @internal
391
     */
392
    public contextualGroup: ExpressionGroupItem;
393

394
    /**
395
     * @hidden @internal
396
     */
397
    public filteringLogics;
398

399
    /**
400
     * @hidden @internal
401
     */
402
    public selectedCondition: string;
403

404
    /**
405
     * @hidden @internal
406
     */
407
    public searchValue: { value: any } = { value: null };
304✔
408

409
    /**
410
     * @hidden @internal
411
     */
412
    public pickerOutlet: IgxOverlayOutletDirective | ElementRef;
413

414
    /**
415
     * @hidden @internal
416
     */
417
    public prevFocusedExpression: ExpressionOperandItem;
418

419
    /**
420
     * @hidden @internal
421
     */
422
    public initialOperator = 0;
304✔
423

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

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

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

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

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

469
    /**
470
     * @hidden @internal
471
     */
472
    public groupContextMenuDropDownOverlaySettings: OverlaySettings = {
304✔
473
        scrollStrategy: new AbsoluteScrollStrategy(),
474
        modal: false,
475
        closeOnOutsideClick: true
476
    };
477

478
    private destroy$ = new Subject<any>();
304✔
479
    private _timeoutId: any;
480
    private _lastFocusedChipIndex: number;
481
    private _focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
304✔
482
    private _parentExpression: ExpressionOperandItem;
483
    private _selectedEntity: EntityType;
484
    private _selectedReturnFields: string | string[];
485
    private _selectedField: FieldType;
486
    private _editingInputsContainer: ElementRef;
487
    private _currentGroupButtonsContainer: ElementRef;
488
    private _addModeExpression: ExpressionOperandItem;
489
    private _editedExpression: ExpressionOperandItem;
490
    private _preventInit = false;
304✔
491
    private _prevFocusedContainer: ElementRef;
492
    private _expandedExpressions: IFilteringExpression[] = [];
304✔
493
    private _fields: FieldType[];
494
    private _locale;
495
    private _defaultLocale;
496
    private _entityNewValue: EntityType;
497
    private _resourceStrings = null;
304✔
498
    private _defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN);
304✔
499

500
    /**
501
     * Returns if the select entity dropdown at the root level is disabled after the initial selection.
502
     */
503
    public get disableEntityChange(): boolean {
504

505
        return !this.parentExpression && this.selectedEntity ? this.queryBuilder.disableEntityChange : false;
12,244✔
506
    }
507

508
    /**
509
     * Returns if the fields combo at the root level is disabled.
510
     */
511
    public get disableReturnFieldsChange(): boolean {
512

513
        return !this.selectedEntity || this.queryBuilder.disableReturnFieldsChange;
5,876✔
514
    }
515

516
    /**
517
     * Returns the current level.
518
     */
519
    public get level(): number {
520
        let parent = this.elRef.nativeElement.parentElement;
11,433✔
521
        let _level = 0;
11,433✔
522
        while (parent) {
11,433✔
523
            if (parent.localName === 'igx-query-builder-tree') {
96,407✔
524
                _level++;
6,181✔
525
            }
526
            parent = parent.parentElement;
96,407✔
527
        }
528
        return _level;
11,433✔
529
    }
530

531
    private _positionSettings = {
304✔
532
        horizontalStartPoint: HorizontalAlignment.Right,
533
        verticalStartPoint: VerticalAlignment.Top
534
    };
535

536
    private _overlaySettings: OverlaySettings = {
304✔
537
        closeOnOutsideClick: false,
538
        modal: false,
539
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings),
540
        scrollStrategy: new CloseScrollStrategy()
541
    };
542

543
    /** @hidden */
544
    protected isAdvancedFiltering(): boolean {
545
        return (this.entities?.length === 1 && !this.entities[0]?.name) ||
20,968✔
546
            this.entities?.find(e => e.childEntities?.length > 0) !== undefined ||
37,038✔
547
            (this.entities?.length > 0 && this.queryBuilder?.entities?.length > 0 && this.entities !== this.queryBuilder?.entities);
548
    }
549

550
    /** @hidden */
551
    protected isHierarchicalNestedQuery(): boolean {
552
        return this.queryBuilder.entities !== this.entities
14,310✔
553
    }
554

555
    /** @hidden */
556
    protected isSearchValueInputDisabled(): boolean {
557
        return !this.selectedField ||
1,699✔
558
            !this.selectedCondition ||
559
            (this.selectedField &&
560
                (this.selectedField.filters.condition(this.selectedCondition).isUnary ||
561
                    this.selectedField.filters.condition(this.selectedCondition).isNestedQuery));
562
    }
563

564
    constructor() {
565
        this.initLocale();
304✔
566
        this.dragService.register(this, this.elRef);
304✔
567
    }
568

569
    /**
570
     * @hidden @internal
571
     */
572
    public ngAfterViewInit(): void {
573
        this._overlaySettings.outlet = this.overlayOutlet;
304✔
574
        this.entitySelectOverlaySettings.outlet = this.overlayOutlet;
304✔
575
        this.fieldSelectOverlaySettings.outlet = this.overlayOutlet;
304✔
576
        this.conditionSelectOverlaySettings.outlet = this.overlayOutlet;
304✔
577
        this.returnFieldSelectOverlaySettings.outlet = this.overlayOutlet;
304✔
578
        this.addExpressionDropDownOverlaySettings.outlet = this.overlayOutlet;
304✔
579
        this.groupContextMenuDropDownOverlaySettings.outlet = this.overlayOutlet;
304✔
580

581
        if (this.isAdvancedFiltering() && this.entities?.length === 1) {
304✔
582
            this.selectedEntity = this.entities[0].name;
65✔
583
            if (this._selectedEntity.fields.find(f => f.field === this.expectedReturnField)) {
346✔
584
                this._selectedReturnFields = [this.expectedReturnField];
5✔
585
            }
586
        }
587

588
        // Trigger additional change detection cycle
589
        this.cdr.detectChanges();
304✔
590
    }
591

592
    /**
593
     * @hidden @internal
594
     */
595
    public ngOnDestroy(): void {
596
        this.destroy$.next(true);
304✔
597
        this.destroy$.complete();
304✔
598
    }
599

600
    /**
601
     * @hidden @internal
602
     */
603
    public set selectedEntity(value: string) {
604
        this._selectedEntity = this.entities?.find(el => el.name === value);
65✔
605
    }
606

607
    /**
608
     * @hidden @internal
609
     */
610
    public get selectedEntity(): EntityType {
611
        return this._selectedEntity;
85,530✔
612
    }
613

614
    /**
615
     * @hidden @internal
616
     */
617
    public onEntitySelectChanging(event: ISelectionEventArgs) {
618
        event.cancel = true;
43✔
619
        this._entityNewValue = event.newSelection.value;
43✔
620
        if (event.oldSelection.value && this.queryBuilder.showEntityChangeDialog) {
43✔
621
            this.entityChangeDialog.open();
6✔
622
        } else {
623
            this.onEntityChangeConfirm();
37✔
624
        }
625
    }
626

627
    /**
628
     * @hidden
629
     */
630
    public onShowEntityChangeDialogChange(eventArgs: IChangeCheckboxEventArgs) {
631
        this.queryBuilder.showEntityChangeDialog = !eventArgs.checked;
1✔
632
    }
633

634
    /**
635
     * @hidden
636
     */
637
    public onEntityChangeCancel() {
638
        this.entityChangeDialog.close();
3✔
639
        this.entitySelect.close();
3✔
640
        this._entityNewValue = null;
3✔
641
    }
642

643
    /**
644
     * @hidden
645
     */
646
    public onEntityChangeConfirm() {
647
        if (this._parentExpression) {
39✔
648
            this._expressionTree = this.createExpressionTreeFromGroupItem(this.createExpressionGroupItem(this._expressionTree));
6✔
649
        }
650

651
        this._selectedEntity = this._entityNewValue;
39✔
652
        if (!this._selectedEntity.fields) {
39!
653
            this._selectedEntity.fields = [];
×
654
        }
655
        this.fields = this._entityNewValue ? this._entityNewValue.fields : [];
39!
656

657
        if (this._selectedEntity.fields.find(f => f.field === this.expectedReturnField)) {
150✔
658
            this._selectedReturnFields = [this.expectedReturnField];
6✔
659
        } else {
660
            this._selectedReturnFields = this.parentExpression ? [] : this._entityNewValue.fields?.map(f => f.field);
132!
661
        }
662

663
        if (this._expressionTree) {
39✔
664
            this._expressionTree.entity = this._entityNewValue.name;
5✔
665

666
            const returnFields = Array.isArray(this._selectedReturnFields) ? this._selectedReturnFields : [this._selectedReturnFields];
5!
667
            this._expressionTree.returnFields = this.fields.length === returnFields.length ? ['*'] : returnFields;
5✔
668

669
            this._expressionTree.filteringOperands = [];
5✔
670

671
            this._editedExpression = null;
5✔
672
            if (!this.parentExpression) {
5✔
673
                this.expressionTreeChange.emit(this._expressionTree);
4✔
674
            }
675

676
            this.rootGroup = null;
5✔
677
            this.currentGroup = this.rootGroup;
5✔
678
        }
679

680
        this._selectedField = null;
39✔
681
        this.selectedCondition = null;
39✔
682
        this.searchValue.value = null;
39✔
683

684
        this.entityChangeDialog.close();
39✔
685
        this.entitySelect.close();
39✔
686

687
        this._entityNewValue = null;
39✔
688
        this.innerQueryNewExpressionTree = null;
39✔
689

690
        this.initExpressionTree(this._selectedEntity.name, this.selectedReturnFields);
39✔
691
    }
692

693
    /**
694
     * @hidden @internal
695
     */
696
    public set selectedReturnFields(value: string[]) {
697
        if (this._selectedReturnFields !== value) {
5✔
698
            this._selectedReturnFields = value;
5✔
699

700
            if (this._expressionTree && !this.parentExpression) {
5✔
701
                this._expressionTree.returnFields = value.length === this.fields.length ? ['*'] : value;
5✔
702
                this.expressionTreeChange.emit(this._expressionTree);
5✔
703
            }
704
        }
705
    }
706

707
    /**
708
     * @hidden @internal
709
     */
710
    public get selectedReturnFields(): string[] {
711
        if (typeof this._selectedReturnFields == 'string') {
58,216!
712
            return [this._selectedReturnFields];
×
713
        }
714
        return this._selectedReturnFields;
58,216✔
715
    }
716

717
    /**
718
     * @hidden @internal
719
     */
720
    public set selectedField(value: FieldType) {
721
        const oldValue = this._selectedField;
208✔
722

723
        if (this._selectedField !== value) {
208✔
724
            this._selectedField = value;
176✔
725
            if (this._selectedField && !this._selectedField.dataType) {
176!
726
                this._selectedField.filters = this.getFilters(this._selectedField);
×
727
            }
728

729
            this.selectDefaultCondition();
176✔
730
            if (oldValue && this._selectedField && this._selectedField.dataType !== oldValue.dataType) {
176✔
731
                this.searchValue.value = null;
17✔
732
                this.cdr.detectChanges();
17✔
733
            }
734
        }
735
    }
736

737
    /**
738
     * @hidden @internal
739
     */
740
    public get selectedField(): FieldType {
741
        return this._selectedField;
57,604✔
742
    }
743

744
    /**
745
     * @hidden @internal
746
     *
747
     * used by the grid
748
     */
749
    public setPickerOutlet(outlet?: IgxOverlayOutletDirective | ElementRef) {
750
        this.pickerOutlet = outlet;
59✔
751
    }
752

753
    /**
754
     * @hidden @internal
755
     *
756
     * used by the grid
757
     */
758
    public get isContextMenuVisible(): boolean {
759
        return !this.groupContextMenuDropDown.collapsed;
×
760
    }
761

762
    /**
763
     * @hidden @internal
764
     */
765
    public get hasEditedExpression(): boolean {
766
        return this._editedExpression !== undefined && this._editedExpression !== null;
31,369✔
767
    }
768

769
    /**
770
     * @hidden @internal
771
     */
772
    public addCondition(parent: ExpressionGroupItem, afterExpression?: ExpressionOperandItem, isUIInteraction?: boolean) {
773
        this.cancelOperandAdd();
82✔
774

775
        const operandItem = new ExpressionOperandItem({
82✔
776
            fieldName: null,
777
            condition: null,
778
            conditionName: null,
779
            ignoreCase: true,
780
            searchVal: null
781
        }, parent);
782

783
        const groupItem = new ExpressionGroupItem(this.getOperator(null) ?? FilteringLogic.And, parent);
82!
784
        this.contextualGroup = groupItem;
82✔
785
        this.initialOperator = null;
82✔
786

787
        this._lastFocusedChipIndex = this._lastFocusedChipIndex === undefined ? -1 : this._lastFocusedChipIndex;
82✔
788

789
        if (parent) {
82✔
790
            if (afterExpression) {
11✔
791
                const index = parent.children.indexOf(afterExpression);
1✔
792
                parent.children.splice(index + 1, 0, operandItem);
1✔
793
            } else {
794
                parent.children.push(operandItem);
10✔
795
            }
796
            this._lastFocusedChipIndex++;
11✔
797
        } else {
798
            this.rootGroup = groupItem;
71✔
799
            operandItem.parent = groupItem;
71✔
800
            this.rootGroup.children.push(operandItem);
71✔
801
            this._lastFocusedChipIndex = 0;
71✔
802
        }
803

804
        this._focusDelay = 250;
82✔
805

806
        if (isUIInteraction && !afterExpression) {
82✔
807
            this._lastFocusedChipIndex = this.expressionsChips.length;
78✔
808
            this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
78✔
809
        }
810

811
        this.enterExpressionEdit(operandItem);
82✔
812
    }
813

814
    /**
815
     * @hidden @internal
816
     */
817
    public addReverseGroup(parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
818
        parent = parent ?? this.rootGroup;
3!
819

820
        if (parent.operator === FilteringLogic.And) {
3✔
821
            this.addGroup(FilteringLogic.Or, parent, afterExpression);
2✔
822
        } else {
823
            this.addGroup(FilteringLogic.And, parent, afterExpression);
1✔
824
        }
825
    }
826

827
    /**
828
     * @hidden @internal
829
     */
830
    public endGroup(groupItem: ExpressionGroupItem) {
831
        this.currentGroup = groupItem.parent;
×
832
    }
833

834
    /**
835
     * @hidden @internal
836
     */
837
    public commitExpression() {
838
        this.commitOperandEdit();
45✔
839
        this.focusEditedExpressionChip();
45✔
840
    }
841

842
    /**
843
     * @hidden @internal
844
     */
845
    public discardExpression(expressionItem?: ExpressionOperandItem) {
846
        this.cancelOperandEdit();
11✔
847
        if (expressionItem && expressionItem.expression.fieldName) {
11✔
848
            this.focusEditedExpressionChip();
4✔
849
        }
850
    }
851

852
    /**
853
     * @hidden @internal
854
     */
855
    public commitOperandEdit() {
856
        const actualSearchValue = this.searchValue.value;
60✔
857
        if (this._editedExpression) {
60✔
858
            this._editedExpression.expression.fieldName = this.selectedField.field;
57✔
859
            this._editedExpression.expression.condition = this.selectedField.filters.condition(this.selectedCondition);
57✔
860
            this._editedExpression.expression.conditionName = this.selectedCondition;
57✔
861
            this._editedExpression.expression.searchVal = DataUtil.parseValue(this.selectedField.dataType, actualSearchValue) || actualSearchValue;
57✔
862
            this._editedExpression.fieldLabel = this.selectedField.label
57!
863
                ? this.selectedField.label
864
                : this.selectedField.header
57✔
865
                    ? this.selectedField.header
866
                    : this.selectedField.field;
867

868
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0]
57✔
869
            if (innerQuery && this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) {
57✔
870
                innerQuery.exitEditAddMode();
8✔
871
                this._editedExpression.expression.searchTree = this.getExpressionTreeCopy(innerQuery.expressionTree);
8✔
872
                const returnFields = innerQuery.selectedReturnFields.length > 0 ?
8!
873
                                        innerQuery.selectedReturnFields :
874
                                        [innerQuery.fields[0].field];
875
                this._editedExpression.expression.searchTree.returnFields = returnFields;
8✔
876
            } else {
877
                this._editedExpression.expression.searchTree = null;
49✔
878
            }
879
            this.innerQueryNewExpressionTree = null;
57✔
880

881
            if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary || this.selectedField.filters.condition(this.selectedCondition)?.isNestedQuery) {
57✔
882
                this._editedExpression.expression.searchVal = null;
13✔
883
            }
884

885
            this._editedExpression.inEditMode = false;
57✔
886
            this._editedExpression = null;
57✔
887
        }
888

889
        if (this.selectedReturnFields.length === 0) {
60!
890
            this.selectedReturnFields = this.fields.map(f => f.field);
×
891
        }
892

893
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, this.selectedEntity?.name, this.selectedReturnFields);
60✔
894
        if (!this.parentExpression) {
60✔
895
            this.expressionTreeChange.emit(this._expressionTree);
50✔
896
        }
897
    }
898

899
    /**
900
     * @hidden @internal
901
     */
902
    public cancelOperandAdd() {
903
        if (this._addModeExpression) {
608!
904
            this._addModeExpression.inAddMode = false;
×
905
            this._addModeExpression = null;
×
906
        }
907
    }
908

909
    /**
910
     * @hidden @internal
911
     */
912
    public deleteItem = (expressionItem: ExpressionItem, skipEmit: boolean = false) => {
304✔
913
        if (!expressionItem.parent) {
48✔
914
            this.rootGroup = null;
15✔
915
            this.currentGroup = null;
15✔
916
            //this._expressionTree = null;
917
            return;
15✔
918
        }
919

920
        if (expressionItem === this.currentGroup) {
33!
921
            this.currentGroup = this.currentGroup.parent;
×
922
        }
923

924
        const children = expressionItem.parent.children;
33✔
925
        const index = children.indexOf(expressionItem);
33✔
926
        children.splice(index, 1);
33✔
927
        const entity = this.expressionTree ? this.expressionTree.entity : null;
33✔
928
        const returnFields = this.expressionTree ? this.expressionTree.returnFields : null;
33✔
929
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, entity, returnFields); // TODO: don't recreate if not necessary
33✔
930

931
        if (!children.length) {
33✔
932
            this.deleteItem(expressionItem.parent, true);
16✔
933
        }
934

935
        if (!this.parentExpression && !skipEmit) {
33✔
936
            this.expressionTreeChange.emit(this._expressionTree);
31✔
937
        }
938
    }
939

940
    /**
941
     * @hidden @internal
942
     */
943
    public cancelOperandEdit() {
944
        if (this.innerQueries) {
535✔
945
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
231✔
946
            if (innerQuery) {
231✔
947
                if (innerQuery._editedExpression) {
9✔
948
                    innerQuery.cancelOperandEdit();
1✔
949
                }
950

951
                innerQuery.expressionTree = this.getExpressionTreeCopy(this._editedExpression.expression.searchTree);
9✔
952
                this.innerQueryNewExpressionTree = null;
9✔
953
            }
954
        }
955

956
        if (this._editedExpression) {
535✔
957
            this._editedExpression.inEditMode = false;
30✔
958

959
            if (!this._editedExpression.expression.fieldName) {
30✔
960
                this.deleteItem(this._editedExpression);
16✔
961
            }
962

963
            this._editedExpression = null;
30✔
964
        }
965

966
        if (!this.expressionTree && this.contextualGroup) {
535✔
967
            this.initialOperator = this.contextualGroup.operator;
1✔
968
        }
969
    }
970

971
    /**
972
     * @hidden @internal
973
     */
974
    public operandCanBeCommitted(): boolean {
975
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
1,766✔
976
        return this.selectedField && this.selectedCondition &&
1,766✔
977
            (
978
                (
979
                    ((!Array.isArray(this.searchValue.value) && !!this.searchValue.value) || (Array.isArray(this.searchValue.value) && this.searchValue.value.length !== 0)) &&
980
                    !(this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery)
981
                ) ||
982
                (
983
                    this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery && innerQuery && !!innerQuery.expressionTree && innerQuery.selectedReturnFields?.length > 0
984
                ) ||
985
                this.selectedField.filters.condition(this.selectedCondition)?.isUnary
986
            );
987
    }
988

989
    /**
990
     * @hidden @internal
991
     */
992
    public canCommitCurrentState(): boolean {
993
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
36✔
994
        if (innerQuery) {
36✔
995
            return this.selectedReturnFields?.length > 0 && innerQuery.canCommitCurrentState();
8✔
996
        } else {
997
            return this.selectedReturnFields?.length > 0 &&
28✔
998
                (
999
                    (!this._editedExpression) || // no edited expr
1000
                    (this._editedExpression && !this.selectedField) || // empty edited expr
1001
                    (this._editedExpression && this.operandCanBeCommitted() === true) // valid edited expr
1002
                );
1003
        }
1004
    }
1005

1006
    /**
1007
     * @hidden @internal
1008
     */
1009
    public commitCurrentState(): void {
1010
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
3✔
1011
        if (innerQuery) {
3✔
1012
            innerQuery.commitCurrentState();
1✔
1013
        }
1014

1015
        if (this._editedExpression) {
3✔
1016
            if (this.selectedField) {
3!
1017
                this.commitOperandEdit();
3✔
1018
            } else {
1019
                this.deleteItem(this._editedExpression);
×
1020
                this._editedExpression = null;
×
1021
            }
1022
        }
1023
    }
1024

1025
    /**
1026
     * @hidden @internal
1027
     */
1028
    public exitEditAddMode(shouldPreventInit = false) {
49✔
1029
        if (!this._editedExpression) {
244✔
1030
            return;
229✔
1031
        }
1032

1033
        this.exitOperandEdit();
15✔
1034
        this.cancelOperandAdd();
15✔
1035

1036
        if (shouldPreventInit) {
15✔
1037
            this._preventInit = true;
7✔
1038
        }
1039
    }
1040

1041
    /**
1042
     * @hidden @internal
1043
     *
1044
     * used by the grid
1045
     */
1046
    public exitOperandEdit() {
1047
        if (!this._editedExpression) {
35✔
1048
            return;
16✔
1049
        }
1050

1051
        if (this.operandCanBeCommitted()) {
19✔
1052
            this.commitOperandEdit();
9✔
1053
        } else {
1054
            this.cancelOperandEdit();
10✔
1055
        }
1056
    }
1057

1058
    /**
1059
     * @hidden @internal
1060
     */
1061
    public isExpressionGroup(expression: ExpressionItem): boolean {
1062
        return expression instanceof ExpressionGroupItem;
34,538✔
1063
    }
1064

1065
    /**
1066
     * @hidden @internal
1067
     */
1068
    public onExpressionFocus(expressionItem: ExpressionOperandItem) {
1069
        if (this.prevFocusedExpression) {
40✔
1070
            this.prevFocusedExpression.focused = false;
4✔
1071
        }
1072
        expressionItem.focused = true;
40✔
1073
        this.prevFocusedExpression = expressionItem;
40✔
1074
    }
1075

1076
    /**
1077
     * @hidden @internal
1078
     */
1079
    public onExpressionBlur(event, expressionItem: ExpressionOperandItem) {
1080
        if (this._prevFocusedContainer && this._prevFocusedContainer !== event.target.closest('.igx-filter-tree__expression-item')) {
2!
1081
            expressionItem.focused = false;
×
1082
        }
1083
        this._prevFocusedContainer = event.target.closest('.igx-filter-tree__expression-item');
2✔
1084
    }
1085

1086
    /**
1087
     * @hidden @internal
1088
     */
1089
    public onChipRemove(expressionItem: ExpressionItem) {
1090
        this.exitEditAddMode();
5✔
1091
        this.deleteItem(expressionItem);
5✔
1092
    }
1093

1094
    /**
1095
     * @hidden @internal
1096
     */
1097
    public focusChipAfterDrag = (index: number) => {
304✔
1098
        this._lastFocusedChipIndex = index;
11✔
1099
        this.focusEditedExpressionChip();
11✔
1100
    }
1101
    /**
1102
     * @hidden @internal
1103
     */
1104
    public addExpressionBlur() {
1105
        if (this.prevFocusedExpression) {
×
1106
            this.prevFocusedExpression.focused = false;
×
1107
        }
1108
        if (this.addExpressionItemDropDown && !this.addExpressionItemDropDown.collapsed) {
×
1109
            this.addExpressionItemDropDown.close();
×
1110
        }
1111
    }
1112

1113
    /**
1114
     * @hidden @internal
1115
     */
1116
    public onChipClick(expressionItem: ExpressionOperandItem, chip: IgxChipComponent) {
1117
        this.enterExpressionEdit(expressionItem, chip);
56✔
1118
    }
1119

1120
    /**
1121
     * @hidden @internal
1122
     */
1123
    public enterExpressionEdit(expressionItem: ExpressionOperandItem, chip?: IgxChipComponent) {
1124
        this.exitEditAddMode(true);
138✔
1125
        this.cdr.detectChanges();
138✔
1126
        this._lastFocusedChipIndex = chip ? this.expressionsChips.toArray().findIndex(expr => expr === chip) : this._lastFocusedChipIndex;
138✔
1127
        this.enterEditMode(expressionItem);
138✔
1128
    }
1129

1130

1131
    /**
1132
     * @hidden @internal
1133
     */
1134
    public clickExpressionAdd(targetButton: HTMLElement, chip: IgxChipComponent) {
1135
        this.exitEditAddMode(true);
5✔
1136
        this.cdr.detectChanges();
5✔
1137
        this._lastFocusedChipIndex = this.expressionsChips.toArray().findIndex(expr => expr === chip);
23✔
1138
        this.openExpressionAddDialog(targetButton);
5✔
1139
    }
1140

1141
    /**
1142
     * @hidden @internal
1143
     */
1144
    public openExpressionAddDialog(targetButton: HTMLElement) {
1145
        this.addExpressionDropDownOverlaySettings.target = targetButton;
5✔
1146
        this.addExpressionDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
5✔
1147
            horizontalDirection: HorizontalAlignment.Right,
1148
            horizontalStartPoint: HorizontalAlignment.Left,
1149
            verticalStartPoint: VerticalAlignment.Bottom
1150
        });
1151

1152
        this.addExpressionItemDropDown.open(this.addExpressionDropDownOverlaySettings);
5✔
1153
    }
1154

1155
    /**
1156
     * @hidden @internal
1157
     */
1158
    public enterExpressionAdd(event: ISelectionEventArgs, expressionItem: ExpressionOperandItem) {
1159
        if (this._addModeExpression) {
1!
1160
            this._addModeExpression.inAddMode = false;
×
1161
        }
1162

1163
        if (this.parentExpression) {
1!
1164
            this.inEditModeChange.emit(this.parentExpression);
×
1165
        }
1166

1167
        const parent = expressionItem.parent ?? this.rootGroup;
1!
1168
        requestAnimationFrame(() => {
1✔
1169
            if (event.newSelection.value === 'addCondition') {
1!
1170
                this.addCondition(parent, expressionItem);
1✔
1171
            } else if (event.newSelection.value === 'addGroup') {
×
1172
                this.addReverseGroup(parent, expressionItem);
×
1173
            }
1174
            expressionItem.inAddMode = true;
1✔
1175
            this._addModeExpression = expressionItem;
1✔
1176
        })
1177
    }
1178

1179
    /**
1180
     * @hidden @internal
1181
     */
1182
    public enterEditMode(expressionItem: ExpressionOperandItem) {
1183
        if (this._editedExpression) {
138!
1184
            this._editedExpression.inEditMode = false;
×
1185
        }
1186

1187
        if (this.parentExpression) {
138✔
1188
            this.inEditModeChange.emit(this.parentExpression);
16✔
1189
        }
1190

1191
        expressionItem.hovered = false;
138✔
1192
        this.fields = this.selectedEntity ? this.selectedEntity.fields : null;
138✔
1193
        this.selectedField =
138✔
1194
            expressionItem.expression.fieldName ?
138✔
1195
                this.fields?.find(field => field.field === expressionItem.expression.fieldName)
90✔
1196
                : null;
1197
        this.selectedCondition =
138✔
1198
            expressionItem.expression.condition ?
138✔
1199
                expressionItem.expression.condition.name :
1200
                null;
1201
        this.searchValue.value = expressionItem.expression.searchVal instanceof Set ?
138✔
1202
            Array.from(expressionItem.expression.searchVal) :
1203
            expressionItem.expression.searchVal;
1204

1205
        expressionItem.inEditMode = true;
138✔
1206
        this._editedExpression = expressionItem;
138✔
1207
        this.cdr.detectChanges();
138✔
1208

1209
        this.entitySelectOverlaySettings.target = this.entitySelect.getEditElement();
138✔
1210
        this.entitySelectOverlaySettings.excludeFromOutsideClick = [this.entitySelect.getEditElement() as HTMLElement];
138✔
1211
        this.entitySelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
138✔
1212

1213
        if (this.returnFieldSelect) {
138✔
1214
            this.returnFieldSelectOverlaySettings.target = this.returnFieldSelect.getEditElement();
16✔
1215
            this.returnFieldSelectOverlaySettings.excludeFromOutsideClick = [this.returnFieldSelect.getEditElement() as HTMLElement];
16✔
1216
            this.returnFieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
16✔
1217
        }
1218
        if (this.fieldSelect) {
138✔
1219
            this.fieldSelectOverlaySettings.target = this.fieldSelect.getEditElement();
135✔
1220
            this.fieldSelectOverlaySettings.excludeFromOutsideClick = [this.fieldSelect.getEditElement() as HTMLElement];
135✔
1221
            this.fieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
135✔
1222
        }
1223
        if (this.conditionSelect) {
138✔
1224
            this.conditionSelectOverlaySettings.target = this.conditionSelect.getEditElement();
135✔
1225
            this.conditionSelectOverlaySettings.excludeFromOutsideClick = [this.conditionSelect.getEditElement() as HTMLElement];
135✔
1226
            this.conditionSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
135✔
1227
        }
1228

1229
        if (!this.selectedField) {
138✔
1230
            this.fieldSelect.input.nativeElement.focus();
82✔
1231
        } else if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary) {
56✔
1232
            this.conditionSelect?.input.nativeElement.focus();
5✔
1233
        } else {
1234
            const input = this.searchValueInput?.nativeElement || this.picker?.getEditElement();
51✔
1235
            input?.focus();
51✔
1236
        }
1237

1238
        (this.editingInputs?.nativeElement.parentElement as HTMLElement)?.scrollIntoView({ block: "nearest", inline: "nearest" });
138✔
1239
    }
1240

1241
    /**
1242
     * @hidden @internal
1243
     */
1244
    public onConditionSelectChanging(event: ISelectionEventArgs) {
1245
        event.cancel = true;
67✔
1246
        this.selectedCondition = event.newSelection.value;
67✔
1247
        this.conditionSelect.close();
67✔
1248
        this.cdr.detectChanges();
67✔
1249
    }
1250

1251
    /**
1252
     * @hidden @internal
1253
     */
1254
    public onKeyDown(eventArgs: KeyboardEvent) {
1255
        eventArgs.stopPropagation();
×
1256
    }
1257

1258
    /**
1259
     * @hidden @internal
1260
     */
1261
    public onGroupClick(groupContextMenuDropDown: any, targetButton: HTMLButtonElement, groupItem: ExpressionGroupItem) {
1262
        this.exitEditAddMode();
9✔
1263
        this.cdr.detectChanges();
9✔
1264

1265
        this.groupContextMenuDropDown = groupContextMenuDropDown;
9✔
1266
        this.groupContextMenuDropDownOverlaySettings.target = targetButton;
9✔
1267
        this.groupContextMenuDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
9✔
1268
            horizontalDirection: HorizontalAlignment.Right,
1269
            horizontalStartPoint: HorizontalAlignment.Left,
1270
            verticalStartPoint: VerticalAlignment.Bottom
1271
        });
1272

1273
        if (groupContextMenuDropDown.collapsed) {
9✔
1274
            this.contextualGroup = groupItem;
8✔
1275
            groupContextMenuDropDown.open(this.groupContextMenuDropDownOverlaySettings);
8✔
1276
        } else {
1277
            groupContextMenuDropDown.close();
1✔
1278
        }
1279
    }
1280

1281
    /**
1282
     * @hidden @internal
1283
     */
1284
    public getOperator(expressionItem: any) {
1285
        // if (!expressionItem && !this.expressionTree && !this.initialOperator) {
1286
        //     this.initialOperator = 0;
1287
        // }
1288

1289
        const operator = expressionItem ?
90,064✔
1290
            expressionItem.operator :
1291
            this.expressionTree ?
3,226✔
1292
                this.expressionTree.operator :
1293
                this.initialOperator;
1294
        return operator;
90,064✔
1295
    }
1296

1297
    /**
1298
     * @hidden @internal
1299
     */
1300
    public getSwitchGroupText(expressionItem: any) {
1301
        const operator = this.getOperator(expressionItem);
14,997✔
1302
        const condition = operator === FilteringLogic.Or ? this.resourceStrings.igx_query_builder_and_label : this.resourceStrings.igx_query_builder_or_label
14,997✔
1303
        return this.resourceStrings.igx_query_builder_switch_group.replace('{0}', condition.toUpperCase());
14,997✔
1304
    }
1305

1306
    /**
1307
     * @hidden @internal
1308
     */
1309
    public onGroupContextMenuDropDownSelectionChanging(event: ISelectionEventArgs) {
1310
        event.cancel = true;
4✔
1311

1312
        if (event.newSelection.value === 'switchCondition') {
4✔
1313
            const newOperator = (!this.expressionTree ? this.initialOperator : (this.contextualGroup ?? this._expressionTree).operator) === 0 ? 1 : 0;
3!
1314
            this.selectFilteringLogic(newOperator);
3✔
1315
        } else if (event.newSelection.value === 'ungroup') {
1✔
1316
            this.ungroup();
1✔
1317
        }
1318

1319
        this.groupContextMenuDropDown.close();
4✔
1320
    }
1321

1322
    /**
1323
     * @hidden @internal
1324
     */
1325
    public ungroup() {
1326
        const selectedGroup = this.contextualGroup;
1✔
1327
        const parent = selectedGroup.parent;
1✔
1328
        if (parent) {
1✔
1329
            const index = parent.children.indexOf(selectedGroup);
1✔
1330
            parent.children.splice(index, 1, ...selectedGroup.children);
1✔
1331

1332
            for (const expr of selectedGroup.children) {
1✔
1333
                expr.parent = parent;
2✔
1334
            }
1335
        }
1336
        this.commitOperandEdit();
1✔
1337
    }
1338

1339
    /**
1340
     * @hidden @internal
1341
     */
1342
    public selectFilteringLogic(index: number) {
1343
        if (!this.expressionTree) {
3!
1344
            this.initialOperator = index;
×
1345
            return;
×
1346
        }
1347

1348
        if (this.contextualGroup) {
3✔
1349
            this.contextualGroup.operator = index as FilteringLogic;
2✔
1350
            this.commitOperandEdit();
2✔
1351
        } else if (this.expressionTree) {
1✔
1352
            this._expressionTree.operator = index as FilteringLogic;
1✔
1353
        }
1354

1355
        this.initialOperator = null;
3✔
1356
    }
1357

1358
    /**
1359
     * @hidden @internal
1360
     */
1361
    public getConditionFriendlyName(name: string): string {
1362
        // 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.
1363
        // So instead of in/notIn we end up with 'inQuery'/'notInQuery', hence removing the suffix from the friendly name.
1364
        return this.resourceStrings[`igx_query_builder_filter_${name?.replace('Query', '')}`] || name;
60,837✔
1365
    }
1366

1367
    /**
1368
     * @hidden @internal
1369
     */
1370
    public isDate(value: any) {
1371
        return value instanceof Date;
20,059✔
1372
    }
1373

1374
    /**
1375
     * @hidden @internal
1376
     */
1377
    public invokeClick(eventArgs: KeyboardEvent) {
1378
        if (!this.dragService.dropGhostExpression && this.platform.isActivationKey(eventArgs)) {
2✔
1379
            eventArgs.preventDefault();
2✔
1380
            (eventArgs.currentTarget as HTMLElement).click();
2✔
1381
        }
1382
    }
1383

1384
    /**
1385
     * @hidden @internal
1386
     */
1387
    public openPicker(args: KeyboardEvent) {
1388
        if (this.platform.isActivationKey(args)) {
×
1389
            args.preventDefault();
×
1390
            this.picker.open();
×
1391
        }
1392
    }
1393

1394
    /**
1395
     * @hidden @internal
1396
     */
1397
    public onOutletPointerDown(event) {
1398
        // This prevents closing the select's dropdown when clicking the scroll
1399
        event.preventDefault();
60✔
1400
    }
1401

1402
    /**
1403
     * @hidden @internal
1404
     */
1405
    public getConditionList(): string[] {
1406
        if (!this.selectedField) return [];
1,720✔
1407

1408
        if (!this.selectedField.filters) {
1,252!
1409
            this.selectedField.filters = this.getFilters(this.selectedField);
×
1410
        }
1411

1412
        if ((this.isAdvancedFiltering() && !this.entities[0].childEntities) ||
1,252✔
1413
            (this.isHierarchicalNestedQuery() && this.selectedEntity.name && !this.selectedEntity.childEntities)) {
1414
            return this.selectedField.filters.conditionList();
207✔
1415
        }
1416

1417
        return this.selectedField.filters.extendedConditionList();
1,045✔
1418
    }
1419

1420
    /**
1421
     * @hidden @internal
1422
     */
1423
    public getFormatter(field: string) {
1424
        return this.fields?.find(el => el.field === field)?.formatter;
72,298✔
1425
    }
1426

1427
    /**
1428
     * @hidden @internal
1429
     */
1430
    public getFormat(field: string) {
1431
        return this.fields?.find(el => el.field === field).pipeArgs.format;
1,845✔
1432
    }
1433

1434
    /**
1435
     * @hidden @internal
1436
     *
1437
     * used by the grid
1438
     */
1439
    public setAddButtonFocus() {
1440
        if (this.addRootAndGroupButton) {
53!
1441
            this.addRootAndGroupButton.nativeElement.focus();
×
1442
        } else if (this.addConditionButton) {
53✔
1443
            this.addConditionButton.nativeElement.focus();
20✔
1444
        }
1445
    }
1446

1447
    /**
1448
     * @hidden @internal
1449
     */
1450
    public context(expression: ExpressionItem, afterExpression?: ExpressionItem) {
1451
        return {
56,371✔
1452
            $implicit: expression,
1453
            afterExpression
1454
        };
1455
    }
1456

1457
    public formatReturnFields(innerTree: IFilteringExpressionsTree) {
1458
        const returnFields = innerTree.returnFields;
3,642✔
1459
        let text = returnFields.join(', ');
3,642✔
1460
        const innerTreeEntity = this.entities?.find(el => el.name === innerTree.entity);
3,642✔
1461
        if (returnFields.length === innerTreeEntity?.fields.length) {
3,642!
1462
            text = this.resourceStrings.igx_query_builder_all_fields;
×
1463
        } else {
1464
            text = returnFields.join(', ');
3,642✔
1465
            text = text.length > 25 ? text.substring(0, 25) + ' ...' : text;
3,642!
1466
        }
1467
        return text;
3,642✔
1468
    }
1469

1470
    public isInEditMode(): boolean {
1471
        return !this.parentExpression || (this.parentExpression && this.parentExpression.inEditMode);
65,049✔
1472
    }
1473

1474
    public onInEditModeChanged(expressionItem: ExpressionOperandItem) {
1475
        if (!expressionItem.inEditMode) {
16!
1476
            this.enterExpressionEdit(expressionItem);
×
1477
        }
1478
    }
1479

1480
    public getExpressionTreeCopy(expressionTree: IExpressionTree, shouldAssignInnerQueryExprTree?: boolean): IExpressionTree {
1481
        if (!expressionTree) {
322✔
1482
            return null;
290✔
1483
        }
1484

1485
        const exprTreeCopy = new FilteringExpressionsTree(expressionTree.operator, expressionTree.fieldName, expressionTree.entity, expressionTree.returnFields);
32✔
1486
        exprTreeCopy.filteringOperands = [];
32✔
1487

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

1490
        if (!this.innerQueryNewExpressionTree && shouldAssignInnerQueryExprTree) {
32✔
1491
            this.innerQueryNewExpressionTree = exprTreeCopy;
19✔
1492
        }
1493

1494
        return exprTreeCopy;
32✔
1495
    }
1496

1497
    public onSelectAllClicked() {
1498
        if (
2✔
1499
            (this._selectedReturnFields.length > 0 && this._selectedReturnFields.length < this._selectedEntity.fields.length) ||
5✔
1500
            this._selectedReturnFields.length == this._selectedEntity.fields.length
1501
        ) {
1502
            this.returnFieldsCombo.deselectAllItems();
1✔
1503
        } else {
1504
            this.returnFieldsCombo.selectAllItems();
1✔
1505
        }
1506
    }
1507

1508
    public onReturnFieldSelectChanging(event: IComboSelectionChangingEventArgs | ISelectionEventArgs) {
1509
        let newSelection = [];
11✔
1510
        if (Array.isArray(event.newSelection)) {
11✔
1511
            newSelection = event.newSelection.map(item => item.field)
11✔
1512
        } else {
1513
            newSelection.push(event.newSelection.value);
6✔
1514
            this._selectedReturnFields = newSelection;
6✔
1515
        }
1516

1517
        this.initExpressionTree(this.selectedEntity.name, newSelection);
11✔
1518
    }
1519

1520
    public initExpressionTree(selectedEntityName: string, selectedReturnFields: string[]) {
1521
        if (!this._expressionTree) {
50✔
1522
            this._expressionTree = this.createExpressionTreeFromGroupItem(new ExpressionGroupItem(FilteringLogic.And, this.rootGroup), selectedEntityName, selectedReturnFields);
34✔
1523
        }
1524

1525
        if (!this.parentExpression) {
50✔
1526
            this.expressionTreeChange.emit(this._expressionTree);
38✔
1527
        }
1528
    }
1529

1530
    public getSearchValueTemplateContext(defaultSearchValueTemplate): any {
1531
        const ctx = {
1,720✔
1532
            $implicit: this.searchValue,
1533
            selectedField: this.selectedField,
1534
            selectedCondition: this.selectedCondition,
1535
            defaultSearchValueTemplate: defaultSearchValueTemplate
1536
        };
1537
        return ctx;
1,720✔
1538
    }
1539

1540
    private getPipeArgs(field: FieldType) {
1541
        let pipeArgs = {...field.pipeArgs};
3,279✔
1542
        if (!pipeArgs) {
3,279!
1543
            pipeArgs = { digitsInfo: DEFAULT_PIPE_DIGITS_INFO };
×
1544
        }
1545

1546
        if (!pipeArgs.format) {
3,279✔
1547
            pipeArgs.format = field.dataType === GridColumnDataType.Time ?
3,044!
1548
                DEFAULT_PIPE_TIME_FORMAT : field.dataType === GridColumnDataType.DateTime ?
3,044!
1549
                    DEFAULT_PIPE_DATE_TIME_FORMAT : DEFAULT_PIPE_DATE_FORMAT;
1550
        }
1551

1552
        return pipeArgs;
3,279✔
1553
    }
1554

1555
    private selectDefaultCondition() {
1556
        if (this.selectedField && this.selectedField.filters) {
176✔
1557
            this.selectedCondition = this.selectedField.filters.conditionList().indexOf('equals') >= 0 ? 'equals' : this.selectedField.filters.conditionList()[0];
126✔
1558
        }
1559
    }
1560

1561
    private getFilters(field: FieldType) {
1562
        if (!field.filters) {
3,279✔
1563
            switch (field.dataType) {
3,044!
1564
                case GridColumnDataType.Boolean:
1565
                    return IgxBooleanFilteringOperand.instance();
753✔
1566
                case GridColumnDataType.Number:
1567
                case GridColumnDataType.Currency:
1568
                case GridColumnDataType.Percent:
1569
                    return IgxNumberFilteringOperand.instance();
1,131✔
1570
                case GridColumnDataType.Date:
1571
                    return IgxDateFilteringOperand.instance();
380✔
1572
                case GridColumnDataType.Time:
1573
                    return IgxTimeFilteringOperand.instance();
×
1574
                case GridColumnDataType.DateTime:
1575
                    return IgxDateTimeFilteringOperand.instance();
×
1576
                case GridColumnDataType.String:
1577
                default:
1578
                    return IgxStringFilteringOperand.instance();
780✔
1579
            }
1580
        } else {
1581
            return field.filters;
235✔
1582
        }
1583
    }
1584

1585

1586
    private addGroup(operator: FilteringLogic, parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
1587
        this.cancelOperandAdd();
3✔
1588

1589
        const groupItem = new ExpressionGroupItem(operator, parent);
3✔
1590

1591
        if (parent) {
3!
1592
            if (afterExpression) {
3!
1593
                const index = parent.children.indexOf(afterExpression);
×
1594
                parent.children.splice(index + 1, 0, groupItem);
×
1595
            } else {
1596
                parent.children.push(groupItem);
3✔
1597
            }
1598
        } else {
1599
            this.rootGroup = groupItem;
×
1600
        }
1601

1602
        this.addCondition(groupItem);
3✔
1603
        this.currentGroup = groupItem;
3✔
1604
    }
1605

1606
    private createExpressionGroupItem(expressionTree: IExpressionTree, parent?: ExpressionGroupItem, entityName?: string): ExpressionGroupItem {
1607
        let groupItem: ExpressionGroupItem;
1608
        if (expressionTree) {
862✔
1609
            groupItem = new ExpressionGroupItem(expressionTree.operator, parent);
688✔
1610
            if (!expressionTree.filteringOperands) {
688!
1611
                return groupItem;
×
1612
            }
1613

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

1617
                if (isTree(expr)) {
1,495✔
1618
                    groupItem.children.push(this.createExpressionGroupItem(expr, groupItem, expressionTree.entity));
88✔
1619
                } else {
1620
                    const filteringExpr = expr as IFilteringExpression;
1,407✔
1621
                    const exprCopy: IFilteringExpression = {
1,407✔
1622
                        fieldName: filteringExpr.fieldName,
1623
                        condition: filteringExpr.condition,
1624
                        conditionName: filteringExpr.condition?.name || filteringExpr.conditionName,
1,407!
1625
                        searchVal: filteringExpr.searchVal,
1626
                        searchTree: filteringExpr.searchTree,
1627
                        ignoreCase: filteringExpr.ignoreCase
1628
                    };
1629
                    const operandItem = new ExpressionOperandItem(exprCopy, groupItem);
1,407✔
1630
                    const field = this.fields?.find(el => el.field === filteringExpr.fieldName);
2,012✔
1631
                    operandItem.fieldLabel = field?.label || field?.header || field?.field;
1,407✔
1632
                    if (this._expandedExpressions.filter(e => e.searchTree == operandItem.expression.searchTree).length > 0) {
1,407!
1633
                        operandItem.expanded = true;
×
1634
                    }
1635
                    groupItem.children.push(operandItem);
1,407✔
1636
                }
1637
            }
1638

1639

1640
            if (expressionTree.entity) {
688✔
1641
                entityName = expressionTree.entity;
629✔
1642
            }
1643
            const entity = this.entities?.find(el => el.name === entityName);
977✔
1644
            if (entity) {
688✔
1645
                this.fields = entity.fields;
627✔
1646
            }
1647

1648
            this._selectedEntity = this.entities?.find(el => el.name === entityName);
977✔
1649
            this._selectedReturnFields =
688✔
1650
                !expressionTree.returnFields || expressionTree.returnFields.includes('*') || expressionTree.returnFields.includes('All') || expressionTree.returnFields.length === 0
2,892✔
1651
                    ? this.fields?.map(f => f.field)
1,120✔
1652
                    : this.fields?.filter(f => expressionTree.returnFields.indexOf(f.field) >= 0).map(f => f.field);
1,778✔
1653
        }
1654
        return groupItem;
862✔
1655
    }
1656

1657
    private createExpressionTreeFromGroupItem(groupItem: ExpressionGroupItem, entity?: string, returnFields?: string[]): FilteringExpressionsTree {
1658
        if (!groupItem) {
152✔
1659
            return null;
5✔
1660
        }
1661

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

1664
        for (let i = 0; i < groupItem.children.length; i++) {
147✔
1665
            const item = groupItem.children[i];
200✔
1666

1667
            if (item instanceof ExpressionGroupItem) {
200✔
1668
                const subTree = this.createExpressionTreeFromGroupItem((item as ExpressionGroupItem), entity, returnFields);
19✔
1669
                expressionTree.filteringOperands.push(subTree);
19✔
1670
            } else {
1671
                expressionTree.filteringOperands.push((item as ExpressionOperandItem).expression);
181✔
1672
            }
1673
        }
1674

1675
        return expressionTree;
147✔
1676
    }
1677

1678
    private scrollElementIntoView(target: HTMLElement) {
1679
        const container = this.expressionsContainer.nativeElement;
668✔
1680
        const targetOffset = target.offsetTop - container.offsetTop;
668✔
1681
        const delta = 10;
668✔
1682

1683
        if (container.scrollTop + delta > targetOffset) {
668✔
1684
            container.scrollTop = targetOffset - delta;
447✔
1685
        } else if (container.scrollTop + container.clientHeight < targetOffset + target.offsetHeight + delta) {
221✔
1686
            container.scrollTop = targetOffset + target.offsetHeight + delta - container.clientHeight;
216✔
1687
        }
1688
    }
1689

1690
    private focusEditedExpressionChip() {
1691
        if (this._timeoutId) {
60✔
1692
            clearTimeout(this._timeoutId);
5✔
1693
        }
1694

1695
        this._timeoutId = setTimeout(() => {
60✔
1696
            if (this._lastFocusedChipIndex != -1) {
60✔
1697
                //Sort the expression chip list.
1698
                //If there was a recent drag&drop and the tree hasn't rerendered(child query), they will be unordered
1699
                const sortedChips = this.expressionsChips.toArray().sort(function (a, b) {
58✔
1700
                    if (a === b) return 0;
75!
1701
                    if (a.chipArea.nativeElement.compareDocumentPosition(b.chipArea.nativeElement) & 2) {
75✔
1702
                        // b comes before a
1703
                        return 1;
68✔
1704
                    }
1705
                    return -1;
7✔
1706
                });
1707
                const chipElement = sortedChips[this._lastFocusedChipIndex]?.nativeElement;
58✔
1708
                if (chipElement) {
58✔
1709
                    chipElement.focus();
50✔
1710
                }
1711
                this._lastFocusedChipIndex = -1;
58✔
1712
                this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
58✔
1713
            }
1714
        }, this._focusDelay);
1715
    }
1716

1717
    private init() {
1718
        this.cancelOperandAdd();
508✔
1719
        this.cancelOperandEdit();
508✔
1720

1721
        // Ignore values of certain properties for the comparison
1722
        const propsToIgnore = ['parent', 'hovered', 'ignoreCase', 'inEditMode', 'inAddMode'];
508✔
1723
        const propsReplacer = function replacer(key, value) {
508✔
1724
            if (propsToIgnore.indexOf(key) >= 0) {
19,600✔
1725
                return undefined;
2,611✔
1726
            } else {
1727
                return value;
16,989✔
1728
            }
1729
        };
1730

1731
        // Skip root being recreated if the same
1732
        const newRootGroup = this.createExpressionGroupItem(this.expressionTree);
508✔
1733
        if (JSON.stringify(this.rootGroup, propsReplacer) !== JSON.stringify(newRootGroup, propsReplacer)) {
508✔
1734
            this.rootGroup = this.createExpressionGroupItem(this.expressionTree);
260✔
1735
            this.currentGroup = this.rootGroup;
260✔
1736
        }
1737

1738
        if (this.rootGroup?.children?.length == 0) {
508✔
1739
            this.rootGroup = null;
40✔
1740
            this.currentGroup = null;
40✔
1741
        }
1742
    }
1743

1744
    private initLocale() {
1745
        this._defaultLocale = getCurrentI18n();
304✔
1746
        this._locale = this._localeId !== DEFAULT_LOCALE ? this._localeId : this._locale;
304!
1747
        onResourceChangeHandle(this.destroy$, this.onResourceChange, this);
304✔
1748
    }
1749

1750
    private onResourceChange(args: CustomEvent<IResourceChangeEventArgs>) {
1751
        this._defaultLocale = args.detail.newLocale;
2✔
1752
        if (!this._locale) {
2!
NEW
1753
            this._defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN, false);
×
1754
        }
1755
    }
1756

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