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

IgniteUI / igniteui-angular / 14195181851

01 Apr 2025 12:08PM UTC coverage: 91.608% (-0.02%) from 91.625%
14195181851

Pull #15597

github

web-flow
Merge 78ae75fce into ede4fa679
Pull Request #15597: fix(*): adding random function for unsecure context #15461

13317 of 15578 branches covered (85.49%)

4 of 10 new or added lines in 3 files covered. (40.0%)

43 existing lines in 1 file now uncovered.

26855 of 29315 relevant lines covered (91.61%)

33971.13 hits per line

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

91.78
/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts
1
import {
2
    AfterViewInit,
3
    EventEmitter,
4
    LOCALE_ID,
5
    Output,
6
    TemplateRef
7
} from '@angular/core';
8
import { getLocaleFirstDayOfWeek, NgTemplateOutlet, NgClass, DatePipe } from '@angular/common';
9
import { Inject } from '@angular/core';
10
import {
11
    Component, Input, ViewChild, ChangeDetectorRef, ViewChildren, QueryList, ElementRef, OnDestroy, HostBinding
12
} from '@angular/core';
13
import { FormsModule } from '@angular/forms';
14
import { Subject } from 'rxjs';
15
import { IgxChipComponent } from '../chips/chip.component';
16
import { IQueryBuilderResourceStrings, QueryBuilderResourceStringsEN } from '../core/i18n/query-builder-resources';
17
import { PlatformUtil, trackByIdentity } from '../core/utils';
18
import { DataType, DataUtil } from '../data-operations/data-util';
19
import { IgxBooleanFilteringOperand, IgxDateFilteringOperand, IgxDateTimeFilteringOperand, IgxNumberFilteringOperand, IgxStringFilteringOperand, IgxTimeFilteringOperand } from '../data-operations/filtering-condition';
20
import { FilteringLogic, IFilteringExpression } from '../data-operations/filtering-expression.interface';
21
import { FilteringExpressionsTree, IExpressionTree, IFilteringExpressionsTree } from '../data-operations/filtering-expressions-tree';
22
import { IgxDatePickerComponent } from '../date-picker/date-picker.component';
23

24
import { IgxButtonDirective } from '../directives/button/button.directive';
25
import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/date-time-editor.directive';
26

27
import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive';
28
import { FieldType, EntityType } from '../grids/common/grid.interface';
29
import { IgxSelectComponent } from '../select/select.component';
30
import { HorizontalAlignment, OverlaySettings, VerticalAlignment } from '../services/overlay/utilities';
31
import { AbsoluteScrollStrategy, AutoPositionStrategy, CloseScrollStrategy, ConnectedPositioningStrategy } from '../services/public_api';
32
import { IgxTimePickerComponent } from '../time-picker/time-picker.component';
33
import { IgxPickerToggleComponent, IgxPickerClearComponent } from '../date-common/picker-icons.common';
34
import { IgxInputDirective } from '../directives/input/input.directive';
35
import { IgxInputGroupComponent } from '../input-group/input-group.component';
36
import { IgxSelectItemComponent } from '../select/select-item.component';
37
import { IgxPrefixDirective } from '../directives/prefix/prefix.directive';
38
import { IgxIconComponent } from '../icon/icon.component';
39
import { getCurrentResourceStrings } from '../core/i18n/resources';
40
import { IgxIconButtonDirective } from '../directives/button/icon-button.directive';
41
import { IComboSelectionChangingEventArgs, IgxComboComponent } from "../combo/combo.component";
42
import { IgxComboHeaderDirective } from '../combo/public_api';
43
import { IgxCheckboxComponent } from "../checkbox/checkbox.component";
44
import { IChangeCheckboxEventArgs } from '../checkbox/checkbox-base.directive';
45
import { IgxDialogComponent } from "../dialog/dialog.component";
46
import { ISelectionEventArgs } from '../drop-down/drop-down.common';
47
import { IgxTooltipDirective } from '../directives/tooltip/tooltip.directive';
48
import { IgxTooltipTargetDirective } from '../directives/tooltip/tooltip-target.directive';
49
import { IgxQueryBuilderSearchValueTemplateDirective } from './query-builder.directives';
50
import { IgxQueryBuilderComponent } from './query-builder.component';
51
import { IgxDragIgnoreDirective, IgxDropDirective } from '../directives/drag-drop/drag-drop.directive';
52
import { IgxDropDownComponent } from '../drop-down/drop-down.component';
53
import { IgxDropDownItemComponent } from '../drop-down/drop-down-item.component';
54
import { IgxDropDownItemNavigationDirective } from '../drop-down/drop-down-navigation.directive';
55
import { IgxQueryBuilderDragService } from './query-builder-drag.service';
56
import { isTree } from '../data-operations/expressions-tree-util';
57
import { ExpressionGroupItem, ExpressionItem, ExpressionOperandItem, IgxFieldFormatterPipe } from './query-builder.common';
58

59
const DEFAULT_PIPE_DATE_FORMAT = 'mediumDate';
2✔
60
const DEFAULT_PIPE_TIME_FORMAT = 'mediumTime';
2✔
61
const DEFAULT_PIPE_DATE_TIME_FORMAT = 'medium';
2✔
62
const DEFAULT_PIPE_DIGITS_INFO = '1.0-3';
2✔
63
const DEFAULT_CHIP_FOCUS_DELAY = 50;
2✔
64

65
/** @hidden */
66
@Component({
67
    selector: 'igx-query-builder-tree',
68
    templateUrl: './query-builder-tree.component.html',
69
    host: { 'class': 'igx-query-builder-tree' },
70
    imports: [
71
        DatePipe,
72
        FormsModule,
73
        IgxButtonDirective,
74
        IgxCheckboxComponent,
75
        IgxChipComponent,
76
        IgxComboComponent,
77
        IgxComboHeaderDirective,
78
        IgxDatePickerComponent,
79
        IgxDateTimeEditorDirective,
80
        IgxDialogComponent,
81
        IgxDragIgnoreDirective,
82
        IgxDropDirective,
83
        IgxDropDownComponent,
84
        IgxDropDownItemComponent,
85
        IgxDropDownItemNavigationDirective,
86
        IgxFieldFormatterPipe,
87
        IgxIconButtonDirective,
88
        IgxIconComponent,
89
        IgxInputDirective,
90
        IgxInputGroupComponent,
91
        IgxOverlayOutletDirective,
92
        IgxPickerClearComponent,
93
        IgxPickerToggleComponent,
94
        IgxPrefixDirective,
95
        IgxSelectComponent,
96
        IgxSelectItemComponent,
97
        IgxTimePickerComponent,
98
        IgxTooltipDirective,
99
        IgxTooltipTargetDirective,
100
        NgClass,
101
        NgTemplateOutlet
102
    ],
103
    providers: [
104
        IgxQueryBuilderDragService
105
    ],
106
})
107
export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
2✔
108
    /**
109
     * @hidden @internal
110
     */
111
    public _expressionTree: IExpressionTree;
112

113
    /**
114
     * @hidden @internal
115
     */
116
    public _expressionTreeCopy: IExpressionTree;
117

118
    /**
119
     * @hidden @internal
120
     */
121
    @HostBinding('class') public get getClass() {
122
        return `igx-query-builder-tree--level-${this.level}`;
11,063✔
123
    }
124

125
    /**
126
     * Sets/gets the entities.
127
     */
128
    @Input()
129
    public entities: EntityType[];
130

131
    /**
132
     * Sets/gets the parent query builder component.
133
     */
134
    @Input()
135
    public queryBuilder: IgxQueryBuilderComponent;
136

137
    /**
138
     * Sets/gets the search value template.
139
     */
140
    @Input()
141
    public searchValueTemplate: TemplateRef<IgxQueryBuilderSearchValueTemplateDirective> = null;
277✔
142

143
    /**
144
    * Returns the parent expression operand.
145
    */
146
    @Input()
147
    public get parentExpression(): ExpressionOperandItem {
148
        return this._parentExpression;
149,977✔
149
    }
150

151
    /**
152
     * Sets the parent expression operand.
153
     */
154
    public set parentExpression(value: ExpressionOperandItem) {
155
        this._parentExpression = value;
134✔
156
    }
157

158
    /**
159
    * Returns the fields.
160
    */
161
    public get fields(): FieldType[] {
162
        if (!this._fields && this.isAdvancedFiltering()) {
57,485✔
163
            this._fields = this.entities[0].fields;
44✔
164
        }
165

166
        return this._fields;
57,485✔
167
    }
168

169
    /**
170
     * Sets the fields.
171
     */
172
    @Input()
