Skip to content

Commit c80d0e8

Browse files
authored
Merge branch '21.0.x' into gedinakova/fix-CSV-col-headers-when-grouped-21.0
2 parents 9dfa0b2 + 8694d53 commit c80d0e8

File tree

2 files changed

+114
-56
lines changed

2 files changed

+114
-56
lines changed

projects/igniteui-angular/grids/core/src/services/pdf/pdf-exporter-grid.spec.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import { GridIDNameJobTitleComponent } from '../../../../../test-utils/grid-samp
66
import { first } from 'rxjs/operators';
77
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
88
import { NestedColumnGroupsGridComponent, ColumnGroupTestComponent, BlueWhaleGridComponent } from '../../../../../test-utils/grid-mch-sample.spec';
9-
import { IgxHierarchicalGridTestBaseComponent } from '../../../../../test-utils/hierarchical-grid-components.spec';
9+
import { IgxHierarchicalGridExportComponent, IgxHierarchicalGridTestBaseComponent } from '../../../../../test-utils/hierarchical-grid-components.spec';
1010
import { IgxTreeGridSortingComponent, IgxTreeGridPrimaryForeignKeyComponent } from '../../../../../test-utils/tree-grid-components.spec';
1111
import { CustomSummariesComponent } from 'igniteui-angular/grids/grid/src/grid-summary.spec';
1212
import { IgxHierarchicalGridComponent } from 'igniteui-angular/grids/hierarchical-grid';
1313
import { IgxPivotGridMultipleRowComponent, IgxPivotGridTestComplexHierarchyComponent } from '../../../../../test-utils/pivot-grid-samples.spec';
1414
import { IgxPivotGridComponent } from 'igniteui-angular/grids/pivot-grid';
1515
import { PivotRowLayoutType } from 'igniteui-angular/grids/core';
16-
import { wait } from 'igniteui-angular/test-utils/ui-interactions.spec';
16+
import { UIInteractions, wait } from 'igniteui-angular/test-utils/ui-interactions.spec';
1717

