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

IgniteUI / igniteui-angular / 13287444581

12 Feb 2025 02:18PM UTC coverage: 10.56% (-81.0%) from 91.606%
13287444581

Pull #15359

github

web-flow
Merge a24969adb into 32cfe83f6
Pull Request #15359: fix(time-picker): exclude from SSR toggle events #15135

933 of 15233 branches covered (6.12%)

3037 of 28759 relevant lines covered (10.56%)

352.42 hits per line

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

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

20
import { IgxButtonDirective } from '../directives/button/button.directive';
21
import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/date-time-editor.directive';
22

23
import { IgxOverlayOutletDirective, IgxToggleDirective } from '../directives/toggle/toggle.directive';
24
import { FieldType } from '../grids/common/grid.interface';
25
import { IgxIconService } from '../icon/icon.service';
26
import { IgxSelectComponent } from '../select/select.component';
27
import { HorizontalAlignment, OverlaySettings, Point, VerticalAlignment } from '../services/overlay/utilities';
28
import { AbsoluteScrollStrategy, AutoPositionStrategy, CloseScrollStrategy, ConnectedPositioningStrategy } from '../services/public_api';
29
import { IgxTimePickerComponent } from '../time-picker/time-picker.component';
30
import { IgxQueryBuilderHeaderComponent } from './query-builder-header.component';
31
import { IgxPickerToggleComponent, IgxPickerClearComponent } from '../date-common/picker-icons.common';
32
import { IgxInputDirective } from '../directives/input/input.directive';
33
import { IgxInputGroupComponent } from '../input-group/input-group.component';
34
import { IgxSelectItemComponent } from '../select/select-item.component';
35
import { IgxSuffixDirective } from '../directives/suffix/suffix.directive';
36
import { IgxPrefixDirective } from '../directives/prefix/prefix.directive';
37
import { IgxIconComponent } from '../icon/icon.component';
38
import { getCurrentResourceStrings } from '../core/i18n/resources';
39
import { IgxIconButtonDirective } from '../directives/button/icon-button.directive';
40

41
const DEFAULT_PIPE_DATE_FORMAT = 'mediumDate';
2✔
42
const DEFAULT_PIPE_TIME_FORMAT = 'mediumTime';
2✔
43
const DEFAULT_PIPE_DATE_TIME_FORMAT = 'medium';
2✔
44
const DEFAULT_PIPE_DIGITS_INFO = '1.0-3';
2✔
45
const DEFAULT_DATE_TIME_FORMAT = 'dd/MM/yyyy HH:mm:ss a';
2✔
46
const DEFAULT_TIME_FORMAT = 'hh:mm:ss a';
2✔
47

48
@Pipe({
49
    name: 'fieldFormatter',
50
    standalone: true
51
})
52
export class IgxFieldFormatterPipe implements PipeTransform {
2✔
53

54
    public transform(value: any, formatter: (v: any, data: any, fieldData?: any) => any, rowData: any, fieldData?: any) {
55
        return formatter(value, rowData, fieldData);
×
56
    }
57
}
58

59
/**
60
 * @hidden @internal
61
 *
62
 * Internal class usage
63
 */
64
class ExpressionItem {
65
    public parent: ExpressionGroupItem;
66
    public selected: boolean;
67
    constructor(parent?: ExpressionGroupItem) {
68
        this.parent = parent;
×
69
    }
70
}
71

72
/**
73
 * @hidden @internal
74
 *
75
 * Internal class usage
76
 */
77
class ExpressionGroupItem extends ExpressionItem {
78
    public operator: FilteringLogic;
79
    public children: ExpressionItem[];
80
    constructor(operator: FilteringLogic, parent?: ExpressionGroupItem) {
81
        super(parent);
×
82
        this.operator = operator;
×
83
        this.children = [];
×
84
    }
85
}
86

87
/**
88
 * @hidden @internal
89
 *
90
 * Internal class usage
91
 */
92
class ExpressionOperandItem extends ExpressionItem {
93
    public expression: IFilteringExpression;
94
    public inEditMode: boolean;
95
    public inAddMode: boolean;
96
    public hovered: boolean;
97
    public fieldLabel: string;
98
    constructor(expression: IFilteringExpression, parent: ExpressionGroupItem) {
99
        super(parent);
×
100
        this.expression = expression;
×
101
    }
102
}
103

104
/**
105
 * A component used for operating with complex filters by creating or editing conditions
106
 * and grouping them using AND/OR logic.
107
 * It is used internally in the Advanced Filtering of the Grid.
108
 *
109
 * @example
110
 * ```html
111
 * <igx-query-builder [fields]="this.fields">
112
 * </igx-query-builder>
113
 * ```
114
 */
