Skip to content

Conversation

@kdinev
Copy link
Member

@kdinev kdinev commented Dec 1, 2025

Closes #

Additional information (check all that apply):

  • Bug fix
  • New functionality
  • Documentation
  • Demos
  • CI/CD

Checklist:

  • All relevant tags have been applied to this PR
  • This PR includes unit tests covering all the new code (test guidelines)
  • This PR includes API docs for newly added methods/properties (api docs guidelines)
  • This PR includes feature/README.MD updates for the feature docs
  • This PR includes general feature table updates in the root README.MD
  • This PR includes CHANGELOG.MD updates for newly added functionality
  • This PR contains breaking changes
  • This PR includes ng update migrations for the breaking changes (migrations guidelines)
  • This PR includes behavioral changes and the feature specification has been updated with them

Copilot AI and others added 30 commits October 18, 2025 09:48
Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
- Added indentation support for TreeGrid and HierarchicalGrid records in PDF export
- Records with level property now indent by 15pt per level in first column
- Skip hidden records (collapsed hierarchy nodes) from export
- Added jspdf to ng-add schematic dependencies map for automatic installation

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Grids are typically wide, so landscape orientation is more suitable as the default for grid exports.

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Created a comprehensive demo showing IgxPdfExporterService API usage:
- Interactive configuration panel for all export options
- Examples with regular grid, tree grid, and hierarchical grid
- Direct service API usage (not through toolbar UI)
- Live configuration of orientation, page size, borders, and font size
- Located at /gridPdfExport route

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
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 <1472513+kdinev@users.noreply.github.com>
Added comment to clarify that TreeGrid PDF export indentation works for both:
- Hierarchical data binding
- Flat self-referencing data with foreignKey

The implementation already handles both cases correctly since the base exporter
normalizes both modes into TreeGridRecord with proper level property.

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Updated drawDataRow to properly handle SummaryRecord types:
- Detects when record is a SummaryRecord
- Extracts label and value/summaryResult from summary objects
- Formats as "label: value" similar to Excel export
- Falls back to just value if no label present
- Prevents "[object Object]" display in PDF exports

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Implemented multi-level header support for column groups:
- Detects when columns have multi-level headers (column groups)
- Draws headers level by level from parent to child groups
- Properly handles columnSpan for grouped headers
- Respects startIndex for correct positioning
- Redraws multi-level headers on page breaks
- Maintains compatibility with simple single-level headers

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Fixed bug where column headers at level 2 and beyond were not rendered:
- Updated filter logic to include headers at each specific level
- Now correctly includes both MultiColumnHeader and ColumnHeader types at any level
- Maintains proper spanning behavior for leaf columns from earlier levels
- Simplified logic for better clarity and correctness

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Added extensive test coverage for PDF export functionality:

PDF Exporter Service Tests (pdf-exporter.spec.ts):
- Portrait orientation export
- Various page sizes (a3, a5, legal)
- Different font sizes
- Large datasets requiring pagination
- Long text truncation
- Mixed data types handling
- Custom filename
- Empty rows in data
- exportStarted event emission
- exportEnded event with pdf object

PDF Grid Exporter Tests (pdf-exporter-grid.spec.ts):
- Landscape orientation
- Table borders disabled
- Custom font size
- Different page sizes
- ignoreColumnsOrder option
- ignoreFiltering option
- ignoreSorting option
- exportStarted event for grid export
- Multiple columns handling
- Custom filename from options

Grid Toolbar Exporter Tests (grid-toolbar.spec.ts):
- PDF option displayed by default
- PDF option hidden when exportPDF is false
- PDF option shown when exportPDF is true
- Custom PDF text display
- PDF export triggered on click
- exportStarted event for PDF export
- PDF export cancellation

Increased test coverage for newly added PDF export code in both
the exporter service and toolbar component.

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Removed tests that referenced the non-existent exportStarted event on
IgxPdfExporterService. The base exporter only has exportEnded event,
not exportStarted. This matches the pattern used by Excel and CSV exporters.

Fixed tests in:
- pdf-exporter.spec.ts: Removed exportStarted event test
- pdf-exporter-grid.spec.ts: Removed exportStarted event test for grid export

Note: The grid-toolbar-exporter component still has its own exportStarted
output event which is correct and those tests remain unchanged.

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
…ical and tree grids

Added 6 new tests to cover advanced grid export scenarios:

1. Grid with multi-column headers (OneGroupThreeColsGridComponent)
   - Tests export of grids with column groups

2. Grid with nested multi-column headers (NestedColumnGroupsGridComponent)
   - Tests export of grids with deeply nested column group hierarchies

3. Grid with summaries (IgxGridFilteringComponent)
   - Tests export of grids with summary rows
   - Verifies summary values are properly formatted

4. Hierarchical Grid (IgxHierarchicalGridTestBaseComponent)
   - Tests export of hierarchical grids with child tables
   - Verifies child table headers are included

5. Tree Grid with hierarchical data (IgxTreeGridSortingComponent)
   - Tests export of tree grids with nested data structure
   - Verifies proper indentation based on level

6. Tree Grid with flat self-referencing data (IgxTreeGridPrimaryForeignKeyComponent)
   - Tests export of tree grids using primaryKey/foreignKey
   - Verifies indentation works for flat data with parent-child relationships

All tests follow the existing pattern and validate that the PDF exporter
correctly handles these advanced grid configurations.

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Implemented text truncation with ellipsis for column headers when text is wider
than the available header space. This applies to both:

1. Multi-level column headers (column groups) in drawMultiLevelHeaders
2. Simple column headers in drawTableHeaders

The truncation logic:
- Calculates max text width as column width minus 10px padding (5px each side)
- If header text exceeds available space, truncates character by character
- Adds ellipsis (...) to indicate truncation
- Similar to existing cell value truncation logic

Also added test case to verify truncation works with nested column groups on
smaller page size (a5) where truncation is more likely to occur.

This prevents header text from overflowing cell boundaries and overlapping with
adjacent headers, maintaining proper PDF layout.

Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
@kdinev kdinev added refactoring excel-exporter squash-merge Merge PR with "Squash and Merge" option version: 21.0.x labels Dec 2, 2025
@kdinev kdinev requested a review from Copilot December 2, 2025 09:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR converts the fflate library from a static import to dynamic imports across the Excel exporter functionality to reduce initial bundle size. The change affects the main exporter service and all Excel file generation classes.

Key Changes:

  • Removed static import statements for fflate functions (strToU8, zip)
  • Replaced synchronous calls with dynamic import('fflate').then() patterns
  • Added a clarifying comment in the main exporter service

Reviewed changes

Copilot reviewed 2 out of 4 changed files in this pull request and generated 14 comments.

File Description
excel-exporter.ts Updated main exporter to dynamically import zip function from fflate
excel-files.ts Converted all strToU8 calls to use dynamic imports instead of static import

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

