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

Commit f06977f

Browse files
committed
feat(export): add Export to Excel feature
1 parent 1665f34 commit f06977f

22 files changed

+590
-45
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
"custom-event-polyfill": "^1.0.7",
126126
"del": "^3.0.0",
127127
"del-cli": "^1.1.0",
128+
"excel-builder-webpack": "^1.0.3",
128129
"gulp": "^4.0.2",
129130
"gulp-bump": "^3.1.3",
130131
"gulp-sass": "^4.0.2",
@@ -133,6 +134,8 @@
133134
"jest-extended": "^0.11.2",
134135
"jest-junit": "^6.4.0",
135136
"jest-preset-angular": "^6.0.1",
137+
"jszip": "^3.2.2",
138+
"lodash": "^4.17.15",
136139
"ng-packagr": "~5.3.0",
137140
"ngx-bootstrap": "^4.3.0",
138141
"node-sass": "^4.12.0",
Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,44 @@
11
<div class="container-fluid">
2-
<h2>{{title}}</h2>
3-
<div class="subtitle" [innerHTML]="subTitle"></div>
2+
<h2>{{title}}</h2>
3+
<div class="subtitle" [innerHTML]="subTitle"></div>
44

5-
<hr/>
5+
<hr />
66

7-
<div class="row col-sm-12">
8-
<button class="btn btn-default btn-sm" (click)="switchLanguage()">
9-
<i class="fa fa-language"></i>
10-
Switch Language
11-
</button>
12-
<b>Locale:</b> <span style="font-style: italic">{{selectedLanguage + '.json'}}</span>
7+
<div class="row col-sm-12">
8+
<button class="btn btn-default btn-sm" (click)="switchLanguage()">
9+
<i class="fa fa-language"></i>
10+
Switch Language
11+
</button>
12+
<b>Locale:</b> <span style="font-style: italic">{{selectedLanguage + '.json'}}</span>
1313

14-
<span style="margin-left: 20px">
15-
<button class="btn btn-default btn-sm" (click)="exportToFile('csv')">
16-
<i class="fa fa-download"></i>
17-
Download to CSV
18-
</button>
19-
<button class="btn btn-default btn-sm" (click)="exportToFile('txt')">
20-
<i class="fa fa-download"></i>
21-
Download to Text
22-
</button>
23-
<button class="btn btn-default btn-sm" (click)="dynamicallyAddTitleHeader()">
24-
<i class="fa fa-plus"></i>
25-
Dynamically Duplicate Title Column
26-
</button>
27-
</span>
28-
</div>
14+
<span style="margin-left: 20px">
15+
<button class="btn btn-default btn-sm" (click)="exportToFile('csv')">
16+
<i class="fa fa-download"></i>
17+
Download to CSV
18+
</button>
19+
<button class="btn btn-default btn-sm" (click)="exportToFile('txt')">
20+
<i class="fa fa-download"></i>
21+
Download to Text
22+
</button>
23+
<button class="btn btn-default btn-sm" (click)="exportToExcel()">
24+
<i class="fa fa-file-excel-o text-success"></i>
25+
Download to Excel
26+
</button>
27+
</span>
28+
<span style="margin-left: 10px">
29+
<button class="btn btn-default btn-sm" (click)="dynamicallyAddTitleHeader()">
30+
<i class="fa fa-plus"></i>
31+
Dynamically Duplicate Title Column
32+
</button>
33+
</span>
34+
</div>
2935

30-
<div class="col-sm-12">
31-
<angular-slickgrid gridId="grid12"
32-
(onAngularGridCreated)="angularGridReady($event)"
33-
[columnDefinitions]="columnDefinitions"
34-
[gridOptions]="gridOptions"
35-
[dataset]="dataset">
36-
</angular-slickgrid>
37-
</div>
36+
<div class="col-sm-12">
37+
<angular-slickgrid gridId="grid12"
38+
(onAngularGridCreated)="angularGridReady($event)"
39+
[columnDefinitions]="columnDefinitions"
40+
[gridOptions]="gridOptions"
41+
[dataset]="dataset">
42+
</angular-slickgrid>
43+
</div>
3844
</div>

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ export class GridLocalizationComponent implements OnInit {
7676
{
7777
id: 'duration', name: 'Duration (days)', field: 'duration', headerKey: 'DURATION', sortable: true,
7878
formatter: Formatters.percentCompleteBar, minWidth: 100,
79+
exportWithFormatter: false,
7980
filterable: true,
81+
type: FieldType.number,
8082
filter: { model: Filters.slider, /* operator: '>=',*/ params: { hideSliderNumber: true } }
8183
},
8284
{ 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 } },
@@ -123,6 +125,8 @@ export class GridLocalizationComponent implements OnInit {
123125
},
124126
enableAutoResize: true,
125127
enableExcelCopyBuffer: true,
128+
enableExcelExport: true,
129+
enableExport: true,
126130
enableFiltering: true,
127131
enableTranslate: true,
128132
i18n: this.translate,
@@ -170,6 +174,13 @@ export class GridLocalizationComponent implements OnInit {
170174
this.columnDefinitions = this.columnDefinitions.slice();
171175
}
172176

