diff --git a/CHANGELOG.md b/CHANGELOG.md index 844cb74855d..1884d208bd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,9 @@ All notable changes for each version of this project will be documented in this - Introduced the `selectionChanged` event for both components. The event is not cancelable and is emitted after the selection is committed and the component state is updated. - Added `disableClear` input that allows hiding the clear button even when items are selected. Defaults to `false`. +- `IgxPivotGrid` + - Added `headerFormatter` optional property to `IPivotDimension`. This is a display-only callback `(value, dimension?, rowData?) => string | null | undefined` applied when rendering row and column dimension header text. Returning `null` or `undefined` falls back to the raw dimension value. The `IgxPivotDateDimension` uses this to render `fullDate` leaf values in a locale-aware short-date format automatically. + ### General - `IgxOverlayService` diff --git a/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts b/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts index a0ee52c9459..bc2233371f3 100644 --- a/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts +++ b/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts @@ -106,9 +106,14 @@ export class IgxPivotDateDimension implements IPivotDimension { public childLevel?: IPivotDimension; /** @hidden @internal */ public memberName = 'AllPeriods'; - /** @hidden @internal */ - public locale?: string; public displayName: string; + /** + * Gets/Sets the locale used for date dimension member formatting (e.g. month names). + * When set, overrides the global I18nManager locale for this dimension. + * Set automatically by the pivot grid component when the grid locale changes. + * @hidden @internal + */ + public locale?: string; private _resourceStrings: IGridResourceStrings = null; private _baseDimension: IPivotDimension; private _options: IPivotDateDimensionOptions = {}; @@ -141,13 +146,16 @@ export class IgxPivotDateDimension implements IPivotDimension { this.enabled = inBaseDimension.enabled; this.displayName = inBaseDimension.displayName || this.resourceStrings.igx_grid_pivot_date_dimension_total; - const baseDimension = options.fullDate ? inBaseDimension : null; + const baseDimension: IPivotDimension = options.fullDate + ? this.createLeafDateDimension(inBaseDimension) + : null; + const monthDimensionDef: IPivotDimension = { memberName: 'Months', memberFunction: (rec) => { const recordValue = PivotUtil.extractValueFromDimension(inBaseDimension, rec); - const dateValue = recordValue ? getDateFormatter().createDateFromValue(recordValue) : null; - return recordValue ? getDateFormatter().formatDateTime(dateValue, this.locale, { month: 'long'}) : rec['Months']; + const dateValue = (recordValue != null && recordValue !== '') ? getDateFormatter().createDateFromValue(recordValue) : null; + return dateValue ? getDateFormatter().formatDateTime(dateValue, this.locale, { month: 'long'}) : rec['Months']; }, enabled: true, childLevel: baseDimension @@ -158,8 +166,8 @@ export class IgxPivotDateDimension implements IPivotDimension { memberName: 'Quarters', memberFunction: (rec) => { const recordValue = PivotUtil.extractValueFromDimension(inBaseDimension, rec); - const dateValue = recordValue ? getDateFormatter().createDateFromValue(recordValue) : null; - return recordValue ? `Q` + Math.ceil((dateValue.getMonth() + 1) / 3) : rec['Quarters']; + const dateValue = (recordValue != null && recordValue !== '') ? getDateFormatter().createDateFromValue(recordValue) : null; + return dateValue ? `Q` + Math.ceil((dateValue.getMonth() + 1) / 3) : rec['Quarters']; }, enabled: true, childLevel: monthDimension @@ -170,8 +178,8 @@ export class IgxPivotDateDimension implements IPivotDimension { memberName: 'Years', memberFunction: (rec) => { const recordValue = PivotUtil.extractValueFromDimension(inBaseDimension, rec); - const dateValue = recordValue ? getDateFormatter().createDateFromValue(recordValue) : null; - return recordValue ? dateValue.getFullYear().toString() : rec['Years']; + const dateValue = (recordValue != null && recordValue !== '') ? getDateFormatter().createDateFromValue(recordValue) : null; + return dateValue ? dateValue.getFullYear().toString() : rec['Years']; }, enabled: true, childLevel: quarterDimension @@ -187,6 +195,25 @@ export class IgxPivotDateDimension implements IPivotDimension { } } + private createLeafDateDimension(inBaseDimension: IPivotDimension): IPivotDimension { + if (inBaseDimension.headerFormatter || inBaseDimension.memberFunction) { + // User supplied their own formatter/memberFunction — use the dimension as-is. + return inBaseDimension; + } + // No user-supplied formatter: add a locale-aware formatter that shows dates + // in short-date format while preserving the original dimension instance. + const dateFormatter = getDateFormatter(); + inBaseDimension.headerFormatter = (value: any) => { + const hasValue = value !== null && value !== undefined && value !== ''; + const dateValue = hasValue ? dateFormatter.createDateFromValue(value) : null; + if (dateValue) { + return dateFormatter.formatDateTime(dateValue, this.locale, { dateStyle: 'short' }); + } + return hasValue ? String(value) : ''; + }; + return inBaseDimension; + } + /** @hidden @internal */ public memberFunction = (_data) => this.resourceStrings.igx_grid_pivot_date_dimension_total; } diff --git a/projects/igniteui-angular/grids/core/src/pivot-grid.interface.ts b/projects/igniteui-angular/grids/core/src/pivot-grid.interface.ts index 393aa18a658..fc891c2b847 100644 --- a/projects/igniteui-angular/grids/core/src/pivot-grid.interface.ts +++ b/projects/igniteui-angular/grids/core/src/pivot-grid.interface.ts @@ -143,6 +143,22 @@ export interface IPivotDimension { /** @hidden @internal */ autoWidth?: number; horizontalSummary? : boolean; + /* csTreatAsEvent: PivotDimensionFormatterEventHandler */ + /* blazorOnlyScript */ + /** + * Optional function to format the display value of a dimension header. + * Unlike `memberFunction`, this does not affect the data key used for grouping or sorting — + * it is applied when rendering the dimension header text (both row and column dimension headers). + * When set, the return value of this function is shown instead of the raw dimension value. + * Return `null` or `undefined` to fall back to the raw value. + * + * @example + * ```typescript + * // Display dates in a locale-aware short date format. + * { memberName: 'Date', enabled: true, headerFormatter: (value) => new Date(value).toLocaleDateString() } + * ``` + */ + headerFormatter?: (value: any, dimension?: IPivotDimension, rowData?: IPivotGridGroupRecord) => string | null | undefined; } /* marshalByValue */ diff --git a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.ts b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.ts index d048fdd3b24..2b2e0ef1564 100644 --- a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.ts +++ b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.ts @@ -2383,7 +2383,10 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni const ref = isGroup ? createComponent(IgxColumnGroupComponent, { environmentInjector: this.envInjector, elementInjector: this.injector }) : createComponent(IgxColumnComponent, { environmentInjector: this.envInjector, elementInjector: this.injector }); - ref.instance.header = parent != null ? key.split(parent.header + this.pivotKeys.columnDimensionSeparator)[1] : key; + const parentPath = parent != null ? parent.field + this.pivotKeys.columnDimensionSeparator : null; + const rawHeader = parentPath != null && key.startsWith(parentPath) ? key.substring(parentPath.length) : key; + const dim = value.dimension as IPivotDimension; + ref.instance.header = dim?.headerFormatter != null ? (dim.headerFormatter(rawHeader, dim, undefined) ?? rawHeader) : rawHeader; ref.instance.field = key; ref.instance.parent = parent; if (value.dimension.width) { diff --git a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts index 33a773cf136..e1f7d154e55 100644 --- a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts +++ b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts @@ -140,6 +140,30 @@ describe('IgxPivotGrid #pivotGrid', () => { expect(actualDataTypeValue).toEqual('$71.89'); }); + it('should provide context to dimension header formatter', () => { + const pivotGrid = fixture.componentInstance.pivotGrid; + const rowDimension = pivotGrid.pivotConfiguration.rows[0]; + const headerFormatter = jasmine.createSpy('headerFormatter') + .and.callFake((value, dimension, rowData) => { + expect(dimension).toEqual(jasmine.objectContaining({ memberName: rowDimension.memberName })); + expect(rowData).toBeDefined(); + return `formatted-${value}`; + }); + rowDimension.headerFormatter = headerFormatter; + + pivotGrid.pipeTrigger++; + pivotGrid.setupColumns(); + fixture.detectChanges(); + + const rowHeaders = fixture.debugElement.queryAll(By.directive(IgxPivotRowDimensionHeaderComponent)); + expect(rowHeaders[0].componentInstance.column.header).toBe('formatted-All'); + + const [rawValue, dimension, rowData] = headerFormatter.calls.mostRecent().args; + expect(rawValue).toBe('All'); + expect(dimension).toEqual(jasmine.objectContaining({ memberName: rowDimension.memberName })); + expect(rowData.dimensionValues).toBeDefined(); + }); + it('should apply css class to cells from measures', () => { fixture.detectChanges(); const pivotGrid = fixture.componentInstance.pivotGrid; @@ -1245,7 +1269,8 @@ describe('IgxPivotGrid #pivotGrid', () => { // check rows const rows = pivotGrid.rowList.toArray(); expect(rows.length).toBe(5); - const expectedHeaders = ['All Periods', '2021', 'Q4', 'December', '12/08/2021']; + const formattedDate = Intl.DateTimeFormat(pivotGrid.locale, { dateStyle: 'short' }).format(new Date(2021, 11, 8)); + const expectedHeaders = ['All Periods', '2021', 'Q4', 'December', formattedDate]; const rowHeaders = fixture.debugElement.queryAll( By.directive(IgxPivotRowDimensionHeaderComponent)); const rowDimensionHeaders = rowHeaders.map(x => x.componentInstance.column.header); diff --git a/projects/igniteui-angular/grids/pivot-grid/src/pivot-row-dimension-content.component.ts b/projects/igniteui-angular/grids/pivot-grid/src/pivot-row-dimension-content.component.ts index 5abfa05e44b..9912deb2bf7 100644 --- a/projects/igniteui-angular/grids/pivot-grid/src/pivot-row-dimension-content.component.ts +++ b/projects/igniteui-angular/grids/pivot-grid/src/pivot-row-dimension-content.component.ts @@ -183,7 +183,11 @@ export class IgxPivotRowDimensionContentComponent extends IgxGridHeaderRowCompon protected extractFromDimension(dim: IPivotDimension, rowData: IPivotGridGroupRecord) { const field = dim.memberName; - const header = rowData?.dimensionValues.get(field); + const rawHeader = rowData?.dimensionValues.get(field); + let header = rawHeader; + if (dim.headerFormatter != null) { + header = dim.headerFormatter(rawHeader, dim, rowData) ?? rawHeader; + } const col = this._createColComponent(field, header, dim); return col; }