Skip to content

Commit fe80320

Browse files
Merge pull request #8193 from IgniteUI/ibarakov/fix-5813-10.2.x
feat(grid-esf): make excel style filtering work like in excel
2 parents 88bf397 + 12288e3 commit fe80320

File tree

9 files changed

+441
-89
lines changed

9 files changed

+441
-89
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ All notable changes for each version of this project will be documented in this
66
### General
77
- `IgxGridActions`
88
- Added `asMenuItems` Input for grid actions - `igx-grid-editing-actions`, `igx-grid-pinning-actions`. When set to true will render the related action buttons as separate menu items with button and label.
9+
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
10+
- **Behavioral Change** - The Excel Style Filtering has been reworked to provide filtering experience such as in Excel. This includes the following changes:
11+
- You can close the Excel Style Filtering menu by pressing `Ctrl + Shift + L`.
12+
- You can apply the filter by pressing `Enter`.
13+
- When searching items in the Excel Style Filtering menu, only the rows that match your search term will be filtered in.
14+
- By checking the `Add current selection to filter` option, the new search results will be added to the previously filtered items.
915
- `IgxInputGroup`
1016
- **Breaking Change** - Removed `fluent`, `fluent_search`, `bootstrap`, and `indigo` as possible values for the `type` input property.
1117
- **Behavioral Change** - The styling of the input group is now dictated by the theme being used. The remaining `types` - `line`, `border`, and `box` will only have effect on the styling when used with the `material` theme. The `search` type will affect styling when used with all themes. Changing the theme at runtime will not change the styling of the input group, a page refresh is required.

projects/igniteui-angular/src/lib/core/i18n/grid-resources.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export interface IGridResourceStrings {
6464
igx_grid_excel_apply?: string;
6565
igx_grid_excel_search_placeholder?: string;
6666
igx_grid_excel_select_all?: string;
67+
igx_grid_excel_select_all_search_results?: string;
68+
igx_grid_excel_add_to_filter?: string;
6769
igx_grid_excel_blanks?: string;
6870
igx_grid_excel_hide?: string;
6971
igx_grid_excel_show?: string;
@@ -174,6 +176,8 @@ export const GridResourceStringsEN: IGridResourceStrings = {
174176
igx_grid_excel_apply: 'apply',
175177
igx_grid_excel_search_placeholder: 'Search',
176178
igx_grid_excel_select_all: 'Select All',
179+
igx_grid_excel_select_all_search_results: 'Select all search results',
180+
igx_grid_excel_add_to_filter: 'Add current selection to filter',
177181
igx_grid_excel_blanks: '(Blanks)',
178182
igx_grid_excel_hide: 'Hide column',
179183
igx_grid_excel_show: 'Show column',

projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,14 @@ export class IgxGridFilteringRowComponent implements AfterViewInit {
175175
this.input.nativeElement.focus();
176176
}
177177

178-
@HostListener('keydown.esc', ['$event'])
179-
public onEscHandler(evt) {
180-
evt.preventDefault();
181-
evt.stopPropagation();
182-
this.close();
178+
@HostListener('keydown', ['$event'])
179+
public onKeydownHandler(evt) {
180+
if (evt.key === KEYS.ESCAPE || evt.key === KEYS.ESCAPE_IE ||
181+
evt.ctrlKey && evt.shiftKey && evt.key.toLowerCase() === 'l') {
182+
evt.preventDefault();
183+
evt.stopPropagation();
184+
this.close();
185+
}
183186
}
184187

185188
get disabled(): boolean {
@@ -260,8 +263,9 @@ export class IgxGridFilteringRowComponent implements AfterViewInit {
260263
} else if (event.altKey && (event.key === KEYS.DOWN_ARROW || event.key === KEYS.DOWN_ARROW_IE)) {
261264
this.inputGroupPrefix.nativeElement.focus();
262265
this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
263-
} else if (event.key === KEYS.ESCAPE || event.key === KEYS.ESCAPE_IE) {
264-
this.close();
266+
} else if (event.key === KEYS.ESCAPE || event.key === KEYS.ESCAPE_IE ||
267+
event.ctrlKey && event.shiftKey && event.key.toLowerCase() === 'l') {
268+
this.close();
265269
}
266270
}
267271

projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-search.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
igxInput
88
tabindex="0"
99
[(ngModel)]="searchValue"
10+
(ngModelChange)="filterListData()"
11+
(keydown)="onInputKeyDown($event)"
1012
[placeholder]="esf.column?.grid.resourceStrings.igx_grid_excel_search_placeholder"
1113
autocomplete="off"/>
1214
<igx-icon
@@ -21,7 +23,7 @@
2123
<igx-list #list [displayDensity]="esf.displayDensity" [isLoading]="isLoading">
2224
<div [style.overflow]="'hidden'" [style.position]="'relative'">
2325
<igx-list-item
24-
*igxFor="let item of esf.listData | excelStyleSearchFilter: searchValue; scrollOrientation : 'vertical'; containerSize: containerSize; itemSize: itemSize">
26+
*igxFor="let item of displayedListData scrollOrientation : 'vertical'; containerSize: containerSize; itemSize: itemSize">
2527
<igx-checkbox
2628
[value]="item"
2729
tabindex="-1"

projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-search.component.ts

Lines changed: 126 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import { IgxInputDirective } from '../../../directives/input/input.directive';
1212
import { DisplayDensity } from '../../../core/density';
1313
import { IgxForOfDirective } from '../../../directives/for-of/for_of.directive';
14-
import { IgxGridExcelStyleFilteringComponent } from './grid.excel-style-filtering.component';
14+
import { IgxGridExcelStyleFilteringComponent, FilterListItem } from './grid.excel-style-filtering.component';
1515
import { FilteringExpressionsTree } from '../../../data-operations/filtering-expressions-tree';
1616
import { FilteringLogic } from '../../../data-operations/filtering-expression.interface';
1717
import { DataType } from '../../../data-operations/data-util';
@@ -23,6 +23,7 @@ import { Subject } from 'rxjs';
2323
import { IgxListComponent } from '../../../list/public_api';
2424
import { IChangeCheckboxEventArgs } from '../../../checkbox/checkbox.component';
2525
import { takeUntil } from 'rxjs/operators';
26+
import { KEYS } from '../../../core/utils';
2627

2728
@Directive({
2829
selector: '[igxExcelStyleLoading]'
@@ -42,8 +43,30 @@ export class IgxExcelStyleLoadingValuesTemplateDirective {
4243
export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
4344
private static readonly filterOptimizationThreshold = 2;
4445
private _isLoading;
46+
private _addToCurrentFilter: FilterListItem;
4547
private destroy$ = new Subject<boolean>();
4648

49+
/**
50+
* @hidden @internal
51+
*/
52+
public get addToCurrentFilter(): FilterListItem {
53+
if (!this._addToCurrentFilter) {
54+
const addToCurrentFilterItem = {
55+
isSelected: false,
56+
isFiltered: false,
57+
indeterminate: false,
58+
isSpecial: true,
59+
isBlanks: false,
60+
value: this.esf.grid.resourceStrings.igx_grid_excel_add_to_filter,
61+
label: this.esf.grid.resourceStrings.igx_grid_excel_add_to_filter
62+
};
63+
64+
this._addToCurrentFilter = addToCurrentFilterItem;
65+
}
66+
67+
return this._addToCurrentFilter;
68+
}
69+
4770
/**
4871
* @hidden @internal
4972
*/
@@ -66,6 +89,11 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
6689
*/
6790
public searchValue: any;
6891

92+
/**
93+
* @hidden @internal
94+
*/
95+
public displayedListData: FilterListItem[];
96+
6997
/**
7098
* @hidden @internal
7199
*/
@@ -106,13 +134,6 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
106134
}
107135
}
108136

109-
/**
110-
* @hidden @internal
111-
*/
112-
get applyButtonDisabled() {
113-
return this.esf.listData[0] && !this.esf.listData[0].isSelected && !this.esf.listData[0].indeterminate;
114-
}
115-
116137
constructor(public cdr: ChangeDetectorRef, public esf: IgxGridExcelStyleFilteringComponent) {
117138
esf.loadingStart.pipe(takeUntil(this.destroy$)).subscribe(() => {
118139
this.isLoading = true;
@@ -129,6 +150,10 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
129150
esf.columnChange.pipe(takeUntil(this.destroy$)).subscribe(() => {
130151
this.virtDir.resetScrollPosition();
131152
});
153+
154+
esf.listDataLoaded.pipe(takeUntil(this.destroy$)).subscribe(() => {
155+
this.filterListData();
156+
});
132157
}
133158

134159
public ngAfterViewInit() {
@@ -154,28 +179,38 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
154179
*/
155180
public clearInput() {
156181
this.searchValue = null;
182+
this.filterListData();
157183
}
158184

159185
/**
160186
* @hidden @internal
161187
*/
162188
public onCheckboxChange(eventArgs: IChangeCheckboxEventArgs) {
163-
const selectedIndex = this.esf.listData.indexOf(eventArgs.checkbox.value);
189+
const selectedIndex = this.displayedListData.indexOf(eventArgs.checkbox.value);
190+
const selectAllBtn = this.displayedListData[0];
191+
164192
if (selectedIndex === 0) {
165-
this.esf.listData.forEach(element => {
193+
this.displayedListData.forEach(element => {
194+
if (element === this.addToCurrentFilter) { return; }
166195
element.isSelected = eventArgs.checked;
167-
this.esf.listData[0].indeterminate = false;
168196
});
197+
198+
selectAllBtn.indeterminate = false;
169199
} else {
170200
eventArgs.checkbox.value.isSelected = eventArgs.checked;
171-
if (!this.esf.listData.slice(1, this.esf.listData.length).find(el => el.isSelected === false)) {
172-
this.esf.listData[0].indeterminate = false;
173-
this.esf.listData[0].isSelected = true;
174-
} else if (!this.esf.listData.slice(1, this.esf.listData.length).find(el => el.isSelected === true)) {
175-
this.esf.listData[0].indeterminate = false;
176-
this.esf.listData[0].isSelected = false;
201+
const indexToStartSlicing = this.displayedListData.indexOf(this.addToCurrentFilter) > -1 ? 2 : 1;
202+
203+
const slicedArray =
204+
this.displayedListData.slice(indexToStartSlicing, this.displayedListData.length);
205+
206+
if (!slicedArray.find(el => el.isSelected === false)) {
207+
selectAllBtn.indeterminate = false;
208+
selectAllBtn.isSelected = true;
209+
} else if (!slicedArray.find(el => el.isSelected === true)) {
210+
selectAllBtn.indeterminate = false;
211+
selectAllBtn.isSelected = false;
177212
} else {
178-
this.esf.listData[0].indeterminate = true;
213+
selectAllBtn.indeterminate = true;
179214
}
180215
}
181216
eventArgs.checkbox.nativeCheckbox.nativeElement.blur();
@@ -203,12 +238,84 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
203238
}
204239
}
205240

241+
/**
242+
* @hidden @internal
243+
*/
244+
get applyButtonDisabled(): boolean {
245+
return this.esf.listData[0] && !this.esf.listData[0].isSelected && !this.esf.listData[0].indeterminate ||
246+
this.displayedListData && this.displayedListData.length === 0;
247+
}
248+
249+
/**
250+
* @hidden @internal
251+
*/
252+
public onInputKeyDown(event): void {
253+
if (event.key === KEYS.ENTER) {
254+
event.preventDefault();
255+
this.applyFilter();
256+
}
257+
}
258+
259+
/**
260+
* @hidden @internal
261+
*/
262+
public filterListData(): void {
263+
if (!this.esf.listData || !this.esf.listData.length) {
264+
this.displayedListData = [];
265+
266+
return;
267+
}
268+
269+
const searchAllBtn = this.esf.listData[0];
270+
271+
if (!this.searchValue) {
272+
const anyFiltered = this.esf.listData.some(i => i.isFiltered);
273+
const anyUnfiltered = this.esf.listData.some(i => !i.isFiltered);
274+
275+
if (anyFiltered && anyUnfiltered) {
276+
searchAllBtn.indeterminate = true;
277+
}
278+
279+
this.esf.listData.forEach(i => i.isSelected = i.isFiltered);
280+
this.displayedListData = this.esf.listData;
281+
searchAllBtn.label = this.esf.grid.resourceStrings.igx_grid_excel_select_all;
282+
283+
return;
284+
}
285+
286+
const searchVal = this.searchValue.toLowerCase();
287+
288+
this.displayedListData = this.esf.listData.filter((it, i) => (i === 0 && it.isSpecial) ||
289+
(it.label !== null && it.label !== undefined) &&
290+
!it.isBlanks &&
291+
it.label.toString().toLowerCase().indexOf(searchVal) > -1);
292+
293+
this.esf.listData.forEach(i => i.isSelected = false);
294+
this.displayedListData.forEach(i => i.isSelected = true);
295+
296+
this.displayedListData.splice(1, 0, this.addToCurrentFilter);
297+
298+
searchAllBtn.indeterminate = false;
299+
searchAllBtn.label = this.esf.grid.resourceStrings.igx_grid_excel_select_all_search_results;
300+
301+
if (this.displayedListData.length === 2) {
302+
this.displayedListData = [];
303+
}
304+
}
305+
206306
/**
207307
* @hidden @internal
208308
*/
209309
public applyFilter() {
210310
const filterTree = new FilteringExpressionsTree(FilteringLogic.Or, this.esf.column.field);
211-
const selectedItems = this.esf.listData.slice(1, this.esf.listData.length).filter(el => el.isSelected === true);
311+
312+
const item = this.displayedListData[1];
313+
const addToCurrentFilterOptionVisible = item === this.addToCurrentFilter;
314+
315+
const selectedItems = addToCurrentFilterOptionVisible && item.isSelected ?
316+
this.esf.listData.slice(1, this.esf.listData.length).filter(el => el.isSelected || el.isFiltered) :
317+
this.esf.listData.slice(1, this.esf.listData.length).filter(el => el.isSelected);
318+
212319
const unselectedItem = this.esf.listData.slice(1, this.esf.listData.length).find(el => el.isSelected === false);
213320

214321
if (unselectedItem) {

projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-search.pipe.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)