173
    public set fields(fields: FieldType[]) {
174
        this._fields = fields;
776✔
175
        
176
        this._fields = this._fields?.map(f => ({...f, filters: this.getFilters(f), pipeArgs: this.getPipeArgs(f) }));
2,996✔
177
        
178
        if (!this._fields && this.isAdvancedFiltering()) {
776✔
179
            this._fields = this.entities[0].fields;
27✔
180
        }
181
    }
182

183
    /**
184
    * Returns the expression tree.
185
    */
186
    public get expressionTree(): IExpressionTree {
187
        return this._expressionTree;
5,843✔
188
    }
189

190
    /**
191
     * Sets the expression tree.
192
     */
193
    @Input()
194
    public set expressionTree(expressionTree: IExpressionTree) {
195
        this._expressionTree = expressionTree;
487✔
196
        if (!expressionTree) {
487✔
197
            this._selectedEntity = null;
145✔
198
            this._selectedReturnFields = [];
145✔
199
        }
200

201
        if (!this._preventInit) {
487✔
202
            this.init();
478✔
203
        }
204
    }
205

206
    /**
207
     * Gets the `locale` of the query builder.
208
     * If not set, defaults to application's locale.
209
     */
210
    @Input()
211
    public get locale(): string {
212
        return this._locale;
954✔
213
    }
214

215
    /**
216
     * Sets the `locale` of the query builder.
217
     * Expects a valid BCP 47 language tag.
218
     */
219
    public set locale(value: string) {
220
        this._locale = value;
420✔
221
        // if value is invalid, set it back to _localeId
222
        try {
420✔
223
            getLocaleFirstDayOfWeek(this._locale);
420✔
224
        } catch {
225
            this._locale = this._localeId;
99✔
226
        }
227
    }
228

229
    /**
230
     * Sets the resource strings.
231
     * By default it uses EN resources.
232
     */
233
    @Input()
234
    public set resourceStrings(value: IQueryBuilderResourceStrings) {
235
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
144✔
236
    }
237

238
    /**
239
     * Returns the resource strings.
240
     */
241
    public get resourceStrings(): IQueryBuilderResourceStrings {
242
        return this._resourceStrings;
235,877✔
243
    }
244

245
    /**
246
     * Event fired as the expression tree is changed.
247
     */
248
    @Output()
249
    public expressionTreeChange = new EventEmitter<IExpressionTree>();
277✔
250

251
    /**
252
     * Event fired if a nested query builder tree is being edited.
253
     */
254
    @Output()
255
    public inEditModeChange = new EventEmitter<ExpressionOperandItem>();
277✔
256

257
    @ViewChild('entitySelect', { read: IgxSelectComponent })
258
    protected entitySelect: IgxSelectComponent;
259

260
    @ViewChild('editingInputs', { read: ElementRef })
261
    private editingInputs: ElementRef;
262

263
    @ViewChild('returnFieldsCombo', { read: IgxComboComponent })
264
    private returnFieldsCombo: IgxComboComponent;
265

266
    @ViewChild('returnFieldSelect', { read: IgxSelectComponent })
267
    protected returnFieldSelect: IgxSelectComponent;
268

269
    @ViewChild('fieldSelect', { read: IgxSelectComponent })
270
    private fieldSelect: IgxSelectComponent;
271

272
    @ViewChild('conditionSelect', { read: IgxSelectComponent })
273
    private conditionSelect: IgxSelectComponent;
274

275
    @ViewChild('searchValueInput', { read: ElementRef })
276
    private searchValueInput: ElementRef;
277

278
    @ViewChild('picker')
279
    private picker: IgxDatePickerComponent | IgxTimePickerComponent;
280

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

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

287
    @ViewChild('entityChangeDialog', { read: IgxDialogComponent })
288
    private entityChangeDialog: IgxDialogComponent;
289

290
    @ViewChild('addOptionsDropDown', { read: IgxDropDownComponent })
291
    private addExpressionItemDropDown: IgxDropDownComponent;
292

293
    @ViewChild('groupContextMenuDropDown', { read: IgxDropDownComponent })
294
    private groupContextMenuDropDown: IgxDropDownComponent;
295

296
    /**
297
     * @hidden @internal
298
     */
299
    @ViewChildren(IgxChipComponent, { read: IgxChipComponent })
300
    public expressionsChips: QueryList<IgxChipComponent>;
301

302
    @ViewChild('editingInputsContainer', { read: ElementRef })
303
    protected set editingInputsContainer(value: ElementRef) {
304
        if ((value && !this._editingInputsContainer) ||
758✔
305
            (value && this._editingInputsContainer && this._editingInputsContainer.nativeElement !== value.nativeElement)) {
306
            requestAnimationFrame(() => {
271✔
307
                this.scrollElementIntoView(value.nativeElement);
271✔
308
            });
309
        }
310

311
        this._editingInputsContainer = value;
758✔
312
    }
313

314
    /** @hidden */
315
    protected get editingInputsContainer(): ElementRef {
UNCOV
316
        return this._editingInputsContainer;
×
317
    }
318

319
    @ViewChild('currentGroupButtonsContainer', { read: ElementRef })
320
    protected set currentGroupButtonsContainer(value: ElementRef) {
321
        if ((value && !this._currentGroupButtonsContainer) ||
758✔
322
            (value && this._currentGroupButtonsContainer && this._currentGroupButtonsContainer.nativeElement !== value.nativeElement)) {
323
            requestAnimationFrame(() => {
348✔
324
                this.scrollElementIntoView(value.nativeElement);
348✔
325
            });
326
        }
327

328
        this._currentGroupButtonsContainer = value;
758✔
329
    }
330

331
    /** @hidden */
332
    protected get currentGroupButtonsContainer(): ElementRef {
UNCOV
333
        return this._currentGroupButtonsContainer;
×
334
    }
335

336
    @ViewChild('expressionsContainer')
337
    private expressionsContainer: ElementRef;
338

339
    @ViewChild('overlayOutlet', { read: IgxOverlayOutletDirective, static: true })
340
    private overlayOutlet: IgxOverlayOutletDirective;
341

342
    @ViewChildren(IgxQueryBuilderTreeComponent)
343
    private innerQueries: QueryList<IgxQueryBuilderTreeComponent>;
344

345
    /**
346
     * @hidden @internal
347
     */
348
    public innerQueryNewExpressionTree: IExpressionTree;
349

350
    /**
351
     * @hidden @internal
352
     */
353
    public rootGroup: ExpressionGroupItem;
354

355
    /**
356
     * @hidden @internal
357
     */
358
    public selectedExpressions: ExpressionOperandItem[] = [];
277✔
359

360
    /**
361
     * @hidden @internal
362
     */
363
    public currentGroup: ExpressionGroupItem;
364

365
    /**
366
     * @hidden @internal
367
     */
368
    public contextualGroup: ExpressionGroupItem;
369

370
    /**
371
     * @hidden @internal
372
     */
373
    public filteringLogics;
374

375
    /**
376
     * @hidden @internal
377
     */
378
    public selectedCondition: string;
379

380
    /**
381
     * @hidden @internal
382
     */
383
    public searchValue: { value: any } = { value: null };
277✔
384

385
    /**
386
     * @hidden @internal
387
     */
388
    public pickerOutlet: IgxOverlayOutletDirective | ElementRef;
389

390
    /**
391
     * @hidden @internal
392
     */
393
    public prevFocusedExpression: ExpressionOperandItem;
394

395
    /**
396
     * @hidden @internal
397
     */
398
    public initialOperator = 0;
277✔
399

400
    /**
401
     * @hidden @internal
402
     */
403
    public returnFieldSelectOverlaySettings: OverlaySettings = {
277✔
404
        scrollStrategy: new AbsoluteScrollStrategy(),
405
        modal: false,
406
        closeOnOutsideClick: true
407
    };
408

409
    /**
410
     * @hidden @internal
411
     */
412
    public entitySelectOverlaySettings: OverlaySettings = {
277✔
413
        scrollStrategy: new AbsoluteScrollStrategy(),
414
        modal: false,
415
        closeOnOutsideClick: true
416
    };
417

418
    /**
419
     * @hidden @internal
420
     */
421
    public fieldSelectOverlaySettings: OverlaySettings = {
277✔
422
        scrollStrategy: new AbsoluteScrollStrategy(),
423
        modal: false,
424
        closeOnOutsideClick: true
425
    };
426

427
    /**
428
     * @hidden @internal
429
     */
430
    public conditionSelectOverlaySettings: OverlaySettings = {
277✔
431
        scrollStrategy: new AbsoluteScrollStrategy(),
432
        modal: false,
433
        closeOnOutsideClick: true
434
    };
