Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit d787131

Browse files
committed
feat(excel): add Excel Export feature and add full unit test suite
1 parent f06977f commit d787131

28 files changed

+1427
-190
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,13 @@
8181
"@ngx-translate/http-loader": "^4.0.0",
8282
"core-js": "^2.6.1",
8383
"dompurify": "^1.0.9",
84+
"excel-builder-webpack": "^1.0.3",
8485
"flatpickr": ">=4.5.0",
8586
"font-awesome": "^4.7.0",
8687
"jquery": ">=3.2.1",
8788
"jquery-ui-dist": "^1.12.1",
89+
"jszip": "^3.2.2",
90+
"lodash": "^4.17.15",
8891
"lodash.isequal": "^4.5.0",
8992
"moment-mini": "^2.22.1",
9093
"rxjs": "^6.3.3",
@@ -125,7 +128,6 @@
125128
"custom-event-polyfill": "^1.0.7",
126129
"del": "^3.0.0",
127130
"del-cli": "^1.1.0",
128-
"excel-builder-webpack": "^1.0.3",
129131
"gulp": "^4.0.2",
130132
"gulp-bump": "^3.1.3",
131133
"gulp-sass": "^4.0.2",
@@ -134,8 +136,6 @@
134136
"jest-extended": "^0.11.2",
135137
"jest-junit": "^6.4.0",
136138
"jest-preset-angular": "^6.0.1",
137-
"jszip": "^3.2.2",
138-
"lodash": "^4.17.15",
139139
"ng-packagr": "~5.3.0",
140140
"ngx-bootstrap": "^4.3.0",
141141
"node-sass": "^4.12.0",

