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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

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

14.74
/projects/igniteui-angular/src/lib/grids/filtering/grid-filtering.service.ts
1
import {
2
    Injectable,
3
    OnDestroy,
4
} from '@angular/core';
5
import { FilteringExpressionsTree, IFilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree';
6
import { IFilteringExpression, FilteringLogic } from '../../data-operations/filtering-expression.interface';
7
import { Subject } from 'rxjs';
8
import { takeUntil, first } from 'rxjs/operators';
9
import { IForOfState } from '../../directives/for-of/for_of.directive';
10
import { IFilteringOperation } from '../../data-operations/filtering-condition';
11
import { IColumnResizeEventArgs, IFilteringEventArgs } from '../common/events';
12
import { OverlayCancelableEventArgs, OverlaySettings, VerticalAlignment } from '../../services/overlay/utilities';
13
import { IgxOverlayService } from '../../services/overlay/overlay';
14
import { useAnimation } from '@angular/animations';
15
import { AbsoluteScrollStrategy } from '../../services/overlay/scroll/absolute-scroll-strategy';
16
import { IgxIconService } from '../../icon/icon.service';
17
import { editor, pinLeft, unpinLeft } from '@igniteui/material-icons-extended';
18
import { ExpressionUI, generateExpressionsList } from './excel-style/common';
19
import { ColumnType, GridType } from '../common/grid.interface';
20
import { formatDate } from '../../core/utils';
21
import { ExcelStylePositionStrategy } from './excel-style/excel-style-position-strategy';
22
import { fadeIn } from 'igniteui-angular/animations';
23
import { ExpressionsTreeUtil, isTree } from '../../data-operations/expressions-tree-util';
24

25
/**
26
 * @hidden
27
 */
28
@Injectable()
29
export class IgxFilteringService implements OnDestroy {
2✔
30
    public isFilterRowVisible = false;
39✔
31
    public filteredColumn: ColumnType = null;
39✔
32
    public selectedExpression: IFilteringExpression = null;
39✔
33
    public columnToMoreIconHidden = new Map<string, boolean>();
39✔
34
    public activeFilterCell = 0;
39✔
35
    public grid: GridType;
36

37
    private columnsWithComplexFilter = new Set<string>();
39✔
38
    private areEventsSubscribed = false;
39✔
39
    protected destroy$ = new Subject<boolean>();
39✔
40
    private isFiltering = false;
39✔
41
    private columnToExpressionsMap = new Map<string, ExpressionUI[]>();
39✔
42
    private columnStartIndex = -1;
39✔
43
    protected _filterMenuOverlaySettings: OverlaySettings = {
39✔
44
        closeOnEscape: true,
45
        closeOnOutsideClick: true,
46
        modal: false,
47
        positionStrategy: new ExcelStylePositionStrategy({
48
            verticalStartPoint: VerticalAlignment.Bottom,
49
            openAnimation: useAnimation(fadeIn, { params: { duration: '250ms' } }),
50
            closeAnimation: null
51
        }),
52
        scrollStrategy: new AbsoluteScrollStrategy()
53
    };
54
    protected lastActiveNode;
55

56
    constructor(
57
        private iconService: IgxIconService,
39✔
58
        protected _overlayService: IgxOverlayService,
39✔
59
    ) { }
60

61
    public ngOnDestroy(): void {
62
        this.destroy$.next(true);
21✔
63
        this.destroy$.complete();
21✔
64
    }
65

66
    public toggleFilterDropdown(element: HTMLElement, column: ColumnType) {
67

UNCOV
68
        const filterIcon = column.filteringExpressionsTree ? 'igx-excel-filter__icon--filtered' : 'igx-excel-filter__icon';
×
UNCOV
69
        const filterIconTarget = element.querySelector(`.${filterIcon}`) as HTMLElement || element;
×
70

UNCOV
71
        const id = this.grid.createFilterDropdown(column, {
×
72
            ...this._filterMenuOverlaySettings,
73
            ...{ target: filterIconTarget }
74
        });
75

UNCOV
76
        this._overlayService.opening
×
77
            .pipe(
UNCOV
78
                first(overlay => overlay.id === id),
×
79
                takeUntil(this.destroy$)
80
            )
81
            .subscribe((event: OverlayCancelableEventArgs) => {
UNCOV
82
                if (event.componentRef) {
×
UNCOV
83
                    event.componentRef.instance.initialize(column, this._overlayService);
×
UNCOV
84
                    event.componentRef.instance.overlayComponentId = id;
×
85
                }
UNCOV
86
                this.lastActiveNode = this.grid.navigation.activeNode;
×
87
            });
88

UNCOV
89
        this._overlayService.closed
×
90
            .pipe(
UNCOV
91
                first(overlay => overlay.id === id),
×
92
                takeUntil(this.destroy$)
93
            )
94
            .subscribe(() => {
UNCOV
95
                this._overlayService.detach(id);
×
UNCOV
96
                this.grid.navigation.activeNode = this.lastActiveNode;
×
UNCOV
97
                this.grid.theadRow.nativeElement.focus();
×
98
            });
99

UNCOV
100
        this._overlayService.show(id);
×
101
    }
102

103
    /**
104
     * Subscribe to grid's events.
105
     */
106
    public subscribeToEvents() {
UNCOV
107
        if (!this.areEventsSubscribed) {
×
UNCOV
108
            this.areEventsSubscribed = true;
×
109

UNCOV
110
            this.grid.columnResized.pipe(takeUntil(this.destroy$)).subscribe((eventArgs: IColumnResizeEventArgs) => {
×
UNCOV
111
                this.updateFilteringCell(eventArgs.column);
×
112
            });
113

UNCOV
114
            this.grid.parentVirtDir.chunkLoad.pipe(takeUntil(this.destroy$)).subscribe((eventArgs: IForOfState) => {
×
UNCOV
115
                if (eventArgs.startIndex !== this.columnStartIndex) {
×
UNCOV
116
                    this.columnStartIndex = eventArgs.startIndex;
×
UNCOV
117
                    this.grid.filterCellList.forEach((filterCell) => {
×
UNCOV
118
                        filterCell.updateFilterCellArea();
×
119
                    });
120
                }
121
            });
122

UNCOV
123
            this.grid.columnMovingEnd.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
124
                this.grid.filterCellList.forEach((filterCell) => {
×
UNCOV
125
                    filterCell.updateFilterCellArea();
×
126
                });
127
            });
128
        }
129
    }