1818
describe('PDF Grid Exporter', () => {
1919
let exporter: IgxPdfExporterService;
@@ -328,6 +328,50 @@ describe('PDF Grid Exporter', () => {
328328
exporter.export(grid, options);
329329
});
330330

331+
it('should export the correct number of child data rows from a hierarchical grid', (done) => {
332+
const fix = TestBed.createComponent(IgxHierarchicalGridExportComponent);
333+
fix.detectChanges();
334+
335+
const hGrid = fix.componentInstance.hGrid;
336+
hGrid.data = hGrid.data.slice(0, 1); // Limit data for test performance
337+
338+
// Expand first few rows to realize all inner levels, same as in Excel tests
339+
const firstRow = hGrid.gridAPI.get_row_by_index(0) as any;
340+
341+
UIInteractions.simulateClickAndSelectEvent(firstRow.expander);
342+
fix.detectChanges();
343+
344+
let childGrids = hGrid.gridAPI.getChildGrids(false) as any[];
345+
const firstChildGrid = childGrids[0];
346+
const firstChildRow = firstChildGrid.gridAPI.get_row_by_index(0) as any;
347+
const secondChildRow = firstChildGrid.gridAPI.get_row_by_index(1) as any;
348+
UIInteractions.simulateClickAndSelectEvent(secondChildRow.expander);
349+
fix.detectChanges();
350+
351+
UIInteractions.simulateClickAndSelectEvent(firstChildRow.expander);
352+
fix.detectChanges();
353+
354+
childGrids = hGrid.gridAPI.getChildGrids(false) as any[];
355+
const thirdChildGrid = childGrids[1];
356+
const thirdChildRow = thirdChildGrid.gridAPI.get_row_by_index(0) as any;
357+
UIInteractions.simulateClickAndSelectEvent(thirdChildRow.expander);
358+
fix.detectChanges();
359+
360+
// Calculate expected number of data rows to be exported
361+
const allGrids = [hGrid, ...(hGrid.gridAPI.getChildGrids(true) as any[])];
362+
const expectedRows = allGrids.reduce((acc, g) => acc + g.data.length, 0);
363+
364+
// Spy PDF row drawing to count exported rows
365+
const drawDataRowSpy = spyOn<any>(exporter as any, 'drawDataRow').and.callThrough();
366+
367+
exporter.exportEnded.pipe(first()).subscribe(() => {
368+
expect(drawDataRowSpy.calls.count()).toBe(expectedRows);
369+
done();
370+
});
371+
372+
exporter.export(hGrid, options);
373+
});
374+
331375
it('should export tree grid with hierarchical data', (done) => {
332376
TestBed.configureTestingModule({
333377
imports: [

projects/igniteui-angular/grids/core/src/services/pdf/pdf-exporter.ts

Lines changed: 68 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ export class IgxPdfExporterService extends IgxBaseExporter {
270270
// Draw data rows
271271
pdf.setFont('helvetica', 'normal');
272272

273+
// Check if this is a tree grid export (tree grids can have both TreeGridRecord and DataRecord types for nested children)
274+
const isTreeGridExport = data.some(record => record.type === ExportRecordType.TreeGridRecord);
275+
273276
// For pivot grids, get row dimension columns to help with value lookup
274277
const rowDimensionColumnsByLevel: Map<number, any[]> = new Map();
275278
if (isPivotGrid && defaultOwner) {
@@ -333,12 +336,14 @@ export class IgxPdfExporterService extends IgxBaseExporter {
333336
// Calculate indentation for hierarchical records
334337
// TreeGrid supports both hierarchical data and flat self-referencing data (with foreignKey)
335338
// In both cases, the base exporter sets the level property on TreeGridRecord
336-
const isTreeGrid = record.type === 'TreeGridRecord';
339+
// Note: Nested child records without children are created as DataRecord type,
340+
// but they still have a level property and should be treated as tree grid records
337341
const recordIsHierarchicalGrid = record.type === 'HierarchicalGridRecord';
338342

339343
// For tree grids, indentation is visual (in the first column text)
340344
// For hierarchical grids, we don't use indentation (level determines column offset instead)
341-
const indentLevel = isTreeGrid ? (record.level || 0) : 0;
345+
// If this is a tree grid export and the record has a level property, use it for indentation
346+
const indentLevel = (isTreeGridExport && record.level !== undefined) ? (record.level || 0) : 0;
342347
const indent = indentLevel * indentSize;
343348

344349
// Draw parent row
@@ -347,28 +352,21 @@ export class IgxPdfExporterService extends IgxBaseExporter {
347352

348353
// For hierarchical grids, check if this record has child records
349354
if (recordIsHierarchicalGrid) {
350-
const allDescendants = [];
355+
const allDescendants: Array<IExportRecord & { __index: number }> = [];
351356

352-
// Collect all descendant records (children, grandchildren, etc.) that belong to this parent
353-
// Child records have a different owner (island object) than the parent
354357
let j = i + 1;
355358
while (j < data.length && data[j].level > record.level) {
356-
// Include all descendants (any level deeper)
357359
if (!data[j].hidden) {
358-
allDescendants.push(data[j]);
360+
// Attach the original index into data
361+
allDescendants.push({ ...(data[j] as any), __index: j });
359362
}
360363
j++;
361364
}
362365

363-
// If there are descendant records, draw child table(s)
364366
if (allDescendants.length > 0) {
365-
// Group descendants by owner to separate different child grids
366-
// Owner is the actual island object, not a string
367-
// Only collect DIRECT children (one level deeper) for initial grouping
368-
const directDescendantsByOwner = new Map<any, IExportRecord[]>();
367+
const directDescendantsByOwner = new Map<any, Array<IExportRecord & { __index: number }>>();
369368

370369
for (const desc of allDescendants) {
371-
// Only include records that are exactly one level deeper (direct children)
372370
if (desc.level === record.level + 1) {
373371
const owner = desc.owner;
374372
if (!directDescendantsByOwner.has(owner)) {
@@ -378,13 +376,12 @@ export class IgxPdfExporterService extends IgxBaseExporter {
378376
}
379377
}
380378

381-
// Draw each child grid separately with its direct children only
382379
for (const [owner, directChildren] of directDescendantsByOwner) {
383380
yPosition = this.drawHierarchicalChildren(
384381
pdf,
385382
data,
386-
allDescendants, // Pass all descendants so grandchildren can be found
387-
directChildren,
383+
allDescendants, // descendants WITH __index
384+
directChildren, // direct children WITH __index
388385
owner,
389386
yPosition,
390387
margin,
@@ -397,7 +394,6 @@ export class IgxPdfExporterService extends IgxBaseExporter {
397394
);
398395
}
399396

400-
// Skip the descendant records we just processed
401397
i = j - 1;
402398
}
403399
}
@@ -644,7 +640,7 @@ export class IgxPdfExporterService extends IgxBaseExporter {
644640
private drawHierarchicalChildren(
645641
pdf: jsPDF,
646642
allData: IExportRecord[],
647-
allDescendants: IExportRecord[], // All descendants to search for grandchildren
643+
allDescendants: any[], // All descendants to search for grandchildren
648644
childRecords: IExportRecord[], // Direct children to render at this level
649645
childOwner: any, // Owner is the island object, not a string
650646
yPosition: number,
@@ -768,49 +764,67 @@ export class IgxPdfExporterService extends IgxBaseExporter {
768764
this.drawDataRow(pdf, childRecord, childColumns, [], childTableX, yPosition, childColumnWidth, rowHeight, 0, options);
769765
yPosition += rowHeight;
770766

771-
// Check if this child has grandchildren (deeper levels in different child grids)
772-
// Look for grandchildren in allDescendants that are direct descendants of this childRecord
773-
const grandchildrenForThisRecord = allDescendants.filter(r =>
774-
r.level === childRecord.level + 1 && r.type !== 'HeaderRecord'
775-
);
767+
// allDescendants here is an array of records with an extra __index property
768+
const childIndex = (childRecord as any).__index as number | undefined;
769+
770+
if (childIndex !== undefined) {
771+
// Find this child's position in allDescendants (by original index)
772+
const childPosInDesc = allDescendants.findIndex(d => d.__index === childIndex);
776773

777-
if (grandchildrenForThisRecord.length > 0) {
778-
// Group grandchildren by their owner (different child islands under this record)
779-
const grandchildrenByOwner = new Map<any, IExportRecord[]>();
780-
781-
for (const gc of grandchildrenForThisRecord) {
782-
// Use the actual owner object
783-
const gcOwner = gc.owner;
784-
// Only include grandchildren that have a different owner (separate child grid)
785-
if (gcOwner !== childOwner) {
786-
if (!grandchildrenByOwner.has(gcOwner)) {
787-
grandchildrenByOwner.set(gcOwner, []);
774+
if (childPosInDesc !== -1) {
775+
const subtree: Array<IExportRecord & { __index: number }> = [];
776+
const childLevel = childRecord.level;
777+
778+
// Collect all deeper records until we hit same-or-higher level
779+
for (let k = childPosInDesc + 1; k < allDescendants.length; k++) {
780+
const rec = allDescendants[k];
781+
if (rec.level <= childLevel) {
782+
break;
783+
}
784+
if (rec.type !== 'HeaderRecord') {
785+
subtree.push(rec);
788786
}
789-
grandchildrenByOwner.get(gcOwner)!.push(gc);
790787
}
791-
}
792788

793-
// Recursively draw each grandchild owner's records with increased indentation
794-
for (const [gcOwner, directGrandchildren] of grandchildrenByOwner) {
795-
yPosition = this.drawHierarchicalChildren(
796-
pdf,
797-
allData,
798-
allDescendants, // Pass all descendants so great-grandchildren can be found
799-
directGrandchildren, // Direct grandchildren to render
800-
gcOwner,
801-
yPosition,
802-
margin,
803-
indentPerLevel + 20, // Increase indentation for next level
804-
usableWidth,
805-
pageHeight,
806-
headerHeight,
807-
rowHeight,
808-
options
809-
);
789+
if (subtree.length > 0) {
790+
// Direct grandchildren for this child: exactly one level deeper
791+
const grandchildrenForThisRecord = subtree.filter(r =>
792+
r.level === childRecord.level + 1 && r.owner !== childOwner
793+
);
794+
795+
if (grandchildrenForThisRecord.length > 0) {
796+
const grandchildrenByOwner = new Map<any, Array<IExportRecord & { __index: number }>>();
797+
798+
for (const gc of grandchildrenForThisRecord) {
799+
const gcOwner = gc.owner;
800+
if (!grandchildrenByOwner.has(gcOwner)) {
801+
grandchildrenByOwner.set(gcOwner, []);
802+
}
803+
grandchildrenByOwner.get(gcOwner)!.push(gc);
804+
}
805+
806+
for (const [gcOwner, directGrandchildren] of grandchildrenByOwner) {
807+
yPosition = this.drawHierarchicalChildren(
808+
pdf,
809+
allData,
810+
subtree, // only this child's subtree for deeper levels
811+
directGrandchildren,
812+
gcOwner,
813+
yPosition,
814+
margin,
815+
indentPerLevel + 20,
816+
usableWidth,
817+
pageHeight,
818+
headerHeight,
819+
rowHeight,
820+
options
821+
);
822+
}
823+
}
824+
}
810825
}
811826
}
812827
}
813-
814828
// Add spacing after child table
815829
yPosition += 5;
816830

0 commit comments

Comments
 (0)