Skip to content

Cell merge POC #16018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions projects/igniteui-angular/src/lib/grids/common/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ export const GridSelectionMode = {
} as const;
export type GridSelectionMode = (typeof GridSelectionMode)[keyof typeof GridSelectionMode];


/**
* Enumeration representing different cell merging modes for the grid elements.
* - 'never': Never merge cells.
* - 'always': Always merge adjacent cells based on merge strategy.
* - 'onSort': Only merge cells in column that are sorted.
*/
export const GridCellMergeMode = {
never: 'never',
always: 'always',
onSort: 'onSort'
} as const;
export type GridCellMergeMode = (typeof GridCellMergeMode)[keyof typeof GridCellMergeMode];

/** Enumeration representing different column display order options. */
export const ColumnDisplayOrder = {
Alphabetical: 'Alphabetical',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ColumnPinningPosition, FilterMode, GridPagingMode, GridSelectionMode, GridSummaryCalculationMode, GridSummaryPosition, GridValidationTrigger, RowPinningPosition, Size } from './enums';
import { ColumnPinningPosition, FilterMode, GridCellMergeMode, GridPagingMode, GridSelectionMode, GridSummaryCalculationMode, GridSummaryPosition, GridValidationTrigger, RowPinningPosition, Size } from './enums';
import {
ISearchInfo, IGridCellEventArgs, IRowSelectionEventArgs, IColumnSelectionEventArgs,
IPinColumnCancellableEventArgs, IColumnVisibilityChangedEventArgs, IColumnVisibilityChangingEventArgs,
Expand Down Expand Up @@ -690,6 +690,7 @@ export interface GridServiceType {
export interface GridType extends IGridDataBindable {
/** Represents the locale of the grid: `USD`, `EUR`, `GBP`, `CNY`, `JPY`, etc. */
locale: string;
cellMergeMode: GridCellMergeMode;
resourceStrings: IGridResourceStrings;
/* blazorSuppress */
/** Represents the native HTML element itself */
Expand Down Expand Up @@ -1180,6 +1181,7 @@ export interface GridType extends IGridDataBindable {
getEmptyRecordObjectFor(inRow: RowType): any;
isSummaryRow(rec: any): boolean;
isRecordPinned(rec: any): boolean;
isRecordMerged(rec: any): boolean;
getInitialPinnedIndex(rec: any): number;
isRecordPinnedByViewIndex(rowIndex: number): boolean;
isColumnGrouped(fieldName: string): boolean;
Expand Down
19 changes: 18 additions & 1 deletion projects/igniteui-angular/src/lib/grids/grid-base.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ import {
RowPinningPosition,
GridPagingMode,
GridValidationTrigger,
Size
Size,
GridCellMergeMode
} from './common/enums';
import {
IGridCellEventArgs,
Expand Down Expand Up @@ -2911,6 +2912,14 @@ export abstract class IgxGridBaseDirective implements GridType,
// }
}

/**
* Gets/Sets cell merge mode.
*
*/
@WatchChanges()
@Input()
public cellMergeMode: GridCellMergeMode = GridCellMergeMode.never;

/**
* Gets/Sets row selection mode
*
Expand Down Expand Up @@ -3635,6 +3644,14 @@ export abstract class IgxGridBaseDirective implements GridType,
return this.getInitialPinnedIndex(rec) !== -1;
}

/**
* @hidden
* @internal
*/
public isRecordMerged(rec) {
return rec.cellMergeMeta;
}

/**
* @hidden
* @internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,19 @@
}
}
<ng-template igxGridFor let-col [igxGridForOf]="unpinnedColumns | igxNotGrouped" [igxForScrollContainer]="grid.parentVirtDir" [igxForScrollOrientation]="'horizontal'" [igxForContainerSize]="grid.unpinnedWidth" [igxForSizePropName]="'calcPixelWidth'" [igxForTrackBy]="grid.trackColumnChanges" #igxDirRef>
<ng-container *ngTemplateOutlet="col.visibleIndex === 0 && grid.hasDetails ? expandableCellTemplate : cellTemplate; context: getContext(col, this)"></ng-container>
@if (this.hasMergedCells) {
<div [style.height.px]="this.grid.rowHeight"
[style.visibility]="this.data.cellMergeMeta.get(col.field).root ? 'hidden' : 'visible'"
class="igx-grid__mrl-block" [ngStyle]="{
'grid-template-rows':this.getMergeCellSpan(col)
}">
<ng-container *ngTemplateOutlet="col.visibleIndex === 0 && grid.hasDetails ? expandableCellTemplate : mergedCellTemplate; context: getContext(col, this)"></ng-container>
</div>
}
@else {
<ng-container *ngTemplateOutlet="col.visibleIndex === 0 && grid.hasDetails ? expandableCellTemplate : cellTemplate; context: getContext(col, this)"></ng-container>
}

</ng-template>
@if (pinnedColumns.length > 0 && !grid.isPinningToStart) {
@for (col of pinnedColumns | igxNotGrouped; track trackPinnedColumn(col)) {
Expand Down Expand Up @@ -158,6 +170,40 @@
</igx-expandable-grid-cell>
</ng-template>

<ng-template #mergedCellTemplate let-col>
<igx-grid-cell
class="igx-grid__td igx-grid__td--fw"
[class.igx-grid__td--edited]="key | transactionState:col.field:grid.rowEditable:grid.transactions:grid.pipeTrigger:grid.gridAPI.crudService.cell:grid.gridAPI.crudService.row"
[class.igx-grid__td--pinned]="col.pinned"
[class.igx-grid__td--number]="col.dataType === 'number' || col.dataType === 'percent' || col.dataType === 'currency'"
[ngClass]="col.cellClasses | igxCellStyleClasses:data[col.field]:data:col.field:viewIndex:grid.pipeTrigger"
[ngStyle]="col.cellStyles | igxCellStyles:data[col.field]:data:col.field:viewIndex:grid.pipeTrigger"
[editMode]="col.editable && this.grid.crudService.targetInEdit(index, col.index)"
[column]="col"
[lastPinned]="col.columnLayoutChild ? null : col.isLastPinned"
[firstPinned]="col.columnLayoutChild ? null : col.isFirstPinned"
[formatter]="col.formatter"
[intRow]="this"
[rowData]="data.recordRef"
[visibleColumnIndex]="col.visibleIndex"
[value]="data.recordRef | dataMapper:col.field:grid.pipeTrigger:data.recordRef[col.field]:col.hasNestedPath"
[cellTemplate]="col.bodyTemplate"
[cellValidationErrorTemplate]="col.errorTemplate"
[lastSearchInfo]="grid.lastSearchInfo"
[active]="isCellActive(col.visibleIndex)"
[cellSelectionMode]="grid.cellSelection"
[displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)"
[style.height.px]="data.cellMergeMeta.get(col.field).rowSpan * (this.grid.rowHeight + 1)"
[style.zIndex]="data.cellMergeMeta.get(col.field).rowSpan"
[style.min-width]="col.resolvedWidth"
[style.max-width]="col.resolvedWidth"
[style.flex-basis]="col.resolvedWidth"
[width]="col.getCellWidth()"
[style.background]="'white'"
#cell>
</igx-grid-cell>
</ng-template>

<ng-template #mrlCellTemplate let-col>
<igx-grid-cell
class="igx-grid__td igx-grid__td--fw"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
| gridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:id:showSummaryOnCollapse:pipeTrigger:summaryPipeTrigger
| gridDetails:hasDetails:expansionStates:pipeTrigger
| gridAddRow:false:pipeTrigger
| gridRowPinning:id:false:pipeTrigger"
| gridRowPinning:id:false:pipeTrigger
| gridCellMerge:pipeTrigger"
let-rowIndex="index" [igxForScrollOrientation]="'vertical'" [igxForScrollContainer]="verticalScroll"
[igxForContainerSize]="calcHeight"
[igxForItemSize]="hasColumnLayouts ? rowHeight * multiRowLayoutRowSize + 1 : renderedRowHeight"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { IGridGroupingStrategy } from '../common/strategy';
import { IgxGridValidationService } from './grid-validation.service';
import { IgxGridDetailsPipe } from './grid.details.pipe';
import { IgxGridSummaryPipe } from './grid.summary.pipe';
import { IgxGridGroupingPipe, IgxGridPagingPipe, IgxGridSortingPipe, IgxGridFilteringPipe } from './grid.pipes';
import { IgxGridGroupingPipe, IgxGridPagingPipe, IgxGridSortingPipe, IgxGridFilteringPipe, IgxGridCellMergePipe } from './grid.pipes';
import { IgxSummaryDataPipe } from '../summaries/grid-root-summary.pipe';
import { IgxGridTransactionPipe, IgxHasVisibleColumnsPipe, IgxGridRowPinningPipe, IgxGridAddRowPipe, IgxGridRowClassesPipe, IgxGridRowStylesPipe, IgxStringReplacePipe } from '../common/pipes';
import { IgxGridColumnResizerComponent } from '../resizing/resizer.component';
Expand Down Expand Up @@ -151,7 +151,8 @@ export interface IGroupingDoneEventArgs extends IBaseEventArgs {
IgxGridFilteringPipe,
IgxGridSummaryPipe,
IgxGridDetailsPipe,
IgxStringReplacePipe
IgxStringReplacePipe,
IgxGridCellMergePipe
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
Expand Down
47 changes: 47 additions & 0 deletions projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { GridType, IGX_GRID_BASE } from '../common/grid.interface';
import { FilterUtil, IFilteringStrategy } from '../../data-operations/filtering-strategy';
import { ISortingExpression } from '../../data-operations/sorting-strategy';
import { IGridSortingStrategy, IGridGroupingStrategy } from '../common/strategy';
import { GridCellMergeMode } from 'igniteui-angular';

/**
* @hidden
Expand Down Expand Up @@ -76,6 +77,52 @@ export class IgxGridGroupingPipe implements PipeTransform {
}
}

@Pipe({
name: 'gridCellMerge',
standalone: true
})
export class IgxGridCellMergePipe implements PipeTransform {

constructor(@Inject(IGX_GRID_BASE) private grid: GridType) { }

public transform(collection: any, _pipeTrigger: number) {
if (this.grid.cellMergeMode === GridCellMergeMode.never) {
return collection;
}
const visibleColumns = this.grid.visibleColumns;
let prev = null;
let result = [];
for (const rec of collection) {
let recData = { recordRef: rec, cellMergeMeta: new Map<string, IMergeByResult>() };
for (const col of visibleColumns) {
recData.cellMergeMeta.set(col.field, { rowSpan: 1 });
//TODO condition can be a strategy or some callback that the user can set.
//TODO can also be limited to only sorted columns
if ( prev && prev.recordRef[col.field] === rec[col.field]) {
// const root = prev.cellMergeMeta.get(col.field)?.root ?? prev;
// root.cellMergeMeta.get(col.field).rowSpan += 1;
// recData.cellMergeMeta.get(col.field).root = root;
recData.cellMergeMeta.get(col.field).prev = prev;
let curr = prev;
while(curr) {
curr.cellMergeMeta.get(col.field).rowSpan += 1;
curr = curr.cellMergeMeta.get(col.field).prev;
}
}
}
prev = recData;
result.push(recData);
}
return result;
}
}

export interface IMergeByResult {
rowSpan: number;
root?: any;
prev?: any;
}

/**
* @hidden
*/
Expand Down
10 changes: 10 additions & 0 deletions projects/igniteui-angular/src/lib/grids/row.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { mergeWith } from 'lodash-es';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { trackByIdentity } from '../core/utils';
import { IMergeByResult } from './grid/grid.pipes';

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

public get hasMergedCells(): boolean {
return this.grid.isRecordMerged(this.data);
}

/**
* Gets the expanded state of the row.
* ```typescript
Expand Down Expand Up @@ -592,6 +597,11 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy {
this.addAnimationEnd.emit(this);
}

protected getMergeCellSpan(col: ColumnType){
const rowCount = this.data.cellMergeMeta.get(col.field).rowSpan;
return `repeat(${rowCount},51px)`;
}

/**
* @hidden
*/
Expand Down
5 changes: 5 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ export class AppComponent implements OnInit {
icon: 'view_column',
name: 'Grid Cell Editing'
},
{
link: '/gridCellMerging',
icon: 'view_column',
name: 'Grid Cell Merging'
},
{
link: '/gridClipboard',
icon: 'insert_comment',
Expand Down
5 changes: 5 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { TimePickerSampleComponent } from './time-picker/time-picker.sample';
import { ToastShowcaseSampleComponent } from './toast-showcase/toast-showcase.sample';
import { VirtualForSampleComponent } from './virtual-for-directive/virtual-for.sample';
import { GridCellEditingComponent } from './grid-cellEditing/grid-cellEditing.component';
import { GridCellMergingComponent } from './grid-cellMerging/grid-cellMerging.component';
import { GridSampleComponent } from './grid/grid.sample';
import { GridColumnMovingSampleComponent } from './grid-column-moving/grid-column-moving.sample';
import { GridColumnSelectionSampleComponent } from './grid-column-selection/grid-column-selection.sample';
Expand Down Expand Up @@ -419,6 +420,10 @@ export const appRoutes: Routes = [
path: 'gridCellEditing',
component: GridCellEditingComponent
},
{
path: 'gridCellMerging',
component: GridCellMergingComponent
},
{
path: 'gridConditionalCellStyling',
component: GridCellStylingSampleComponent
Expand Down
15 changes: 15 additions & 0 deletions src/app/grid-cellMerging/grid-cellMerging.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<h4 class="sample-title">Grid with cell merge</h4>
<igx-grid [data]="data" width="800px" height="550px" [moving]="true" [cellMergeMode]="'always'">
<igx-column field="ProductID" header="Product ID" width="200px">
</igx-column>
<igx-column field="ReorderLevel" width="200px">
</igx-column>
<igx-column required field="ProductName" width="200px">
</igx-column>
<igx-column field="UnitsInStock" header="UnitsInStock" width="200px">
</igx-column>
<igx-column field="OrderDate" width="200px">
</igx-column>
<igx-column field="Discontinued" header="Discontinued">
</igx-column>
</igx-grid>
19 changes: 19 additions & 0 deletions src/app/grid-cellMerging/grid-cellMerging.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.sample-actions {
display: flex;
flex-wrap: wrap;
margin: 1rem 0;
gap: 0.5rem;
}

.density-chooser {
margin-bottom: 1rem;

igx-buttongroup {
display: block;
width: 500px;
}
}

.grid-size {
--ig-size: var(--ig-size-small);
}
Loading
Loading