115
@Component({
116
    selector: 'igx-query-builder',
117
    templateUrl: './query-builder.component.html',
118
    imports: [NgIf, IgxQueryBuilderHeaderComponent, IgxButtonDirective, IgxIconComponent, IgxChipComponent, IgxPrefixDirective, IgxSuffixDirective, IgxSelectComponent, FormsModule, NgFor, IgxSelectItemComponent, IgxInputGroupComponent, IgxInputDirective, IgxDatePickerComponent, IgxPickerToggleComponent, IgxPickerClearComponent, IgxTimePickerComponent, IgxDateTimeEditorDirective, NgTemplateOutlet, NgClass, IgxToggleDirective, IgxButtonGroupComponent, IgxOverlayOutletDirective, DatePipe, IgxFieldFormatterPipe, IgxIconButtonDirective]
119
})
120
export class IgxQueryBuilderComponent implements AfterViewInit, OnDestroy {
2✔
121
    /**
122
     * @hidden @internal
123
     */
124
    @HostBinding('class.igx-query-builder')
125
    public cssClass = 'igx-query-builder';
×
126

127
    /**
128
     * @hidden @internal
129
     */
130
    @HostBinding('style.display')
131
    public display = 'block';
×
132

133
    /**
134
    * Returns the fields.
135
    */
136
    public get fields(): FieldType[] {
137
        return this._fields;
×
138
    }
139

140
    /**
141
     * Sets the fields.
142
     */
143
    @Input()
144
    public set fields(fields: FieldType[]) {
145
        this._fields = fields;
×
146

147
        if (this._fields) {
×
148
            this.registerSVGIcons();
×
149

150
            this._fields.forEach(field => {
×
151
                this.setFilters(field);
×
152
                this.setFormat(field);
×
153
            });
154
        }
155
    }
156

157
    /**
158
    * Returns the expression tree.
159
    */
160
     public get expressionTree(): IExpressionTree {
161
        return this._expressionTree;
×
162
    }
163

164
    /**
165
     * Sets the expression tree.
166
     */
167
    @Input()
168
    public set expressionTree(expressionTree: IExpressionTree) {
169
        this._expressionTree = expressionTree;
×
170

171
        this.init();
×
172
    }
173

174
    /**
175
     * Gets the `locale` of the query builder.
176
     * If not set, defaults to application's locale.
177
     */
178
    @Input()
179
    public get locale(): string {
180
        return this._locale;
×
181
    }
182

183
    /**
184
     * Sets the `locale` of the query builder.
185
     * Expects a valid BCP 47 language tag.
186
     */
187
    public set locale(value: string) {
188
        this._locale = value;
×
189
        // if value is invalid, set it back to _localeId
190
        try {
×
191
            getLocaleFirstDayOfWeek(this._locale);
×
192
        } catch (e) {
193
            this._locale = this._localeId;
×
194
        }
195
    }
196

197
    /**
198
     * Sets the resource strings.
199
     * By default it uses EN resources.
200
     */
201
    @Input()
202
    public set resourceStrings(value: IQueryBuilderResourceStrings) {
203
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
204
    }
205

206
    /**
207
     * Returns the resource strings.
208
     */
209
    public get resourceStrings(): IQueryBuilderResourceStrings {
210
        return this._resourceStrings;
×
211
    }
212

213
    /**
214
     * Event fired as the expression tree is changed.
215
     *
216
     * ```html
217
     *  <igx-query-builder (expressionTreeChange)='onExpressionTreeChange()'></igx-query-builder>
218
     * ```
219
     */
220
    @Output()
221
    public expressionTreeChange = new EventEmitter();
×
222

223
    @ViewChild('fieldSelect', { read: IgxSelectComponent })
224
    private fieldSelect: IgxSelectComponent;
225

226
    @ViewChild('conditionSelect', { read: IgxSelectComponent })
227
    private conditionSelect: IgxSelectComponent;
228

229
    @ViewChild('searchValueInput', { read: ElementRef })
230
    private searchValueInput: ElementRef;
231

232
    @ViewChild('picker')
233
    private picker: IgxDatePickerComponent | IgxTimePickerComponent;
234

235
    @ViewChild('addRootAndGroupButton', { read: ElementRef })
236
    private addRootAndGroupButton: ElementRef;
237

238
    @ViewChild('addConditionButton', { read: ElementRef })
239
    private addConditionButton: ElementRef;
240

241
    /**
242
     * @hidden @internal
243
     */
244
    @ContentChild(IgxQueryBuilderHeaderComponent)
245
    public headerContent: IgxQueryBuilderHeaderComponent;
246

247
    @ViewChild('editingInputsContainer', { read: ElementRef })
248
    protected set editingInputsContainer(value: ElementRef) {
249
        if ((value && !this._editingInputsContainer) ||
×
250
            (value && this._editingInputsContainer && this._editingInputsContainer.nativeElement !== value.nativeElement)) {
251
            requestAnimationFrame(() => {
×
252
                this.scrollElementIntoView(value.nativeElement);
×
253
            });
254
        }
255

256
        this._editingInputsContainer = value;
×
257
    }
258

259
    /** @hidden */
260
    protected get editingInputsContainer(): ElementRef {
261
        return this._editingInputsContainer;
×
262
    }
263

264
    @ViewChild('addModeContainer', { read: ElementRef })
265
    protected set addModeContainer(value: ElementRef) {
266
        if ((value && !this._addModeContainer) ||
×
267
            (value && this._addModeContainer && this._addModeContainer.nativeElement !== value.nativeElement)) {
268
            requestAnimationFrame(() => {
×
269
                this.scrollElementIntoView(value.nativeElement);
×
270
            });
271
        }
272

273
        this._addModeContainer = value;
×
274
    }
275

276
    /** @hidden */
277
    protected get addModeContainer(): ElementRef {
278
        return this._addModeContainer;
×
279
    }
280

281
    @ViewChild('currentGroupButtonsContainer', { read: ElementRef })
282
    protected set currentGroupButtonsContainer(value: ElementRef) {
283
        if ((value && !this._currentGroupButtonsContainer) ||
×
284
            (value && this._currentGroupButtonsContainer && this._currentGroupButtonsContainer.nativeElement !== value.nativeElement)) {
285
            requestAnimationFrame(() => {
×
286
                this.scrollElementIntoView(value.nativeElement);
×
287
            });
288
        }
289

290
        this._currentGroupButtonsContainer = value;
×
291
    }
292

293
    /** @hidden */
294
    protected get currentGroupButtonsContainer(): ElementRef {
295
        return this._currentGroupButtonsContainer;
×
296
    }
297

298
    @ViewChild(IgxToggleDirective)
299
    private contextMenuToggle: IgxToggleDirective;
300

301
    @ViewChildren(IgxChipComponent)
302
    private chips: QueryList<IgxChipComponent>;
303

304
    @ViewChild('expressionsContainer')
305
    private expressionsContainer: ElementRef;
306

307
    @ViewChild('overlayOutlet', { read: IgxOverlayOutletDirective, static: true })
308
    private overlayOutlet: IgxOverlayOutletDirective;
309

310
    /**
311
     * @hidden @internal
312
     */
313
    public rootGroup: ExpressionGroupItem;
314

315
    /**
316
     * @hidden @internal
317
     */
318
    public selectedExpressions: ExpressionOperandItem[] = [];
×
319

320
    /**
321
     * @hidden @internal
322
     */
323
    public currentGroup: ExpressionGroupItem;
324

325
    /**
326
     * @hidden @internal
327
     */
328
    public contextualGroup: ExpressionGroupItem;
329

330
    /**
331
     * @hidden @internal
332
     */
333
    public filteringLogics;
334

335
    /**
336
     * @hidden @internal
337
     */
338
    public selectedCondition: string;
339

340
    /**
341
     * @hidden @internal
342
     */
343
    public searchValue: any;
344

345
    /**
346
     * @hidden @internal
347
     */
348
    public pickerOutlet: IgxOverlayOutletDirective | ElementRef;
349

350
    /**
351
     * @hidden @internal
352
     */
353
    public fieldSelectOverlaySettings: OverlaySettings = {
×
354
        scrollStrategy: new AbsoluteScrollStrategy(),
355
        modal: false,
356
        closeOnOutsideClick: false
357
    };
358

359
    /**
360
     * @hidden @internal
361
     */
362
    public conditionSelectOverlaySettings: OverlaySettings = {
×
363
        scrollStrategy: new AbsoluteScrollStrategy(),
364
        modal: false,
365
        closeOnOutsideClick: false
366
    };
367

368
    private destroy$ = new Subject<any>();
×
369
    private _selectedField: FieldType;
370
    private _clickTimer;
371
    private _dblClickDelay = 200;
×
372
    private _preventChipClick = false;
×
373
    private _editingInputsContainer: ElementRef;
374
    private _addModeContainer: ElementRef;
375
    private _currentGroupButtonsContainer: ElementRef;
376
    private _addModeExpression: ExpressionOperandItem;
377
    private _editedExpression: ExpressionOperandItem;
378
    private _selectedGroups: ExpressionGroupItem[] = [];
×
379
    private _fields: FieldType[];
380
    private _expressionTree: IExpressionTree;
381
    private _locale;
382
    private _resourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN);
