Skip to content

Commit

Permalink
Merge pull request #5747 from IgniteUI/grid-exposing-filtering-strate…
Browse files Browse the repository at this point in the history
…gy-master

feat(filtering-strategy): exposing filtering strategy on a grid level
  • Loading branch information
zdrawku authored Sep 17, 2019
2 parents b70d00a + 8109003 commit f7d79c7
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 9 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
All notable changes for each version of this project will be documented in this file.

## 8.2.0

### New theme
Ignite UI for angular now have a new theme that mimics Microsoft "Fluent" design system.
Depending on your use case you can use one of the following mixins:
Expand Down Expand Up @@ -31,6 +30,7 @@ For more information about the theming please read our [documentation](https://w
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
- Advanced Filtering functionality is added. In the advanced filtering dialog, you could create groups of conditions across all grid columns. The advanced filtering button is shown in the grid's toolbar when `allowAdvancedFiltering` and `showToolbar` properties are set to `true`. You could also open/close the advanced filtering dialog using the `openAdvancedFilteringDialog` and `closeAdvancedFilteringDialog` methods.
- `uniqueColumnValuesStrategy` input is added. This property provides a callback for loading unique column values on demand. If this property is provided, the unique values it generates will be used by the Excel Style Filtering (instead of using the unique values from the data that is bound to the grid).
- `[filterStrategy] - input that allows you to override the default filtering strategy`
- `igxExcelStyleLoading` directive is added, which can be used to provide a custom loading template for the Excel Style Filtering. If this property is not provided, a default loading template will be used instead.
- introduced new properties `cellSelection` and `rowSelection` which accept GridSelection mode enumeration. Grid selection mode could be none, single or multiple. Also `hideRowSelectors` property is added, which allows you to show and hide row selectors when row selection is enabled.
- introduced functionality for templating row and header selectors - [spec](https://github.com/IgniteUI/igniteui-angular/wiki/Row-Selection-Templating-(Grid-feature))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
}

export class FilteringStrategy extends BaseFilteringStrategy {
private static _instace: FilteringStrategy = null;

public constructor() { super(); }

public static instance() {
return this._instace || (this._instace = new this());
}

public filter<T>(data: T[], expressionsTree: IFilteringExpressionsTree, advancedExpressionsTree?: IFilteringExpressionsTree): T[] {
let i;
let rec;
Expand Down
23 changes: 23 additions & 0 deletions projects/igniteui-angular/src/lib/grids/grid-base.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import { IgxAdvancedFilteringDialogComponent } from './filtering/advanced-filter
import { IgxColumnResizingService } from './grid-column-resizing.service';
import { IgxHeadSelectorDirective, IgxRowSelectorDirective } from './igx-row-selectors.module';
import { DeprecateProperty } from '../core/deprecateDecorators';
import { IFilteringStrategy } from '../data-operations/filtering-strategy';
import { IgxRowExpandedIndicatorDirective, IgxRowCollapsedIndicatorDirective,
IgxHeaderExpandIndicatorDirective, IgxHeaderCollapseIndicatorDirective } from './grid/grid.directives';
import { GridKeydownTargetType, GridSelectionMode, GridSummaryPosition, GridSummaryCalculationMode, FilterMode } from './common/enums';
Expand Down Expand Up @@ -242,6 +243,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
private _locale = null;
public _destroyed = false;
private overlayIDs = [];
private _filteringStrategy: IFilteringStrategy;

private _hostWidth;
private _advancedFilteringOverlayId: string;
Expand Down Expand Up @@ -1082,6 +1084,27 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
}
}

/**
* Gets the filtering strategy of the grid.
* ```typescript
* let filterStrategy = this.grid.filterStrategy
* ```
*/
@Input()
get filterStrategy(): IFilteringStrategy {
return this._filteringStrategy;
}

/**
* Sets the filtering strategy of the grid.
* ```html
* <igx-grid #grid [data]="localData" [filterStrategy]="filterStrategy"></igx-grid>
* ```
*/
set filterStrategy(classRef: IFilteringStrategy) {
this._filteringStrategy = classRef;
}

/**
* An @Input property that provides a callback for loading unique column values on demand.
* If this property is provided, the unique values it generates will be used by the Excel Style Filtering.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import {
IgxTestExcelFilteringDatePickerComponent,
IgxGridFilteringTemplateComponent,
IgxGridFilteringESFTemplatesComponent,
IgxGridFilteringESFLoadOnDemandComponent
IgxGridFilteringESFLoadOnDemandComponent,
CustomFilteringStrategyComponent
} from '../../test-utils/grid-samples.spec';
import { HelperUtils, resizeObserverIgnoreError } from '../../test-utils/helper-utils.spec';
import { GridSelectionMode, FilterMode } from '../common/enums';
Expand Down Expand Up @@ -3601,7 +3602,8 @@ describe('IgxGrid - Filtering actions - Excel style filtering #grid', () => {
IgxTestExcelFilteringDatePickerComponent,
IgxGridFilteringESFTemplatesComponent,
IgxGridFilteringESFLoadOnDemandComponent,
IgxGridFilteringMCHComponent
IgxGridFilteringMCHComponent,
CustomFilteringStrategyComponent
],
imports: [
NoopAnimationsModule,
Expand Down Expand Up @@ -5843,6 +5845,97 @@ describe('IgxGrid - Filtering actions - Excel style filtering #grid', () => {
GridFunctions.verifyColumnIsPinned(column, true, 8);
}));
});
describe('IgxGrid - Custom Filtering Strategy #grid', () => {
let fix;
let grid;

beforeEach(async(() => {
resizeObserverIgnoreError();
fix = TestBed.createComponent(CustomFilteringStrategyComponent);
grid = fix.componentInstance.grid;
fix.detectChanges();
}));

it('Should be able to set custom filtering strategy', () => {
expect(grid.filterStrategy).toBeUndefined();
grid.filterStrategy = fix.componentInstance.strategy;
fix.detectChanges();

expect(grid.filterStrategy).toEqual(fix.componentInstance.strategy);
});

it('Should be able to override getFieldValue method', fakeAsync(() => {
GridFunctions.clickFilterCellChip(fix, 'Name'); // Name column contains nasted object as a vulue
fix.detectChanges();
GridFunctions.typeValueInFilterRowInput('ca', fix);
tick(50);
GridFunctions.submitFilterRowInput(fix);
tick(50);
fix.detectChanges();

expect(grid.filteredData).toEqual([]);
GridFunctions.resetFilterRow(fix);
GridFunctions.closeFilterRow(fix);
fix.detectChanges();

// Apply the custom strategy and perform the same filter
grid.filterStrategy = fix.componentInstance.strategy;
fix.detectChanges();
GridFunctions.clickFilterCellChip(fix, 'Name');
fix.detectChanges();
GridFunctions.typeValueInFilterRowInput('ca', fix);
tick(50);
GridFunctions.submitFilterRowInput(fix);
tick(50);
fix.detectChanges();

expect(grid.filteredData).toEqual(
[{ ID: 1, Name: { FirstName: 'Casey', LastName: 'Houston' }, JobTitle: 'Vice President', Company: 'Company A' }]);
}));

it('Should be able to override findMatchByExpression method', fakeAsync(() => {
GridFunctions.clickFilterCellChip(fix, 'JobTitle'); // Default strategy is case not sensitive
fix.detectChanges();
GridFunctions.typeValueInFilterRowInput('direct', fix);
tick(50);
GridFunctions.submitFilterRowInput(fix);
tick(50);
fix.detectChanges();

expect(grid.filteredData).toEqual([
{ ID: 2, Name: { FirstName: 'Gilberto', LastName: 'Todd' } , JobTitle: 'Director', Company: 'Company C' },
{ ID: 3, Name: { FirstName: 'Tanya', LastName: 'Bennett' } , JobTitle: 'Director', Company: 'Company A' }]);
GridFunctions.resetFilterRow(fix);
GridFunctions.closeFilterRow(fix);
fix.detectChanges();

// Apply the custom strategy and perform the same filter
grid.filterStrategy = fix.componentInstance.strategy;
fix.detectChanges();
GridFunctions.clickFilterCellChip(fix, 'JobTitle');
fix.detectChanges();
GridFunctions.typeValueInFilterRowInput('direct', fix);
tick(50);
GridFunctions.submitFilterRowInput(fix);
tick(50);
fix.detectChanges();

expect(grid.filteredData).toEqual([]);
}));

it('should use the custom filtering stategy when filter the grid through API method', fakeAsync(() => {
grid.filterStrategy = fix.componentInstance.strategy;
fix.detectChanges();
grid.filter('Name', 'D', IgxStringFilteringOperand.instance().condition('contains'));
tick(30);
fix.detectChanges();

expect(grid.filteredData).toEqual([
{ ID: 7, Name: { FirstName: 'Debra', LastName: 'Morton' } ,
JobTitle: 'Associate Software Developer', Company: 'Company B' },
{ ID: 10, Name: { FirstName: 'Eduardo', LastName: 'Ramirez' }, JobTitle: 'Manager', Company: 'Company E' }]);
}));
});
});

const expectedResults = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
<ng-template igxGridFor let-rowData [igxGridForOf]="data
| gridTransaction:id:pipeTrigger
| visibleColumns:hasVisibleColumns
| gridFiltering:filteringExpressionsTree:advancedFilteringExpressionsTree:id:pipeTrigger
| gridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:id:pipeTrigger
| gridSort:sortingExpressions:id:pipeTrigger
| gridGroupBy:groupingExpressions:groupingExpansionState:groupsExpanded:id:groupsRecords:pipeTrigger
| gridPaging:page:perPage:id:pipeTrigger
Expand Down
3 changes: 3 additions & 0 deletions projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IgxGridComponent } from './grid.component';
import { IGroupingExpression } from '../../data-operations/grouping-expression.interface';
import { GridBaseAPIService } from '../api.service';
import { IgxGridBaseComponent, IGridDataBindable } from '../grid-base.component';
import { IFilteringStrategy } from '../../data-operations/filtering-strategy';

/**
*@hidden
Expand Down Expand Up @@ -130,10 +131,12 @@ export class IgxGridFilteringPipe implements PipeTransform {
constructor(private gridAPI: GridBaseAPIService<IgxGridBaseComponent & IGridDataBindable>) { }

public transform(collection: any[], expressionsTree: IFilteringExpressionsTree,
filterStrategy: IFilteringStrategy,
advancedExpressionsTree: IFilteringExpressionsTree, id: string, pipeTrigger: number) {
const grid = this.gridAPI.grid;
const state = {
expressionsTree: expressionsTree,
strategy: filterStrategy,
advancedExpressionsTree: advancedExpressionsTree
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
<ng-template igxGridFor let-rowData [igxGridForOf]="data
| gridTransaction:id:pipeTrigger
| visibleColumns:hasVisibleColumns
| gridFiltering:filteringExpressionsTree:advancedFilteringExpressionsTree:id:pipeTrigger
| gridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:id:pipeTrigger
| gridSort:sortingExpressions:id:pipeTrigger
| gridHierarchicalPaging:page:perPage:id:pipeTrigger
| gridHierarchical:hierarchicalState:id:primaryKey:childLayoutKeys:pipeTrigger" let-rowIndex="index"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IgxTreeGridFilteringComponent, IgxTreeGridFilteringRowEditingComponent
import { TreeGridFunctions } from '../../test-utils/tree-grid-functions.spec';
import { configureTestSuite } from '../../test-utils/configure-suite';
import { IgxStringFilteringOperand, IgxNumberFilteringOperand, IgxDateFilteringOperand } from '../../data-operations/filtering-condition';
import { FilteringStrategy } from '../../data-operations/filtering-strategy';

describe('IgxTreeGrid - Filtering actions #tGrid', () => {
configureTestSuite();
Expand Down Expand Up @@ -389,6 +390,45 @@ describe('IgxTreeGrid - Filtering actions #tGrid', () => {
// if there are any parent nodes in this collection then the changes were preserved
expect(filteredParentNodes.length).toBeGreaterThan(0);
}));
});

it('should be able to apply custom filter strategy', fakeAsync(() => {
expect(treeGrid.filterStrategy).toBeUndefined();
treeGrid.filter('Name', 'd', IgxStringFilteringOperand.instance().condition('contains'), true);
tick(30);
fix.detectChanges();

expect(treeGrid.rowList.length).toBe(9);

treeGrid.clearFilter();
fix.detectChanges();
// tslint:disable-next-line: no-use-before-declare
const customFilter = new CustomTreeGridFilterStrategy();
// apply the same filter condition but with custu
treeGrid.filterStrategy = customFilter;
fix.detectChanges();

treeGrid.filter('Name', 'd', IgxStringFilteringOperand.instance().condition('contains'), true);
tick(30);
fix.detectChanges();

expect(treeGrid.rowList.length).toBe(4);
expect(treeGrid.filteredData.map(rec => rec.ID)).toEqual([ 847, 225, 663, 141]);
}));
});
class CustomTreeGridFilterStrategy extends FilteringStrategy {

public filter(data: [], expressionsTree): any[] {
const result = [];
if (!expressionsTree || !expressionsTree.filteringOperands ||
expressionsTree.filteringOperands.length === 0 || !data.length) {
return data;
}
data.forEach((rec: any) => {
if (this.matchRecord(rec.data, expressionsTree)) {
result.push(rec);
}
});
return result;
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
| treeGridTransaction:id:pipeTrigger
| visibleColumns:hasVisibleColumns
| treeGridHierarchizing:primaryKey:foreignKey:childDataKey:id:pipeTrigger
| treeGridFiltering:filteringExpressionsTree:advancedFilteringExpressionsTree:id:pipeTrigger
| treeGridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:id:pipeTrigger
| treeGridSorting:sortingExpressions:id:pipeTrigger
| treeGridFlattening:id:expansionDepth:expansionStates:pipeTrigger
| treeGridPaging:page:perPage:id:pipeTrigger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Pipe, PipeTransform } from '@angular/core';
import { DataUtil } from '../../data-operations/data-util';
import { GridBaseAPIService } from '../api.service';
import { IgxTreeGridComponent } from './tree-grid.component';
import { BaseFilteringStrategy, IFilteringStrategy } from '../../data-operations/filtering-strategy';
import { IFilteringExpressionsTree, FilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree';
import { BaseFilteringStrategy } from '../../data-operations/filtering-strategy';
import { IFilteringState } from '../../data-operations/filtering-state.interface';
import { ITreeGridRecord } from './tree-grid.interfaces';
import { IgxTreeGridAPIService } from './tree-grid-api.service';
Expand Down Expand Up @@ -62,14 +62,19 @@ export class IgxTreeGridFilteringPipe implements PipeTransform {
}

public transform(hierarchyData: ITreeGridRecord[], expressionsTree: IFilteringExpressionsTree,
filterStrategy: IFilteringStrategy,
advancedFilteringExpressionsTree: IFilteringExpressionsTree, id: string, pipeTrigger: number): ITreeGridRecord[] {
const grid: IgxTreeGridComponent = this.gridAPI.grid;
const state = {
const state: IFilteringState = {
expressionsTree: expressionsTree,
advancedExpressionsTree: advancedFilteringExpressionsTree,
strategy: new TreeGridFilteringStrategy()
};

if (filterStrategy) {
state.strategy = filterStrategy;
}

this.resetFilteredOutProperty(grid.records);

if (FilteringExpressionsTree.empty(state.expressionsTree) && FilteringExpressionsTree.empty(state.advancedExpressionsTree)) {
Expand Down
40 changes: 40 additions & 0 deletions projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,46 @@ export class IgxGridFilteringComponent extends BasicGridComponent {
}
}

export class CustomFilterStrategy extends FilteringStrategy {
public constructor() { super(); }

public findMatchByExpression(rec: object, expr): boolean {
const cond = expr.condition;
const val = this.getFieldValue(rec, expr.fieldName);
const ignoreCase = expr.fieldName === 'JobTitle' ? false : true;
return cond.logic(val, expr.searchVal, ignoreCase);
}

public filter<T>(data: T[], expressionsTree: IFilteringExpressionsTree): T[] {
return super.filter(data, expressionsTree);
}

public getFieldValue(rec: object, fieldName: string): any {
return fieldName === 'Name' ? rec[fieldName]['FirstName'] : rec[fieldName];
}
}

@Component({
template: `<igx-grid [data]="data" height="500px" width="600px" [allowFiltering]='true'>
<igx-column [field]="'ID'" [header]="'ID'" [filterable]="false"></igx-column>
<igx-column width="100px" [field]="'Name'" [filterable]="filterable">
<ng-template igxCell let-val>
<span>{{val.FirstName}}</span>
</ng-template>
</igx-column>
<igx-column [field]="'JobTitle'" [filterable]="filterable" ></igx-column>
<igx-column [field]="'Company'" [filterable]="filterable" ></igx-column>
</igx-grid>`
})
export class CustomFilteringStrategyComponent extends BasicGridComponent {
public strategy = new CustomFilterStrategy();
public filterable = true;

public data = SampleTestData.personNameObjectJobCompany();
}



@Component({
template: `<igx-grid [data]="data" height="500px" [allowFiltering]='true'
[filterMode]="'excelStyleFilter'" [uniqueColumnValuesStrategy]="columnValuesStrategy">
Expand Down
Loading

0 comments on commit f7d79c7

Please sign in to comment.