diff --git a/CHANGELOG.md b/CHANGELOG.md index d4d2f2f00f5..0df6068e80b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/projects/igniteui-angular/src/lib/combo/README.md b/projects/igniteui-angular/src/lib/combo/README.md index 417341c70ed..20e016d5b5a 100644 --- a/projects/igniteui-angular/src/lib/combo/README.md +++ b/projects/igniteui-angular/src/lib/combo/README.md @@ -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 diff --git a/projects/igniteui-angular/src/lib/combo/combo.common.ts b/projects/igniteui-angular/src/lib/combo/combo.common.ts index 031c4ff315e..bcde6eed190 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.common.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.common.ts @@ -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('IgxComboComponentToken'); @@ -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 { @@ -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 @@ -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 + * + * + * ``` + */ + + @Input() + public get filteringOptions(): IComboFilteringOptions { + return this._filteringOptions || this._defaultFilteringOptions; + } + public set filteringOptions(value: IComboFilteringOptions) { + this._filteringOptions = value; + } protected _data = []; protected _value = ''; @@ -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; @@ -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 = () => { diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.html b/projects/igniteui-angular/src/lib/combo/combo.component.html index acdbd33dfaa..cf984c3271e 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.html +++ b/projects/igniteui-angular/src/lib/combo/combo.component.html @@ -51,7 +51,7 @@ [style.maxHeight.px]="itemsMaxHeight" [igxDropDownItemNavigation]="dropdown" (focus)="dropdown.onFocus()" [tabindex]="dropdown.collapsed ? -1 : 0" role="listbox" [attr.id]="dropdown.id"> diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index ab6a70d053a..5901faebe0a 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -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'; @@ -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(); @@ -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(); @@ -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(); @@ -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(); @@ -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(); @@ -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); })); diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.ts b/projects/igniteui-angular/src/lib/combo/combo.component.ts index 402065f27e6..f1675c0599a 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.ts @@ -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 */ @@ -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 * * ``` */ @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 @@ -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) { @@ -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; } /** diff --git a/projects/igniteui-angular/src/lib/combo/combo.pipes.ts b/projects/igniteui-angular/src/lib/combo/combo.pipes.ts index e31959e969b..2aa4e425519 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.pipes.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.pipes.ts @@ -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' }) @@ -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); } } @@ -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)); } diff --git a/projects/igniteui-angular/src/lib/simple-combo/README.md b/projects/igniteui-angular/src/lib/simple-combo/README.md index 782e591b599..d20f6e529ca 100644 --- a/projects/igniteui-angular/src/lib/simple-combo/README.md +++ b/projects/igniteui-angular/src/lib/simple-combo/README.md @@ -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` | +| `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[]` | diff --git a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html index 0896cd68019..9db99dfe5de 100644 --- a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html +++ b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html @@ -52,7 +52,7 @@ (keydown)="handleItemKeyDown($event)"> diff --git a/src/app/combo/combo.sample.html b/src/app/combo/combo.sample.html index 103ccb29f79..caf6337cd1b 100644 --- a/src/app/combo/combo.sample.html +++ b/src/app/combo/combo.sample.html @@ -99,7 +99,10 @@
Reactive Form
- + +
+ Use custom filter function + Filter by group key
diff --git a/src/app/combo/combo.sample.ts b/src/app/combo/combo.sample.ts index e96d0528586..f795116bd6f 100644 --- a/src/app/combo/combo.sample.ts +++ b/src/app/combo/combo.sample.ts @@ -1,12 +1,23 @@ -import { Component, ViewChild, OnInit, TemplateRef, AfterViewInit, ElementRef } from '@angular/core'; -import { IgxComboComponent, IComboSelectionChangingEventArgs, - DisplayDensity, OverlaySettings, VerticalAlignment, HorizontalAlignment, GlobalPositionStrategy, - scaleInCenter, scaleOutCenter, ElasticPositionStrategy, ConnectedPositioningStrategy, IgxSimpleComboComponent -} from 'igniteui-angular'; -import { ButtonGroupAlignment } from 'igniteui-angular'; -import { take } from 'rxjs/operators'; +import { AfterViewInit, Component, ElementRef, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; +import { + ButtonGroupAlignment, + ConnectedPositioningStrategy, + DisplayDensity, + ElasticPositionStrategy, + GlobalPositionStrategy, + HorizontalAlignment, + IChangeSwitchEventArgs, + IComboSelectionChangingEventArgs, + IgxComboComponent, + IgxSimpleComboComponent, + OverlaySettings, + scaleInCenter, + scaleOutCenter, + VerticalAlignment } from 'igniteui-angular'; import { cloneDeep } from 'lodash'; -import { UntypedFormGroup, Validators, UntypedFormControl, UntypedFormBuilder } from '@angular/forms'; +import { IComboFilteringOptions } from 'projects/igniteui-angular/src/lib/combo/combo.common'; +import { take } from 'rxjs/operators'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -23,7 +34,6 @@ export class ComboSampleComponent implements OnInit, AfterViewInit { private customItemTemplate; @ViewChild('simpleCombo', { read: IgxSimpleComboComponent, static: true }) private simpleCombo; - private hasCustomFilter = false; public alignment: ButtonGroupAlignment = ButtonGroupAlignment.vertical; public toggleItemState = false; @@ -185,25 +195,33 @@ export class ComboSampleComponent implements OnInit, AfterViewInit { console.log(event); } - public changeFiltering() { - if (this.hasCustomFilter) { - this.igxCombo.filterFunction = undefined; - this.simpleCombo.filterFunction = undefined; - } else { + public changeFiltering(e: IChangeSwitchEventArgs) { + if (e.checked) { this.igxCombo.filterFunction = this.customFilterFunction; this.simpleCombo.filterFunction = this.customFilterFunction; + } else { + this.igxCombo.filterFunction = undefined; + this.simpleCombo.filterFunction = undefined; } + } - this.hasCustomFilter = !this.hasCustomFilter; + public changeFilteringKey(e: IChangeSwitchEventArgs) { + if (e.checked) { + this.igxCombo.filteringOptions.filteringKey = 'region'; + this.simpleCombo.filteringOptions.filteringKey = 'region'; + } else { + this.igxCombo.filteringOptions.filteringKey = undefined; + this.simpleCombo.filteringOptions.filteringKey = undefined; + } } - private customFilterFunction = (collection: any[], filterValue: any) => { + private customFilterFunction = (collection: any[], filterValue: any, filteringOptions: IComboFilteringOptions) => { if (!filterValue) { return collection; } - const searchTerm = this.igxCombo.filteringOptions.caseSensitive ? filterValue.trim() : filterValue.toLowerCase().trim(); - return collection.filter(i => this.igxCombo.filteringOptions.caseSensitive ? - i[this.igxCombo.displayKey]?.includes(searchTerm) || i[this.igxCombo.groupKey]?.includes(searchTerm) : - i[this.igxCombo.displayKey]?.toString().toLowerCase().includes(searchTerm) || i[this.igxCombo.groupKey]?.toString().toLowerCase().includes(searchTerm)) + const searchTerm = filteringOptions.caseSensitive ? filterValue.trim() : filterValue.toLowerCase().trim(); + return collection.filter(i => filteringOptions.caseSensitive ? + i[filteringOptions.filteringKey]?.includes(searchTerm) || i[this.igxCombo.groupKey]?.includes(searchTerm) : + i[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm) || i[this.igxCombo.groupKey]?.toString().toLowerCase().includes(searchTerm)) } }