Skip to content

Commit 30d984d

Browse files
authored
Merge pull request #10336 from IgniteUI/kdragieva/grid-sorting-master
feat(IgxGridBaseDirective): Add Single and Multiple sorting options
2 parents 6fce6d3 + f2ca7bf commit 30d984d

26 files changed

+450
-332
lines changed

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22

33
All notable changes for each version of this project will be documented in this file.
44

5-
## 13.2.0
5+
## 13.2.0
66

77
### New palette
88

99
A new fluent light and dark palettes that use the default fluent colors - `$light-fluent-palette` and `$dark-fluent-palette`.
1010

11+
### New Features
12+
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
13+
- new *sortingOption* property has been introduced on grid level; This property allows you to set either `single` or `multiple` sorting mode; When single mode is enabled you can sort one column at a time; The default value of the property is `multiple`;
14+
15+
- **Behavioral Change** - sorting and grouping expressions are now working separately; If grouping/sorting expressions are in a conflict, grouping expressions take precedence. You can read more about that in our official documentation.
1116
## 13.1.0
1217

1318
### New Features
@@ -138,7 +143,7 @@ A new fluent light and dark palettes that use the default fluent colors - `$ligh
138143
- `IgxDialog`
139144
- Added `focusTrap` input to set whether the Tab key focus is trapped within the dialog when opened. Defaults to `true`.
140145
- `IgxProgressBar`
141-
- Exposed new animationDuration input - sets the duration of the progress animation.
146+
- Exposed new animationDuration input - sets the duration of the progress animation.
142147

143148
### General
144149

projects/igniteui-angular/src/lib/grids/api.service.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { IgxCell, IgxGridCRUDService, IgxEditRow } from './common/crud.service';
88
import { CellType, ColumnType, GridServiceType, GridType, RowType } from './common/grid.interface';
99
import { IGridEditEventArgs, IRowToggleEventArgs } from './common/events';
1010
import { IgxColumnMovingService } from './moving/moving.service';
11+
import { IGroupingExpression } from '../data-operations/grouping-expression.interface';
1112
import { ISortingExpression, SortingDirection } from '../data-operations/sorting-strategy';
1213
import { FilterUtil } from '../data-operations/filtering-strategy';
1314

@@ -221,6 +222,15 @@ export class GridBaseAPIService<T extends GridType> implements GridServiceType {
221222
this.grid.sortingExpressions = sortingState;
222223
}
223224

225+
public sort_decoupled(expression: IGroupingExpression): void {
226+
if (expression.dir === SortingDirection.None) {
227+
this.remove_grouping_expression(expression.fieldName);
228+
}
229+
const groupingState = cloneArray((this.grid as any).groupingExpressions);
230+
this.prepare_grouping_expression([groupingState], expression);
231+
(this.grid as any).groupingExpressions = groupingState;
232+
}
233+
224234
public sort_multiple(expressions: ISortingExpression[]): void {
225235
const sortingState = cloneArray(this.grid.sortingExpressions);
226236

@@ -234,6 +244,17 @@ export class GridBaseAPIService<T extends GridType> implements GridServiceType {
234244
this.grid.sortingExpressions = sortingState;
235245
}
236246

247+
public sort_groupBy_multiple(expressions: ISortingExpression[]): void {
248+
const groupingState = cloneArray((this.grid as any).groupingExpressions);
249+
250+
for (const each of expressions) {
251+
if (each.dir === SortingDirection.None) {
252+
this.remove_grouping_expression(each.fieldName);
253+
}
254+
this.prepare_grouping_expression([groupingState], each);
255+
}
256+
}
257+
237258
public clear_sort(fieldName: string) {
238259
const sortingState = this.grid.sortingExpressions;
239260
const index = sortingState.findIndex((expr) => expr.fieldName === fieldName);
@@ -460,6 +481,43 @@ export class GridBaseAPIService<T extends GridType> implements GridServiceType {
460481
});
461482
}
462483

484+
public prepare_grouping_expression(stateCollections: Array<Array<any>>, expression: IGroupingExpression) {
485+
if (expression.dir === SortingDirection.None) {
486+
stateCollections.forEach(state => {
487+
state.splice(state.findIndex((expr) => expr.fieldName === expression.fieldName), 1);
488+
});
489+
return;
490+
}
491+
492+
/**
493+
* We need to make sure the states in each collection with same fields point to the same object reference.
494+
* If the different state collections provided have different sizes we need to get the largest one.
495+
* That way we can get the state reference from the largest one that has the same fieldName as the expression to prepare.
496+
*/
497+
let maxCollection = stateCollections[0];
498+
for (let i = 1; i < stateCollections.length; i++) {
499+
if (maxCollection.length < stateCollections[i].length) {
500+
maxCollection = stateCollections[i];
501+
}
502+
}
503+
const maxExpr = maxCollection.find((expr) => expr.fieldName === expression.fieldName);
504+
505+
stateCollections.forEach(collection => {
506+
const myExpr = collection.find((expr) => expr.fieldName === expression.fieldName);
507+
if (!myExpr && !maxExpr) {
508+
// Expression with this fieldName is missing from the current and the max collection.
509+
collection.push(expression);
510+
} else if (!myExpr && maxExpr) {
511+
// Expression with this fieldName is missing from the current and but the max collection has.
512+
collection.push(maxExpr);
513+
Object.assign(maxExpr, expression);
514+
} else {
515+
// The current collection has the expression so just update it.
516+
Object.assign(myExpr, expression);
517+
}
518+
});
519+
}
520+
463521
public remove_grouping_expression(_fieldName) {
464522
}
465523

projects/igniteui-angular/src/lib/grids/columns/interfaces.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,7 @@ export interface IColumnPipeArgs {
4343
*/
4444
display?: string;
4545
}
46+
47+
export interface ISortingOptions {
48+
mode: 'single' | 'multiple';
49+
}

projects/igniteui-angular/src/lib/grids/common/events.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { IBaseEventArgs, CancelableEventArgs } from '../../core/utils';
22
import { GridKeydownTargetType } from './enums';
33
import { CellType, ColumnType, GridType, RowType } from './grid.interface';
44
import { IFilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree';
5+
import { IGroupingExpression } from '../../data-operations/grouping-expression.interface';
56
import { IgxBaseExporter } from '../../services/exporter-common/base-export-service';
67
import { IgxExporterOptionsBase } from '../../services/exporter-common/exporter-options-base';
78
import { ISortingExpression } from '../../data-operations/sorting-strategy';
@@ -201,7 +202,8 @@ export interface IActiveNodeChangeEventArgs extends IBaseEventArgs {
201202
}
202203

203204
export interface ISortingEventArgs extends IBaseEventArgs, CancelableEventArgs {
204-
sortingExpressions: ISortingExpression | Array<ISortingExpression>;
205+
sortingExpressions?: ISortingExpression | Array<ISortingExpression>;
206+
groupingExpressions?: IGroupingExpression | Array<IGroupingExpression>;
205207
}
206208

207209
export interface IFilteringEventArgs extends IBaseEventArgs, CancelableEventArgs {

projects/igniteui-angular/src/lib/grids/common/grid.interface.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { ITreeGridRecord } from '../tree-grid/tree-grid.interfaces';
2727
import { State, Transaction, TransactionService } from '../../services/transaction/transaction';
2828
import { GridColumnDataType } from '../../data-operations/data-util';
2929
import { IgxFilteringOperand } from '../../data-operations/filtering-condition';
30-
import { IColumnPipeArgs, MRLResizeColumnInfo } from '../columns/interfaces';
30+
import { IColumnPipeArgs, ISortingOptions, MRLResizeColumnInfo } from '../columns/interfaces';
3131
import { IgxSummaryResult } from '../summaries/grid-summary';
3232
import { ISortingExpression, ISortingStrategy, SortingDirection } from '../../data-operations/sorting-strategy';
3333
import { IGridGroupingStrategy, IGridSortingStrategy } from './strategy';
@@ -444,6 +444,7 @@ export interface GridType extends IGridDataBindable {
444444
records?: Map<any, ITreeGridRecord>;
445445
processedExpandedFlatData?: any[] | null;
446446
processedRecords?: Map<any, ITreeGridRecord>;
447+
treeGroupArea?: any;
447448

448449
activeNodeChange: EventEmitter<IActiveNodeChangeEventArgs>;
449450
gridKeydown: EventEmitter<IGridKeydownEventArgs>;
@@ -499,6 +500,7 @@ export interface GridType extends IGridDataBindable {
499500
filteringExpressionsTreeChange: EventEmitter<IFilteringExpressionsTree>;
500501
advancedFilteringExpressionsTree: IFilteringExpressionsTree;
501502
advancedFilteringExpressionsTreeChange: EventEmitter<IFilteringExpressionsTree>;
503+
sortingOptions: ISortingOptions;
502504

503505
batchEditing: boolean;
504506
groupingExpansionState?: IGroupByExpandState[];

projects/igniteui-angular/src/lib/grids/grid-base.directive.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,15 @@ import { IgxPaginatorComponent } from '../paginator/paginator.component';
138138
import { IgxGridHeaderRowComponent } from './headers/grid-header-row.component';
139139
import { IgxGridGroupByAreaComponent } from './grouping/grid-group-by-area.component';
140140
import { IgxFlatTransactionFactory, TRANSACTION_TYPE } from '../services/transaction/transaction-factory.service';
141+
import { ISortingOptions } from './columns/interfaces';
141142
import { GridSelectionRange, IgxGridTransaction } from './common/types';
142143
import { VerticalAlignment, HorizontalAlignment, PositionSettings, OverlaySettings } from '../services/overlay/utilities';
143144
import { IgxOverlayService } from '../services/overlay/overlay';
144145
import { ConnectedPositioningStrategy } from '../services/overlay/position/connected-positioning-strategy';
145146
import { ContainerPositionStrategy } from '../services/overlay/position/container-position-strategy';
146147
import { AbsoluteScrollStrategy } from '../services/overlay/scroll/absolute-scroll-strategy';
147148
import { Action, StateUpdateEvent, TransactionEventOrigin } from '../services/transaction/transaction';
148-
import { ISortingExpression, SortingDirection } from '../data-operations/sorting-strategy';
149+
import { ISortingExpression } from '../data-operations/sorting-strategy';
149150
import { IGridSortingStrategy } from './common/strategy';
150151
import { IgxGridExcelStyleFilteringComponent } from './filtering/excel-style/grid.excel-style-filtering.component';
151152
import { IgxGridHeaderComponent } from './headers/grid-header.component';
@@ -2110,6 +2111,33 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
21102111
this._sortingStrategy = value;
21112112
}
21122113

2114+
/**
2115+
* Gets/Sets the sorting options - single or multiple sorting.
2116+
* Accepts an `ISortingOptions` object with any of the `mode` properties.
2117+
*
2118+
* @example
2119+
* ```typescript
2120+
* const _sortingOptions: ISortingOptions = {
2121+
* mode: 'single'
2122+
* }
2123+
* ```html
2124+
* <igx-grid [sortingOptions]="sortingOptions"><igx-grid>
2125+
* ```
2126+
*/
2127+
@Input()
2128+
public set sortingOptions(value: ISortingOptions) {
2129+
this.clearSort();
2130+
this._sortingOptions = Object.assign(this._sortingOptions, value);
2131+
}
2132+
2133+
/**
2134+
* @hidden
2135+
* @internal
2136+
*/
2137+
public get sortingOptions() {
2138+
return this._sortingOptions;
2139+
}
2140+
21132141
/**
21142142
* Gets/Sets the current selection state.
21152143
*
@@ -2764,6 +2792,7 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
27642792
protected _userOutletDirective: IgxOverlayOutletDirective;
27652793
protected _transactions: TransactionService<Transaction, State>;
27662794
protected _batchEditing = false;
2795+
protected _sortingOptions: ISortingOptions = { mode: 'multiple' };
27672796
protected _filterStrategy: IFilteringStrategy = new FilteringStrategy();
27682797
protected _autoGeneratedCols = [];
27692798
protected _dataView = [];
@@ -4527,14 +4556,15 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
45274556

45284557
if (expression instanceof Array) {
45294558
for (const each of expression) {
4530-
if (each.dir === SortingDirection.None) {
4531-
this.gridAPI.remove_grouping_expression(each.fieldName);
4532-
}
45334559
this.gridAPI.prepare_sorting_expression([sortingState], each);
45344560
}
45354561
} else {
4536-
if (expression.dir === SortingDirection.None) {
4537-
this.gridAPI.remove_grouping_expression(expression.fieldName);
4562+
if (this._sortingOptions.mode === 'single') {
4563+
this.columns.forEach((col) => {
4564+
if (!(col.field === expression.fieldName)) {
4565+
this.clearSort(col.field);
4566+
}
4567+
});
45384568
}
45394569
this.gridAPI.prepare_sorting_expression([sortingState], expression);
45404570
}

projects/igniteui-angular/src/lib/grids/grid/grid-api.service.ts

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,16 @@ export class IgxGridAPIService extends GridBaseAPIService<GridType> implements G
1212

1313
public groupBy(expression: IGroupingExpression): void {
1414
const groupingState = cloneArray(this.grid.groupingExpressions);
15-
const sortingState = cloneArray(this.grid.sortingExpressions);
16-
this.prepare_sorting_expression([sortingState, groupingState], expression);
15+
this.prepare_grouping_expression([groupingState], expression);
1716
this.grid.groupingExpressions = groupingState;
1817
this.arrange_sorting_expressions();
1918
}
2019

2120
public groupBy_multiple(expressions: IGroupingExpression[]): void {
2221
const groupingState = cloneArray(this.grid.groupingExpressions);
23-
const sortingState = cloneArray(this.grid.sortingExpressions);
2422

2523
for (const each of expressions) {
26-
this.prepare_sorting_expression([sortingState, groupingState], each);
24+
this.prepare_grouping_expression([groupingState], each);
2725
}
2826

2927
this.grid.groupingExpressions = groupingState;
@@ -32,14 +30,11 @@ export class IgxGridAPIService extends GridBaseAPIService<GridType> implements G
3230

3331
public clear_groupby(name?: string | Array<string>) {
3432
const groupingState = cloneArray(this.grid.groupingExpressions);
35-
const sortingState = cloneArray(this.grid.sortingExpressions);
3633

3734
if (name) {
3835
const names = typeof name === 'string' ? [name] : name;
3936
const groupedCols = groupingState.filter((state) => names.indexOf(state.fieldName) < 0);
40-
const newSortingExpr = sortingState.filter((state) => names.indexOf(state.fieldName) < 0);
4137
this.grid.groupingExpressions = groupedCols;
42-
this.grid.sortingExpressions = newSortingExpr;
4338
names.forEach((colName) => {
4439
const grExprIndex = groupingState.findIndex((exp) => exp.fieldName === colName);
4540
const grpExpandState = this.grid.groupingExpansionState;
@@ -56,13 +51,6 @@ export class IgxGridAPIService extends GridBaseAPIService<GridType> implements G
5651
// clear all
5752
this.grid.groupingExpressions = [];
5853
this.grid.groupingExpansionState = [];
59-
for (const grExpr of groupingState) {
60-
const sortExprIndex = sortingState.findIndex((exp) => exp.fieldName === grExpr.fieldName);
61-
if (sortExprIndex > -1) {
62-
sortingState.splice(sortExprIndex, 1);
63-
}
64-
}
65-
this.grid.sortingExpressions = sortingState;
6654
}
6755
}
6856

@@ -134,19 +122,14 @@ export class IgxGridAPIService extends GridBaseAPIService<GridType> implements G
134122

135123
public arrange_sorting_expressions() {
136124
const groupingState = this.grid.groupingExpressions;
137-
this.grid.sortingExpressions.sort((a, b) => {
138-
const groupExprA = groupingState.find((expr) => expr.fieldName === a.fieldName);
139-
const groupExprB = groupingState.find((expr) => expr.fieldName === b.fieldName);
140-
if (groupExprA && groupExprB) {
141-
return groupingState.indexOf(groupExprA) > groupingState.indexOf(groupExprB) ? 1 : -1;
142-
} else if (groupExprA) {
143-
return -1;
144-
} else if (groupExprB) {
145-
return 1;
146-
} else {
147-
return 0;
125+
const sortingState = cloneArray(this.grid.sortingExpressions);
126+
for (const grExpr of groupingState) {
127+
const sortExprIndex = sortingState.findIndex((exp) => exp.fieldName === grExpr.fieldName);
128+
if (sortExprIndex > -1) {
129+
sortingState.splice(sortExprIndex, 1);
148130
}
149-
});
131+
}
132+
this.grid.sortingExpressions = sortingState;
150133
}
151134

152135
public get_groupBy_record_id(gRow: IGroupByRecord): string {

projects/igniteui-angular/src/lib/grids/grid/grid.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
| gridAddRow:true:pipeTrigger
5353
| gridRowPinning:id:true:pipeTrigger
5454
| gridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:id:pipeTrigger:filteringPipeTrigger:true
55-
| gridSort:sortingExpressions:sortStrategy:id:pipeTrigger:true as pinnedData'>
55+
| gridSort:sortingExpressions:groupingExpressions:sortStrategy:id:pipeTrigger:true as pinnedData'>
5656
<div #pinContainer *ngIf='pinnedData.length > 0'
5757
[ngClass]="{
5858
'igx-grid__tr--pinned-bottom': !isRowPinningToTop,
@@ -70,7 +70,7 @@
7070
| gridTransaction:id:pipeTrigger
7171
| visibleColumns:hasVisibleColumns
7272
| gridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:id:pipeTrigger:filteringPipeTrigger
73-
| gridSort:sortingExpressions:sortStrategy:id:pipeTrigger
73+
| gridSort:sortingExpressions:groupingExpressions:sortStrategy:id:pipeTrigger
7474
| gridGroupBy:groupingExpressions:groupingExpansionState:groupStrategy:groupsExpanded:id:groupsRecords:pipeTrigger
7575
| gridPaging:paginator?.page:paginator?.perPage:id:pipeTrigger
7676
| gridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:id:showSummaryOnCollapse:pipeTrigger:summaryPipeTrigger

0 commit comments

Comments
 (0)