Skip to content

Commit

Permalink
fix(ngrid): colum specific sorting is not working
Browse files Browse the repository at this point in the history
Fixes #45
  • Loading branch information
shlomiassaf committed Sep 4, 2019
1 parent b60f41e commit 39428ad
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import { PblNgridModule } from '@pebula/ngrid';
import { BindNgModule } from '@pebula/apps/shared';
import { ExampleCommonModule } from '@pebula/apps/ngrid-examples/example-common';
import { ColumnSortExample } from './column-sort.component';
import { ColumnSpecificSortingExample } from './column-specific-sorting.component';

@NgModule({
declarations: [ ColumnSortExample ],
declarations: [ ColumnSortExample, ColumnSpecificSortingExample ],
imports: [
CommonModule,
ExampleCommonModule,
PblNgridModule,
],
exports: [ ColumnSortExample ],
entryComponents: [ ColumnSortExample ],
exports: [ ColumnSortExample, ColumnSpecificSortingExample ],
entryComponents: [ ColumnSortExample, ColumnSpecificSortingExample ],
})
@BindNgModule(ColumnSortExample)
@BindNgModule(ColumnSortExample, ColumnSpecificSortingExample)
export class ColumnSortExampleModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<pbl-ngrid [dataSource]="ds" [columns]="columns">
<div *pblNgridHeaderCellDef="'*'; col as col;">
{{col.label}}
<span *ngIf="ds.sort.column && ds.sort.column.id === col.id"> [{{ ds.sort.sort.order | uppercase }}]</span>
</div>
</pbl-ngrid>

<div fxLayout="row" fxLayoutGap="16px" style="padding: 8px">
<button *ngFor="let key of ['name', 'settings.emailFrequency']"
fxFlex="noshrink" mat-stroked-button color="primary" (click)="toggleActive(key)">{{ key }} [{{ getNextDirection(key) }}]</button>
<div fxFlex="*"></div>
<button fxFlex="noshrink" mat-stroked-button color="accent" (click)="clear()">Clear</button>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { createDS, columnFactory, PblNgridSorter, PblColumn, PblNgridSortInstructions, PblNgridSortOrder } from '@pebula/ngrid';

import { Person, DemoDataSource } from '@pebula/apps/shared-data';
import { Example } from '@pebula/apps/shared';

/**
* Sorts by the length of the field
*/
const lengthSorter: PblNgridSorter = (column: PblColumn, sort: PblNgridSortInstructions, data: any[]): any[] => {
const tol = sort.order === 'desc' ? -1 : 1;
return data.sort( (p1: any, p2: any) => {
const v1 = column.getValue(p1) || '';
const v2 = column.getValue(p2) || '';
if (v1.length > v2.length) {
return -1 * tol;
} else if (v2.length > v1.length) {
return 1 * tol;
}
return 0;
});
}

/**
* Sorts by the email frequency of the field
*/
const emailFrequencySorter: PblNgridSorter = (column: PblColumn, sort: PblNgridSortInstructions, data: any[]): any[] => {
const FREQ_MAP = {
Never: 0,
Yearly: 2,
Seldom: 3,
Often: 4,
Weekly: 5,
Daily: 6
};

const tol = sort.order === 'desc' ? -1 : 1;
return data.sort( (p1: any, p2: any) => {
const v1 = FREQ_MAP[column.getValue(p1) || 'Never'];
const v2 = FREQ_MAP[column.getValue(p2) || 'Never'];
if (v1 > v2) {
return -1 * tol;
} else if (v2 > v1) {
return 1 * tol;
}
return 0;
});
}