177+
exportToExcel() {
178+
this.angularGrid.excelExportService.exportToExcel({
179+
filename: 'Export',
180+
format: FileType.xlsx
181+
});
182+
}
183+
173184
exportToFile(type = 'csv') {
174185
this.angularGrid.exportService.exportToFile({
175186
delimiter: (type === 'csv') ? DelimiterType.comma : DelimiterType.tab,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { SlickPaginationComponent } from './slick-pagination.component';
1010
import { CollectionService } from '../services/collection.service';
1111
import {
1212
AngularUtilService,
13+
ExcelExportService,
1314
ExportService,
1415
ExtensionService,
1516
FilterService,
@@ -74,6 +75,7 @@ describe('App Component', () => {
7475
providers: [
7576
AngularUtilService,
7677
CollectionService,
78+
ExcelExportService,
7779
ExportService,
7880
ExtensionService,
7981
ExtensionUtility,

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { isObservable, Observable, Subscription } from 'rxjs';
3333

3434
// Services
3535
import { AngularUtilService } from '../services/angularUtil.service';
36+
import { ExcelExportService } from '../services/excelExport.service';
3637
import { ExportService } from './../services/export.service';
3738
import { ExtensionService } from '../services/extension.service';
3839
import { ExtensionUtility } from '../extensions/extensionUtility';
@@ -79,6 +80,7 @@ const slickgridEventPrefix = 'sg';
7980
ColumnPickerExtension,
8081
DraggableGroupingExtension,
8182
ExtensionService,
83+
ExcelExportService,
8284
ExportService,
8385
ExtensionUtility,
8486
FilterFactory,
@@ -165,6 +167,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
165167

166168
constructor(
167169
private elm: ElementRef,
170+
private excelExportService: ExcelExportService,
168171
private exportService: ExportService,
169172
private extensionService: ExtensionService,
170173
private extensionUtility: ExtensionUtility,
@@ -330,6 +333,11 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
330333
this.exportService.init(this.grid, this.dataView);
331334
}
332335

336+
// 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+
}
340+
333341
// once all hooks are in placed and the grid is initialized, we can emit an event
334342
this.onGridInitialized.emit(this.grid);
335343

@@ -351,6 +359,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
351359

352360
// return all available Services (non-singleton)
353361
backendService: this.gridOptions && this.gridOptions.backendServiceApi && this.gridOptions.backendServiceApi.service,
362+
excelExportService: this.excelExportService,
354363
exportService: this.exportService,
355364
extensionService: this.extensionService,
356365
filterService: this.filterService,

src/app/modules/angular-slickgrid/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class Constants {
1313
TEXT_ENDS_WITH: 'Ends With',
1414
TEXT_EXPORT_IN_CSV_FORMAT: 'Export in CSV format',
1515
TEXT_EXPORT_IN_TEXT_FORMAT: 'Export in Text format (Tab delimited)',
16+
TEXT_EXPORT_TO_EXCEL: 'Export to Excel',
1617
TEXT_FORCE_FIT_COLUMNS: 'Force fit columns',
1718
TEXT_GROUP_BY: 'Group By',
1819
TEXT_HIDE_COLUMN: 'Hide Column',

src/app/modules/angular-slickgrid/extensions/__tests__/gridMenuExtension.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { TestBed } from '@angular/core/testing';
22
import { TranslateService, TranslateModule } from '@ngx-translate/core';
3+
34
import { GridOption } from '../../models/gridOption.interface';
45
import { GridMenuExtension } from '../gridMenuExtension';
56
import { ExtensionUtility } from '../extensionUtility';
67
import { SharedService } from '../../services/shared.service';
78
import { Column, DelimiterType, FileType } from '../../models';
8-
import { ExportService, FilterService, SortService } from '../../services';
9+
import { ExcelExportService, ExportService, FilterService, SortService } from '../../services';
910

1011
declare var Slick: any;
1112

@@ -127,6 +128,7 @@ describe('gridMenuExtension', () => {
127128
CLEAR_ALL_FILTERS: 'Supprimer tous les filtres',
128129
CLEAR_ALL_SORTING: 'Supprimer tous les tris',
129130
EXPORT_TO_CSV: 'Exporter en format CSV',
131+
EXPORT_TO_EXCEL: 'Exporter vers Excel',
130132
EXPORT_TO_TAB_DELIMITED: 'Exporter en format texte (délimité par tabulation)',
131133
HELP: 'Aide',
132134
COMMANDS: 'Commandes',
@@ -396,7 +398,7 @@ describe('gridMenuExtension', () => {
396398
});
397399

398400
it('should have the "export-csv" menu command when "enableExport" is set', () => {
399-
const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportTextDelimitedCommand: true } } as unknown as GridOption;
401+
const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportExcelCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption;
400402
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
401403
extension.register();
402404
extension.register(); // calling 2x register to make sure it doesn't duplicate commands
@@ -406,14 +408,14 @@ describe('gridMenuExtension', () => {
406408
});
407409

408410
it('should not have the "export-csv" menu command when "enableExport" and "hideExportCsvCommand" are set', () => {
409-
const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption;
411+
const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption;
410412
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
411413
extension.register();
412414
expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([]);
413415
});
414416

415417
it('should have the "export-text-delimited" menu command when "enableExport" is set', () => {
416-
const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportCsvCommand: true } } as unknown as GridOption;
418+
const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportCsvCommand: true, hideExportExcelCommand: true } } as unknown as GridOption;
417419
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
418420
extension.register();
419421
extension.register(); // calling 2x register to make sure it doesn't duplicate commands
@@ -423,7 +425,7 @@ describe('gridMenuExtension', () => {
423425
});
424426

425427
it('should not have the "export-text-delimited" menu command when "enableExport" and "hideExportCsvCommand" are set', () => {
426-
const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption;
428+
const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption;
427429
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
428430
extension.register();
429431
expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([]);
@@ -471,6 +473,7 @@ describe('gridMenuExtension', () => {
471473
extension.register();
472474
expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([
473475
{ command: 'export-csv', disabled: false, iconCssClass: 'fa fa-download', positionOrder: 53, title: 'Exporter en format CSV' },
476+
{ command: 'export-excel', disabled: false, iconCssClass: 'fa fa-file-excel-o text-success', positionOrder: 53, title: 'Exporter vers Excel' },
474477
{ command: 'help', disabled: false, iconCssClass: 'fa fa-question-circle', positionOrder: 99, title: 'Aide', titleKey: 'HELP' },
475478
]);
476479
});
@@ -480,6 +483,7 @@ describe('gridMenuExtension', () => {
480483
extension.register();
481484
expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([
482485
{ command: 'export-csv', disabled: false, iconCssClass: 'fa fa-download', positionOrder: 53, title: 'Exporter en format CSV' },
486+
{ command: 'export-excel', disabled: false, iconCssClass: 'fa fa-file-excel-o text-success', positionOrder: 53, title: 'Exporter vers Excel' },
483487
{ command: 'help', disabled: false, iconCssClass: 'fa fa-question-circle', positionOrder: 99, title: 'Aide', titleKey: 'HELP' },
484488
]);
485489
});
@@ -680,7 +684,7 @@ describe('gridMenuExtension', () => {
680684
describe('without ngx-translate', () => {
681685
beforeEach(() => {
682686
translate = null;
683-
extension = new GridMenuExtension({} as ExportService, {} as ExtensionUtility, {} as FilterService, { gridOptions: { enableTranslate: true } } as SharedService, {} as SortService, translate);
687+
extension = new GridMenuExtension({} as ExportService, {} as ExtensionUtility, {} as FilterService, { gridOptions: { enableTranslate: true } } as SharedService, {} as SortService, {} as ExcelExportService, translate);
684688
});
685689

686690
it('should throw an error if "enableTranslate" is set but the I18N Service is null', () => {

src/app/modules/angular-slickgrid/extensions/gridMenuExtension.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Locale,
1515
SlickEventHandler,
1616
} from '../models';
17+
import { ExcelExportService } from '../services/excelExport.service';
1718
import { ExportService } from '../services/export.service';
1819
import { ExtensionUtility } from './extensionUtility';
1920
import { FilterService } from '../services/filter.service';
@@ -39,6 +40,7 @@ export class GridMenuExtension implements Extension {
3940
private filterService: FilterService,
4041
private sharedService: SharedService,
4142
private sortService: SortService,
43+
@Optional() private excelExportService: ExcelExportService,
4244
@Optional() private translate: TranslateService,
4345
) {
4446
this._eventHandler = new Slick.EventHandler();
@@ -320,7 +322,7 @@ export class GridMenuExtension implements Extension {
320322
}
321323
}
322324

323-
// show grid menu: export to file
325+
// show grid menu: Export to file
324326
if (this.sharedService.gridOptions && this.sharedService.gridOptions.enableExport && this.sharedService.gridOptions.gridMenu && !this.sharedService.gridOptions.gridMenu.hideExportCsvCommand) {
325327
const commandName = 'export-csv';
326328
if (!originalCustomItems.find((item) => item.command === commandName)) {
@@ -336,6 +338,22 @@ export class GridMenuExtension implements Extension {
336338
}
337339
}
338340

341+
// show grid menu: Export to Excel
342+
if (this.sharedService.gridOptions && this.sharedService.gridOptions.enableExport && this.sharedService.gridOptions.gridMenu && !this.sharedService.gridOptions.gridMenu.hideExportExcelCommand) {
343+
const commandName = 'export-excel';
344+
if (!originalCustomItems.find((item) => item.command === commandName)) {
345+
gridMenuCustomItems.push(
346+
{
347+
iconCssClass: this.sharedService.gridOptions.gridMenu.iconExportExcelCommand || 'fa fa-file-excel-o text-success',
348+
title: this.sharedService.gridOptions.enableTranslate ? this.translate.instant('EXPORT_TO_EXCEL') : this._locales && this._locales.TEXT_EXPORT_TO_EXCEL,
349+
disabled: false,
350+
command: commandName,
351+
positionOrder: 53
352+
}
353+
);
354+
}
355+
}
356+
339357
// show grid menu: export to text file as tab delimited
340358
if (this.sharedService.gridOptions && this.sharedService.gridOptions.enableExport && this.sharedService.gridOptions.gridMenu && !this.sharedService.gridOptions.gridMenu.hideExportTextDelimitedCommand) {
341359
const commandName = 'export-text-delimited';
@@ -382,15 +400,21 @@ export class GridMenuExtension implements Extension {
382400
delimiter: DelimiterType.comma,
383401
filename: 'export',
384402
format: FileType.csv,
385-
useUtf8WithBom: true
403+
useUtf8WithBom: true,
404+
});
405+
break;
406+
case 'export-excel':
407+
this.excelExportService.exportToExcel({
408+
filename: 'export',
409+
format: FileType.xlsx,
386410
});
387411
break;
388412
case 'export-text-delimited':
389413
this.exportService.exportToFile({
390414
delimiter: DelimiterType.tab,
391415
filename: 'export',
392416
format: FileType.txt,
393-
useUtf8WithBom: true
417+
useUtf8WithBom: true,
394418
});
395419
break;
396420
case 'toggle-filter':

src/app/modules/angular-slickgrid/global-grid-options.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,23 @@ export const GlobalGridOptions: GridOption = {
3535
enableCellNavigation: false,
3636
enableColumnPicker: true,
3737
enableColumnReorder: true,
38-
enableExport: true,
38+
enableExcelExport: false,
39+
enableExport: true, // CSV by default (could export to CSV)
3940
enableGridMenu: true,
4041
enableHeaderMenu: true,
4142
enableMouseHoverHighlightRow: true,
4243
enableSorting: true,
4344
enableTextSelectionOnCells: true,
4445
explicitInitialization: true,
46+
excelExportOptions: {
47+
exportWithFormatter: false,
48+
filename: 'export',
49+
format: FileType.xlsx,
50+
groupingColumnHeaderTitle: 'Group By',
51+
groupingAggregatorRowText: '',
52+
sanitizeDataExport: false,
53+
useUtf8WithBom: true
54+
},
4555
exportOptions: {
4656
delimiter: DelimiterType.comma,
4757
exportWithFormatter: false,
@@ -57,6 +67,7 @@ export const GlobalGridOptions: GridOption = {
5767
hideClearAllFiltersCommand: false,
5868
hideClearAllSortingCommand: false,
5969
hideExportCsvCommand: false,
70+
hideExportExcelCommand: true,
6071
hideExportTextDelimitedCommand: true,
6172
hideForceFitButton: false,
6273
hideRefreshDatasetCommand: false,
@@ -67,6 +78,7 @@ export const GlobalGridOptions: GridOption = {
6778
iconClearAllFiltersCommand: 'fa fa-filter text-danger',
6879
iconClearAllSortingCommand: 'fa fa-unsorted text-danger',
6980
iconExportCsvCommand: 'fa fa-download',
81+
iconExportExcelCommand: 'fa fa-file-excel-o text-success',
7082
iconExportTextDelimitedCommand: 'fa fa-download',
7183
iconRefreshDatasetCommand: 'fa fa-refresh',
7284
iconToggleFilterCommand: 'fa fa-random',

0 commit comments

Comments
 (0)