export class RootRelsFile implements IExcelFile {
public writeElement(folder: Object) {
folder['.rels'] = strToU8(ExcelStrings.getRels());
import('fflate').then(({ strToU8 }) => folder['.rels'] = strToU8(ExcelStrings.getRels()));
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeElement method appears to be synchronous based on its signature, but the dynamic import makes it asynchronous without returning a Promise. Callers expecting synchronous completion will not wait for the file data to be written. Consider changing the method signature to return Promise or ensure the caller awaits completion.

Copilot uses AI. Check for mistakes.
export class AppFile implements IExcelFile {
public writeElement(folder: Object, worksheetData: WorksheetData) {
folder['app.xml'] = strToU8(ExcelStrings.getApp(worksheetData.options.worksheetName));
import('fflate').then(({ strToU8 }) => folder['app.xml'] = strToU8(ExcelStrings.getApp(worksheetData.options.worksheetName)));
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeElement method appears to be synchronous based on its signature, but the dynamic import makes it asynchronous without returning a Promise. Callers expecting synchronous completion will not wait for the file data to be written. Consider changing the method signature to return Promise or ensure the caller awaits completion.

Copilot uses AI. Check for mistakes.
export class CoreFile implements IExcelFile {
public writeElement(folder: Object) {
folder['core.xml'] = strToU8(ExcelStrings.getCore());
import('fflate').then(({ strToU8 }) => folder['core.xml'] = strToU8(ExcelStrings.getCore()));
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeElement method appears to be synchronous based on its signature, but the dynamic import makes it asynchronous without returning a Promise. Callers expecting synchronous completion will not wait for the file data to be written. Consider changing the method signature to return Promise or ensure the caller awaits completion.

Copilot uses AI. Check for mistakes.
public writeElement(folder: Object, worksheetData: WorksheetData) {
const hasSharedStrings = !worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders;
folder['workbook.xml.rels'] = strToU8(ExcelStrings.getWorkbookRels(hasSharedStrings));
import('fflate').then(({ strToU8 }) => folder['workbook.xml.rels'] = strToU8(ExcelStrings.getWorkbookRels(hasSharedStrings)));
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeElement method appears to be synchronous based on its signature, but the dynamic import makes it asynchronous without returning a Promise. Callers expecting synchronous completion will not wait for the file data to be written. Consider changing the method signature to return Promise or ensure the caller awaits completion.

Copilot uses AI. Check for mistakes.
export class ThemeFile implements IExcelFile {
public writeElement(folder: Object) {
folder['theme1.xml'] = strToU8(ExcelStrings.getTheme());
import('fflate').then(({ strToU8 }) => folder['theme1.xml'] = strToU8(ExcelStrings.getTheme()));
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeElement method appears to be synchronous based on its signature, but the dynamic import makes it asynchronous without returning a Promise. Callers expecting synchronous completion will not wait for the file data to be written. Consider changing the method signature to return Promise or ensure the caller awaits completion.

Copilot uses AI. Check for mistakes.
Comment on lines +756 to +762
import('fflate').then(({ strToU8 }) => {
folder['sharedStrings.xml'] = strToU8(ExcelStrings.getSharedStringXML(
dict.stringsCount,
sortedValues.length,
sharedStrings.join(''))
);
});
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeElement method appears to be synchronous based on its signature, but the dynamic import makes it asynchronous without returning a Promise. Callers expecting synchronous completion will not wait for the file data to be written. Consider changing the method signature to return Promise or ensure the caller awaits completion.

Copilot uses AI. Check for mistakes.
}

folder['table1.xml'] = strToU8(ExcelStrings.getTablesXML(autoFilterDimension, tableDimension, tableColumns, sortString));
import('fflate').then(({ strToU8 }) => folder['table1.xml'] = strToU8(ExcelStrings.getTablesXML(autoFilterDimension, tableDimension, tableColumns, sortString)));
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeElement method appears to be synchronous based on its signature, but the dynamic import makes it asynchronous without returning a Promise. Callers expecting synchronous completion will not wait for the file data to be written. Consider changing the method signature to return Promise or ensure the caller awaits completion.

Copilot uses AI. Check for mistakes.
export class WorksheetRelsFile implements IExcelFile {
public writeElement(folder: Object) {
folder['sheet1.xml.rels'] = strToU8(ExcelStrings.getWorksheetRels());
import('fflate').then(({ strToU8 }) => folder['sheet1.xml.rels'] = strToU8(ExcelStrings.getWorksheetRels()));
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeElement method appears to be synchronous based on its signature, but the dynamic import makes it asynchronous without returning a Promise. Callers expecting synchronous completion will not wait for the file data to be written. Consider changing the method signature to return Promise or ensure the caller awaits completion.

Copilot uses AI. Check for mistakes.
this.exportEnded.emit({ xlsx: fileData });
done();
// Dynamically import fflate to reduce initial bundle size
import('fflate').then(({ zip }) => {
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dynamic import of fflate is executed on every export operation. Consider caching the imported module at the class level to avoid redundant imports for subsequent exports within the same session.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

@kdinev kdinev requested a review from gedinakova December 2, 2025 09:28
…exporter.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI commented Dec 2, 2025

@kdinev I've opened a new pull request, #16574, to work on those changes. Once the pull request is ready, I'll request review from you.

@gedinakova gedinakova added the 🛠️ status: in-development Issues and PRs with active development on them label Dec 5, 2025
@gedinakova gedinakova self-assigned this Dec 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

excel-exporter 🛠️ status: in-development Issues and PRs with active development on them refactoring squash-merge Merge PR with "Squash and Merge" option version: 21.0.x

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants