Skip to content

Commit a88e896

Browse files
Copilotkdinev
andcommitted
Add child table headers for hierarchical grid PDF export
For IgxHierarchicalGridComponent exports, child records now display in separate child tables with their own headers beneath each parent row that has children: - Child tables are indented to show hierarchy - Each child table includes proper column headers - Child tables automatically paginate if needed - Extracted reusable methods for drawing headers and rows Co-authored-by: kdinev <[email protected]>
1 parent f8db2f8 commit a88e896

File tree

1 file changed

+161
-58
lines changed

1 file changed

+161
-58
lines changed

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

Lines changed: 161 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -85,45 +85,28 @@ export class IgxPdfExporterService extends IgxBaseExporter {
8585
const rowHeight = 20;
8686
const headerHeight = 25;
8787
const indentSize = 15; // Indentation per level for hierarchical data
88+
const childTableIndent = 30; // Indent for child tables
8889

8990
let yPosition = margin;
9091

9192
// Set font
9293
pdf.setFontSize(options.fontSize);
9394

94-
// Draw table headers
95-
pdf.setFont('helvetica', 'bold');
96-
pdf.setFillColor(240, 240, 240);
97-
98-
if (options.showTableBorders) {
99-
pdf.rect(margin, yPosition, usableWidth, headerHeight, 'F');
100-
}
101-
102-
columns.forEach((col, index) => {
103-
const xPosition = margin + (index * columnWidth);
104-
const headerText = col.header || col.field;
105-
106-
if (options.showTableBorders) {
107-
pdf.rect(xPosition, yPosition, columnWidth, headerHeight);
108-
}
109-
110-
// Center text in cell
111-
const textWidth = pdf.getTextWidth(headerText);
112-
const textX = xPosition + (columnWidth - textWidth) / 2;
113-
const textY = yPosition + headerHeight / 2 + options.fontSize / 3;
114-
115-
pdf.text(headerText, textX, textY);
116-
});
117-
95+
// Draw main table headers
96+
this.drawTableHeaders(pdf, columns, margin, yPosition, columnWidth, headerHeight, usableWidth, options);
11897
yPosition += headerHeight;
11998

12099
// Draw data rows
121100
pdf.setFont('helvetica', 'normal');
122101

123-
data.forEach((record) => {
102+
let i = 0;
103+
while (i < data.length) {
104+
const record = data[i];
105+
124106
// Skip hidden records (collapsed hierarchy)
125107
if (record.hidden) {
126-
return;
108+
i++;
109+
continue;
127110
}
128111

129112
// Check if we need a new page
@@ -133,54 +116,174 @@ export class IgxPdfExporterService extends IgxBaseExporter {
133116
}
134117

135118
// Calculate indentation for hierarchical records
136-
const isHierarchical = record.type === 'TreeGridRecord' || record.type === 'HierarchicalGridRecord';
137-
const indentLevel = isHierarchical ? (record.level || 0) : 0;
119+
const isTreeGrid = record.type === 'TreeGridRecord';
120+
const isHierarchicalGrid = record.type === 'HierarchicalGridRecord';
121+
const indentLevel = (isTreeGrid || isHierarchicalGrid) ? (record.level || 0) : 0;
138122
const indent = indentLevel * indentSize;
139123

140-
columns.forEach((col, index) => {
141-
const xPosition = margin + (index * columnWidth);
142-
let cellValue = record.data[col.field];
124+
// Draw parent row
125+
this.drawDataRow(pdf, record, columns, margin, yPosition, columnWidth, rowHeight, indent, options);
126+
yPosition += rowHeight;
127+
128+
// For hierarchical grids, check if this record has child records
129+
if (isHierarchicalGrid) {
130+
const childRecords = [];
131+
let childOwner = null;
143132

144-
// Convert value to string
145-
if (cellValue === null || cellValue === undefined) {
146-
cellValue = '';
147-
} else if (cellValue instanceof Date) {
148-
cellValue = cellValue.toLocaleDateString();
149-
} else {
150-
cellValue = String(cellValue);
133+
// Collect all child records that belong to this parent
134+
let j = i + 1;
135+
while (j < data.length && data[j].owner !== DEFAULT_OWNER && data[j].level > record.level) {
136+
if (!data[j].hidden) {
137+
childRecords.push(data[j]);
138+
if (!childOwner) {
139+
childOwner = data[j].owner;
140+
}
141+
}
142+
j++;
151143
}
152144

153-
if (options.showTableBorders) {
154-
pdf.rect(xPosition, yPosition, columnWidth, rowHeight);
155-
}
145+
// If there are child records, draw a child table
146+
if (childRecords.length > 0 && childOwner) {
147+
const childColumns = this._ownersMap.get(childOwner)?.columns.filter(
148+
col => col.field && !col.skip && col.headerType === ExportHeaderType.ColumnHeader
149+
) || [];
156150

157-
// Apply indentation to the first column for hierarchical data
158-
const textIndent = (index === 0 && isHierarchical) ? indent : 0;
159-
160-
// Truncate text if it's too long, accounting for indentation
161-
const maxTextWidth = columnWidth - 10 - textIndent;
162-
let displayText = cellValue;
163-
164-
if (pdf.getTextWidth(displayText) > maxTextWidth) {
165-
while (pdf.getTextWidth(displayText + '...') > maxTextWidth && displayText.length > 0) {
166-
displayText = displayText.substring(0, displayText.length - 1);
151+
if (childColumns.length > 0) {
152+
// Add some spacing before child table
153+
yPosition += 5;
154+
155+
// Check if child table fits on current page
156+
const childTableHeight = headerHeight + (childRecords.length * rowHeight) + 10;
157+
if (yPosition + childTableHeight > pageHeight - margin) {
158+
pdf.addPage();
159+
yPosition = margin;
160+
}
161+
162+
// Draw child table with indentation
163+
const childTableWidth = usableWidth - childTableIndent;
164+
const childColumnWidth = childTableWidth / childColumns.length;
165+
const childTableX = margin + childTableIndent;
166+
167+
// Draw child table headers
168+
this.drawTableHeaders(pdf, childColumns, childTableX, yPosition, childColumnWidth, headerHeight, childTableWidth, options);
169+
yPosition += headerHeight;
170+
171+
// Draw child table rows
172+
childRecords.forEach((childRecord) => {
173+
// Check if we need a new page
174+
if (yPosition + rowHeight > pageHeight - margin) {
175+
pdf.addPage();
176+
yPosition = margin;
177+
// Redraw headers on new page
178+
this.drawTableHeaders(pdf, childColumns, childTableX, yPosition, childColumnWidth, headerHeight, childTableWidth, options);
179+
yPosition += headerHeight;
180+
}
181+
182+
this.drawDataRow(pdf, childRecord, childColumns, childTableX, yPosition, childColumnWidth, rowHeight, 0, options);
183+
yPosition += rowHeight;
184+
});
185+
186+
// Add spacing after child table
187+
yPosition += 5;
167188
}
168-
displayText += '...';
169-
}
170189

171-
const textY = yPosition + rowHeight / 2 + options.fontSize / 3;
172-
pdf.text(displayText, xPosition + 5 + textIndent, textY);
173-
});
190+
// Skip the child records we just processed
191+
i = j - 1;
192+
}
193+
}
174194

175-
yPosition += rowHeight;
176-
});
195+
i++;
196+
}
177197

178198
// Save the PDF
179199
this.saveFile(pdf, options.fileName);
180200
this.exportEnded.emit({ pdf });
181201
done();
182202
}
183203

204+
private drawTableHeaders(
205+
pdf: jsPDF,
206+
columns: any[],
207+
xStart: number,
208+
yPosition: number,
209+
columnWidth: number,
210+
headerHeight: number,
211+
tableWidth: number,
212+
options: IgxPdfExporterOptions
213+
): void {
214+
pdf.setFont('helvetica', 'bold');
215+
pdf.setFillColor(240, 240, 240);
216+
217+
if (options.showTableBorders) {
218+
pdf.rect(xStart, yPosition, tableWidth, headerHeight, 'F');
219+
}
220+
221+
columns.forEach((col, index) => {
222+
const xPosition = xStart + (index * columnWidth);
223+
const headerText = col.header || col.field;
224+
225+
if (options.showTableBorders) {
226+
pdf.rect(xPosition, yPosition, columnWidth, headerHeight);
227+
}
228+
229+
// Center text in cell
230+
const textWidth = pdf.getTextWidth(headerText);
231+
const textX = xPosition + (columnWidth - textWidth) / 2;
232+
const textY = yPosition + headerHeight / 2 + options.fontSize / 3;
233+
234+
pdf.text(headerText, textX, textY);
235+
});
236+
237+
pdf.setFont('helvetica', 'normal');
238+
}
239+
240+
private drawDataRow(
241+
pdf: jsPDF,
242+
record: IExportRecord,
243+
columns: any[],
244+
xStart: number,
245+
yPosition: number,
246+
columnWidth: number,
247+
rowHeight: number,
248+
indent: number,
249+
options: IgxPdfExporterOptions
250+
): void {
251+
columns.forEach((col, index) => {
252+
const xPosition = xStart + (index * columnWidth);
253+
let cellValue = record.data[col.field];
254+
255+
// Convert value to string
256+
if (cellValue === null || cellValue === undefined) {
257+
cellValue = '';
258+
} else if (cellValue instanceof Date) {
259+
cellValue = cellValue.toLocaleDateString();
260+
} else {
261+
cellValue = String(cellValue);
262+
}
263+
264+
if (options.showTableBorders) {
265+
pdf.rect(xPosition, yPosition, columnWidth, rowHeight);
266+
}
267+
268+
// Apply indentation to the first column for hierarchical data
269+
const textIndent = (index === 0) ? indent : 0;
270+
271+
// Truncate text if it's too long, accounting for indentation
272+
const maxTextWidth = columnWidth - 10 - textIndent;
273+
let displayText = cellValue;
274+
275+
if (pdf.getTextWidth(displayText) > maxTextWidth) {
276+
while (pdf.getTextWidth(displayText + '...') > maxTextWidth && displayText.length > 0) {
277+
displayText = displayText.substring(0, displayText.length - 1);
278+
}
279+
displayText += '...';
280+
}
281+
282+
const textY = yPosition + rowHeight / 2 + options.fontSize / 3;
283+
pdf.text(displayText, xPosition + 5 + textIndent, textY);
284+
});
285+
}
286+
184287
private saveFile(pdf: jsPDF, fileName: string): void {
185288
const blob = pdf.output('blob');
186289
ExportUtilities.saveBlobToFile(blob, fileName);

0 commit comments

Comments
 (0)