×
383

384
    private _positionSettings = {
×
385
        horizontalStartPoint: HorizontalAlignment.Right,
386
        verticalStartPoint: VerticalAlignment.Top
387
    };
388

389
    private _overlaySettings: OverlaySettings = {
×
390
        closeOnOutsideClick: false,
391
        modal: false,
392
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings),
393
        scrollStrategy: new CloseScrollStrategy()
394
    };
395

396
    constructor(public cdr: ChangeDetectorRef,
×
397
        protected iconService: IgxIconService,
×
398
        protected platform: PlatformUtil,
×
399
        protected el: ElementRef,
×
400
        @Inject(LOCALE_ID) protected _localeId: string) {
×
401
        this.locale = this.locale || this._localeId;
×
402
    }
403

404
    /**
405
     * @hidden @internal
406
     */
407
    public ngAfterViewInit(): void {
408
        this._overlaySettings.outlet = this.overlayOutlet;
×
409
        this.fieldSelectOverlaySettings.outlet = this.overlayOutlet;
×
410
        this.conditionSelectOverlaySettings.outlet = this.overlayOutlet;
×
411
    }
412

413
    /**
414
     * @hidden @internal
415
     */
416
    public ngOnDestroy(): void {
417
        this.destroy$.next(true);
×
418
        this.destroy$.complete();
×
419
    }
420

421
    /**
422
     * @hidden @internal
423
     */
424
    public set selectedField(value: FieldType) {
425
        const oldValue = this._selectedField;
×
426

427
        if (this._selectedField !== value) {
×
428
            this._selectedField = value;
×
429
            if (oldValue && this._selectedField && this._selectedField.dataType !== oldValue.dataType) {
×
430
                this.selectedCondition = null;
×
431
                this.searchValue = null;
×
432
                this.cdr.detectChanges();
×
433
            }
434
        }
435
    }
436

437
    /**
438
     * @hidden @internal
439
     */
440
    public get selectedField(): FieldType {
441
        return this._selectedField;
×
442
    }
443

444
    /**
445
     * @hidden @internal
446
     *
447
     * used by the grid
448
     */
449
    public setPickerOutlet(outlet?: IgxOverlayOutletDirective | ElementRef) {
450
        this.pickerOutlet = outlet;
×
451
    }