@Component({
selector: 'pbl-column-specific-sorting-example',
templateUrl: './column-specific-sorting.component.html',
styleUrls: ['./column-specific-sorting.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
@Example('pbl-column-specific-sorting-example', { title: 'Column Specific Sorting' })
export class ColumnSpecificSortingExample {
columns = columnFactory()
.default({minWidth: 100})
.table(
{ prop: 'id', width: '40px' },
{ prop: 'name', sort: lengthSorter },
{ prop: 'gender',width: '50px' },
{ prop: 'settings.emailFrequency', sort: emailFrequencySorter }
)
.build();
ds = createDS<Person>().onTrigger( () => this.datasource.getPeople(100, 500) ).create();

constructor(private datasource: DemoDataSource) { }

clear(): void {
this.ds.setSort();
}

toggleActive(columnId: string): void {
const currentSort = this.ds.sort;
let order: PblNgridSortOrder = 'asc';
if (currentSort && currentSort.column && currentSort.column.id === columnId) {
order = currentSort.sort && currentSort.sort.order as any;
if (order === 'asc') {
order = 'desc';
} else if (order === 'desc') {
this.clear();
return;
} else {
order = 'asc';
}
}
this.ds.hostGrid.setSort(columnId, { order });
}

getNextDirection(key: string): string {
const sort = this.ds.sort;
if (!sort.column || sort.column.id !== key) {
return 'asc';
} else {
return sort.sort.order === 'asc' ? 'desc' : 'clear';
}
}
}
114 changes: 84 additions & 30 deletions apps/ngrid-demo-app/content/features/column/column-sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,69 @@ ordinal: 4
---
# Column Sorting

Column sorting is activated and configured in 2 locations:
In NGrid, column sorting is the re-ordering of datasource items based on **logic** and **sort order** (criterion).

1. DataSource (API)
2. Grid instance (API)
> This section covers the basics of sorting, custom sorting and programmatic sorting. For UI reactive sorting (click on header to sort) see the [sorting section](../../../plugins/ngrid-material/mat-sort) in the material plugin.
In addition, you can configure a column specific sorting behaviour in the column definitions.
## Sort Order

## Column definitions
The `sort order` defines the sort state (on/off) and logical order:

In the **Column** definitions we define if the column is sortable and optionally how to sort it.
- **asc** - Ascending order (start to end, 1 to 10, A to Z)
- **desc** - Descending order (end to start, 10 to 1, A to A)
- `undefined` - Sorting is not enabled

```typescript
export interface PblColumnDefinition extends PblBaseColumnDefinition {
// ...
## Sort Logic

sort?: boolean | PblNgridSorter;
The sort logic is where we determine if a value comes before or after another value.

// ...
For example, sorting a numeric column will be based on the decimal numeric system. Sorting of a textual column will be based on the alphabet order.

## Sorting Function

To sort a column a sorting function is **required**. The sorting function accepts the **column**, **sort order** & **data set** and returns a sorted data-set, it is the implementation of the sort logic.

```typescript
export interface PblNgridSorter<T = any> {
(column: PblColumn, sort: PblNgridSortInstructions, data: T[]): T[];
}
```

Setting `sort: true` will mark the column as sortable and the sort logic is to be sent when setting the current sort.
I> The sorting function is **required** for every column we want to sort but there is a default sorting function attached to all sortable column if no custom one attached.

Or, we can provide a sorting logic for the column via `PblNgridSorter`:
## Sortable Columns

By default, columns are not sortable, to enable sorting the `sort` property must be set on the column definitions:

```typescript
export type PblNgridSortOrder = 'asc' | 'desc';
export interface PblColumnDefinition extends PblBaseColumnDefinition {
// ...

export interface PblNgridSortInstructions {
order?: PblNgridSortOrder;
}
sort?: boolean | PblNgridSorter;

export interface PblNgridSorter<T = any> {
(column: PblColumn, sort: PblNgridSortInstructions, data: T[]): T[];
// ...
}
```

Note that you can set a `boolean` or a sorting function (`PblNgridSorter`).

- If `true` is set, the column is marked sortable and no sorting logic is attached to it.
- If a sorting function is set, the column is marked sortable and the custom sorting logic is stored.

Wether `true` or a function is set, the sort function used is picked when sorting is applied. This is covered later in "Picking a Sorting function"

W> Note that defining `sort` for a column does not activate it, to activate a sorting for a column you need to use the API.

## DataSource (API)
## Activating Sort for a Column

Column sorting can be activated 2 locations:

Through the datasource (`PblDataSource`) we define the sorting programmatically.
1. DataSource API
2. Grid Instance API

We can define the current sorted column, the direction of the sort (`asc` or `desc`) and an optional custom sort logic.
Both APIs are similar, with the Grid API adding some sugar on top of the DataSource API.

If we don't supply any of those, it will just clear the active sort.
Let's take a quick look at the **DataSource API** signature:

```typescript
/**
Expand All @@ -70,19 +87,56 @@ If we don't supply any of those, it will just clear the active sort.
setSort(column: PblColumn, sort: PblNgridSortDefinition, skipUpdate?: boolean): void;
```

### Grid instance (API)
The 1st signature is used for clearing up the current sort.
The 2nd signature is used for defining a new sort.

The **Grid Instance API** is quite identical:

The grid instance API is just sugar around the datasource API, allowing you to reference columns by their **id** or **alias**.
```typescript
setSort(skipUpdate?: boolean): void;
setSort(columnOrSortAlias: PblColumn | string, sort: PblNgridSortDefinition, skipUpdate?: boolean): void;
```

Internally, it will resolve the column instances and call the datasource API.
The difference is that we can provide a column id or alias instance of a column instance and the grid will find the instance for us and use
the DataSource API to activate the sort.

<blockquote class="info icon">
<div class="icon-location"></div>
This section deals with the programmatic approach to sorting. For UI reactive sorting (click on header to sort) see the <a [routerLink]="['../..', 'extensions', 'mat-sort']">sorting section</a> in the material plugin.
</blockquote>
### Sorting Definitions

The 2nd parameter in `setSort` is the `PblNgridSortDefinition`:

```typescript
export interface PblNgridSortDefinition {
order: PblNgridSortOrder;
sortFn?: PblNgridSorter;
}
```

The sort definitions is made up of:

- **order** - The sort order we want to use ('asc' or 'desc')
- **sortFn** - The sort function (logic) we want to use (optional)

I> The **sortFn** is optional, see the "Picking a Sorting function" for more details.

## Picking a Sorting function

When sorting is applied the grid will search for a sorting function in the following order and choose the **first match**:

- `PblNgridSortDefinition.sortFn`
- The sorting function defined on the `sort` property of the column definition
- The default sorting function

In other words, providing a sorting function to `setSort` will override any sorting function defined on the column definitions.
If no function provided and no sorting function exists on the column definition, the default sorting function is used.

## The Default Sorting Function

The default sorting function is very simple, it uses the `<` and `>` operators to determine the logical order between 2 values. This is applied on all data types.

<div pbl-example-view="pbl-column-sort-example"></div>

<div pbl-example-view="pbl-column-specific-sorting-example"></div>

## Aliasing

Aliasing is just another way to reference a column indirectly (i.e. not through it's object reference).
Expand Down
13 changes: 10 additions & 3 deletions libs/ngrid/src/lib/data-source/sorting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,22 @@ export function applySort<T>(column: PblColumn, sort: PblNgridSortDefinition, da
if (!sort || !sort.order) {
return data;
}
const sortFn: PblNgridSorter<T> = typeof sort.sortFn === 'function' ? sort.sortFn : defaultSorter;

const sortFn: PblNgridSorter<T> = typeof sort.sortFn === 'function'
? sort.sortFn
: typeof column.sort === 'function'
? column.sort
: defaultSorter
;

return column && data
? sortFn(column, sort, data)
? sortFn(column, sort, data.slice())
: data || []
;
}

function defaultSorter<T>(column: PblColumn, sort: PblNgridSortInstructions, data: T[]): T[] {
return data.slice().sort((a, b) => {
return data.sort((a, b) => {
let valueA = column.getValue(a);
let valueB = column.getValue(b);

Expand Down

0 comments on commit 39428ad

Please sign in to comment.