435

436
    /**
437
     * @hidden @internal
438
     */
439
    public addExpressionDropDownOverlaySettings: OverlaySettings = {
277✔
440
        scrollStrategy: new AbsoluteScrollStrategy(),
441
        modal: false,
442
        closeOnOutsideClick: true
443
    };
444

445
    /**
446
     * @hidden @internal
447
     */
448
    public groupContextMenuDropDownOverlaySettings: OverlaySettings = {
277✔
449
        scrollStrategy: new AbsoluteScrollStrategy(),
450
        modal: false,
451
        closeOnOutsideClick: true
452
    };
453

454
    private destroy$ = new Subject<any>();
277✔
455
    private _timeoutId: any;
456
    private _lastFocusedChipIndex: number;
457
    private _focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
277✔
458
    private _parentExpression: ExpressionOperandItem;
459
    private _selectedEntity: EntityType;
460
    private _selectedReturnFields: string | string[];
461
    private _selectedField: FieldType;
462
    private _editingInputsContainer: ElementRef;
463
    private _currentGroupButtonsContainer: ElementRef;
464
    private _addModeExpression: ExpressionOperandItem;
465
    private _editedExpression: ExpressionOperandItem;
466
    private _preventInit = false;
277✔
467
    private _prevFocusedContainer: ElementRef;
468
    private _expandedExpressions: IFilteringExpression[] = [];
277✔
469
    private _fields: FieldType[];
470
    private _locale;
471
    private _entityNewValue: EntityType;
472
    private _resourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN);
277✔
473

474
    /**
475
     * Returns if the select entity dropdown at the root level is disabled after the initial selection.
476
     */
477
    public get disableEntityChange(): boolean {
478

479
        return !this.parentExpression && this.selectedEntity ? this.queryBuilder.disableEntityChange : false;
11,790✔
480
    }
481

482
    /**
483
     * Returns if the fields combo at the root level is disabled.
484
     */
485
    public get disableReturnFieldsChange(): boolean {
486

487
        return !this.selectedEntity || this.queryBuilder.disableReturnFieldsChange;
5,588✔
488
    }
489

490
    /**
491
     * Returns the current level.
492
     */
493
    public get level(): number {
494
        let parent = this.elRef.nativeElement.parentElement;
11,063✔
495
        let _level = 0;
11,063✔
496
        while (parent) {
11,063✔
497
            if (parent.localName === 'igx-query-builder-tree') {
91,930✔
498
                _level++;
6,016✔
499
            }
500
            parent = parent.parentElement;
91,930✔
501
        }
502
        return _level;
11,063✔
503
    }
504

505
    private _positionSettings = {
277✔
506
        horizontalStartPoint: HorizontalAlignment.Right,
507
        verticalStartPoint: VerticalAlignment.Top
508
    };
509

510
    private _overlaySettings: OverlaySettings = {
277✔
511
        closeOnOutsideClick: false,
512
        modal: false,
513
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings),
514
        scrollStrategy: new CloseScrollStrategy()
515
    };
516

517
    /** @hidden */
518
    protected isAdvancedFiltering(): boolean {
519
        return this.entities?.length === 1 && !this.entities[0]?.name;
18,339✔
520
    }
521

522
    /** @hidden */
523
    protected isSearchValueInputDisabled(): boolean {
524
        return !this.selectedField ||
1,477✔
525
            !this.selectedCondition ||
526
            (this.selectedField &&
527
                (this.selectedField.filters.condition(this.selectedCondition).isUnary ||
528
                    this.selectedField.filters.condition(this.selectedCondition).isNestedQuery));
529
    }
530

531
    constructor(public cdr: ChangeDetectorRef,
277✔
532
        public dragService: IgxQueryBuilderDragService,
277✔
533
        protected platform: PlatformUtil,
277✔
534
        private elRef: ElementRef,
277✔
535
        @Inject(LOCALE_ID) protected _localeId: string) {
277✔
536
        this.locale = this.locale || this._localeId;
277✔
537
        this.dragService.register(this, elRef);
277✔
538
    }
539

540
    /**
541
     * @hidden @internal
542
     */
543
    public ngAfterViewInit(): void {
544
        this._overlaySettings.outlet = this.overlayOutlet;
277✔
545
        this.entitySelectOverlaySettings.outlet = this.overlayOutlet;
277✔
546
        this.fieldSelectOverlaySettings.outlet = this.overlayOutlet;
277✔
547
        this.conditionSelectOverlaySettings.outlet = this.overlayOutlet;
277✔
548
        this.returnFieldSelectOverlaySettings.outlet = this.overlayOutlet;
277✔
549
        this.addExpressionDropDownOverlaySettings.outlet = this.overlayOutlet;
277✔
550
        this.groupContextMenuDropDownOverlaySettings.outlet = this.overlayOutlet;
277✔
551
        // Trigger additional change detection cycle
552
        this.cdr.detectChanges();
277✔
553
    }
554

555
    /**
556
     * @hidden @internal
557
     */
558
    public ngOnDestroy(): void {
559
        this.destroy$.next(true);
277✔
560
        this.destroy$.complete();
277✔
561
    }
562

563
    /**
564
     * @hidden @internal
565
     */
566
    public set selectedEntity(value: string) {
UNCOV
567
        this._selectedEntity = this.entities?.find(el => el.name === value);
×
568
    }
569

570
    /**
571
     * @hidden @internal
572
     */
573
    public get selectedEntity(): EntityType {
574
        return this._selectedEntity;
66,817✔
575
    }
576

577
    /**
578
     * @hidden @internal
579
     */
580
    public onEntitySelectChanging(event: ISelectionEventArgs) {
581
        event.cancel = true;
41✔
582
        this._entityNewValue = event.newSelection.value;
41✔
583
        if (event.oldSelection.value && this.queryBuilder.showEntityChangeDialog) {
41✔
584
            this.entityChangeDialog.open();
6✔
585
        } else {
586
            this.onEntityChangeConfirm();
35✔
587
        }
588
    }
589

590
    /**
591
     * @hidden
592
     */
593
    public onShowEntityChangeDialogChange(eventArgs: IChangeCheckboxEventArgs) {
594
        this.queryBuilder.showEntityChangeDialog = !eventArgs.checked;
1✔
595
    }
596

597
    /**
598
     * @hidden
599
     */
600
    public onEntityChangeCancel() {
601
        this.entityChangeDialog.close();
3✔
602
        this.entitySelect.close();
3✔
603
        this._entityNewValue = null;
3✔
604
    }
605

606
    /**
607
     * @hidden
608
     */
609
    public onEntityChangeConfirm() {
610
        if (this._parentExpression) {
37✔
611
            this._expressionTree = this.createExpressionTreeFromGroupItem(this.createExpressionGroupItem(this._expressionTree));
5✔
612
        }
613

614
        this._selectedEntity = this._entityNewValue;
37✔
615
        if (!this._selectedEntity.fields) {
37!
UNCOV
616
            this._selectedEntity.fields = [];
×
617
        }
618
        this.fields = this._entityNewValue ? this._entityNewValue.fields : [];
37!
619

620
        this._selectedReturnFields = this.parentExpression ? [] : this._entityNewValue.fields?.map(f => f.field);
128✔
621

622
        if (this._expressionTree) {
37✔
623
            this._expressionTree.entity = this._entityNewValue.name;
4✔
624
            this._expressionTree.returnFields = [];
4✔
625
            this._expressionTree.filteringOperands = [];
4✔
626

627
            this._editedExpression = null;
4✔
628
            if (!this.parentExpression) {
4✔
629
                this.expressionTreeChange.emit(this._expressionTree);
3✔
630
            }
631

632
            this.rootGroup = null;
4✔
633
            this.currentGroup = this.rootGroup;
4✔
634
        }
635

636
        this._selectedField = null;
37✔
637
        this.selectedCondition = null;
37✔
638
        this.searchValue.value = null;
37✔
639

640
        this.entityChangeDialog.close();
37✔
641
        this.entitySelect.close();
37✔
642

643
        this._entityNewValue = null;
37✔
644
        this.innerQueryNewExpressionTree = null;
37✔
645

646
        this.initExpressionTree(this._selectedEntity.name, this.selectedReturnFields);
37✔
647
    }
648

649
    /**
650
     * @hidden @internal
651
     */
652
    public set selectedReturnFields(value: string[]) {
653
        if (this._selectedReturnFields !== value) {
2✔
654
            this._selectedReturnFields = value;
2✔
655

656
            if (this._expressionTree && !this.parentExpression) {
2✔
657
                this._expressionTree.returnFields = value;
2✔
658
                this.expressionTreeChange.emit(this._expressionTree);
2✔
659
            }
660
        }
661
    }