452

453
    /**
454
     * @hidden @internal
455
     *
456
     * used by the grid
457
     */
458
    public get isContextMenuVisible(): boolean {
459
        return !this.contextMenuToggle.collapsed;
×
460
    }
461

462
    /**
463
     * @hidden @internal
464
     */
465
    public get hasEditedExpression(): boolean {
466
        return this._editedExpression !== undefined && this._editedExpression !== null;
×
467
    }
468

469
    /**
470
     * @hidden @internal
471
     */
472
    public addCondition(parent: ExpressionGroupItem, afterExpression?: ExpressionItem) {
473
        this.cancelOperandAdd();
×
474

475
        const operandItem = new ExpressionOperandItem({
×
476
            fieldName: null,
477
            condition: null,
478
            ignoreCase: true,
479
            searchVal: null
480
        }, parent);
481

482
        if (afterExpression) {
×
483
            const index = parent.children.indexOf(afterExpression);
×
484
            parent.children.splice(index + 1, 0, operandItem);
×
485
        } else {
486
            parent.children.push(operandItem);
×
487
        }
488

489
        this.enterExpressionEdit(operandItem);
×
490
    }
491

492
    /**
493
     * @hidden @internal
494
     */
495
    public addAndGroup(parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
496
        this.addGroup(FilteringLogic.And, parent, afterExpression);
×
497
    }
498

499
    /**
500
     * @hidden @internal
501
     */
502
    public addOrGroup(parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
503
        this.addGroup(FilteringLogic.Or, parent, afterExpression);
×
504
    }
505

506
    /**
507
     * @hidden @internal
508
     */
509
    public endGroup(groupItem: ExpressionGroupItem) {
510
        this.currentGroup = groupItem.parent;
×
511
    }
512

513
    /**
514
     * @hidden @internal
515
     */
516
    public commitOperandEdit() {
517
        if (this._editedExpression) {
×
518
            this._editedExpression.expression.fieldName = this.selectedField.field;
×
519
            this._editedExpression.expression.condition = this.selectedField.filters.condition(this.selectedCondition);
×
520
            this._editedExpression.expression.searchVal = DataUtil.parseValue(this.selectedField.dataType, this.searchValue);
×
521
            this._editedExpression.fieldLabel = this.selectedField.label
×
522
                ? this.selectedField.label
523
                : this.selectedField.header
×
524
                    ? this.selectedField.header
525
                    : this.selectedField.field;
526
            this._editedExpression.inEditMode = false;
×
527
            this._editedExpression = null;
×
528
        }
529

530
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup);
×
531
        this.expressionTreeChange.emit();
×
532
    }
533

534
    /**
535
     * @hidden @internal
536
     */
537
    public cancelOperandAdd() {
538
        if (this._addModeExpression) {
×
539
            this._addModeExpression.inAddMode = false;
×
540
            this._addModeExpression = null;
×
541
        }
542
    }
543

544
    /**
545
     * @hidden @internal
546
     */
547
    public cancelOperandEdit() {
548
        if (this._editedExpression) {
×
549
            this._editedExpression.inEditMode = false;
×
550

551
            if (!this._editedExpression.expression.fieldName) {
×
552
                this.deleteItem(this._editedExpression);
×
553
            }
554

555
            this._editedExpression = null;
×
556
        }
557
    }
558

559
    /**
560
     * @hidden @internal
561
     */
562
    public operandCanBeCommitted(): boolean {
563
        return this.selectedField && this.selectedCondition &&
×
564
            (!!this.searchValue || this.selectedField.filters.condition(this.selectedCondition).isUnary);
565
    }
566

567
    /**
568
     * @hidden @internal
569
     *
570
     * used by the grid
571
     */
572
    public exitOperandEdit() {
573
        if (!this._editedExpression) {
×
574
            return;
×
575
        }
576

577
        if (this.operandCanBeCommitted()) {
×
578
            this.commitOperandEdit();
×
579
        } else {
580
            this.cancelOperandEdit();
×
581
        }
582
    }
583

584
    /**
585
     * @hidden @internal
586
     */
587
    public isExpressionGroup(expression: ExpressionItem): boolean {
588
        return expression instanceof ExpressionGroupItem;
×
589
    }
590

591
    /**
592
     * @hidden @internal
593
     */
594
    public onChipRemove(expressionItem: ExpressionItem) {
595
        this.deleteItem(expressionItem);
×
596
    }
597

598
    /**
599
     * @hidden @internal
600
     */
601
    public onChipClick(expressionItem: ExpressionOperandItem) {
602
        this._clickTimer = setTimeout(() => {
×
603
            if (!this._preventChipClick) {
×
604
                this.onToggleExpression(expressionItem);
×
605
            }
606
            this._preventChipClick = false;
×
607
        }, this._dblClickDelay);
608
    }
609

610
    /**
611
     * @hidden @internal
612
     */
613
    public onChipDblClick(expressionItem: ExpressionOperandItem) {
614
        clearTimeout(this._clickTimer);
×
615
        this._preventChipClick = true;
×
616
        this.enterExpressionEdit(expressionItem);
×
617
    }
618

619
    /**
620
     * @hidden @internal
621
     */
