Skip to content

Commit 6b837e2

Browse files
wnvkoPlamenaMitevaLipata
authored
Expose combo filtering strategy 14.0.x (#11844)
* fix(combo): add combo filtering strategy # Conflicts: # projects/igniteui-angular/src/lib/combo/combo.common.ts # projects/igniteui-angular/src/lib/combo/combo.component.html * fix(combo): draft variant of combo filter strategy * feat(combos): add filtering function # Conflicts: # projects/igniteui-angular/src/lib/combo/combo.common.ts * feat(combos): update CHANGELOG.md, #11810 # Conflicts: # CHANGELOG.md * feat(combos): update combos' READMEs * chore(combos): fix merge confilicts * test(combos): revert back some CSS class consts * test(combos): add test for filterFuncion * refactor(combos): move default filter function to combo pipe * chore(combos): update CHANGELOG with new feature added in next version * chore(combos): revert wrongly picked changes * refactor(combo-pipe): pass default function as optinal parameter * chore(combos): remove leftovers Co-authored-by: plamenamiteva <[email protected]> Co-authored-by: lipata <[email protected]>
1 parent 599ba7b commit 6b837e2

11 files changed

+200
-28
lines changed

CHANGELOG.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
All notable changes for each version of this project will be documented in this file.
44

5+
## 14.0.6
6+
7+
### New Features
8+
- `IgxCombo` and `IgxSimpleComboComponent`
9+
- `filterFunction` input is added. The new property allows changing of the way filtering is done in the combos. By default filtering is made over the values in combo's data when it is a collection of primitive values, or over the values as defined in `displayKey` of the combo. If custom filtering function is provided filtering will be done as specified in the provided function.
10+
511
## 14.0.0
612

713
- `IgxDatePicker` and `IgxDateRangePicker` now expose a `weekStart` input property like the `IgxCalendar`
814

9-
### New Features
10-
1115
### General
1216
- Updating dependency to Angular 14
1317
- `Migrations`

projects/igniteui-angular/src/lib/combo/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ Setting `[displayDensity]` affects the control's items' and inputs' css properti
322322
| `valid` | gets if control is valid, when used in a form | boolean |
323323
| `overlaySettings` | gets/sets the custom overlay settings that control how the drop-down list displays | OverlaySettings |
324324
| `autoFocusSearch` | controls whether the search input should be focused when the combo is opened | boolean |
325+
| `filterFunction` | Gets/Sets the custom filtering function of the combo | `(collection: any[], searchValue: any, caseSensitive: boolean) => any[]` |
325326

326327
### Getters
327328
| Name | Description | Type |

projects/igniteui-angular/src/lib/combo/combo.common.ts

+11
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,17 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
384384
this._groupSortingDirection = val;
385385
}
386386

