-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ngrid): enhance sorting and filtering APIs (#20)
* feat(ngrid): enhance sorting and filtering APIs (#19)
- Loading branch information
1 parent
abae5a5
commit 9961314
Showing
21 changed files
with
613 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
209 changes: 209 additions & 0 deletions
209
.../src/lib/modules/column-features/column-filtering/column-filtering.component.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
# Column Filtering | ||
|
||
Column filtering can be set / configured in 2 locations: | ||
|
||
1. DataSource (API) | ||
2. Grid instance (API) | ||
|
||
In addition, you can configure a column specific filtering behaviour in the column definitions. | ||
|
||
<blockquote class="warn icon"> | ||
<div class="icon-location"></div> | ||
This page refer to client-side filtering where filtering is done in the browser and not on the server. For more information read the <a [routerLink]="['../..', 'concepts', 'datasource-quickthrough']" fragment="client-side">datasource quick-through</a>. | ||
</blockquote> | ||
|
||
## Activating a filter | ||
|
||
Activating is done through on of the APIs (DataSource or Grid). | ||
|
||
There are 2 filter types: | ||
|
||
- Value filter | ||
- Predicate filter | ||
|
||
**Value filter** is managed by the grid, we are activating the filter by providing a value that we want to find a match to in one or more columns. | ||
If the value has a match the row will pass the filter, otherwise it will get filtered out. | ||
|
||
**Predicate Filter** is un-managed, we have full control over the filtering process. We provide a predicate function that accepts a row | ||
and a collection of columns and return `true` if the row passed the filter and `false` if not. | ||
|
||
I> In the most basic level all filters are `predicates`, the `value` filter is just an API to organize and simplify the use of function predicates. | ||
|
||
## Value Filter | ||
|
||
The value filter approach is simple and consistent across grid's. | ||
It relays on pre-defined filtering behaviour which is used to filter the value. | ||
|
||
The grid comes with one pre-defined behaviour that compares the inclusion (text) of the provided value in a column value. | ||
For example, if the filter value is "oh" and the value in the column `name` for a given row is "John", it will pass the filter. | ||
|
||
Here we search for `oh` in either column `name` or `email`: | ||
|
||
```typescript | ||
grid.setFilter('oh', ['name', 'email']); | ||
``` | ||
|
||
I> In the example above me make use of the `Grid instance (API)`, which allows us to provide a string reference to a column | ||
|
||
Of course, this is too generic, filtering the value **11** will return `211`, `11`, `341123`, etc... | ||
|
||
### Value filter behaviour | ||
|
||
To really leverage the value filter we need to control how certain column filter certain values. | ||
|
||
This is done in the column definition, where we can set the `filter` property which will act as a predicate | ||
specifically for the column it is defined on. | ||
|
||
```typescript | ||
export interface PblColumnDefinition extends PblBaseColumnDefinition { | ||
// ... | ||
|
||
/** | ||
* A custom predicate function to filter rows using the current column. | ||
* | ||
* Valid only when filtering by value. | ||
* See `PblDataSource.setFilter` for more information. | ||
*/ | ||
filter?: DataSourceColumnPredicate; | ||
/** | ||
* Additional data to be used by the filter | ||
*/ | ||
filterData?: any; | ||
// ... | ||
} | ||
``` | ||
|
||
The definition for `DataSourceColumnPredicate`: | ||
|
||
```typescript | ||
/** | ||
* A function the return true then the value should be included in the result or false when not. | ||
* This is a single column filter predicated, returning false will filter out the entire row but the | ||
* predicate is only intended to filter a specific column. | ||
*/ | ||
export type DataSourceColumnPredicate = (filterValue: any, colValue: any, row?: any, col?: PblColumn) => boolean; | ||
``` | ||
|
||
A function that accept a `filterValue` (the value to filter by), a `colValue` (the value of the cell for the column for a given row), the row and the column instance. | ||
|
||
For example, a numeric filter: | ||
|
||
```typescript | ||
const numericFilter = (filterValue: number, colValue: number) => filterValue === colValue | ||
``` | ||
|
||
Now, let's build a numeric range filter: | ||
|
||
```typescript | ||
const numericFilter = (filterValue: number, colValue: number) => colValue > ??? && colValue < ??? | ||
``` | ||
We're stuck, we need the min / max value of the range. The `filterValue` does not have to be of the same type of the column we're filtering... | ||
```typescript | ||
const numericRangeFilter = (filterValue: { min: number, max: number }, colValue: number) => colValue > filterValue.min && colValue < filterValue.max | ||
``` | ||
Which we will activate like this: | ||
```typescript | ||
grid.setFilter({ min: 21, max: 120 }, ['age']); | ||
``` | ||
These are simple examples, they can be extended per your requirements, using a dedicated library or your own custom logic. | ||
### Real-world value filters | ||
As you might have realized by now, value filters are powerful because they allow to create a reusable filtering system | ||
for the entire app without much effort. | ||
Combined with the `type` property you can automate the assignment of filters based on types. | ||
I> In the future, we hope to release a column pack package with pre-defined sorting and filtering predicates attached to | ||
pre-defined types. | ||
### Value filter cons | ||
Currently, you can't join 2 (or more) values filters into a single AND or OR expression | ||
<docsi-mat-example-with-source title="Value Filter" contentClass="table-height-300 mat-elevation-z7" [query]="[{section: 'ex-1'}]"> | ||
<!--@pebula-example:ex-1--> | ||
<div fxLayout="row" fxLayoutGap="16px" style="padding: 8px"> | ||
<button fxFlex="noshrink" mat-stroked-button color="primary" (click)="filterBalance(true)">Balance: Negative</button> | ||
<button fxFlex="noshrink" mat-stroked-button color="primary" (click)="filterBalance(false)">Balance: Positive</button> | ||
<div fxFlex="*"></div> | ||
<button fxFlex="noshrink" mat-stroked-button color="primary" (click)="clearFilter()">Clear Filter</button> | ||
</div> | ||
<pbl-ngrid blockUi [dataSource]="ds" [columns]="columns"></pbl-ngrid> | ||
<div>Filtered Rows: {{ ds.filteredData.length }}</div> | ||
<!--@pebula-example:ex-1--> | ||
</docsi-mat-example-with-source> | ||
## Predicate Filter | ||
The predicate filter is the "hands-on" manual approach. | ||
Instead of accepting a value, it accepts a function of type `DataSourcePredicate`: | ||
```typescript | ||
/** | ||
* A function the return true then the row should be included in the result or false when not. | ||
* @param row The row in the data source that the filter apply on | ||
* @param properties A list of column instances (`PblColumn`) to filter values by. | ||
*/ | ||
export type DataSourcePredicate = (row: any, properties: PblColumn[]) => boolean; | ||
``` | ||
Like the value filter predicate function (`DataSourceColumnPredicate`) its job is to determine if a row is passed | ||
the filter or not (filtered out). | ||
However, value filter predicates work on the column level, a specific column in a row. | ||
Here we work on the row level, the grid will provide the rows to the filter, one by one, including the columns participating the in filtering process. | ||
The filter is responsible from here, for the entire row including it's columns. | ||
In most cases, value filters will do the job, but for more complex scenarios we can use the predicate filter. | ||
<p>A good example for predicate filtering in action is <a [routerLink]="['../..', 'stories', 'multi-column-filter']">the multi-column filter</a> which demonstrate | ||
filtering using 2 (or more) columns at the same time.</p> | ||
I> Value filters are implemented using a special predicate filter that manage the process. | ||
### Syncing the filter | ||
Syncing the filter is simply re-running the filtering process with the current filter registered. | ||
Calling `setFilter` with the same filter function will not work because the filter is cached. | ||
```typescript | ||
grid.setFilter({myPredicateFunction, ['age']); | ||
``` | ||
|
||
Instead, use: | ||
|
||
```typescript | ||
grid.ds.syncFilter(); | ||
``` | ||
|
||
You might ask why a re-running the filter is needed? great question! | ||
|
||
If you're running a value filter then no, you don't need to sync the filter, just call `setFilter` with the new value. | ||
|
||
If you're running a predicate filter, use the `syncFilter` to re-run the filter when the value changes. In predicate filter there is | ||
no "value" to filter by, you provide it so a mechanism to update the filter based on value updates is required. | ||
|
||
The the multi-column filter example (link above) makes use of `syncFilter`. | ||
|
||
## Clearing The filter | ||
|
||
To clear the current filter call the `setFilter` method with no parameters | ||
|
||
```typescript | ||
grid.setFilter(); | ||
``` | ||
|
||
## Accessing Filtered Data | ||
|
||
The data store holds the last filtered subset, located in `PblDataSource.filteredData`. | ||
|
||
The `filteredData` property will always return an array, either empty or populated. |
Empty file.
43 changes: 43 additions & 0 deletions
43
...d/features/src/lib/modules/column-features/column-filtering/column-filtering.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* @pebula-example:ex-1 */ | ||
import { ChangeDetectionStrategy, Component } from '@angular/core'; | ||
import { createDS, columnFactory, PblNgridSortOrder } from '@pebula/ngrid'; | ||
import { Person, DemoDataSource } from '@pebula/apps/ngrid/shared'; | ||
|
||
const numericFilter = (filterValue: number, colValue: number) => filterValue === colValue | ||
const numericRangeFilter = (filterValue: { min: number, max: number }, colValue: number) => colValue > filterValue.min && colValue < filterValue.max | ||
|
||
const COLUMNS = columnFactory() | ||
.default({minWidth: 100}) | ||
.table( | ||
{ prop: 'id', width: '40px' }, | ||
{ prop: 'name' }, | ||
{ prop: 'gender', width: '50px' }, | ||
{ prop: 'balance', width: '200px', filter: numericRangeFilter }, | ||
) | ||
.build(); | ||
|
||
@Component({ | ||
selector: 'pbl-column-filtering-grid-example-component', | ||
templateUrl: './column-filtering.component.html', | ||
styleUrls: ['./column-filtering.component.scss'], | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class ColumnFilteringGridExampleComponent { | ||
columns = COLUMNS; | ||
ds = createDS<Person>().onTrigger( () => this.datasource.getPeople(500) ).create(); | ||
|
||
constructor(private datasource: DemoDataSource) { } | ||
|
||
clearFilter(): void { | ||
this.ds.setFilter(); | ||
} | ||
|
||
filterBalance(negative: boolean): void { | ||
if (negative) { | ||
this.ds.hostGrid.setFilter({ min: Number.MIN_SAFE_INTEGER, max: 0}, [ 'balance' ]); | ||
} else { | ||
this.ds.hostGrid.setFilter({ min: 0, max: Number.MAX_SAFE_INTEGER}, [ 'balance' ]); | ||
} | ||
} | ||
} | ||
/* @pebula-example:ex-1 */ |
14 changes: 14 additions & 0 deletions
14
libs/apps/ngrid/features/src/lib/modules/column-features/column-filtering/docsi.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"mdTemplate": [ "./column-filtering.component.md" ], | ||
"codeRef": [ | ||
{ | ||
"file": "./column-filtering.component.ts", | ||
"section": ["ex-1"] | ||
}, | ||
{ | ||
"file": "./column-filtering.component.md", | ||
"lang": "html", | ||
"section": ["ex-1"] | ||
} | ||
] | ||
} |
Oops, something went wrong.