622
    public enterExpressionEdit(expressionItem: ExpressionOperandItem) {
623
        this.clearSelection();
×
624
        this.exitOperandEdit();
×
625
        this.cancelOperandAdd();
×
626

627
        if (this._editedExpression) {
×
628
            this._editedExpression.inEditMode = false;
×
629
        }
630

631
        expressionItem.hovered = false;
×
632

633
        this.selectedField = expressionItem.expression.fieldName ?
×
634
            this.fields.find(field => field.field === expressionItem.expression.fieldName) : null;
×
635
        this.selectedCondition = expressionItem.expression.condition ?
×
636
            expressionItem.expression.condition.name : null;
637
        this.searchValue = expressionItem.expression.searchVal;
×
638

639
        expressionItem.inEditMode = true;
×
640
        this._editedExpression = expressionItem;
×
641

642
        this.cdr.detectChanges();
×
643

644
        this.fieldSelectOverlaySettings.target = this.fieldSelect.element;
×
645
        this.fieldSelectOverlaySettings.excludeFromOutsideClick = [this.fieldSelect.element as HTMLElement];
×
646
        this.fieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
×
647
        this.conditionSelectOverlaySettings.target = this.conditionSelect.element;
×
648
        this.conditionSelectOverlaySettings.excludeFromOutsideClick = [this.conditionSelect.element as HTMLElement];
×
649
        this.conditionSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
×
650

651
        if (!this.selectedField) {
×
652
            this.fieldSelect.input.nativeElement.focus();
×
653
            } else if (this.selectedField.filters.condition(this.selectedCondition).isUnary) {
×
654
                this.conditionSelect.input.nativeElement.focus();
×
655
        } else {
656
            const input = this.searchValueInput?.nativeElement || this.picker?.getEditElement();
×
657
            input.focus();
×
658
        }
659
    }
660

661
    /**
662
     * @hidden @internal
663
     */
664
    public clearSelection() {
665
        for (const group of this._selectedGroups) {
×
666
            group.selected = false;
×
667
        }
668
        this._selectedGroups = [];
×
669

670
        for (const expr of this.selectedExpressions) {
×
671
            expr.selected = false;
×
672
        }
673
        this.selectedExpressions = [];
×
674

675
        this.toggleContextMenu();
×
676
    }
677

678
    /**
679
     * @hidden @internal
680
     */
681
    public enterExpressionAdd(expressionItem: ExpressionOperandItem) {
682
        this.clearSelection();
×
683
        this.exitOperandEdit();
×
684

685
        if (this._addModeExpression) {
×
686
            this._addModeExpression.inAddMode = false;
×
687
        }
688

689
        expressionItem.inAddMode = true;
×
690
        this._addModeExpression = expressionItem;
×
691
        if (expressionItem.selected) {
×
692
            this.toggleExpression(expressionItem);
×
693
        }
694
    }
695

696
    /**
697
     * @hidden @internal
698
     */
699
    public contextMenuClosed() {
700
        this.contextualGroup = null;
×
701
    }
702

703
    /**
704
     * @hidden @internal
705
     */
706
    public onKeyDown(eventArgs: KeyboardEvent) {
707
        eventArgs.stopPropagation();
×
708
        const key = eventArgs.key;
×
709
        if (!this.contextMenuToggle.collapsed && (key === this.platform.KEYMAP.ESCAPE)) {
×
710
            this.clearSelection();
×
711
        }
712
    }
713

714
    /**
715
     * @hidden @internal
716
     */
717
    public createAndGroup() {
718
        this.createGroup(FilteringLogic.And);
×
719
    }
720

721
    /**
722
     * @hidden @internal
723
     */
724
    public createOrGroup() {
725
        this.createGroup(FilteringLogic.Or);
×
726
    }
727

728
    /**
729
     * @hidden @internal
730
     */
731
    public deleteFilters() {
732
        for (const expr of this.selectedExpressions) {
×
733
            this.deleteItem(expr);
×
734
        }
735

736
        this.clearSelection();
×
737
    }
738

739
    /**
740
     * @hidden @internal
741
     */
742
    public onGroupClick(groupItem: ExpressionGroupItem) {
743
        this.toggleGroup(groupItem);
×
744
    }
745

746
    /**
747
     * @hidden @internal
748
     */
749
    public ungroup() {
750
        const selectedGroup = this.contextualGroup;
×
751
        const parent = selectedGroup.parent;
×
752
        if (parent) {
×
753
            const index = parent.children.indexOf(selectedGroup);
×
754
            parent.children.splice(index, 1, ...selectedGroup.children);
×
755

756
            for (const expr of selectedGroup.children) {
×
757
                expr.parent = parent;
×
758
            }
759
        }
760

761
        this.clearSelection();
×
762
        this.commitOperandEdit();
×
763
    }
764

765
    /**
766
     * @hidden @internal
767
     */
768
    public deleteGroup() {
769
        let selectedGroup = this.contextualGroup;
×
770
        let parent = selectedGroup.parent;
×
771
        if (!parent) {
×
772
            this.rootGroup = null;
×
773
        }
774

775
        while (parent) {
×
776
            let index = parent.children.indexOf(selectedGroup);
×
777
            parent.children.splice(index, 1);
×
778
            selectedGroup = parent;
×
779
            parent = parent.children.length === 0 ? parent.parent : null;
×
780
        }
781

782
        if (this.rootGroup?.children.length === 0) {
×
783
            this.rootGroup = null;
×
784
        }
785

786
        this.clearSelection();
×
787
        this.commitOperandEdit();
×
788
    }
789

790
    /**
791
     * @hidden @internal
792
     */
