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

IgniteUI / igniteui-angular / 13389996225

18 Feb 2025 11:57AM CUT coverage: 91.688% (+0.07%) from 91.622%
13389996225

Pull #14647

github

web-flow
Merge 5c69b53da into 10ddb05cf
Pull Request #14647: feat(query-builder): support for nested queries and other improvements

13327 of 15595 branches covered (85.46%)

900 of 976 new or added lines in 19 files covered. (92.21%)

2 existing lines in 2 files now uncovered.

26882 of 29319 relevant lines covered (91.69%)

33779.93 hits per line

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

91.67
/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts
1
import { booleanAttribute, ContentChild, EventEmitter, Output, TemplateRef } from '@angular/core';
2
import {
3
    Component, Input, ViewChild, ElementRef, OnDestroy, HostBinding
4
} from '@angular/core';
5
import { Subject } from 'rxjs';
6
import { IQueryBuilderResourceStrings, QueryBuilderResourceStringsEN } from '../core/i18n/query-builder-resources';
7
import { IExpressionTree } from '../data-operations/filtering-expressions-tree';
8
import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive';
9
import { EntityType, FieldType } from '../grids/common/grid.interface';
10
import { IgxQueryBuilderHeaderComponent } from './query-builder-header.component';
11
import { getCurrentResourceStrings } from '../core/i18n/resources';
12
import { IgxQueryBuilderTreeComponent } from './query-builder-tree.component';
13
import { IgxIconService } from '../icon/icon.service';
14
import { editor } from '@igniteui/material-icons-extended';
15
import { IgxQueryBuilderSearchValueTemplateDirective } from './query-builder.directives';
16
import { recreateTree } from '../data-operations/expressions-tree-util';
17

18
/**
19
 * A component used for operating with complex filters by creating or editing conditions
20
 * and grouping them using AND/OR logic.
21
 * It is used internally in the Advanced Filtering of the Grid.
22
 *
23
 * @example
24
 * ```html
25
 * <igx-query-builder [entities]="this.entities">
26
 * </igx-query-builder>
27
 * ```
28
 */