662

663
    /**
664
     * @hidden @internal
665
     */
666
    public get selectedReturnFields(): string[] {
667
        if (typeof this._selectedReturnFields == 'string') {
44,463!
UNCOV
668
            return [this._selectedReturnFields];
×
669
        }
670
        return this._selectedReturnFields;
44,463✔
671
    }
672

673
    /**
674
     * @hidden @internal
675
     */
676
    public set selectedField(value: FieldType) {
677
        const oldValue = this._selectedField;
184✔
678

679
        if (this._selectedField !== value) {
184✔
680
            this._selectedField = value;
153✔
681
            this.selectDefaultCondition();
153✔
682
            if (oldValue && this._selectedField && this._selectedField.dataType !== oldValue.dataType) {
153✔
683
                this.searchValue.value = null;
17✔
684
                this.cdr.detectChanges();
17✔
685
            }
686
        }
687
    }
688

689
    /**
690
     * @hidden @internal
691
     */
692
    public get selectedField(): FieldType {
693
        return this._selectedField;
43,447✔
694
    }
695

696
    /**
697
     * @hidden @internal
698
     *
699
     * used by the grid
700
     */
701
    public setPickerOutlet(outlet?: IgxOverlayOutletDirective | ElementRef) {
702
        this.pickerOutlet = outlet;
44✔
703
    }
704

705
    /**
706
     * @hidden @internal
707
     *
708
     * used by the grid
709
     */
710
    public get isContextMenuVisible(): boolean {
UNCOV
711
        return !this.groupContextMenuDropDown.collapsed;
×
712
    }
713

714
    /**
715
     * @hidden @internal
716
     */
717
    public get hasEditedExpression(): boolean {
718
        return this._editedExpression !== undefined && this._editedExpression !== null;
30,614✔
719
    }
720

721
    /**
722
     * @hidden @internal
723
     */
724
    public addCondition(parent: ExpressionGroupItem, afterExpression?: ExpressionOperandItem, isUIInteraction?: boolean) {
725
        this.cancelOperandAdd();
67✔
726

727
        const operandItem = new ExpressionOperandItem({
67✔
728
            fieldName: null,
729
            condition: null,
730
            conditionName: null,
731
            ignoreCase: true,
732
            searchVal: null
733
        }, parent);
734

735
        const groupItem = new ExpressionGroupItem(this.getOperator(null) ?? FilteringLogic.And, parent);
67!
736
        this.contextualGroup = groupItem;
67✔
737
        this.initialOperator = null;
67✔
738

739
        this._lastFocusedChipIndex = this._lastFocusedChipIndex === undefined ? -1 : this._lastFocusedChipIndex;
67✔
740

741
        if (parent) {
67✔
742
            if (afterExpression) {
11✔
743
                const index = parent.children.indexOf(afterExpression);
1✔
744
                parent.children.splice(index + 1, 0, operandItem);
1✔
745
            } else {
746
                parent.children.push(operandItem);
10✔
747
            }
748
            this._lastFocusedChipIndex++;
11✔
749
        } else {
750
            this.rootGroup = groupItem;
56✔
751
            operandItem.parent = groupItem;
56✔
752
            this.rootGroup.children.push(operandItem);
56✔
753
            this._lastFocusedChipIndex = 0;
56✔
754
        }
755

756
        this._focusDelay = 250;
67✔
757

758
        if (isUIInteraction && !afterExpression) {
67✔
759
            this._lastFocusedChipIndex = this.expressionsChips.length;
63✔
760
            this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
63✔
761
        }
762

763
        this.enterExpressionEdit(operandItem);
67✔
764
    }
765

766
    /**
767
     * @hidden @internal
768
     */
769
    public addReverseGroup(parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
770
        parent = parent ?? this.rootGroup;
3!
771

772
        if (parent.operator === FilteringLogic.And) {
3✔
773
            this.addGroup(FilteringLogic.Or, parent, afterExpression);
2✔
774
        } else {
775
            this.addGroup(FilteringLogic.And, parent, afterExpression);
1✔
776
        }
777
    }
778

779
    /**
780
     * @hidden @internal
781
     */
782
    public endGroup(groupItem: ExpressionGroupItem) {
UNCOV
783
        this.currentGroup = groupItem.parent;
×
784
    }
785

786
    /**
787
     * @hidden @internal
788
     */
789
    public commitExpression() {
790
        this.commitOperandEdit();
44✔
791
        this.focusEditedExpressionChip();
44✔
792
    }
793

794
    /**
795
     * @hidden @internal
796
     */
797
    public discardExpression(expressionItem?: ExpressionOperandItem) {
798
        this.cancelOperandEdit();
11✔
799
        if (expressionItem && expressionItem.expression.fieldName) {
11✔
800
            this.focusEditedExpressionChip();
4✔
801
        }
802
    }
803

804
    /**
805
     * @hidden @internal
806
     */
807
    public commitOperandEdit() {
808
        const actualSearchValue = this.searchValue.value;
59✔
809
        if (this._editedExpression) {
59✔
810
            this._editedExpression.expression.fieldName = this.selectedField.field;
56✔
811
            this._editedExpression.expression.condition = this.selectedField.filters.condition(this.selectedCondition);
56✔
812
            this._editedExpression.expression.conditionName = this.selectedCondition;
56✔
813
            this._editedExpression.expression.searchVal = DataUtil.parseValue(this.selectedField.dataType, actualSearchValue) || actualSearchValue;
56✔
814
            this._editedExpression.fieldLabel = this.selectedField.label
56!
815
                ? this.selectedField.label
816
                : this.selectedField.header
56✔
817
                    ? this.selectedField.header
818
                    : this.selectedField.field;
819

820
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0]
56✔
821
            if (innerQuery && this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) {
56✔
822
                innerQuery.exitEditAddMode();
7✔
823
                this._editedExpression.expression.searchTree = this.getExpressionTreeCopy(innerQuery.expressionTree);
7✔
824
                this._editedExpression.expression.searchTree.returnFields = innerQuery.selectedReturnFields;
7✔
825
            } else {
826
                this._editedExpression.expression.searchTree = null;
49✔
827
            }
828
            this.innerQueryNewExpressionTree = null;
56✔
829

830
            if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary || this.selectedField.filters.condition(this.selectedCondition)?.isNestedQuery) {
56✔
831
                this._editedExpression.expression.searchVal = null;
12✔
832
            }
833

834
            this._editedExpression.inEditMode = false;
56✔
835
            this._editedExpression = null;
56✔
836
        }
837

838
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, this.selectedEntity?.name, this.selectedReturnFields);
59✔
839
        if (!this.parentExpression) {
59✔
840
            this.expressionTreeChange.emit(this._expressionTree);
50✔
841
        }
842
    }
843

844
    /**
845
     * @hidden @internal
846
     */
847
    public cancelOperandAdd() {
848
        if (this._addModeExpression) {
563!
UNCOV
849
            this._addModeExpression.inAddMode = false;
×
UNCOV
850
            this._addModeExpression = null;
×
851
        }
852
    }
853

854
    /**
855
     * @hidden @internal
856
     */
857
    public deleteItem = (expressionItem: ExpressionItem, skipEmit: boolean = false) => {
277✔
858
        if (!expressionItem.parent) {
48✔
859
            this.rootGroup = null;
15✔
860
            this.currentGroup = null;
15✔
861
            //this._expressionTree = null;
862
            return;
15✔
863
        }
864

865
        if (expressionItem === this.currentGroup) {
33!
UNCOV
866
            this.currentGroup = this.currentGroup.parent;
×
867
        }
868

869
        const children = expressionItem.parent.children;
33✔
870
        const index = children.indexOf(expressionItem);
33✔
871
        children.splice(index, 1);
33✔
872
        const entity = this.expressionTree ? this.expressionTree.entity : null;
33✔
873
        const returnFields = this.expressionTree ? this.expressionTree.returnFields : null;
33✔
874
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, entity, returnFields); // TODO: don't recreate if not necessary
33✔
875

876
        if (!children.length) {
33✔
877
            this.deleteItem(expressionItem.parent, true);
16✔
878
        }
879

880
        if (!this.parentExpression && !skipEmit) {
33✔
881
            this.expressionTreeChange.emit(this._expressionTree);
31✔
882
        }
883
    }
884

885
    /**
886
     * @hidden @internal
887
     */