793
    public selectFilteringLogic(event: IButtonGroupEventArgs) {
794
        this.contextualGroup.operator = event.index as FilteringLogic;
×
795
        this.commitOperandEdit();
×
796
    }
797

798
    /**
799
     * @hidden @internal
800
     */
801
    public getConditionFriendlyName(name: string): string {
802
        return this.resourceStrings[`igx_query_builder_filter_${name}`] || name;
×
803
    }
804

805
    /**
806
     * @hidden @internal
807
     */
808
    public isDate(value: any) {
809
        return value instanceof Date;
×
810
    }
811

812
    /**
813
     * @hidden @internal
814
     */
815
    public onExpressionsScrolled() {
816
        if (!this.contextMenuToggle.collapsed) {
×
817
            this.calculateContextMenuTarget();
×
818
            this.contextMenuToggle.reposition();
×
819
        }
820
    }
821

822
    /**
823
     * @hidden @internal
824
     */
825
    public invokeClick(eventArgs: KeyboardEvent) {
826
        if (this.platform.isActivationKey(eventArgs)) {
×
827
            eventArgs.preventDefault();
×
828
            (eventArgs.currentTarget as HTMLElement).click();
×
829
        }
830
    }
831

832
    /**
833
     * @hidden @internal
834
     */
835
    public openPicker(args: KeyboardEvent) {
836
        if (this.platform.isActivationKey(args)) {
×
837
            args.preventDefault();
×
838
            this.picker.open();
×
839
        }
840
    }
841

842
    /**
843
     * @hidden @internal
844
     */
845
    public onOutletPointerDown(event) {
846
        // This prevents closing the select's dropdown when clicking the scroll
847
        event.preventDefault();
×
848
    }
849

850
    /**
851
     * @hidden @internal
852
     */
853
    public getConditionList(): string[] {
854
        return this.selectedField ? this.selectedField.filters.conditionList() : [];
×
855
    }
856

857
    /**
858
     * @hidden @internal
859
     */
860
    public getFormatter(field: string) {
861
        return this.fields.find(el => el.field === field).formatter;
×
862
    }
863

864
    /**
865
     * @hidden @internal
866
     */
867
    public getFormat(field: string) {
868
        return this.fields.find(el => el.field === field).pipeArgs.format;
×
869
    }
870

871
    /**
872
     * @hidden @internal
873
     *
874
     * used by the grid
875
     */
876
    public setAddButtonFocus() {
877
        if (this.addRootAndGroupButton) {
×
878
            this.addRootAndGroupButton.nativeElement.focus();
×
879
        } else if (this.addConditionButton) {
×
880
            this.addConditionButton.nativeElement.focus();
×
881
        }
882
    }
883

884
    /**
885
     * @hidden @internal
886
     */
887
    public context(expression: ExpressionItem, afterExpression?: ExpressionItem) {
888
        return {
×
889
            $implicit: expression,
890
            afterExpression
891
        };
892
    }
893

894
    /**
895
     * @hidden @internal
896
     */
897
    public onChipSelectionEnd() {
898
        const contextualGroup = this.findSingleSelectedGroup();
×
899
        if (contextualGroup || this.selectedExpressions.length > 1) {
×
900
            this.contextualGroup = contextualGroup;
×
901
            this.calculateContextMenuTarget();
×
902
            if (this.contextMenuToggle.collapsed) {
×
903
                this.contextMenuToggle.open(this._overlaySettings);
×
904
            } else {
905
                this.contextMenuToggle.reposition();
×
906
            }
907
        }
908
    }
909

910
    private setFormat(field: FieldType) {
911
        if (!field.pipeArgs) {
×
912
            field.pipeArgs = { digitsInfo: DEFAULT_PIPE_DIGITS_INFO };
×
913
        }
914

915
        if (!field.pipeArgs.format) {
×
916
            field.pipeArgs.format = field.dataType === DataType.Time ?
×
917
                DEFAULT_PIPE_TIME_FORMAT : field.dataType === DataType.DateTime ?
×
918
                    DEFAULT_PIPE_DATE_TIME_FORMAT : DEFAULT_PIPE_DATE_FORMAT;
919
        }
920
    }
921

922
    private setFilters(field: FieldType) {
923
        if (!field.filters) {
×
924
            switch (field.dataType) {
×
925
                case DataType.Boolean:
926
                    field.filters = IgxBooleanFilteringOperand.instance();
×
927
                    break;
×
928
                case DataType.Number:
929
                case DataType.Currency:
930
                case DataType.Percent:
931
                    field.filters = IgxNumberFilteringOperand.instance();
×
932
                    break;
×
933
                case DataType.Date:
934
                    field.filters = IgxDateFilteringOperand.instance();
×
935
                    break;
×
936
                case DataType.Time:
937
                    field.filters = IgxTimeFilteringOperand.instance();
×
938
                    break;
×
939
                case DataType.DateTime:
940
                    field.filters = IgxDateTimeFilteringOperand.instance();
×
941
                    break;
×
942
                case DataType.String:
943
                default:
944
                    field.filters = IgxStringFilteringOperand.instance();
×
945
                    break;
×
946
            }
947

948
        }
949
    }
950

951
    private onToggleExpression(expressionItem: ExpressionOperandItem) {
952
        this.exitOperandEdit();
×
953
        this.toggleExpression(expressionItem);
×
954

955
        this.toggleContextMenu();
×
956
    }
957

