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

IgniteUI / igniteui-angular / 14407887577

11 Apr 2025 04:38PM UTC coverage: 91.586%. First build
14407887577

Pull #15562

github

web-flow
Merge bf41866d7 into f153d4db3
Pull Request #15562: feat(h-grid): advanced filtering POC with sample

13402 of 15680 branches covered (85.47%)

72 of 84 new or added lines in 6 files covered. (85.71%)

26985 of 29464 relevant lines covered (91.59%)

34203.44 hits per line

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

92.35
/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';
3✔
60
const DEFAULT_PIPE_TIME_FORMAT = 'mediumTime';
3✔
61
const DEFAULT_PIPE_DATE_TIME_FORMAT = 'medium';
3✔
62
const DEFAULT_PIPE_DIGITS_INFO = '1.0-3';
3✔
63
const DEFAULT_CHIP_FOCUS_DELAY = 50;
3✔
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 {
3✔
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,360✔
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;
290✔
142

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

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

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

166
        return this._fields;
58,480✔
167
    }
168

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

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

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

201
        if (!this._preventInit) {
504✔
202
            this.init();
495✔
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;
997✔
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;
440✔
221
        // if value is invalid, set it back to _localeId
222
        try {
440✔
223
            getLocaleFirstDayOfWeek(this._locale);
440✔
224
        } catch {
225
            this._locale = this._localeId;
101✔
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);
151✔
236
    }
237

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

245
    /**
246
     * Gets/sets the expected return field.
247
     */
248
    @Input() public expectedReturnField: string = null;
290✔
249

250
    /**
251
     * Event fired as the expression tree is changed.
252
     */
253
    @Output()
254
    public expressionTreeChange = new EventEmitter<IExpressionTree>();
290✔
255

256
    /**
257
     * Event fired if a nested query builder tree is being edited.
258
     */
259
    @Output()
260
    public inEditModeChange = new EventEmitter<ExpressionOperandItem>();
290✔
261

262
    @ViewChild('entitySelect', { read: IgxSelectComponent })
263
    protected entitySelect: IgxSelectComponent;
264

265
    @ViewChild('editingInputs', { read: ElementRef })
266
    private editingInputs: ElementRef;
267

268
    @ViewChild('returnFieldsCombo', { read: IgxComboComponent })
269
    private returnFieldsCombo: IgxComboComponent;
270

271
    @ViewChild('returnFieldSelect', { read: IgxSelectComponent })
272
    protected returnFieldSelect: IgxSelectComponent;
273

274
    @ViewChild('fieldSelect', { read: IgxSelectComponent })
275
    private fieldSelect: IgxSelectComponent;
276

277
    @ViewChild('conditionSelect', { read: IgxSelectComponent })
278
    private conditionSelect: IgxSelectComponent;
279

280
    @ViewChild('searchValueInput', { read: ElementRef })
281
    private searchValueInput: ElementRef;
282

283
    @ViewChild('picker')
284
    private picker: IgxDatePickerComponent | IgxTimePickerComponent;
285

286
    @ViewChild('addRootAndGroupButton', { read: ElementRef })
287
    private addRootAndGroupButton: ElementRef;
288

289
    @ViewChild('addConditionButton', { read: ElementRef })
290
    private addConditionButton: ElementRef;
291

292
    @ViewChild('entityChangeDialog', { read: IgxDialogComponent })
293
    private entityChangeDialog: IgxDialogComponent;
294

295
    @ViewChild('addOptionsDropDown', { read: IgxDropDownComponent })
296
    private addExpressionItemDropDown: IgxDropDownComponent;
297

298
    @ViewChild('groupContextMenuDropDown', { read: IgxDropDownComponent })
299
    private groupContextMenuDropDown: IgxDropDownComponent;
300

301
    /**
302
     * @hidden @internal
303
     */
304
    @ViewChildren(IgxChipComponent, { read: IgxChipComponent })
305
    public expressionsChips: QueryList<IgxChipComponent>;
306

307
    @ViewChild('editingInputsContainer', { read: ElementRef })
308
    protected set editingInputsContainer(value: ElementRef) {
309
        if ((value && !this._editingInputsContainer) ||
801✔
310
            (value && this._editingInputsContainer && this._editingInputsContainer.nativeElement !== value.nativeElement)) {
311
            requestAnimationFrame(() => {
285✔
312
                this.scrollElementIntoView(value.nativeElement);
285✔
313
            });
314
        }
315

316
        this._editingInputsContainer = value;
801✔
317
    }
318

319
    /** @hidden */
320
    protected get editingInputsContainer(): ElementRef {
321
        return this._editingInputsContainer;
×
322
    }
323

324
    @ViewChild('currentGroupButtonsContainer', { read: ElementRef })
325
    protected set currentGroupButtonsContainer(value: ElementRef) {
326
        if ((value && !this._currentGroupButtonsContainer) ||
801✔
327
            (value && this._currentGroupButtonsContainer && this._currentGroupButtonsContainer.nativeElement !== value.nativeElement)) {
328
            requestAnimationFrame(() => {
364✔
329
                this.scrollElementIntoView(value.nativeElement);
364✔
330
            });
331
        }
332

333
        this._currentGroupButtonsContainer = value;
801✔
334
    }
335

336
    /** @hidden */
337
    protected get currentGroupButtonsContainer(): ElementRef {
338
        return this._currentGroupButtonsContainer;
×
339
    }
340

341
    @ViewChild('expressionsContainer')
342
    private expressionsContainer: ElementRef;
343

344
    @ViewChild('overlayOutlet', { read: IgxOverlayOutletDirective, static: true })
345
    private overlayOutlet: IgxOverlayOutletDirective;
346

347
    @ViewChildren(IgxQueryBuilderTreeComponent)
348
    private innerQueries: QueryList<IgxQueryBuilderTreeComponent>;
349

350
    /**
351
     * @hidden @internal
352
     */
353
    public innerQueryNewExpressionTree: IExpressionTree;
354

355
    /**
356
     * @hidden @internal
357
     */
358
    public rootGroup: ExpressionGroupItem;
359

360
    /**
361
     * @hidden @internal
362
     */
363
    public selectedExpressions: ExpressionOperandItem[] = [];
290✔
364

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

370
    /**
371
     * @hidden @internal
372
     */
373
    public contextualGroup: ExpressionGroupItem;
374

375
    /**
376
     * @hidden @internal
377
     */
378
    public filteringLogics;
379

380
    /**
381
     * @hidden @internal
382
     */
383
    public selectedCondition: string;
384

385
    /**
386
     * @hidden @internal
387
     */
388
    public searchValue: { value: any } = { value: null };
290✔
389

390
    /**
391
     * @hidden @internal
392
     */
393
    public pickerOutlet: IgxOverlayOutletDirective | ElementRef;
394

395
    /**
396
     * @hidden @internal
397
     */
398
    public prevFocusedExpression: ExpressionOperandItem;
399

400
    /**
401
     * @hidden @internal
402
     */
403
    public initialOperator = 0;
290✔
404

405
    /**
406
     * @hidden @internal
407
     */
408
    public returnFieldSelectOverlaySettings: OverlaySettings = {
290✔
409
        scrollStrategy: new AbsoluteScrollStrategy(),
410
        modal: false,
411
        closeOnOutsideClick: true
412
    };