130

131
    /**
132
     * Close filtering row if a column is hidden.
133
     */
134
    public hideFilteringRowOnColumnVisibilityChange(col: ColumnType) {
UNCOV
135
        const filteringRow = this.grid?.filteringRow;
×
136

UNCOV
137
        if (filteringRow && filteringRow.column && filteringRow.column === col) {
×
UNCOV
138
            filteringRow.close();
×
139
        }
140
    }
141

142
    /**
143
     * Internal method to create expressionsTree and filter grid used in both filter modes.
144
     */
145
    public filterInternal(field: string, expressions: FilteringExpressionsTree | Array<ExpressionUI> = null): void {
×
UNCOV
146
        this.isFiltering = true;
×
147

148
        let expressionsTree;
NEW
149
        if (expressions && 'operator' in expressions) {
×
UNCOV
150
            expressionsTree = expressions;
×
151
        } else {
UNCOV
152
            expressionsTree = this.createSimpleFilteringTree(field, expressions);
×
153
        }
154

UNCOV
155
        if (expressionsTree.filteringOperands.length === 0) {
×
UNCOV
156
            this.clearFilter(field);
×
157
        } else {
UNCOV
158
            this.filter(field, null, expressionsTree);
×
159
        }
160

UNCOV
161
        this.isFiltering = false;
×
162
    }
163

164
    /**
165
     * Execute filtering on the grid.
166
     */
