Skip to content

Commit

Permalink
Merge pull request #11847 from IgniteUI/mvenkov/extend-combo-filterin…
Browse files Browse the repository at this point in the history
…g-options

Extend combos' filtering options - 14.0
  • Loading branch information
Lipata authored Jul 12, 2022
2 parents 6b837e2 + 142d59d commit 8d2221d
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 83 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ All notable changes for each version of this project will be documented in this
### New Features
- `IgxCombo` and `IgxSimpleComboComponent`
- `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.
- `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.
- **Breaking Changes** - `filterable` property of `IgxComboComponent` is now deprecated and will be removed in future version. Use `filteringOptions.filterable` instead.

## 14.0.0

Expand Down
1 change: 1 addition & 0 deletions projects/igniteui-angular/src/lib/combo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ Setting `[displayDensity]` affects the control's items' and inputs' css properti
| `valid` | gets if control is valid, when used in a form | boolean |
| `overlaySettings` | gets/sets the custom overlay settings that control how the drop-down list displays | OverlaySettings |
| `autoFocusSearch` | controls whether the search input should be focused when the combo is opened | boolean |
| `filteringOptions` | Configures the way combo items will be filtered | IComboFilteringOptions |
| `filterFunction` | Gets/Sets the custom filtering function of the combo | `(collection: any[], searchValue: any, caseSensitive: boolean) => any[]` |

### Getters
Expand Down
46 changes: 37 additions & 9 deletions projects/igniteui-angular/src/lib/combo/combo.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ import {
IgxComboAddItemDirective, IgxComboClearIconDirective, IgxComboEmptyDirective,
IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboHeaderItemDirective, IgxComboItemDirective, IgxComboToggleIconDirective
} from './combo.directives';
import {
IComboFilteringOptions, IComboItemAdditionEvent, IComboSearchInputEventArgs
} from './public_api';
import { IComboItemAdditionEvent, IComboSearchInputEventArgs } from './public_api';

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

Expand Down Expand Up @@ -112,6 +110,16 @@ export enum IgxComboState {
INVALID = IgxInputState.INVALID
}

/** The filtering criteria to be applied on data search */
export interface IComboFilteringOptions {
/** Defines filtering case-sensitivity */
caseSensitive: boolean;
/** Defines whether filtering is allowed */
filterable: boolean;
/** Defines optional key to filter against complex list items. Default to displayKey if provided.*/
filteringKey?: string;
}