413

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

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

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

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

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

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

479
    /**
480
     * Returns if the select entity dropdown at the root level is disabled after the initial selection.
481
     */
482
    public get disableEntityChange(): boolean {
483

484
        return !this.parentExpression && this.selectedEntity ? this.queryBuilder.disableEntityChange : false;
12,135✔
485
    }
486

487
    /**
488
     * Returns if the fields combo at the root level is disabled.
489
     */
490
    public get disableReturnFieldsChange(): boolean {
491

492
        return !this.selectedEntity || this.queryBuilder.disableReturnFieldsChange;
5,783✔
493
    }
494

495
    /**
496
     * Returns the current level.
497
     */
498
    public get level(): number {
499
        let parent = this.elRef.nativeElement.parentElement;
11,360✔
500
        let _level = 0;
11,360✔
501
        while (parent) {
11,360✔
502
            if (parent.localName === 'igx-query-builder-tree') {
95,523✔
503
                _level++;
6,165✔
504
            }
505
            parent = parent.parentElement;
95,523✔
506
        }
507
        return _level;
11,360✔
508
    }
509

510
    private _positionSettings = {
290✔
511
        horizontalStartPoint: HorizontalAlignment.Right,
512
        verticalStartPoint: VerticalAlignment.Top
513
    };
514

515
    private _overlaySettings: OverlaySettings = {
290✔
516
        closeOnOutsideClick: false,
517
        modal: false,
518
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings),
519
        scrollStrategy: new CloseScrollStrategy()
520
    };
521

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

529
    /** @hidden */
530
    protected isHierarchicalGridNestedQuery(): boolean {
531
        return this.queryBuilder.entities !== this.entities
14,064✔
532
    }
533

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

543
    constructor(public cdr: ChangeDetectorRef,
290✔
544
        public dragService: IgxQueryBuilderDragService,
290✔
545
        protected platform: PlatformUtil,
290✔
546
        private elRef: ElementRef,
290✔
547
        @Inject(LOCALE_ID) protected _localeId: string) {
290✔
548
        this.locale = this.locale || this._localeId;
290✔
549
        this.dragService.register(this, elRef);
290✔
550
    }
551

552
    /**
553
     * @hidden @internal
554
     */
555
    public ngAfterViewInit(): void {
556
        this._overlaySettings.outlet = this.overlayOutlet;
290✔
557
        this.entitySelectOverlaySettings.outlet = this.overlayOutlet;
290✔
558
        this.fieldSelectOverlaySettings.outlet = this.overlayOutlet;
290✔
559
        this.conditionSelectOverlaySettings.outlet = this.overlayOutlet;
290✔
560
        this.returnFieldSelectOverlaySettings.outlet = this.overlayOutlet;
290✔
561
        this.addExpressionDropDownOverlaySettings.outlet = this.overlayOutlet;
290✔
562
        this.groupContextMenuDropDownOverlaySettings.outlet = this.overlayOutlet;
290✔
563
        
564
        if (this.isAdvancedFiltering() && this.entities?.length === 1) {
290✔
565
            this.selectedEntity = this.entities[0].name;
53✔
566
        }
567

568
        // Trigger additional change detection cycle
569
        this.cdr.detectChanges();
290✔
570
    }
571

572
    /**
573
     * @hidden @internal
574
     */
575
    public ngOnDestroy(): void {
576
        this.destroy$.next(true);
290✔
577
        this.destroy$.complete();
290✔
578
    }
579

580
    /**
581
     * @hidden @internal
582
     */
583
    public set selectedEntity(value: string) {
584
        this._selectedEntity = this.entities?.find(el => el.name === value);
53✔
585
    }
586

587
    /**
588
     * @hidden @internal
589
     */
590
    public get selectedEntity(): EntityType {
591
        return this._selectedEntity;
82,534✔
592
    }
593

594
    /**
595
     * @hidden @internal
596
     */
597
    public onEntitySelectChanging(event: ISelectionEventArgs) {
598
        event.cancel = true;
42✔
599
        this._entityNewValue = event.newSelection.value;
42✔
600
        if (event.oldSelection.value && this.queryBuilder.showEntityChangeDialog) {
42✔
601
            this.entityChangeDialog.open();
6✔
602
        } else {
603
            this.onEntityChangeConfirm();
36✔
604
        }
605
    }
606

607
    /**
608
     * @hidden
609
     */
610
    public onShowEntityChangeDialogChange(eventArgs: IChangeCheckboxEventArgs) {
611
        this.queryBuilder.showEntityChangeDialog = !eventArgs.checked;
1✔
612
    }
613

614
    /**
615
     * @hidden
616
     */
617
    public onEntityChangeCancel() {
618
        this.entityChangeDialog.close();
3✔
619
        this.entitySelect.close();
3✔
620
        this._entityNewValue = null;
3✔
621
    }
622

623
    /**
624
     * @hidden
625
     */
626
    public onEntityChangeConfirm() {
627
        if (this._parentExpression) {
38✔
628
            this._expressionTree = this.createExpressionTreeFromGroupItem(this.createExpressionGroupItem(this._expressionTree));
5✔
629
        }
630

631
        this._selectedEntity = this._entityNewValue;
38✔
632
        if (!this._selectedEntity.fields) {
38!
633
            this._selectedEntity.fields = [];
×
634
        }
635
        this.fields = this._entityNewValue ? this._entityNewValue.fields : [];
38!
636

637
        if (this._selectedEntity.fields.find(f => f.field === this.expectedReturnField)) {
145✔
638
            this._selectedReturnFields = [this.expectedReturnField];
5✔
639
        } else {
640
            this._selectedReturnFields = this.parentExpression ? [] : this._entityNewValue.fields?.map(f => f.field);
132!
641
        }
642

643
        if (this._expressionTree) {
38✔
644
            this._expressionTree.entity = this._entityNewValue.name;
5✔
645

646
            this._expressionTree.returnFields = this.fields.length === this._selectedReturnFields.length ? ['*'] : this._selectedReturnFields;
5✔
647

648
            this._expressionTree.filteringOperands = [];
5✔
649

650
            this._editedExpression = null;
5✔
651
            if (!this.parentExpression) {
5✔
652
                this.expressionTreeChange.emit(this._expressionTree);
4✔
653
            }
654

655
            this.rootGroup = null;
5✔
656
            this.currentGroup = this.rootGroup;
5✔
657
        }
658

659
        this._selectedField = null;
38✔
660
        this.selectedCondition = null;
38✔
661
        this.searchValue.value = null;
38✔
662

663
        this.entityChangeDialog.close();
38✔
664
        this.entitySelect.close();
38✔
665

666
        this._entityNewValue = null;
38✔
667
        this.innerQueryNewExpressionTree = null;
38✔
668

669
        this.initExpressionTree(this._selectedEntity.name, this.selectedReturnFields);
38✔
670
    }
671

672
    /**
673
     * @hidden @internal
674
     */
675
    public set selectedReturnFields(value: string[]) {
676
        if (this._selectedReturnFields !== value) {
5✔
677
            this._selectedReturnFields = value;
5✔
678

679
            if (this._expressionTree && !this.parentExpression) {
5✔
680
                this._expressionTree.returnFields = value.length === this.fields.length ? ['*'] : value;
5✔
681
                this.expressionTreeChange.emit(this._expressionTree);
5✔
682
            }
683
        }
684
    }
