Skip to content

Commit a89d39c

Browse files
committed
feat(combos): extend filtering options
1 parent cad2f15 commit a89d39c

File tree

11 files changed

+161
-130
lines changed

11 files changed

+161
-130
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ All notable changes for each version of this project will be documented in this
1111
{{ value.member }}
1212
</ng-template>
1313
```
14+
### New Features
15+
- `IgxCombo` and `IgxSimpleComboComponent`
16+
- `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.
17+
- `filteringOptions` are extended and now contains `filterable` and `filteringKey` properties. Setting `filterable` determines whether combo will be filterable. By default filtering is done over the data value when they are primitive, or over the field of the values equal to `displayKey`. `filteringKey` allows to filter data by any data related key.
18+
- **Breaking Changes** - `filterable` property of `IgxComboComponent` is now deprecated and will be removed in future version. Use `filteringOptions.filterable` instead.
1419
1520
## 14.0.0
1621
- `IgxDatePicker` and `IgxDateRangePicker` now expose a `weekStart` input property like the `IgxCalendar`

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+
| `filteringOptions` | Configures the way combo items will be filtered | IComboFilteringOptions |
325326
| `filterFunction` | Gets/Sets the custom filtering function of the combo | `(collection: any[], searchValue: any, caseSensitive: boolean) => any[]` |
326327

327328
### Getters

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

+36-17
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ import {
3939
IgxComboAddItemDirective, IgxComboClearIconDirective, IgxComboEmptyDirective,
4040
IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboHeaderItemDirective, IgxComboItemDirective, IgxComboToggleIconDirective
4141
} from './combo.directives';
42-
import {
43-
IComboFilteringOptions, IComboItemAdditionEvent, IComboSearchInputEventArgs
44-
} from './public_api';
42+
import { IComboItemAdditionEvent, IComboSearchInputEventArgs } from './public_api';
4543

4644
export const IGX_COMBO_COMPONENT = new InjectionToken<IgxComboBase>('IgxComboComponentToken');
4745

@@ -114,6 +112,16 @@ export enum IgxComboState {
114112
INVALID = IgxInputState.INVALID
115113
}
116114

115+
/** The filtering criteria to be applied on data search */
116+
export interface IComboFilteringOptions {
117+
/** Defines filtering case-sensitivity */
118+
caseSensitive: boolean;
119+
/** Defines whether filtering is allowed */
120+
filterable: boolean;
121+
/** Defines optional key to filter against complex list items. Default to displayKey if provided.*/
122+
filteringKey?: string;
123+
}
124+
117125
@Directive()
118126
export abstract class IgxComboBaseDirective extends DisplayDensityBase implements IgxComboBase, OnInit, DoCheck,
119127
AfterViewInit, OnDestroy, ControlValueAccessor {
@@ -395,13 +403,7 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
395403
* ```
396404
*/
397405
@Input()
398-
public get filterFunction(): (collection: any[], searchValue: any, caseSensitive: boolean) => any[] {
399-
return this._customFilterFunction;
400-
}
401-
402-
public set filterFunction(value: (collection: any[], searchValue: any, caseSensitive: boolean) => any[]) {
403-
this._customFilterFunction = value;
404-
}
406+
public filterFunction: (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions) => any[];
405407

406408
/**
407409
* An @Input property that set aria-labelledby attribute
@@ -858,12 +860,29 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
858860
/** @hidden @internal */
859861
public defaultFallbackGroup = 'Other';
860862
/** @hidden @internal */
861-
public filteringOptions: IComboFilteringOptions = {
862-
caseSensitive: false
863-
};
864-
/** @hidden @internal */
865863
public activeDescendant = '';
866864

865+
/**
866+
* Configures the way combo items will be filtered.
867+
*
868+
* ```typescript
869+
* // get
870+
* let myFilteringOptions = this.combo.filteringOptions;
871+
* ```
872+
*
873+
* ```html
874+
* <!--set-->
875+
* <igx-combo [filteringOptions]='myFilteringOptions'></igx-combo>
876+
* ```
877+
*/
878+
879+
@Input()
880+
public get filteringOptions(): IComboFilteringOptions {
881+
return this._filteringOptions || this._defaultFilteringOptions;
882+
}
883+
public set filteringOptions(value: IComboFilteringOptions) {
884+
this._filteringOptions = value;
885+
}
867886
protected _data = [];
868887
protected _value = '';
869888
protected _groupKey = '';
@@ -883,8 +902,8 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
883902
private _itemsMaxHeight = null;
884903
private _overlaySettings: OverlaySettings;
885904
private _groupSortingDirection: SortingDirection = SortingDirection.Asc;
886-
private _customFilterFunction: (collection: any[], searchValue: any, caseSensitive: boolean) => any[] = null;
887-
905+
private _filteringOptions: IComboFilteringOptions;
906+
private _defaultFilteringOptions: IComboFilteringOptions = { caseSensitive: false, filterable: true };
888907
public abstract dropdown: IgxComboDropDownComponent;
889908

890909
public abstract selectionChanging: EventEmitter<any>;
@@ -1160,7 +1179,7 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
11601179

11611180
/** @hidden @internal */
11621181
public toggleCaseSensitive() {
1163-
this.filteringOptions = { caseSensitive: !this.filteringOptions.caseSensitive };
1182+
this.filteringOptions = Object.assign({}, this.filteringOptions, { caseSensitive: !this.filteringOptions.caseSensitive });
11641183
}
11651184

11661185
protected onStatusChanged = () => {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
[tabindex]="dropdown.collapsed ? -1 : 0" [attr.id]="dropdown.id" aria-multiselectable="true"
5858
[attr.aria-activedescendant]="this.activeDescendant">
5959
<igx-combo-item [itemHeight]='itemHeight' *igxFor="let item of data
60-
| comboFiltering:filterFunction:filterValue:filteringOptions:filterable:displayKey
60+
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction
6161
| comboGrouping:groupKey:valueKey:groupSortingDirection;
6262
index as rowIndex; containerSize: itemsMaxHeight; scrollOrientation: 'vertical'; itemSize: itemHeight"
6363
[value]="item" [isHeader]="item.isHeader" [index]="rowIndex" [role]="item.isHeader? 'group' : 'option'">

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

+40-49
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
1-
import { AfterViewInit, ChangeDetectorRef, Component, Injectable, OnInit, ViewChild, OnDestroy, DebugElement } from '@angular/core';
2-
import { TestBed, tick, fakeAsync, ComponentFixture, waitForAsync } from '@angular/core/testing';
1+
import { AfterViewInit, ChangeDetectorRef, Component, DebugElement, Injectable, OnDestroy, OnInit, ViewChild } from '@angular/core';
2+
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
3+
import {
4+
FormsModule, NgControl, NgForm, NgModel, ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators
5+
} from '@angular/forms';
36
import { By } from '@angular/platform-browser';
47
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
5-
import { UntypedFormGroup, UntypedFormControl, Validators, UntypedFormBuilder, ReactiveFormsModule,
6-
FormsModule, NgControl, NgModel, NgForm } from '@angular/forms';
7-
import {
8-
IgxComboComponent,
9-
IgxComboModule,
10-
IComboSelectionChangingEventArgs,
11-
IComboSearchInputEventArgs,
12-
IComboItemAdditionEvent
13-
} from './combo.component';
14-
import { IgxComboItemComponent } from './combo-item.component';
15-
import { IgxComboDropDownComponent } from './combo-dropdown.component';
16-
import { IgxComboAddItemComponent } from './combo-add-item.component';
17-
import { IgxComboFilteringPipe } from './combo.pipes';
18-
import { IgxInputState } from '../directives/input/input.directive';
19-
import { IForOfState } from '../directives/for-of/for_of.directive';
20-
import { IgxToggleModule } from '../directives/toggle/toggle.directive';
218
import { BehaviorSubject, Observable } from 'rxjs';
229
import { take } from 'rxjs/operators';
23-
import { UIInteractions, wait } from '../test-utils/ui-interactions.spec';
24-
import { configureTestSuite } from '../test-utils/configure-suite';
2510
import { DisplayDensity } from '../core/density';
26-
import { AbsoluteScrollStrategy, ConnectedPositioningStrategy } from '../services/public_api';
2711
import { IgxSelectionAPIService } from '../core/selection';
28-
import { IgxIconService } from '../icon/public_api';
2912
import { IBaseCancelableBrowserEventArgs } from '../core/utils';
3013
import { SortingDirection } from '../data-operations/sorting-strategy';
31-
import { IgxComboState } from './combo.common';
14+
import { IForOfState } from '../directives/for-of/for_of.directive';
15+
import { IgxInputState } from '../directives/input/input.directive';
16+
import { IgxToggleModule } from '../directives/toggle/toggle.directive';
3217
import { IgxDropDownItemBaseDirective } from '../drop-down/public_api';
18+
import { IgxIconService } from '../icon/public_api';
19+
import { AbsoluteScrollStrategy, ConnectedPositioningStrategy } from '../services/public_api';
20+
import { configureTestSuite } from '../test-utils/configure-suite';
21+
import { UIInteractions, wait } from '../test-utils/ui-interactions.spec';
22+
import { IgxComboAddItemComponent } from './combo-add-item.component';
23+
import { IgxComboDropDownComponent } from './combo-dropdown.component';
24+
import { IgxComboItemComponent } from './combo-item.component';
25+
import { IComboFilteringOptions, IgxComboState } from './combo.common';
26+
import {
27+
IComboItemAdditionEvent, IComboSearchInputEventArgs, IComboSelectionChangingEventArgs, IgxComboComponent,
28+
IgxComboModule
29+
} from './combo.component';
30+
import { IgxComboFilteringPipe } from './combo.pipes';
3331

3432
const CSS_CLASS_COMBO = 'igx-combo';
3533
const CSS_CLASS_COMBO_DROPDOWN = 'igx-combo__drop-down';
@@ -2689,14 +2687,14 @@ describe('igxCombo', () => {
26892687
combo.close();
26902688
tick();
26912689
fixture.detectChanges();
2692-
// set filter function to search only on valueKyes
2693-
combo.filterFunction = (collection: any[], searchValue: any): any[] => {
2690+
combo.filteringOptions = { caseSensitive: false, filterable: true, filteringKey: combo.groupKey };
2691+
combo.filterFunction = (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions): any[] => {
26942692
if (!collection) return [];
26952693
if (!searchValue) return collection;
2696-
const searchTerm = combo.filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
2697-
return collection.filter(i => combo.filteringOptions.caseSensitive ?
2698-
i[combo.displayKey]?.includes(searchTerm) || i[combo.groupKey]?.includes(searchTerm) :
2699-
i[combo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[combo.groupKey]?.toString().toLowerCase().includes(searchTerm))
2694+
const searchTerm = filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
2695+
return collection.filter(i => filteringOptions.caseSensitive ?
2696+
i[filteringOptions.filteringKey]?.includes(searchTerm) :
2697+
i[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm))
27002698
}
27012699
combo.open();
27022700
tick();
@@ -2708,11 +2706,12 @@ describe('igxCombo', () => {
27082706
fixture.detectChanges();
27092707
expect(combo.dropdown.items.length).toBeGreaterThan(0);
27102708

2711-
combo.filterFunction = null;
2709+
combo.filterFunction = undefined;
2710+
combo.filteringOptions = undefined;
27122711
fixture.detectChanges();
27132712
expect(combo.dropdown.items.length).toEqual(0);
27142713
}));
2715-
it('Should update filtering when custom filterFunction is provided and filteringOptions are changed', fakeAsync(() => {
2714+
it('Should update filtering when custom filterFunction is provided and filteringOptions.caseSensitive is changed', fakeAsync(() => {
27162715
combo.open();
27172716
tick();
27182717
fixture.detectChanges();
@@ -2726,14 +2725,14 @@ describe('igxCombo', () => {
27262725
combo.close();
27272726
tick();
27282727
fixture.detectChanges();
2729-
// set filter function to search only on valueKyes
2730-
combo.filterFunction = (collection: any[], searchValue: any): any[] => {
2728+
combo.filteringOptions = { caseSensitive: false, filterable: true, filteringKey: combo.groupKey };
2729+
combo.filterFunction = (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions): any[] => {
27312730
if (!collection) return [];
27322731
if (!searchValue) return collection;
2733-
const searchTerm = combo.filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
2734-
return collection.filter(i => combo.filteringOptions.caseSensitive ?
2735-
i[combo.displayKey]?.includes(searchTerm) || i[combo.groupKey]?.includes(searchTerm) :
2736-
i[combo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[combo.groupKey]?.toString().toLowerCase().includes(searchTerm))
2732+
const searchTerm = filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
2733+
return collection.filter(i => filteringOptions.caseSensitive ?
2734+
i[filteringOptions.filteringKey]?.includes(searchTerm) :
2735+
i[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm))
27372736
}
27382737
combo.open();
27392738
tick();
@@ -2745,11 +2744,11 @@ describe('igxCombo', () => {
27452744
fixture.detectChanges();
27462745
expect(combo.dropdown.items.length).toBeGreaterThan(0);
27472746

2748-
combo.filteringOptions = { caseSensitive: true };
2747+
combo.filteringOptions = Object.assign({}, combo.filteringOptions, { caseSensitive: true });
27492748
fixture.detectChanges();
27502749
expect(combo.dropdown.items.length).toEqual(0);
27512750
}));
2752-
it('Should update filtering when custom filterFunction is provided and filterable is changed', fakeAsync(() => {
2751+
it('Should update filtering when custom filteringOptions are provided', fakeAsync(() => {
27532752
combo.open();
27542753
tick();
27552754
fixture.detectChanges();
@@ -2763,15 +2762,7 @@ describe('igxCombo', () => {
27632762
combo.close();
27642763
tick();
27652764
fixture.detectChanges();
2766-
// set filter function to search only on valueKyes
2767-
combo.filterFunction = (collection: any[], searchValue: any): any[] => {
2768-
if (!collection) return [];
2769-
if (!searchValue) return collection;
2770-
const searchTerm = combo.filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
2771-
return collection.filter(i => combo.filteringOptions.caseSensitive ?
2772-
i[combo.displayKey]?.includes(searchTerm) || i[combo.groupKey]?.includes(searchTerm) :
2773-
i[combo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[combo.groupKey]?.toString().toLowerCase().includes(searchTerm))
2774-
}
2765+
combo.filteringOptions = { caseSensitive: false, filterable: true, filteringKey: combo.groupKey };
27752766
combo.open();
27762767
tick();
27772768
fixture.detectChanges();
@@ -2787,7 +2778,7 @@ describe('igxCombo', () => {
27872778
fixture.detectChanges();
27882779
expect(combo.dropdown.items.length).toEqual(0);
27892780

2790-
combo.filterable = false;
2781+
combo.filteringOptions = Object.assign({}, combo.filteringOptions, { filterable: false });
27912782
fixture.detectChanges();
27922783
expect(combo.dropdown.items.length).toBeGreaterThan(0);
27932784
}));

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

+10-9
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,6 @@ import { IgxComboAPIService } from './combo.api';
3636
import { EditorProvider } from '../core/edit-provider';
3737
import { IgxInputGroupType, IGX_INPUT_GROUP_TYPE } from '../input-group/public_api';
3838

39-
/** The filtering criteria to be applied on data search */
40-
export interface IComboFilteringOptions {
41-
/** Defines filtering case-sensitivity */
42-
caseSensitive: boolean;
43-
}
44-
4539
/** Event emitted when an igx-combo's selection is changing */
4640
export interface IComboSelectionChangingEventArgs extends IBaseCancelableEventArgs {
4741
/** An array containing the values that are currently selected */
@@ -124,13 +118,20 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
124118
public autoFocusSearch = true;
125119

126120
/**
121+
* @deprecated in version 14.0.0. Use the IComboFilteringOptions.filterable
122+
*
127123
* An @Input property that enabled/disables filtering in the list. The default is `true`.
128124
* ```html
129125
* <igx-combo [filterable]="false">
130126
* ```
131127
*/
132128
@Input()
133-
public filterable = true;
129+
public get filterable(): boolean {
130+
return this.filteringOptions.filterable;
131+
}
132+
public set filterable(value: boolean) {
133+
this.filteringOptions = Object.assign({}, this.filteringOptions, { filterable: value });
134+
}
134135

135136
/**
136137
* Defines the placeholder value for the combo dropdown search field
@@ -171,7 +172,7 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
171172

172173
/** @hidden @internal */
173174
public get filteredData(): any[] | null {
174-
return this.filterable ? this._filteredData : this.data;
175+
return this.filteringOptions.filterable ? this._filteredData : this.data;
175176
}
176177
/** @hidden @internal */
177178
public set filteredData(val: any[] | null) {
@@ -203,7 +204,7 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
203204

204205
/** @hidden @internal */
205206
public get displaySearchInput(): boolean {
206-
return this.filterable || this.allowCustomValues;
207+
return this.filteringOptions.filterable || this.allowCustomValues;
207208
}
208209

209210
/**

0 commit comments

Comments
 (0)