888
    public cancelOperandEdit() {
889
        if (this.innerQueries) {
505✔
890
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
228✔
891
            if (innerQuery) {
228✔
892
                if (innerQuery._editedExpression) {
9✔
893
                    innerQuery.cancelOperandEdit();
1✔
894
                }
895

896
                innerQuery.expressionTree = this.getExpressionTreeCopy(this._editedExpression.expression.searchTree);
9✔
897
                this.innerQueryNewExpressionTree = null;
9✔
898
            }
899
        }
900

901
        if (this._editedExpression) {
505✔
902
            this._editedExpression.inEditMode = false;
30✔
903

904
            if (!this._editedExpression.expression.fieldName) {
30✔
905
                this.deleteItem(this._editedExpression);
16✔
906
            }
907

908
            this._editedExpression = null;
30✔
909
        }
910

911
        if (!this.expressionTree && this.contextualGroup) {
505✔
912
            this.initialOperator = this.contextualGroup.operator;
1✔
913
        }
914
    }
915

916
    /**
917
     * @hidden @internal
918
     */
919
    public operandCanBeCommitted(): boolean {
920
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
1,544✔
921

922
        return this.selectedField && this.selectedCondition &&
1,544✔
923
            (
924
                (
925
                    ((!Array.isArray(this.searchValue.value) && !!this.searchValue.value) || (Array.isArray(this.searchValue.value) && this.searchValue.value.length !== 0)) &&
926
                    !(this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery)
927
                ) ||
928
                (
929
                    this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery && innerQuery && !!innerQuery.expressionTree && innerQuery.selectedReturnFields?.length > 0
930
                ) ||
931
                this.selectedField.filters.condition(this.selectedCondition)?.isUnary
932
            );
933
    }
934

935
    /**
936
     * @hidden @internal
937
     */
938
    public canCommitCurrentState(): boolean {
939
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
36✔
940
        if (innerQuery) {
36✔
941
            return this.selectedReturnFields?.length > 0 && innerQuery.canCommitCurrentState();
8✔
942
        } else {
943
            return this.selectedReturnFields?.length > 0 &&
28✔
944
                (
945
                    (!this._editedExpression) || // no edited expr
946
                    (this._editedExpression && !this.selectedField) || // empty edited expr
947
                    (this._editedExpression && this.operandCanBeCommitted() === true) // valid edited expr
948
                );
949
        }
950
    }
951

952
    /**
953
     * @hidden @internal
954
     */
955
    public commitCurrentState(): void {
956
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
3✔
957
        if (innerQuery) {
3✔
958
            innerQuery.commitCurrentState();
1✔
959
        }
960

961
        if (this._editedExpression) {
3✔
962
            if (this.selectedField) {
3!
963
                this.commitOperandEdit();
3✔
964
            } else {
UNCOV
965
                this.deleteItem(this._editedExpression);
×
UNCOV
966
                this._editedExpression = null;
×
967
            }
968
        }
969
    }
970

971
    /**
972
     * @hidden @internal
973
     */
974
    public exitEditAddMode(shouldPreventInit = false) {
39✔
975
        if (!this._editedExpression) {
211✔
976
            return;
196✔
977
        }
978

979
        this.exitOperandEdit();
15✔
980
        this.cancelOperandAdd();
15✔
981

982
        if (shouldPreventInit) {
15✔
983
            this._preventInit = true;
7✔
984
        }
985
    }
986

987
    /**
988
     * @hidden @internal
989
     *
990
     * used by the grid
991
     */
992
    public exitOperandEdit() {
993
        if (!this._editedExpression) {
34✔
994
            return;
15✔
995
        }
996

997
        if (this.operandCanBeCommitted()) {
19✔
998
            this.commitOperandEdit();
9✔
999
        } else {
1000
            this.cancelOperandEdit();
10✔
1001
        }
1002
    }
1003

1004
    /**
1005
     * @hidden @internal
1006
     */
1007
    public isExpressionGroup(expression: ExpressionItem): boolean {
1008
        return expression instanceof ExpressionGroupItem;
34,156✔
1009
    }
1010

1011
    /**
1012
     * @hidden @internal
1013
     */
1014
    public onExpressionFocus(expressionItem: ExpressionOperandItem) {
1015
        if (this.prevFocusedExpression) {
41✔
1016
            this.prevFocusedExpression.focused = false;
4✔
1017
        }
1018
        expressionItem.focused = true;
41✔
1019
        this.prevFocusedExpression = expressionItem;
41✔
1020
    }
1021

1022
    /**
1023
     * @hidden @internal
1024
     */
1025
    public onExpressionBlur(event, expressionItem: ExpressionOperandItem) {
1026
        if (this._prevFocusedContainer && this._prevFocusedContainer !== event.target.closest('.igx-filter-tree__expression-item')) {
2!
UNCOV
1027
            expressionItem.focused = false;
×
1028
        }
1029
        this._prevFocusedContainer = event.target.closest('.igx-filter-tree__expression-item');
2✔
1030
    }
1031

1032
    /**
1033
     * @hidden @internal
1034
     */
1035
    public onChipRemove(expressionItem: ExpressionItem) {
1036
        this.exitEditAddMode();
5✔
1037
        this.deleteItem(expressionItem);
5✔
1038
    }
1039

1040
    /**
1041
     * @hidden @internal
1042
     */
1043
    public focusChipAfterDrag = (index: number) => {
277✔
1044
        this._lastFocusedChipIndex = index;
11✔
1045
        this.focusEditedExpressionChip();
11✔
1046
    }
1047
    /**
1048
     * @hidden @internal
1049
     */
1050
    public addExpressionBlur() {
UNCOV
1051
        if (this.prevFocusedExpression) {
×
UNCOV
1052
            this.prevFocusedExpression.focused = false;
×
1053
        }
UNCOV
1054
        if (this.addExpressionItemDropDown && !this.addExpressionItemDropDown.collapsed) {
×
UNCOV
1055
            this.addExpressionItemDropDown.close();
×
1056
        }
1057
    }
1058

1059
    /**
1060
     * @hidden @internal
1061
     */
1062
    public onChipClick(expressionItem: ExpressionOperandItem, chip: IgxChipComponent) {
1063
        this.enterExpressionEdit(expressionItem, chip);
55✔
1064
    }
1065

1066
    /**
1067
     * @hidden @internal
1068
     */
1069
    public enterExpressionEdit(expressionItem: ExpressionOperandItem, chip?: IgxChipComponent) {
1070
        this.exitEditAddMode(true);
122✔
1071
        this.cdr.detectChanges();
122✔
1072
        this._lastFocusedChipIndex = chip ? this.expressionsChips.toArray().findIndex(expr => expr === chip) : this._lastFocusedChipIndex;
122✔
1073
        this.enterEditMode(expressionItem);
122✔
1074
    }
1075

1076

1077
    /**
1078
     * @hidden @internal
1079
     */
1080
    public clickExpressionAdd(targetButton: HTMLElement, chip: IgxChipComponent) {
1081
        this.exitEditAddMode(true);
4✔
1082
        this.cdr.detectChanges();
4✔
1083
        this._lastFocusedChipIndex = this.expressionsChips.toArray().findIndex(expr => expr === chip);
22✔
1084
        this.openExpressionAddDialog(targetButton);
4✔
1085
    }
1086

1087
    /**
1088
     * @hidden @internal
1089
     */
1090
    public openExpressionAddDialog(targetButton: HTMLElement) {
1091
        this.addExpressionDropDownOverlaySettings.target = targetButton;
4✔
1092
        this.addExpressionDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
4✔
1093
            horizontalDirection: HorizontalAlignment.Right,
1094
            horizontalStartPoint: HorizontalAlignment.Left,
1095
            verticalStartPoint: VerticalAlignment.Bottom
1096
        });
1097

1098
        this.addExpressionItemDropDown.open(this.addExpressionDropDownOverlaySettings);
4✔
1099
    }
1100

1101
    /**
1102
     * @hidden @internal
1103
     */
1104
    public enterExpressionAdd(event: ISelectionEventArgs, expressionItem: ExpressionOperandItem) {
1105
        if (this._addModeExpression) {
1!
UNCOV
1106
            this._addModeExpression.inAddMode = false;
×
1107
        }
1108

1109
        if (this.parentExpression) {
1!
UNCOV
1110
            this.inEditModeChange.emit(this.parentExpression);
×
1111
        }
1112

1113
        const parent = expressionItem.parent ?? this.rootGroup;
1!
1114
        requestAnimationFrame(() => {
1✔
1115
            if (event.newSelection.value === 'addCondition') {
1!
1116
                this.addCondition(parent, expressionItem);
1✔
UNCOV
1117
            } else if (event.newSelection.value === 'addGroup') {
×
UNCOV
1118
                this.addReverseGroup(parent, expressionItem);
×
1119
            }
1120
            expressionItem.inAddMode = true;
1✔
1121
            this._addModeExpression = expressionItem;
1✔
1122
        })
1123
    }