685

686
    /**
687
     * @hidden @internal
688
     */
689
    public get selectedReturnFields(): string[] {
690
        if (typeof this._selectedReturnFields == 'string') {
49,176!
691
            return [this._selectedReturnFields];
×
692
        }
693
        return this._selectedReturnFields;
49,176✔
694
    }
695

696
    /**
697
     * @hidden @internal
698
     */
699
    public set selectedField(value: FieldType) {
700
        const oldValue = this._selectedField;
201✔
701

702
        if (this._selectedField !== value) {
201✔
703
            this._selectedField = value;
168✔
704
            if (this._selectedField && !this._selectedField.dataType) {
168!
NEW
705
                this._selectedField.filters = this.getFilters(this._selectedField);
×
706
            }
707

708
            this.selectDefaultCondition();
168✔
709
            if (oldValue && this._selectedField && this._selectedField.dataType !== oldValue.dataType) {
168✔
710
                this.searchValue.value = null;
17✔
711
                this.cdr.detectChanges();
17✔
712
            }
713
        }
714
    }
715

716
    /**
717
     * @hidden @internal
718
     */
719
    public get selectedField(): FieldType {
720
        return this._selectedField;
56,840✔
721
    }
722

723
    /**
724
     * @hidden @internal
725
     *
726
     * used by the grid
727
     */
728
    public setPickerOutlet(outlet?: IgxOverlayOutletDirective | ElementRef) {
729
        this.pickerOutlet = outlet;
49✔
730
    }
731

732
    /**
733
     * @hidden @internal
734
     *
735
     * used by the grid
736
     */
737
    public get isContextMenuVisible(): boolean {
738
        return !this.groupContextMenuDropDown.collapsed;
×
739
    }
740

741
    /**
742
     * @hidden @internal
743
     */
744
    public get hasEditedExpression(): boolean {
745
        return this._editedExpression !== undefined && this._editedExpression !== null;
31,175✔
746
    }
747

748
    /**
749
     * @hidden @internal
750
     */
751
    public addCondition(parent: ExpressionGroupItem, afterExpression?: ExpressionOperandItem, isUIInteraction?: boolean) {
752
        this.cancelOperandAdd();
77✔
753

754
        const operandItem = new ExpressionOperandItem({
77✔
755
            fieldName: null,
756
            condition: null,
757
            conditionName: null,
758
            ignoreCase: true,
759
            searchVal: null
760
        }, parent);
761

762
        const groupItem = new ExpressionGroupItem(this.getOperator(null) ?? FilteringLogic.And, parent);
77!
763
        this.contextualGroup = groupItem;
77✔
764
        this.initialOperator = null;
77✔
765

766
        this._lastFocusedChipIndex = this._lastFocusedChipIndex === undefined ? -1 : this._lastFocusedChipIndex;
77✔
767

768
        if (parent) {
77✔
769
            if (afterExpression) {
11✔
770
                const index = parent.children.indexOf(afterExpression);
1✔
771
                parent.children.splice(index + 1, 0, operandItem);
1✔
772
            } else {
773
                parent.children.push(operandItem);
10✔
774
            }
775
            this._lastFocusedChipIndex++;
11✔
776
        } else {
777
            this.rootGroup = groupItem;
66✔
778
            operandItem.parent = groupItem;
66✔
779
            this.rootGroup.children.push(operandItem);
66✔
780
            this._lastFocusedChipIndex = 0;
66✔
781
        }
782

783
        this._focusDelay = 250;
77✔
784

785
        if (isUIInteraction && !afterExpression) {
77✔
786
            this._lastFocusedChipIndex = this.expressionsChips.length;
73✔
787
            this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
73✔
788
        }
789

790
        this.enterExpressionEdit(operandItem);
77✔
791
    }
792

793
    /**
794
     * @hidden @internal
795
     */
796
    public addReverseGroup(parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
797
        parent = parent ?? this.rootGroup;
3!
798

799
        if (parent.operator === FilteringLogic.And) {
3✔
800
            this.addGroup(FilteringLogic.Or, parent, afterExpression);
2✔
801
        } else {
802
            this.addGroup(FilteringLogic.And, parent, afterExpression);
1✔
803
        }
804
    }
805

806
    /**
807
     * @hidden @internal
808
     */
809
    public endGroup(groupItem: ExpressionGroupItem) {
810
        this.currentGroup = groupItem.parent;
×
811
    }
812

813
    /**
814
     * @hidden @internal
815
     */
816
    public commitExpression() {
817
        this.commitOperandEdit();
46✔
818
        this.focusEditedExpressionChip();
46✔
819
    }
820

821
    /**
822
     * @hidden @internal
823
     */
824
    public discardExpression(expressionItem?: ExpressionOperandItem) {
825
        this.cancelOperandEdit();
11✔
826
        if (expressionItem && expressionItem.expression.fieldName) {
11✔
827
            this.focusEditedExpressionChip();
4✔
828
        }
829
    }
830

831
    /**
832
     * @hidden @internal
833
     */
834
    public commitOperandEdit() {
835
        const actualSearchValue = this.searchValue.value;
61✔
836
        if (this._editedExpression) {
61✔
837
            this._editedExpression.expression.fieldName = this.selectedField.field;
58✔
838
            this._editedExpression.expression.condition = this.selectedField.filters.condition(this.selectedCondition);
58✔
839
            this._editedExpression.expression.conditionName = this.selectedCondition;
58✔
840
            this._editedExpression.expression.searchVal = DataUtil.parseValue(this.selectedField.dataType, actualSearchValue) || actualSearchValue;
58✔
841
            this._editedExpression.fieldLabel = this.selectedField.label
58!
842
                ? this.selectedField.label
843
                : this.selectedField.header
58!
844
                    ? this.selectedField.header
845
                    : this.selectedField.field;
846

847
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0]
58✔
848
            if (innerQuery && this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) {
58✔
849
                innerQuery.exitEditAddMode();
8✔
850
                this._editedExpression.expression.searchTree = this.getExpressionTreeCopy(innerQuery.expressionTree);
8✔
851
                const returnFields = innerQuery.selectedReturnFields.length > 0 ?
8!
852
                                        innerQuery.selectedReturnFields :
853
                                        [innerQuery.fields[0].field];
854
                this._editedExpression.expression.searchTree.returnFields = returnFields;
8✔
855
            } else {
856
                this._editedExpression.expression.searchTree = null;
50✔
857
            }
858
            this.innerQueryNewExpressionTree = null;
58✔
859

860
            if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary || this.selectedField.filters.condition(this.selectedCondition)?.isNestedQuery) {
58✔
861
                this._editedExpression.expression.searchVal = null;
13✔
862
            }
863

864
            this._editedExpression.inEditMode = false;
58✔
865
            this._editedExpression = null;
58✔
866
        }
867

868
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, this.selectedEntity?.name, this.selectedReturnFields);
61✔
869
        if (!this.parentExpression) {
61✔
870
            this.expressionTreeChange.emit(this._expressionTree);
51✔
871
        }
872
    }
873

874
    /**
875
     * @hidden @internal
876
     */
