diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f326bbdc78..92b34b909ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,9 +47,11 @@ All notable changes for each version of this project will be documented in this - Inputs `showToolbar`, `toolbarTitle`, `columnHiding`, `columnHidingTitle`, `hiddenColumnsText`, `columnPinning`, `columnPinningTitle`, `pinnedColumnsText`. Use `IgxGridToolbarComponent`, `IgxGridToolbarHidingComponent`, `IgxGridToolbarPinningComponent` instead. +- `igxGrid` + - Exposed a `groupStrategy` input that functions similarly to `sortStrategy`, allowing customization of the grouping behavior of the grid. Please, refer to the [Group By ](https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid/groupby) topic for more information. - `IgxColumnActionsComponent` - **Breaking Change** - The following input has been removed - - Input `columns`. Use `igxGrid` `columns` input instead. + - Input `columns`. Use `igxGrid` `columns` input instead. - `IgxCarousel` - **Breaking Changes** -The carousel animation type `CarouselAnimationType` is renamed to `HorizontalAnimationType`. diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts index e0a1603c3e5..0383bec4257 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts @@ -263,7 +263,7 @@ const testGroupBy = () => { // sort const res = DataUtil.sort(data, [expr]); // group by - DataUtil.group(res, state, null, groupRecords); + DataUtil.group(res, state, undefined, null, groupRecords); expect(groupRecords.length).toEqual(2); expect(groupRecords[0].records.length).toEqual(3); expect(groupRecords[1].records.length).toEqual(2); @@ -279,7 +279,7 @@ const testGroupBy = () => { // sort const sorted = DataUtil.sort(data, [expr, expr2]); // group by - DataUtil.group(sorted, state, null, groupRecords); + DataUtil.group(sorted, state, undefined, null, groupRecords); expect(groupRecords.length).toEqual(2); expect(groupRecords[0].records.length).toEqual(3); expect(groupRecords[1].records.length).toEqual(2); diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index 95b920f5368..e199c4881ba 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -11,6 +11,7 @@ import { IGroupByRecord } from './groupby-record.interface'; import { IGroupingState } from './groupby-state.interface'; import { ISortingExpression } from './sorting-expression.interface'; import { FilteringStrategy } from './filtering-strategy'; +import { IGridGroupingStrategy } from './grouping-strategy'; import { ITreeGridRecord } from '../grids/tree-grid/public_api'; import { cloneValue, mergeObjects, mkenum } from '../core/utils'; import { Transaction, TransactionType, HierarchicalTransaction } from '../services/transaction/transaction'; @@ -73,9 +74,8 @@ export class DataUtil { return rec; } - public static group(data: T[], state: IGroupingState, grid: GridType = null, + public static group(data: T[], state: IGroupingState, grouping: IGridGroupingStrategy = new IgxGrouping(), grid: GridType = null, groupsRecords: any[] = [], fullResult: IGroupByResult = { data: [], metadata: [] }): IGroupByResult { - const grouping = new IgxGrouping(); groupsRecords.splice(0, groupsRecords.length); return grouping.groupBy(data, state, grid, groupsRecords, fullResult); } diff --git a/projects/igniteui-angular/src/lib/data-operations/grouping-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/grouping-strategy.ts index 1cf180ee300..be8fdfdeb81 100644 --- a/projects/igniteui-angular/src/lib/data-operations/grouping-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/grouping-strategy.ts @@ -1,9 +1,14 @@ import { IGroupByRecord } from './groupby-record.interface'; -import { IgxSorting } from './sorting-strategy'; +import { IgxSorting, IGridSortingStrategy } from './sorting-strategy'; import { IGroupingState } from './groupby-state.interface'; import { IGroupByResult } from './grouping-result.interface'; -export class IgxGrouping extends IgxSorting { + +export interface IGridGroupingStrategy extends IGridSortingStrategy { + groupBy(data: any[], state: IGroupingState, grid?: any, groupsRecords?: any[], fullResult?: IGroupByResult): IGroupByResult; +} + +export class IgxGrouping extends IgxSorting implements IGridGroupingStrategy { public groupBy(data: any[], state: IGroupingState, grid?: any, groupsRecords?: any[], fullResult: IGroupByResult = { data: [], metadata: [] }): IGroupByResult { const metadata: IGroupByRecord[] = []; diff --git a/projects/igniteui-angular/src/lib/grids/README.md b/projects/igniteui-angular/src/lib/grids/README.md index 329c5a5735b..be5724d6d0f 100644 --- a/projects/igniteui-angular/src/lib/grids/README.md +++ b/projects/igniteui-angular/src/lib/grids/README.md @@ -185,6 +185,7 @@ Below is the list of all inputs that the developers may set to configure the gri |`evenRowCSS`|string|Additional styling classes applied to all even rows in the grid.| |`oddRowCSS`|string|Additional styling classes applied to all odd rows in the grid.| |`paginationTemplate`|TemplateRef|You can provide a custom `ng-template` for the pagination part of the grid.| +|`groupStrategy`| IGridGroupingStrategy | Provides custom group strategy to be used when grouping | |`groupingExpressions`| Array | The group by state of the grid. |`groupingExpansionState`| Array | The list of expansion states of the group rows. Contains the expansion state(expanded: boolean) and an unique identifier for the group row (Array) that contains a list of the group row's parents described via their fieldName and value. |`groupsExpanded`| boolean | Determines whether created groups are rendered expanded or collapsed. | diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html index fcc9b789609..f8398899452 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html @@ -71,7 +71,7 @@ | visibleColumns:hasVisibleColumns | gridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:id:pipeTrigger:filteringPipeTrigger | gridSort:sortingExpressions:sortStrategy:id:pipeTrigger - | gridGroupBy:groupingExpressions:groupingExpansionState:groupsExpanded:id:groupsRecords:pipeTrigger + | gridGroupBy:groupingExpressions:groupingExpansionState:groupStrategy:groupsExpanded:id:groupsRecords:pipeTrigger | gridPaging:paginator?.page:paginator?.perPage:id:pipeTrigger | gridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:id:showSummaryOnCollapse:pipeTrigger:summaryPipeTrigger | gridDetails:hasDetails:expansionStates:pipeTrigger diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts index 0e473e54b55..98913a348d7 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts @@ -33,6 +33,7 @@ import { IgxGridGroupByAreaComponent } from '../grouping/grid-group-by-area.comp import { IgxGridCell } from '../grid-public-cell'; import { CellType } from '../common/cell.interface'; import { DeprecateMethod } from '../../core/deprecateDecorators'; +import { IGridGroupingStrategy } from '../../data-operations/grouping-strategy'; let NEXT_ID = 0; @@ -263,6 +264,10 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType, * @hidden */ protected _groupAreaTemplate: TemplateRef; + /** + * @hidden + */ + protected _groupStrategy: IGridGroupingStrategy; /** * @hidden */ @@ -455,6 +460,25 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType, this._hideGroupedColumns = value; } + /** + * Gets/Sets the grouping strategy of the grid. + * + * @remarks The default IgxGrouping extends from IgxSorting and a custom one can be used as a `sortStrategy` as well. + * + * @example + * ```html + * + * ``` + */ + @Input() + public get groupStrategy(): IGridGroupingStrategy { + return this._groupStrategy; + } + + public set groupStrategy(value: IGridGroupingStrategy) { + this._groupStrategy = value; + } + /** * Gets/Sets the message displayed inside the GroupBy drop area where columns can be dragged on. * diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts index de619785e58..a052c6d1f1d 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts @@ -20,6 +20,7 @@ import { GridSelectionFunctions, GridFunctions } from '../../test-utils/grid-fun import { GridSelectionMode } from '../common/enums'; import { ControlsFunction } from '../../test-utils/controls-functions.spec'; import { IGroupingExpression } from '../../data-operations/grouping-expression.interface'; +import { IgxGrouping } from '../../data-operations/grouping-strategy'; describe('IgxGrid - GroupBy #grid', () => { @@ -602,6 +603,21 @@ describe('IgxGrid - GroupBy #grid', () => { expect(grid.groupingExpressionsChange.emit).toHaveBeenCalledTimes(0); })); + it('should group unbound column with custom grouping strategy', fakeAsync(() => { + const fix = TestBed.createComponent(GroupableGridComponent); + fix.componentInstance.data.forEach((r, i) => { + r['fieldValue1'] = Math.floor(i / 3); + r['fieldValue2'] = Math.floor(i / 4); + }); + fix.detectChanges(); + fix.componentInstance.instance.groupBy({ + fieldName: 'UnboundField', dir: SortingDirection.Desc, ignoreCase: false + }); + fix.detectChanges(); + const groupRows = fix.componentInstance.instance.groupsRowList.toArray(); + expect(groupRows.length).toEqual(4); + })); + // GroupBy + Sorting integration it('should apply sorting on each group\'s records when non-grouped column is sorted.', fakeAsync(() => { const fix = TestBed.createComponent(DefaultGridComponent); @@ -1848,36 +1864,36 @@ describe('IgxGrid - GroupBy #grid', () => { })); it('Should have the correct properties in the custom row selector template', fakeAsync(() => { - const fix = TestBed.createComponent(GridGroupByRowCustomSelectorsComponent); - const grid = fix.componentInstance.instance; - fix.componentInstance.width = '1200px'; - tick(); - grid.columnWidth = '200px'; - tick(); - grid.rowSelection = GridSelectionMode.multiple; - tick(); - fix.detectChanges(); + const fix = TestBed.createComponent(GridGroupByRowCustomSelectorsComponent); + const grid = fix.componentInstance.instance; + fix.componentInstance.width = '1200px'; + tick(); + grid.columnWidth = '200px'; + tick(); + grid.rowSelection = GridSelectionMode.multiple; + tick(); + fix.detectChanges(); - grid.groupBy({ - fieldName: 'ProductName', dir: SortingDirection.Desc, ignoreCase: false - }); - tick(); - fix.detectChanges(); + grid.groupBy({ + fieldName: 'ProductName', dir: SortingDirection.Desc, ignoreCase: false + }); + tick(); + fix.detectChanges(); - const grRow = grid.groupsRowList.toArray()[0]; - const contextSelect = { selectedCount: 0, totalCount: 2, groupRow: grid.groupsRowList.toArray()[0].groupRow }; - const contextUnselect = { selectedCount: 2, totalCount: 2, groupRow: grid.groupsRowList.toArray()[0].groupRow }; + const grRow = grid.groupsRowList.toArray()[0]; + const contextSelect = { selectedCount: 0, totalCount: 2, groupRow: grid.groupsRowList.toArray()[0].groupRow }; + const contextUnselect = { selectedCount: 2, totalCount: 2, groupRow: grid.groupsRowList.toArray()[0].groupRow }; - spyOn(fix.componentInstance, 'onGroupByRowClick').and.callThrough(); + spyOn(fix.componentInstance, 'onGroupByRowClick').and.callThrough(); - grRow.nativeElement.querySelector('.igx-checkbox__composite').click(); - fix.detectChanges(); - expect(fix.componentInstance.onGroupByRowClick).toHaveBeenCalledWith(fix.componentInstance.groupByRowClick, contextSelect); + grRow.nativeElement.querySelector('.igx-checkbox__composite').click(); + fix.detectChanges(); + expect(fix.componentInstance.onGroupByRowClick).toHaveBeenCalledWith(fix.componentInstance.groupByRowClick, contextSelect); - grRow.nativeElement.querySelector('.igx-checkbox__composite').click(); - fix.detectChanges(); - expect(fix.componentInstance.onGroupByRowClick).toHaveBeenCalledWith(fix.componentInstance.groupByRowClick, contextUnselect); - })); + grRow.nativeElement.querySelector('.igx-checkbox__composite').click(); + fix.detectChanges(); + expect(fix.componentInstance.onGroupByRowClick).toHaveBeenCalledWith(fix.componentInstance.groupByRowClick, contextUnselect); + })); // GroupBy + Resizing it('should retain same size for group row after a column is resized.', fakeAsync(() => { @@ -3488,12 +3504,32 @@ export class DefaultGridComponent extends DataParent { } } +const formatUnboundValueFunction = (rowData: any | undefined): string | undefined => rowData.fieldValue1 + ' ' + rowData.fieldValue2; + + +class MySortingStrategy extends IgxGrouping { + protected getFieldValue( + obj: any, + key: string, + isDate = false, + isTime = false + ): unknown { + if (key !== 'UnboundField') { + return super.getFieldValue(obj, key, isDate, isTime); + } + + return formatUnboundValueFunction(obj); + } +} + @Component({ template: ` + [data]="data" + [sortStrategy]="sortStrategy" + [groupStrategy]="groupStrategy"> @@ -3501,6 +3537,9 @@ export class DefaultGridComponent extends DataParent { dataType="number"> + + ` @@ -3511,6 +3550,13 @@ export class GroupableGridComponent extends DataParent { public width = '800px'; public height = '700px'; + + public sortStrategy = new MySortingStrategy(); + public groupStrategy = this.sortStrategy; + + public formatUnboundValue(value: string, rowData: any | undefined): string | undefined { + return formatUnboundValueFunction(rowData); + } } @Component({ diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts index 500f2dbacf5..71a32c90e12 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts @@ -13,6 +13,7 @@ import { IgxGridBaseDirective } from '../grid-base.directive'; import { GridType } from '../common/grid.interface'; import { IFilteringStrategy } from '../../data-operations/filtering-strategy'; import { IGridSortingStrategy } from '../../data-operations/sorting-strategy'; +import { IGridGroupingStrategy } from '../../data-operations/grouping-strategy'; import { GridPagingMode } from '../common/enums'; /** @@ -60,7 +61,8 @@ export class IgxGridGroupingPipe implements PipeTransform { } public transform(collection: any[], expression: IGroupingExpression | IGroupingExpression[], - expansion: IGroupByExpandState | IGroupByExpandState[], defaultExpanded: boolean, + expansion: IGroupByExpandState | IGroupByExpandState[], + groupingStrategy: IGridGroupingStrategy, defaultExpanded: boolean, id: string, groupsRecords: any[], _pipeTrigger: number): IGroupByResult { const state = { expressions: [], expansion: [], defaultExpanded }; @@ -79,7 +81,7 @@ export class IgxGridGroupingPipe implements PipeTransform { } else { state.expansion = grid.groupingExpansionState; state.defaultExpanded = grid.groupsExpanded; - result = DataUtil.group(cloneArray(collection), state, grid, groupsRecords, fullResult); + result = DataUtil.group(cloneArray(collection), state, groupingStrategy, grid, groupsRecords, fullResult); } grid.groupingFlatResult = result.data; grid.groupingResult = fullResult.data; diff --git a/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts b/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts index eb35616f621..10a4c9809ee 100644 --- a/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts +++ b/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts @@ -694,7 +694,7 @@ export abstract class IgxBaseExporter { if (hasGrouping && !this.options.ignoreGrouping) { const groupsRecords = []; - DataUtil.group(cloneArray(gridData), groupedGridGroupingState, grid, groupsRecords); + DataUtil.group(cloneArray(gridData), groupedGridGroupingState, grid.groupStrategy, grid, groupsRecords); gridData = groupsRecords; }