1124

1125
    /**
1126
     * @hidden @internal
1127
     */
1128
    public enterEditMode(expressionItem: ExpressionOperandItem) {
1129
        if (this._editedExpression) {
122!
UNCOV
1130
            this._editedExpression.inEditMode = false;
×
1131
        }
1132

1133
        if (this.parentExpression) {
122✔
1134
            this.inEditModeChange.emit(this.parentExpression);
13✔
1135
        }
1136

1137
        expressionItem.hovered = false;
122✔
1138
        this.fields = this.selectedEntity ? this.selectedEntity.fields : null;
122✔
1139
        this.selectedField =
122✔
1140
            expressionItem.expression.fieldName ?
122✔
1141
                this.fields?.find(field => field.field === expressionItem.expression.fieldName)
89✔
1142
                : null;
1143
        this.selectedCondition =
122✔
1144
            expressionItem.expression.condition ?
122✔
1145
                expressionItem.expression.condition.name :
1146
                null;
1147
        this.searchValue.value = expressionItem.expression.searchVal instanceof Set ?
122✔
1148
            Array.from(expressionItem.expression.searchVal) :
1149
            expressionItem.expression.searchVal;
1150

1151
        expressionItem.inEditMode = true;
122✔
1152
        this._editedExpression = expressionItem;
122✔
1153
        this.cdr.detectChanges();
122✔
1154

1155
        this.entitySelectOverlaySettings.target = this.entitySelect.element;
122✔
1156
        this.entitySelectOverlaySettings.excludeFromOutsideClick = [this.entitySelect.element as HTMLElement];
122✔
1157
        this.entitySelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
122✔
1158

1159
        if (this.returnFieldSelect) {
122✔
1160
            this.returnFieldSelectOverlaySettings.target = this.returnFieldSelect.element;
13✔
1161
            this.returnFieldSelectOverlaySettings.excludeFromOutsideClick = [this.returnFieldSelect.element as HTMLElement];
13✔
1162
            this.returnFieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
13✔
1163
        }
1164
        if (this.fieldSelect) {
122✔
1165
            this.fieldSelectOverlaySettings.target = this.fieldSelect.element;
119✔
1166
            this.fieldSelectOverlaySettings.excludeFromOutsideClick = [this.fieldSelect.element as HTMLElement];
119✔
1167
            this.fieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
119✔
1168
        }
1169
        if (this.conditionSelect) {
122✔
1170
            this.conditionSelectOverlaySettings.target = this.conditionSelect.element;
119✔
1171
            this.conditionSelectOverlaySettings.excludeFromOutsideClick = [this.conditionSelect.element as HTMLElement];
119✔
1172
            this.conditionSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
119✔
1173
        }
1174

1175
        if (!this.selectedField) {
122✔
1176
            this.fieldSelect.input.nativeElement.focus();
67✔
1177
        } else if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary) {
55✔
1178
            this.conditionSelect?.input.nativeElement.focus();
5✔
1179
        } else {
1180
            const input = this.searchValueInput?.nativeElement || this.picker?.getEditElement();
50✔
1181
            input?.focus();
50✔
1182
        }
1183

1184
        (this.editingInputs?.nativeElement.parentElement as HTMLElement)?.scrollIntoView({ block: "nearest", inline: "nearest" });
122✔
1185
    }
1186

1187
    /**
1188
     * @hidden @internal
1189
     */
1190
    public onConditionSelectChanging(event: ISelectionEventArgs) {
1191
        event.cancel = true;
61✔
1192
        this.selectedCondition = event.newSelection.value;
61✔
1193
        this.conditionSelect.close();
61✔
1194
        this.cdr.detectChanges();
61✔
1195
    }
1196

1197
    /**
1198
     * @hidden @internal
1199
     */
1200
    public onKeyDown(eventArgs: KeyboardEvent) {
UNCOV
1201
        eventArgs.stopPropagation();
×
1202
    }
1203

1204
    /**
1205
     * @hidden @internal
1206
     */
1207
    public onGroupClick(groupContextMenuDropDown: any, targetButton: HTMLButtonElement, groupItem: ExpressionGroupItem) {
1208
        this.exitEditAddMode();
7✔
1209
        this.cdr.detectChanges();
7✔
1210

1211
        this.groupContextMenuDropDown = groupContextMenuDropDown;
7✔
1212
        this.groupContextMenuDropDownOverlaySettings.target = targetButton;
7✔
1213
        this.groupContextMenuDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
7✔
1214
            horizontalDirection: HorizontalAlignment.Right,
1215
            horizontalStartPoint: HorizontalAlignment.Left,
1216
            verticalStartPoint: VerticalAlignment.Bottom
1217
        });
1218

1219
        if (groupContextMenuDropDown.collapsed) {
7!
1220
            this.contextualGroup = groupItem;
7✔
1221
            groupContextMenuDropDown.open(this.groupContextMenuDropDownOverlaySettings);
7✔
1222
        } else {
UNCOV
1223
            groupContextMenuDropDown.close();
×
1224
        }
1225
    }
1226

1227
    /**
1228
     * @hidden @internal
1229
     */
1230
    public getOperator(expressionItem: any) {
1231
        // if (!expressionItem && !this.expressionTree && !this.initialOperator) {
1232
        //     this.initialOperator = 0;
1233
        // }
1234

1235
        const operator = expressionItem ?
87,469✔
1236
            expressionItem.operator :
1237
            this.expressionTree ?
2,521✔
1238
                this.expressionTree.operator :
1239
                this.initialOperator;
1240
        return operator;
87,469✔
1241
    }
1242

1243
    /**
1244
     * @hidden @internal
1245
     */
1246
    public getSwitchGroupText(expressionItem: any) {
1247
        const operator = this.getOperator(expressionItem);
14,567✔
1248
        const condition = operator === FilteringLogic.Or ? this.resourceStrings.igx_query_builder_and_label : this.resourceStrings.igx_query_builder_or_label
14,567✔
1249
        return this.resourceStrings.igx_query_builder_switch_group.replace('{0}', condition.toUpperCase());
14,567✔
1250
    }
1251

1252
    /**
1253
     * @hidden @internal
1254
     */
1255
    public onGroupContextMenuDropDownSelectionChanging(event: ISelectionEventArgs) {
1256
        event.cancel = true;
4✔
1257

1258
        if (event.newSelection.value === 'switchCondition') {
4✔
1259
            const newOperator = (!this.expressionTree ? this.initialOperator : (this.contextualGroup ?? this._expressionTree).operator) === 0 ? 1 : 0;
3!
1260
            this.selectFilteringLogic(newOperator);
3✔
1261
        } else if (event.newSelection.value === 'ungroup') {
1✔
1262
            this.ungroup();
1✔
1263
        }
1264

1265
        this.groupContextMenuDropDown.close();
4✔
1266
    }
1267

1268
    /**
1269
     * @hidden @internal
1270
     */
1271
    public ungroup() {
1272
        const selectedGroup = this.contextualGroup;
1✔
1273
        const parent = selectedGroup.parent;
1✔
1274
        if (parent) {
1✔
1275
            const index = parent.children.indexOf(selectedGroup);
1✔
1276
            parent.children.splice(index, 1, ...selectedGroup.children);
1✔
1277

1278
            for (const expr of selectedGroup.children) {
1✔
1279
                expr.parent = parent;
2✔
1280
            }
1281
        }
1282
        this.commitOperandEdit();
1✔
1283
    }
1284

1285
    /**
1286
     * @hidden @internal
1287
     */
1288
    public selectFilteringLogic(index: number) {
1289
        if (!this.expressionTree) {
3!
UNCOV
1290
            this.initialOperator = index;
×
UNCOV
1291
            return;
×
1292
        }
1293

1294
        if (this.contextualGroup) {
3✔
1295
            this.contextualGroup.operator = index as FilteringLogic;
2✔
1296
            this.commitOperandEdit();
2✔
1297
        } else if (this.expressionTree) {
1✔
1298
            this._expressionTree.operator = index as FilteringLogic;
1✔
1299
        }
1300

1301
        this.initialOperator = null;
3✔
1302
    }
1303

1304
    /**
1305
     * @hidden @internal
1306
     */
1307
    public getConditionFriendlyName(name: string): string {
1308
        // 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.
1309
        // So instead of in/notIn we end up with 'inQuery'/'notInQuery', hence removing the suffix from the friendly name.
1310
        return this.resourceStrings[`igx_query_builder_filter_${name?.replace('Query', '')}`] || name;
57,107✔
1311
    }
1312

1313
    /**
1314
     * @hidden @internal
1315
     */
1316
    public isDate(value: any) {
1317
        return value instanceof Date;
19,949✔
1318
    }
1319

1320
    /**
1321
     * @hidden @internal
1322
     */
1323
    public invokeClick(eventArgs: KeyboardEvent) {
1324
        if (!this.dragService.dropGhostExpression && this.platform.isActivationKey(eventArgs)) {
2✔
1325
            eventArgs.preventDefault();
2✔
1326
            (eventArgs.currentTarget as HTMLElement).click();
2✔
1327
        }
1328
    }
1329

1330
    /**
1331
     * @hidden @internal
1332
     */
1333
    public openPicker(args: KeyboardEvent) {
UNCOV
1334
        if (this.platform.isActivationKey(args)) {
×
UNCOV
1335
            args.preventDefault();
×
UNCOV
1336
            this.picker.open();
×
1337
        }
1338
    }
1339

1340
    /**
1341
     * @hidden @internal
1342
     */
1343
    public onOutletPointerDown(event) {
1344
        // This prevents closing the select's dropdown when clicking the scroll
1345
        event.preventDefault();
53✔
1346
    }
1347

1348
    /**
1349
     * @hidden @internal
1350
     */
1351
    public getConditionList(): string[] {
1352
        if (!this.selectedField) return [];
1,498✔
1353

1354
        if (this.entities?.length === 1 && !this.entities[0].name) {
1,106✔
1355
            return this.selectedField.filters.conditionList();
214✔
1356
        }
1357

1358
        return this.selectedField.filters.extendedConditionList();
892✔
1359
    }
1360

1361
    /**
1362
     * @hidden @internal
1363
     */
1364
    public getFormatter(field: string) {
1365
        return this.fields?.find(el => el.field === field)?.formatter;
71,896✔
1366
    }
1367

1368
    /**
1369
     * @hidden @internal
1370
     */
1371
    public getFormat(field: string) {
1372
        return this.fields?.find(el => el.field === field).pipeArgs.format;
1,755✔
1373
    }
1374

1375
    /**
1376
     * @hidden @internal
1377
     *
1378
     * used by the grid
1379
     */
1380
    public setAddButtonFocus() {
1381
        if (this.addRootAndGroupButton) {
39!
UNCOV
1382
            this.addRootAndGroupButton.nativeElement.focus();
×
1383
        } else if (this.addConditionButton) {
39✔
1384
            this.addConditionButton.nativeElement.focus();
18✔
1385
        }
1386
    }
1387

1388
    /**
1389
     * @hidden @internal
1390
     */
1391
    public context(expression: ExpressionItem, afterExpression?: ExpressionItem) {
1392
        return {
55,335✔
1393
            $implicit: expression,
1394
            afterExpression
1395
        };
1396
    }
1397

1398
    public formatReturnFields(innerTree: IFilteringExpressionsTree) {
1399
        const returnFields = innerTree.returnFields;
3,610✔
1400
        let text = returnFields.join(', ');
3,610✔
1401
        const innerTreeEntity = this.entities?.find(el => el.name === innerTree.entity);
3,610✔
1402
        if (returnFields.length === innerTreeEntity?.fields.length) {
3,610!
UNCOV
1403
            text = this.resourceStrings.igx_query_builder_all_fields;
×
1404
        } else {
1405
            text = returnFields.join(', ');
3,610✔
1406
            text = text.length > 25 ? text.substring(0, 25) + ' ...' : text;
3,610!
1407
        }
1408
        return text;
3,610✔
1409
    }
1410

1411
    public isInEditMode(): boolean {
1412
        return !this.parentExpression || (this.parentExpression && this.parentExpression.inEditMode);
63,407✔
1413
    }
1414

1415
    public onInEditModeChanged(expressionItem: ExpressionOperandItem) {
1416
        if (!expressionItem.inEditMode) {
13!
UNCOV
1417
            this.enterExpressionEdit(expressionItem);
×
1418
        }
1419
    }
1420

1421
    public getExpressionTreeCopy(expressionTree: IExpressionTree, shouldAssignInnerQueryExprTree?: boolean): IExpressionTree {
1422
        if (!expressionTree) {
210✔
1423
            return null;
180✔
1424
        }
1425

1426
        const exprTreeCopy = new FilteringExpressionsTree(expressionTree.operator, expressionTree.fieldName, expressionTree.entity, expressionTree.returnFields);
30✔
1427
        exprTreeCopy.filteringOperands = [];
30✔
1428

1429
        expressionTree.filteringOperands.forEach(o => isTree(o) ? exprTreeCopy.filteringOperands.push(this.getExpressionTreeCopy(o)) : exprTreeCopy.filteringOperands.push(o));
55!
1430

1431
        if (!this.innerQueryNewExpressionTree && shouldAssignInnerQueryExprTree) {
30✔
1432
            this.innerQueryNewExpressionTree = exprTreeCopy;
18✔
1433
        }
1434

1435
        return exprTreeCopy;
30✔
1436
    }
1437

1438
    public onSelectAllClicked() {
UNCOV
1439
        if (
×
1440
            (this._selectedReturnFields.length > 0 && this._selectedReturnFields.length < this._selectedEntity.fields.length) ||
×
1441
            this._selectedReturnFields.length == this._selectedEntity.fields.length
1442
        ) {
UNCOV
1443
            this.returnFieldsCombo.deselectAllItems();
×
1444
        } else {
UNCOV
1445
            this.returnFieldsCombo.selectAllItems();
×
1446
        }
1447
    }
1448

1449
    public onReturnFieldSelectChanging(event: IComboSelectionChangingEventArgs | ISelectionEventArgs) {
1450
        let newSelection = [];
8✔
1451
        if (Array.isArray(event.newSelection)) {
8✔
1452
            newSelection = event.newSelection.map(item => item.field)
6✔
1453
        } else {
1454
            newSelection.push(event.newSelection.value);
6✔
1455
            this._selectedReturnFields = newSelection;
6✔
1456
        }
1457

1458
        this.initExpressionTree(this.selectedEntity.name, newSelection);
8✔
1459
    }
1460

1461
    public initExpressionTree(selectedEntityName: string, selectedReturnFields: string[]) {
1462
        if (!this._expressionTree) {
45✔
1463
            this._expressionTree = this.createExpressionTreeFromGroupItem(new ExpressionGroupItem(FilteringLogic.And, this.rootGroup), selectedEntityName, selectedReturnFields);
33✔
1464
        }
1465

1466
        if (!this.parentExpression) {
45✔
1467
            this.expressionTreeChange.emit(this._expressionTree);
34✔
1468
        }
1469
    }
1470

1471
    public getSearchValueTemplateContext(defaultSearchValueTemplate): any {
1472
        const ctx = {
1,498✔
1473
            $implicit: this.searchValue,
1474
            selectedField: this.selectedField,
1475
            selectedCondition: this.selectedCondition,
1476
            defaultSearchValueTemplate: defaultSearchValueTemplate
1477
        };
1478
        return ctx;
1,498✔
1479
    }
1480

1481
    private getPipeArgs(field: FieldType) {
1482
        let pipeArgs = {...field.pipeArgs};
2,996✔
1483
        if (!pipeArgs) {
2,996!
UNCOV
1484
            pipeArgs = { digitsInfo: DEFAULT_PIPE_DIGITS_INFO };
×
1485
        }
1486

1487
        if (!pipeArgs.format) {
2,996✔
1488
            pipeArgs.format = field.dataType === DataType.Time ?
2,996!
1489
                DEFAULT_PIPE_TIME_FORMAT : field.dataType === DataType.DateTime ?
2,996!
1490
                    DEFAULT_PIPE_DATE_TIME_FORMAT : DEFAULT_PIPE_DATE_FORMAT;
1491
        }
1492
        
1493
        return pipeArgs;
2,996✔
1494
    }
1495

1496
    private selectDefaultCondition() {
1497
        if (this.selectedField && this.selectedField.filters) {
153✔
1498
            this.selectedCondition = this.selectedField.filters.conditionList().indexOf('equals') >= 0 ? 'equals' : this.selectedField.filters.conditionList()[0];
117✔
1499
        }
1500
    }
1501