877
    public cancelOperandAdd() {
878
        if (this._addModeExpression) {
591!
879
            this._addModeExpression.inAddMode = false;
×
880
            this._addModeExpression = null;
×
881
        }
882
    }
883

884
    /**
885
     * @hidden @internal
886
     */
887
    public deleteItem = (expressionItem: ExpressionItem, skipEmit: boolean = false) => {
290✔
888
        if (!expressionItem.parent) {
50✔
889
            this.rootGroup = null;
16✔
890
            this.currentGroup = null;
16✔
891
            //this._expressionTree = null;
892
            return;
16✔
893
        }
894

895
        if (expressionItem === this.currentGroup) {
34!
896
            this.currentGroup = this.currentGroup.parent;
×
897
        }
898

899
        const children = expressionItem.parent.children;
34✔
900
        const index = children.indexOf(expressionItem);
34✔
901
        children.splice(index, 1);
34✔
902
        const entity = this.expressionTree ? this.expressionTree.entity : null;
34✔
903
        const returnFields = this.expressionTree ? this.expressionTree.returnFields : null;
34✔
904
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, entity, returnFields); // TODO: don't recreate if not necessary
34✔
905

906
        if (!children.length) {
34✔
907
            this.deleteItem(expressionItem.parent, true);
17✔
908
        }
909

910
        if (!this.parentExpression && !skipEmit) {
34✔
911
            this.expressionTreeChange.emit(this._expressionTree);
31✔
912
        }
913
    }
914

915
    /**
916
     * @hidden @internal
917
     */
918
    public cancelOperandEdit() {
919
        if (this.innerQueries) {
523✔
920
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
233✔
921
            if (innerQuery) {
233✔
922
                if (innerQuery._editedExpression) {
9✔
923
                    innerQuery.cancelOperandEdit();
1✔
924
                }
925

926
                innerQuery.expressionTree = this.getExpressionTreeCopy(this._editedExpression.expression.searchTree);
9✔
927
                this.innerQueryNewExpressionTree = null;
9✔
928
            }
929
        }
930

931
        if (this._editedExpression) {
523✔
932
            this._editedExpression.inEditMode = false;
31✔
933

934
            if (!this._editedExpression.expression.fieldName) {
31✔
935
                this.deleteItem(this._editedExpression);
17✔
936
            }
937

938
            this._editedExpression = null;
31✔
939
        }
940

941
        if (!this.expressionTree && this.contextualGroup) {
523✔
942
            this.initialOperator = this.contextualGroup.operator;
1✔
943
        }
944
    }
945

946
    /**
947
     * @hidden @internal
948
     */
949
    public operandCanBeCommitted(): boolean {
950
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
1,732✔
951
        return this.selectedField && this.selectedCondition &&
1,732✔
952
            (
953
                (
954
                    ((!Array.isArray(this.searchValue.value) && !!this.searchValue.value) || (Array.isArray(this.searchValue.value) && this.searchValue.value.length !== 0)) &&
955
                    !(this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery)
956
                ) ||
957
                (
958
                    this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery && innerQuery && !!innerQuery.expressionTree && innerQuery.selectedReturnFields?.length > 0
959
                ) ||
960
                this.selectedField.filters.condition(this.selectedCondition)?.isUnary
961
            );
962
    }
963
    
964
    /**
965
     * @hidden @internal
966
     */
967
    public canCommitCurrentState(): boolean {
968
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
36✔
969
        if (innerQuery) {
36✔
970
            return this.selectedReturnFields?.length > 0 && innerQuery.canCommitCurrentState();
8✔
971
        } else {
972
            return this.selectedReturnFields?.length > 0 &&
28✔
973
                (
974
                    (!this._editedExpression) || // no edited expr
975
                    (this._editedExpression && !this.selectedField) || // empty edited expr
976
                    (this._editedExpression && this.operandCanBeCommitted() === true) // valid edited expr
977
                );
978
        }
979
    }
980

981
    /**
982
     * @hidden @internal
983
     */
984
    public commitCurrentState(): void {
985
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
3✔
986
        if (innerQuery) {
3✔
987
            innerQuery.commitCurrentState();
1✔
988
        }
989

990
        if (this._editedExpression) {
3✔
991
            if (this.selectedField) {
3!
992
                this.commitOperandEdit();
3✔
993
            } else {
994
                this.deleteItem(this._editedExpression);
×
995
                this._editedExpression = null;
×
996
            }
997
        }
998
    }
999

1000
    /**
1001
     * @hidden @internal
1002
     */
1003
    public exitEditAddMode(shouldPreventInit = false) {
47✔
1004
        if (!this._editedExpression) {
234✔
1005
            return;
218✔
1006
        }
1007

1008
        this.exitOperandEdit();
16✔
1009
        this.cancelOperandAdd();
16✔
1010

1011
        if (shouldPreventInit) {
16✔
1012
            this._preventInit = true;
7✔
1013
        }
1014
    }
1015

1016
    /**
1017
     * @hidden @internal
1018
     *
1019
     * used by the grid
1020
     */
1021
    public exitOperandEdit() {
1022
        if (!this._editedExpression) {
36✔
1023
            return;
16✔
1024
        }
1025

1026
        if (this.operandCanBeCommitted()) {
20✔
1027
            this.commitOperandEdit();
9✔
1028
        } else {
1029
            this.cancelOperandEdit();
11✔
1030
        }
1031
    }
1032

1033
    /**
1034
     * @hidden @internal
1035
     */
1036
    public isExpressionGroup(expression: ExpressionItem): boolean {
1037
        return expression instanceof ExpressionGroupItem;
34,513✔
1038
    }
1039

1040
    /**
1041
     * @hidden @internal
1042
     */
1043
    public onExpressionFocus(expressionItem: ExpressionOperandItem) {
1044
        if (this.prevFocusedExpression) {
41✔
1045
            this.prevFocusedExpression.focused = false;
4✔
1046
        }
1047
        expressionItem.focused = true;
41✔
1048
        this.prevFocusedExpression = expressionItem;
41✔
1049
    }
1050

1051
    /**
1052
     * @hidden @internal
1053
     */
1054
    public onExpressionBlur(event, expressionItem: ExpressionOperandItem) {
1055
        if (this._prevFocusedContainer && this._prevFocusedContainer !== event.target.closest('.igx-filter-tree__expression-item')) {
2!
1056
            expressionItem.focused = false;
×
1057
        }
1058
        this._prevFocusedContainer = event.target.closest('.igx-filter-tree__expression-item');
2✔
1059
    }
1060

1061
    /**
1062
     * @hidden @internal
1063
     */
1064
    public onChipRemove(expressionItem: ExpressionItem) {
1065
        this.exitEditAddMode();
5✔
1066
        this.deleteItem(expressionItem);
5✔
1067
    }
1068

1069
    /**
1070
     * @hidden @internal
1071
     */
1072
    public focusChipAfterDrag = (index: number) => {
290✔
1073
        this._lastFocusedChipIndex = index;
11✔
1074
        this.focusEditedExpressionChip();
11✔
1075
    }
1076
    /**
1077
     * @hidden @internal
1078
     */
