Skip to content

Commit c280ff8

Browse files
committed
feat(pdf export): fixing child table rendering
1 parent 6658762 commit c280ff8

File tree

3 files changed

+121
-95
lines changed

3 files changed

+121
-95
lines changed

projects/igniteui-angular/src/lib/services/pdf/pdf-exporter.ts

Lines changed: 104 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -151,40 +151,57 @@ export class IgxPdfExporterService extends IgxBaseExporter {
151151

152152
// For hierarchical grids, check if this record has child records
153153
if (isHierarchicalGrid) {
154-
const childRecords = [];
155-
let childOwner = null;
154+
const allDescendants = [];
156155

157-
// Collect only direct child records (next level) that belong to this parent
156+
// Collect all descendant records (children, grandchildren, etc.) that belong to this parent
157+
// Child records have a different owner (island object) than the parent
158158
let j = i + 1;
159-
while (j < data.length && data[j].owner !== DEFAULT_OWNER && data[j].level > record.level) {
160-
// Only include direct children (one level deeper)
161-
if (data[j].level === record.level + 1 && !data[j].hidden) {
162-
childRecords.push(data[j]);
163-
if (!childOwner) {
164-
childOwner = data[j].owner;
165-
}
159+
while (j < data.length && data[j].level > record.level) {
160+
// Include all descendants (any level deeper)
161+
if (!data[j].hidden) {
162+
allDescendants.push(data[j]);
166163
}
167164
j++;
168165
}
169166

170-
// If there are child records, draw a child table
171-
if (childRecords.length > 0 && childOwner) {
172-
yPosition = this.drawHierarchicalChildren(
173-
pdf,
174-
data,
175-
childRecords,
176-
childOwner,
177-
yPosition,
178-
margin,
179-
childTableIndent,
180-
usableWidth,
181-
pageHeight,
182-
headerHeight,
183-
rowHeight,
184-
options
185-
);
167+
// If there are descendant records, draw child table(s)
168+
if (allDescendants.length > 0) {
169+
// Group descendants by owner to separate different child grids
170+
// Owner is the actual island object, not a string
171+
// Only collect DIRECT children (one level deeper) for initial grouping
172+
const directDescendantsByOwner = new Map<any, IExportRecord[]>();
173+
174+
for (const desc of allDescendants) {
175+
// Only include records that are exactly one level deeper (direct children)
176+
if (desc.level === record.level + 1) {
177+
const owner = desc.owner;
178+
if (!directDescendantsByOwner.has(owner)) {
179+
directDescendantsByOwner.set(owner, []);
180+
}
181+
directDescendantsByOwner.get(owner)!.push(desc);
182+
}
183+
}
184+
185+
// Draw each child grid separately with its direct children only
186+
for (const [owner, directChildren] of directDescendantsByOwner) {
187+
yPosition = this.drawHierarchicalChildren(
188+
pdf,
189+
data,
190+
allDescendants, // Pass all descendants so grandchildren can be found
191+
directChildren,
192+
owner,
193+
yPosition,
194+
margin,
195+
childTableIndent,
196+
usableWidth,
197+
pageHeight,
198+
headerHeight,
199+
rowHeight,
200+
options
201+
);
202+
}
186203

187-
// Skip the child records we just processed
204+
// Skip the descendant records we just processed
188205
i = j - 1;
189206
}
190207
}
@@ -281,8 +298,9 @@ export class IgxPdfExporterService extends IgxBaseExporter {
281298
private drawHierarchicalChildren(
282299
pdf: jsPDF,
283300
allData: IExportRecord[],
284-
childRecords: IExportRecord[],
285-
childOwner: string,
301+
allDescendants: IExportRecord[], // All descendants to search for grandchildren
302+
childRecords: IExportRecord[], // Direct children to render at this level
303+
childOwner: any, // Owner is the island object, not a string
286304
yPosition: number,
287305
margin: number,
288306
indentPerLevel: number,
@@ -292,6 +310,7 @@ export class IgxPdfExporterService extends IgxBaseExporter {
292310
rowHeight: number,
293311
options: IgxPdfExporterOptions
294312
): number {
313+
// Get columns for this child owner (owner is the island object)
295314
const childColumns = this._ownersMap.get(childOwner)?.columns.filter(
296315
col => col.field && !col.skip && col.headerType === ExportHeaderType.ColumnHeader
297316
) || [];
@@ -300,74 +319,81 @@ export class IgxPdfExporterService extends IgxBaseExporter {
300319
return yPosition;
301320
}
302321

303-
// Add some spacing before child table
304-
yPosition += 5;
322+
// Filter out header records - they should not be rendered as data rows
323+
const dataRecords = childRecords.filter(r => r.type !== 'HeaderRecord');
305324

306-
// Check if child table fits on current page
307-
const childTableHeight = headerHeight + (childRecords.length * rowHeight) + 10;
308-
if (yPosition + childTableHeight > pageHeight - margin) {
309-
pdf.addPage();
310-
yPosition = margin;
325+
if (dataRecords.length === 0) {
326+
return yPosition;
311327
}
312328

329+
// Add some spacing before child table
330+
yPosition += 5;
331+
313332
// Draw child table with indentation
314333
const childTableWidth = usableWidth - indentPerLevel;
315334
const childColumnWidth = childTableWidth / childColumns.length;
316335
const childTableX = margin + indentPerLevel;
317336

337+
// Check if we need a new page for headers
338+
if (yPosition + headerHeight > pageHeight - margin) {
339+
pdf.addPage();
340+
yPosition = margin;
341+
}
342+
318343
// Draw child table headers
319344
this.drawTableHeaders(pdf, childColumns, childTableX, yPosition, childColumnWidth, headerHeight, childTableWidth, options);
320345
yPosition += headerHeight;
321346

322-
// Process each child record
323-
let childIndex = 0;
324-
while (childIndex < childRecords.length) {
325-
const childRecord = childRecords[childIndex];
326-
327-
// Check if this child record has its own children (next level in hierarchy)
328-
const childRecordIndex = allData.indexOf(childRecord);
329-
const grandchildrenByOwner = new Map<string, IExportRecord[]>();
330-
331-
if (childRecordIndex >= 0 && childRecordIndex + 1 < allData.length) {
332-
// Look for grandchildren and group them by owner (each owner represents a different child island)
333-
let k = childRecordIndex + 1;
334-
335-
// Collect all grandchildren that belong to this child, grouped by owner
336-
while (k < allData.length && allData[k].level > childRecord.level) {
337-
// Only include direct children (next level)
338-
if (allData[k].level === childRecord.level + 1 && !allData[k].hidden) {
339-
const owner = allData[k].owner.toString();
340-
if (!grandchildrenByOwner.has(owner)) {
341-
grandchildrenByOwner.set(owner, []);
342-
}
343-
grandchildrenByOwner.get(owner)!.push(allData[k]);
344-
}
345-
k++;
346-
}
347+
// Find the minimum level in these records (direct children of parent)
348+
const minLevel = Math.min(...dataRecords.map(r => r.level));
349+
350+
// Process each record at the minimum level (direct children)
351+
const directChildren = dataRecords.filter(r => r.level === minLevel);
352+
353+
for (const childRecord of directChildren) {
354+
// Check if we need a new page
355+
if (yPosition + rowHeight > pageHeight - margin) {
356+
pdf.addPage();
357+
yPosition = margin;
358+
// Redraw headers on new page
359+
this.drawTableHeaders(pdf, childColumns, childTableX, yPosition, childColumnWidth, headerHeight, childTableWidth, options);
360+
yPosition += headerHeight;
347361
}
348362

349-
// If this child has grandchildren, render them as nested child tables (one per owner/island)
350-
if (grandchildrenByOwner.size > 0) {
351-
// Check if we need a new page for parent row
352-
if (yPosition + rowHeight > pageHeight - margin) {
353-
pdf.addPage();
354-
yPosition = margin;
355-
// Redraw headers on new page
356-
this.drawTableHeaders(pdf, childColumns, childTableX, yPosition, childColumnWidth, headerHeight, childTableWidth, options);
357-
yPosition += headerHeight;
358-
}
363+
// Draw the child record
364+
this.drawDataRow(pdf, childRecord, childColumns, childTableX, yPosition, childColumnWidth, rowHeight, 0, options);
365+
yPosition += rowHeight;
359366

360-
// Draw the parent row (child record)
361-
this.drawDataRow(pdf, childRecord, childColumns, childTableX, yPosition, childColumnWidth, rowHeight, 0, options);
362-
yPosition += rowHeight;
367+
// Check if this child has grandchildren (deeper levels in different child grids)
368+
// Look for grandchildren in allDescendants that are direct descendants of this childRecord
369+
const grandchildrenForThisRecord = allDescendants.filter(r =>
370+
r.level === childRecord.level + 1 && r.type !== 'HeaderRecord'
371+
);
372+
373+
if (grandchildrenForThisRecord.length > 0) {
374+
// Group grandchildren by their owner (different child islands under this record)
375+
const grandchildrenByOwner = new Map<any, IExportRecord[]>();
376+
377+
for (const gc of grandchildrenForThisRecord) {
378+
// Use the actual owner object
379+
const gcOwner = gc.owner;
380+
// Only include grandchildren that have a different owner (separate child grid)
381+
if (gcOwner !== childOwner) {
382+
if (!grandchildrenByOwner.has(gcOwner)) {
383+
grandchildrenByOwner.set(gcOwner, []);
384+
}
385+
grandchildrenByOwner.get(gcOwner)!.push(gc);
386+
}
387+
}
363388

364-
// Recursively draw each island's grandchildren as a separate nested child table
365-
for (const [grandchildOwner, grandchildRecords] of grandchildrenByOwner) {
389+
// Recursively draw each grandchild owner's records with increased indentation
390+
for (const [gcOwner, directGrandchildren] of grandchildrenByOwner) {
366391
yPosition = this.drawHierarchicalChildren(
367392
pdf,
368393
allData,
369-
grandchildRecords,
370-
grandchildOwner,
394+
allDescendants, // Pass all descendants so great-grandchildren can be found
395+
directGrandchildren, // Direct grandchildren to render
396+
gcOwner,
371397
yPosition,
372398
margin,
373399
indentPerLevel + 30, // Increase indentation for next level
@@ -378,22 +404,7 @@ export class IgxPdfExporterService extends IgxBaseExporter {
378404
options
379405
);
380406
}
381-
} else {
382-
// No grandchildren, just draw this child as a regular row
383-
// Check if we need a new page
384-
if (yPosition + rowHeight > pageHeight - margin) {
385-
pdf.addPage();
386-
yPosition = margin;
387-
// Redraw headers on new page
388-
this.drawTableHeaders(pdf, childColumns, childTableX, yPosition, childColumnWidth, headerHeight, childTableWidth, options);
389-
yPosition += headerHeight;
390-
}
391-
392-
this.drawDataRow(pdf, childRecord, childColumns, childTableX, yPosition, childColumnWidth, rowHeight, 0, options);
393-
yPosition += rowHeight;
394407
}
395-
396-
childIndex++;
397408
}
398409

399410
// Add spacing after child table

src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
<div class="hgrid-sample">
22
<h4 class="sample-title">Sample One</h4>
33
<igx-hierarchical-grid #hierarchicalGrid [data]="localData" [autoGenerate]="true" [allowAdvancedFiltering]="true">
4-
<igx-grid-toolbar></igx-grid-toolbar>
4+
<igx-grid-toolbar>
5+
<igx-grid-toolbar-actions>
6+
<igx-grid-toolbar-pinning></igx-grid-toolbar-pinning>
7+
<igx-grid-toolbar-hiding></igx-grid-toolbar-hiding>
8+
<igx-grid-toolbar-advanced-filtering></igx-grid-toolbar-advanced-filtering>
9+
<igx-grid-toolbar-exporter></igx-grid-toolbar-exporter>
10+
</igx-grid-toolbar-actions>
11+
</igx-grid-toolbar>
512
<igx-row-island [key]="'Albums'" [autoGenerate]="true">
613
<igx-row-island [key]="'Songs'" [autoGenerate]="true">
714
<!-- <igx-column [field]="'Number'" [dataType]="'number'"></igx-column>

src/app/hierarchical-grid/hierarchical-grid.sample.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ <h4 class="sample-title">Sample One</h4>
88
<button igxButton="contained" (click)="getState()">Get state</button>
99
<igx-hierarchical-grid [batchEditing]="true" #grid1 [data]="localData" [showExpandAll]="true" [pinning]="{rows: 0}" class="hgrid"
1010
[rowSelection]="selectionMode" [autoGenerate]="false" [allowFiltering]="true"
11-
[rowDraggable]="true" [height]="'600px'" filterMode="excelStyleFilter" [width]="'100%'" [expandChildren]="rootExpanded"
11+
[rowDraggable]="true" [height]="'800px'" filterMode="excelStyleFilter" [width]="'100%'" [expandChildren]="rootExpanded"
1212
#hGrid [rowClasses]="rowClasses">
1313
<igx-grid-excel-style-filtering [minHeight]="'200px'" [maxHeight]="'500px'">
1414
<igx-excel-style-column-operations>
@@ -138,6 +138,14 @@ <h4 class="sample-title">Sample two</h4>
138138
</div>
139139
<igx-hierarchical-grid [showExpandAll]='true' [primaryKey]="'ID'" [data]="localData" [autoGenerate]="true" [height]="'600px'"
140140
[width]="'100%'" [rowEditable]="true" #hGrid2>
141+
<igx-grid-toolbar>
142+
<app-grid-search-box [grid]="hGrid2"></app-grid-search-box>
143+
<igx-grid-toolbar-actions>
144+
<igx-grid-toolbar-pinning></igx-grid-toolbar-pinning>
145+
<igx-grid-toolbar-hiding></igx-grid-toolbar-hiding>
146+
<igx-grid-toolbar-exporter></igx-grid-toolbar-exporter>
147+
</igx-grid-toolbar-actions>
148+
</igx-grid-toolbar>
141149
<igx-row-island [key]="'childData'" [autoGenerate]="true" [rowSelection]='selectionMode' [batchEditing]="true" [rowEditable]="true"
142150
[allowFiltering]="true">
143151
<igx-row-island [key]="'childData'" [autoGenerate]="true" [rowSelection]='selectionMode' [batchEditing]="true" [rowEditable]="true"

0 commit comments

Comments
 (0)