1502
    private getFilters(field: FieldType) {
1503
        if (!field.filters) {
2,996✔
1504
            switch (field.dataType) {
2,996!
1505
                case DataType.Boolean:
1506
                    return IgxBooleanFilteringOperand.instance();
749✔
1507
                case DataType.Number:
1508
                case DataType.Currency:
1509
                case DataType.Percent:
1510
                    return IgxNumberFilteringOperand.instance();
1,119✔
1511
                case DataType.Date:
1512
                    return IgxDateFilteringOperand.instance();
379✔
1513
                case DataType.Time:
UNCOV
1514
                    return IgxTimeFilteringOperand.instance();
×
1515
                case DataType.DateTime:
UNCOV
1516
                    return IgxDateTimeFilteringOperand.instance();
×
1517
                case DataType.String:
1518
                default:
1519
                    return IgxStringFilteringOperand.instance();
749✔
1520
            }
1521
        }
1522
    }
1523

1524

1525
    private addGroup(operator: FilteringLogic, parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
1526
        this.cancelOperandAdd();
3✔
1527

1528
        const groupItem = new ExpressionGroupItem(operator, parent);
3✔
1529

1530
        if (parent) {
3!
1531
            if (afterExpression) {
3!
UNCOV
1532
                const index = parent.children.indexOf(afterExpression);
×
UNCOV
1533
                parent.children.splice(index + 1, 0, groupItem);
×
1534
            } else {
1535
                parent.children.push(groupItem);
3✔
1536
            }
1537
        } else {
UNCOV
1538
            this.rootGroup = groupItem;
×
1539
        }
1540

1541
        this.addCondition(groupItem);
3✔
1542
        this.currentGroup = groupItem;
3✔
1543
    }
1544

1545
    private createExpressionGroupItem(expressionTree: IExpressionTree, parent?: ExpressionGroupItem, entityName?: string): ExpressionGroupItem {
1546
        let groupItem: ExpressionGroupItem;
1547
        if (expressionTree) {
826✔
1548
            groupItem = new ExpressionGroupItem(expressionTree.operator, parent);
674✔
1549
            if (!expressionTree.filteringOperands) {
674!
UNCOV
1550
                return groupItem;
×
1551
            }
1552

1553
            for (let i = 0; i < expressionTree.filteringOperands.length; i++) {
674✔
1554
                const expr = expressionTree.filteringOperands[i];
1,471✔
1555

1556
                if (isTree(expr)) {
1,471✔
1557
                    groupItem.children.push(this.createExpressionGroupItem(expr, groupItem, expressionTree.entity));
90✔
1558
                } else {
1559
                    const filteringExpr = expr as IFilteringExpression;
1,381✔
1560
                    const exprCopy: IFilteringExpression = {
1,381✔
1561
                        fieldName: filteringExpr.fieldName,
1562
                        condition: filteringExpr.condition,
1563
                        conditionName: filteringExpr.condition?.name || filteringExpr.conditionName,
1,381!
1564
                        searchVal: filteringExpr.searchVal,
1565
                        searchTree: filteringExpr.searchTree,
1566
                        ignoreCase: filteringExpr.ignoreCase
1567
                    };
1568
                    const operandItem = new ExpressionOperandItem(exprCopy, groupItem);
1,381✔
1569
                    const field = this.fields?.find(el => el.field === filteringExpr.fieldName);
1,992✔
1570
                    operandItem.fieldLabel = field?.label || field?.header || field?.field;
1,381✔
1571
                    if (this._expandedExpressions.filter(e => e.searchTree == operandItem.expression.searchTree).length > 0) {
1,381!
UNCOV
1572
                        operandItem.expanded = true;
×
1573
                    }
1574
                    groupItem.children.push(operandItem);
1,381✔
1575
                }
1576
            }
1577

1578

1579
            if (expressionTree.entity) {
674✔
1580
                entityName = expressionTree.entity;
617✔
1581
            }
1582
            const entity = this.entities?.find(el => el.name === entityName);
967✔
1583
            if (entity) {
674✔
1584
                this.fields = entity.fields;
617✔
1585
            }
1586

1587
            this._selectedEntity = this.entities?.find(el => el.name === entityName);
967✔
1588
            this._selectedReturnFields =
674✔
1589
                !expressionTree.returnFields || expressionTree.returnFields.includes('*') || expressionTree.returnFields.includes('All') || expressionTree.returnFields.length === 0
2,832✔
1590
                    ? this.fields?.map(f => f.field)
1,221✔
1591
                    : this.fields?.filter(f => expressionTree.returnFields.indexOf(f.field) >= 0).map(f => f.field);
1,641✔
1592
        }
1593
        return groupItem;
826✔
1594
    }
1595

1596
    private createExpressionTreeFromGroupItem(groupItem: ExpressionGroupItem, entity?: string, returnFields?: string[]): FilteringExpressionsTree {
1597
        if (!groupItem) {
149✔
1598
            return null;
4✔
1599
        }
1600

1601
        const expressionTree = new FilteringExpressionsTree(groupItem.operator, undefined, entity, returnFields);
145✔
1602

1603
        for (let i = 0; i < groupItem.children.length; i++) {
145✔
1604
            const item = groupItem.children[i];
199✔
1605

1606
            if (item instanceof ExpressionGroupItem) {
199✔
1607
                const subTree = this.createExpressionTreeFromGroupItem((item as ExpressionGroupItem), entity, returnFields);
19✔
1608
                expressionTree.filteringOperands.push(subTree);
19✔
1609
            } else {
1610
                expressionTree.filteringOperands.push((item as ExpressionOperandItem).expression);
180✔
1611
            }
1612
        }
1613

1614
        return expressionTree;
145✔
1615
    }
1616

1617
    private scrollElementIntoView(target: HTMLElement) {
1618
        const container = this.expressionsContainer.nativeElement;
619✔
1619
        const targetOffset = target.offsetTop - container.offsetTop;
619✔
1620
        const delta = 10;
619✔
1621

1622
        if (container.scrollTop + delta > targetOffset) {
619✔
1623
            container.scrollTop = targetOffset - delta;
408✔
1624
        } else if (container.scrollTop + container.clientHeight < targetOffset + target.offsetHeight + delta) {
211✔
1625
            container.scrollTop = targetOffset + target.offsetHeight + delta - container.clientHeight;
206✔
1626
        }
1627
    }
1628

1629
    private focusEditedExpressionChip() {
1630
        if (this._timeoutId) {
59✔
1631
            clearTimeout(this._timeoutId);
5✔
1632
        }
1633

1634
        this._timeoutId = setTimeout(() => {
59✔
1635
            if (this._lastFocusedChipIndex != -1) {
59✔
1636
                //Sort the expression chip list.
1637
                //If there was a recent drag&drop and the tree hasn't rerendered(child query), they will be unordered
1638
                const sortedChips = this.expressionsChips.toArray().sort(function (a, b) {
57✔
1639
                    if (a === b) return 0;
73!
1640
                    if (a.chipArea.nativeElement.compareDocumentPosition(b.chipArea.nativeElement) & 2) {
73✔
1641
                        // b comes before a
1642
                        return 1;
60✔
1643
                    }
1644
                    return -1;
13✔
1645
                });
1646
                const chipElement = sortedChips[this._lastFocusedChipIndex]?.nativeElement;
57✔
1647
                if (chipElement) {
57✔
1648
                    chipElement.focus();
49✔
1649
                }
1650
                this._lastFocusedChipIndex = -1;
57✔
1651
                this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
57✔
1652
            }
1653
        }, this._focusDelay);
1654
    }
1655

1656
    private init() {
1657
        this.cancelOperandAdd();
478✔
1658
        this.cancelOperandEdit();
478✔
1659

1660
        // Ignore values of certain properties for the comparison
1661
        const propsToIgnore = ['parent', 'hovered', 'ignoreCase', 'inEditMode', 'inAddMode'];
478✔
1662
        const propsReplacer = function replacer(key, value) {
478✔
1663
            if (propsToIgnore.indexOf(key) >= 0) {
19,183✔
1664
                return undefined;
2,564✔
1665
            } else {
1666
                return value;
16,619✔
1667
            }
1668
        };
1669

1670
        // Skip root being recreated if the same
1671
        const newRootGroup = this.createExpressionGroupItem(this.expressionTree);
478✔
1672
        if (JSON.stringify(this.rootGroup, propsReplacer) !== JSON.stringify(newRootGroup, propsReplacer)) {
478✔
1673
            this.rootGroup = this.createExpressionGroupItem(this.expressionTree);
253✔
1674
            this.currentGroup = this.rootGroup;
253✔
1675
        }
1676

1677
        if (this.rootGroup?.children?.length == 0) {
478✔
1678
            this.rootGroup = null;
40✔
1679
            this.currentGroup = null;
40✔
1680
        }
1681
    }
1682

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