Skip to content

Commit 7562cc5

Browse files
MKirovaMKirova
authored andcommitted
chore(*): Cell merge POC.
1 parent d0361ce commit 7562cc5

File tree

13 files changed

+371
-6
lines changed

13 files changed

+371
-6
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ export const GridSelectionMode = {
7373
} as const;
7474
export type GridSelectionMode = (typeof GridSelectionMode)[keyof typeof GridSelectionMode];
7575

76+
77+
/**
78+
* Enumeration representing different cell merging modes for the grid elements.
79+
* - 'never': Never merge cells.
80+
* - 'always': Always merge adjacent cells based on merge strategy.
81+
* - 'onSort': Only merge cells in column that are sorted.
82+
*/
83+
export const GridCellMergeMode = {
84+
never: 'never',
85+
always: 'always',
86+
onSort: 'onSort'
87+
} as const;
88+
export type GridCellMergeMode = (typeof GridCellMergeMode)[keyof typeof GridCellMergeMode];
89+
7690
/** Enumeration representing different column display order options. */
7791
export const ColumnDisplayOrder = {
7892
Alphabetical: 'Alphabetical',

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ColumnPinningPosition, FilterMode, GridPagingMode, GridSelectionMode, GridSummaryCalculationMode, GridSummaryPosition, GridValidationTrigger, RowPinningPosition, Size } from './enums';
1+
import { ColumnPinningPosition, FilterMode, GridCellMergeMode, GridPagingMode, GridSelectionMode, GridSummaryCalculationMode, GridSummaryPosition, GridValidationTrigger, RowPinningPosition, Size } from './enums';
22
import {
33
ISearchInfo, IGridCellEventArgs, IRowSelectionEventArgs, IColumnSelectionEventArgs,
44
IPinColumnCancellableEventArgs, IColumnVisibilityChangedEventArgs, IColumnVisibilityChangingEventArgs,
@@ -690,6 +690,7 @@ export interface GridServiceType {
690690
export interface GridType extends IGridDataBindable {
691691
/** Represents the locale of the grid: `USD`, `EUR`, `GBP`, `CNY`, `JPY`, etc. */
692692
locale: string;
693+
cellMergeMode: GridCellMergeMode;
693694
resourceStrings: IGridResourceStrings;
694695
/* blazorSuppress */
695696
/** Represents the native HTML element itself */
@@ -1180,6 +1181,7 @@ export interface GridType extends IGridDataBindable {
11801181
getEmptyRecordObjectFor(inRow: RowType): any;
11811182
isSummaryRow(rec: any): boolean;
11821183
isRecordPinned(rec: any): boolean;
1184+
isRecordMerged(rec: any): boolean;
11831185
getInitialPinnedIndex(rec: any): number;
11841186
isRecordPinnedByViewIndex(rowIndex: number): boolean;
11851187
isColumnGrouped(fieldName: string): boolean;

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ import {
9393
RowPinningPosition,
9494
GridPagingMode,
9595
GridValidationTrigger,
96-
Size
96+
Size,
97+
GridCellMergeMode
9798
} from './common/enums';
9899
import {
99100
IGridCellEventArgs,
@@ -2911,6 +2912,14 @@ export abstract class IgxGridBaseDirective implements GridType,
29112912
// }
29122913
}
29132914

2915+
/**
2916+
* Gets/Sets cell merge mode.
2917+
*
2918+
*/
2919+
@WatchChanges()
2920+
@Input()
2921+
public cellMergeMode: GridCellMergeMode = GridCellMergeMode.never;
2922+
29142923
/**
29152924
* Gets/Sets row selection mode
29162925
*
@@ -3635,6 +3644,14 @@ export abstract class IgxGridBaseDirective implements GridType,
36353644
return this.getInitialPinnedIndex(rec) !== -1;
36363645
}
36373646

3647+
/**
3648+
* @hidden
3649+
* @internal
3650+
*/
3651+
public isRecordMerged(rec) {
3652+
return rec.cellMergeMeta;
3653+
}
3654+
36383655
/**
36393656
* @hidden
36403657
* @internal

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

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,19 @@
3131
}
3232
}
3333
<ng-template igxGridFor let-col [igxGridForOf]="unpinnedColumns | igxNotGrouped" [igxForScrollContainer]="grid.parentVirtDir" [igxForScrollOrientation]="'horizontal'" [igxForContainerSize]="grid.unpinnedWidth" [igxForSizePropName]="'calcPixelWidth'" [igxForTrackBy]="grid.trackColumnChanges" #igxDirRef>
34-
<ng-container *ngTemplateOutlet="col.visibleIndex === 0 && grid.hasDetails ? expandableCellTemplate : cellTemplate; context: getContext(col, this)"></ng-container>
34+
@if (this.hasMergedCells) {
35+
<div [style.height.px]="this.grid.rowHeight"
36+
[style.visibility]="this.data.cellMergeMeta.get(col.field).root ? 'hidden' : 'visible'"
37+
class="igx-grid__mrl-block" [ngStyle]="{
38+
'grid-template-rows':this.getMergeCellSpan(col)
39+
}">
40+
<ng-container *ngTemplateOutlet="col.visibleIndex === 0 && grid.hasDetails ? expandableCellTemplate : mergedCellTemplate; context: getContext(col, this)"></ng-container>
41+
</div>
42+
}
43+
@else {
44+
<ng-container *ngTemplateOutlet="col.visibleIndex === 0 && grid.hasDetails ? expandableCellTemplate : cellTemplate; context: getContext(col, this)"></ng-container>
45+
}
46+
3547
</ng-template>
3648
@if (pinnedColumns.length > 0 && !grid.isPinningToStart) {
3749
@for (col of pinnedColumns | igxNotGrouped; track trackPinnedColumn(col)) {
@@ -158,6 +170,40 @@
158170
</igx-expandable-grid-cell>
159171
</ng-template>
160172

173+
<ng-template #mergedCellTemplate let-col>
174+
<igx-grid-cell
175+
class="igx-grid__td igx-grid__td--fw"
176+
[class.igx-grid__td--edited]="key | transactionState:col.field:grid.rowEditable:grid.transactions:grid.pipeTrigger:grid.gridAPI.crudService.cell:grid.gridAPI.crudService.row"
177+
[class.igx-grid__td--pinned]="col.pinned"
178+
[class.igx-grid__td--number]="col.dataType === 'number' || col.dataType === 'percent' || col.dataType === 'currency'"
179+
[ngClass]="col.cellClasses | igxCellStyleClasses:data[col.field]:data:col.field:viewIndex:grid.pipeTrigger"
180+
[ngStyle]="col.cellStyles | igxCellStyles:data[col.field]:data:col.field:viewIndex:grid.pipeTrigger"
181+
[editMode]="col.editable && this.grid.crudService.targetInEdit(index, col.index)"
182+
[column]="col"
183+
[lastPinned]="col.columnLayoutChild ? null : col.isLastPinned"
184+
[firstPinned]="col.columnLayoutChild ? null : col.isFirstPinned"
185+
[formatter]="col.formatter"
186+
[intRow]="this"
187+
[rowData]="data.recordRef"
188+
[visibleColumnIndex]="col.visibleIndex"
189+
[value]="data.recordRef | dataMapper:col.field:grid.pipeTrigger:data.recordRef[col.field]:col.hasNestedPath"
190+
[cellTemplate]="col.bodyTemplate"
191+
[cellValidationErrorTemplate]="col.errorTemplate"
192+
[lastSearchInfo]="grid.lastSearchInfo"
193+
[active]="isCellActive(col.visibleIndex)"
194+
[cellSelectionMode]="grid.cellSelection"
195+
[displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)"
196+
[style.height.px]="data.cellMergeMeta.get(col.field).rowSpan * (this.grid.rowHeight + 1)"
197+
[style.zIndex]="100"
198+
[style.min-width]="col.resolvedWidth"
199+
[style.max-width]="col.resolvedWidth"
200+
[style.flex-basis]="col.resolvedWidth"
201+
[width]="col.getCellWidth()"
202+
[style.background]="'white'"
203+
#cell>
204+
</igx-grid-cell>
205+
</ng-template>
206+
161207
<ng-template #mrlCellTemplate let-col>
162208
<igx-grid-cell
163209
class="igx-grid__td igx-grid__td--fw"

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@
8080
| gridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:id:showSummaryOnCollapse:pipeTrigger:summaryPipeTrigger
8181
| gridDetails:hasDetails:expansionStates:pipeTrigger
8282
| gridAddRow:false:pipeTrigger
83-
| gridRowPinning:id:false:pipeTrigger"
83+
| gridRowPinning:id:false:pipeTrigger
84+
| gridCellMerge:pipeTrigger"
8485
let-rowIndex="index" [igxForScrollOrientation]="'vertical'" [igxForScrollContainer]="verticalScroll"
8586
[igxForContainerSize]="calcHeight"
8687
[igxForItemSize]="hasColumnLayouts ? rowHeight * multiRowLayoutRowSize + 1 : renderedRowHeight"

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { IGridGroupingStrategy } from '../common/strategy';
3434
import { IgxGridValidationService } from './grid-validation.service';
3535
import { IgxGridDetailsPipe } from './grid.details.pipe';
3636
import { IgxGridSummaryPipe } from './grid.summary.pipe';
37-
import { IgxGridGroupingPipe, IgxGridPagingPipe, IgxGridSortingPipe, IgxGridFilteringPipe } from './grid.pipes';
37+
import { IgxGridGroupingPipe, IgxGridPagingPipe, IgxGridSortingPipe, IgxGridFilteringPipe, IgxGridCellMergePipe } from './grid.pipes';
3838
import { IgxSummaryDataPipe } from '../summaries/grid-root-summary.pipe';
3939
import { IgxGridTransactionPipe, IgxHasVisibleColumnsPipe, IgxGridRowPinningPipe, IgxGridAddRowPipe, IgxGridRowClassesPipe, IgxGridRowStylesPipe, IgxStringReplacePipe } from '../common/pipes';
4040
import { IgxGridColumnResizerComponent } from '../resizing/resizer.component';
@@ -151,7 +151,8 @@ export interface IGroupingDoneEventArgs extends IBaseEventArgs {
151151
IgxGridFilteringPipe,
152152
IgxGridSummaryPipe,
153153
IgxGridDetailsPipe,
154-
IgxStringReplacePipe
154+
IgxStringReplacePipe,
155+
IgxGridCellMergePipe
155156
],
156157
schemas: [CUSTOM_ELEMENTS_SCHEMA]
157158
})

projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { GridType, IGX_GRID_BASE } from '../common/grid.interface';
99
import { FilterUtil, IFilteringStrategy } from '../../data-operations/filtering-strategy';
1010
import { ISortingExpression } from '../../data-operations/sorting-strategy';
1111
import { IGridSortingStrategy, IGridGroupingStrategy } from '../common/strategy';
12+
import { GridCellMergeMode } from 'igniteui-angular';
1213

1314
/**
1415
* @hidden
@@ -76,6 +77,45 @@ export class IgxGridGroupingPipe implements PipeTransform {
7677
}
7778
}
7879

80+
@Pipe({
81+
name: 'gridCellMerge',
82+
standalone: true
83+
})
84+
export class IgxGridCellMergePipe implements PipeTransform {
85+
86+
constructor(@Inject(IGX_GRID_BASE) private grid: GridType) { }
87+
88+
public transform(collection: any, _pipeTrigger: number) {
89+
if (this.grid.cellMergeMode === GridCellMergeMode.never) {
90+
return collection;
91+
}
92+
const visibleColumns = this.grid.visibleColumns;
93+
let prev = null;
94+
let result = [];
95+
for (const rec of collection) {
96+
let recData = { recordRef: rec, cellMergeMeta: new Map<string, IMergeByResult>() };
97+
for (const col of visibleColumns) {
98+
recData.cellMergeMeta.set(col.field, { rowSpan: 1 });
99+
//TODO condition can be a strategy or some callback that the user can set.
100+
//TODO can also be limited to only sorted columns
101+
if ( prev && prev.recordRef[col.field] === rec[col.field]) {
102+
const root = prev.cellMergeMeta.get(col.field)?.root ?? prev;
103+
root.cellMergeMeta.get(col.field).rowSpan += 1;
104+
recData.cellMergeMeta.get(col.field).root = root;
105+
}
106+
}
107+
prev = recData;
108+
result.push(recData);
109+
}
110+
return result;
111+
}
112+
}
113+
114+
export interface IMergeByResult {
115+
rowSpan: number;
116+
root?: any;
117+
}
118+
79119
/**
80120
* @hidden
81121
*/

projects/igniteui-angular/src/lib/grids/row.directive.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { mergeWith } from 'lodash-es';
2727
import { Subject } from 'rxjs';
2828
import { takeUntil } from 'rxjs/operators';
2929
import { trackByIdentity } from '../core/utils';
30+
import { IMergeByResult } from './grid/grid.pipes';
3031

3132
@Directive({
3233
selector: '[igxRowBaseComponent]',
@@ -117,6 +118,10 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy {
117118
return this.grid.isRecordPinned(this.data);
118119
}
119120

121+
public get hasMergedCells(): boolean {
122+
return this.grid.isRecordMerged(this.data);
123+
}
124+
120125
/**
121126
* Gets the expanded state of the row.
122127
* ```typescript
@@ -592,6 +597,11 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy {
592597
this.addAnimationEnd.emit(this);
593598
}
594599

600+
protected getMergeCellSpan(col: ColumnType){
601+
const rowCount = this.data.cellMergeMeta.get(col.field).rowSpan;
602+
return `repeat(${rowCount},51px)`;
603+
}
604+
595605
/**
596606
* @hidden
597607
*/

src/app/app.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ export class AppComponent implements OnInit {
211211
icon: 'view_column',
212212
name: 'Grid Cell Editing'
213213
},
214+
{
215+
link: '/gridCellMerging',
216+
icon: 'view_column',
217+
name: 'Grid Cell Merging'
218+
},
214219
{
215220
link: '/gridClipboard',
216221
icon: 'insert_comment',

src/app/app.routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { TimePickerSampleComponent } from './time-picker/time-picker.sample';
5151
import { ToastShowcaseSampleComponent } from './toast-showcase/toast-showcase.sample';
5252
import { VirtualForSampleComponent } from './virtual-for-directive/virtual-for.sample';
5353
import { GridCellEditingComponent } from './grid-cellEditing/grid-cellEditing.component';
54+
import { GridCellMergingComponent } from './grid-cellMerging/grid-cellMerging.component';
5455
import { GridSampleComponent } from './grid/grid.sample';
5556
import { GridColumnMovingSampleComponent } from './grid-column-moving/grid-column-moving.sample';
5657
import { GridColumnSelectionSampleComponent } from './grid-column-selection/grid-column-selection.sample';
@@ -419,6 +420,10 @@ export const appRoutes: Routes = [
419420
path: 'gridCellEditing',
420421
component: GridCellEditingComponent
421422
},
423+
{
424+
path: 'gridCellMerging',
425+
component: GridCellMergingComponent
426+
},
422427
{
423428
path: 'gridConditionalCellStyling',
424429
component: GridCellStylingSampleComponent

0 commit comments

Comments
 (0)