src/app/examples/grid-clientside.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export class GridClientSideComponent implements OnInit {
122122
filterable: true, filter: { model: Filters.compoundInputNumber }
123123
},
124124
{
125-
id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, minWidth: 75, exportWithFormatter: true,
125+
id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, minWidth: 75, exportWithFormatter: false,
126126
type: FieldType.date, filterable: true, filter: { model: Filters.compoundDate }
127127
},
128128
{
@@ -175,6 +175,7 @@ export class GridClientSideComponent implements OnInit {
175175
containerId: 'demo-container',
176176
sidePadding: 15
177177
},
178+
enableExcelExport: true,
178179
enableExcelCopyBuffer: true,
179180
enableFiltering: true,
180181
// enableFilterTrimWhiteSpace: true,
@@ -235,7 +236,6 @@ export class GridClientSideComponent implements OnInit {
235236
}
236237
});
237238
}
238-
239239
return tempDataset;
240240
}
241241

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,59 @@
11
<div class="container-fluid">
2-
<h2>{{title}}</h2>
3-
<div class="subtitle" [innerHTML]="subTitle"></div>
4-
<div class="row col-sm-12">
5-
<button class="btn btn-default btn-xs" (click)="loadData(500)">
6-
500 rows
7-
</button>
8-
<button class="btn btn-default btn-xs" (click)="loadData(50000)">
9-
50k rows
10-
</button>
11-
<button class="btn btn-default btn-xs" (click)="clearGrouping()">
12-
<i class="fa fa-times"></i> Clear grouping
13-
</button>
14-
<button class="btn btn-default btn-xs" (click)="collapseAllGroups()">
15-
<i class="fa fa-compress"></i> Collapse all groups
16-
</button>
17-
<button class="btn btn-default btn-xs" (click)="expandAllGroups()">
18-
<i class="fa fa-expand"></i> Expand all groups
19-
</button>
20-
<button class="btn btn-default btn-xs" (click)="exportToCsv('csv')">
21-
<i class="fa fa-download"></i> Export to CSV
22-
</button>
23-
</div>
24-
<hr/>
25-
<div class="row col-sm-12">
26-
<button class="btn btn-default btn-xs" (click)="groupByDuration()">
27-
Group by duration &amp; sort groups by value
28-
</button>
29-
<button class="btn btn-default btn-xs" (click)="groupByDurationOrderByCount(false)">
30-
Group by duration &amp; sort groups by count
31-
</button>
32-
</div>
33-
<div class="row col-sm-12">
34-
<button class="btn btn-default btn-xs" (click)="groupByDurationOrderByCount(true)">
35-
Group by duration &amp; sort groups by count, aggregate collapsed
36-
</button>
37-
<button class="btn btn-default btn-xs" (click)="groupByDurationEffortDriven()">
38-
Group by duration then effort-driven
39-
</button>
40-
<button class="btn btn-default btn-xs" (click)="groupByDurationEffortDrivenPercent()">
41-
Group by duration then effort-driven then percent.
42-
</button>
43-
<span [hidden]="!processing">
44-
<i class="fa fa-refresh fa-spin fa-lg fa-fw"></i>
45-
</span>
46-
</div>
2+
<h2>{{title}}</h2>
3+
<div class="subtitle" [innerHTML]="subTitle"></div>
4+
<div class="row col-sm-12">
5+
<button class="btn btn-default btn-xs" (click)="loadData(500)">
6+
500 rows
7+
</button>
8+
<button class="btn btn-default btn-xs" (click)="loadData(50000)">
9+
50k rows
10+
</button>
11+
<button class="btn btn-default btn-xs" (click)="clearGrouping()">
12+
<i class="fa fa-times"></i> Clear grouping
13+
</button>
14+
<button class="btn btn-default btn-xs" (click)="collapseAllGroups()">
15+
<i class="fa fa-compress"></i> Collapse all groups
16+
</button>
17+
<button class="btn btn-default btn-xs" (click)="expandAllGroups()">
18+
<i class="fa fa-expand"></i> Expand all groups
19+
</button>
20+
<button class="btn btn-default btn-xs" (click)="exportToCsv('csv')">
21+
<i class="fa fa-download"></i> Export to CSV
22+
</button>
23+
<button class="btn btn-default btn-xs" (click)="exportToExcel()">
24+
<i class="fa fa-file-excel-o text-success"></i> Export to Excel
25+
</button>
26+
</div>
27+
<hr />
28+
<div class="row col-sm-12">
29+
<button class="btn btn-default btn-xs" (click)="groupByDuration()">
30+
Group by duration &amp; sort groups by value
31+
</button>
32+
<button class="btn btn-default btn-xs" (click)="groupByDurationOrderByCount(false)">
33+
Group by duration &amp; sort groups by count
34+
</button>
35+
</div>
36+
<div class="row col-sm-12">
37+
<button class="btn btn-default btn-xs" (click)="groupByDurationOrderByCount(true)">
38+
Group by duration &amp; sort groups by count, aggregate collapsed
39+
</button>
40+
<button class="btn btn-default btn-xs" (click)="groupByDurationEffortDriven()">
41+
Group by duration then effort-driven
42+
</button>
43+
<button class="btn btn-default btn-xs" (click)="groupByDurationEffortDrivenPercent()">
44+
Group by duration then effort-driven then percent.
45+
</button>
46+
<span [hidden]="!processing">
47+
<i class="fa fa-refresh fa-spin fa-lg fa-fw"></i>
48+
</span>
49+
</div>
4750

48-
<angular-slickgrid gridId="grid2"
49-
[dataset]="dataset"
50-
[columnDefinitions]="columnDefinitions"
51-
[gridOptions]="gridOptions"
52-
(onAngularGridCreated)="angularGridReady($event)">
53-
</angular-slickgrid>
51+
<angular-slickgrid gridId="grid2"
52+
[dataset]="dataset"
53+
[columnDefinitions]="columnDefinitions"
54+
[gridOptions]="gridOptions"
55+
(onGridBeforeExportToFile)="processing = true"
56+
(onGridAfterExportToFile)="processing = false"
57+
(onAngularGridCreated)="angularGridReady($event)">
58+
</angular-slickgrid>
5459
</div>

src/app/examples/grid-grouping.component.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { Subscription } from 'rxjs';
2020
@Component({
2121
templateUrl: './grid-grouping.component.html'
2222
})
23-
export class GridGroupingComponent implements OnInit, OnDestroy {
23+
export class GridGroupingComponent implements OnInit {
2424
title = 'Example 14: Grouping';
2525
subTitle = `
2626
(<a href="https://github.com/ghiscoding/Angular-Slickgrid/wiki/Grouping-&-Aggregators" target="_blank">Wiki docs</a>)
@@ -39,16 +39,9 @@ export class GridGroupingComponent implements OnInit, OnDestroy {
3939
gridObj: any;
4040
dataviewObj: any;
4141
processing = false;
42-
exportBeforeSub: Subscription;
43-
exportAfterSub: Subscription;
4442

4543
constructor() { }
4644

47-
ngOnDestroy() {
48-
this.exportBeforeSub.unsubscribe();
49-
this.exportAfterSub.unsubscribe();
50-
}
51-
5245
ngOnInit(): void {
5346
this.columnDefinitions = [
5447
{
@@ -142,11 +135,15 @@ export class GridGroupingComponent implements OnInit, OnDestroy {
142135
containerId: 'demo-container',
143136
sidePadding: 15
144137
},
138+
enableExcelExport: true,
145139
enableFiltering: true,
146140
enableGrouping: true,
147141
exportOptions: {
148142
sanitizeDataExport: true
149143
},
144+
excelExportOptions: {
145+
sanitizeDataExport: true
146+
},
150147
gridMenu: {
151148
hideExportTextDelimitedCommand: false
152149
}
@@ -159,10 +156,6 @@ export class GridGroupingComponent implements OnInit, OnDestroy {
159156
this.angularGrid = angularGrid;
160157
this.gridObj = angularGrid.slickGrid;
161158
this.dataviewObj = angularGrid.dataView;
162-
163-
// display a spinner while downloading
164-
this.exportBeforeSub = this.angularGrid.exportService.onGridBeforeExportToFile.subscribe(() => this.processing = true);
165-
this.exportAfterSub = this.angularGrid.exportService.onGridAfterExportToFile.subscribe(() => this.processing = false);
166159
}
167160

168161
loadData(rowCount: number) {
@@ -201,6 +194,13 @@ export class GridGroupingComponent implements OnInit, OnDestroy {
201194
this.dataviewObj.expandAllGroups();
202195
}
203196

197+
exportToExcel() {
198+
this.angularGrid.excelExportService.exportToExcel({
199+
filename: 'Export',
200+
format: FileType.xlsx
201+
});
202+
}
203+
204204
exportToCsv(type = 'csv') {
205205
this.angularGrid.exportService.exportToFile({
206206
delimiter: (type === 'csv') ? DelimiterType.comma : DelimiterType.tab,

src/app/examples/grid-localization.component.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ export class GridLocalizationComponent implements OnInit {
8181
type: FieldType.number,
8282
filter: { model: Filters.slider, /* operator: '>=',*/ params: { hideSliderNumber: true } }
8383
},
84-
{ id: 'start', name: 'Start', field: 'start', headerKey: 'START', formatter: Formatters.dateIso, outputType: FieldType.dateIso, type: FieldType.date, minWidth: 100, filterable: true, filter: { model: Filters.compoundDate } },
84+
{
85+
id: 'start', name: 'Start', field: 'start', headerKey: 'START', minWidth: 100,
86+
formatter: Formatters.dateIso, outputType: FieldType.dateIso, type: FieldType.date, exportWithFormatter: true,
87+
filterable: true, filter: { model: Filters.compoundDate }
88+
},
8589
{ id: 'finish', name: 'Finish', field: 'finish', headerKey: 'FINISH', formatter: Formatters.dateIso, outputType: FieldType.dateIso, type: FieldType.date, minWidth: 100, filterable: true, filter: { model: Filters.compoundDate } },
8690
{
8791
id: 'completedBool', name: 'Completed', field: 'completedBool', headerKey: 'COMPLETED', minWidth: 100,
@@ -130,6 +134,30 @@ export class GridLocalizationComponent implements OnInit {
130134
enableFiltering: true,
131135
enableTranslate: true,
132136
i18n: this.translate,
137+
excelExportOptions: {
138+
// optionally pass a custom header to the Excel Sheet
139+
// a lot of the info can be found on Web Archive of Excel-Builder
140+
// http://web.archive.org/web/20160907052007/http://excelbuilderjs.com/cookbook/fontsAndColors.html
141+
customExcelHeader: (workbook, sheet) => {
142+
const customTitle = this.translate.currentLang === 'fr' ? 'Titre qui est suffisament long pour être coupé' : 'My header that is long enough to wrap';
143+
const stylesheet = workbook.getStyleSheet();
144+
const aFormatDefn = {
145+
'font': { 'size': 12, 'fontName': 'Calibri', 'bold': true, color: 'FF0000FF' }, // every color starts with FF, then regular HTML color
146+
'alignment': { 'wrapText': true }
147+
};
148+
const formatterId = stylesheet.createFormat(aFormatDefn);
149+
sheet.setRowInstructions(0, { height: 30 }); // change height of row 0
150+
151+
// excel cells start with A1 which is upper left corner
152+
sheet.mergeCells('B1', 'D1');
153+
const cols = [];
154+
// push empty data on A1
155+
cols.push({ value: '' });
156+
// push data in B1 cell with metadata formatter
157+
cols.push({ value: customTitle, metadata: { style: formatterId.id } });
158+
sheet.data.push(cols);
159+
}
160+
},
133161
exportOptions: {
134162
// set at the grid option level, meaning all column will evaluate the Formatter (when it has a Formatter defined)
135163
exportWithFormatter: true,
@@ -141,13 +169,13 @@ export class GridLocalizationComponent implements OnInit {
141169
}
142170
};
143171

144-
this.loadData();
172+
this.loadData(1000);
145173
}
146174

147175
// mock a dataset
148-
loadData() {
176+
loadData(count: number) {
149177
this.dataset = [];
150-
for (let i = 0; i < 1000; i++) {
178+
for (let i = 0; i < count; i++) {
151179
const randomYear = 2000 + Math.floor(Math.random() * 30);
152180
const randomMonth = Math.floor(Math.random() * 11);
153181
const randomDay = Math.floor((Math.random() * 29));

src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
} from './../models/index';
3030
import { FilterFactory } from '../filters/filterFactory';
3131
import { SlickgridConfig } from '../slickgrid-config';
32-
import { isObservable, Observable, Subscription } from 'rxjs';
32+
import { isObservable, Observable, Subscription, Subject } from 'rxjs';
3333

3434
// Services
3535
import { AngularUtilService } from '../services/angularUtil.service';
@@ -80,7 +80,6 @@ const slickgridEventPrefix = 'sg';
8080
ColumnPickerExtension,
8181
DraggableGroupingExtension,
8282
ExtensionService,
83-
ExcelExportService,
8483
ExportService,
8584
ExtensionUtility,
8685
FilterFactory,
@@ -133,6 +132,10 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
133132
@Output() onBeforeGridDestroy = new EventEmitter<any>();
134133
@Output() onAfterGridDestroyed = new EventEmitter<boolean>();
135134
@Output() onGridStateChanged = new EventEmitter<GridStateChange>();
135+
@Output() onGridBeforeExportToFile = this.exportService.onGridBeforeExportToFile;
136+
@Output() onGridAfterExportToFile = this.exportService.onGridAfterExportToFile;
137+
@Output() onGridBeforeExportToExcel = new Subject<boolean>();
138+
@Output() onGridAfterExportToExcel = new Subject<any>();
136139
@Input() customDataView: any;
137140
@Input() gridId: string;
138141
@Input() gridOptions: GridOption;
@@ -167,7 +170,6 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
167170

168171
constructor(
169172
private elm: ElementRef,
170-
private excelExportService: ExcelExportService,
171173
private exportService: ExportService,
172174
private extensionService: ExtensionService,
173175
private extensionUtility: ExtensionUtility,
@@ -334,8 +336,12 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
334336
}
335337

336338
// if Excel Export is enabled, initialize the service with the necessary grid and other objects
337-
if (this.gridOptions.enableExcelExport) {
338-
this.excelExportService.init(this.grid, this.dataView);
339+
if (this.gridOptions.enableExcelExport && this.sharedService) {
340+
// create an instance of the ExcelExportService only when required (opt-in)
341+
this.sharedService.excelExportService = new ExcelExportService(this.translate);
342+
this.onGridBeforeExportToExcel = this.sharedService.excelExportService.onGridBeforeExportToExcel;
343+
this.onGridAfterExportToExcel = this.sharedService.excelExportService.onGridAfterExportToExcel;
344+
this.sharedService.excelExportService.init(this.grid, this.dataView);
339345
}
340346

341347
// once all hooks are in placed and the grid is initialized, we can emit an event
@@ -359,7 +365,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
359365

360366
// return all available Services (non-singleton)
361367
backendService: this.gridOptions && this.gridOptions.backendServiceApi && this.gridOptions.backendServiceApi.service,
362-
excelExportService: this.excelExportService,
368+
excelExportService: this.sharedService && this.sharedService.excelExportService,
363369
exportService: this.exportService,
364370
extensionService: this.extensionService,
365371
filterService: this.filterService,

0 commit comments

Comments
 (0)