Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { GridIDNameJobTitleComponent } from '../../../../../test-utils/grid-samp
import { first } from 'rxjs/operators';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { NestedColumnGroupsGridComponent, ColumnGroupTestComponent, BlueWhaleGridComponent } from '../../../../../test-utils/grid-mch-sample.spec';
import { IgxHierarchicalGridTestBaseComponent } from '../../../../../test-utils/hierarchical-grid-components.spec';
import { IgxHierarchicalGridExportComponent, IgxHierarchicalGridTestBaseComponent } from '../../../../../test-utils/hierarchical-grid-components.spec';
import { IgxTreeGridSortingComponent, IgxTreeGridPrimaryForeignKeyComponent } from '../../../../../test-utils/tree-grid-components.spec';
import { CustomSummariesComponent } from 'igniteui-angular/grids/grid/src/grid-summary.spec';
import { IgxHierarchicalGridComponent } from 'igniteui-angular/grids/hierarchical-grid';
import { IgxPivotGridMultipleRowComponent, IgxPivotGridTestComplexHierarchyComponent } from '../../../../../test-utils/pivot-grid-samples.spec';
import { IgxPivotGridComponent } from 'igniteui-angular/grids/pivot-grid';
import { PivotRowLayoutType } from 'igniteui-angular/grids/core';
import { wait } from 'igniteui-angular/test-utils/ui-interactions.spec';
import { UIInteractions, wait } from 'igniteui-angular/test-utils/ui-interactions.spec';

describe('PDF Grid Exporter', () => {
let exporter: IgxPdfExporterService;
Expand Down Expand Up @@ -328,6 +328,50 @@ describe('PDF Grid Exporter', () => {
exporter.export(grid, options);
});

it('should export the correct number of child data rows from a hierarchical grid', (done) => {
const fix = TestBed.createComponent(IgxHierarchicalGridExportComponent);
fix.detectChanges();

const hGrid = fix.componentInstance.hGrid;
hGrid.data = hGrid.data.slice(0, 1); // Limit data for test performance

// Expand first few rows to realize all inner levels, same as in Excel tests
const firstRow = hGrid.gridAPI.get_row_by_index(0) as any;

UIInteractions.simulateClickAndSelectEvent(firstRow.expander);
fix.detectChanges();

let childGrids = hGrid.gridAPI.getChildGrids(false) as any[];
const firstChildGrid = childGrids[0];
const firstChildRow = firstChildGrid.gridAPI.get_row_by_index(0) as any;
const secondChildRow = firstChildGrid.gridAPI.get_row_by_index(1) as any;
UIInteractions.simulateClickAndSelectEvent(secondChildRow.expander);
fix.detectChanges();

UIInteractions.simulateClickAndSelectEvent(firstChildRow.expander);
fix.detectChanges();

childGrids = hGrid.gridAPI.getChildGrids(false) as any[];
const thirdChildGrid = childGrids[1];
const thirdChildRow = thirdChildGrid.gridAPI.get_row_by_index(0) as any;
UIInteractions.simulateClickAndSelectEvent(thirdChildRow.expander);
fix.detectChanges();

// Calculate expected number of data rows to be exported
const allGrids = [hGrid, ...(hGrid.gridAPI.getChildGrids(true) as any[])];
const expectedRows = allGrids.reduce((acc, g) => acc + g.data.length, 0);

// Spy PDF row drawing to count exported rows
const drawDataRowSpy = spyOn<any>(exporter as any, 'drawDataRow').and.callThrough();

exporter.exportEnded.pipe(first()).subscribe(() => {
expect(drawDataRowSpy.calls.count()).toBe(expectedRows);
done();
});

exporter.export(hGrid, options);
});

it('should export tree grid with hierarchical data', (done) => {
TestBed.configureTestingModule({
imports: [
Expand Down
113 changes: 61 additions & 52 deletions projects/igniteui-angular/grids/core/src/services/pdf/pdf-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,28 +352,21 @@ export class IgxPdfExporterService extends IgxBaseExporter {

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

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

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

for (const desc of allDescendants) {
// Only include records that are exactly one level deeper (direct children)
if (desc.level === record.level + 1) {
const owner = desc.owner;
if (!directDescendantsByOwner.has(owner)) {
Expand All @@ -383,13 +376,12 @@ export class IgxPdfExporterService extends IgxBaseExporter {
}
}

// Draw each child grid separately with its direct children only
for (const [owner, directChildren] of directDescendantsByOwner) {
yPosition = this.drawHierarchicalChildren(
pdf,
data,
allDescendants, // Pass all descendants so grandchildren can be found
directChildren,
allDescendants, // descendants WITH __index
directChildren, // direct children WITH __index
owner,
yPosition,
margin,
Expand All @@ -402,7 +394,6 @@ export class IgxPdfExporterService extends IgxBaseExporter {
);
}

// Skip the descendant records we just processed
i = j - 1;
}
}
Expand Down Expand Up @@ -649,7 +640,7 @@ export class IgxPdfExporterService extends IgxBaseExporter {
private drawHierarchicalChildren(
pdf: jsPDF,
allData: IExportRecord[],
allDescendants: IExportRecord[], // All descendants to search for grandchildren
allDescendants: any[], // All descendants to search for grandchildren
childRecords: IExportRecord[], // Direct children to render at this level
childOwner: any, // Owner is the island object, not a string
yPosition: number,
Expand Down Expand Up @@ -773,49 +764,67 @@ export class IgxPdfExporterService extends IgxBaseExporter {
this.drawDataRow(pdf, childRecord, childColumns, [], childTableX, yPosition, childColumnWidth, rowHeight, 0, options);
yPosition += rowHeight;

// Check if this child has grandchildren (deeper levels in different child grids)
// Look for grandchildren in allDescendants that are direct descendants of this childRecord
const grandchildrenForThisRecord = allDescendants.filter(r =>
r.level === childRecord.level + 1 && r.type !== 'HeaderRecord'
);
// allDescendants here is an array of records with an extra __index property
const childIndex = (childRecord as any).__index as number | undefined;

if (childIndex !== undefined) {
// Find this child's position in allDescendants (by original index)
const childPosInDesc = allDescendants.findIndex(d => d.__index === childIndex);

if (grandchildrenForThisRecord.length > 0) {
// Group grandchildren by their owner (different child islands under this record)
const grandchildrenByOwner = new Map<any, IExportRecord[]>();

for (const gc of grandchildrenForThisRecord) {
// Use the actual owner object
const gcOwner = gc.owner;
// Only include grandchildren that have a different owner (separate child grid)
if (gcOwner !== childOwner) {
if (!grandchildrenByOwner.has(gcOwner)) {
grandchildrenByOwner.set(gcOwner, []);
if (childPosInDesc !== -1) {
const subtree: Array<IExportRecord & { __index: number }> = [];
const childLevel = childRecord.level;

// Collect all deeper records until we hit same-or-higher level
for (let k = childPosInDesc + 1; k < allDescendants.length; k++) {
const rec = allDescendants[k];
if (rec.level <= childLevel) {
break;
}
if (rec.type !== 'HeaderRecord') {
subtree.push(rec);
}
grandchildrenByOwner.get(gcOwner)!.push(gc);
}
}

// Recursively draw each grandchild owner's records with increased indentation
for (const [gcOwner, directGrandchildren] of grandchildrenByOwner) {
yPosition = this.drawHierarchicalChildren(
pdf,
allData,
allDescendants, // Pass all descendants so great-grandchildren can be found
directGrandchildren, // Direct grandchildren to render
gcOwner,
yPosition,
margin,
indentPerLevel + 20, // Increase indentation for next level
usableWidth,
pageHeight,
headerHeight,
rowHeight,
options
);
if (subtree.length > 0) {
// Direct grandchildren for this child: exactly one level deeper
const grandchildrenForThisRecord = subtree.filter(r =>
r.level === childRecord.level + 1 && r.owner !== childOwner
);

if (grandchildrenForThisRecord.length > 0) {
const grandchildrenByOwner = new Map<any, Array<IExportRecord & { __index: number }>>();

for (const gc of grandchildrenForThisRecord) {
const gcOwner = gc.owner;
if (!grandchildrenByOwner.has(gcOwner)) {
grandchildrenByOwner.set(gcOwner, []);
}
grandchildrenByOwner.get(gcOwner)!.push(gc);
}

for (const [gcOwner, directGrandchildren] of grandchildrenByOwner) {
yPosition = this.drawHierarchicalChildren(
pdf,
allData,
subtree, // only this child's subtree for deeper levels
directGrandchildren,
gcOwner,
yPosition,
margin,
indentPerLevel + 20,
usableWidth,
pageHeight,
headerHeight,
rowHeight,
options
);
}
}
}
}
}
}

// Add spacing after child table
yPosition += 5;

Expand Down
Loading