958
    private toggleExpression(expressionItem: ExpressionOperandItem) {
959
        expressionItem.selected = !expressionItem.selected;
×
960

961
        if (expressionItem.selected) {
×
962
            this.selectedExpressions.push(expressionItem);
×
963
        } else {
964
            const index = this.selectedExpressions.indexOf(expressionItem);
×
965
            this.selectedExpressions.splice(index, 1);
×
966
            this.deselectParentRecursive(expressionItem);
×
967
        }
968
    }
969

970
    private addGroup(operator: FilteringLogic, parent?: ExpressionGroupItem, afterExpression?: ExpressionItem) {
971
        this.cancelOperandAdd();
×
972

973
        const groupItem = new ExpressionGroupItem(operator, parent);
×
974

975
        if (parent) {
×
976
            if (afterExpression) {
×
977
                const index = parent.children.indexOf(afterExpression);
×
978
                parent.children.splice(index + 1, 0, groupItem);
×
979
            } else {
980
                parent.children.push(groupItem);
×
981
            }
982
        } else {
983
            this.rootGroup = groupItem;
×
984
        }
985

986
        this.addCondition(groupItem);
×
987
        this.currentGroup = groupItem;
×
988
    }
989

990
    private createExpressionGroupItem(expressionTree: IExpressionTree, parent?: ExpressionGroupItem): ExpressionGroupItem | null {
991
        if (!expressionTree) {
×
992
            return null;
×
993
        }
994

995
        const groupItem = new ExpressionGroupItem(expressionTree.operator, parent);
×
996

997
        for (const expr of expressionTree.filteringOperands) {
×
998
            if (expr instanceof FilteringExpressionsTree) {
×
999
                const childGroup = this.createExpressionGroupItem(expr, groupItem);
×
1000
                if (childGroup) {
×
1001
                    groupItem.children.push(childGroup);
×
1002
                }
1003
            } else {
1004
                const filteringExpr = expr as IFilteringExpression;
×
1005
                const field = this.fields.find(el => el.field === filteringExpr.fieldName);
×
1006

1007
                if (field) {
×
1008
                    const exprCopy: IFilteringExpression = { ...filteringExpr };
×
1009
                    const operandItem = new ExpressionOperandItem(exprCopy, groupItem);
×
1010
                    operandItem.fieldLabel = field.label || field.header || field.field;
×
1011
                    groupItem.children.push(operandItem);
×
1012
                }
1013
            }
1014
        }
1015

1016
        return groupItem.children.length > 0 ? groupItem : null;
×
1017
    }
1018

1019
    private createExpressionTreeFromGroupItem(groupItem: ExpressionGroupItem): FilteringExpressionsTree {
1020
        if (!groupItem) {
×
1021
            return null;
×
1022
        }
1023

1024
        const expressionTree = new FilteringExpressionsTree(groupItem.operator);
×
1025

1026
        for (const item of groupItem.children) {
×
1027
            if (item instanceof ExpressionGroupItem) {
×
1028
                const subTree = this.createExpressionTreeFromGroupItem((item as ExpressionGroupItem));
×
1029
                expressionTree.filteringOperands.push(subTree);
×
1030
            } else {
1031
                expressionTree.filteringOperands.push((item as ExpressionOperandItem).expression);
×
1032
            }
1033
        }
1034

1035
        return expressionTree;
×
1036
    }
1037

1038
    private toggleContextMenu() {
1039
        const contextualGroup = this.findSingleSelectedGroup();
×
1040

1041
        if (contextualGroup || this.selectedExpressions.length > 1) {
×
1042
            this.contextualGroup = contextualGroup;
×
1043

1044
            if (contextualGroup) {
×
1045
                this.filteringLogics = [
×
1046
                    {
1047
                        label: this.resourceStrings.igx_query_builder_filter_operator_and,
1048
                        selected: contextualGroup.operator === FilteringLogic.And
1049
                    },
1050
                    {
1051
                        label: this.resourceStrings.igx_query_builder_filter_operator_or,
1052
                        selected: contextualGroup.operator === FilteringLogic.Or
1053
                    }
1054
                ];
1055
            }
1056
        } else if (this.contextMenuToggle) {
×
1057
            this.contextMenuToggle.close();
×
1058
        }
1059
    }
1060

1061
    private findSingleSelectedGroup(): ExpressionGroupItem {
1062
        for (const group of this._selectedGroups) {
×
1063
            const containsAllSelectedExpressions = this.selectedExpressions.every(op => this.isInsideGroup(op, group));
×
1064

1065
            if (containsAllSelectedExpressions) {
×
1066
                return group;
×
1067
            }
1068
        }
1069

1070
        return null;
×
1071
    }
1072

1073
    private isInsideGroup(item: ExpressionItem, group: ExpressionGroupItem): boolean {
1074
        if (!item) {
×
1075
            return false;
×
1076
        }
1077

1078
        if (item.parent === group) {
×
1079
            return true;
×
1080
        }
1081

1082
        return this.isInsideGroup(item.parent, group);
×
1083
    }
1084

1085
    private deleteItem(expressionItem: ExpressionItem) {
1086
        if (!expressionItem.parent) {
×
1087
            this.rootGroup = null;
×
1088
            this.currentGroup = null;
×
1089
            this._expressionTree = null;
×
1090
            return;
×
1091
        }
1092

1093
        if (expressionItem === this.currentGroup) {
×
1094
            this.currentGroup = this.currentGroup.parent;
×
1095
        }
1096

1097
        const children = expressionItem.parent.children;
×
1098
        const index = children.indexOf(expressionItem);
×
1099
        children.splice(index, 1);
×
1100
        this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup);