167
    public filter(field: string, value: any, conditionOrExpressionTree?: IFilteringOperation | IFilteringExpressionsTree,
168
        ignoreCase?: boolean) {
169

UNCOV
170
        const grid = this.grid;
×
171

UNCOV
172
        const col = grid.getColumnByName(field);
×
UNCOV
173
        const filteringIgnoreCase = ignoreCase || (col ? col.filteringIgnoreCase : false);
×
174

UNCOV
175
        const filteringTree = grid.filteringExpressionsTree;
×
UNCOV
176
        const columnFilteringExpressionsTree = ExpressionsTreeUtil.find(filteringTree, field) as IFilteringExpressionsTree;
×
UNCOV
177
        conditionOrExpressionTree = conditionOrExpressionTree ?? columnFilteringExpressionsTree;
×
UNCOV
178
        const fieldFilterIndex = ExpressionsTreeUtil.findIndex(filteringTree, field);
×
179

180
        const newFilteringTree: FilteringExpressionsTree =
UNCOV
181
            this.prepare_filtering_expression(filteringTree, field, value, conditionOrExpressionTree,
×
182
                filteringIgnoreCase, fieldFilterIndex, true);
183

UNCOV
184
        const eventArgs: IFilteringEventArgs = {
×
185
            owner: grid,
186
            filteringExpressions: ExpressionsTreeUtil.find(newFilteringTree, field) as FilteringExpressionsTree, cancel: false
187
        };
UNCOV
188
        this.grid.filtering.emit(eventArgs);
×
189

UNCOV
190
        if (eventArgs.cancel) {
×
191
            return;
×
192
        }
193

UNCOV
194
        if (conditionOrExpressionTree) {
×
UNCOV
195
            this.filter_internal(field, value, conditionOrExpressionTree, filteringIgnoreCase);
×
196
        } else {
UNCOV
197
            const expressionsTreeForColumn = ExpressionsTreeUtil.find(this.grid.filteringExpressionsTree, field);
×
UNCOV
198
            if (!expressionsTreeForColumn) {
×
UNCOV
199
                throw new Error('Invalid condition or Expression Tree!');
×
NEW
200
            } else if (isTree(expressionsTreeForColumn)) {
×
201
                this.filter_internal(field, value, expressionsTreeForColumn, filteringIgnoreCase);
×
202
            } else {
NEW
203
                this.filter_internal(field, value, expressionsTreeForColumn.condition, filteringIgnoreCase);
×
204
            }
205
        }
UNCOV
206
        const doneEventArgs = ExpressionsTreeUtil.find(this.grid.filteringExpressionsTree, field) as FilteringExpressionsTree;
×
207
        // Wait for the change detection to update filtered data through the pipes and then emit the event.
UNCOV
208
        requestAnimationFrame(() => this.grid.filteringDone.emit(doneEventArgs));
×
209
    }
210

211
    public filter_global(term, condition, ignoreCase) {
212
        if (!condition) {
×
213
            return;
×
214
        }
215

216
        const filteringTree = this.grid.filteringExpressionsTree;
×
217
        this.grid.crudService.endEdit(false);
×
218
        this.grid.page = 0;
×
219

220
        filteringTree.filteringOperands = [];
×
221
        for (const column of this.grid.columns) {
×
222
            this.prepare_filtering_expression(filteringTree, column.field, term,
×
223
                condition, ignoreCase || column.filteringIgnoreCase);
×
224
        }
225

226
        this.grid.filteringExpressionsTree = filteringTree;
×
227
    }
228

229
    /**
230
     * Clears the filter of a given column if name is provided. Otherwise clears the filters of all columns.
231
     */
232
    public clearFilter(field: string): void {
UNCOV
233
        if (field) {
×
UNCOV
234
            const column = this.grid.getColumnByName(field);
×
UNCOV
235
            if (!column) {
×
UNCOV
236
                return;
×
237
            }
238
        }
239

UNCOV
240
        const emptyFilter = new FilteringExpressionsTree(null, field);
×
UNCOV
241
        const onFilteringEventArgs: IFilteringEventArgs = {
×
242
            owner: this.grid,
243
            filteringExpressions: emptyFilter,
244
            cancel: false
245
        };
246

UNCOV
247
        this.grid.filtering.emit(onFilteringEventArgs);
×
248

UNCOV
249
        if (onFilteringEventArgs.cancel) {
×
250
            return;
×
251
        }
252

UNCOV
253
        this.isFiltering = true;
×
UNCOV
254
        this.clear_filter(field);
×
255

256
        // Wait for the change detection to update filtered data through the pipes and then emit the event.
UNCOV
257
        requestAnimationFrame(() => this.grid.filteringDone.emit(emptyFilter));
×
258

UNCOV
259
        if (field) {
×
UNCOV
260
            const expressions = this.getExpressions(field);
×
UNCOV
261
            expressions.length = 0;
×
262
        } else {
UNCOV
263
            this.grid.columns.forEach(c => {
×
UNCOV
264
                const expressions = this.getExpressions(c.field);
×
UNCOV
265
                expressions.length = 0;
×
266
            });
267
        }
268

UNCOV
269
        this.isFiltering = false;
×
270
    }