29
@Component({
30
    selector: 'igx-query-builder',
31
    templateUrl: './query-builder.component.html',
32
    imports: [IgxQueryBuilderHeaderComponent, IgxQueryBuilderTreeComponent]
33
})
34
export class IgxQueryBuilderComponent implements OnDestroy {
2✔
35
    /**
36
     * @hidden @internal
37
     */
38
    @HostBinding('class.igx-query-builder')
39
    public cssClass = 'igx-query-builder';
135✔
40

41
    /**
42
     * @hidden @internal
43
     */
44
    @HostBinding('style.display')
45
    public display = 'block';
135✔
46

47
    /**
48
     * Gets/sets whether the confirmation dialog should be shown when changing entity.
49
     * Default value is `true`.
50
     */
51
    @Input({ transform: booleanAttribute })
52
    public showEntityChangeDialog = true;
135✔
53

54
    /**
55
     * Returns the entities.
56
     * @hidden
57
     */
58
    public get entities(): EntityType[] {
59
        return this._entities;
2,562✔
60
    }
61

62
    /**
63
     * Sets the entities.
64
     * @hidden
65
     */
66
    @Input()
67
    public set entities(entities: EntityType[]) {
68
        if (entities !== this._entities) {
359✔
69
            if (entities && this.expressionTree) {
359✔
70
                this._expressionTree = recreateTree(this._expressionTree, entities);
63✔
71
            }
72
        }
73
        this._entities = entities;
359✔
74
    }
75

76
    /**
77
     * Returns the fields.
78
     * @hidden
79
     * @deprecated in version 19.1.0. Use the `entities` property instead.
80
     */
81
    public get fields(): FieldType[] {
UNCOV
82
        return this._fields;
×
83
    }
84

85
    /**
86
     * Sets the fields.
87
     * @hidden
88
     * @deprecated in version 19.1.0. Use the `entities` property instead.
89
     */
90
    @Input()
91
    public set fields(fields: FieldType[]) {
NEW
92
        if (fields) {
×
NEW
93
            this._fields = fields;
×
NEW
94
            this.entities = [
×
95
                {
96
                    name: null,
97
                    fields: fields
98
                }
99
            ];
100
        }
101
    }
102

103
    /**
104
    * Returns the expression tree.
105
    */
106
    public get expressionTree(): IExpressionTree {
107
        return this._expressionTree;
2,578✔
108
    }
109

110
    /**
111
     * Sets the expression tree.
112
     */
113
    @Input()
114
    public set expressionTree(expressionTree: IExpressionTree) {
115
        if (expressionTree !== this._expressionTree) {
117✔
116
            if (this.entities && expressionTree) {
80✔
117
                this._expressionTree = recreateTree(expressionTree, this.entities);
74✔
118
            } else {
119
                this._expressionTree = expressionTree;
6✔
120
            }
121
        }
122
    }
123

124
    /**
125
     * Gets the `locale` of the query builder.
126
     * If not set, defaults to application's locale.
127
     */
128
    @Input()
129
    public locale: string;
130

131
    /**
132
     * Sets the resource strings.
133
     * By default it uses EN resources.
134
     */
135
    @Input()
136
    public set resourceStrings(value: IQueryBuilderResourceStrings) {
137
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
1✔
138
    }
139

140
    /**
141
     * Returns the resource strings.
142
     */
143
    public get resourceStrings(): IQueryBuilderResourceStrings {
144
        return this._resourceStrings;
3,832✔
145
    }
146

147
    /**
148
     * Disables subsequent entity changes at the root level after the initial selection.
149
     */
150
    @Input()
151
    public disableEntityChange = false;
135✔
152

153
    /**
154
     * Event fired as the expression tree is changed.
155
     *
156
     * ```html
157
     *  <igx-query-builder (expressionTreeChange)='onExpressionTreeChange()'></igx-query-builder>
158
     * ```
159
     */
160
    @Output()
161
    public expressionTreeChange = new EventEmitter();
135✔
162

163
    /**
164
     * @hidden @internal
165
     */
166
    @ContentChild(IgxQueryBuilderHeaderComponent)
167
    public headerContent: IgxQueryBuilderHeaderComponent;
168

169
    /**
170
     * @hidden @internal
171
     */
172
    @ContentChild(IgxQueryBuilderSearchValueTemplateDirective, { read: TemplateRef })
173
    public searchValueTemplate: TemplateRef<any>;
174

175
    /**
176
     * @hidden @internal
177
     */
178
    @ViewChild(IgxQueryBuilderTreeComponent)
179
    public queryTree: IgxQueryBuilderTreeComponent;
180

181
    private destroy$ = new Subject<any>();
135✔
182
    private _resourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN);
135✔
183
    private _expressionTree: IExpressionTree;
184
    private _fields: FieldType[];
185
    private _entities: EntityType[];
186
    private _shouldEmitTreeChange = true;
135✔
187

188
    constructor(protected iconService: IgxIconService) {
135✔
189
        this.registerSVGIcons();
135✔
190
    }
191

192
    /**
193
     * Returns whether the expression tree can be committed in the current state.
194
     */
195
    public canCommit(): boolean {
196
        return this.queryTree?.canCommitCurrentState() === true;
28✔
197
    }
198

199
    /**
200
     * Commits the expression tree in the current state if it is valid. If not throws an exception.
201
     */
202
    public commit(): void {
203
        if (this.canCommit()) {
3✔
204
            this._shouldEmitTreeChange = false;
2✔
205
            this.queryTree.commitCurrentState();
2✔
206
            this._shouldEmitTreeChange = true;
2✔
207
        } else {
208
            throw new Error('Expression tree can\'t be committed in the current state. Use `canCommit` method to check if the current state is valid.');
1✔
209
        }
210
    }
211