×
1101

1102
        if (!children.length) {
×
1103
            this.deleteItem(expressionItem.parent);
×
1104
        }
1105

1106
        this.expressionTreeChange.emit();
×
1107
    }
1108

1109
    private createGroup(operator: FilteringLogic) {
1110
        const chips = this.chips.toArray();
×
1111
        const minIndex = this.selectedExpressions.reduce((i, e) => Math.min(i, chips.findIndex(c => c.data === e)), Number.MAX_VALUE);
×
1112
        const firstExpression = chips[minIndex].data;
×
1113

1114
        const parent = firstExpression.parent;
×
1115
        const groupItem = new ExpressionGroupItem(operator, parent);
×
1116

1117
        const index = parent.children.indexOf(firstExpression);
×
1118
        parent.children.splice(index, 0, groupItem);
×
1119

1120
        for (const expr of this.selectedExpressions) {
×
1121
            groupItem.children.push(expr);
×
1122
            this.deleteItem(expr);
×
1123
            expr.parent = groupItem;
×
1124
        }
1125

1126
        this.clearSelection();
×
1127
    }
1128

1129
    private toggleGroup(groupItem: ExpressionGroupItem) {
1130
        this.exitOperandEdit();
×
1131
        if (groupItem.children && groupItem.children.length) {
×
1132
            this.toggleGroupRecursive(groupItem, !groupItem.selected);
×
1133
            if (!groupItem.selected) {
×
1134
                this.deselectParentRecursive(groupItem);
×
1135
            }
1136
            this.toggleContextMenu();
×
1137
        }
1138
    }
1139

1140
    private toggleGroupRecursive(groupItem: ExpressionGroupItem, selected: boolean) {
1141
        if (groupItem.selected !== selected) {
×
1142
            groupItem.selected = selected;
×
1143

1144
            if (groupItem.selected) {
×
1145
                this._selectedGroups.push(groupItem);
×
1146
            } else {
1147
                const index = this._selectedGroups.indexOf(groupItem);
×
1148
                this._selectedGroups.splice(index, 1);
×
1149
            }
1150
        }
1151

1152
        for (const expr of groupItem.children) {
×
1153
            if (expr instanceof ExpressionGroupItem) {
×
1154
                this.toggleGroupRecursive(expr, selected);
×
1155
            } else {
1156
                const operandExpression = expr as ExpressionOperandItem;
×
1157
                if (operandExpression.selected !== selected) {
×
1158
                    this.toggleExpression(operandExpression);
×
1159
                }
1160
            }
1161
        }
1162
    }
1163

1164
    private deselectParentRecursive(expressionItem: ExpressionItem) {
1165
        const parent = expressionItem.parent;
×
1166
        if (parent) {
×
1167
            if (parent.selected) {
×
1168
                parent.selected = false;
×
1169
                const index = this._selectedGroups.indexOf(parent);
×
1170
                this._selectedGroups.splice(index, 1);
×
1171
            }
1172
            this.deselectParentRecursive(parent);
×
1173
        }
1174
    }
1175

1176
    private calculateContextMenuTarget() {
1177
        const containerRect = this.expressionsContainer.nativeElement.getBoundingClientRect();
×
1178
        const chips = this.chips.filter(c => this.selectedExpressions.indexOf(c.data) !== -1);
×
1179
        let minTop = chips.reduce((t, c) =>
×
1180
            Math.min(t, c.nativeElement.getBoundingClientRect().top), Number.MAX_VALUE);
×
1181
        minTop = Math.max(containerRect.top, minTop);
×
1182
        minTop = Math.min(containerRect.bottom, minTop);
×
1183
        let maxRight = chips.reduce((r, c) =>
×
1184
            Math.max(r, c.nativeElement.getBoundingClientRect().right), 0);
×
1185
        maxRight = Math.max(maxRight, containerRect.left);
×
1186
        maxRight = Math.min(maxRight, containerRect.right);
×
1187
        this._overlaySettings.target = new Point(maxRight, minTop);
×
1188
    }
1189

1190
    private scrollElementIntoView(target: HTMLElement) {
1191
        const container = this.expressionsContainer.nativeElement;
×
1192
        const targetOffset = target.offsetTop - container.offsetTop;
×
1193
        const delta = 10;
×
1194

1195
        if (container.scrollTop + delta > targetOffset) {
×
1196
            container.scrollTop = targetOffset - delta;
×
1197
        } else if (container.scrollTop + container.clientHeight < targetOffset + target.offsetHeight + delta) {
×
1198
            container.scrollTop = targetOffset + target.offsetHeight + delta - container.clientHeight;
×
1199
        }
1200
    }
1201

1202
    private init() {
1203
        this.clearSelection();
×
1204
        this.cancelOperandAdd();
×
1205
        this.cancelOperandEdit();
×
1206
        this.rootGroup = this.createExpressionGroupItem(this.expressionTree);
×
1207
        this.currentGroup = this.rootGroup;
×
1208
    }
1209

1210
    private registerSVGIcons(): void {
1211
        const editorIcons = editor as any[];
×
1212

1213
        editorIcons.forEach((icon) => {
×
1214
            this.iconService.addSvgIconFromText(icon.name, icon.value, 'imx-icons');
×
1215
        });
1216
    }
1217
}
1218

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc