Skip to content

Commit b340e00

Browse files
authored
feat(pivot-grid): Add pivotConfiguration change event and clone Value strategy. (#12859)
1 parent 7ff88a2 commit b340e00

File tree

11 files changed

+211
-87
lines changed

11 files changed

+211
-87
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ import { IgxGridModule } from 'igniteui-angular';
8484
- `IgxGrid`, `IgxHierarchicalGrid`:
8585
- `totalItemCount` can now also be bound as `Input` in remote virtualization scenarios.
8686
- `rowExpandedIndicatorTemplate`, `rowCollapsedIndicatorTemplate`, `headerExpandedIndicatorTemplate`, `headerCollapsedIndicatorTemplate` can now also be bound as `Input` to provide templates for the row and header expand/collapse indicators respectively. This is in addition to the existing equivalent template directives to allow reuse.
87+
- `IgxPivotGrid`
88+
- Added `pivotConfigurationChanged` event triggered any time any of `pivotConfiguration` properties is changed via the UI.
8789
- `ISortingExpression` now accepts an optional generic type parameter for type narrowing of the `fieldName` property to keys of the data item, e.g. `ISortingExpression<MyDataItem>`
90+
- `Util`
91+
- Added new `CachedDataCloneStrategy` that allows for cloning object with circular references .
8892

8993
## 15.1.0
9094

projects/igniteui-angular/src/lib/core/utils.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,46 @@ export const cloneValue = (value: any): any => {
143143
return value;
144144
};
145145

146+
/**
147+
* Creates deep clone of provided value.
148+
* Supports primitive values, dates and objects.
149+
* If passed value is array returns shallow copy of the array.
150+
* For Objects property values and references are cached and reused.
151+
* This allows for circular references to same objects.
152+
*
153+
* @param value value to clone
154+
* @param cache map of cached values already parsed
155+
* @returns Deep copy of provided value
156+
* @hidden
157+
*/
158+
export const cloneValueCached = (value: any, cache: Map<any, any>): any => {
159+
if (isDate(value)) {
160+
return new Date(value.getTime());
161+
}
162+
if (Array.isArray(value)) {
163+
return [...value];
164+
}
165+
166+
if (value instanceof Map || value instanceof Set) {
167+
return value;
168+
}
169+
170+
if (isObject(value)) {
171+
if (cache.has(value)) {
172+
return cache.get(value);
173+
}
174+
175+
const result = {};
176+
cache.set(value, result);
177+
178+
for (const key of Object.keys(value)) {
179+
result[key] = cloneValueCached(value[key], cache);
180+
}
181+
return result;
182+
}
183+
return value;
184+
};
185+
146186
/**
147187
* Parse provided input to Date.
148188
*
Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
1-
import { cloneValue } from "../core/utils";
1+
import { cloneValue, cloneValueCached } from "../core/utils";
22

33
export interface IDataCloneStrategy {
4+
/**
5+
* Clones provided data
6+
* @param data primitive value, date and object to be cloned
7+
* @returns deep copy of provided value
8+
*/
49
clone(data: any): any;
510
}
611

12+
/**
13+
* Simplified data clone strategy that deep clones primitive values, dates and objects.
14+
* Does not support circular references in objects.
15+
*/
716
export class DefaultDataCloneStrategy implements IDataCloneStrategy {
817
public clone(data: any): any {
918
return cloneValue(data);
1019
}
1120
}
21+
22+
/**
23+
* Data clone strategy that is uses cache to deep clone primitive values, dates and objects.
24+
* It allows using circular references inside object.
25+
*/
26+
export class CachedDataCloneStrategy implements IDataCloneStrategy {
27+
public clone(data: any): any {
28+
return cloneValueCached(data, new Map<any, any>);
29+
}
30+
}

projects/igniteui-angular/src/lib/data-operations/pivot-strategy.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { PivotUtil } from '../grids/pivot-grid/pivot-util';
55
import { FilteringStrategy, IgxFilterItem } from './filtering-strategy';
66
import { cloneArray } from '../core/utils';
77
import { IFilteringExpressionsTree } from './filtering-expressions-tree';
8+
import { IDataCloneStrategy } from './data-clone-strategy';
89

