Skip to content

Commit

Permalink
feat(excel): add some extra styling & width options for Excel export (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding committed Feb 21, 2020
1 parent dc3afa9 commit d87ce2c
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 13 deletions.
6 changes: 5 additions & 1 deletion src/app/examples/grid-contextmenu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,11 @@ export class GridContextMenuComponent implements OnInit {
enableSorting: true,
enableTranslate: true,
excelExportOptions: {
exportWithFormatter: true
exportWithFormatter: true,
customColumnWidth: 15,

// you can customize how the header titles will be styled (defaults to Bold)
columnHeaderStyle: { font: { bold: true, italic: true } }
},
i18n: this.translate,

Expand Down
9 changes: 9 additions & 0 deletions src/app/modules/angular-slickgrid/models/column.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,21 @@ export interface Column {
/** Defaults to false, which leads to exclude the column from getting a header menu. For example, the checkbox row selection should not have a header menu. */
excludeFromHeaderMenu?: boolean;

/** If defined this will be set as column width in Excel */
exportColumnWidth?: number;

/**
* Export with a Custom Formatter, useful when we want to use a different Formatter for the export.
* For example, we might have a boolean field with "Formatters.checkmark" but we would like see a translated value for (True/False).
*/
exportCustomFormatter?: Formatter;

/**
* Export with a Custom Group Total Formatter, useful when we want to use a different Formatter for the export.
* For example, we might have a boolean field with "Formatters.checkmark" but we would like see a translated value for (True/False).
*/
exportCustomGroupTotalsFormatter?: GroupTotalsFormatter;

/**
* Defaults to false, which leads to Formatters being evaluated on export.
* Most often used with dates that are stored as UTC but displayed as Date ISO (or any other format) with a Formatter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export interface ExcelExportOption {
/** Defaults to true, when grid is using Grouping, it will show indentation of the text with collapsed/expanded symbol as well */
addGroupIndentation?: boolean;

/** If defined apply the style to header columns. Else use the bold style */
columnHeaderStyle?: any;

/** If set then this will be used as column width for all columns */
customColumnWidth?: number;

/** Defaults to false, which leads to all Formatters of the grid being evaluated on export. You can also override a column by changing the propery on the column itself */
exportWithFormatter?: boolean;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core';
import * as moment from 'moment-mini';

import {
Column,
FileType,
Formatter,
GridOption,
Column,
GroupTotalsFormatter,
FieldType,
SortDirectionNumber,
ExcelExportOption,
Expand All @@ -21,6 +22,14 @@ import { GroupTotalFormatters } from '../..';

const myBoldHtmlFormatter: Formatter = (row, cell, value, columnDef, dataContext) => value !== null ? { text: `<b>${value}</b>` } : null;
const myUppercaseFormatter: Formatter = (row, cell, value, columnDef, dataContext) => value ? { text: value.toUpperCase() } : null;
const myUppercaseGroupTotalFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column) => {
const field = columnDef.field || '';
const val = totals.sum && totals.sum[field];
if (val != null && !isNaN(+val)) {
return `Custom: ${val}`;
}
return '';
};
const myCustomObjectFormatter: Formatter = (row: number, cell: number, value: any, columnDef: Column, dataContext: any, grid: any) => {
let textValue = value && value.hasOwnProperty('text') ? value.text : value;
const toolTip = value && value.hasOwnProperty('toolTip') ? value.toolTip : '';
Expand Down Expand Up @@ -387,7 +396,7 @@ describe('ExcelExportService', () => {
});
});

it(`should have the Order without html tags when the grid option has "sanitizeDataExport" enabled`, async () => {
it(`should have the Order without html tags when the grid option has "sanitizeDataExport" is enabled`, async () => {
mockGridOptions.excelExportOptions = { sanitizeDataExport: true };
mockCollection = [{ id: 1, userId: '2B02', firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 }];
jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length);
Expand Down Expand Up @@ -417,6 +426,36 @@ describe('ExcelExportService', () => {
});
});

it(`should have different styling for header titles when the grid option has "columnHeaderStyle" provided with custom styles`, async () => {
mockGridOptions.excelExportOptions = { columnHeaderStyle: { font: { bold: true, italic: true } } };
mockCollection = [{ id: 1, userId: '2B02', firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 }];
jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length);
jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection[0]);
const spyOnAfter = jest.spyOn(service.onGridAfterExportToExcel, 'next');
const spyUrlCreate = jest.spyOn(URL, 'createObjectURL');
const spyDownload = jest.spyOn(service, 'startDownloadFile');

const optionExpectation = { filename: 'export.xlsx', format: 'xlsx' };

service.init(gridStub, dataViewStub);
await service.exportToExcel(mockExportExcelOptions);

expect(spyOnAfter).toHaveBeenCalledWith(optionExpectation);
expect(spyUrlCreate).toHaveBeenCalledWith(mockExcelBlob);
expect(spyDownload).toHaveBeenCalledWith({
...optionExpectation, blob: new Blob(), data: [
[
{ metadata: { style: 5, }, value: 'User Id', },
{ metadata: { style: 5, }, value: 'FirstName', },
{ metadata: { style: 5, }, value: 'LastName', },
{ metadata: { style: 5, }, value: 'Position', },
{ metadata: { style: 5, }, value: 'Order', },
],
['2B02', 'Jane', 'DOE', 'FINANCE_MANAGER', '<b>1</b>'],
]
});
});

it(`should have a custom Title when "customExcelHeader" is provided`, async () => {
mockGridOptions.excelExportOptions = {
sanitizeDataExport: true,
Expand Down Expand Up @@ -491,7 +530,7 @@ describe('ExcelExportService', () => {
});


it(`should Date exported correctly when Field Type is provided and we use "exportWithFormatter" set to True & False`, async () => {
it(`should expect Date exported correctly when Field Type is provided and we use "exportWithFormatter" set to True & False`, async () => {
mockCollection = [
{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', startDate: '2005-12-20T18:19:19.992Z', endDate: null },
{ id: 1, userId: '1E09', firstName: 'Jane', lastName: 'Doe', position: 'HUMAN_RESOURCES', startDate: '2010-10-09T18:19:19.992Z', endDate: '2024-01-02T16:02:02.000Z' },
Expand Down Expand Up @@ -641,6 +680,7 @@ describe('ExcelExportService', () => {
exportWithFormatter: true,
formatter: Formatters.multiple, params: { formatters: [myBoldHtmlFormatter, myCustomObjectFormatter] },
groupTotalsFormatter: GroupTotalFormatters.sumTotals,
exportCustomGroupTotalsFormatter: myUppercaseGroupTotalFormatter,
},
] as Column[];

Expand Down Expand Up @@ -706,7 +746,7 @@ describe('ExcelExportService', () => {
['▿ Order: 20 (2 items)'],
['', '1E06', 'John', 'Z', 'SALES_REP', '10'],
['', '2B02', 'Jane', 'DOE', 'FINANCE_MANAGER', '10'],
['', '', '', '', '', '20'],
['', '', '', '', '', 'Custom: 20'],
]
});
});
Expand Down Expand Up @@ -1231,7 +1271,7 @@ describe('ExcelExportService', () => {
});

it('should return a date time format when using FieldType.date', async () => {
const input = new Date('2012-02-28T23:01:52.103Z');
const input = new Date(Date.UTC(2012, 1, 28, 23, 1, 52, 103));
const expectedDate = '2012-02-28';

service.init(gridStub, dataViewStub);
Expand Down
50 changes: 43 additions & 7 deletions src/app/modules/angular-slickgrid/services/excelExport.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { ExcelWorkbook } from './../models/excelWorkbook.interface';
import { ExcelStylesheet } from './../models/excelStylesheet.interface';
import { Injectable, Optional } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import * as ExcelBuilder from 'excel-builder-webpacker';
import { Subject } from 'rxjs';
import * as moment_ from 'moment-mini';
const moment = moment_; // patch to fix rollup "moment has no default export" issue, document here https://github.com/rollup/rollup/issues/670


import { ExcelWorkbook } from './../models/excelWorkbook.interface';
import { ExcelStylesheet } from './../models/excelStylesheet.interface';
import {
Column,
ExcelCellFormat,
Expand Down Expand Up @@ -120,6 +119,9 @@ export class ExcelExportService {
this._gridOptions.excelExportOptions.customExcelHeader(this._workbook, this._sheet);
}

const columns = this._grid && this._grid.getColumns && this._grid.getColumns() || [];
this._sheet.setColumns(this.getColumnStyles(columns));

const currentSheetData = this._sheet.data;
let finalOutput = currentSheetData;
if (Array.isArray(currentSheetData) && Array.isArray(dataOutput)) {
Expand Down Expand Up @@ -239,16 +241,46 @@ export class ExcelExportService {

// data variable which will hold all the fields data of a row
const outputData = [];
const columnHeaderStyle = this._gridOptions && this._gridOptions.excelExportOptions && this._gridOptions.excelExportOptions.columnHeaderStyle;
let columnHeaderStyleId = this._stylesheetFormats.boldFormatter.id;
if (columnHeaderStyle) {
columnHeaderStyleId = this._stylesheet.createFormat(columnHeaderStyle).id;
}

// get all column headers (it might include a "Group by" title at A1 cell)
outputData.push(this.getColumnHeaderData(columns, { style: this._stylesheetFormats.boldFormatter.id }));
// also style the headers, defaults to Bold but user could pass his own style
outputData.push(this.getColumnHeaderData(columns, { style: columnHeaderStyleId }));

// Populate the rest of the Grid Data
this.pushAllGridRowDataToArray(outputData, columns);

return outputData;
}

/** Get each column style including a style for the width of each column */
private getColumnStyles(columns: Column[]): any[] {
const grouping = this._dataView && this._dataView.getGrouping && this._dataView.getGrouping();
const columnStyles = [];
if (grouping) {
columnStyles.push({
bestFit: true,
columnStyles: (this._gridOptions && this._gridOptions.excelExportOptions && this._gridOptions.excelExportOptions.customColumnWidth) || 10
});
}

columns.forEach((columnDef: Column) => {
const skippedField = columnDef.excludeFromExport || false;
// if column width is 0, then we consider that field as a hidden field and should not be part of the export
if ((columnDef.width === undefined || columnDef.width > 0) && !skippedField) {
columnStyles.push({
bestFit: true,
width: columnDef.exportColumnWidth || (this._gridOptions && this._gridOptions.excelExportOptions && this._gridOptions.excelExportOptions.customColumnWidth) || 10
});
}
});
return columnStyles;
}

/** Get all column headers and format them in Bold */
private getColumnHeaderData(columns: Column[], metadata: ExcelMetadata): string[] | ExcelCellFormat[] {
let outputHeaderTitles: ExcelCellFormat[] = [];
Expand Down Expand Up @@ -420,9 +452,13 @@ export class ExcelExportService {

const skippedField = columnDef.excludeFromExport || false;

// if there's a groupTotalsFormatter, we will re-run it to get the exact same output as what is shown in UI
if (columnDef.groupTotalsFormatter) {
itemData = columnDef.groupTotalsFormatter(itemObj, columnDef);
// if there's a exportCustomGroupTotalsFormatter or groupTotalsFormatter, we will re-run it to get the exact same output as what is shown in UI
if (columnDef.exportCustomGroupTotalsFormatter) {
itemData = columnDef.exportCustomGroupTotalsFormatter(itemObj, columnDef);
} else {
if (columnDef.groupTotalsFormatter) {
itemData = columnDef.groupTotalsFormatter(itemObj, columnDef);
}
}

// does the user want to sanitize the output data (remove HTML tags)?
Expand Down

0 comments on commit d87ce2c

Please sign in to comment.