diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example12.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example12.ts
index 614916cf7..1346adb99 100644
--- a/examples/webpack-demo-vanilla-bundle/src/examples/example12.ts
+++ b/examples/webpack-demo-vanilla-bundle/src/examples/example12.ts
@@ -220,8 +220,8 @@ export class Example12 {
id: 'complexity', name: 'Complexity', field: 'complexity', minWidth: 100,
type: FieldType.number,
sortable: true, filterable: true, columnGroup: 'Analysis',
- formatter: (_row, _cell, value) => this.complexityLevelList[value].label,
- exportCustomFormatter: (_row, _cell, value) => this.complexityLevelList[value].label,
+ formatter: (_row, _cell, value) => this.complexityLevelList[value]?.label,
+ exportCustomFormatter: (_row, _cell, value) => this.complexityLevelList[value]?.label,
filter: {
model: Filters.multipleSelect,
collection: this.complexityLevelList
diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example16.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example16.ts
index 2349265c5..69d442139 100644
--- a/examples/webpack-demo-vanilla-bundle/src/examples/example16.ts
+++ b/examples/webpack-demo-vanilla-bundle/src/examples/example16.ts
@@ -128,16 +128,22 @@ export class Example16 {
},
},
{
- id: 'cost', name: 'Cost', field: 'cost',
+ id: 'cost', name: 'Cost (in €)', field: 'cost',
width: 90,
sortable: true,
filterable: true,
- exportWithFormatter: false,
+ exportWithFormatter: true,
// filter: { model: Filters.compoundInput },
- // formatter: Formatters.dollar,
+ // formatter: Formatters.currency,
formatter: Formatters.multiple,
- // params: { formatters: [Formatters.dollar, (row, cell, value) => `${value || ''}`] },
- params: { formatters: [Formatters.dollar, (row, cell, value) => `${value || ''}`] },
+ // params: { formatters: [Formatters.currency, (row, cell, value) => `${value || ''}`] },
+ params: {
+ formatters: [
+ Formatters.currency,
+ (row, cell, value) => `${value || ''}`
+ ],
+ currencySuffix: ' €'
+ },
customTooltip: {
useRegularTooltip: true,
useRegularTooltipFromFormatterOnly: true,
@@ -328,6 +334,10 @@ export class Example16 {
textExportOptions: {
exportWithFormatter: true
},
+ formatterOptions: {
+ // decimalSeparator: ',',
+ thousandSeparator: ' '
+ },
// Custom Tooltip options can be defined in a Column or Grid Options or a mixed of both (first options found wins)
registerExternalResources: [new SlickCustomTooltip(), new ExcelExportService(), new TextExportService()],
customTooltip: {
@@ -393,7 +403,7 @@ export class Example16 {
percentComplete: Math.floor(Math.random() * (100 - 5 + 1) + 5),
start: new Date(randomYear, randomMonth, randomDay),
finish: randomFinish < new Date() ? '' : randomFinish, // make sure the random date is earlier than today
- cost: (i % 33 === 0) ? null : Math.round(Math.random() * 10000) / 100,
+ cost: (i % 33 === 0) ? null : Math.round(Math.random() * 1000000) / 100,
effortDriven: (i % 5 === 0),
prerequisites: (i % 2 === 0) && i !== 0 && i < 50 ? [i, i - 1] : [],
};
diff --git a/package.json b/package.json
index 2f6e83823..ab02b3d07 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
"cypress:ci": "cypress run --config-file test/cypress.config.ts",
"predev": "pnpm run -r build:incremental && pnpm run -r sass:copy",
"dev": "run-p dev:watch webpack:watch",
- "dev:watch": "lerna watch --file-delimiter=\",\" --glob=\"src/**/*.{ts,scss}\" --ignored=\"src/**/*.spec.ts\" -- cross-env-shell pnpm run -r --filter $LERNA_PACKAGE_NAME dev",
+ "dev:watch": "lerna watch --no-bail --file-delimiter=\",\" --glob=\"src/**/*.{ts,scss}\" --ignored=\"src/**/*.spec.ts\" -- cross-env-shell pnpm run -r --filter $LERNA_PACKAGE_NAME dev",
"webpack:watch": "pnpm -r --parallel run webpack:dev",
"preview:publish": "lerna publish from-package --dry-run",
"preview:version": "lerna version --dry-run",
diff --git a/packages/common/src/formatters/__tests__/formatterUtilities.spec.ts b/packages/common/src/formatters/__tests__/formatterUtilities.spec.ts
index 1caad128f..e3d39ec08 100644
--- a/packages/common/src/formatters/__tests__/formatterUtilities.spec.ts
+++ b/packages/common/src/formatters/__tests__/formatterUtilities.spec.ts
@@ -59,27 +59,23 @@ describe('formatterUtilities', () => {
describe('getValueFromParamsOrGridOptions method', () => {
it('should return options found in the Grid Option when not found in Column Definition "params" property', () => {
const gridOptions = { formatterOptions: { minDecimal: 2 } } as GridOption;
- const gridSpy = (gridStub.getOptions as jest.Mock).mockReturnValue(gridOptions);
- const output = getValueFromParamsOrFormatterOptions('minDecimal', {} as Column, gridStub, -1);
+ const output = getValueFromParamsOrFormatterOptions('minDecimal', {} as Column, gridOptions, -1);
- expect(gridSpy).toHaveBeenCalled();
expect(output).toBe(2);
});
it('should return options found in the Column Definition "params" even if exist in the Grid Option as well', () => {
const gridOptions = { formatterOptions: { minDecimal: 2 } } as GridOption;
- const gridSpy = (gridStub.getOptions as jest.Mock).mockReturnValue(gridOptions);
- const output = getValueFromParamsOrFormatterOptions('minDecimal', { params: { minDecimal: 3 } } as Column, gridStub, -1);
+ const output = getValueFromParamsOrFormatterOptions('minDecimal', { params: { minDecimal: 3 } } as Column, gridOptions, -1);
- expect(gridSpy).toHaveBeenCalled();
expect(output).toBe(3);
});
it('should return default value when not found in "params" (columnDef) neither the "formatterOptions" (gridOption)', () => {
const defaultValue = 5;
- const output = getValueFromParamsOrFormatterOptions('minDecimal', { field: 'column1' } as Column, {} as unknown as SlickGrid, defaultValue);
+ const output = getValueFromParamsOrFormatterOptions('minDecimal', { field: 'column1' } as Column, {} as unknown as GridOption, defaultValue);
expect(output).toBe(defaultValue);
});
});
diff --git a/packages/common/src/formatters/formatterUtilities.ts b/packages/common/src/formatters/formatterUtilities.ts
index 41846d1a8..aa19cb15e 100644
--- a/packages/common/src/formatters/formatterUtilities.ts
+++ b/packages/common/src/formatters/formatterUtilities.ts
@@ -59,17 +59,18 @@ export function retrieveFormatterOptions(columnDef: Column, grid: SlickGrid, num
default:
break;
}
- const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, defaultMinDecimal);
- const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, defaultMaxDecimal);
- const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, Constants.DEFAULT_NUMBER_DECIMAL_SEPARATOR);
- const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, Constants.DEFAULT_NUMBER_THOUSAND_SEPARATOR);
- const wrapNegativeNumber = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, Constants.DEFAULT_NEGATIVE_NUMBER_WRAPPED_IN_BRAQUET);
- const currencyPrefix = getValueFromParamsOrFormatterOptions('currencyPrefix', columnDef, grid, '');
- const currencySuffix = getValueFromParamsOrFormatterOptions('currencySuffix', columnDef, grid, '');
+ const gridOptions = ((grid && typeof grid.getOptions === 'function') ? grid.getOptions() : {}) as GridOption;
+ const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, gridOptions, defaultMinDecimal);
+ const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, gridOptions, defaultMaxDecimal);
+ const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, gridOptions, Constants.DEFAULT_NUMBER_DECIMAL_SEPARATOR);
+ const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, gridOptions, Constants.DEFAULT_NUMBER_THOUSAND_SEPARATOR);
+ const wrapNegativeNumber = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, gridOptions, Constants.DEFAULT_NEGATIVE_NUMBER_WRAPPED_IN_BRAQUET);
+ const currencyPrefix = getValueFromParamsOrFormatterOptions('currencyPrefix', columnDef, gridOptions, '');
+ const currencySuffix = getValueFromParamsOrFormatterOptions('currencySuffix', columnDef, gridOptions, '');
if (formatterType === 'cell') {
- numberPrefix = getValueFromParamsOrFormatterOptions('numberPrefix', columnDef, grid, '');
- numberSuffix = getValueFromParamsOrFormatterOptions('numberSuffix', columnDef, grid, '');
+ numberPrefix = getValueFromParamsOrFormatterOptions('numberPrefix', columnDef, gridOptions, '');
+ numberSuffix = getValueFromParamsOrFormatterOptions('numberSuffix', columnDef, gridOptions, '');
}
return { minDecimal, maxDecimal, decimalSeparator, thousandSeparator, wrapNegativeNumber, currencyPrefix, currencySuffix, numberPrefix, numberSuffix };
@@ -81,8 +82,7 @@ export function retrieveFormatterOptions(columnDef: Column, grid: SlickGrid, num
* 2- Grid Options "formatterOptions"
* 3- nothing found, return default value provided
*/
-export function getValueFromParamsOrFormatterOptions(optionName: string, columnDef: Column, grid: SlickGrid, defaultValue?: any) {
- const gridOptions = ((grid && typeof grid.getOptions === 'function') ? grid.getOptions() : {}) as GridOption;
+export function getValueFromParamsOrFormatterOptions(optionName: string, columnDef: Column, gridOptions: GridOption, defaultValue?: any) {
const params = columnDef && columnDef.params;
if (params && params.hasOwnProperty(optionName)) {
diff --git a/packages/common/src/global-grid-options.ts b/packages/common/src/global-grid-options.ts
index 977be6af4..a46befc88 100644
--- a/packages/common/src/global-grid-options.ts
+++ b/packages/common/src/global-grid-options.ts
@@ -157,7 +157,7 @@ export const GlobalGridOptions: GridOption = {
groupCollapsedSymbol: '⮞',
groupExpandedSymbol: '⮟',
groupingAggregatorRowText: '',
- sanitizeDataExport: false,
+ sanitizeDataExport: true,
},
textExportOptions: {
delimiter: DelimiterType.comma,
@@ -166,7 +166,7 @@ export const GlobalGridOptions: GridOption = {
format: FileType.csv,
groupingColumnHeaderTitle: 'Group By',
groupingAggregatorRowText: '',
- sanitizeDataExport: false,
+ sanitizeDataExport: true,
useUtf8WithBom: true
},
gridAutosizeColsMode: GridAutosizeColsMode.none,
diff --git a/packages/common/src/interfaces/columnExcelExportOption.interface.ts b/packages/common/src/interfaces/columnExcelExportOption.interface.ts
index 7e6cb4991..7aabf0946 100644
--- a/packages/common/src/interfaces/columnExcelExportOption.interface.ts
+++ b/packages/common/src/interfaces/columnExcelExportOption.interface.ts
@@ -1,5 +1,6 @@
import { Column } from './column.interface';
import { ExcelCellFormat } from './excelCellFormat.interface';
+import { GridOption } from './gridOption.interface';
/** Excel custom export options (formatting & width) that can be applied to a column */
export interface ColumnExcelExportOption {
@@ -27,7 +28,7 @@ export interface GroupTotalExportOption {
valueParserCallback?: GetGroupTotalValueCallback;
}
-export type GetDataValueCallback = (data: Date | string | number, columnDef: Column, excelFormatterId: number | undefined, excelStylesheet: unknown) => Date | string | number | ExcelCellFormat;
+export type GetDataValueCallback = (data: Date | string | number, columnDef: Column, excelFormatterId: number | undefined, excelStylesheet: unknown, gridOptions: GridOption) => Date | string | number | ExcelCellFormat;
export type GetGroupTotalValueCallback = (totals: any, columnDef: Column, groupType: string, excelStylesheet: unknown) => Date | string | number;
/**
diff --git a/packages/excel-export/src/excelExport.service.spec.ts b/packages/excel-export/src/excelExport.service.spec.ts
index eb8b42f57..5d8d5be8e 100644
--- a/packages/excel-export/src/excelExport.service.spec.ts
+++ b/packages/excel-export/src/excelExport.service.spec.ts
@@ -819,13 +819,14 @@ describe('ExcelExportService', () => {
let mockItem1;
let mockItem2;
let mockGroup1;
- let parserCallbackSpy = jest.fn();
- let groupTotalParserCallbackSpy = jest.fn();
+ const parserCallbackSpy = jest.fn();
+ const groupTotalParserCallbackSpy = jest.fn();
beforeEach(() => {
mockGridOptions.enableGrouping = true;
mockGridOptions.enableTranslate = false;
mockGridOptions.excelExportOptions = { sanitizeDataExport: true, addGroupIndentation: true };
+ mockGridOptions.formatterOptions = { decimalSeparator: ',' };
mockColumns = [
{ id: 'id', field: 'id', excludeFromExport: true },
@@ -863,7 +864,7 @@ describe('ExcelExportService', () => {
};
mockItem1 = { id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10, cost: 22 };
- mockItem2 = { id: 1, userId: '2B02', firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 10, cost: 33 };
+ mockItem2 = { id: 1, userId: '2B02', firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 10, cost: '$33,01' };
mockGroup1 = {
collapsed: 0, count: 2, groupingKey: '10', groups: null, level: 0, selectChecked: false,
rows: [mockItem1, mockItem2],
@@ -908,8 +909,8 @@ describe('ExcelExportService', () => {
{ metadata: { style: 1, }, value: 'Cost', },
],
['⮟ Order: 20 (2 items)'],
- ['', '1E06', 'John', 'X', 'SALES_REP', { metadata: { style: 3, type: "number", }, value: 10, }, 8888],
- ['', '2B02', 'Jane', 'DOE', 'FINANCE_MANAGER', { metadata: { style: 3, type: "number", }, value: 10, }, 8888],
+ ['', '1E06', 'John', 'X', 'SALES_REP', { metadata: { style: 3, type: 'number', }, value: 10, }, 8888],
+ ['', '2B02', 'Jane', 'DOE', 'FINANCE_MANAGER', { metadata: { style: 3, type: 'number', }, value: 10, }, 8888],
['', '', '', '', '', { value: 20, metadata: { style: 5, type: 'number' } }, ''],
]
});
@@ -921,7 +922,7 @@ describe('ExcelExportService', () => {
numFmtId: 103,
}
});
- expect(parserCallbackSpy).toHaveBeenCalledWith(22, mockColumns[6], undefined, expect.anything());
+ expect(parserCallbackSpy).toHaveBeenCalledWith(22, mockColumns[6], undefined, expect.anything(), mockGridOptions);
});
});
@@ -1028,7 +1029,7 @@ describe('ExcelExportService', () => {
let mockGroup2;
let mockGroup3;
let mockGroup4;
- let groupTotalParserCallbackSpy = jest.fn();
+ const groupTotalParserCallbackSpy = jest.fn();
beforeEach(() => {
mockGridOptions.enableGrouping = true;
diff --git a/packages/excel-export/src/excelExport.service.ts b/packages/excel-export/src/excelExport.service.ts
index 54babc94a..bd0fe44a7 100644
--- a/packages/excel-export/src/excelExport.service.ts
+++ b/packages/excel-export/src/excelExport.service.ts
@@ -239,7 +239,7 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ
* All other browsers will use plain javascript on client side to produce a file download.
* @param options
*/
- startDownloadFile(options: { filename: string, blob: Blob, data: any[] }) {
+ startDownloadFile(options: { filename: string, blob: Blob, data: any[]; }) {
// when using IE/Edge, then use different download call
if (typeof (navigator as any).msSaveOrOpenBlob === 'function') {
(navigator as any).msSaveOrOpenBlob(options.blob, options.filename);
@@ -583,14 +583,15 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ
}
this._regularCellExcelFormats[columnDef.id] = cellStyleFormat;
}
- const { stylesheetFormatterId, getDataValueParser } = this._regularCellExcelFormats[columnDef.id];
- itemData = getDataValueParser(itemData, columnDef, stylesheetFormatterId, this._stylesheet);
- // does the user want to sanitize the output data (remove HTML tags)?
+ // sanitize early, when enabled, any HTML tags (remove HTML tags)
if (typeof itemData === 'string' && (columnDef.sanitizeDataExport || this._excelExportOptions.sanitizeDataExport)) {
itemData = sanitizeHtmlToText(itemData as string);
}
+ const { stylesheetFormatterId, getDataValueParser } = this._regularCellExcelFormats[columnDef.id];
+ itemData = getDataValueParser(itemData, columnDef, stylesheetFormatterId, this._stylesheet, this._gridOptions);
+
rowOutputStrings.push(itemData);
idx++;
}
diff --git a/packages/excel-export/src/excelUtils.spec.ts b/packages/excel-export/src/excelUtils.spec.ts
index c938cbe35..482fe52e6 100644
--- a/packages/excel-export/src/excelUtils.spec.ts
+++ b/packages/excel-export/src/excelUtils.spec.ts
@@ -1,6 +1,6 @@
import { Column, ExcelStylesheet, FieldType, Formatters, GridOption, GroupTotalFormatters, SlickGrid } from '@slickgrid-universal/common';
-import { getExcelFormatFromGridFormatter, getNumericFormatterOptions, isColumnDateType, useCellFormatByFieldType } from './excelUtils';
+import { getExcelFormatFromGridFormatter, getExcelNumberCallback, getNumericFormatterOptions, useCellFormatByFieldType } from './excelUtils';
const mockGridOptions = {
enableExcelExport: true,
@@ -20,7 +20,7 @@ const stylesheetStub = {
} as unknown as ExcelStylesheet;
describe('excelUtils', () => {
- let mockedFormatId = 135;
+ const mockedFormatId = 135;
let createFormatSpy: any;
beforeEach(() => {
@@ -31,6 +31,32 @@ describe('excelUtils', () => {
jest.clearAllMocks();
});
+ describe('getExcelNumberCallback() method', () => {
+ it('should return same data when input not a number', () => {
+ const output = getExcelNumberCallback('something else', {} as Column, 3, {}, mockGridOptions);
+ expect(output).toEqual({ metadata: { style: 3 }, value: 'something else' });
+ });
+
+ it('should return same data when input value is already a number', () => {
+ const output = getExcelNumberCallback(9.33, {} as Column, 3, {}, mockGridOptions);
+ expect(output).toEqual({ metadata: { style: 3 }, value: 9.33 });
+ });
+
+ it('should return parsed number when input value can be parsed to a number', () => {
+ const output = getExcelNumberCallback('$1,209.33', {} as Column, 3, {}, mockGridOptions);
+ expect(output).toEqual({ metadata: { style: 3 }, value: 1209.33 });
+ });
+
+ it('should be able to provide a number with different decimal separator as formatter options and return parsed number when input value can be parsed to a number', () => {
+ const output = getExcelNumberCallback(
+ '1 244 209,33€', {} as Column, 3, {},
+ {
+ ...mockGridOptions, formatterOptions: { decimalSeparator: ',', thousandSeparator: ' ' }
+ });
+ expect(output).toEqual({ metadata: { style: 3 }, value: 1244209.33 });
+ });
+ });
+
describe('decimal formatter', () => {
afterEach(() => {
jest.clearAllMocks();
@@ -341,6 +367,26 @@ describe('excelUtils', () => {
});
});
+ it('should get formatter options for Formatters.dollarColoredBold when using Formatters.multiple and 1 of its formatter is dollarColoredBold formatter', () => {
+ const column = {
+ type: FieldType.number, formatter: Formatters.multiple,
+ params: { formatters: [Formatters.dollarColoredBold, Formatters.bold], displayNegativeNumberWithParentheses: true, thousandSeparator: ',' }
+ } as Column;
+ const output = getNumericFormatterOptions(column, gridStub, 'cell');
+
+ expect(output).toEqual({
+ currencyPrefix: '',
+ currencySuffix: '',
+ decimalSeparator: '.',
+ maxDecimal: 4,
+ minDecimal: 2,
+ numberPrefix: '',
+ numberSuffix: '',
+ thousandSeparator: ',',
+ wrapNegativeNumber: true,
+ });
+ });
+
it('should get formatter options for Formatters.dollarColored', () => {
const column = {
type: FieldType.number, formatter: Formatters.dollarColored,
@@ -401,6 +447,26 @@ describe('excelUtils', () => {
});
});
+ it('should get formatter options for Formatters.percent when using Formatters.multiple and 1 of its formatter is percent formatter', () => {
+ const column = {
+ type: FieldType.number, formatter: Formatters.multiple,
+ params: { formatters: [Formatters.percent, Formatters.bold], displayNegativeNumberWithParentheses: true, thousandSeparator: ',' }
+ } as Column;
+ const output = getNumericFormatterOptions(column, gridStub, 'cell');
+
+ expect(output).toEqual({
+ currencyPrefix: '',
+ currencySuffix: '',
+ decimalSeparator: '.',
+ maxDecimal: undefined,
+ minDecimal: undefined,
+ numberPrefix: '',
+ numberSuffix: '',
+ thousandSeparator: ',',
+ wrapNegativeNumber: true,
+ });
+ });
+
it('should get formatter options for Formatters.percentComplete', () => {
const column = {
type: FieldType.number, formatter: Formatters.percentComplete,
@@ -611,7 +677,8 @@ describe('excelUtils', () => {
it('should get excel excel metadata style with regular number format when a custom GroupTotalFormatters is provided', () => {
const columnDef = {
type: FieldType.number, formatter: Formatters.decimal,
- groupTotalsFormatter: (totals: any, columnDef: Column, grid: SlickGrid) => `Some Total: ${totals.sum}`,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ groupTotalsFormatter: (totals: any, _columnDef: Column, _grid: SlickGrid) => `Some Total: ${totals.sum}`,
} as Column;
const output = getExcelFormatFromGridFormatter(stylesheetStub, { numberFormatter: { id: 3 } }, columnDef, gridStub, 'group');
@@ -716,6 +783,39 @@ describe('excelUtils', () => {
expect(output).toEqual({ groupType: '', stylesheetFormatter: { id: 3 } });
});
+
+ it('should get excel excel metadata style with regular number format when using Formatters.multiple and a custom Formatter is provided', () => {
+ const columnDef = {
+ type: FieldType.number,
+ formatter: Formatters.multiple,
+ params: { formatters: [() => `Something rendered`, Formatters.bold], },
+ } as unknown as Column;
+ const output = getExcelFormatFromGridFormatter(stylesheetStub, { numberFormatter: { id: 3 } }, columnDef, gridStub, 'cell');
+
+ expect(output).toEqual({ groupType: '', stylesheetFormatter: { id: 3 } });
+ });
+
+ it('should get excel excel metadata style format for Formatters.currency when using Formatters.multiple and the first multiple formatters is currency formatter', () => {
+ const column = {
+ type: FieldType.number,
+ formatter: Formatters.multiple,
+ params: { formatters: [Formatters.currency, Formatters.bold], displayNegativeNumberWithParentheses: false, thousandSeparator: ' ' }
+ } as Column;
+ const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'cell');
+
+ expect(output).toEqual({ groupType: '', stylesheetFormatter: { id: 135 } });
+ });
+
+ it('should get excel excel metadata style format for Formatters.dollar when using Formatters.multiple and the last formatter is dollar formatter', () => {
+ const column = {
+ type: FieldType.number,
+ formatter: Formatters.multiple,
+ params: { formatters: [Formatters.bold, Formatters.dollar], displayNegativeNumberWithParentheses: false, thousandSeparator: ' ' }
+ } as Column;
+ const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'cell');
+
+ expect(output).toEqual({ groupType: '', stylesheetFormatter: { id: 135 } });
+ });
});
});
});
\ No newline at end of file
diff --git a/packages/excel-export/src/excelUtils.ts b/packages/excel-export/src/excelUtils.ts
index db05fb9fe..f56589071 100644
--- a/packages/excel-export/src/excelUtils.ts
+++ b/packages/excel-export/src/excelUtils.ts
@@ -1,13 +1,16 @@
import {
Column,
+ Constants,
ExcelStylesheet,
FieldType,
+ Formatter,
Formatters,
FormatterType,
getColumnFieldType,
GetDataValueCallback,
+ getValueFromParamsOrFormatterOptions,
+ GridOption,
GroupTotalFormatters,
- isNumber,
retrieveFormatterOptions,
sanitizeHtmlToText,
SlickGrid,
@@ -17,11 +20,24 @@ export type ExcelFormatter = object & { id: number; };
// define all type of potential excel data function callbacks
export const getExcelSameInputDataCallback: GetDataValueCallback = (data) => data;
-export const getExcelNumberCallback: GetDataValueCallback = (data, _col, excelFormatterId) => ({
- value: isNumber(data) ? +data : data,
+export const getExcelNumberCallback: GetDataValueCallback = (data, column, excelFormatterId, _excelSheet, gridOptions) => ({
+ value: typeof data === 'string' && /\d/g.test(data) ? parseNumberWithFormatterOptions(data, column, gridOptions) : data,
metadata: { style: excelFormatterId }
});
+/** Parse a number which the user might have provided formatter options (for example a user might have provided { decimalSeparator: ',', thousandSeparator: ' '}) */
+export function parseNumberWithFormatterOptions(value: any, column: Column, gridOptions: GridOption) {
+ let outValue = value;
+ if (typeof value === 'string' && value) {
+ const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', column, gridOptions, Constants.DEFAULT_NUMBER_DECIMAL_SEPARATOR);
+ const val: number | string = (decimalSeparator === ',')
+ ? parseFloat(value.replace(/[^0-9\,]+/g, '').replace(',', '.'))
+ : parseFloat(value.replace(/[^\d\.]/g, ''));
+ outValue = isNaN(val) ? value : val;
+ }
+ return outValue;
+}
+
/** use different Excel Stylesheet Format as per the Field Type */
export function useCellFormatByFieldType(stylesheet: ExcelStylesheet, stylesheetFormatters: any, columnDef: Column, grid: SlickGrid) {
const fieldType = getColumnFieldType(columnDef);
@@ -71,28 +87,46 @@ export function getNumericFormatterOptions(columnDef: Column, grid: SlickGrid, f
break;
}
} else {
- switch (columnDef.formatter) {
- case Formatters.currency:
- case Formatters.dollar:
- case Formatters.dollarColored:
- case Formatters.dollarColoredBold:
- dataType = 'currency';
- break;
- case Formatters.percent:
- case Formatters.percentComplete:
- case Formatters.percentCompleteBar:
- case Formatters.percentCompleteBarWithText:
- case Formatters.percentSymbol:
- dataType = 'percent';
- break;
- case Formatters.decimal:
- default:
- // use "decimal" instead of "regular" to show optional decimals "##" in Excel
- dataType = 'decimal';
- break;
+ // when formatter is a Formatter.multiple, we need to loop through each of its formatter to find the best numeric data type
+ if (columnDef.formatter === Formatters.multiple && Array.isArray(columnDef.params?.formatters)) {
+ dataType = 'decimal';
+ for (const formatter of columnDef.params.formatters) {
+ dataType = getFormatterNumericDataType(formatter);
+ if (dataType !== 'decimal') {
+ break; // if we found something different than the default (decimal) then we can assume that we found our type so we can stop & return
+ }
+ }
+ } else {
+ dataType = getFormatterNumericDataType(columnDef.formatter);
}
}
- return retrieveFormatterOptions(columnDef, grid, dataType, formatterType);
+ return retrieveFormatterOptions(columnDef, grid, dataType!, formatterType);
+}
+
+export function getFormatterNumericDataType(formatter?: Formatter) {
+ let dataType: 'currency' | 'decimal' | 'percent' | 'regular';
+
+ switch (formatter) {
+ case Formatters.currency:
+ case Formatters.dollar:
+ case Formatters.dollarColored:
+ case Formatters.dollarColoredBold:
+ dataType = 'currency';
+ break;
+ case Formatters.percent:
+ case Formatters.percentComplete:
+ case Formatters.percentCompleteBar:
+ case Formatters.percentCompleteBarWithText:
+ case Formatters.percentSymbol:
+ dataType = 'percent';
+ break;
+ case Formatters.decimal:
+ default:
+ // use "decimal" instead of "regular" to show optional decimals "##" in Excel
+ dataType = 'decimal';
+ break;
+ }
+ return dataType;
}
export function getExcelFormatFromGridFormatter(stylesheet: ExcelStylesheet, stylesheetFormatters: any, columnDef: Column, grid: SlickGrid, formatterType: FormatterType) {
@@ -134,6 +168,21 @@ export function getExcelFormatFromGridFormatter(stylesheet: ExcelStylesheet, sty
switch (fieldType) {
case FieldType.number:
switch (columnDef.formatter) {
+ case Formatters.multiple:
+ // when formatter is a Formatter.multiple, we need to loop through each of its formatter to find the best possible Excel format
+ if (Array.isArray(columnDef.params?.formatters)) {
+ for (const formatter of columnDef.params.formatters) {
+ const { stylesheetFormatter: stylesheetFormatterResult } = getExcelFormatFromGridFormatter(stylesheet, stylesheetFormatters, { ...columnDef, formatter } as Column, grid, formatterType);
+ if (stylesheetFormatterResult !== stylesheetFormatters.numberFormatter) {
+ stylesheetFormatter = stylesheetFormatterResult;
+ break;
+ }
+ }
+ }
+ if (!stylesheetFormatter) {
+ stylesheetFormatter = stylesheetFormatters.numberFormatter;
+ }
+ break;
case Formatters.currency:
case Formatters.decimal:
case Formatters.dollar:
diff --git a/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts b/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts
index 974fc05e9..e9db5964c 100644
--- a/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts
+++ b/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts
@@ -34,13 +34,12 @@ export const SalesforceGlobalGridOptions = {
},
enableExcelExport: true,
excelExportOptions: {
+ exportWithFormatter: true,
mimeType: '', // Salesforce doesn't like Excel MIME type (not allowed), but we can bypass the problem by using no type at all
sanitizeDataExport: true
},
filterTypingDebounce: 250,
formatterOptions: {
- minDecimal: 0,
- maxDecimal: 2,
thousandSeparator: ','
},
frozenHeaderWidthCalcDifferential: 2,
diff --git a/test/cypress/e2e/example16.cy.ts b/test/cypress/e2e/example16.cy.ts
index a5a63c275..1e48b7af5 100644
--- a/test/cypress/e2e/example16.cy.ts
+++ b/test/cypress/e2e/example16.cy.ts
@@ -1,5 +1,5 @@
describe('Example 16 - Regular & Custom Tooltips', { retries: 1 }, () => {
- const titles = ['', 'Title', 'Duration', 'Description', 'Description 2', 'Cost', '% Complete', 'Start', 'Finish', 'Effort Driven', 'Prerequisites', 'Action'];
+ const titles = ['', 'Title', 'Duration', 'Description', 'Description 2', 'Cost (in €)', '% Complete', 'Start', 'Finish', 'Effort Driven', 'Prerequisites', 'Action'];
const GRID_ROW_HEIGHT = 33;
it('should display Example title', () => {