271

272
    public clear_filter(fieldName: string) {
273
        const grid = this.grid;
6✔
274
        grid.crudService.endEdit(false);
6✔
275
        const filteringState = grid.filteringExpressionsTree;
6✔
276
        const index = ExpressionsTreeUtil.findIndex(filteringState, fieldName);
6✔
277

278
        if (index > -1) {
6!
UNCOV
279
            filteringState.filteringOperands.splice(index, 1);
×
280
        } else if (!fieldName) {
6!
UNCOV
281
            filteringState.filteringOperands = [];
×
282
        }
283

284
        grid.filteringExpressionsTree = filteringState;
6✔
285
    }
286

287
    /**
288
     * Filters all the `IgxColumnComponent` in the `IgxGridComponent` with the same condition.
289
     * @deprecated in version 19.0.0.
290
     */
291
    public filterGlobal(value: any, condition, ignoreCase?) {
UNCOV
292
        if (!condition) {
×
UNCOV
293
            return;
×
294
        }
295

UNCOV
296
        const filteringTree = this.grid.filteringExpressionsTree;
×
UNCOV
297
        const newFilteringTree = new FilteringExpressionsTree(filteringTree.operator, filteringTree.fieldName);
×
298

UNCOV
299
        for (const column of this.grid.columns) {
×
UNCOV
300
            this.prepare_filtering_expression(newFilteringTree, column.field, value, condition,
×
301
                ignoreCase || column.filteringIgnoreCase);
×
302
        }
303

UNCOV
304
        const eventArgs: IFilteringEventArgs = { owner: this.grid, filteringExpressions: newFilteringTree, cancel: false };
×
UNCOV
305
        this.grid.filtering.emit(eventArgs);
×
UNCOV
306
        if (eventArgs.cancel) {
×
307
            return;
×
308
        }
309

UNCOV
310
        this.grid.crudService.endEdit(false);
×
UNCOV
311
        this.grid.page = 0;
×
UNCOV
312
        this.grid.filteringExpressionsTree = newFilteringTree;
×
313

314
        // Wait for the change detection to update filtered data through the pipes and then emit the event.
UNCOV
315
        requestAnimationFrame(() => this.grid.filteringDone.emit(this.grid.filteringExpressionsTree));
×
316
    }
317

318
    /**
319
     * Register filtering SVG icons in the icon service.
320
     */
321
    public registerSVGIcons(): void {
322
        const editorIcons = editor as any[];
76✔
323
        editorIcons.forEach(icon => {
76✔
324
            this.iconService.addSvgIconFromText(icon.name, icon.value, 'imx-icons');
4,560✔
325
        });
326
        this.iconService.addSvgIconFromText(pinLeft.name, pinLeft.value, 'imx-icons');
76✔
327
        this.iconService.addSvgIconFromText(unpinLeft.name, unpinLeft.value, 'imx-icons');
76✔
328
    }
329

330
    /**
331
     * Returns the ExpressionUI array for a given column.
332
     */
333
    public getExpressions(columnId: string): ExpressionUI[] {
UNCOV
334
        if (!this.columnToExpressionsMap.has(columnId)) {
×
UNCOV
335
            const column = this.grid.columns.find((col) => col.field === columnId);
×
UNCOV
336
            const expressionUIs = new Array<ExpressionUI>();
×
UNCOV
337
            if (column) {
×
UNCOV
338
                this.generateExpressionsList(column.filteringExpressionsTree, this.grid.filteringExpressionsTree.operator, expressionUIs);
×
UNCOV
339
                this.columnToExpressionsMap.set(columnId, expressionUIs);
×
340
            }
UNCOV
341
            return expressionUIs;
×
342
        }
343

UNCOV
344
        return this.columnToExpressionsMap.get(columnId);
×
345
    }