387+
/**
388+
* Gets/Sets the custom filtering function of the combo.
389+
*
390+
* @example
391+
* ```html
392+
* <igx-comb #combo [data]="localData" [filterFunction]="filterFunction"></igx-combo>
393+
* ```
394+
*/
395+
@Input()
396+
public filterFunction: (collection: any[], searchValue: any, caseSensitive: boolean) => any[];
397+
387398
/**
388399
* An @Input property that set aria-labelledby attribute
389400
* ```html

projects/igniteui-angular/src/lib/combo/combo.component.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
[style.maxHeight.px]="itemsMaxHeight" [igxDropDownItemNavigation]="dropdown" (focus)="dropdown.onFocus()"
5252
[tabindex]="dropdown.collapsed ? -1 : 0" role="listbox" [attr.id]="dropdown.id">
5353
<igx-combo-item role="option" [itemHeight]='itemHeight' *igxFor="let item of data
54-
| comboFiltering:filterValue:displayKey:filteringOptions:filterable
54+
| comboFiltering:filterValue:displayKey:filteringOptions:filterable:filterFunction
5555
| comboGrouping:groupKey:valueKey:groupSortingDirection;
5656
index as rowIndex; containerSize: itemsMaxHeight; scrollOrientation: 'vertical'; itemSize: itemHeight"
5757
[value]="item" [isHeader]="item.isHeader" [index]="rowIndex">

projects/igniteui-angular/src/lib/combo/combo.component.spec.ts

+116
Original file line numberDiff line numberDiff line change
@@ -2693,6 +2693,122 @@ describe('igxCombo', () => {
26932693
expect(combo.searchValue).toEqual('Test');
26942694
cancelSub.unsubscribe();
26952695
});
2696+
it('Should filter the data when custom filterFunction is provided', fakeAsync(() => {
2697+
combo.open();
2698+
tick();
2699+
fixture.detectChanges();
2700+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2701+
2702+
combo.searchValue = 'new england';
2703+
combo.handleInputChange();
2704+
fixture.detectChanges();
2705+
expect(combo.dropdown.items.length).toEqual(0);
2706+
2707+
combo.close();
2708+
tick();
2709+
fixture.detectChanges();
2710+
// set filter function to search only on valueKyes
2711+
combo.filterFunction = (collection: any[], searchValue: any): any[] => {
2712+
if (!collection) return [];
2713+
if (!searchValue) return collection;
2714+
const searchTerm = combo.filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
2715+
return collection.filter(i => combo.filteringOptions.caseSensitive ?
2716+
i[combo.displayKey]?.includes(searchTerm) || i[combo.groupKey]?.includes(searchTerm) :
2717+
i[combo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[combo.groupKey]?.toString().toLowerCase().includes(searchTerm))
2718+
}
2719+
combo.open();
2720+
tick();
2721+
fixture.detectChanges();
2722+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2723+
2724+
combo.searchValue = 'new england';
2725+
combo.handleInputChange();
2726+
fixture.detectChanges();
2727+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2728+
2729+
combo.filterFunction = undefined;
2730+
fixture.detectChanges();
2731+
expect(combo.dropdown.items.length).toEqual(0);
2732+
}));
2733+
it('Should update filtering when custom filterFunction is provided and filteringOptions are changed', fakeAsync(() => {
2734+
combo.open();
2735+
tick();
2736+
fixture.detectChanges();
2737+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2738+
2739+
combo.searchValue = 'new england';
2740+
combo.handleInputChange();
2741+
fixture.detectChanges();
2742+
expect(combo.dropdown.items.length).toEqual(0);
2743+
2744+
combo.close();
2745+
tick();
2746+
fixture.detectChanges();
2747+
// set filter function to search only on valueKyes
2748+
combo.filterFunction = (collection: any[], searchValue: any): any[] => {
2749+
if (!collection) return [];
2750+
if (!searchValue) return collection;
2751+
const searchTerm = combo.filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
2752+
return collection.filter(i => combo.filteringOptions.caseSensitive ?
2753+
i[combo.displayKey]?.includes(searchTerm) || i[combo.groupKey]?.includes(searchTerm) :
2754+
i[combo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[combo.groupKey]?.toString().toLowerCase().includes(searchTerm))
2755+
}
2756+
combo.open();
2757+
tick();
2758+
fixture.detectChanges();
2759+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2760+
2761+
combo.searchValue = 'new england';
2762+
combo.handleInputChange();
2763+
fixture.detectChanges();
2764+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2765+
2766+
combo.filteringOptions = { caseSensitive: true };
2767+
fixture.detectChanges();
2768+
expect(combo.dropdown.items.length).toEqual(0);
2769+
}));
2770+
it('Should update filtering when custom filterFunction is provided and filterable is changed', fakeAsync(() => {
2771+
combo.open();
2772+
tick();
2773+
fixture.detectChanges();
2774+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2775+
2776+
combo.searchValue = 'new england';
2777+
combo.handleInputChange();
2778+
fixture.detectChanges();
2779+
expect(combo.dropdown.items.length).toEqual(0);
2780+
2781+
combo.close();
2782+
tick();
2783+
fixture.detectChanges();
2784+
// set filter function to search only on valueKyes
2785+
combo.filterFunction = (collection: any[], searchValue: any): any[] => {
2786+
if (!collection) return [];
2787+
if (!searchValue) return collection;
2788+
const searchTerm = combo.filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
2789+
return collection.filter(i => combo.filteringOptions.caseSensitive ?
2790+
i[combo.displayKey]?.includes(searchTerm) || i[combo.groupKey]?.includes(searchTerm) :
2791+
i[combo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[combo.groupKey]?.toString().toLowerCase().includes(searchTerm))
2792+
}
2793+
combo.open();
2794+
tick();
2795+
fixture.detectChanges();
2796+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2797+
2798+
combo.searchValue = 'new england';
2799+
combo.handleInputChange();
2800+
fixture.detectChanges();
2801+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2802+
2803+
combo.searchValue = 'value not in the list';
2804+
combo.handleInputChange();
2805+
fixture.detectChanges();
2806+
expect(combo.dropdown.items.length).toEqual(0);
2807+
2808+
combo.filterable = false;
2809+
fixture.detectChanges();
2810+
expect(combo.dropdown.items.length).toBeGreaterThan(0);
2811+
}));
26962812
});
26972813
describe('Form control tests: ', () => {
26982814
describe('Reactive form tests: ', () => {

projects/igniteui-angular/src/lib/combo/combo.pipes.ts

+32-19
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,38 @@
11
import { Inject, Pipe, PipeTransform } from '@angular/core';
22
import { cloneArray } from '../core/utils';
33
import { DataUtil } from '../data-operations/data-util';
4-
import { IGX_COMBO_COMPONENT, IgxComboBase } from './combo.common';
54
import { DefaultSortingStrategy, SortingDirection } from '../data-operations/sorting-strategy';
5+
import { IgxComboBase, IGX_COMBO_COMPONENT } from './combo.common';
66
import { IComboFilteringOptions } from './combo.component';
77

88
/** @hidden */
9-
@Pipe({
10-
name: 'comboClean'
11-
})
9+
@Pipe({ name: 'comboClean' })
1210
export class IgxComboCleanPipe implements PipeTransform {
1311
public transform(collection: any[]) {
1412
return collection.filter(e => !!e);
1513
}
1614
}
1715

