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

IgniteUI / igniteui-angular / 14101962945

27 Mar 2025 08:22AM UTC coverage: 91.596% (-0.07%) from 91.669%
14101962945

push

github

web-flow
fix(circular-progress): make sure that the fill-color-default can be used by the user and flip the gradient for star and end to follow the progressbar progress direction (#15560)

13358 of 15619 branches covered (85.52%)

26920 of 29390 relevant lines covered (91.6%)

33937.04 hits per line

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

92.26
/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}`;
3,415✔
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;
233✔
142

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

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

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

166
        return this._fields;
18,684✔
167
    }
168

169
    /**
170
     * Sets the fields.
171
     */
172
    @Input()
173
    public set fields(fields: FieldType[]) {
174
        this._fields = fields;
682✔
175

176
        if (!this._fields && this.isAdvancedFiltering()) {
682✔
177
            this._fields = this.entities[0].fields;
27✔
178
        }
179

180
        if (this._fields) {
682✔
181
            this._fields.forEach(field => {
682✔
182
                this.setFilters(field);
2,803✔
183
                this.setFormat(field);
2,803✔
184
            });
185
        }
186
    }
187

188
    /**
189
    * Returns the expression tree.
190
    */
191
    public get expressionTree(): IExpressionTree {
192
        return this._expressionTree;
5,755✔
193
    }
194

195
    /**
196
     * Sets the expression tree.
197
     */
198
    @Input()
199
    public set expressionTree(expressionTree: IExpressionTree) {
200
        this._expressionTree = expressionTree;
440✔
201
        if (!expressionTree) {
440✔
202
            this._selectedEntity = null;
142✔
203
            this._selectedReturnFields = [];
142✔
204
        }
205

206
        if (!this._preventInit) {
440✔
207
            this.init();
431✔
208
        }
209
    }
210

211
    /**
212
     * Gets the `locale` of the query builder.
213
     * If not set, defaults to application's locale.
214
     */
215
    @Input()
216
    public get locale(): string {
217
        return this._locale;
938✔
218
    }
219

220
    /**
221
     * Sets the `locale` of the query builder.
222
     * Expects a valid BCP 47 language tag.
223
     */
224
    public set locale(value: string) {
225
        this._locale = value;
373✔
226
        // if value is invalid, set it back to _localeId
227
        try {
373✔
228
            getLocaleFirstDayOfWeek(this._locale);
373✔
229
        } catch {
230
            this._locale = this._localeId;
96✔
231
        }
232
    }
233

234
    /**
235
     * Sets the resource strings.
236
     * By default it uses EN resources.
237
     */
238
    @Input()
239
    public set resourceStrings(value: IQueryBuilderResourceStrings) {
240
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
141✔
241
    }
242

243
    /**
244
     * Returns the resource strings.
245
     */
246
    public get resourceStrings(): IQueryBuilderResourceStrings {
247
        return this._resourceStrings;
93,177✔
248
    }
249

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

256
    /**
257
     * Event fired if a nested query builder tree is being edited.
258
     */
259
    @Output()
260
    public inEditModeChange = new EventEmitter<ExpressionOperandItem>();
233✔
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) ||
668✔
310
            (value && this._editingInputsContainer && this._editingInputsContainer.nativeElement !== value.nativeElement)) {
311
            requestAnimationFrame(() => {
228✔
312
                this.scrollElementIntoView(value.nativeElement);
228✔
313
            });
314
        }
315

316
        this._editingInputsContainer = value;
668✔
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) ||
668✔
327
            (value && this._currentGroupButtonsContainer && this._currentGroupButtonsContainer.nativeElement !== value.nativeElement)) {
328
            requestAnimationFrame(() => {
304✔
329
                this.scrollElementIntoView(value.nativeElement);
304✔
330
            });
331
        }
332

333
        this._currentGroupButtonsContainer = value;
668✔
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[] = [];
233✔
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 };
233✔
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;
233✔
404

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

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

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

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

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

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

459
    private destroy$ = new Subject<any>();
233✔
460
    private _timeoutId: any;
461
    private _lastFocusedChipIndex: number;
462
    private _focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
233✔
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;
233✔
472
    private _prevFocusedContainer: ElementRef;
473
    private _expandedExpressions: IFilteringExpression[] = [];
233✔
474
    private _fields: FieldType[];
475
    private _locale;
476
    private _entityNewValue: EntityType;
477
    private _resourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN);
233✔
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;
4,101✔
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;
2,784✔
493
    }
494

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

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

515
    private _overlaySettings: OverlaySettings = {
233✔
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;
7,751✔
525
    }
526

527
    /** @hidden */
528
    protected isSearchValueInputDisabled(): boolean {
529
        return !this.selectedField ||
1,481✔
530
            !this.selectedCondition ||
531
            (this.selectedField &&
532
                (this.selectedField.filters.condition(this.selectedCondition).isUnary ||
533
                    this.selectedField.filters.condition(this.selectedCondition).isNestedQuery));
534
    }
535

536
    constructor(public cdr: ChangeDetectorRef,
233✔
537
        public dragService: IgxQueryBuilderDragService,
233✔
538
        protected platform: PlatformUtil,
233✔
539
        private elRef: ElementRef,
233✔
540
        @Inject(LOCALE_ID) protected _localeId: string) {
233✔
541
        this.locale = this.locale || this._localeId;
233✔
542
        this.dragService.register(this, elRef);
233✔
543
    }
544

545
    /**
546
     * @hidden @internal
547
     */
548
    public ngAfterViewInit(): void {
549
        this._overlaySettings.outlet = this.overlayOutlet;
233✔
550
        this.entitySelectOverlaySettings.outlet = this.overlayOutlet;
233✔
551
        this.fieldSelectOverlaySettings.outlet = this.overlayOutlet;
233✔
552
        this.conditionSelectOverlaySettings.outlet = this.overlayOutlet;
233✔
553
        this.returnFieldSelectOverlaySettings.outlet = this.overlayOutlet;
233✔
554
        this.addExpressionDropDownOverlaySettings.outlet = this.overlayOutlet;
233✔
555
        this.groupContextMenuDropDownOverlaySettings.outlet = this.overlayOutlet;
233✔
556
        // Trigger additional change detection cycle
557
        this.cdr.detectChanges();
233✔
558
    }
559

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

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

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

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

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

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

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

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

625
        this._selectedReturnFields = this.parentExpression ? [] : this._entityNewValue.fields?.map(f => f.field);
132✔
626

627
        if (this._expressionTree) {
38✔
628
            this._expressionTree.entity = this._entityNewValue.name;
5✔
629

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

632
            this._expressionTree.filteringOperands = [];
5✔
633

634
            this._editedExpression = null;
5✔
635
            if (!this.parentExpression) {
5✔
636
                this.expressionTreeChange.emit(this._expressionTree);
4✔
637
            }
638

639
            this.rootGroup = null;
5✔
640
            this.currentGroup = this.rootGroup;
5✔
641
        }
642

643
        this._selectedField = null;
38✔
644
        this.selectedCondition = null;
38✔
645
        this.searchValue.value = null;
38✔
646

647
        this.entityChangeDialog.close();
38✔
648
        this.entitySelect.close();
38✔
649

650
        this._entityNewValue = null;
38✔
651
        this.innerQueryNewExpressionTree = null;
38✔
652

653
        this.initExpressionTree(this._selectedEntity.name, this.selectedReturnFields);
38✔
654
    }
655

656
    /**
657
     * @hidden @internal
658
     */
659
    public set selectedReturnFields(value: string[]) {
660
        if (this._selectedReturnFields !== value) {
5✔
661
            this._selectedReturnFields = value;
5✔
662

663
            if (this._expressionTree && !this.parentExpression) {
5✔
664
                this._expressionTree.returnFields = value.length === this.fields.length ? ['*'] : value;
5✔
665
                this.expressionTreeChange.emit(this._expressionTree);
5✔
666
            }
667
        }
668
    }
669

670
    /**
671
     * @hidden @internal
672
     */
673
    public get selectedReturnFields(): string[] {
674
        if (typeof this._selectedReturnFields == 'string') {
17,231!
675
            return [this._selectedReturnFields];
×
676
        }
677
        return this._selectedReturnFields;
17,231✔
678
    }
679

680
    /**
681
     * @hidden @internal
682
     */
683
    public set selectedField(value: FieldType) {
684
        const oldValue = this._selectedField;
185✔
685

686
        if (this._selectedField !== value) {
185✔
687
            this._selectedField = value;
142✔
688
            this.selectDefaultCondition();
142✔
689
            if (oldValue && this._selectedField && this._selectedField.dataType !== oldValue.dataType) {
142✔
690
                this.searchValue.value = null;
17✔
691
                this.cdr.detectChanges();
17✔
692
            }
693
        }
694
    }
695

696
    /**
697
     * @hidden @internal
698
     */
699
    public get selectedField(): FieldType {
700
        return this._selectedField;
43,455✔
701
    }
702

703
    /**
704
     * @hidden @internal
705
     *
706
     * used by the grid
707
     */
708
    public setPickerOutlet(outlet?: IgxOverlayOutletDirective | ElementRef) {
709
        this.pickerOutlet = outlet;
44✔
710
    }
711

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

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

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

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

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

746
        this._lastFocusedChipIndex = this._lastFocusedChipIndex === undefined ? -1 : this._lastFocusedChipIndex;
68✔
747

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

763
        this._focusDelay = 250;
68✔
764

765
        if (isUIInteraction && !afterExpression) {
68✔
766
            this._lastFocusedChipIndex = this.expressionsChips.length;
64✔
767
            this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
64✔
768
        }
769

770
        this.enterExpressionEdit(operandItem);
68✔
771
    }
772

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

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

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

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

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

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

827
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0]
56✔
828
            if (innerQuery && this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) {
56✔
829
                innerQuery.exitEditAddMode();
7✔
830
                this._editedExpression.expression.searchTree = this.getExpressionTreeCopy(innerQuery.expressionTree);
7✔
831
                this._editedExpression.expression.searchTree.returnFields = innerQuery.selectedReturnFields;
7✔
832
            } else {
833
                this._editedExpression.expression.searchTree = null;
49✔
834
            }
835
            this.innerQueryNewExpressionTree = null;
56✔
836

837
            if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary || this.selectedField.filters.condition(this.selectedCondition)?.isNestedQuery) {
56✔
838
                this._editedExpression.expression.searchVal = null;
12✔
839
            }
840

841
            this._editedExpression.inEditMode = false;
56✔
842
            this._editedExpression = null;
56✔
843
        }
844

845
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, this.selectedEntity?.name, this.selectedReturnFields);
59✔
846
        if (!this.parentExpression) {
59✔
847
            this.expressionTreeChange.emit(this._expressionTree);
50✔
848
        }
849
    }
850

851
    /**
852
     * @hidden @internal
853
     */
854
    public cancelOperandAdd() {
855
        if (this._addModeExpression) {
517!
856
            this._addModeExpression.inAddMode = false;
×
857
            this._addModeExpression = null;
×
858
        }
859
    }
860

861
    /**
862
     * @hidden @internal
863
     */
864
    public deleteItem = (expressionItem: ExpressionItem, skipEmit: boolean = false) => {
233✔
865
        if (!expressionItem.parent) {
48✔
866
            this.rootGroup = null;
15✔
867
            this.currentGroup = null;
15✔
868
            //this._expressionTree = null;
869
            return;
15✔
870
        }
871

872
        if (expressionItem === this.currentGroup) {
33!
873
            this.currentGroup = this.currentGroup.parent;
×
874
        }
875

876
        const children = expressionItem.parent.children;
33✔
877
        const index = children.indexOf(expressionItem);
33✔
878
        children.splice(index, 1);
33✔
879
        const entity = this.expressionTree ? this.expressionTree.entity : null;
33✔
880
        const returnFields = this.expressionTree ? this.expressionTree.returnFields : null;
33✔
881
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, entity, returnFields); // TODO: don't recreate if not necessary
33✔
882

883
        if (!children.length) {
33✔
884
            this.deleteItem(expressionItem.parent, true);
16✔
885
        }
886

887
        if (!this.parentExpression && !skipEmit) {
33✔
888
            this.expressionTreeChange.emit(this._expressionTree);
31✔
889
        }
890
    }
891

892
    /**
893
     * @hidden @internal
894
     */
895
    public cancelOperandEdit() {
896
        if (this.innerQueries) {
458✔
897
            const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
225✔
898
            if (innerQuery) {
225✔
899
                if (innerQuery._editedExpression) {
9✔
900
                    innerQuery.cancelOperandEdit();
1✔
901
                }
902

903
                innerQuery.expressionTree = this.getExpressionTreeCopy(this._editedExpression.expression.searchTree);
9✔
904
                this.innerQueryNewExpressionTree = null;
9✔
905
            }
906
        }
907

908
        if (this._editedExpression) {
458✔
909
            this._editedExpression.inEditMode = false;
30✔
910

911
            if (!this._editedExpression.expression.fieldName) {
30✔
912
                this.deleteItem(this._editedExpression);
16✔
913
            }
914

915
            this._editedExpression = null;
30✔
916
        }
917

918
        if (!this.expressionTree && this.contextualGroup) {
458✔
919
            this.initialOperator = this.contextualGroup.operator;
1✔
920
        }
921
    }
922

923
    /**
924
     * @hidden @internal
925
     */
926
    public operandCanBeCommitted(): boolean {
927
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
1,548✔
928

929
        return this.selectedField && this.selectedCondition &&
1,548✔
930
            (
931
                (
932
                    ((!Array.isArray(this.searchValue.value) && !!this.searchValue.value) || (Array.isArray(this.searchValue.value) && this.searchValue.value.length !== 0)) &&
933
                    !(this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery)
934
                ) ||
935
                (
936
                    this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery && innerQuery && !!innerQuery.expressionTree && innerQuery.selectedReturnFields?.length > 0
937
                ) ||
938
                this.selectedField.filters.condition(this.selectedCondition)?.isUnary
939
            );
940
    }
941

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

959
    /**
960
     * @hidden @internal
961
     */
962
    public commitCurrentState(): void {
963
        const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
3✔
964
        if (innerQuery) {
3✔
965
            innerQuery.commitCurrentState();
1✔
966
        }
967

968
        if (this._editedExpression) {
3✔
969
            if (this.selectedField) {
3!
970
                this.commitOperandEdit();
3✔
971
            } else {
972
                this.deleteItem(this._editedExpression);
×
973
                this._editedExpression = null;
×
974
            }
975
        }
976
    }
977

978
    /**
979
     * @hidden @internal
980
     */
981
    public exitEditAddMode(shouldPreventInit = false) {
45✔
982
        if (!this._editedExpression) {
219✔
983
            return;
204✔
984
        }
985

986
        this.exitOperandEdit();
15✔
987
        this.cancelOperandAdd();
15✔
988

989
        if (shouldPreventInit) {
15✔
990
            this._preventInit = true;
7✔
991
        }
992
    }
993

994
    /**
995
     * @hidden @internal
996
     *
997
     * used by the grid
998
     */
999
    public exitOperandEdit() {
1000
        if (!this._editedExpression) {
34✔
1001
            return;
15✔
1002
        }
1003

1004
        if (this.operandCanBeCommitted()) {
19✔
1005
            this.commitOperandEdit();
9✔
1006
        } else {
1007
            this.cancelOperandEdit();
10✔
1008
        }
1009
    }
1010

1011
    /**
1012
     * @hidden @internal
1013
     */
1014
    public isExpressionGroup(expression: ExpressionItem): boolean {
1015
        return expression instanceof ExpressionGroupItem;
8,275✔
1016
    }
1017

1018
    /**
1019
     * @hidden @internal
1020
     */
1021
    public onExpressionFocus(expressionItem: ExpressionOperandItem) {
1022
        if (this.prevFocusedExpression) {
41✔
1023
            this.prevFocusedExpression.focused = false;
4✔
1024
        }
1025
        expressionItem.focused = true;
41✔
1026
        this.prevFocusedExpression = expressionItem;
41✔
1027
    }
1028

1029
    /**
1030
     * @hidden @internal
1031
     */
1032
    public onExpressionBlur(event, expressionItem: ExpressionOperandItem) {
1033
        if (this._prevFocusedContainer && this._prevFocusedContainer !== event.target.closest('.igx-filter-tree__expression-item')) {
2!
1034
            expressionItem.focused = false;
×
1035
        }
1036
        this._prevFocusedContainer = event.target.closest('.igx-filter-tree__expression-item');
2✔
1037
    }
1038

1039
    /**
1040
     * @hidden @internal
1041
     */
1042
    public onChipRemove(expressionItem: ExpressionItem) {
1043
        this.exitEditAddMode();
5✔
1044
        this.deleteItem(expressionItem);
5✔
1045
    }
1046

1047
    /**
1048
     * @hidden @internal
1049
     */
1050
    public focusChipAfterDrag = (index: number) => {
233✔
1051
        this._lastFocusedChipIndex = index;
11✔
1052
        this.focusEditedExpressionChip();
11✔
1053
    }
1054
    /**
1055
     * @hidden @internal
1056
     */
1057
    public addExpressionBlur() {
1058
        if (this.prevFocusedExpression) {
×
1059
            this.prevFocusedExpression.focused = false;
×
1060
        }
1061
        if (this.addExpressionItemDropDown && !this.addExpressionItemDropDown.collapsed) {
×
1062
            this.addExpressionItemDropDown.close();
×
1063
        }
1064
    }
1065

1066
    /**
1067
     * @hidden @internal
1068
     */
1069
    public onChipClick(expressionItem: ExpressionOperandItem, chip: IgxChipComponent) {
1070
        this.enterExpressionEdit(expressionItem, chip);
55✔
1071
    }
1072

1073
    /**
1074
     * @hidden @internal
1075
     */
1076
    public enterExpressionEdit(expressionItem: ExpressionOperandItem, chip?: IgxChipComponent) {
1077
        this.exitEditAddMode(true);
123✔
1078
        this.cdr.detectChanges();
123✔
1079
        this._lastFocusedChipIndex = chip ? this.expressionsChips.toArray().findIndex(expr => expr === chip) : this._lastFocusedChipIndex;
123✔
1080
        this.enterEditMode(expressionItem);
123✔
1081
    }
1082

1083

1084
    /**
1085
     * @hidden @internal
1086
     */
1087
    public clickExpressionAdd(targetButton: HTMLElement, chip: IgxChipComponent) {
1088
        this.exitEditAddMode(true);
4✔
1089
        this.cdr.detectChanges();
4✔
1090
        this._lastFocusedChipIndex = this.expressionsChips.toArray().findIndex(expr => expr === chip);
22✔
1091
        this.openExpressionAddDialog(targetButton);
4✔
1092
    }
1093

1094
    /**
1095
     * @hidden @internal
1096
     */
1097
    public openExpressionAddDialog(targetButton: HTMLElement) {
1098
        this.addExpressionDropDownOverlaySettings.target = targetButton;
4✔
1099
        this.addExpressionDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
4✔
1100
            horizontalDirection: HorizontalAlignment.Right,
1101
            horizontalStartPoint: HorizontalAlignment.Left,
1102
            verticalStartPoint: VerticalAlignment.Bottom
1103
        });
1104

1105
        this.addExpressionItemDropDown.open(this.addExpressionDropDownOverlaySettings);
4✔
1106
    }
1107

1108
    /**
1109
     * @hidden @internal
1110
     */
1111
    public enterExpressionAdd(event: ISelectionEventArgs, expressionItem: ExpressionOperandItem) {
1112
        if (this._addModeExpression) {
1!
1113
            this._addModeExpression.inAddMode = false;
×
1114
        }
1115

1116
        if (this.parentExpression) {
1!
1117
            this.inEditModeChange.emit(this.parentExpression);
×
1118
        }
1119

1120
        const parent = expressionItem.parent ?? this.rootGroup;
1!
1121
        requestAnimationFrame(() => {
1✔
1122
            if (event.newSelection.value === 'addCondition') {
1!
1123
                this.addCondition(parent, expressionItem);
1✔
1124
            } else if (event.newSelection.value === 'addGroup') {
×
1125
                this.addReverseGroup(parent, expressionItem);
×
1126
            }
1127
            expressionItem.inAddMode = true;
1✔
1128
            this._addModeExpression = expressionItem;
1✔
1129
        })
1130
    }
1131

1132
    /**
1133
     * @hidden @internal
1134
     */
1135
    public enterEditMode(expressionItem: ExpressionOperandItem) {
1136
        if (this._editedExpression) {
123!
1137
            this._editedExpression.inEditMode = false;
×
1138
        }
1139

1140
        if (this.parentExpression) {
123✔
1141
            this.inEditModeChange.emit(this.parentExpression);
13✔
1142
        }
1143

1144
        expressionItem.hovered = false;
123✔
1145
        this.fields = this.selectedEntity ? this.selectedEntity.fields : null;
123✔
1146
        this.selectedField =
123✔
1147
            expressionItem.expression.fieldName ?
123✔
1148
                this.fields?.find(field => field.field === expressionItem.expression.fieldName)
89✔
1149
                : null;
1150
        this.selectedCondition =
123✔
1151
            expressionItem.expression.condition ?
123✔
1152
                expressionItem.expression.condition.name :
1153
                null;
1154
        this.searchValue.value = expressionItem.expression.searchVal instanceof Set ?
123✔
1155
            Array.from(expressionItem.expression.searchVal) :
1156
            expressionItem.expression.searchVal;
1157

1158
        expressionItem.inEditMode = true;
123✔
1159
        this._editedExpression = expressionItem;
123✔
1160
        this.cdr.detectChanges();
123✔
1161

1162
        this.entitySelectOverlaySettings.target = this.entitySelect.element;
123✔
1163
        this.entitySelectOverlaySettings.excludeFromOutsideClick = [this.entitySelect.element as HTMLElement];
123✔
1164
        this.entitySelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
123✔
1165

1166
        if (this.returnFieldSelect) {
123✔
1167
            this.returnFieldSelectOverlaySettings.target = this.returnFieldSelect.element;
13✔
1168
            this.returnFieldSelectOverlaySettings.excludeFromOutsideClick = [this.returnFieldSelect.element as HTMLElement];
13✔
1169
            this.returnFieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
13✔
1170
        }
1171
        if (this.fieldSelect) {
123✔
1172
            this.fieldSelectOverlaySettings.target = this.fieldSelect.element;
120✔
1173
            this.fieldSelectOverlaySettings.excludeFromOutsideClick = [this.fieldSelect.element as HTMLElement];
120✔
1174
            this.fieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
120✔
1175
        }
1176
        if (this.conditionSelect) {
123✔
1177
            this.conditionSelectOverlaySettings.target = this.conditionSelect.element;
120✔
1178
            this.conditionSelectOverlaySettings.excludeFromOutsideClick = [this.conditionSelect.element as HTMLElement];
120✔
1179
            this.conditionSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
120✔
1180
        }
1181

1182
        if (!this.selectedField) {
123✔
1183
            this.fieldSelect.input.nativeElement.focus();
68✔
1184
        } else if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary) {
55✔
1185
            this.conditionSelect.input.nativeElement.focus();
5✔
1186
        } else {
1187
            const input = this.searchValueInput?.nativeElement || this.picker?.getEditElement();
50✔
1188
            input?.focus();
50✔
1189
        }
1190

1191
        (this.editingInputs?.nativeElement.parentElement as HTMLElement)?.scrollIntoView({ block: "nearest", inline: "nearest" });
123✔
1192
    }
1193

1194
    /**
1195
     * @hidden @internal
1196
     */
1197
    public onConditionSelectChanging(event: ISelectionEventArgs) {
1198
        event.cancel = true;
61✔
1199
        this.selectedCondition = event.newSelection.value;
61✔
1200
        this.conditionSelect.close();
61✔
1201
        this.cdr.detectChanges();
61✔
1202
    }
1203

1204
    /**
1205
     * @hidden @internal
1206
     */
1207
    public onKeyDown(eventArgs: KeyboardEvent) {
1208
        eventArgs.stopPropagation();
×
1209
    }
1210

1211
    /**
1212
     * @hidden @internal
1213
     */
1214
    public onGroupClick(groupContextMenuDropDown: any, targetButton: HTMLButtonElement, groupItem: ExpressionGroupItem) {
1215
        this.exitEditAddMode();
7✔
1216
        this.cdr.detectChanges();
7✔
1217

1218
        this.groupContextMenuDropDown = groupContextMenuDropDown;
7✔
1219
        this.groupContextMenuDropDownOverlaySettings.target = targetButton;
7✔
1220
        this.groupContextMenuDropDownOverlaySettings.positionStrategy = new ConnectedPositioningStrategy({
7✔
1221
            horizontalDirection: HorizontalAlignment.Right,
1222
            horizontalStartPoint: HorizontalAlignment.Left,
1223
            verticalStartPoint: VerticalAlignment.Bottom
1224
        });
1225

1226
        if (groupContextMenuDropDown.collapsed) {
7!
1227
            this.contextualGroup = groupItem;
7✔
1228
            groupContextMenuDropDown.open(this.groupContextMenuDropDownOverlaySettings);
7✔
1229
        } else {
1230
            groupContextMenuDropDown.close();
×
1231
        }
1232
    }
1233

1234
    /**
1235
     * @hidden @internal
1236
     */
1237
    public getOperator(expressionItem: any) {
1238
        // if (!expressionItem && !this.expressionTree && !this.initialOperator) {
1239
        //     this.initialOperator = 0;
1240
        // }
1241

1242
        const operator = expressionItem ?
24,398✔
1243
            expressionItem.operator :
1244
            this.expressionTree ?
2,546✔
1245
                this.expressionTree.operator :
1246
                this.initialOperator;
1247
        return operator;
24,398✔
1248
    }
1249

1250
    /**
1251
     * @hidden @internal
1252
     */
1253
    public getSwitchGroupText(expressionItem: any) {
1254
        const operator = this.getOperator(expressionItem);
4,055✔
1255
        const condition = operator === FilteringLogic.Or ? this.resourceStrings.igx_query_builder_and_label : this.resourceStrings.igx_query_builder_or_label
4,055✔
1256
        return this.resourceStrings.igx_query_builder_switch_group.replace('{0}', condition.toUpperCase());
4,055✔
1257
    }
1258

1259
    /**
1260
     * @hidden @internal
1261
     */
1262
    public onGroupContextMenuDropDownSelectionChanging(event: ISelectionEventArgs) {
1263
        event.cancel = true;
4✔
1264

1265
        if (event.newSelection.value === 'switchCondition') {
4✔
1266
            const newOperator = (!this.expressionTree ? this.initialOperator : (this.contextualGroup ?? this._expressionTree).operator) === 0 ? 1 : 0;
3!
1267
            this.selectFilteringLogic(newOperator);
3✔
1268
        } else if (event.newSelection.value === 'ungroup') {
1✔
1269
            this.ungroup();
1✔
1270
        }
1271

1272
        this.groupContextMenuDropDown.close();
4✔
1273
    }
1274

1275
    /**
1276
     * @hidden @internal
1277
     */
1278
    public ungroup() {
1279
        const selectedGroup = this.contextualGroup;
1✔
1280
        const parent = selectedGroup.parent;
1✔
1281
        if (parent) {
1✔
1282
            const index = parent.children.indexOf(selectedGroup);
1✔
1283
            parent.children.splice(index, 1, ...selectedGroup.children);
1✔
1284

1285
            for (const expr of selectedGroup.children) {
1✔
1286
                expr.parent = parent;
2✔
1287
            }
1288
        }
1289
        this.commitOperandEdit();
1✔
1290
    }
1291

1292
    /**
1293
     * @hidden @internal
1294
     */
1295
    public selectFilteringLogic(index: number) {
1296
        if (!this.expressionTree) {
3!
1297
            this.initialOperator = index;
×
1298
            return;
×
1299
        }
1300

1301
        if (this.contextualGroup) {
3✔
1302
            this.contextualGroup.operator = index as FilteringLogic;
2✔
1303
            this.commitOperandEdit();
2✔
1304
        } else if (this.expressionTree) {
1✔
1305
            this._expressionTree.operator = index as FilteringLogic;
1✔
1306
        }
1307

1308
        this.initialOperator = null;
3✔
1309
    }
1310

1311
    /**
1312
     * @hidden @internal
1313
     */
1314
    public getConditionFriendlyName(name: string): string {
1315
        // 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.
1316
        // So instead of in/notIn we end up with 'inQuery'/'notInQuery', hence removing the suffix from the friendly name.
1317
        return this.resourceStrings[`igx_query_builder_filter_${name?.replace('Query', '')}`] || name;
33,291✔
1318
    }
1319

1320
    /**
1321
     * @hidden @internal
1322
     */
1323
    public isDate(value: any) {
1324
        return value instanceof Date;
4,543✔
1325
    }
1326

1327
    /**
1328
     * @hidden @internal
1329
     */
1330
    public invokeClick(eventArgs: KeyboardEvent) {
1331
        if (!this.dragService.dropGhostExpression && this.platform.isActivationKey(eventArgs)) {
2✔
1332
            eventArgs.preventDefault();
2✔
1333
            (eventArgs.currentTarget as HTMLElement).click();
2✔
1334
        }
1335
    }
1336

1337
    /**
1338
     * @hidden @internal
1339
     */
1340
    public openPicker(args: KeyboardEvent) {
1341
        if (this.platform.isActivationKey(args)) {
×
1342
            args.preventDefault();
×
1343
            this.picker.open();
×
1344
        }
1345
    }
1346

1347
    /**
1348
     * @hidden @internal
1349
     */
1350
    public onOutletPointerDown(event) {
1351
        // This prevents closing the select's dropdown when clicking the scroll
1352
        event.preventDefault();
57✔
1353
    }
1354

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

1361
        if (this.entities?.length === 1 && !this.entities[0].name) {
1,106✔
1362
            return this.selectedField.filters.conditionList();
214✔
1363
        }
1364

1365
        return this.selectedField.filters.extendedConditionList();
892✔
1366
    }
1367

1368
    /**
1369
     * @hidden @internal
1370
     */
1371
    public getFormatter(field: string) {
1372
        return this.fields?.find(el => el.field === field)?.formatter;
20,044✔
1373
    }
1374

1375
    /**
1376
     * @hidden @internal
1377
     */
1378
    public getFormat(field: string) {
1379
        return this.fields?.find(el => el.field === field).pipeArgs.format;
1,839✔
1380
    }
1381

1382
    /**
1383
     * @hidden @internal
1384
     *
1385
     * used by the grid
1386
     */
1387
    public setAddButtonFocus() {
1388
        if (this.addRootAndGroupButton) {
39!
1389
            this.addRootAndGroupButton.nativeElement.focus();
×
1390
        } else if (this.addConditionButton) {
39✔
1391
            this.addConditionButton.nativeElement.focus();
18✔
1392
        }
1393
    }
1394

1395
    /**
1396
     * @hidden @internal
1397
     */
1398
    public context(expression: ExpressionItem, afterExpression?: ExpressionItem) {
1399
        return {
14,090✔
1400
            $implicit: expression,
1401
            afterExpression
1402
        };
1403
    }
1404

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

1418
    public isInEditMode(): boolean {
1419
        return !this.parentExpression || (this.parentExpression && this.parentExpression.inEditMode);
19,327✔
1420
    }
1421

1422
    public onInEditModeChanged(expressionItem: ExpressionOperandItem) {
1423
        if (!expressionItem.inEditMode) {
13!
1424
            this.enterExpressionEdit(expressionItem);
×
1425
        }
1426
    }
1427

1428
    public getExpressionTreeCopy(expressionTree: IExpressionTree, shouldAssignInnerQueryExprTree?: boolean): IExpressionTree {
1429
        if (!expressionTree) {
210✔
1430
            return null;
180✔
1431
        }
1432

1433
        const exprTreeCopy = new FilteringExpressionsTree(expressionTree.operator, expressionTree.fieldName, expressionTree.entity, expressionTree.returnFields);
30✔
1434
        exprTreeCopy.filteringOperands = [];
30✔
1435

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

1438
        if (!this.innerQueryNewExpressionTree && shouldAssignInnerQueryExprTree) {
30✔
1439
            this.innerQueryNewExpressionTree = exprTreeCopy;
18✔
1440
        }
1441

1442
        return exprTreeCopy;
30✔
1443
    }
1444

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

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

1465
        this.initExpressionTree(this.selectedEntity.name, newSelection);
11✔
1466
    }
1467

1468
    public initExpressionTree(selectedEntityName: string, selectedReturnFields: string[]) {
1469
        if (!this._expressionTree) {
49✔
1470
            this._expressionTree = this.createExpressionTreeFromGroupItem(new ExpressionGroupItem(FilteringLogic.And, this.rootGroup), selectedEntityName, selectedReturnFields);
33✔
1471
        }
1472

1473
        if (!this.parentExpression) {
49✔
1474
            this.expressionTreeChange.emit(this._expressionTree);
38✔
1475
        }
1476
    }
1477

1478
    public getSearchValueTemplateContext(defaultSearchValueTemplate): any {
1479
        const ctx = {
1,502✔
1480
            $implicit: this.searchValue,
1481
            selectedField: this.selectedField,
1482
            selectedCondition: this.selectedCondition,
1483
            defaultSearchValueTemplate: defaultSearchValueTemplate
1484
        };
1485
        return ctx;
1,502✔
1486
    }
1487

1488
    private setFormat(field: FieldType) {
1489
        if (!field.pipeArgs) {
2,803✔
1490
            field.pipeArgs = { digitsInfo: DEFAULT_PIPE_DIGITS_INFO };
8✔
1491
        }
1492

1493
        if (!field.pipeArgs.format) {
2,803✔
1494
            field.pipeArgs.format = field.dataType === DataType.Time ?
8!
1495
                DEFAULT_PIPE_TIME_FORMAT : field.dataType === DataType.DateTime ?
8!
1496
                    DEFAULT_PIPE_DATE_TIME_FORMAT : DEFAULT_PIPE_DATE_FORMAT;
1497
        }
1498
    }
1499

1500
    private selectDefaultCondition() {
1501
        if (this.selectedField && this.selectedField.filters) {
142✔
1502
            this.selectedCondition = this.selectedField.filters.conditionList().indexOf('equals') >= 0 ? 'equals' : this.selectedField.filters.conditionList()[0];
106✔
1503
        }
1504
    }
1505

1506
    private setFilters(field: FieldType) {
1507
        if (!field.filters) {
2,803✔
1508
            switch (field.dataType) {
8!
1509
                case DataType.Boolean:
1510
                    field.filters = IgxBooleanFilteringOperand.instance();
2✔
1511
                    break;
2✔
1512
                case DataType.Number:
1513
                case DataType.Currency:
1514
                case DataType.Percent:
1515
                    field.filters = IgxNumberFilteringOperand.instance();
3✔
1516
                    break;
3✔
1517
                case DataType.Date:
1518
                    field.filters = IgxDateFilteringOperand.instance();
1✔
1519
                    break;
1✔
1520
                case DataType.Time:
1521
                    field.filters = IgxTimeFilteringOperand.instance();
×
1522
                    break;
×
1523
                case DataType.DateTime:
1524
                    field.filters = IgxDateTimeFilteringOperand.instance();
×
1525
                    break;
×
1526
                case DataType.String:
1527
                default:
1528
                    field.filters = IgxStringFilteringOperand.instance();
2✔
1529
                    break;
2✔
1530
            }
1531
        }
1532
    }
1533

1534

1535
    private addGroup(operator: FilteringLogic, parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
1536
        this.cancelOperandAdd();
3✔
1537

1538
        const groupItem = new ExpressionGroupItem(operator, parent);
3✔
1539

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

1551
        this.addCondition(groupItem);
3✔
1552
        this.currentGroup = groupItem;
3✔
1553
    }
1554

1555
    private createExpressionGroupItem(expressionTree: IExpressionTree, parent?: ExpressionGroupItem, entityName?: string): ExpressionGroupItem {
1556
        let groupItem: ExpressionGroupItem;
1557
        if (expressionTree) {
727✔
1558
            groupItem = new ExpressionGroupItem(expressionTree.operator, parent);
578✔
1559
            if (!expressionTree.filteringOperands) {
578!
1560
                return groupItem;
×
1561
            }
1562

1563
            for (let i = 0; i < expressionTree.filteringOperands.length; i++) {
578✔
1564
                const expr = expressionTree.filteringOperands[i];
1,273✔
1565

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

1588

1589
            if (expressionTree.entity) {
578✔
1590
                entityName = expressionTree.entity;
521✔
1591
            }
1592
            const entity = this.entities?.find(el => el.name === entityName);
857✔
1593
            if (entity) {
578✔
1594
                this.fields = entity.fields;
521✔
1595
            }
1596

1597
            this._selectedEntity = this.entities?.find(el => el.name === entityName);
857✔
1598
            this._selectedReturnFields =
578✔
1599
                !expressionTree.returnFields || expressionTree.returnFields.includes('*') || expressionTree.returnFields.includes('All') || expressionTree.returnFields.length === 0
2,380✔
1600
                    ? this.fields?.map(f => f.field)
1,165✔
1601
                    : this.fields?.filter(f => expressionTree.returnFields.indexOf(f.field) >= 0).map(f => f.field);
1,313✔
1602
        }
1603
        return groupItem;
727✔
1604
    }
1605

1606
    private createExpressionTreeFromGroupItem(groupItem: ExpressionGroupItem, entity?: string, returnFields?: string[]): FilteringExpressionsTree {
1607
        if (!groupItem) {
149✔
1608
            return null;
4✔
1609
        }
1610

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

1613
        for (let i = 0; i < groupItem.children.length; i++) {
145✔
1614
            const item = groupItem.children[i];
199✔
1615

1616
            if (item instanceof ExpressionGroupItem) {
199✔
1617
                const subTree = this.createExpressionTreeFromGroupItem((item as ExpressionGroupItem), entity, returnFields);
19✔
1618
                expressionTree.filteringOperands.push(subTree);
19✔
1619
            } else {
1620
                expressionTree.filteringOperands.push((item as ExpressionOperandItem).expression);
180✔
1621
            }
1622
        }
1623

1624
        return expressionTree;
145✔
1625
    }
1626

1627
    private scrollElementIntoView(target: HTMLElement) {
1628
        const container = this.expressionsContainer.nativeElement;
532✔
1629
        const targetOffset = target.offsetTop - container.offsetTop;
532✔
1630
        const delta = 10;
532✔
1631

1632
        if (container.scrollTop + delta > targetOffset) {
532✔
1633
            container.scrollTop = targetOffset - delta;
319✔
1634
        } else if (container.scrollTop + container.clientHeight < targetOffset + target.offsetHeight + delta) {
213✔
1635
            container.scrollTop = targetOffset + target.offsetHeight + delta - container.clientHeight;
208✔
1636
        }
1637
    }
1638

1639
    private focusEditedExpressionChip() {
1640
        if (this._timeoutId) {
59✔
1641
            clearTimeout(this._timeoutId);
5✔
1642
        }
1643

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

1666
    private init() {
1667
        this.cancelOperandAdd();
431✔
1668
        this.cancelOperandEdit();
431✔
1669

1670
        // Ignore values of certain properties for the comparison
1671
        const propsToIgnore = ['parent', 'hovered', 'ignoreCase', 'inEditMode', 'inAddMode'];
431✔
1672
        const propsReplacer = function replacer(key, value) {
431✔
1673
            if (propsToIgnore.indexOf(key) >= 0) {
17,536✔
1674
                return undefined;
2,326✔
1675
            } else {
1676
                return value;
15,210✔
1677
            }
1678
        };
1679

1680
        // Skip root being recreated if the same
1681
        const newRootGroup = this.createExpressionGroupItem(this.expressionTree);
431✔
1682
        if (JSON.stringify(this.rootGroup, propsReplacer) !== JSON.stringify(newRootGroup, propsReplacer)) {
431✔
1683
            this.rootGroup = this.createExpressionGroupItem(this.expressionTree);
209✔
1684
            this.currentGroup = this.rootGroup;
209✔
1685
        }
1686

1687
        if (this.rootGroup?.children?.length == 0) {
431✔
1688
            this.rootGroup = null;
40✔
1689
            this.currentGroup = null;
40✔
1690
        }
1691
    }
1692

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