From 88c90825c8267b6ed1001cc5cbbb54dfa3199a80 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Sat, 10 Dec 2022 16:32:39 -0500 Subject: [PATCH] fix(export): create custom Excel cell format with Formatters.decimal - when using `Formatters.decimal`, the Excel export should automatically create custom format depending on decimal formatter options provided --- packages/common/src/enums/fieldType.enum.ts | 4 +- packages/excel-export/package.json | 1 + .../src/excelExport.service.spec.ts | 158 +++++---- .../excel-export/src/excelExport.service.ts | 89 +----- packages/excel-export/src/excelUtils.spec.ts | 302 ++++++++++++++++++ packages/excel-export/src/excelUtils.ts | 98 ++++++ pnpm-lock.yaml | 2 + 7 files changed, 512 insertions(+), 142 deletions(-) create mode 100644 packages/excel-export/src/excelUtils.spec.ts create mode 100644 packages/excel-export/src/excelUtils.ts diff --git a/packages/common/src/enums/fieldType.enum.ts b/packages/common/src/enums/fieldType.enum.ts index 04ad3b809..f5c0df72e 100644 --- a/packages/common/src/enums/fieldType.enum.ts +++ b/packages/common/src/enums/fieldType.enum.ts @@ -32,10 +32,10 @@ export const FieldType = { /** Format: 'YYYY-MM-DD HH:mm:ss' <=> 2001-02-28 14:01:01 */ dateTimeIso: 'dateTimeIso', - /** Format: 'YYYY-MM-DD h:mm:ss a' <=> 2001-02-28 11:01:01 pm */ + /** Format: 'YYYY-MM-DD hh:mm:ss a' <=> 2001-02-28 11:01:01 pm */ dateTimeIsoAmPm: 'dateTimeIsoAmPm', - /** Format: 'YYYY-MM-DD h:mm:ss A' <=> 2001-02-28 11:01:01 PM */ + /** Format: 'YYYY-MM-DD hh:mm:ss A' <=> 2001-02-28 11:01:01 PM */ dateTimeIsoAM_PM: 'dateTimeIsoAM_PM', /** Format: 'YYYY-MM-DD HH:mm' <=> 2001-02-28 14:01 */ diff --git a/packages/excel-export/package.json b/packages/excel-export/package.json index 50c5ac585..90e193ef9 100644 --- a/packages/excel-export/package.json +++ b/packages/excel-export/package.json @@ -48,6 +48,7 @@ "moment-mini": "^2.29.4" }, "devDependencies": { + "@slickgrid-universal/event-pub-sub": "workspace:~", "cross-env": "^7.0.3", "npm-run-all2": "^6.0.4", "rimraf": "^3.0.2" diff --git a/packages/excel-export/src/excelExport.service.spec.ts b/packages/excel-export/src/excelExport.service.spec.ts index ca1b1042b..192814398 100644 --- a/packages/excel-export/src/excelExport.service.spec.ts +++ b/packages/excel-export/src/excelExport.service.spec.ts @@ -1,4 +1,4 @@ -import moment = require('moment-mini'); +import moment from 'moment-mini'; import { BasePubSubService } from '@slickgrid-universal/event-pub-sub'; import { Column, @@ -19,6 +19,7 @@ import { import { ContainerServiceStub } from '../../../test/containerServiceStub'; import { TranslateServiceStub } from '../../../test/translateServiceStub'; import { ExcelExportService } from './excelExport.service'; +import { useCellFormatByFieldType } from './excelUtils'; const pubSubServiceStub = { publish: jest.fn(), @@ -89,7 +90,7 @@ describe('ExcelExportService', () => { mockGridOptions.translater = translateService; (navigator as any).__defineGetter__('appName', () => 'Netscape'); - navigator.msSaveOrOpenBlob = undefined as any; + (navigator as any).msSaveOrOpenBlob = undefined as any; mockExcelBlob = new Blob(['', ''], { type: `text/xlsx;charset=utf-8;` }); mockExportExcelOptions = { @@ -188,7 +189,7 @@ describe('ExcelExportService', () => { it('should call "msSaveOrOpenBlob" with a Blob and xlsx file when browser is IE11 when exporting as xlsx', async () => { const optionExpectation = { filename: 'export.xlsx', format: FileType.xlsx }; - navigator.msSaveOrOpenBlob = jest.fn(); + (navigator as any).msSaveOrOpenBlob = jest.fn(); const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); const spyMsSave = jest.spyOn(navigator, 'msSaveOrOpenBlob'); @@ -427,11 +428,11 @@ describe('ExcelExportService', () => { 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', }, + { metadata: { style: 4, }, value: 'User Id', }, + { metadata: { style: 4, }, value: 'FirstName', }, + { metadata: { style: 4, }, value: 'LastName', }, + { metadata: { style: 4, }, value: 'Position', }, + { metadata: { style: 4, }, value: 'Order', }, ], ['2B02', 'Jane', 'DOE', 'FINANCE_MANAGER', '1'], ] @@ -478,7 +479,7 @@ describe('ExcelExportService', () => { ...optionExpectation, blob: new Blob(), data: [ [ { value: '' }, - { metadata: { style: 5, }, value: 'My header that is long enough to wrap', } + { metadata: { style: 4, }, value: 'My header that is long enough to wrap', } ], [ { metadata: { style: 1, }, value: 'User Id', }, @@ -540,8 +541,8 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'StartDate', }, { metadata: { style: 1, }, value: 'EndDate', }, ], - ['1E06', 'John', 'Z', 'SALES_REP', { metadata: { style: 5, }, value: '2005-12-20' }, ''], - ['1E09', 'Jane', 'Doe', 'HUMAN_RESOURCES', { metadata: { style: 6, }, value: '2010-10-09' }, '2024-01-02'], + ['1E06', 'John', 'Z', 'SALES_REP', { metadata: { style: 4, }, value: '2005-12-20' }, ''], + ['1E09', 'Jane', 'Doe', 'HUMAN_RESOURCES', { metadata: { style: 4, }, value: '2010-10-09' }, '2024-01-02'], ] }); }); @@ -1026,254 +1027,277 @@ describe('ExcelExportService', () => { it('should return a date time format when using FieldType.dateTime and a Date object as input', async () => { const input = new Date('2012-02-28 15:07:59'); const expectedDate = '2012-02-28 15:07:59'; + const column = { type: FieldType.dateTime } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTime); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeIso', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '2012-02-28 15:07:59'; + const column = { type: FieldType.dateTimeIso } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeIso); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeShortIso', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '2012-02-28 15:07'; + const column = { type: FieldType.dateTimeShortIso } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeShortIso); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeIsoAmPm', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '2012-02-28 03:07:59 pm'; + const column = { type: FieldType.dateTimeIsoAmPm } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeIsoAmPm); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeIsoAM_PM', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '2012-02-28 03:07:59 PM'; + const column = { type: FieldType.dateTimeIsoAM_PM } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeIsoAM_PM); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateEuro', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '28/02/2012'; + const column = { type: FieldType.dateEuro } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateEuro); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateEuroShort', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '28/2/12'; + const column = { type: FieldType.dateEuroShort } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateEuroShort); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeEuro', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '28/02/2012 15:07:59'; + const column = { type: FieldType.dateTimeEuro } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeEuro); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeShortEuro', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '28/02/2012 15:07'; + const column = { type: FieldType.dateTimeShortEuro } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeShortEuro); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeEuroAmPm', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '28/02/2012 03:07:59 pm'; + const column = { type: FieldType.dateTimeEuroAmPm } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeEuroAmPm); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeEuroAM_PM', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '28/02/2012 03:07:59 PM'; + const column = { type: FieldType.dateTimeEuroAM_PM } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeEuroAM_PM); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeEuroShort', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '28/2/12 15:7:59'; + const column = { type: FieldType.dateTimeEuroShort } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeEuroShort); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeEuroShortAmPm', async () => { const input = '2012-02-28 15:07:59'; const expectedDate = '28/2/12 3:7:59 pm'; + const column = { type: FieldType.dateTimeEuroShortAmPm } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeEuroShortAmPm); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateUs', async () => { const input = new Date('2012-02-28 15:07:59'); const expectedDate = '02/28/2012'; + const column = { type: FieldType.dateUs } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateUs); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateUsShort', async () => { const input = new Date('2012-02-28 15:07:59'); const expectedDate = '2/28/12'; + const column = { type: FieldType.dateUsShort } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateUsShort); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeUs', async () => { const input = new Date('2012-02-28 15:07:59'); const expectedDate = '02/28/2012 15:07:59'; + const column = { type: FieldType.dateTimeUs } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeUs); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeShortUs', async () => { const input = new Date('2012-02-28 15:07:59'); const expectedDate = '02/28/2012 15:07'; + const column = { type: FieldType.dateTimeShortUs } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeShortUs); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeUsAmPm', async () => { const input = new Date('2012-02-28 15:07:59'); const expectedDate = '02/28/2012 03:07:59 pm'; + const column = { type: FieldType.dateTimeUsAmPm } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeUsAmPm); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeUsAM_PM', async () => { const input = new Date('2012-02-28 15:07:59'); const expectedDate = '02/28/2012 03:07:59 PM'; + const column = { type: FieldType.dateTimeUsAM_PM } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeUsAM_PM); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeUsShort', async () => { const input = new Date('2012-02-28 15:07:59'); const expectedDate = '2/28/12 15:7:59'; + const column = { type: FieldType.dateTimeUsShort } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeUsShort); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.dateTimeUsShortAmPm', async () => { const input = new Date('2012-02-28 15:07:59'); const expectedDate = '2/28/12 3:7:59 pm'; + const column = { type: FieldType.dateTimeUsShortAmPm } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateTimeUsShortAmPm); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); xit('should return a date time format when using FieldType.dateUtc', async () => { const input = moment('2013-05-23T17:55:00.325').utcOffset(420); // timezone that is +7 UTC hours const expectedDate = '2013-05-24T04:55:00.325+07:00'; + const column = { type: FieldType.dateUtc } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.dateUtc); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); it('should return a date time format when using FieldType.date', async () => { const input = new Date(Date.UTC(2012, 1, 28, 23, 1, 52, 103)); const expectedDate = '2012-02-28'; + const column = { type: FieldType.date } as Column; service.init(gridStub, container); await service.exportToExcel(mockExportExcelOptions); - const output = service.useCellFormatByFieldType(input, FieldType.date); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - expect(output).toEqual({ metadata: { style: 5 }, value: expectedDate }); + expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); }); }); @@ -1315,11 +1339,11 @@ describe('ExcelExportService', () => { expect(spyDownload).toHaveBeenCalledWith({ ...optionExpectation, blob: new Blob(), data: [ [ - { metadata: { style: 5, }, value: 'User Profile', }, - { metadata: { style: 5, }, value: 'User Profile', }, - { metadata: { style: 5, }, value: 'Company Profile', }, - { metadata: { style: 5, }, value: 'Company Profile', }, - { metadata: { style: 5, }, value: 'Sales', }, + { metadata: { style: 4, }, value: 'User Profile', }, + { metadata: { style: 4, }, value: 'User Profile', }, + { metadata: { style: 4, }, value: 'Company Profile', }, + { metadata: { style: 4, }, value: 'Company Profile', }, + { metadata: { style: 4, }, value: 'Sales', }, ], [ { metadata: { style: 1, }, value: 'FirstName', }, @@ -1374,11 +1398,11 @@ describe('ExcelExportService', () => { expect(spyDownload).toHaveBeenCalledWith({ ...optionExpectation, blob: new Blob(), data: [ [ - { metadata: { style: 5, }, value: 'User Profile', }, - { metadata: { style: 5, }, value: 'User Profile', }, - { metadata: { style: 5, }, value: 'Company Profile', }, - { metadata: { style: 5, }, value: 'Company Profile', }, - { metadata: { style: 5, }, value: 'Sales', }, + { metadata: { style: 4, }, value: 'User Profile', }, + { metadata: { style: 4, }, value: 'User Profile', }, + { metadata: { style: 4, }, value: 'Company Profile', }, + { metadata: { style: 4, }, value: 'Company Profile', }, + { metadata: { style: 4, }, value: 'Sales', }, ], [ { metadata: { style: 1, }, value: 'First Name', }, diff --git a/packages/excel-export/src/excelExport.service.ts b/packages/excel-export/src/excelExport.service.ts index d9708bcaa..157988d27 100644 --- a/packages/excel-export/src/excelExport.service.ts +++ b/packages/excel-export/src/excelExport.service.ts @@ -1,12 +1,9 @@ import * as ExcelBuilder from 'excel-builder-webpacker'; -import * as moment_ from 'moment-mini'; -const moment = (moment_ as any)['default'] || moment_; // patch to fix rollup "moment has no default export" issue, document here https://github.com/rollup/rollup/issues/670 import { // utility functions exportWithFormatterWhenDefined, getTranslationPrefix, - mapMomentDateFormatWithFieldType, sanitizeHtmlToText, // interfaces @@ -16,9 +13,6 @@ import { ExcelExportService as BaseExcelExportService, ExternalResource, FileType, - FieldType, - Formatter, - Formatters, GridOption, KeyTitlePair, Locale, @@ -38,6 +32,7 @@ import { ExcelWorkbook, ExcelWorksheet, } from './interfaces/index'; +import { useCellFormatByFieldType } from './excelUtils'; const DEFAULT_EXPORT_OPTIONS: ExcelExportOption = { filename: 'export', @@ -76,7 +71,15 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ /** Getter for the Grid Options pulled through the Grid Object */ protected get _gridOptions(): GridOption { - return (this._grid && this._grid.getOptions) ? this._grid.getOptions() : {}; + return this._grid?.getOptions() || {}; + } + + get stylesheet() { + return this._stylesheet; + } + + get stylesheetFormats() { + return this._stylesheetFormats; } dispose() { @@ -131,13 +134,12 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ const boldFormatter = this._stylesheet.createFormat({ font: { bold: true } }); const stringFormatter = this._stylesheet.createFormat({ format: '@' }); const numberFormatter = this._stylesheet.createFormat({ format: '0' }); - const usdFormatter = this._stylesheet.createFormat({ format: '$#,##0.00' }); this._stylesheetFormats = { boldFormatter, - dollarFormatter: usdFormatter, numberFormatter, stringFormatter, }; + this._sheet.setColumnFormats([boldFormatter]); // get the CSV output from the grid data const dataOutput = this.getDataOutput(); @@ -145,11 +147,11 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ // trigger a download file // wrap it into a setTimeout so that the EventAggregator has enough time to start a pre-process like showing a spinner setTimeout(async () => { - if (this._gridOptions && this._gridOptions.excelExportOptions && this._gridOptions.excelExportOptions.customExcelHeader) { + if (this._gridOptions?.excelExportOptions?.customExcelHeader) { this._gridOptions.excelExportOptions.customExcelHeader(this._workbook, this._sheet); } - const columns = this._grid && this._grid.getColumns && this._grid.getColumns() || []; + const columns = this._grid?.getColumns() || []; this._sheet.setColumns(this.getColumnStyles(columns)); const currentSheetData = this._sheet.data; @@ -234,74 +236,16 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ } } - /** use different Excel Stylesheet Format as per the Field Type */ - useCellFormatByFieldType(data: string | Date | moment_.Moment, fieldType: typeof FieldType[keyof typeof FieldType], formatter?: Formatter): ExcelCellFormat | string { - let outputData: ExcelCellFormat | string | Date | moment_.Moment = data; - switch (fieldType) { - case FieldType.dateTime: - case FieldType.dateTimeIso: - case FieldType.dateTimeShortIso: - case FieldType.dateTimeIsoAmPm: - case FieldType.dateTimeIsoAM_PM: - case FieldType.dateEuro: - case FieldType.dateEuroShort: - case FieldType.dateTimeEuro: - case FieldType.dateTimeShortEuro: - case FieldType.dateTimeEuroAmPm: - case FieldType.dateTimeEuroAM_PM: - case FieldType.dateTimeEuroShort: - case FieldType.dateTimeEuroShortAmPm: - case FieldType.dateUs: - case FieldType.dateUsShort: - case FieldType.dateTimeUs: - case FieldType.dateTimeShortUs: - case FieldType.dateTimeUsAmPm: - case FieldType.dateTimeUsAM_PM: - case FieldType.dateTimeUsShort: - case FieldType.dateTimeUsShortAmPm: - case FieldType.dateUtc: - case FieldType.date: - case FieldType.dateIso: - outputData = data; - if (data) { - const defaultDateFormat = mapMomentDateFormatWithFieldType(fieldType); - const isDateValid = moment(data as string, defaultDateFormat, false).isValid(); - const outputDate = (data && isDateValid) ? moment(data as string).format(defaultDateFormat) : data; - const dateFormatter = this._stylesheet.createFormat({ format: defaultDateFormat }); - outputData = { value: outputDate, metadata: { style: dateFormatter.id } }; - } - break; - case FieldType.number: - const num = parseFloat(data as string); - const val = isNaN(num) ? null : num; - - switch (formatter) { - case Formatters.dollar: - case Formatters.dollarColored: - case Formatters.dollarColoredBold: - outputData = { value: val, metadata: { style: this._stylesheetFormats.dollarFormatter.id} }; - break; - default: - outputData = { value: val, metadata: { style: this._stylesheetFormats.numberFormatter.id } }; - break; - } - break; - default: - outputData = data; - } - return outputData as string; - } - // ----------------------- // protected functions // ----------------------- protected getDataOutput(): Array { - const columns = this._grid && this._grid.getColumns && this._grid.getColumns() || []; + const columns = this._grid?.getColumns() || []; // data variable which will hold all the fields data of a row const outputData: Array = []; - const columnHeaderStyle = this._gridOptions && this._gridOptions.excelExportOptions && this._gridOptions.excelExportOptions.columnHeaderStyle; + const columnHeaderStyle = this._gridOptions?.excelExportOptions?.columnHeaderStyle; let columnHeaderStyleId = this._stylesheetFormats.boldFormatter.id; if (columnHeaderStyle) { columnHeaderStyleId = this._stylesheet.createFormat(columnHeaderStyle).id; @@ -528,7 +472,6 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ for (let col = 0; col < columnsLn; col++) { const columnDef = columns[col]; - const fieldType = columnDef.outputType || columnDef.type || FieldType.string; // skip excluded column if (columnDef.excludeFromExport) { @@ -595,7 +538,7 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ // use different Excel Stylesheet Format as per the Field Type if (!columnDef.exportWithFormatter) { - itemData = this.useCellFormatByFieldType(itemData as string, fieldType, columnDef.formatter); + itemData = useCellFormatByFieldType(this._stylesheet, this._stylesheetFormats, itemData as string, columnDef, this._grid); } rowOutputStrings.push(itemData); diff --git a/packages/excel-export/src/excelUtils.spec.ts b/packages/excel-export/src/excelUtils.spec.ts new file mode 100644 index 000000000..b98758f93 --- /dev/null +++ b/packages/excel-export/src/excelUtils.spec.ts @@ -0,0 +1,302 @@ +import { Column, ExcelStylesheet, FieldType, Formatters, GridOption, SlickGrid } from '@slickgrid-universal/common'; +import moment from 'moment-mini'; + +import { useCellFormatByFieldType } from './excelUtils'; + +const mockGridOptions = { + enableExcelExport: true, + enablePagination: true, + enableFiltering: true, +} as GridOption; + +const gridStub = { + getColumnIndex: jest.fn(), + getOptions: () => mockGridOptions, + getColumns: jest.fn(), + getGrouping: jest.fn(), +} as unknown as SlickGrid; + +const stylesheetStub = { + createFormat: jest.fn(), +} as unknown as ExcelStylesheet; + +describe('excelUtils', () => { + let mockedFormatId = 135; + let createFormatSpy: any; + + beforeEach(() => { + createFormatSpy = jest.spyOn(stylesheetStub, 'createFormat').mockReturnValue({ id: mockedFormatId }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('date formatter', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTime is provided', () => { + const column = { type: FieldType.dateTime } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD HH:mm:ss' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28 15:07:59' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeIso is provided', () => { + const column = { type: FieldType.dateTimeIso } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD HH:mm:ss' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28 15:07:59' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeShortIso is provided', () => { + const column = { type: FieldType.dateTimeShortIso } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD HH:mm' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28 15:07' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeIsoAmPm is provided', () => { + const column = { type: FieldType.dateTimeIsoAmPm } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD hh:mm:ss a' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28 03:07:59 pm' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeIsoAM_PM is provided', () => { + const column = { type: FieldType.dateTimeIsoAM_PM } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD hh:mm:ss A' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28 03:07:59 PM' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateEuro is provided', () => { + const column = { type: FieldType.dateEuro } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'DD/MM/YYYY' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/02/2012' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateEuroShort is provided', () => { + const column = { type: FieldType.dateEuroShort } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'D/M/YY' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/2/12' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeEuro is provided', () => { + const column = { type: FieldType.dateTimeEuro } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'DD/MM/YYYY HH:mm:ss' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/02/2012 15:07:59' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeShortEuro is provided', () => { + const column = { type: FieldType.dateTimeShortEuro } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'DD/MM/YYYY HH:mm' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/02/2012 15:07' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeEuroAmPm is provided', () => { + const column = { type: FieldType.dateTimeEuroAmPm } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'DD/MM/YYYY hh:mm:ss a' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/02/2012 03:07:59 pm' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeEuroAM_PM is provided', () => { + const column = { type: FieldType.dateTimeEuroAM_PM } as Column; + const input = '2012-02-28 15:07:59'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'DD/MM/YYYY hh:mm:ss A' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/02/2012 03:07:59 PM' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeEuroShort is provided', () => { + const column = { type: FieldType.dateTimeEuroShort } as Column; + const input = '2012-02-28 15:07:46'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'D/M/YY H:m:s' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/2/12 15:7:46' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeEuroShortAmPm is provided', () => { + const column = { type: FieldType.dateTimeEuroShortAmPm } as Column; + const input = '2012-02-28 15:07:46'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'D/M/YY h:m:s a' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/2/12 3:7:46 pm' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateUs is provided', () => { + const column = { type: FieldType.dateUs } as Column; + const input = new Date('2012-02-28 15:07:59'); + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'MM/DD/YYYY' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '02/28/2012' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateUsShort is provided', () => { + const column = { type: FieldType.dateUsShort } as Column; + const input = new Date('2012-02-28 15:07:59'); + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'M/D/YY' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2/28/12' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeUs is provided', () => { + const column = { type: FieldType.dateTimeUs } as Column; + const input = new Date('2012-02-28 15:07:59'); + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'MM/DD/YYYY HH:mm:ss' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '02/28/2012 15:07:59' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeShortUs is provided', () => { + const column = { type: FieldType.dateTimeShortUs } as Column; + const input = new Date('2012-02-28 15:07:59'); + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'MM/DD/YYYY HH:mm' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '02/28/2012 15:07' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeUsAmPm is provided', () => { + const column = { type: FieldType.dateTimeUsAmPm } as Column; + const input = new Date('2012-02-28 15:07:59'); + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'MM/DD/YYYY hh:mm:ss a' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '02/28/2012 03:07:59 pm' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeUsAM_PM is provided', () => { + const column = { type: FieldType.dateTimeUsAM_PM } as Column; + const input = new Date('2012-02-28 15:07:59'); + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'MM/DD/YYYY hh:mm:ss A' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '02/28/2012 03:07:59 PM' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeUsShort is provided', () => { + const column = { type: FieldType.dateTimeUsShort } as Column; + const input = new Date('2012-02-28 15:07:59'); + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'M/D/YY H:m:s' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2/28/12 15:7:59' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateTimeUsShortAmPm is provided', () => { + const column = { type: FieldType.dateTimeUsShortAmPm } as Column; + const input = new Date('2012-02-28 15:07:59'); + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'M/D/YY h:m:s a' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2/28/12 3:7:59 pm' }); + }); + + xit('should call createFormat with a format of an ISO date when FieldType.dateUtc is provided', () => { + const column = { type: FieldType.dateUtc } as Column; + const input = moment('2013-05-23T17:55:00.325').utcOffset(420); // timezone that is +7 UTC hours + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2013-05-24T04:55:00.325+07:00' }); + }); + + it('should call createFormat with a format of an ISO date when FieldType.dateIso is provided', () => { + const column = { type: FieldType.dateIso } as Column; + const input = '2012-02-28 15:07:46'; + const output = useCellFormatByFieldType(stylesheetStub, {}, input, column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28' }); + }); + }); + + describe('decimal formatter', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should call createFormat with a format of "###0.00" when a number is provided without any specific formatter options', () => { + const column = { type: FieldType.number, formatter: Formatters.decimal } as Column; + const output = useCellFormatByFieldType(stylesheetStub, {}, '12', column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: '###0.00' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + }); + + it('should call createFormat with a format of "###0.0##" when a number is provided minDecimal & maxDecimal formatter options', () => { + const column = { type: FieldType.number, formatter: Formatters.decimal, params: { minDecimal: 1, maxDecimal: 3 } } as Column; + const output = useCellFormatByFieldType(stylesheetStub, {}, '12', column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: '###0.0##' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + }); + + it('should call createFormat with a format of "€ ###0.00" when a number is provided minDecimal & maxDecimal formatter options', () => { + const column = { type: FieldType.number, formatter: Formatters.decimal, params: { numberPrefix: '€ ' } } as Column; + const output = useCellFormatByFieldType(stylesheetStub, {}, '12', column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: '€ ###0.00' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + }); + + it('should call createFormat with a format of "#,##0.00" when a number is provided minDecimal & maxDecimal formatter options', () => { + const column = { type: FieldType.number, formatter: Formatters.decimal, params: { thousandSeparator: ',' } } as Column; + const output = useCellFormatByFieldType(stylesheetStub, {}, '12', column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: '#,##0.00' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + }); + + it('should call createFormat with a format of "# ##0.00 USD" when a number is provided thousandSeparator & numberSuffix formatter options', () => { + const column = { type: FieldType.number, formatter: Formatters.decimal, params: { thousandSeparator: ' ', numberSuffix: ' USD' } } as Column; + const output = useCellFormatByFieldType(stylesheetStub, {}, '12', column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: '# ##0.00 USD' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + }); + + it('should call createFormat with a format of "#,##0.00 USD;(#,##0.00 USD)" when a number is provided displayNegativeNumberWithParentheses, thousandSeparator & numberSuffix formatter options', () => { + const column = { type: FieldType.number, formatter: Formatters.decimal, params: { displayNegativeNumberWithParentheses: true, thousandSeparator: ',', numberSuffix: ' USD' } } as Column; + const output = useCellFormatByFieldType(stylesheetStub, {}, '12', column, gridStub); + + expect(createFormatSpy).toHaveBeenCalledWith({ format: '#,##0.00 USD;(#,##0.00 USD)' }); + expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + }); + }); + +}); \ No newline at end of file diff --git a/packages/excel-export/src/excelUtils.ts b/packages/excel-export/src/excelUtils.ts new file mode 100644 index 000000000..2156a1338 --- /dev/null +++ b/packages/excel-export/src/excelUtils.ts @@ -0,0 +1,98 @@ +import { Column, ExcelCellFormat, ExcelStylesheet, FieldType, Formatters, getValueFromParamsOrFormatterOptions, mapMomentDateFormatWithFieldType, SlickGrid } from '@slickgrid-universal/common'; +import * as moment_ from 'moment-mini'; +const moment = (moment_ as any)['default'] || moment_; // patch to fix rollup "moment has no default export" issue, document here https://github.com/rollup/rollup/issues/670 + +/** use different Excel Stylesheet Format as per the Field Type */ +export function useCellFormatByFieldType(stylesheet: ExcelStylesheet, stylesheetFormatters: any, data: string | Date | moment_.Moment, columnDef: Column, grid: SlickGrid): ExcelCellFormat | string { + const fieldType = columnDef.outputType || columnDef.type || FieldType.string; + let outputData: ExcelCellFormat | string | Date | moment_.Moment = data; + + switch (fieldType) { + case FieldType.dateTime: + case FieldType.dateTimeIso: + case FieldType.dateTimeShortIso: + case FieldType.dateTimeIsoAmPm: + case FieldType.dateTimeIsoAM_PM: + case FieldType.dateEuro: + case FieldType.dateEuroShort: + case FieldType.dateTimeEuro: + case FieldType.dateTimeShortEuro: + case FieldType.dateTimeEuroAmPm: + case FieldType.dateTimeEuroAM_PM: + case FieldType.dateTimeEuroShort: + case FieldType.dateTimeEuroShortAmPm: + case FieldType.dateUs: + case FieldType.dateUsShort: + case FieldType.dateTimeUs: + case FieldType.dateTimeShortUs: + case FieldType.dateTimeUsAmPm: + case FieldType.dateTimeUsAM_PM: + case FieldType.dateTimeUsShort: + case FieldType.dateTimeUsShortAmPm: + case FieldType.dateUtc: + case FieldType.date: + case FieldType.dateIso: + outputData = data; + if (data) { + const defaultDateFormat = mapMomentDateFormatWithFieldType(fieldType); + const isDateValid = moment(data as string, defaultDateFormat, false).isValid(); + const outputDate = (data && isDateValid) ? moment(data as string).format(defaultDateFormat) : data; + if (!stylesheetFormatters.hasOwnProperty(fieldType)) { + stylesheetFormatters[fieldType] = stylesheet.createFormat({ format: defaultDateFormat }); + } + outputData = { value: outputDate, metadata: { style: stylesheetFormatters[fieldType].id } }; + } + break; + case FieldType.number: + const num = parseFloat(data as string); + const val = isNaN(num) ? null : +num; + let stylesheetFormatter: object & { id: string; }; + + switch (columnDef.formatter) { + case Formatters.decimal: + case Formatters.dollar: + case Formatters.dollarColored: + case Formatters.dollarColoredBold: + stylesheetFormatter = createOrGetStylesheetFormatter(stylesheet, stylesheetFormatters, columnDef, grid); + break; + default: + stylesheetFormatter = stylesheetFormatters.numberFormatter; + break; + } + outputData = { value: val, metadata: { style: stylesheetFormatter.id } }; + break; + default: + outputData = data; + } + return outputData as string; +} + +function createOrGetStylesheetFormatter(stylesheet: ExcelStylesheet, stylesheetFormatters: any, columnDef: Column, grid: SlickGrid) { + const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, 2); + const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, 2); + const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); + const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); + const numberPrefix = getValueFromParamsOrFormatterOptions('numberPrefix', columnDef, grid, ''); + const numberSuffix = getValueFromParamsOrFormatterOptions('numberSuffix', columnDef, grid, ''); + const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const numberFormat = `${numberPrefix}#${thousandSeparator}##0${decimalSeparator}${excelNumberFormatPadding(minDecimal, maxDecimal)}${numberSuffix}`; + const format = displayNegativeNumberWithParentheses ? `${numberFormat};(${numberFormat})` : numberFormat; + + if (!stylesheetFormatters.hasOwnProperty(format)) { + stylesheetFormatters[format] = stylesheet.createFormat({ format }); // save new formatter with its format as a prop key + } + return stylesheetFormatters[format]; +} + +/** Get number format for a number cell, for example { minDecimal: 2, maxDecimal: 5 } will return "00###" */ +function excelNumberFormatPadding(minDecimal: number, maxDecimal: number) { + return textPadding('0', minDecimal) + textPadding('#', maxDecimal - minDecimal); +} + +function textPadding(numberStr: string, count: number): string { + let output = ''; + for (let i = 0; i < count; i++) { + output += numberStr; + } + return output; +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80a08d8e7..6144178c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -293,6 +293,7 @@ importers: packages/excel-export: specifiers: '@slickgrid-universal/common': workspace:~ + '@slickgrid-universal/event-pub-sub': workspace:~ '@slickgrid-universal/utils': workspace:~ cross-env: ^7.0.3 excel-builder-webpacker: ^2.1.7 @@ -305,6 +306,7 @@ importers: excel-builder-webpacker: 2.1.7 moment-mini: 2.29.4 devDependencies: + '@slickgrid-universal/event-pub-sub': link:../event-pub-sub cross-env: 7.0.3 npm-run-all2: 6.0.4 rimraf: 3.0.2