Skip to content

Commit 9831544

Browse files
committed
feat(filtering): add advanced filtering context menu #5496
1 parent 1ebf0ea commit 9831544

File tree

2 files changed

+218
-13
lines changed

2 files changed

+218
-13
lines changed

Diff for: projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.html

+50-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ <h4 class="igx-typography__h6">
4848

4949
<ng-template #filterOperandTemplate let-expressionItem>
5050
<span *ngIf="!expressionItem.inEditMode">
51-
<igx-chip [removable]="true"
51+
<igx-chip [data]="expressionItem"
52+
[removable]="true"
5253
[selected]="expressionItem.selected"
5354
(click)="onChipClick(expressionItem)"
5455
(dblclick)="onChipDblClick(expressionItem)"
@@ -125,7 +126,8 @@ <h4 class="igx-typography__h6">
125126
<ng-template #expressionTreeTemplate let-expressionItem>
126127
<div style="display: flex;">
127128
<div tabindex="0" style="width: 10px; margin-right: 10px;"
128-
[style.background-color]="expressionItem.operator === 0 ? 'lightblue' : 'lightcoral'"></div>
129+
[style.background-color]="expressionItem.operator === 0 ? expressionItem.selected ? 'blue' : 'lightblue' : expressionItem.selected ? 'red' : 'lightcoral'"
130+
(click)="onGroupClick(expressionItem)"></div>
129131
<div style="display: flex; flex-direction: column; align-items: flex-start;">
130132
<ng-container *ngFor="let expr of expressionItem.children">
131133
<ng-container *ngTemplateOutlet="isExpressionGroup(expr) ? expressionTreeTemplate : filterOperandTemplate; context: context(expr)"></ng-container>
@@ -147,6 +149,52 @@ <h4 class="igx-typography__h6">
147149
<ng-container *ngIf="rootGroup">
148150
<ng-container *ngTemplateOutlet="expressionTreeTemplate; context: context(rootGroup)"></ng-container>
149151
</ng-container>
152+
153+
<div igxToggle
154+
style="display: flex; flex-flow: column; width: 200px; background-color: black; margin-left: 20px">
155+
<ng-container *ngIf="selectedGroups.length === 1">
156+
<igx-buttongroup #logicOperatorButtonGroup
157+
[multiSelection]="false">
158+
<span igxButton [displayDensity]="displayDensity"
159+
#andButton
160+
(keydown)="onLogicOperatorKeyDown(0)"
161+
tabindex="0"
162+
[selected]="selectedGroups[0].operator === 0"
163+
type="button"
164+
(click)="onLogicOperatorButtonClicked(0)">
165+
{{ grid.resourceStrings.igx_grid_filter_operator_and }}
166+
</span>
167+
168+
<span igxButton [displayDensity]="displayDensity"
169+
#orButton
170+
tabindex="0"
171+
(keydown)="onLogicOperatorKeyDown(1)"
172+
[selected]="selectedGroups[0].operator === 1"
173+
type="button"
174+
(click)="onLogicOperatorButtonClicked($event, 1)">
175+
{{ grid.resourceStrings.igx_grid_filter_operator_or }}
176+
</span>
177+
</igx-buttongroup>
178+
179+
<button igxButton [displayDensity]="displayDensity" [disabled]="!selectedGroups[0].parent" (click)="ungroup()">
180+
Ungroup
181+
</button>
182+
<button igxButton [displayDensity]="displayDensity" [disabled]="!selectedGroups[0].parent" (click)="deleteGroup()">
183+
Delete
184+
</button>
185+
</ng-container>
186+
<ng-container *ngIf="selectedGroups.length !== 1 && selectedExpressions.length > 1">
187+
<button igxButton [displayDensity]="displayDensity" (click)="createAndGroup()">
188+
Create "And" Group
189+
</button>
190+
<button igxButton [displayDensity]="displayDensity" (click)="createOrGroup()">
191+
Create "Or" Group
192+
</button>
193+
<button igxButton [displayDensity]="displayDensity" (click)="deleteFilters()">
194+
Delete Filters
195+
</button>
196+
</ng-container>
197+
</div>
150198
</article>
151199

152200
<footer class="igx-excel-filter__secondary-footer">

Diff for: projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts

+168-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { Component, AfterViewInit, Input, ViewChild, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
2-
import { PositionSettings, VerticalAlignment, HorizontalAlignment, OverlaySettings } from '../../../services/overlay/utilities';
1+
import { Component, Input, ViewChild, ChangeDetectorRef, ViewChildren, QueryList } from '@angular/core';
2+
import { PositionSettings, VerticalAlignment, HorizontalAlignment, OverlaySettings, Point } from '../../../services/overlay/utilities';
33
import { ConnectedPositioningStrategy } from '../../../services/overlay/position/connected-positioning-strategy';
4-
import { AbsoluteScrollStrategy } from '../../../services/overlay/scroll/absolute-scroll-strategy';
54
import { IgxFilteringService } from '../grid-filtering.service';
65
import { IgxOverlayService } from '../../../services/overlay/overlay';
76
import { DisplayDensity } from '../../../core/displayDensity';
8-
import { IgxToggleDirective } from 'igniteui-angular';
7+
import { IgxToggleDirective, CloseScrollStrategy } from 'igniteui-angular';
98
import { IgxGridBaseComponent, IgxColumnComponent } from '../../grid';
109
import { FilteringExpressionsTree, IFilteringExpressionsTree } from '../../../data-operations/filtering-expressions-tree';
1110
import { FilteringLogic, IFilteringExpression } from '../../../data-operations/filtering-expression.interface';
1211
import { IgxStringFilteringOperand } from '../../../data-operations/filtering-condition';
12+
import { IgxChipComponent } from '../../../chips';
1313

1414
class ExpressionItem {
1515
constructor(parent?: ExpressionGroupItem) {
@@ -60,17 +60,38 @@ export class IgxAdvancedFilteringDialogComponent {
6060

6161
public rootGroup: ExpressionGroupItem;
6262

63-
public selectedExpressions: ExpressionItem[] = [];
63+
public selectedExpressions: ExpressionOperandItem[] = [];
64+
65+
public selectedGroups: ExpressionGroupItem[] = [];
6466

6567
public currentGroup: ExpressionGroupItem;
6668

6769
public editedExpression: ExpressionOperandItem;
6870

6971
public addModeExpression: ExpressionOperandItem;
7072

73+
public selectedGroup: ExpressionGroupItem;
74+
7175
public selectedCondition: string;
7276
public searchValue: string;
7377

78+
public _positionSettings = {
79+
horizontalStartPoint: HorizontalAlignment.Right,
80+
verticalStartPoint: VerticalAlignment.Top
81+
};
82+
public _overlaySettings = {
83+
closeOnOutsideClick: false,
84+
modal: false,
85+
positionStrategy: new ConnectedPositioningStrategy(this._positionSettings),
86+
scrollStrategy: new CloseScrollStrategy()
87+
};
88+
89+
@ViewChild(IgxToggleDirective, { static: true })
90+
public contextMenuToggle: IgxToggleDirective;
91+
92+
@ViewChildren(IgxChipComponent)
93+
public chips: QueryList<IgxChipComponent>;
94+
7495
private _selectedColumn: IgxColumnComponent;
7596
private _clickTimer;
7697
private _dblClickDelay = 200;
@@ -214,7 +235,7 @@ export class IgxAdvancedFilteringDialogComponent {
214235
this.deleteItem(expressionItem);
215236
}
216237

217-
public onChipClick(expressionItem: ExpressionItem) {
238+
public onChipClick(expressionItem: ExpressionOperandItem) {
218239
this._clickTimer = setTimeout(() => {
219240
if (!this._preventChipClick) {
220241
this.toggleExpression(expressionItem);
@@ -230,10 +251,7 @@ export class IgxAdvancedFilteringDialogComponent {
230251
}
231252

232253
public enterExpressionEdit(expressionItem: ExpressionOperandItem) {
233-
for (const expr of this.selectedExpressions) {
234-
expr.selected = false;
235-
}
236-
this.selectedExpressions = [];
254+
this.clearSelection();
237255

238256
if (this.editedExpression) {
239257
this.editedExpression.inEditMode = false;
@@ -249,13 +267,22 @@ export class IgxAdvancedFilteringDialogComponent {
249267
this.editedExpression = expressionItem;
250268
}
251269

270+
private clearSelection() {
271+
for (const expr of this.selectedExpressions) {
272+
expr.selected = false;
273+
}
274+
this.selectedExpressions = [];
275+
276+
this.toggleContextMenu();
277+
}
278+
252279
public enterExpressionAdd(expressionItem: ExpressionOperandItem) {
253280
expressionItem.inAddMode = true;
254281
this.addModeExpression = expressionItem;
255282
this.toggleExpression(expressionItem);
256283
}
257284

258-
private toggleExpression(expressionItem: ExpressionItem) {
285+
private toggleExpression(expressionItem: ExpressionOperandItem) {
259286
expressionItem.selected = !expressionItem.selected;
260287

261288
if (expressionItem.selected) {
@@ -264,6 +291,29 @@ export class IgxAdvancedFilteringDialogComponent {
264291
const index = this.selectedExpressions.indexOf(expressionItem);
265292
this.selectedExpressions.splice(index, 1);
266293
}
294+
295+
this.toggleContextMenu();
296+
}
297+
298+
private toggleContextMenu() {
299+
if (this.selectedExpressions.length > 1) {
300+
setTimeout(() => {
301+
const chips = this.chips.filter(c => this.selectedExpressions.includes(c.data));
302+
const minTop = chips.reduce((t, c) =>
303+
Math.min(t, c.elementRef.nativeElement.getBoundingClientRect().top), Number.MAX_VALUE);
304+
const maxRight = chips.reduce((r, c) =>
305+
Math.max(r, c.elementRef.nativeElement.getBoundingClientRect().right), 0);
306+
this._overlaySettings.positionStrategy.settings.target = new Point(maxRight, minTop);
307+
308+
if (this.contextMenuToggle.collapsed) {
309+
this.contextMenuToggle.open(this._overlaySettings);
310+
} else {
311+
this.contextMenuToggle.reposition();
312+
}
313+
}, 200);
314+
} else {
315+
this.contextMenuToggle.close();
316+
}
267317
}
268318

269319
private deleteItem(expressionItem: ExpressionItem) {
@@ -284,6 +334,113 @@ export class IgxAdvancedFilteringDialogComponent {
284334
eventArgs.stopPropagation();
285335
}
286336

337+
public createAndGroup() {
338+
this.createGroup(FilteringLogic.And);
339+
}
340+
341+
public createOrGroup() {
342+
this.createGroup(FilteringLogic.Or);
343+
}
344+
345+
private createGroup(operator: FilteringLogic) {
346+
const chips = this.chips.toArray();
347+
const minIndex = this.selectedExpressions.reduce((i, e) => Math.min(i, chips.findIndex(c => c.data === e)), Number.MAX_VALUE);
348+
const firstExpression = chips[minIndex].data;
349+
350+
const parent = firstExpression.parent;
351+
const groupItem = new ExpressionGroupItem(operator, parent);
352+
353+
const index = parent.children.indexOf(firstExpression);
354+
parent.children.splice(index, 0, groupItem);
355+
356+
for (const expr of this.selectedExpressions) {
357+
this.deleteItem(expr);
358+
groupItem.children.push(expr);
359+
expr.parent = groupItem;
360+
}
361+
362+
this.clearSelection();
363+
}
364+
365+
public deleteFilters() {
366+
for (const expr of this.selectedExpressions) {
367+
this.deleteItem(expr);
368+
}
369+
370+
this.clearSelection();
371+
}
372+
373+
public onGroupClick(groupItem: ExpressionGroupItem) {
374+
this.toggleGroup(groupItem);
375+
}
376+
377+
private toggleGroup(groupItem: ExpressionGroupItem) {
378+
groupItem.selected = !groupItem.selected;
379+
380+
if (groupItem.selected) {
381+
this.selectedGroups.push(groupItem);
382+
} else {
383+
const index = this.selectedGroups.indexOf(groupItem);
384+
this.selectedGroups.splice(index, 1);
385+
}
386+
387+
this.clearSelection();
388+
389+
for (const group of this.selectedGroups) {
390+
this.selectGroupRecursive(group);
391+
}
392+
393+
this.toggleContextMenu();
394+
}
395+
396+
private selectGroupRecursive(group: ExpressionGroupItem) {
397+
for (const expr of group.children) {
398+
if (expr instanceof ExpressionGroupItem) {
399+
if (!expr.selected) {
400+
expr.selected = true;
401+
}
402+
if (!this.selectedGroups.includes(expr)) {
403+
this.selectedGroups.push(expr);
404+
}
405+
this.selectGroupRecursive(expr);
406+
} else {
407+
const operandItem = expr as ExpressionOperandItem;
408+
if (!operandItem.selected) {
409+
this.toggleExpression(operandItem);
410+
}
411+
}
412+
}
413+
}
414+
415+
public ungroup() {
416+
const parent = this.selectedGroup.parent;
417+
if (parent) {
418+
const index = parent.children.indexOf(this.selectedGroup);
419+
parent.children.splice(index, 1, ...this.selectedGroup.children);
420+
421+
for (const expr of this.selectedGroup.children) {
422+
expr.parent = parent;
423+
}
424+
}
425+
426+
this.selectedGroup = null;
427+
this.toggleContextMenu();
428+
}
429+
430+
public deleteGroup() {
431+
const parent = this.selectedGroup.parent;
432+
if (parent) {
433+
const index = parent.children.indexOf(this.selectedGroup);
434+
parent.children.splice(index, 1);
435+
}
436+
this.selectedGroup = null;
437+
this.toggleContextMenu();
438+
}
439+
440+
public onLogicOperatorButtonClicked(operator: FilteringLogic) {
441+
this.selectedGroups[0].operator = operator;
442+
}
443+
287444
public initialize(filteringService: IgxFilteringService, overlayService: IgxOverlayService,
288445
overlayComponentId: string) {
289446
this.filteringService = filteringService;

0 commit comments

Comments
 (0)