346

347
    /**
348
     * Recreates all ExpressionUIs for all columns. Executed after filtering to refresh the cache.
349
     */
350
    public refreshExpressions() {
351
        if (!this.isFiltering) {
6✔
352
            this.columnsWithComplexFilter.clear();
6✔
353

354
            this.columnToExpressionsMap.forEach((value: ExpressionUI[], key: string) => {
6✔
UNCOV
355
                const column = this.grid.columns.find((col) => col.field === key);
×
UNCOV
356
                if (column) {
×
UNCOV
357
                    value.length = 0;
×
358

UNCOV
359
                    this.generateExpressionsList(column.filteringExpressionsTree, this.grid.filteringExpressionsTree.operator, value);
×
360

UNCOV
361
                    const isComplex = this.isFilteringTreeComplex(column.filteringExpressionsTree);
×
UNCOV
362
                    if (isComplex) {
×
363
                        this.columnsWithComplexFilter.add(key);
×
364
                    }
365

UNCOV
366
                    this.updateFilteringCell(column);
×
367
                } else {
UNCOV
368
                    this.columnToExpressionsMap.delete(key);
×
369
                }
370
            });
371
        }
372
    }
373

374
    /**
375
     * Remove an ExpressionUI for a given column.
376
     */
377
    public removeExpression(columnId: string, indexToRemove: number) {
UNCOV
378
        const expressionsList = this.getExpressions(columnId);
×
379

UNCOV
380
        if (indexToRemove === 0 && expressionsList.length > 1) {
×
UNCOV
381
            expressionsList[1].beforeOperator = null;
×
UNCOV
382
        } else if (indexToRemove === expressionsList.length - 1) {
×
UNCOV
383
            expressionsList[indexToRemove - 1].afterOperator = null;
×
384
        } else {
UNCOV
385
            expressionsList[indexToRemove - 1].afterOperator = expressionsList[indexToRemove + 1].beforeOperator;
×
UNCOV
386
            expressionsList[0].beforeOperator = null;
×
UNCOV
387
            expressionsList[expressionsList.length - 1].afterOperator = null;
×
388
        }
389

UNCOV
390
        expressionsList.splice(indexToRemove, 1);
×
391
    }
392

393
    /**
394
     * Generate filtering tree for a given column from existing ExpressionUIs.
395
     */
396
    public createSimpleFilteringTree(columnId: string, expressionUIList = null): FilteringExpressionsTree {
×
UNCOV
397
        const expressionsList = expressionUIList ? expressionUIList : this.getExpressions(columnId);
×
UNCOV
398
        const expressionsTree = new FilteringExpressionsTree(FilteringLogic.Or, columnId);
×
399
        let currAndBranch: FilteringExpressionsTree;
400

UNCOV
401
        for (const currExpressionUI of expressionsList) {
×
UNCOV
402
            if (!currExpressionUI.expression.condition.isUnary && currExpressionUI.expression.searchVal === null) {
×
403
                if (currExpressionUI.afterOperator === FilteringLogic.And && !currAndBranch) {
×
404
                    currAndBranch = new FilteringExpressionsTree(FilteringLogic.And, columnId);
×
405
                    expressionsTree.filteringOperands.push(currAndBranch);
×
406
                }
407
                continue;
×
408
            }
409

UNCOV
410
            if ((currExpressionUI.beforeOperator === undefined || currExpressionUI.beforeOperator === null ||
×
411
                currExpressionUI.beforeOperator === FilteringLogic.Or) &&
412
                currExpressionUI.afterOperator === FilteringLogic.And) {
413

UNCOV
414
                currAndBranch = new FilteringExpressionsTree(FilteringLogic.And, columnId);
×
UNCOV
415
                expressionsTree.filteringOperands.push(currAndBranch);
×
UNCOV
416
                currAndBranch.filteringOperands.push(currExpressionUI.expression);
×
417

UNCOV
418
            } else if (currExpressionUI.beforeOperator === FilteringLogic.And) {
×
UNCOV
419
                currAndBranch.filteringOperands.push(currExpressionUI.expression);
×
420
            } else {
UNCOV
421
                expressionsTree.filteringOperands.push(currExpressionUI.expression);
×
UNCOV
422
                currAndBranch = null;
×
423
            }
424
        }
425

UNCOV
426
        return expressionsTree;
×
427
    }