1079
    public addExpressionBlur() {
1080
        if (this.prevFocusedExpression) {
×
1081
            this.prevFocusedExpression.focused = false;
×
1082
        }
1083
        if (this.addExpressionItemDropDown && !this.addExpressionItemDropDown.collapsed) {
×
1084
            this.addExpressionItemDropDown.close();
×
1085
        }
1086
    }
1087

1088
    /**
1089
     * @hidden @internal
1090
     */
1091
    public onChipClick(expressionItem: ExpressionOperandItem, chip: IgxChipComponent) {
1092
        this.enterExpressionEdit(expressionItem, chip);
55✔
1093
    }
1094

1095
    /**
1096
     * @hidden @internal
1097
     */
1098
    public enterExpressionEdit(expressionItem: ExpressionOperandItem, chip?: IgxChipComponent) {
1099
        this.exitEditAddMode(true);
132✔
1100
        this.cdr.detectChanges();
132✔
1101
        this._lastFocusedChipIndex = chip ? this.expressionsChips.toArray().findIndex(expr => expr === chip) : this._lastFocusedChipIndex;
132✔
1102
        this.enterEditMode(expressionItem);
132✔
1103
    }
1104

1105

1106
    /**
1107
     * @hidden @internal
1108
     */
1109
    public clickExpressionAdd(targetButton: HTMLElement, chip: IgxChipComponent) {
1110
        this.exitEditAddMode(true);
4✔
1111
        this.cdr.detectChanges();
4✔
1112
        this._lastFocusedChipIndex = this.expressionsChips.toArray().findIndex(expr => expr === chip);
22✔
1113
        this.openExpressionAddDialog(targetButton);
4✔
1114
    }
1115

1116
    /**
1117
     * @hidden @internal
1118
     */
1119
    public openExpressionAddDialog(targetButton: HTMLElement) {
1120
        this.addExpressionDropDownOverlaySettings.target = targetButton;
4✔
1121
        this.addExpressionDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
4✔
1122
            horizontalDirection: HorizontalAlignment.Right,
1123
            horizontalStartPoint: HorizontalAlignment.Left,
1124
            verticalStartPoint: VerticalAlignment.Bottom
1125
        });
1126

1127
        this.addExpressionItemDropDown.open(this.addExpressionDropDownOverlaySettings);
4✔
1128
    }
1129

1130
    /**
1131
     * @hidden @internal
1132
     */
1133
    public enterExpressionAdd(event: ISelectionEventArgs, expressionItem: ExpressionOperandItem) {
1134
        if (this._addModeExpression) {
1!
1135
            this._addModeExpression.inAddMode = false;
×
1136
        }
1137

1138
        if (this.parentExpression) {
1!
1139
            this.inEditModeChange.emit(this.parentExpression);
×
1140
        }
1141

1142
        const parent = expressionItem.parent ?? this.rootGroup;
1!
1143
        requestAnimationFrame(() => {
1✔
1144
            if (event.newSelection.value === 'addCondition') {
1!
1145
                this.addCondition(parent, expressionItem);
1✔
1146
            } else if (event.newSelection.value === 'addGroup') {
×
1147
                this.addReverseGroup(parent, expressionItem);
×
1148
            }
1149
            expressionItem.inAddMode = true;
1✔
1150
            this._addModeExpression = expressionItem;
1✔
1151
        })
1152
    }
1153

1154
    /**
1155
     * @hidden @internal
1156
     */
1157
    public enterEditMode(expressionItem: ExpressionOperandItem) {
1158
        if (this._editedExpression) {
132!
1159
            this._editedExpression.inEditMode = false;
×
1160
        }
1161

1162
        if (this.parentExpression) {
132✔
1163
            this.inEditModeChange.emit(this.parentExpression);
17✔
1164
        }
1165

1166
        expressionItem.hovered = false;
132✔
1167
        this.fields = this.selectedEntity ? this.selectedEntity.fields : null;
132✔
1168
        this.selectedField =
132✔
1169
            expressionItem.expression.fieldName ?
132✔
1170
                this.fields?.find(field => field.field === expressionItem.expression.fieldName)
89✔
1171
                : null;
1172
        this.selectedCondition =
132✔
1173
            expressionItem.expression.condition ?
132✔
1174
                expressionItem.expression.condition.name :
1175
                null;
1176
        this.searchValue.value = expressionItem.expression.searchVal instanceof Set ?
132✔
1177
            Array.from(expressionItem.expression.searchVal) :
1178
            expressionItem.expression.searchVal;
1179

1180
        expressionItem.inEditMode = true;
132✔
1181
        this._editedExpression = expressionItem;
132✔
1182
        this.cdr.detectChanges();
132✔
1183

1184
        this.entitySelectOverlaySettings.target = this.entitySelect.element;
132✔
1185
        this.entitySelectOverlaySettings.excludeFromOutsideClick = [this.entitySelect.element as HTMLElement];
132✔
1186
        this.entitySelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
132✔
1187

1188
        if (this.returnFieldSelect) {
132✔
1189
            this.returnFieldSelectOverlaySettings.target = this.returnFieldSelect.element;
17✔
1190
            this.returnFieldSelectOverlaySettings.excludeFromOutsideClick = [this.returnFieldSelect.element as HTMLElement];
17✔
1191
            this.returnFieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
17✔
1192
        }
1193
        if (this.fieldSelect) {
132✔
1194
            this.fieldSelectOverlaySettings.target = this.fieldSelect.element;
129✔
1195
            this.fieldSelectOverlaySettings.excludeFromOutsideClick = [this.fieldSelect.element as HTMLElement];
129✔
1196
            this.fieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
129✔
1197
        }
1198
        if (this.conditionSelect) {
132✔
1199
            this.conditionSelectOverlaySettings.target = this.conditionSelect.element;
129✔
1200
            this.conditionSelectOverlaySettings.excludeFromOutsideClick = [this.conditionSelect.element as HTMLElement];
129✔
1201
            this.conditionSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
129✔
1202
        }
1203

1204
        if (!this.selectedField) {
132✔
1205
            this.fieldSelect.input.nativeElement.focus();
77✔
1206
        } else if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary) {
55✔
1207
            this.conditionSelect?.input.nativeElement.focus();
5✔
1208
        } else {
1209
            const input = this.searchValueInput?.nativeElement || this.picker?.getEditElement();
50✔
1210
            input?.focus();
50✔
1211
        }
1212

1213
        (this.editingInputs?.nativeElement.parentElement as HTMLElement)?.scrollIntoView({ block: "nearest", inline: "nearest" });
132✔
1214
    }
1215

1216
    /**
1217
     * @hidden @internal
1218
     */
1219
    public onConditionSelectChanging(event: ISelectionEventArgs) {
1220
        event.cancel = true;
66✔
1221
        this.selectedCondition = event.newSelection.value;
66✔
1222
        this.conditionSelect.close();
66✔
1223
        this.cdr.detectChanges();
66✔
1224
    }
1225

1226
    /**
1227
     * @hidden @internal
1228
     */
1229
    public onKeyDown(eventArgs: KeyboardEvent) {
1230
        eventArgs.stopPropagation();
×
1231
    }
1232

1233
    /**
1234
     * @hidden @internal
1235
     */
1236
    public onGroupClick(groupContextMenuDropDown: any, targetButton: HTMLButtonElement, groupItem: ExpressionGroupItem) {
1237
        this.exitEditAddMode();
7✔
1238
        this.cdr.detectChanges();
7✔
1239

1240
        this.groupContextMenuDropDown = groupContextMenuDropDown;
7✔
1241
        this.groupContextMenuDropDownOverlaySettings.target = targetButton;
7✔
1242
        this.groupContextMenuDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
7✔
1243
            horizontalDirection: HorizontalAlignment.Right,
1244
            horizontalStartPoint: HorizontalAlignment.Left,
1245
            verticalStartPoint: VerticalAlignment.Bottom
1246
        });
1247

1248
        if (groupContextMenuDropDown.collapsed) {
7!
1249
            this.contextualGroup = groupItem;
7✔
1250
            groupContextMenuDropDown.open(this.groupContextMenuDropDownOverlaySettings);
7✔
1251
        } else {
1252
            groupContextMenuDropDown.close();
×
1253
        }
1254
    }
1255

1256
    /**
1257
     * @hidden @internal
1258
     */
1259
    public getOperator(expressionItem: any) {
1260
        // if (!expressionItem && !this.expressionTree && !this.initialOperator) {
1261
        //     this.initialOperator = 0;
1262
        // }
1263

1264
        const operator = expressionItem ?
89,489✔
1265
            expressionItem.operator :
1266
            this.expressionTree ?
2,951✔
1267
                this.expressionTree.operator :
1268
                this.initialOperator;
1269
        return operator;
89,489✔
1270
    }
1271

1272
    /**
1273
     * @hidden @internal
1274
     */
1275
    public getSwitchGroupText(expressionItem: any) {
1276
        const operator = this.getOperator(expressionItem);
14,902✔
1277
        const condition = operator === FilteringLogic.Or ? this.resourceStrings.igx_query_builder_and_label : this.resourceStrings.igx_query_builder_or_label
14,902✔
1278
        return this.resourceStrings.igx_query_builder_switch_group.replace('{0}', condition.toUpperCase());
14,902✔
1279
    }
1280

1281
    /**
1282
     * @hidden @internal
1283
     */
1284
    public onGroupContextMenuDropDownSelectionChanging(event: ISelectionEventArgs) {
1285
        event.cancel = true;
4✔
1286

1287
        if (event.newSelection.value === 'switchCondition') {
4✔
1288
            const newOperator = (!this.expressionTree ? this.initialOperator : (this.contextualGroup ?? this._expressionTree).operator) === 0 ? 1 : 0;
3!
1289
            this.selectFilteringLogic(newOperator);
3✔
1290
        } else if (event.newSelection.value === 'ungroup') {
1✔
1291
            this.ungroup();
1✔
1292
        }
1293

1294
        this.groupContextMenuDropDown.close();
4✔
1295
    }
1296

1297
    /**
1298
     * @hidden @internal
1299
     */
1300
    public ungroup() {
1301
        const selectedGroup = this.contextualGroup;
1✔
1302
        const parent = selectedGroup.parent;
1✔
1303
        if (parent) {
1✔
1304
            const index = parent.children.indexOf(selectedGroup);
1✔
1305
            parent.children.splice(index, 1, ...selectedGroup.children);
1✔
1306

1307
            for (const expr of selectedGroup.children) {
1✔
1308
                expr.parent = parent;
2✔
1309
            }
1310
        }
1311
        this.commitOperandEdit();
1✔
1312
    }
1313

1314
    /**
1315
     * @hidden @internal
1316
     */
1317
    public selectFilteringLogic(index: number) {
1318
        if (!this.expressionTree) {
3!
1319
            this.initialOperator = index;
×
1320
            return;
×
1321
        }
1322

1323
        if (this.contextualGroup) {
3✔
1324
            this.contextualGroup.operator = index as FilteringLogic;
2✔
1325
            this.commitOperandEdit();
2✔
1326
        } else if (this.expressionTree) {
1✔
1327
            this._expressionTree.operator = index as FilteringLogic;
1✔
1328
        }
1329

1330
        this.initialOperator = null;
3✔
1331
    }
1332

1333
    /**
1334
     * @hidden @internal
1335
     */
1336
    public getConditionFriendlyName(name: string): string {
1337
        // 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.
1338
        // So instead of in/notIn we end up with 'inQuery'/'notInQuery', hence removing the suffix from the friendly name.
1339
        return this.resourceStrings[`igx_query_builder_filter_${name?.replace('Query', '')}`] || name;
60,485✔
1340
    }
1341

1342
    /**
1343
     * @hidden @internal
1344
     */
1345
    public isDate(value: any) {
1346
        return value instanceof Date;
20,055✔
1347
    }
1348

1349
    /**
1350
     * @hidden @internal
1351
     */
1352
    public invokeClick(eventArgs: KeyboardEvent) {
1353
        if (!this.dragService.dropGhostExpression && this.platform.isActivationKey(eventArgs)) {
2✔
1354
            eventArgs.preventDefault();
2✔
1355
            (eventArgs.currentTarget as HTMLElement).click();
2✔
1356
        }
1357
    }
1358

1359
    /**
1360
     * @hidden @internal
1361
     */
1362
    public openPicker(args: KeyboardEvent) {
1363
        if (this.platform.isActivationKey(args)) {
×
1364
            args.preventDefault();
×
1365
            this.picker.open();
×
1366
        }
1367
    }
1368

1369
    /**
1370
     * @hidden @internal
1371
     */
1372
    public onOutletPointerDown(event) {
1373
        // This prevents closing the select's dropdown when clicking the scroll
1374
        event.preventDefault();
61✔
1375
    }
1376

1377
    /**
1378
     * @hidden @internal
1379
     */
1380
    public getConditionList(): string[] {
1381
        if (!this.selectedField) return [];
1,685✔
1382

1383
        if (!this.selectedField.filters) {
1,239!
NEW
1384
            this.selectedField.filters = this.getFilters(this.selectedField);
×
1385
        }
1386

1387
        if ((this.isAdvancedFiltering() && !this.entities[0].childEntities) ||
1,239✔
1388
            (this.isHierarchicalGridNestedQuery() && this.selectedEntity.name && !this.selectedEntity.childEntities)) {
1389
            return this.selectedField.filters.conditionList();
218✔
1390
        }
1391

1392
        return this.selectedField.filters.extendedConditionList();
1,021✔
1393
    }
1394

1395
    /**
1396
     * @hidden @internal
1397
     */
1398
    public getFormatter(field: string) {
1399
        return this.fields?.find(el => el.field === field)?.formatter;
72,314✔
1400
    }
1401

1402
    /**
1403
     * @hidden @internal
1404
     */
1405
    public getFormat(field: string) {
1406
        return this.fields?.find(el => el.field === field).pipeArgs.format;
1,845✔
1407
    }
1408

1409
    /**
1410
     * @hidden @internal
1411
     *
1412
     * used by the grid
1413
     */
1414
    public setAddButtonFocus() {
1415
        if (this.addRootAndGroupButton) {
44!
1416
            this.addRootAndGroupButton.nativeElement.focus();
×
1417
        } else if (this.addConditionButton) {
44✔
1418
            this.addConditionButton.nativeElement.focus();
18✔
1419
        }
1420
    }
1421

1422
    /**
1423
     * @hidden @internal
1424
     */