910
export class NoopPivotDimensionsStrategy implements IPivotDimensionStrategy {
1011
private static _instance: NoopPivotDimensionsStrategy = null;
@@ -29,8 +30,9 @@ export class PivotRowDimensionsStrategy implements IPivotDimensionStrategy {
2930
public process(
3031
collection: any,
3132
rows: IPivotDimension[],
32-
values?: IPivotValue[],
33-
pivotKeys: IPivotKeys = DEFAULT_PIVOT_KEYS
33+
values: IPivotValue[],
34+
cloneStrategy: IDataCloneStrategy,
35+
pivotKeys: IPivotKeys = DEFAULT_PIVOT_KEYS,
3436
): IPivotGridRecord[] {
3537
let hierarchies;
3638
let data: IPivotGridRecord[];
@@ -39,7 +41,7 @@ export class PivotRowDimensionsStrategy implements IPivotDimensionStrategy {
3941
PivotUtil.assignLevels(currRows);
4042

4143
if (currRows.length === 0) {
42-
hierarchies = PivotUtil.getFieldsHierarchy(collection, [{ memberName: '', enabled: true }], PivotDimensionType.Row, pivotKeys);
44+
hierarchies = PivotUtil.getFieldsHierarchy(collection, [{ memberName: '', enabled: true }], PivotDimensionType.Row, pivotKeys, cloneStrategy);
4345
// generate flat data from the hierarchies
4446
data = PivotUtil.processHierarchy(hierarchies, pivotKeys, 0, true);
4547
return data;
@@ -48,12 +50,12 @@ export class PivotRowDimensionsStrategy implements IPivotDimensionStrategy {
4850
for (const row of currRows) {
4951
if (!data) {
5052
// build hierarchies - groups and subgroups
51-
hierarchies = PivotUtil.getFieldsHierarchy(collection, [row], PivotDimensionType.Row, pivotKeys);
53+
hierarchies = PivotUtil.getFieldsHierarchy(collection, [row], PivotDimensionType.Row, pivotKeys, cloneStrategy);
5254
// generate flat data from the hierarchies
5355
data = PivotUtil.processHierarchy(hierarchies, pivotKeys, 0, true);
5456
prevRowDims.push(row);
5557
} else {
56-
PivotUtil.processGroups(data, row, pivotKeys);
58+
PivotUtil.processGroups(data, row, pivotKeys, cloneStrategy);
5759
}
5860
}
5961
return data;
@@ -71,39 +73,40 @@ export class PivotColumnDimensionsStrategy implements IPivotDimensionStrategy {
7173
collection: IPivotGridRecord[],
7274
columns: IPivotDimension[],
7375
values: IPivotValue[],
76+
cloneStrategy: IDataCloneStrategy,
7477
pivotKeys: IPivotKeys = DEFAULT_PIVOT_KEYS
7578
): any[] {
76-
const res = this.processHierarchy(collection, columns, values, pivotKeys);
79+
const res = this.processHierarchy(collection, columns, values, pivotKeys, cloneStrategy);
7780
return res;
7881
}
7982

80-
private processHierarchy(collection: IPivotGridRecord[], columns: IPivotDimension[], values, pivotKeys) {
83+
private processHierarchy(collection: IPivotGridRecord[], columns: IPivotDimension[], values, pivotKeys, cloneStrategy) {
8184
const result: IPivotGridRecord[] = [];
8285
collection.forEach(rec => {
8386
// apply aggregations based on the created groups and generate column fields based on the hierarchies
84-
this.groupColumns(rec, columns, values, pivotKeys);
87+
this.groupColumns(rec, columns, values, pivotKeys, cloneStrategy);
8588
result.push(rec);
8689
});
8790
return result;
8891
}
8992

90-
private groupColumns(rec: IPivotGridRecord, columns, values, pivotKeys) {
93+
private groupColumns(rec: IPivotGridRecord, columns, values, pivotKeys, cloneStrategy) {
9194
const children = rec.children;
9295
if (children && children.size > 0) {
9396
children.forEach((childRecs) => {
9497
if (childRecs) {
9598
childRecs.forEach(child => {
96-
this.groupColumns(child, columns, values, pivotKeys);
99+
this.groupColumns(child, columns, values, pivotKeys, cloneStrategy);
97100
})
98101
}
99102
});
100103
}
101-
this.applyAggregates(rec, columns, values, pivotKeys);
104+
this.applyAggregates(rec, columns, values, pivotKeys, cloneStrategy);
102105
}
103106

104-
private applyAggregates(rec, columns, values, pivotKeys) {
107+
private applyAggregates(rec, columns, values, pivotKeys, cloneStrategy) {
105108
const leafRecords = this.getLeafs(rec.records, pivotKeys);
106-
const hierarchy = PivotUtil.getFieldsHierarchy(leafRecords, columns, PivotDimensionType.Column, pivotKeys);
109+
const hierarchy = PivotUtil.getFieldsHierarchy(leafRecords, columns, PivotDimensionType.Column, pivotKeys, cloneStrategy);
107110
PivotUtil.applyAggregations(rec, hierarchy, values, pivotKeys)
108111
}
109112

@@ -121,6 +124,7 @@ export class PivotColumnDimensionsStrategy implements IPivotDimensionStrategy {
121124
}
122125

123126
export class DimensionValuesFilteringStrategy extends FilteringStrategy {
127+
124128
/**
125129
* Creates a new instance of FormattedValuesFilteringStrategy.
126130
*
@@ -149,7 +153,8 @@ export class DimensionValuesFilteringStrategy extends FilteringStrategy {
149153
data,
150154
[dim],
151155
PivotDimensionType.Column,
152-
grid.pivotKeys
156+
grid.pivotKeys,
157+
grid.pivotValueCloneStrategy
153158
);
154159
const isNoop = grid.pivotConfiguration.columnStrategy instanceof NoopPivotDimensionsStrategy || grid.pivotConfiguration.rowStrategy instanceof NoopPivotDimensionsStrategy;
155160
const items: IgxFilterItem[] = !isNoop ? this._getFilterItems(allValuesHierarchy, grid.pivotKeys) : [{value : ''}];

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ export class IgxPivotFilteringService extends IgxFilteringService {
3333
protected override filter_internal(fieldName: string, term, conditionOrExpressionsTree: IFilteringOperation | IFilteringExpressionsTree,
3434
ignoreCase: boolean) {
3535
super.filter_internal(fieldName, term, conditionOrExpressionsTree, ignoreCase);
36-
const grid = this.grid;
37-
const config = (grid as IgxPivotGridComponent).pivotConfiguration;
36+
const grid = this.grid as IgxPivotGridComponent;
37+
const config = grid.pivotConfiguration;
3838
const allDimensions = PivotUtil.flatten(config.rows.concat(config.columns).concat(config.filters).filter(x => x !== null && x !== undefined));
3939
const enabledDimensions = allDimensions.filter(x => x && x.enabled);
4040
const dim = enabledDimensions.find(x => x.memberName === fieldName || x.member === fieldName);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
<ng-template igxGridFor let-rowData [igxGridForOf]="data
3434
| pivotGridFilter:pivotConfiguration:filterStrategy:advancedFilteringExpressionsTree:filteringPipeTrigger:pipeTrigger
3535
| pivotGridSort:pivotConfiguration:sortStrategy:pipeTrigger
36-
| pivotGridRow:pivotConfiguration:expansionStates:pipeTrigger:sortingExpressions
37-
| pivotGridColumn:pivotConfiguration:expansionStates:pipeTrigger:sortingExpressions
36+
| pivotGridRow:pivotConfiguration:pivotValueCloneStrategy:expansionStates:pipeTrigger:sortingExpressions
37+
| pivotGridColumn:pivotConfiguration:pivotValueCloneStrategy:expansionStates:pipeTrigger:sortingExpressions
3838
| pivotGridAutoTransform:pivotConfiguration:pipeTrigger
3939
| pivotGridColumnSort:sortingExpressions:sortStrategy:pipeTrigger
4040
| pivotGridRowExpansion:pivotConfiguration:expansionStates:defaultExpandState:pipeTrigger"

projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.component.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { IgxForOfSyncService, IgxForOfScrollSyncService } from '../../directives
3636
import { ColumnType, GridType, IGX_GRID_BASE, RowType } from '../common/grid.interface';
3737
import { IgxGridCRUDService } from '../common/crud.service';
3838
import { IgxGridSummaryService } from '../summaries/grid-summary.service';
39-
import { DEFAULT_PIVOT_KEYS, IDimensionsChange, IgxPivotGridValueTemplateContext, IPivotConfiguration, IPivotDimension, IPivotValue, IValuesChange, PivotDimensionType } from './pivot-grid.interface';
39+
import { DEFAULT_PIVOT_KEYS, IDimensionsChange, IgxPivotGridValueTemplateContext, IPivotConfiguration, IPivotConfigurationChangedEventArgs, IPivotDimension, IPivotValue, IValuesChange, PivotDimensionType } from './pivot-grid.interface';
4040
import { IgxPivotHeaderRowComponent } from './pivot-header-row.component';
4141
import { IgxColumnGroupComponent } from '../columns/column-group.component';
4242
import { IgxColumnComponent } from '../columns/column.component';
@@ -88,6 +88,7 @@ import { IgxColumnMovingDropDirective } from '../moving/moving.drop.directive';
8888
import { IgxGridDragSelectDirective } from '../selection/drag-select.directive';
8989
import { IgxGridBodyDirective } from '../grid.common';
9090
import { IgxColumnResizingService } from '../resizing/resizing.service';
91+
import { DefaultDataCloneStrategy, IDataCloneStrategy } from '../../data-operations/data-clone-strategy';
9192

9293
let NEXT_ID = 0;
9394
const MINIMUM_COLUMN_WIDTH = 200;
@@ -183,6 +184,18 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
183184
@Output()
184185
public dimensionsChange = new EventEmitter<IDimensionsChange>();
185186

187+
/**
188+
* Emitted when any of the pivotConfiguration properties is changed via the grid chip area.
189+
*
190+
* @example
191+
* ```html
192+
* <igx-pivot-grid #grid [data]="localData" [height]="'305px'"
193+
* (pivotConfigurationChanged)="configurationChanged($event)"></igx-grid>
194+
* ```
195+
*/
196+
@Output()
197+
public pivotConfigurationChange = new EventEmitter<IPivotConfigurationChangedEventArgs>();
198+
186199

187200
/**
188201
* Emitted when the dimension is initialized.
@@ -373,6 +386,26 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
373386
}
374387
}
375388

389+
/**
390+
* Gets/Sets the values clone strategy of the pivot grid when assigning them to different dimensions.
391+
*
392+
* @example
393+
* ```html
394+
* <igx-pivot-grid #grid [data]="localData" [pivotValueCloneStrategy]="customCloneStrategy"></igx-pivot-grid>
395+
* ```
396+
* @hidden @internal
397+
*/
398+
@Input()
399+
public get pivotValueCloneStrategy(): IDataCloneStrategy {
400+
return this._pivotValueCloneStrategy;
401+
}
402+
403+
public set pivotValueCloneStrategy(strategy: IDataCloneStrategy) {
404+
if (strategy) {
405+
this._pivotValueCloneStrategy = strategy;
406+
}
407+
}
408+
376409
/**
377410
* @hidden @internal
378411
*/
@@ -600,6 +633,7 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
600633
return this._emptyRowDimension;
601634
}
602635

636+
protected _pivotValueCloneStrategy: IDataCloneStrategy = new DefaultDataCloneStrategy();
603637
protected override _defaultExpandState = false;
604638
protected override _filterStrategy: IFilteringStrategy = new DimensionValuesFilteringStrategy();
605639
private _data;
@@ -1537,6 +1571,7 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
15371571
this.dimensionDataColumns = this.generateDimensionColumns();
15381572
this.reflow();
15391573
}
1574+
this.pivotConfigurationChange.emit({ pivotConfiguration: this.pivotConfiguration });
15401575
}
15411576

15421577
/**
@@ -1615,6 +1650,7 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
16151650
if (dimType === PivotDimensionType.Filter) {
16161651
this.reflow();
16171652
}
1653+
this.pivotConfigurationChange.emit({ pivotConfiguration: this.pivotConfiguration });
16181654
}
16191655

16201656
/**
@@ -1642,6 +1678,7 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
16421678
this.pipeTrigger++;
16431679
this.cdr.detectChanges();
16441680
this.valuesChange.emit({ values });
1681+
this.pivotConfigurationChange.emit({ pivotConfiguration: this.pivotConfiguration });
16451682
}
16461683

16471684
/**
@@ -1682,6 +1719,7 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
16821719
this.setupColumns();
16831720
this.pipeTrigger++;
16841721
this.valuesChange.emit({ values });
1722+
this.pivotConfigurationChange.emit({ pivotConfiguration: this.pivotConfiguration });
16851723
}
16861724
}
16871725

@@ -1702,6 +1740,7 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
17021740
this.pipeTrigger++;
17031741
this.valuesChange.emit({ values: this.pivotConfiguration.values });
17041742
this.reflow();
1743+
this.pivotConfigurationChange.emit({ pivotConfiguration: this.pivotConfiguration });
17051744
}
17061745

17071746
/**
@@ -1727,6 +1766,7 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
17271766
this.setupColumns();
17281767
}
17291768
this.cdr.detectChanges();
1769+
this.pivotConfigurationChange.emit({ pivotConfiguration: this.pivotConfiguration });
17301770
}
17311771

17321772
/**
@@ -1947,7 +1987,8 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
19471987
sortedData,
19481988
this.columnDimensions,
19491989
PivotDimensionType.Column,
1950-
this.pivotKeys
1990+
this.pivotKeys,
1991+
this.pivotValueCloneStrategy
19511992
);
19521993
}
19531994
columns = this.generateColumnHierarchy(fieldsMap, sortedData);

0 commit comments

Comments
 (0)