428

429
    /**
430
     * Returns whether a complex filter is applied to a given column.
431
     */
432
    public isFilterComplex(columnId: string) {
UNCOV
433
        if (this.columnsWithComplexFilter.has(columnId)) {
×
434
            return true;
×
435
        }
436

UNCOV
437
        const column = this.grid.columns.find((col) => col.field === columnId);
×
UNCOV
438
        const isComplex = column && this.isFilteringTreeComplex(column.filteringExpressionsTree);
×
UNCOV
439
        if (isComplex) {
×
440
            this.columnsWithComplexFilter.add(columnId);
×
441
        }
442

UNCOV
443
        return isComplex;
×
444
    }
445

446
    /**
447
     * Returns the string representation of the FilteringLogic operator.
448
     */
449
    public getOperatorAsString(operator: FilteringLogic): any {
UNCOV
450
        if (operator === 0) {
×
UNCOV
451
            return this.grid.resourceStrings.igx_grid_filter_operator_and;
×
452
        } else {
UNCOV
453
            return this.grid.resourceStrings.igx_grid_filter_operator_or;
×
454
        }
455
    }
456

457
    /**
458
     * Generate the label of a chip from a given filtering expression.
459
     */
460
    public getChipLabel(expression: IFilteringExpression): any {
UNCOV
461
        if (expression.condition.isUnary) {
×
UNCOV
462
            return this.grid.resourceStrings[`igx_grid_filter_${expression.condition.name}`] || expression.condition.name;
×
UNCOV
463
        } else if (expression.searchVal instanceof Date) {
×
UNCOV
464
            const column = this.grid.getColumnByName(expression.fieldName);
×
UNCOV
465
            const formatter = column.formatter;
×
UNCOV
466
            if (formatter) {
×
467
                return formatter(expression.searchVal, undefined);
×
468
            }
UNCOV
469
            const pipeArgs = column.pipeArgs;
×
UNCOV
470
            return formatDate(expression.searchVal, pipeArgs.format, this.grid.locale);
×
471
        } else {
UNCOV
472
            return expression.searchVal;
×
473
        }
474
    }
475

476
    /**
477
     * Updates the content of a filterCell.
478
     */
479
    public updateFilteringCell(column: ColumnType) {
UNCOV
480
        const filterCell = column.filterCell;
×
UNCOV
481
        if (filterCell) {
×
UNCOV
482
            filterCell.updateFilterCellArea();
×
483
        }
484
    }
485

486
    public generateExpressionsList(expressions: IFilteringExpressionsTree | IFilteringExpression,
487
        operator: FilteringLogic,
488
        expressionsUIs: ExpressionUI[]): void {
UNCOV
489
        generateExpressionsList(expressions, operator, expressionsUIs);
×
490
    }
491

492
    public isFilteringExpressionsTreeEmpty(expressionTree: IFilteringExpressionsTree): boolean {
493
        if (FilteringExpressionsTree.empty(expressionTree)) {
80✔
494
            return true;
53✔
495
        }
496

497
        for (const expr of expressionTree.filteringOperands) {
27✔
498
            if (isTree(expr)) {
27!
NEW
499
                if (expr.filteringOperands && expr.filteringOperands.length) {
×
UNCOV
500
                    return false;
×
501
                }
502
            } else {
503
                return false;
27✔
504
            }
505
        }
506
        return true;
×
507
    }
508

509
    protected filter_internal(fieldName: string, term, conditionOrExpressionsTree: IFilteringOperation | IFilteringExpressionsTree,
510
        ignoreCase: boolean) {
UNCOV
511
        const filteringTree = this.grid.filteringExpressionsTree;
×
UNCOV
512
        this.grid.crudService.endEdit(false);
×
UNCOV
513
        this.grid.page = 0;
×
514

UNCOV
515
        const fieldFilterIndex = ExpressionsTreeUtil.findIndex(filteringTree, fieldName);
×
UNCOV
516
        this.prepare_filtering_expression(filteringTree, fieldName, term, conditionOrExpressionsTree, ignoreCase, fieldFilterIndex);
×
UNCOV
517
        this.grid.filteringExpressionsTree = filteringTree;
×
518
    }