1425
    public context(expression: ExpressionItem, afterExpression?: ExpressionItem) {
1426
        return {
56,175✔
1427
            $implicit: expression,
1428
            afterExpression
1429
        };
1430
    }
1431

1432
    public formatReturnFields(innerTree: IFilteringExpressionsTree) {
1433
        const returnFields = innerTree.returnFields;
3,642✔
1434
        let text = returnFields.join(', ');
3,642✔
1435
        const innerTreeEntity = this.entities?.find(el => el.name === innerTree.entity);
3,642✔
1436
        if (returnFields.length === innerTreeEntity?.fields.length) {
3,642!
1437
            text = this.resourceStrings.igx_query_builder_all_fields;
×
1438
        } else {
1439
            text = returnFields.join(', ');
3,642✔
1440
            text = text.length > 25 ? text.substring(0, 25) + ' ...' : text;
3,642!
1441
        }
1442
        return text;
3,642✔
1443
    }
1444

1445
    public isInEditMode(): boolean {
1446
        return !this.parentExpression || (this.parentExpression && this.parentExpression.inEditMode);
64,708✔
1447
    }
1448

1449
    public onInEditModeChanged(expressionItem: ExpressionOperandItem) {
1450
        if (!expressionItem.inEditMode) {
17!
1451
            this.enterExpressionEdit(expressionItem);
×
1452
        }
1453
    }
1454

1455
    public getExpressionTreeCopy(expressionTree: IExpressionTree, shouldAssignInnerQueryExprTree?: boolean): IExpressionTree {
1456
        if (!expressionTree) {
309✔
1457
            return null;
278✔
1458
        }
1459

1460
        const exprTreeCopy = new FilteringExpressionsTree(expressionTree.operator, expressionTree.fieldName, expressionTree.entity, expressionTree.returnFields);
31✔
1461
        exprTreeCopy.filteringOperands = [];
31✔
1462

1463
        expressionTree.filteringOperands.forEach(o => isTree(o) ? exprTreeCopy.filteringOperands.push(this.getExpressionTreeCopy(o)) : exprTreeCopy.filteringOperands.push(o));
56!
1464

1465
        if (!this.innerQueryNewExpressionTree && shouldAssignInnerQueryExprTree) {
31✔
1466
            this.innerQueryNewExpressionTree = exprTreeCopy;
18✔
1467
        }
1468

1469
        return exprTreeCopy;
31✔
1470
    }
1471

1472
    public onSelectAllClicked() {
1473
        if (
2✔
1474
            (this._selectedReturnFields.length > 0 && this._selectedReturnFields.length < this._selectedEntity.fields.length) ||
5✔
1475
            this._selectedReturnFields.length == this._selectedEntity.fields.length
1476
        ) {
1477
            this.returnFieldsCombo.deselectAllItems();
1✔
1478
        } else {
1479
            this.returnFieldsCombo.selectAllItems();
1✔
1480
        }
1481
    }
1482

1483
    public onReturnFieldSelectChanging(event: IComboSelectionChangingEventArgs | ISelectionEventArgs) {
1484
        let newSelection = [];
12✔
1485
        if (Array.isArray(event.newSelection)) {
12✔
1486
            newSelection = event.newSelection.map(item => item.field)
11✔
1487
        } else {
1488
            newSelection.push(event.newSelection.value);
7✔
1489
            this._selectedReturnFields = newSelection;
7✔
1490
        }
1491

1492
        this.initExpressionTree(this.selectedEntity.name, newSelection);
12✔
1493
    }
1494

1495
    public initExpressionTree(selectedEntityName: string, selectedReturnFields: string[]) {
1496
        if (!this._expressionTree) {
50✔
1497
            this._expressionTree = this.createExpressionTreeFromGroupItem(new ExpressionGroupItem(FilteringLogic.And, this.rootGroup), selectedEntityName, selectedReturnFields);
33✔
1498
        }
1499

1500
        if (!this.parentExpression) {
50✔
1501
            this.expressionTreeChange.emit(this._expressionTree);
38✔
1502
        }
1503
    }
1504

1505
    public getSearchValueTemplateContext(defaultSearchValueTemplate): any {
1506
        const ctx = {
1,685✔
1507
            $implicit: this.searchValue,
1508
            selectedField: this.selectedField,
1509
            selectedCondition: this.selectedCondition,
1510
            defaultSearchValueTemplate: defaultSearchValueTemplate
1511
        };
1512
        return ctx;
1,685✔
1513
    }
1514

1515
    private getPipeArgs(field: FieldType) {
1516
        let pipeArgs = {...field.pipeArgs};
3,271✔
1517
        if (!pipeArgs) {
3,271!
1518
            pipeArgs = { digitsInfo: DEFAULT_PIPE_DIGITS_INFO };
×
1519
        }
1520

1521
        if (!pipeArgs.format) {
3,271✔
1522
            pipeArgs.format = field.dataType === DataType.Time ?
3,051!
1523
                DEFAULT_PIPE_TIME_FORMAT : field.dataType === DataType.DateTime ?
3,051!
1524
                    DEFAULT_PIPE_DATE_TIME_FORMAT : DEFAULT_PIPE_DATE_FORMAT;
1525
        }
1526
        
1527
        return pipeArgs;
3,271✔
1528
    }
1529

1530
    private selectDefaultCondition() {
1531
        if (this.selectedField && this.selectedField.filters) {
168✔
1532
            this.selectedCondition = this.selectedField.filters.conditionList().indexOf('equals') >= 0 ? 'equals' : this.selectedField.filters.conditionList()[0];
124✔
1533
        }
1534
    }
1535

1536
    private getFilters(field: FieldType) {
1537
        if (!field.filters) {
3,271✔
1538
            switch (field.dataType) {
3,051!
1539
                case DataType.Boolean:
1540
                    return IgxBooleanFilteringOperand.instance();
759✔
1541
                case DataType.Number:
1542
                case DataType.Currency:
1543
                case DataType.Percent:
1544
                    return IgxNumberFilteringOperand.instance();
1,135✔
1545
                case DataType.Date:
1546
                    return IgxDateFilteringOperand.instance();
383✔
1547
                case DataType.Time:
1548
                    return IgxTimeFilteringOperand.instance();
×
1549
                case DataType.DateTime:
1550
                    return IgxDateTimeFilteringOperand.instance();
×
1551
                case DataType.String:
1552
                default:
1553
                    return IgxStringFilteringOperand.instance();
774✔
1554
            }
1555
        } else {
1556
            return field.filters;
220✔
1557
        }
1558
    }
1559

1560

1561
    private addGroup(operator: FilteringLogic, parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
1562
        this.cancelOperandAdd();
3✔
1563

1564
        const groupItem = new ExpressionGroupItem(operator, parent);
3✔
1565

1566
        if (parent) {
3!
1567
            if (afterExpression) {
3!
1568
                const index = parent.children.indexOf(afterExpression);
×
1569
                parent.children.splice(index + 1, 0, groupItem);
×
1570
            } else {
1571
                parent.children.push(groupItem);
3✔
1572
            }
1573
        } else {
1574
            this.rootGroup = groupItem;
×
1575
        }
1576

1577
        this.addCondition(groupItem);
3✔
1578
        this.currentGroup = groupItem;
3✔
1579
    }
1580

