Skip to content

Commit 79390c0

Browse files
Copilotkdinev
andcommitted
Add PDF export service and integrate with grid toolbar
Co-authored-by: kdinev <[email protected]>
1 parent 9414393 commit 79390c0

File tree

10 files changed

+406
-12
lines changed

10 files changed

+406
-12
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,5 @@ extras/docs/themes/sassdoc/sassdoc/*
5656

5757
# Localization sources
5858
i18nRepo
59+
projects/igniteui-angular/node_modules/
60+
projects/igniteui-angular/package-lock.json

projects/igniteui-angular/package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,21 @@
6969
"tree"
7070
],
7171
"dependencies": {
72+
"@igniteui/material-icons-extended": "^3.1.0",
7273
"fflate": "^0.8.1",
73-
"tslib": "^2.3.0",
74+
"igniteui-theming": "^20.0.0",
7475
"igniteui-trial-watermark": "^3.1.0",
76+
"jspdf": "^3.0.2",
7577
"lodash-es": "^4.17.21",
76-
"igniteui-theming": "^20.0.0",
77-
"@igniteui/material-icons-extended": "^3.1.0"
78+
"tslib": "^2.3.0"
7879
},
7980
"peerDependencies": {
81+
"@angular/animations": "20",
8082
"@angular/common": "20",
8183
"@angular/core": "20",
82-
"@angular/animations": "20",
8384
"@angular/forms": "20",
84-
"hammerjs": "^2.0.8",
85-
"@types/hammerjs": "^2.0.40"
85+
"@types/hammerjs": "^2.0.40",
86+
"hammerjs": "^2.0.8"
8687
},
8788
"peerDependenciesMeta": {
8889
"hammerjs": {

projects/igniteui-angular/src/lib/core/i18n/grid-resources.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export interface IGridResourceStrings {
136136
igx_grid_toolbar_exporter_button_label?: string;
137137
igx_grid_toolbar_exporter_excel_entry_text?: string;
138138
igx_grid_toolbar_exporter_csv_entry_text?: string;
139+
igx_grid_toolbar_exporter_pdf_entry_text?: string;
139140
igx_grid_snackbar_addrow_label?: string;
140141
igx_grid_snackbar_addrow_actiontext?: string;
141142
igx_grid_actions_edit_label?: string;
@@ -318,6 +319,7 @@ export const GridResourceStringsEN: IGridResourceStrings = {
318319
igx_grid_toolbar_exporter_button_label: 'Export',
319320
igx_grid_toolbar_exporter_excel_entry_text: 'Export to Excel',
320321
igx_grid_toolbar_exporter_csv_entry_text: 'Export to CSV',
322+
igx_grid_toolbar_exporter_pdf_entry_text: 'Export to PDF',
321323
igx_grid_snackbar_addrow_label: 'Row added',
322324
igx_grid_snackbar_addrow_actiontext: 'SHOW',
323325
igx_grid_actions_edit_label: 'Edit',

projects/igniteui-angular/src/lib/grids/toolbar/common.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ export class IgxExcelTextDirective { }
1313
})
1414
export class IgxCSVTextDirective { }
1515

16+
@Directive({
17+
selector: '[pdfText],pdf-text',
18+
standalone: true
19+
})
20+
export class IgxPdfTextDirective { }
21+
1622
/* blazorElement */
1723
/* wcElementTag: igc-grid-toolbar-title */
1824
/* blazorAlternateBaseType: GridToolbarContent */

projects/igniteui-angular/src/lib/grids/toolbar/grid-toolbar-exporter.component.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,19 @@
4242
}
4343
</li>
4444
}
45+
46+
@if (exportPDF) {
47+
<li #btnExportPdf id="btnExportPdf" class="igx-grid-toolbar__dd-list-items"
48+
igxRipple (click)="exportClicked('pdf', toggleRef)">
49+
<span #pdf>
50+
<ng-content select=[pdfText],pdf-text></ng-content>
51+
</span>
52+
@if (!pdf.childNodes.length) {
53+
<pdf-text>
54+
{{ grid?.resourceStrings.igx_grid_toolbar_exporter_pdf_entry_text }}
55+
</pdf-text>
56+
}
57+
</li>
58+
}
4559
</ul>
4660
</div>

projects/igniteui-angular/src/lib/grids/toolbar/grid-toolbar-exporter.component.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { Component, Input, Output, EventEmitter, Inject, booleanAttribute } from '@angular/core';
22
import { first } from 'rxjs/operators';
33
import { BaseToolbarDirective } from './grid-toolbar.base';
4-
import { IgxExcelTextDirective, IgxCSVTextDirective } from './common';
4+
import { IgxExcelTextDirective, IgxCSVTextDirective, IgxPdfTextDirective } from './common';
55
import {
66
CsvFileTypes,
77
IgxBaseExporter,
88
IgxCsvExporterOptions,
99
IgxCsvExporterService,
1010
IgxExcelExporterOptions,
11-
IgxExcelExporterService
11+
IgxExcelExporterService,
12+
IgxPdfExporterOptions,
13+
IgxPdfExporterService
1214
} from '../../services/public_api';
1315
import { IgxToggleDirective } from '../../directives/toggle/toggle.directive';
1416
import { GridType } from '../common/grid.interface';
@@ -18,7 +20,7 @@ import { IgxRippleDirective } from '../../directives/ripple/ripple.directive';
1820
import { IgxButtonDirective } from '../../directives/button/button.directive';
1921

2022

21-
export type IgxExporterOptions = IgxCsvExporterOptions | IgxExcelExporterOptions;
23+
export type IgxExporterOptions = IgxCsvExporterOptions | IgxExcelExporterOptions | IgxPdfExporterOptions;
2224

2325
/* jsonAPIComplexObject */
2426
/* wcAlternateName: ExporterEventArgs */
@@ -50,7 +52,7 @@ export interface IgxExporterEvent {
5052
@Component({
5153
selector: 'igx-grid-toolbar-exporter',
5254
templateUrl: './grid-toolbar-exporter.component.html',
53-
imports: [IgxButtonDirective, IgxRippleDirective, IgxIconComponent, IgxToggleDirective, IgxExcelTextDirective, IgxCSVTextDirective]
55+
imports: [IgxButtonDirective, IgxRippleDirective, IgxIconComponent, IgxToggleDirective, IgxExcelTextDirective, IgxCSVTextDirective, IgxPdfTextDirective]
5456
})
5557
export class IgxGridToolbarExporterComponent extends BaseToolbarDirective {
5658

@@ -66,6 +68,12 @@ export class IgxGridToolbarExporterComponent extends BaseToolbarDirective {
6668
@Input({ transform: booleanAttribute })
6769
public exportExcel = true;
6870

71+
/**
72+
* Show entry for PDF export.
73+
*/
74+
@Input({ transform: booleanAttribute })
75+
public exportPDF = true;
76+
6977
/**
7078
* The name for the exported file.
7179
*/
@@ -94,11 +102,12 @@ export class IgxGridToolbarExporterComponent extends BaseToolbarDirective {
94102
@Inject(IgxToolbarToken) toolbar: IgxToolbarToken,
95103
private excelExporter: IgxExcelExporterService,
96104
private csvExporter: IgxCsvExporterService,
105+
private pdfExporter: IgxPdfExporterService,
97106
) {
98107
super(toolbar);
99108
}
100109

101-
protected exportClicked(type: 'excel' | 'csv', toggleRef?: IgxToggleDirective) {
110+
protected exportClicked(type: 'excel' | 'csv' | 'pdf', toggleRef?: IgxToggleDirective) {
102111
toggleRef?.close();
103112
this.export(type);
104113
}
@@ -108,7 +117,7 @@ export class IgxGridToolbarExporterComponent extends BaseToolbarDirective {
108117
* Export the grid's data
109118
* @param type File type to export
110119
*/
111-
public export(type: 'excel' | 'csv'): void {
120+
public export(type: 'excel' | 'csv' | 'pdf'): void {
112121
let options: IgxExporterOptions;
113122
let exporter: IgxBaseExporter;
114123

@@ -120,6 +129,10 @@ export class IgxGridToolbarExporterComponent extends BaseToolbarDirective {
120129
case 'excel':
121130
options = new IgxExcelExporterOptions(this.filename);
122131
exporter = this.excelExporter;
132+
break;
133+
case 'pdf':
134+
options = new IgxPdfExporterOptions(this.filename);
135+
exporter = this.pdfExporter;
123136
}
124137

125138
const args = { exporter, options, grid: this.grid, cancel: false } as IgxExporterEvent;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { IgxExporterOptionsBase } from '../exporter-common/exporter-options-base';
2+
3+
/**
4+
* Objects of this class are used to configure the PDF exporting process.
5+
*/
6+
export class IgxPdfExporterOptions extends IgxExporterOptionsBase {
7+
/**
8+
* Specifies the page orientation. (portrait or landscape, portrait by default)
9+
* ```typescript
10+
* let pageOrientation = this.exportOptions.pageOrientation;
11+
* this.exportOptions.pageOrientation = 'landscape';
12+
* ```
13+
*
14+
* @memberof IgxPdfExporterOptions
15+
*/
16+
public pageOrientation: 'portrait' | 'landscape' = 'portrait';
17+
18+
/**
19+
* Specifies the page size. (a4, a3, letter, legal, etc., a4 by default)
20+
* ```typescript
21+
* let pageSize = this.exportOptions.pageSize;
22+
* this.exportOptions.pageSize = 'letter';
23+
* ```
24+
*
25+
* @memberof IgxPdfExporterOptions
26+
*/
27+
public pageSize: string = 'a4';
28+
29+
/**
30+
* Specifies whether to show table borders. (True by default)
31+
* ```typescript
32+
* let showTableBorders = this.exportOptions.showTableBorders;
33+
* this.exportOptions.showTableBorders = false;
34+
* ```
35+
*
36+
* @memberof IgxPdfExporterOptions
37+
*/
38+
public showTableBorders = true;
39+
40+
/**
41+
* Specifies the font size for the table content. (10 by default)
42+
* ```typescript
43+
* let fontSize = this.exportOptions.fontSize;
44+
* this.exportOptions.fontSize = 12;
45+
* ```
46+
*
47+
* @memberof IgxPdfExporterOptions
48+
*/
49+
public fontSize = 10;
50+
51+
constructor(fileName: string) {
52+
super(fileName, '.pdf');
53+
}
54+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { ExportUtilities } from '../exporter-common/export-utilities';
2+
import { IgxPdfExporterService } from './pdf-exporter';
3+
import { IgxPdfExporterOptions } from './pdf-exporter-options';
4+
import { SampleTestData } from '../../test-utils/sample-test-data.spec';
5+
import { first } from 'rxjs/operators';
6+
7+
describe('PDF Exporter', () => {
8+
let exporter: IgxPdfExporterService;
9+
let options: IgxPdfExporterOptions;
10+
11+
beforeEach(() => {
12+
exporter = new IgxPdfExporterService();
13+
options = new IgxPdfExporterOptions('PdfExport');
14+
15+
// Spy the saveBlobToFile method so the files are not really created
16+
spyOn(ExportUtilities, 'saveBlobToFile');
17+
});
18+
19+
it('should be created', () => {
20+
expect(exporter).toBeTruthy();
21+
});
22+
23+
it('should export empty data without errors', (done) => {
24+
exporter.exportEnded.pipe(first()).subscribe(() => {
25+
expect(ExportUtilities.saveBlobToFile).toHaveBeenCalledTimes(1);
26+
done();
27+
});
28+
29+
exporter.exportData([], options);
30+
});
31+
32+
it('should export simple data successfully', (done) => {
33+
const simpleData = [
34+
{ Name: 'John', Age: 30 },
35+
{ Name: 'Jane', Age: 25 }
36+
];
37+
38+
exporter.exportEnded.pipe(first()).subscribe(() => {
39+
expect(ExportUtilities.saveBlobToFile).toHaveBeenCalledTimes(1);
40+
done();
41+
});
42+
43+
exporter.exportData(simpleData, options);
44+
});
45+
46+
it('should export contacts data successfully', (done) => {
47+
exporter.exportEnded.pipe(first()).subscribe(() => {
48+
expect(ExportUtilities.saveBlobToFile).toHaveBeenCalledTimes(1);
49+
done();
50+
});
51+
52+
exporter.exportData(SampleTestData.contactsData(), options);
53+
});
54+
55+
it('should export with custom page orientation', (done) => {
56+
options.pageOrientation = 'landscape';
57+
58+
exporter.exportEnded.pipe(first()).subscribe(() => {
59+
expect(ExportUtilities.saveBlobToFile).toHaveBeenCalledTimes(1);
60+
done();
61+
});
62+
63+
exporter.exportData(SampleTestData.contactsData(), options);
64+
});
65+
66+
it('should export with custom page size', (done) => {
67+
options.pageSize = 'letter';
68+
69+
exporter.exportEnded.pipe(first()).subscribe(() => {
70+
expect(ExportUtilities.saveBlobToFile).toHaveBeenCalledTimes(1);
71+
done();
72+
});
73+
74+
exporter.exportData(SampleTestData.contactsData(), options);
75+
});
76+
77+
it('should export without table borders', (done) => {
78+
options.showTableBorders = false;
79+
80+
exporter.exportEnded.pipe(first()).subscribe(() => {
81+
expect(ExportUtilities.saveBlobToFile).toHaveBeenCalledTimes(1);
82+
done();
83+
});
84+
85+
exporter.exportData(SampleTestData.contactsData(), options);
86+
});
87+
88+
it('should export with custom font size', (done) => {
89+
options.fontSize = 12;
90+
91+
exporter.exportEnded.pipe(first()).subscribe(() => {
92+
expect(ExportUtilities.saveBlobToFile).toHaveBeenCalledTimes(1);
93+
done();
94+
});
95+
96+
exporter.exportData(SampleTestData.contactsData(), options);
97+
});
98+
99+
it('should handle null and undefined values', (done) => {
100+
const dataWithNulls = [
101+
{ Name: 'John', Age: null },
102+
{ Name: undefined, Age: 25 }
103+
];
104+
105+
exporter.exportEnded.pipe(first()).subscribe(() => {
106+
expect(ExportUtilities.saveBlobToFile).toHaveBeenCalledTimes(1);
107+
done();
108+
});
109+
110+
exporter.exportData(dataWithNulls, options);
111+
});
112+
113+
it('should handle date values', (done) => {
114+
const dataWithDates = [
115+
{ Name: 'John', BirthDate: new Date('1990-01-01') },
116+
{ Name: 'Jane', BirthDate: new Date('1995-06-15') }
117+
];
118+
119+
exporter.exportEnded.pipe(first()).subscribe(() => {
120+
expect(ExportUtilities.saveBlobToFile).toHaveBeenCalledTimes(1);
121+
done();
122+
});
123+
124+
exporter.exportData(dataWithDates, options);
125+
});
126+
});

0 commit comments

Comments
 (0)