519

520
    /** Modifies the filteringState object to contain the newly added filtering conditions/expressions.
521
     * If createNewTree is true, filteringState will not be modified (because it directly affects the grid.filteringExpressionsTree),
522
     * but a new object is created and returned.
523
     */
524
    protected prepare_filtering_expression(
525
        filteringState: IFilteringExpressionsTree,
526
        fieldName: string,
527
        searchVal,
528
        conditionOrExpressionsTree: IFilteringOperation | IFilteringExpressionsTree,
529
        ignoreCase: boolean,
530
        insertAtIndex = -1,
×
531
        createNewTree = false): FilteringExpressionsTree {
×
532

NEW
533
        let expressionsTree = conditionOrExpressionsTree && 'operator' in conditionOrExpressionsTree ?
×
534
            conditionOrExpressionsTree : null;
NEW
535
        const condition = conditionOrExpressionsTree && 'operator' in conditionOrExpressionsTree ?
×
536
            null : conditionOrExpressionsTree as IFilteringOperation;
537

UNCOV
538
        let newExpressionsTree = filteringState as FilteringExpressionsTree;
×
539

UNCOV
540
        if (createNewTree) {
×
UNCOV
541
            newExpressionsTree = new FilteringExpressionsTree(filteringState.operator, filteringState.fieldName);
×
UNCOV
542
            newExpressionsTree.filteringOperands = [...filteringState.filteringOperands];
×
543
        }
544

UNCOV
545
        if (condition) {
×
NEW
546
            const newExpression: IFilteringExpression = { fieldName: fieldName, searchVal, condition, conditionName: condition.name, ignoreCase };
×
UNCOV
547
            expressionsTree = new FilteringExpressionsTree(filteringState.operator, fieldName);
×
UNCOV
548
            expressionsTree.filteringOperands.push(newExpression);
×
549
        }
550

UNCOV
551
        if (expressionsTree) {
×
UNCOV
552
            if (insertAtIndex > -1) {
×
UNCOV
553
                newExpressionsTree.filteringOperands[insertAtIndex] = expressionsTree;
×
554
            } else {
UNCOV
555
                newExpressionsTree.filteringOperands.push(expressionsTree);
×
556
            }
557
        }
558

UNCOV
559
        return newExpressionsTree;
×
560
    }
561

562

563
    private isFilteringTreeComplex(expressions: IFilteringExpressionsTree | IFilteringExpression): boolean {
UNCOV
564
        if (!expressions) {
×
UNCOV
565
            return false;
×
566
        }
567

NEW
568
        if (isTree(expressions)) {
×
NEW
569
            if (expressions.operator === FilteringLogic.Or) {
×
NEW
570
                const andOperatorsCount = this.getChildAndOperatorsCount(expressions);
×
571

572
                // having more than one 'And' operator in the sub-tree means that the filter could not be represented without parentheses.
UNCOV
573
                return andOperatorsCount > 1;
×
574
            }
575

UNCOV
576
            let isComplex = false;
×
NEW
577
            for (const operand of expressions.filteringOperands) {
×
UNCOV
578
                isComplex = isComplex || this.isFilteringTreeComplex(operand);
×
579
            }
580

UNCOV
581
            return isComplex;
×
582
        }
583

UNCOV
584
        return false;
×
585
    }
586

587
    private getChildAndOperatorsCount(expressions: IFilteringExpressionsTree): number {
UNCOV
588
        let count = 0;
×
589
        let operand;
UNCOV
590
        for (let i = 0; i < expressions.filteringOperands.length; i++) {
×
UNCOV
591
            operand = expressions[i];
×
NEW
592
            if (operand && isTree(operand)) {
×
593
                if (operand.operator === FilteringLogic.And) {
×
594
                    count++;
×
595
                }
596

NEW
597
                count = count + this.getChildAndOperatorsCount(operand as IFilteringExpressionsTree);
×
598
            }
599
        }
600

UNCOV
601
        return count;
×
602
    }
603
}
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