@Directive()
export abstract class IgxComboBaseDirective extends DisplayDensityBase implements IgxComboBase, OnInit, DoCheck,
AfterViewInit, OnDestroy, ControlValueAccessor {
Expand Down Expand Up @@ -393,7 +401,7 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
* ```
*/
@Input()
public filterFunction: (collection: any[], searchValue: any, caseSensitive: boolean) => any[];
public filterFunction: (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions) => any[];

/**
* An @Input property that set aria-labelledby attribute
Expand Down Expand Up @@ -869,10 +877,28 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
public filterValue = '';
/** @hidden @internal */
public defaultFallbackGroup = 'Other';
/** @hidden @internal */
public filteringOptions: IComboFilteringOptions = {
caseSensitive: false
};

/**
* Configures the way combo items will be filtered.
*
* ```typescript
* // get
* let myFilteringOptions = this.combo.filteringOptions;
* ```
*
* ```html
* <!--set-->
* <igx-combo [filteringOptions]='myFilteringOptions'></igx-combo>
* ```
*/

@Input()
public get filteringOptions(): IComboFilteringOptions {
return this._filteringOptions || this._defaultFilteringOptions;
}
public set filteringOptions(value: IComboFilteringOptions) {
this._filteringOptions = value;
}

protected _data = [];
protected _value = '';
Expand All @@ -893,6 +919,8 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
private _itemsMaxHeight = null;
private _overlaySettings: OverlaySettings;
private _groupSortingDirection: SortingDirection = SortingDirection.Asc;
private _filteringOptions: IComboFilteringOptions;
private _defaultFilteringOptions: IComboFilteringOptions = { caseSensitive: false, filterable: true };

public abstract dropdown: IgxComboDropDownComponent;

Expand Down Expand Up @@ -1160,7 +1188,7 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement

/** @hidden @internal */
public toggleCaseSensitive() {
this.filteringOptions = { caseSensitive: !this.filteringOptions.caseSensitive };
this.filteringOptions = Object.assign({}, this.filteringOptions, { caseSensitive: !this.filteringOptions.caseSensitive });
}

protected onStatusChanged = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
[style.maxHeight.px]="itemsMaxHeight" [igxDropDownItemNavigation]="dropdown" (focus)="dropdown.onFocus()"
[tabindex]="dropdown.collapsed ? -1 : 0" role="listbox" [attr.id]="dropdown.id">
<igx-combo-item role="option" [itemHeight]='itemHeight' *igxFor="let item of data
| comboFiltering:filterValue:displayKey:filteringOptions:filterable:filterFunction
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction
| comboGrouping:groupKey:valueKey:groupSortingDirection;
index as rowIndex; containerSize: itemsMaxHeight; scrollOrientation: 'vertical'; itemSize: itemHeight"
[value]="item" [isHeader]="item.isHeader" [index]="rowIndex">
Expand Down
45 changes: 19 additions & 26 deletions projects/igniteui-angular/src/lib/combo/combo.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { IgxSelectionAPIService } from '../core/selection';
import { IgxIconService } from '../icon/public_api';
import { IBaseCancelableBrowserEventArgs } from '../core/utils';
import { SortingDirection } from '../data-operations/sorting-strategy';
import { IgxComboState } from './combo.common';
import { IComboFilteringOptions, IgxComboState } from './combo.common';
import { IgxDropDownItemBaseDirective } from '../drop-down/public_api';

const CSS_CLASS_COMBO = 'igx-combo';
Expand Down Expand Up @@ -2707,14 +2707,14 @@ describe('igxCombo', () => {
combo.close();
tick();
fixture.detectChanges();
// set filter function to search only on valueKyes
combo.filterFunction = (collection: any[], searchValue: any): any[] => {
combo.filteringOptions = { caseSensitive: false, filterable: true, filteringKey: combo.groupKey };
combo.filterFunction = (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions): any[] => {
if (!collection) return [];
if (!searchValue) return collection;
const searchTerm = combo.filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
return collection.filter(i => combo.filteringOptions.caseSensitive ?
i[combo.displayKey]?.includes(searchTerm) || i[combo.groupKey]?.includes(searchTerm) :
i[combo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[combo.groupKey]?.toString().toLowerCase().includes(searchTerm))
const searchTerm = filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
return collection.filter(i => filteringOptions.caseSensitive ?
i[filteringOptions.filteringKey]?.includes(searchTerm) :
i[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm))
}
combo.open();
tick();
Expand All @@ -2727,10 +2727,11 @@ describe('igxCombo', () => {
expect(combo.dropdown.items.length).toBeGreaterThan(0);

combo.filterFunction = undefined;
combo.filteringOptions = undefined;
fixture.detectChanges();
expect(combo.dropdown.items.length).toEqual(0);
}));
it('Should update filtering when custom filterFunction is provided and filteringOptions are changed', fakeAsync(() => {
it('Should update filtering when custom filterFunction is provided and filteringOptions.caseSensitive is changed', fakeAsync(() => {
combo.open();
tick();
fixture.detectChanges();
Expand All @@ -2744,14 +2745,14 @@ describe('igxCombo', () => {
combo.close();
tick();
fixture.detectChanges();
// set filter function to search only on valueKyes
combo.filterFunction = (collection: any[], searchValue: any): any[] => {
combo.filteringOptions = { caseSensitive: false, filterable: true, filteringKey: combo.groupKey };
combo.filterFunction = (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions): any[] => {
if (!collection) return [];
if (!searchValue) return collection;
const searchTerm = combo.filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
return collection.filter(i => combo.filteringOptions.caseSensitive ?
i[combo.displayKey]?.includes(searchTerm) || i[combo.groupKey]?.includes(searchTerm) :
i[combo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[combo.groupKey]?.toString().toLowerCase().includes(searchTerm))
const searchTerm = filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
return collection.filter(i => filteringOptions.caseSensitive ?
i[filteringOptions.filteringKey]?.includes(searchTerm) :
i[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm))
}
combo.open();
tick();
Expand All @@ -2763,11 +2764,11 @@ describe('igxCombo', () => {
fixture.detectChanges();
expect(combo.dropdown.items.length).toBeGreaterThan(0);

combo.filteringOptions = { caseSensitive: true };
combo.filteringOptions = Object.assign({}, combo.filteringOptions, { caseSensitive: true });
fixture.detectChanges();
expect(combo.dropdown.items.length).toEqual(0);
}));
it('Should update filtering when custom filterFunction is provided and filterable is changed', fakeAsync(() => {
it('Should update filtering when custom filteringOptions are provided', fakeAsync(() => {
combo.open();
tick();
fixture.detectChanges();
Expand All @@ -2781,15 +2782,7 @@ describe('igxCombo', () => {
combo.close();
tick();
fixture.detectChanges();
// set filter function to search only on valueKyes
combo.filterFunction = (collection: any[], searchValue: any): any[] => {
if (!collection) return [];
if (!searchValue) return collection;
const searchTerm = combo.filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
return collection.filter(i => combo.filteringOptions.caseSensitive ?
i[combo.displayKey]?.includes(searchTerm) || i[combo.groupKey]?.includes(searchTerm) :
i[combo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[combo.groupKey]?.toString().toLowerCase().includes(searchTerm))
}
combo.filteringOptions = { caseSensitive: false, filterable: true, filteringKey: combo.groupKey };
combo.open();
tick();
fixture.detectChanges();
Expand All @@ -2805,7 +2798,7 @@ describe('igxCombo', () => {
fixture.detectChanges();
expect(combo.dropdown.items.length).toEqual(0);

combo.filterable = false;
combo.filteringOptions = Object.assign({}, combo.filteringOptions, { filterable: false });
fixture.detectChanges();
expect(combo.dropdown.items.length).toBeGreaterThan(0);
}));
Expand Down
19 changes: 10 additions & 9 deletions projects/igniteui-angular/src/lib/combo/combo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ import { IgxComboAPIService } from './combo.api';
import { EditorProvider } from '../core/edit-provider';
import { IgxInputGroupType, IGX_INPUT_GROUP_TYPE } from '../input-group/public_api';

/** The filtering criteria to be applied on data search */
export interface IComboFilteringOptions {
/** Defines filtering case-sensitivity */
caseSensitive: boolean;
}

/** Event emitted when an igx-combo's selection is changing */
export interface IComboSelectionChangingEventArgs extends IBaseCancelableEventArgs {
/** An array containing the values that are currently selected */
Expand Down Expand Up @@ -124,13 +118,20 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
public autoFocusSearch = true;

/**
* @deprecated in version 14.0.0. Use the IComboFilteringOptions.filterable
*
* An @Input property that enabled/disables filtering in the list. The default is `true`.
* ```html
* <igx-combo [filterable]="false">
* ```
*/
@Input()
public filterable = true;
public get filterable(): boolean {
return this.filteringOptions.filterable;
}
public set filterable(value: boolean) {
this.filteringOptions = Object.assign({}, this.filteringOptions, { filterable: value });
}

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

/** @hidden @internal */
public get filteredData(): any[] | null {
return this.filterable ? this._filteredData : this.data;
return this.filteringOptions.filterable ? this._filteredData : this.data;
}
/** @hidden @internal */
public set filteredData(val: any[] | null) {
Expand Down Expand Up @@ -203,7 +204,7 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie

/** @hidden @internal */
public get displaySearchInput(): boolean {
return this.filterable || this.allowCustomValues;
return this.filteringOptions.filterable || this.allowCustomValues;
}

/**
Expand Down
28 changes: 12 additions & 16 deletions projects/igniteui-angular/src/lib/combo/combo.pipes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { Inject, Pipe, PipeTransform } from '@angular/core';
import { cloneArray } from '../core/utils';
import { DataUtil } from '../data-operations/data-util';
import { DefaultSortingStrategy, SortingDirection } from '../data-operations/sorting-strategy';
import { IgxComboBase, IGX_COMBO_COMPONENT } from './combo.common';
import { IComboFilteringOptions } from './combo.component';
import { IComboFilteringOptions, IgxComboBase, IGX_COMBO_COMPONENT } from './combo.common';

/** @hidden */
@Pipe({ name: 'comboClean' })
Expand All @@ -16,23 +15,20 @@ export class IgxComboCleanPipe implements PipeTransform {
/** @hidden */
@Pipe({ name: 'comboFiltering' })
export class IgxComboFilteringPipe implements PipeTransform {
private displayKey: any;

public transform (
collection: any[],
searchValue: any,
displayKey: any,
filteringOptions: IComboFilteringOptions,
filterable: boolean,
filterFunction: (collection: any[], searchValue: any, caseSensitive: boolean) => any[] = defaultFilterFunction) {
filterFunction: (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions) => any[] = defaultFilterFunction) {
if (!collection) {
return [];
}
if (!filterable) {
if (!filteringOptions.filterable) {
return collection;
}
this.displayKey = displayKey;
return filterFunction.call(this, collection, searchValue, filteringOptions.caseSensitive);
filteringOptions.filteringKey = filteringOptions.filteringKey ?? displayKey;
return filterFunction(collection, searchValue, filteringOptions);
}
}

Expand Down Expand Up @@ -75,17 +71,17 @@ export class IgxComboGroupingPipe implements PipeTransform {
}
}

function defaultFilterFunction (collection: any[], searchValue: any, matchCase: boolean): any[] {
function defaultFilterFunction (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions): any[] {
if (!searchValue) {
return collection;
}
const searchTerm = matchCase ? searchValue.trim() : searchValue.toLowerCase().trim();
if (this.displayKey != null) {
return collection.filter(e => matchCase ?
e[this.displayKey]?.includes(searchTerm) :
e[this.displayKey]?.toString().toLowerCase().includes(searchTerm));
const searchTerm = filteringOptions.caseSensitive ? searchValue.trim() : searchValue.toLowerCase().trim();
if (filteringOptions.filteringKey != null) {
return collection.filter(e => filteringOptions.caseSensitive ?
e[filteringOptions.filteringKey]?.includes(searchTerm) :
e[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm));
} else {
return collection.filter(e => matchCase ?
return collection.filter(e => filteringOptions.caseSensitive ?
e.includes(searchTerm) :
e.toString().toLowerCase().includes(searchTerm));
}
Expand Down
1 change: 1 addition & 0 deletions projects/igniteui-angular/src/lib/simple-combo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ Setting `[displayDensity]` affects the control's items' and inputs' css properti
| `valid` | gets if control is valid, when used in a form. | `boolean` |
| `overlaySettings` | Controls how the dropdown is displayed. | `OverlaySettings` |
| `selected` | Get current selection state. | `Array<any>` |
| `filteringOptions` | Configures the way combo items will be filtered | IComboFilteringOptions |
| `filterFunction` | Gets/Sets the custom filtering function of the combo | `(collection: any[], searchValue: any, caseSensitive: boolean) => any[]` |


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
(keydown)="handleItemKeyDown($event)">
<igx-combo-item role="option" [singleMode]="true" [itemHeight]='itemHeight' (click)="handleItemClick()" *igxFor="let item of data
| comboClean
| comboFiltering:filterValue:displayKey:filteringOptions:true:filterFunction
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction
| comboGrouping:groupKey:valueKey:groupSortingDirection;
index as rowIndex; containerSize: itemsMaxHeight; scrollOrientation: 'vertical'; itemSize: itemHeight"
[value]="item" [isHeader]="item.isHeader" [index]="rowIndex">
Expand Down
5 changes: 4 additions & 1 deletion src/app/combo/combo.sample.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ <h5 class="sample-title">Reactive Form</h5>
<button igxButton="raised" igxRipple (click)="toggleItem('Connecticut')">Toggle "Connecticut"
</button>
<button igxButton="raised" igxRipple (click)="changeItemTemplate()">Change Item Template</button>
<button igxButton="raised" igxRipple (click)="changeFiltering()">Change Filtering</button>
</div>
<div class="btn-container">
<igx-switch (change)="changeFiltering($event)" >Use custom filter function</igx-switch>
<igx-switch (change)="changeFilteringKey($event)" >Filter by group key</igx-switch>
</div>
</div>
<ng-template #customItemTemplate let-display let-key="valueKey">
Expand Down
Loading

0 comments on commit 8d2221d

Please sign in to comment.