212
    /**
213
     * Discards all unsaved changes to the expression tree.
214
     */
215
    public discard(): void {
216
        this.queryTree.cancelOperandEdit();
5✔
217
    }
218

219
    /**
220
     * @hidden @internal
221
     */
222
    public ngOnDestroy(): void {
223
        this.destroy$.next(true);
135✔
224
        this.destroy$.complete();
135✔
225
    }
226

227
    /**
228
     * @hidden @internal
229
     *
230
     * used by the grid
231
     */
232
    public setPickerOutlet(outlet?: IgxOverlayOutletDirective | ElementRef) {
233
        this.queryTree.setPickerOutlet(outlet);
44✔
234
    }
235

236
    /**
237
     * @hidden @internal
238
     *
239
     * used by the grid
240
     */
241
    public get isContextMenuVisible(): boolean {
NEW
242
        return this.queryTree.isContextMenuVisible;
×
243
    }
244

245
    /**
246
     * @hidden @internal
247
     *
248
     * used by the grid
249
     */
250
    public exitOperandEdit() {
251
        this.queryTree.exitOperandEdit();
19✔
252
    }
253

254
    /**
255
     * @hidden @internal
256
     *
257
     * used by the grid
258
     */
259
    public setAddButtonFocus() {
260
        this.queryTree.setAddButtonFocus();
39✔
261
    }
262

263
    public onExpressionTreeChange(tree: IExpressionTree) {
264
        if (tree && this.entities && tree !== this._expressionTree) {
120✔
265
            this._expressionTree = recreateTree(tree, this.entities);
109✔
266
        } else {
267
            this._expressionTree = tree;
11✔
268
        }
269
        if (this._shouldEmitTreeChange) {
120✔
270
            this.expressionTreeChange.emit();
118✔
271
        }
272
    }
273

274
    private registerSVGIcons(): void {
275
        const editorIcons = editor as any[];
135✔
276

277
        editorIcons.forEach((icon) => {
135✔
278
            this.iconService.addSvgIconFromText(icon.name, icon.value, 'imx-icons');
8,100✔
279
            this.iconService.addIconRef(icon.name, 'default', {
8,100✔
280
                name: icon.name,
281
                family: 'imx-icons'
282
            });
283
        });
284

285
        const inIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M560-280H120v-400h720v120h-80v-40H200v240h360v80Zm-360-80v-240 240Zm560 200v-120H640v-80h120v-120h80v120h120v80H840v120h-80Z"/></svg>';
135✔
286
        this.iconService.addSvgIconFromText('in', inIcon, 'imx-icons');
135✔
287
        this.iconService.addIconRef('in', 'default', {
135✔
288
            name: 'in',
289
            family: 'imx-icons'
290
        });
291

292
        const notInIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M560-280H120v-400h720v120h-80v-40H200v240h360v80Zm-360-80v-240 240Zm440 104 84-84-84-84 56-56 84 84 84-84 56 56-83 84 83 84-56 56-84-83-84 83-56-56Z"/></svg>';
135✔
293
        this.iconService.addSvgIconFromText('not-in', notInIcon, 'imx-icons');
135✔
294
        this.iconService.addIconRef('not-in', 'default', {
135✔
295
            name: 'not-in',
296
            family: 'imx-icons'
297
        });
298

299
        this.iconService.addIconRef('add', 'default', {
135✔
300
            name: 'add',
301
            family: 'material',
302
        });
303

304
        this.iconService.addIconRef('close', 'default', {
135✔
305
            name: 'close',
306
            family: 'material',
307
        });
308

309
        this.iconService.addIconRef('check', 'default', {
135✔
310
            name: 'check',
311
            family: 'material',
312
        });
313

314
        this.iconService.addIconRef('delete', 'default', {
135✔
315
            name: 'delete',
316
            family: 'material',
317
        });
318

319
        this.iconService.addIconRef('edit', 'default', {
135✔
320
            name: 'edit',
321
            family: 'material',
322
        });
323
    }
324
}
325

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