1816
/** @hidden */
19-
@Pipe({
20-
name: 'comboFiltering'
21-
})
17+
@Pipe({ name: 'comboFiltering' })
2218
export class IgxComboFilteringPipe implements PipeTransform {
23-
public transform(collection: any[], searchValue: any, displayKey: any,
24-
filteringOptions: IComboFilteringOptions, shouldFilter = false) {
19+
private displayKey: any;
20+
21+
public transform (
22+
collection: any[],
23+
searchValue: any,
24+
displayKey: any,
25+
filteringOptions: IComboFilteringOptions,
26+
filterable: boolean,
27+
filterFunction: (collection: any[], searchValue: any, caseSensitive: boolean) => any[] = defaultFilterFunction) {
2528
if (!collection) {
2629
return [];
2730
}
28-
if (!searchValue || !shouldFilter) {
31+
if (!filterable) {
2932
return collection;
30-
} else {
31-
const searchTerm = filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
32-
if (displayKey != null) {
33-
return collection.filter(e => filteringOptions.caseSensitive ? e[displayKey]?.includes(searchTerm) :
34-
e[displayKey]?.toString().toLowerCase().includes(searchTerm));
35-
} else {
36-
return collection.filter(e => filteringOptions.caseSensitive ? e.includes(searchTerm) :
37-
e.toString().toLowerCase().includes(searchTerm));
38-
}
3933
}
34+
this.displayKey = displayKey;
35+
return filterFunction.call(this, collection, searchValue, filteringOptions.caseSensitive);
4036
}
4137
}
4238

@@ -78,3 +74,20 @@ export class IgxComboGroupingPipe implements PipeTransform {
7874
return data;
7975
}
8076
}
77+
78+
function defaultFilterFunction (collection: any[], searchValue: any, matchCase: boolean): any[] {
79+
if (!searchValue) {
80+
return collection;
81+
}
82+
const searchTerm = matchCase ? searchValue.trim() : searchValue.toLowerCase().trim();
83+
if (this.displayKey != null) {
84+
return collection.filter(e => matchCase ?
85+
e[this.displayKey]?.includes(searchTerm) :
86+
e[this.displayKey]?.toString().toLowerCase().includes(searchTerm));
87+
} else {
88+
return collection.filter(e => matchCase ?
89+
e.includes(searchTerm) :
90+
e.toString().toLowerCase().includes(searchTerm));
91+
}
92+
}
93+

projects/igniteui-angular/src/lib/simple-combo/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ Setting `[displayDensity]` affects the control's items' and inputs' css properti
291291
| `ariaLabelledBy` | Defines label ID related to combo. | `boolean` |
292292
| `valid` | gets if control is valid, when used in a form. | `boolean` |
293293
| `overlaySettings` | Controls how the dropdown is displayed. | `OverlaySettings` |
294-
| `selected` | Get current selection state. | `Array<any>` |
294+
| `selected` | Get current selection state. | `Array<any>` |
295+
| `filterFunction` | Gets/Sets the custom filtering function of the combo | `(collection: any[], searchValue: any, caseSensitive: boolean) => any[]` |
295296

296297

297298
### Methods

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
(keydown)="handleItemKeyDown($event)">
5353
<igx-combo-item role="option" [singleMode]="true" [itemHeight]='itemHeight' (click)="handleItemClick()" *igxFor="let item of data
5454
| comboClean
55-
| comboFiltering:filterValue:displayKey:filteringOptions:true
55+
| comboFiltering:filterValue:displayKey:filteringOptions:true:filterFunction
5656
| comboGrouping:groupKey:valueKey:groupSortingDirection;
5757
index as rowIndex; containerSize: itemsMaxHeight; scrollOrientation: 'vertical'; itemSize: itemHeight"
5858
[value]="item" [isHeader]="item.isHeader" [index]="rowIndex">

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AfterViewInit, ChangeDetectorRef, Component, DebugElement, OnDestroy, OnInit, ViewChild } from '@angular/core';
22
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
3-
import { ReactiveFormsModule, FormsModule, NgForm } from '@angular/forms';
3+
import { FormsModule, NgForm, ReactiveFormsModule } from '@angular/forms';
44
import { By } from '@angular/platform-browser';
55
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
66
import { IgxComboDropDownComponent } from '../combo/combo-dropdown.component';
@@ -12,7 +12,7 @@ import { IgxSelectionAPIService } from '../core/selection';
1212
import { IBaseCancelableBrowserEventArgs, PlatformUtil } from '../core/utils';
1313
import { IgxToggleModule } from '../directives/toggle/toggle.directive';
1414
import { IgxIconComponent, IgxIconModule, IgxIconService } from '../icon/public_api';
15-
import { IgxInputDirective, IgxInputState } from '../input-group/public_api';
15+
import { IgxInputState } from '../input-group/public_api';
1616
import { AbsoluteScrollStrategy, ConnectedPositioningStrategy } from '../services/public_api';
1717
import { configureTestSuite } from '../test-utils/configure-suite';
1818
import { UIInteractions, wait } from '../test-utils/ui-interactions.spec';

src/app/combo/combo.sample.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<div>
1616
<h5 class="sample-title">Simple Combo in Template Form</h5>
1717
<form>
18-
<igx-simple-combo class="input-container" [placeholder]="'Locations'" name="anyName"
18+
<igx-simple-combo #simpleCombo class="input-container" [placeholder]="'Locations'" name="anyName"
1919
#comboModel="ngModel" [(ngModel)]="singleValue" minlength="2" required [data]="items"
2020
[displayKey]="valueKeyVar" [disabled]="isDisabled" [valueKey]="valueKeyVar"
2121
[groupKey]="valueKeyVar ? 'region' : ''" [width]="'100%'">
@@ -99,6 +99,7 @@ <h5 class="sample-title">Reactive Form</h5>
9999
<button igxButton="raised" igxRipple (click)="toggleItem('Connecticut')">Toggle "Connecticut"
100100
</button>
101101
<button igxButton="raised" igxRipple (click)="changeItemTemplate()">Change Item Template</button>
102+
<button igxButton="raised" igxRipple (click)="changeFiltering()">Change Filtering</button>
102103
</div>
103104
</div>
104105
<ng-template #customItemTemplate let-display let-key="valueKey">

src/app/combo/combo.sample.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Component, ViewChild, OnInit, TemplateRef, AfterViewInit, ElementRef } from '@angular/core';
22
import { IgxComboComponent, IComboSelectionChangingEventArgs,
33
DisplayDensity, OverlaySettings, VerticalAlignment, HorizontalAlignment, GlobalPositionStrategy,
4-
scaleInCenter, scaleOutCenter, ElasticPositionStrategy, ConnectedPositioningStrategy
4+
scaleInCenter, scaleOutCenter, ElasticPositionStrategy, ConnectedPositioningStrategy, IgxSimpleComboComponent
55
} from 'igniteui-angular';
66
import { ButtonGroupAlignment } from 'igniteui-angular';
77
import { take } from 'rxjs/operators';
@@ -21,6 +21,9 @@ export class ComboSampleComponent implements OnInit, AfterViewInit {
2121
private comboRef: ElementRef;
2222
@ViewChild('customItemTemplate', { read: TemplateRef, static: true })
2323
private customItemTemplate;
24+
@ViewChild('simpleCombo', { read: IgxSimpleComboComponent, static: true })
25+
private simpleCombo;
26+
private hasCustomFilter = false;
2427

2528
public alignment: ButtonGroupAlignment = ButtonGroupAlignment.vertical;
2629
public toggleItemState = false;
@@ -181,4 +184,26 @@ export class ComboSampleComponent implements OnInit, AfterViewInit {
181184
public handleSelectionChange(event: IComboSelectionChangingEventArgs) {
182185
console.log(event);
183186
}
187+
188+
public changeFiltering() {
189+
if (this.hasCustomFilter) {
190+
this.igxCombo.filterFunction = undefined;
191+
this.simpleCombo.filterFunction = undefined;
192+
} else {
193+
this.igxCombo.filterFunction = this.customFilterFunction;
194+
this.simpleCombo.filterFunction = this.customFilterFunction;
195+
}
196+
197+
this.hasCustomFilter = !this.hasCustomFilter;
198+
}
199+
200+
private customFilterFunction = (collection: any[], filterValue: any) => {
201+
if (!filterValue) {
202+
return collection;
203+
}
204+
const searchTerm = this.igxCombo.filteringOptions.caseSensitive ? filterValue.trim() : filterValue.toLowerCase().trim();
205+
return collection.filter(i => this.igxCombo.filteringOptions.caseSensitive ?
206+
i[this.igxCombo.displayKey]?.includes(searchTerm) || i[this.igxCombo.groupKey]?.includes(searchTerm) :
207+
i[this.igxCombo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[this.igxCombo.groupKey]?.toString().toLowerCase().includes(searchTerm))
208+
}
184209
}

0 commit comments

Comments
 (0)