From ac8f20e9a858e16ce9869e3dca19190c1cd6d30c Mon Sep 17 00:00:00 2001 From: Maximilian Koeller Date: Wed, 26 Feb 2025 16:14:20 +0100 Subject: [PATCH] fix: apply even/odd classes correctly when rows are grouped BREAKING CHANGE: Previously `CellContext.rowIndex` was a `string` if the row was inside a group. Now `CellContext.rowIndex` is always a number. Either containing the index of the row or if the row is inside a group, the index of the group. To access the index value of a row within a group, use the new`CellContext.rowInGroupIndex`. --- .../components/body/body-cell.component.ts | 13 +++-- .../body/body-row.component.spec.ts | 50 +++++++++++++++---- .../lib/components/body/body-row.component.ts | 16 ++++-- .../src/lib/components/body/body.component.ts | 34 ++++++------- .../body/summary/summary-row.component.ts | 2 +- .../src/lib/types/internal.types.ts | 8 +++ .../src/lib/types/public.types.ts | 1 + 7 files changed, 87 insertions(+), 37 deletions(-) diff --git a/projects/ngx-datatable/src/lib/components/body/body-cell.component.ts b/projects/ngx-datatable/src/lib/components/body/body-cell.component.ts index fe7cfb605..74f699fb9 100644 --- a/projects/ngx-datatable/src/lib/components/body/body-cell.component.ts +++ b/projects/ngx-datatable/src/lib/components/body/body-cell.component.ts @@ -29,6 +29,7 @@ import { } from '../../types/public.types'; import { DataTableGhostLoaderComponent } from './ghost-loader/ghost-loader.component'; import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; +import { RowIndex } from '../../types/internal.types'; @Component({ selector: 'datatable-body-cell', @@ -157,14 +158,15 @@ export class DataTableBodyCellComponent return this._expanded; } - @Input() set rowIndex(val: number) { + @Input() set rowIndex(val: RowIndex) { this._rowIndex = val; - this.cellContext.rowIndex = val; + this.cellContext.rowIndex = val?.index; + this.cellContext.rowInGroupIndex = val?.indexInGroup; this.checkValueUpdates(); this.cd.markForCheck(); } - get rowIndex(): number { + get rowIndex(): RowIndex { return this._rowIndex; } @@ -314,7 +316,7 @@ export class DataTableBodyCellComponent private _row: TRow; private _group: TRow[]; private _rowHeight: number; - private _rowIndex: number; + private _rowIndex: RowIndex; private _expanded: boolean; private _element = inject>(ElementRef).nativeElement; private _treeStatus: TreeStatus; @@ -329,7 +331,8 @@ export class DataTableBodyCellComponent column: this.column, rowHeight: this.rowHeight, isSelected: this.isSelected, - rowIndex: this.rowIndex, + rowIndex: this.rowIndex?.index, + rowInGroupIndex: this.rowIndex?.indexInGroup, treeStatus: this.treeStatus, disable$: this.disable$, onTreeAction: () => this.onTreeAction() diff --git a/projects/ngx-datatable/src/lib/components/body/body-row.component.spec.ts b/projects/ngx-datatable/src/lib/components/body/body-row.component.spec.ts index cab83486a..b3262ffb7 100644 --- a/projects/ngx-datatable/src/lib/components/body/body-row.component.spec.ts +++ b/projects/ngx-datatable/src/lib/components/body/body-row.component.spec.ts @@ -1,28 +1,60 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { DataTableBodyRowComponent } from './body-row.component'; -import { DataTableBodyCellComponent } from './body-cell.component'; +import { Component } from '@angular/core'; +import { ScrollbarHelper } from '../../services/scrollbar-helper.service'; +import { TableColumn } from '../../types/table-column.type'; +import { By } from '@angular/platform-browser'; +import { RowIndex } from '../../types/internal.types'; describe('DataTableBodyRowComponent', () => { - let fixture: ComponentFixture; - let component: DataTableBodyRowComponent; + @Component({ + template: ` `, + imports: [DataTableBodyRowComponent], + standalone: true + }) + class TestHostComponent { + rowIndex: RowIndex = { index: 0 }; + row: any = { prop: 'value' }; + columns: TableColumn[] = [{ prop: 'prop', $$valueGetter: () => 'value' }]; + } + + let fixture: ComponentFixture; + let component: TestHostComponent; // provide our implementations or mocks to the dependency injector beforeEach(() => { TestBed.configureTestingModule({ - imports: [DataTableBodyCellComponent, DataTableBodyRowComponent] + imports: [TestHostComponent], + providers: [ScrollbarHelper] }); }); beforeEach(waitForAsync(() => { TestBed.compileComponents().then(() => { - fixture = TestBed.createComponent(DataTableBodyRowComponent); + fixture = TestBed.createComponent(TestHostComponent); component = fixture.componentInstance; }); })); - describe('fixture', () => { - it('should have a component instance', () => { - expect(component).toBeTruthy(); - }); + it('should apply odd/event without groups', () => { + component.rowIndex = { index: 0 }; + fixture.detectChanges(); + const element = fixture.debugElement.query(By.directive(DataTableBodyRowComponent)) + .nativeElement as HTMLElement; + expect(element.classList).toContain('datatable-row-even'); + component.rowIndex = { index: 3 }; + fixture.detectChanges(); + expect(element.classList).toContain('datatable-row-odd'); + }); + + it('should apply event odd/even if row is grouped', () => { + component.rowIndex = { index: 1, indexInGroup: 0 }; + fixture.detectChanges(); + const element = fixture.debugElement.query(By.directive(DataTableBodyRowComponent)) + .nativeElement as HTMLElement; + expect(element.classList).toContain('datatable-row-even'); + component.rowIndex = { index: 666, indexInGroup: 3 }; + fixture.detectChanges(); + expect(element.classList).toContain('datatable-row-odd'); }); }); diff --git a/projects/ngx-datatable/src/lib/components/body/body-row.component.ts b/projects/ngx-datatable/src/lib/components/body/body-row.component.ts index 46d6eaa7b..0910e0777 100644 --- a/projects/ngx-datatable/src/lib/components/body/body-row.component.ts +++ b/projects/ngx-datatable/src/lib/components/body/body-row.component.ts @@ -22,7 +22,7 @@ import { BehaviorSubject } from 'rxjs'; import { ActivateEvent, RowOrGroup, TreeStatus } from '../../types/public.types'; import { AsyncPipe } from '@angular/common'; import { TableColumn } from '../../types/table-column.type'; -import { ColumnGroupWidth, PinnedColumns } from '../../types/internal.types'; +import { ColumnGroupWidth, PinnedColumns, RowIndex } from '../../types/internal.types'; import { DataTableBodyCellComponent } from './body-cell.component'; @Component({ @@ -92,7 +92,7 @@ export class DataTableBodyRowComponent implements DoCheck, OnChanges @Input() row: TRow; @Input() group: TRow[]; @Input() isSelected: boolean; - @Input() rowIndex: number; + @Input() rowIndex: RowIndex | undefined; @Input() displayCheck: (row: TRow, column: TableColumn, value?: any) => boolean; @Input() treeStatus?: TreeStatus = 'collapsed'; @Input() ghostLoadingIndicator = false; @@ -113,10 +113,10 @@ export class DataTableBodyRowComponent implements DoCheck, OnChanges if (this.isSelected) { cls += ' active'; } - if (this.rowIndex % 2 !== 0) { + if (this.innerRowIndex % 2 !== 0) { cls += ' datatable-row-odd'; } - if (this.rowIndex % 2 === 0) { + if (this.innerRowIndex % 2 === 0) { cls += ' datatable-row-even'; } if (this.disable$ && this.disable$.value) { @@ -228,4 +228,12 @@ export class DataTableBodyRowComponent implements DoCheck, OnChanges onTreeAction() { this.treeAction.emit(); } + + /** Returns the row index, or if in a group, the index within a group. */ + private get innerRowIndex(): number { + if (!this.rowIndex) { + return 0; + } + return this.rowIndex.indexInGroup ?? this.rowIndex.index; + } } diff --git a/projects/ngx-datatable/src/lib/components/body/body.component.ts b/projects/ngx-datatable/src/lib/components/body/body.component.ts index aa6ef90a1..755157a53 100644 --- a/projects/ngx-datatable/src/lib/components/body/body.component.ts +++ b/projects/ngx-datatable/src/lib/components/body/body.component.ts @@ -23,7 +23,7 @@ import { TableColumn } from '../../types/table-column.type'; import { DatatableGroupHeaderDirective } from './body-group-header.directive'; import { DatatableRowDetailDirective } from '../row-detail/row-detail.directive'; import { DataTableBodyRowComponent } from './body-row.component'; -import { ColumnGroupWidth } from '../../types/internal.types'; +import { ColumnGroupWidth, RowIndex } from '../../types/internal.types'; import { ActivateEvent, DragEventData, @@ -39,7 +39,6 @@ import { DataTableRowWrapperComponent } from './body-row-wrapper.component'; import { DataTableSummaryRowComponent } from './summary/summary-row.component'; import { DataTableSelectionComponent } from './selection.component'; import { DataTableGhostLoaderComponent } from './ghost-loader/ghost-loader.component'; -import { ProgressBarComponent } from './progress-bar.component'; @Component({ selector: 'datatable-body', @@ -108,7 +107,7 @@ import { ProgressBarComponent } from './progress-bar.component'; [row]="group" [disableCheck]="disableRowCheck" [expanded]="getRowExpanded(group)" - [rowIndex]="getRowIndex(group && group[i])" + [rowIndex]="getRowIndex(group && group[i])?.index ?? 0" [selected]="selected" (rowContextmenu)="rowContextmenu.emit($event)" > @@ -256,7 +255,6 @@ import { ProgressBarComponent } from './progress-bar.component'; }, standalone: true, imports: [ - ProgressBarComponent, DataTableGhostLoaderComponent, DataTableSelectionComponent, ScrollerComponent, @@ -433,7 +431,7 @@ export class DataTableBodyComponent>; listener: any; - rowIndexes = new WeakMap(); + rowIndexes = new WeakMap, RowIndex>(); rowExpansions: any[] = []; _rows: TRow[]; @@ -459,8 +457,7 @@ export class DataTableBodyComponent { - const _idx = `${rowIndex}-${i}`; - this.rowIndexes.set(g, _idx); + group.value.forEach((g: TRow, i: number) => { + this.rowIndexes.set(g, { index: rowIndex, indexInGroup: i }); }); } temp[idx] = group; @@ -621,7 +617,7 @@ export class DataTableBodyComponent only in case of non-virtualized scroll if (this.scrollbarV && this.virtualization) { const detailRowHeight = this.getDetailRowHeight(row) * (expanded ? -1 : 1); - // const idx = this.rowIndexes.get(row) || 0; - const idx = this.getRowIndex(row); + // This is hopefully only called with non-grouped rows. Otherwise, the heightCache fails. + const idx = this.getRowIndex(row).index; this.rowHeightsCache().update(idx, detailRowHeight); } @@ -954,8 +952,8 @@ export class DataTableBodyComponent): number { - return this.rowIndexes.get(row) || 0; + getRowIndex(row: RowOrGroup): RowIndex | undefined { + return this.rowIndexes.get(row); } onTreeAction(row: TRow) { diff --git a/projects/ngx-datatable/src/lib/components/body/summary/summary-row.component.ts b/projects/ngx-datatable/src/lib/components/body/summary/summary-row.component.ts index d4c0e4063..df3c02822 100644 --- a/projects/ngx-datatable/src/lib/components/body/summary/summary-row.component.ts +++ b/projects/ngx-datatable/src/lib/components/body/summary/summary-row.component.ts @@ -38,7 +38,7 @@ function noopSumFunc(cells: any[]): void { [columns]="_internalColumns" [rowHeight]="rowHeight" [row]="summaryRow" - [rowIndex]="-1" + [rowIndex]="{ index: -1 }" > } diff --git a/projects/ngx-datatable/src/lib/types/internal.types.ts b/projects/ngx-datatable/src/lib/types/internal.types.ts index 3a91005dd..6a335688e 100644 --- a/projects/ngx-datatable/src/lib/types/internal.types.ts +++ b/projects/ngx-datatable/src/lib/types/internal.types.ts @@ -36,3 +36,11 @@ export interface DraggableDragEvent { element: HTMLElement; model: TableColumn; } + +/** Represents the index of a row. */ +export interface RowIndex { + /** Index of the row. If the row is inside a group, it will hold the index the group. */ + index: number; + /** Index of a row inside a group. Only present if the row is inside a group. */ + indexInGroup?: number; +} diff --git a/projects/ngx-datatable/src/lib/types/public.types.ts b/projects/ngx-datatable/src/lib/types/public.types.ts index 027992c63..ca346aee1 100644 --- a/projects/ngx-datatable/src/lib/types/public.types.ts +++ b/projects/ngx-datatable/src/lib/types/public.types.ts @@ -72,6 +72,7 @@ export interface CellContext { rowHeight: number; isSelected: boolean; rowIndex: number; + rowInGroupIndex?: number; treeStatus: TreeStatus; disable$: BehaviorSubject; onTreeAction: () => void;