1581
    private createExpressionGroupItem(expressionTree: IExpressionTree, parent?: ExpressionGroupItem, entityName?: string): ExpressionGroupItem {
1582
        let groupItem: ExpressionGroupItem;
1583
        if (expressionTree) {
847✔
1584
            groupItem = new ExpressionGroupItem(expressionTree.operator, parent);
684✔
1585
            if (!expressionTree.filteringOperands) {
684!
1586
                return groupItem;
×
1587
            }
1588

1589
            for (let i = 0; i < expressionTree.filteringOperands.length; i++) {
684✔
1590
                const expr = expressionTree.filteringOperands[i];
1,493✔
1591

1592
                if (isTree(expr)) {
1,493✔
1593
                    groupItem.children.push(this.createExpressionGroupItem(expr, groupItem, expressionTree.entity));
90✔
1594
                } else {
1595
                    const filteringExpr = expr as IFilteringExpression;
1,403✔
1596
                    const exprCopy: IFilteringExpression = {
1,403✔
1597
                        fieldName: filteringExpr.fieldName,
1598
                        condition: filteringExpr.condition,
1599
                        conditionName: filteringExpr.condition?.name || filteringExpr.conditionName,
1,403!
1600
                        searchVal: filteringExpr.searchVal,
1601
                        searchTree: filteringExpr.searchTree,
1602
                        ignoreCase: filteringExpr.ignoreCase
1603
                    };
1604
                    const operandItem = new ExpressionOperandItem(exprCopy, groupItem);
1,403✔
1605
                    const field = this.fields?.find(el => el.field === filteringExpr.fieldName);
2,025✔
1606
                    operandItem.fieldLabel = field?.label || field?.header || field?.field;
1,403✔
1607
                    if (this._expandedExpressions.filter(e => e.searchTree == operandItem.expression.searchTree).length > 0) {
1,403!
1608
                        operandItem.expanded = true;
×
1609
                    }
1610
                    groupItem.children.push(operandItem);
1,403✔
1611
                }
1612
            }
1613

1614

1615
            if (expressionTree.entity) {
684✔
1616
                entityName = expressionTree.entity;
626✔
1617
            }
1618
            const entity = this.entities?.find(el => el.name === entityName);
981✔
1619
            if (entity) {
684✔
1620
                this.fields = entity.fields;
630✔
1621
            }
1622

1623
            this._selectedEntity = this.entities?.find(el => el.name === entityName);
981✔
1624
            this._selectedReturnFields =
684✔
1625
                !expressionTree.returnFields || expressionTree.returnFields.includes('*') || expressionTree.returnFields.includes('All') || expressionTree.returnFields.length === 0
2,874✔
1626
                    ? this.fields?.map(f => f.field)
1,240✔
1627
                    : this.fields?.filter(f => expressionTree.returnFields.indexOf(f.field) >= 0).map(f => f.field);
1,660✔
1628
        }
1629
        return groupItem;
847✔
1630
    }
1631

1632
    private createExpressionTreeFromGroupItem(groupItem: ExpressionGroupItem, entity?: string, returnFields?: string[]): FilteringExpressionsTree {
1633
        if (!groupItem) {
152✔
1634
            return null;
4✔
1635
        }
1636

1637
        const expressionTree = new FilteringExpressionsTree(groupItem.operator, undefined, entity, returnFields);
148✔
1638

1639
        for (let i = 0; i < groupItem.children.length; i++) {
148✔
1640
            const item = groupItem.children[i];
201✔
1641

1642
            if (item instanceof ExpressionGroupItem) {
201✔
1643
                const subTree = this.createExpressionTreeFromGroupItem((item as ExpressionGroupItem), entity, returnFields);
19✔
1644
                expressionTree.filteringOperands.push(subTree);
19✔
1645
            } else {
1646
                expressionTree.filteringOperands.push((item as ExpressionOperandItem).expression);
182✔
1647
            }
1648
        }
1649

1650
        return expressionTree;
148✔
1651
    }
1652

1653
    private scrollElementIntoView(target: HTMLElement) {
1654
        const container = this.expressionsContainer.nativeElement;
649✔
1655
        const targetOffset = target.offsetTop - container.offsetTop;
649✔
1656
        const delta = 10;
649✔
1657

1658
        if (container.scrollTop + delta > targetOffset) {
649✔
1659
            container.scrollTop = targetOffset - delta;
420✔
1660
        } else if (container.scrollTop + container.clientHeight < targetOffset + target.offsetHeight + delta) {
229✔
1661
            container.scrollTop = targetOffset + target.offsetHeight + delta - container.clientHeight;
224✔
1662
        }
1663
    }
1664

1665
    private focusEditedExpressionChip() {
1666
        if (this._timeoutId) {
61✔
1667
            clearTimeout(this._timeoutId);
5✔
1668
        }
1669

1670
        this._timeoutId = setTimeout(() => {
61✔
1671
            if (this._lastFocusedChipIndex != -1) {
61✔
1672
                //Sort the expression chip list.
1673
                //If there was a recent drag&drop and the tree hasn't rerendered(child query), they will be unordered
1674
                const sortedChips = this.expressionsChips.toArray().sort(function (a, b) {
59✔
1675
                    if (a === b) return 0;
73!
1676
                    if (a.chipArea.nativeElement.compareDocumentPosition(b.chipArea.nativeElement) & 2) {
73✔
1677
                        // b comes before a
1678
                        return 1;
60✔
1679
                    }
1680
                    return -1;
13✔
1681
                });
1682
                const chipElement = sortedChips[this._lastFocusedChipIndex]?.nativeElement;
59✔
1683
                if (chipElement) {
59✔
1684
                    chipElement.focus();
51✔
1685
                }
1686
                this._lastFocusedChipIndex = -1;
59✔
1687
                this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
59✔
1688
            }
1689
        }, this._focusDelay);
1690
    }
1691

1692
    private init() {
1693
        this.cancelOperandAdd();
495✔
1694
        this.cancelOperandEdit();
495✔
1695

1696
        // Ignore values of certain properties for the comparison
1697
        const propsToIgnore = ['parent', 'hovered', 'ignoreCase', 'inEditMode', 'inAddMode'];
495✔
1698
        const propsReplacer = function replacer(key, value) {
495✔
1699
            if (propsToIgnore.indexOf(key) >= 0) {
19,527✔
1700
                return undefined;
2,606✔
1701
            } else {
1702
                return value;
16,921✔
1703
            }
1704
        };
1705

1706
        // Skip root being recreated if the same
1707
        const newRootGroup = this.createExpressionGroupItem(this.expressionTree);
495✔
1708
        if (JSON.stringify(this.rootGroup, propsReplacer) !== JSON.stringify(newRootGroup, propsReplacer)) {
495✔
1709
            this.rootGroup = this.createExpressionGroupItem(this.expressionTree);
257✔
1710
            this.currentGroup = this.rootGroup;
257✔
1711
        }
1712

1713
        if (this.rootGroup?.children?.length == 0) {
495✔
1714
            this.rootGroup = null;
40✔
1715
            this.currentGroup = null;
40✔
1716
        }
1717
    }
1718

1719
    /** rootGroup is recreated after clicking Apply, which sets new expressionTree and calls init()*/
1720
    protected trackExpressionItem = trackByIdentity;
290✔
1721
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc