diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example02.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example02.ts index 61da37bf3..32f0b2b91 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example02.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example02.ts @@ -117,22 +117,20 @@ export class Example2 { filter: { model: Filters.compoundDate }, sortable: true, type: FieldType.dateIso, + outputType: FieldType.dateIso, formatter: Formatters.dateIso, - exportWithFormatter: true }, { id: 'cost', name: 'Cost', field: 'cost', minWidth: 70, width: 80, - maxWidth: 120, filterable: true, filter: { model: Filters.compoundInputNumber }, type: FieldType.number, sortable: true, - exportWithFormatter: true, - formatter: Formatters.dollar, + formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollar, - params: { groupFormatterPrefix: 'Total: ' /* , groupFormatterSuffix: ' USD' */ } + params: { displayNegativeNumberWithParentheses: true, numberPrefix: '€ ', minDecimal: 2, maxDecimal: 4, groupFormatterPrefix: 'Total: ' /* , groupFormatterSuffix: ' USD' */ }, }, { id: 'effortDriven', name: 'Effort Driven', @@ -169,7 +167,7 @@ export class Example2 { onColumnsChanged: (e, args) => console.log(e, args) }, enableExcelExport: true, - excelExportOptions: { filename: 'my-export', sanitizeDataExport: true }, + excelExportOptions: { filename: 'my-export', sanitizeDataExport: true, exportWithExcelFormat: true, }, textExportOptions: { filename: 'my-export', sanitizeDataExport: true }, registerExternalResources: [this.excelExportService, new TextExportService()], showCustomFooter: true, // display some metrics in the bottom custom footer diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example07.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example07.ts index 003de9822..b8b23f671 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example07.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example07.ts @@ -234,7 +234,6 @@ export class Example7 { showCustomFooter: true, enableExcelExport: true, excelExportOptions: { - exportWithFormatter: true, sanitizeDataExport: true }, enableCellMenu: true, diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index b1be7bf53..882013673 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -78,6 +78,15 @@ export class Constants { TREE_LEVEL_PROP: '__treeLevel', PARENT_PROP: '__parentId', }; + static readonly DEFAULT_FORMATTER_NUMBER_MIN_DECIMAL = 2; + static readonly DEFAULT_FORMATTER_NUMBER_MAX_DECIMAL = 2; + static readonly DEFAULT_FORMATTER_DOLLAR_MIN_DECIMAL = 2; + static readonly DEFAULT_FORMATTER_DOLLAR_MAX_DECIMAL = 4; + static readonly DEFAULT_FORMATTER_PERCENT_MIN_DECIMAL = undefined; + static readonly DEFAULT_FORMATTER_PERCENT_MAX_DECIMAL = undefined; + static readonly DEFAULT_NUMBER_DECIMAL_SEPARATOR = '.'; + static readonly DEFAULT_NUMBER_THOUSAND_SEPARATOR = ''; + static readonly DEFAULT_NEGATIVE_NUMBER_WRAPPED_IN_BRAQUET = false; static readonly SLIDER_DEFAULT_MIN_VALUE = 0; static readonly SLIDER_DEFAULT_MAX_VALUE = 100; static readonly SLIDER_DEFAULT_STEP = 1; diff --git a/packages/common/src/formatters/decimalFormatter.ts b/packages/common/src/formatters/decimalFormatter.ts index c4b4f44fc..522944708 100644 --- a/packages/common/src/formatters/decimalFormatter.ts +++ b/packages/common/src/formatters/decimalFormatter.ts @@ -2,7 +2,7 @@ import { isNumber } from '@slickgrid-universal/utils'; import { Formatter } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from './formatterUtilities'; +import { retrieveFormatterOptions } from './formatterUtilities'; /** * Display the value as x decimals formatted, defaults to 2 decimals. @@ -10,16 +10,18 @@ import { getValueFromParamsOrFormatterOptions } from './formatterUtilities'; * For example:: `{ formatter: Formatters.decimal, params: { minDecimal: 2, maxDecimal: 4 }}` */ export const decimalFormatter: Formatter = (_row, _cell, value, columnDef, _dataContext, grid) => { - 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 { + minDecimal, + maxDecimal, + numberPrefix, + numberSuffix, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber, + } = retrieveFormatterOptions(columnDef, grid, 'decimal', 'cell'); if (isNumber(value)) { - return formatNumber(value, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, numberPrefix, numberSuffix, decimalSeparator, thousandSeparator); + return formatNumber(value, minDecimal, maxDecimal, wrapNegativeNumber, numberPrefix, numberSuffix, decimalSeparator, thousandSeparator); } return value; }; diff --git a/packages/common/src/formatters/dollarColoredBoldFormatter.ts b/packages/common/src/formatters/dollarColoredBoldFormatter.ts index 7ce22bf92..acf3ab90e 100644 --- a/packages/common/src/formatters/dollarColoredBoldFormatter.ts +++ b/packages/common/src/formatters/dollarColoredBoldFormatter.ts @@ -2,19 +2,21 @@ import { isNumber } from '@slickgrid-universal/utils'; import { Formatter } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from './formatterUtilities'; +import { retrieveFormatterOptions } from './formatterUtilities'; /** Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value, change color of text to red/green on negative/positive value, show it in bold font weight as well */ export const dollarColoredBoldFormatter: Formatter = (_row, _cell, value, columnDef, _dataContext, grid) => { - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, 2); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, 4); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber, + } = retrieveFormatterOptions(columnDef, grid, 'dollar', 'cell'); if (isNumber(value)) { const colorStyle = (value >= 0) ? 'green' : 'red'; - const formattedNumber = formatNumber(value, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '$', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(value, minDecimal, maxDecimal, wrapNegativeNumber, '$', '', decimalSeparator, thousandSeparator); return `${formattedNumber}`; } return value; diff --git a/packages/common/src/formatters/dollarColoredFormatter.ts b/packages/common/src/formatters/dollarColoredFormatter.ts index ad3799b27..d18ff6523 100644 --- a/packages/common/src/formatters/dollarColoredFormatter.ts +++ b/packages/common/src/formatters/dollarColoredFormatter.ts @@ -2,19 +2,21 @@ import { isNumber } from '@slickgrid-universal/utils'; import { Formatter } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from './formatterUtilities'; +import { retrieveFormatterOptions } from './formatterUtilities'; /** Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value, change color of text to red/green on negative/positive value */ export const dollarColoredFormatter: Formatter = (_row, _cell, value, columnDef, _dataContext, grid) => { - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, 2); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, 4); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber, + } = retrieveFormatterOptions(columnDef, grid, 'dollar', 'cell'); if (isNumber(value)) { const colorStyle = (value >= 0) ? 'green' : 'red'; - const formattedNumber = formatNumber(value, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '$', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(value, minDecimal, maxDecimal, wrapNegativeNumber, '$', '', decimalSeparator, thousandSeparator); return `${formattedNumber}`; } return value; diff --git a/packages/common/src/formatters/dollarFormatter.ts b/packages/common/src/formatters/dollarFormatter.ts index 1d0ac69c0..0b045d5d2 100644 --- a/packages/common/src/formatters/dollarFormatter.ts +++ b/packages/common/src/formatters/dollarFormatter.ts @@ -2,18 +2,20 @@ import { isNumber } from '@slickgrid-universal/utils'; import { Formatter } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from './formatterUtilities'; +import { retrieveFormatterOptions } from './formatterUtilities'; /** Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value */ export const dollarFormatter: Formatter = (_row, _cell, value, columnDef, _dataContext, grid) => { - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, 2); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, 4); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber, + } = retrieveFormatterOptions(columnDef, grid, 'dollar', 'cell'); if (isNumber(value)) { - return formatNumber(value, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '$', '', decimalSeparator, thousandSeparator); + return formatNumber(value, minDecimal, maxDecimal, wrapNegativeNumber, '$', '', decimalSeparator, thousandSeparator); } return value; }; diff --git a/packages/common/src/formatters/formatterUtilities.ts b/packages/common/src/formatters/formatterUtilities.ts index 9cc14defb..c1b0bc990 100644 --- a/packages/common/src/formatters/formatterUtilities.ts +++ b/packages/common/src/formatters/formatterUtilities.ts @@ -4,8 +4,12 @@ import { sanitizeHtmlToText } from '../services/domUtilities'; import { mapMomentDateFormatWithFieldType } from '../services/utilities'; import { multipleFormatter } from './multipleFormatter'; import * as moment_ from 'moment-mini'; +import { Constants } from '../constants'; 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 +export type FormatterType = 'group' | 'cell'; +export type NumberType = 'decimal' | 'dollar' | 'percent' | 'regular'; + /** * Automatically add a Custom Formatter on all column definitions that have an Editor. * Instead of manually adding a Custom Formatter on every column definitions that are editables, let's ask the system to do it in an easier automated way. @@ -33,6 +37,42 @@ export function autoAddEditorFormatterToColumnsWithEditor(columnDefinitions: Col } } +export function retrieveFormatterOptions(columnDef: Column, grid: SlickGrid, numberType: NumberType, formatterType: FormatterType) { + let defaultMinDecimal; + let defaultMaxDecimal; + let numberPrefix = ''; + let numberSuffix = ''; + + switch (numberType) { + case 'decimal': + defaultMinDecimal = Constants.DEFAULT_FORMATTER_NUMBER_MIN_DECIMAL; + defaultMaxDecimal = Constants.DEFAULT_FORMATTER_NUMBER_MAX_DECIMAL; + break; + case 'dollar': + defaultMinDecimal = Constants.DEFAULT_FORMATTER_DOLLAR_MIN_DECIMAL; + defaultMaxDecimal = Constants.DEFAULT_FORMATTER_DOLLAR_MAX_DECIMAL; + break; + case 'percent': + defaultMinDecimal = Constants.DEFAULT_FORMATTER_PERCENT_MIN_DECIMAL; + defaultMaxDecimal = Constants.DEFAULT_FORMATTER_PERCENT_MAX_DECIMAL; + break; + 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); + + if (formatterType === 'cell') { + numberPrefix = getValueFromParamsOrFormatterOptions('numberPrefix', columnDef, grid, ''); + numberSuffix = getValueFromParamsOrFormatterOptions('numberSuffix', columnDef, grid, ''); + } + + return { minDecimal, maxDecimal, decimalSeparator, thousandSeparator, wrapNegativeNumber, numberPrefix, numberSuffix }; +} + /** * Find the option value from the following (in order of execution) * 1- Column Definition "params" @@ -96,12 +136,12 @@ export function exportWithFormatterWhenDefined(row: number, col: number let isEvaluatingFormatter = false; // first check if there are any export options provided (as Grid Options) - if (exportOptions && exportOptions.hasOwnProperty('exportWithFormatter')) { + if (exportOptions?.hasOwnProperty('exportWithFormatter')) { isEvaluatingFormatter = !!exportOptions.exportWithFormatter; } // second check if "exportWithFormatter" is provided in the column definition, if so it will have precendence over the Grid Options exportOptions - if (columnDef && columnDef.hasOwnProperty('exportWithFormatter')) { + if (columnDef?.hasOwnProperty('exportWithFormatter')) { isEvaluatingFormatter = !!columnDef.exportWithFormatter; } diff --git a/packages/common/src/formatters/percentCompleteFormatter.ts b/packages/common/src/formatters/percentCompleteFormatter.ts index 6fbadbada..8497b6a58 100644 --- a/packages/common/src/formatters/percentCompleteFormatter.ts +++ b/packages/common/src/formatters/percentCompleteFormatter.ts @@ -2,19 +2,21 @@ import { isNumber } from '@slickgrid-universal/utils'; import { Formatter } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from './formatterUtilities'; +import { retrieveFormatterOptions } from './formatterUtilities'; /** Takes a cell value number (between 0.0-100) and displays a red (<50) or green (>=50) bar */ export const percentCompleteFormatter: Formatter = (_row, _cell, value, columnDef, _dataContext, grid) => { - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber, + } = retrieveFormatterOptions(columnDef, grid, 'percent', 'cell'); if (isNumber(value)) { const colorStyle = (value < 50) ? 'red' : 'green'; - const formattedNumber = formatNumber(value, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '', '%', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(value, minDecimal, maxDecimal, wrapNegativeNumber, '', '%', decimalSeparator, thousandSeparator); const outputFormattedValue = value > 100 ? '100%' : formattedNumber; return `${outputFormattedValue}`; } diff --git a/packages/common/src/formatters/percentFormatter.ts b/packages/common/src/formatters/percentFormatter.ts index b3cc6d29b..a2e44e8ef 100644 --- a/packages/common/src/formatters/percentFormatter.ts +++ b/packages/common/src/formatters/percentFormatter.ts @@ -2,19 +2,21 @@ import { isNumber } from '@slickgrid-universal/utils'; import { Formatter } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from './formatterUtilities'; +import { retrieveFormatterOptions } from './formatterUtilities'; /** Takes a cell value number (between 0.0-1.0) and displays a red (<50) or green (>=50) bar */ export const percentFormatter: Formatter = (_row, _cell, value, columnDef, _dataContext, grid) => { - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber, + } = retrieveFormatterOptions(columnDef, grid, 'percent', 'cell'); if (isNumber(value)) { const percentValue = value * 100; - return formatNumber(percentValue, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '', '%', decimalSeparator, thousandSeparator); + return formatNumber(percentValue, minDecimal, maxDecimal, wrapNegativeNumber, '', '%', decimalSeparator, thousandSeparator); } return value; }; diff --git a/packages/common/src/formatters/percentSymbolFormatter.ts b/packages/common/src/formatters/percentSymbolFormatter.ts index aba4ddd01..637e5cb43 100644 --- a/packages/common/src/formatters/percentSymbolFormatter.ts +++ b/packages/common/src/formatters/percentSymbolFormatter.ts @@ -2,18 +2,20 @@ import { isNumber } from '@slickgrid-universal/utils'; import { Formatter } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from './formatterUtilities'; +import { retrieveFormatterOptions } from './formatterUtilities'; /** Takes a cell value number (between 0-100) and add the "%" after the number */ export const percentSymbolFormatter: Formatter = (_row, _cell, value, columnDef, _dataContext, grid) => { - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber, + } = retrieveFormatterOptions(columnDef, grid, 'percent', 'cell'); if (isNumber(value)) { - return formatNumber(value, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '', '%', decimalSeparator, thousandSeparator); + return formatNumber(value, minDecimal, maxDecimal, wrapNegativeNumber, '', '%', decimalSeparator, thousandSeparator); } return value; }; diff --git a/packages/common/src/global-grid-options.ts b/packages/common/src/global-grid-options.ts index f8c50ab3c..348e185a6 100644 --- a/packages/common/src/global-grid-options.ts +++ b/packages/common/src/global-grid-options.ts @@ -146,6 +146,7 @@ export const GlobalGridOptions: GridOption = { excelExportOptions: { addGroupIndentation: true, exportWithFormatter: false, + exportWithExcelFormat: true, filename: 'export', format: FileType.xlsx, groupingColumnHeaderTitle: 'Group By', diff --git a/packages/common/src/grouping-formatters/__tests__/minTotalsFormatter.spec.ts b/packages/common/src/grouping-formatters/__tests__/minTotalsFormatter.spec.ts index caf44295c..0d6a09d6b 100644 --- a/packages/common/src/grouping-formatters/__tests__/minTotalsFormatter.spec.ts +++ b/packages/common/src/grouping-formatters/__tests__/minTotalsFormatter.spec.ts @@ -8,36 +8,36 @@ describe('minTotalsFormatter', () => { } as unknown as SlickGrid; it('should display an empty string when no value is provided', () => { - const output = minTotalsFormatter({}, {} as Column); + const output = minTotalsFormatter({}, {} as Column, {} as SlickGrid); expect(output).toBe(''); }); it('should display an empty string when the "min" does not find the field property in its object', () => { const columnDef = { id: 'column3', field: 'column3' } as Column; const totals = { min: { column1: 123, column2: 345 } }; - const output = minTotalsFormatter(totals, columnDef, {}); + const output = minTotalsFormatter(totals, columnDef, {} as SlickGrid); expect(output).toBe(''); }); it('should display an empty string when the minimum number is null', () => { const columnDef = { id: 'column1', field: 'column1' } as Column; const totals = { min: { column1: null } }; - const output = minTotalsFormatter(totals, columnDef, {}); + const output = minTotalsFormatter(totals, columnDef, {} as SlickGrid); expect(output).toBe(''); }); it('should display an empty string when the average input is not a number', () => { const columnDef = { id: 'column1', field: 'column1' } as Column; const totals = { min: { column1: 'abc' } }; - const output = minTotalsFormatter(totals, columnDef, {}); + const output = minTotalsFormatter(totals, columnDef, {} as SlickGrid); expect(output).toBe(''); }); it('should display a negative minimum when its input is negative', () => { const totals = { min: { column1: -123, column2: -34.5678, column3: -2.4 } }; - const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1' } as Column, {}); - const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2 } } as Column, {}); + const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1' } as Column, {} as SlickGrid); + const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2 } } as Column, {} as SlickGrid); expect(output1).toBe('-123'); expect(output2).toBe('-34.57'); @@ -46,9 +46,9 @@ describe('minTotalsFormatter', () => { it('should display a negative minimum and thousand separator when its input is negative', () => { const totals = { min: { column1: -12345678, column2: -345678.5678, column3: -2.4 } }; - const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { thousandSeparator: ',' } } as Column, {}); - const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, thousandSeparator: ',' } } as Column, {}); - const output3 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, decimalSeparator: ',', thousandSeparator: '_' } } as Column, {}); + const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { thousandSeparator: ',' } } as Column, {} as SlickGrid); + const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, thousandSeparator: ',' } } as Column, {} as SlickGrid); + const output3 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, decimalSeparator: ',', thousandSeparator: '_' } } as Column, {} as SlickGrid); expect(output1).toBe('-12,345,678'); expect(output2).toBe('-345,678.57'); @@ -58,8 +58,8 @@ describe('minTotalsFormatter', () => { it('should display a negative minimum with parentheses instead of the negative sign when its input is negative', () => { const totals = { min: { column1: -123, column2: -34.5678, column3: -2.4 } }; - const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true } } as Column, {}); - const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true } } as Column, {}); + const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true } } as Column, {} as SlickGrid); + const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true } } as Column, {} as SlickGrid); expect(output1).toBe('(123)'); expect(output2).toBe('(34.57)'); @@ -68,9 +68,9 @@ describe('minTotalsFormatter', () => { it('should display a negative minimum with thousand separator and parentheses instead of the negative sign when its input is negative', () => { const totals = { min: { column1: -12345678, column2: -345678.5678, column3: -2.4 } }; - const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true, thousandSeparator: ',' } } as Column, {}); - const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true, thousandSeparator: ',' } } as Column, {}); - const output3 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true, decimalSeparator: ',', thousandSeparator: '_' } } as Column, {}); + const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true, thousandSeparator: ',' } } as Column, {} as SlickGrid); + const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true, thousandSeparator: ',' } } as Column, {} as SlickGrid); + const output3 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true, decimalSeparator: ',', thousandSeparator: '_' } } as Column, {} as SlickGrid); expect(output1).toBe('(12,345,678)'); expect(output2).toBe('(345,678.57)'); @@ -88,8 +88,8 @@ describe('minTotalsFormatter', () => { it('should display a positive minimum number even when displayNegativeNumberWithParentheses is enabled', () => { const totals = { min: { column1: 123, column2: 34.5678, column3: 2.4 } }; - const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true } } as Column, {}); - const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true } } as Column, {}); + const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true } } as Column, {} as SlickGrid); + const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true } } as Column, {} as SlickGrid); expect(output1).toBe('123'); expect(output2).toBe('34.57'); @@ -98,8 +98,8 @@ describe('minTotalsFormatter', () => { it('should display the same minimum value when a number with decimals is provided', () => { const totals = { min: { column1: 123.55678, column2: 345.2, column3: -2.45 } }; - const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1' } as Column, {}); - const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2' } as Column, {}); + const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1' } as Column, {} as SlickGrid); + const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2' } as Column, {} as SlickGrid); expect(output1).toBe('123.55678'); expect(output2).toBe('345.2'); @@ -121,9 +121,9 @@ describe('minTotalsFormatter', () => { it('should display a minimum number with user defined minimum & maximum decimal count', () => { const totals = { min: { column1: 123.45678, column2: 345.2, column3: -2.45 } }; - const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2 } } as Column, {}); - const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0 } } as Column, {}); - const output3 = minTotalsFormatter(totals, { id: 'column3', field: 'column3', params: { minDecimal: 3, displayNegativeNumberWithParentheses: true } } as Column, {}); + const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2 } } as Column, {} as SlickGrid); + const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0 } } as Column, {} as SlickGrid); + const output3 = minTotalsFormatter(totals, { id: 'column3', field: 'column3', params: { minDecimal: 3, displayNegativeNumberWithParentheses: true } } as Column, {} as SlickGrid); expect(output1).toBe('123.46'); expect(output2).toBe('345.2'); @@ -133,14 +133,14 @@ describe('minTotalsFormatter', () => { it('should display a minimum number a prefix and suffix', () => { const totals = { min: { column1: 123.45678, column2: 345.2, column3: -2.45 } }; - const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2, groupFormatterPrefix: 'min: ' } } as Column, {}); - const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0, groupFormatterSuffix: ' (max)' } } as Column, {}); + const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2, groupFormatterPrefix: 'min: ' } } as Column, {} as SlickGrid); + const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0, groupFormatterSuffix: ' (max)' } } as Column, {} as SlickGrid); const output3 = minTotalsFormatter( totals, { id: 'column3', field: 'column3', params: { minDecimal: 3, displayNegativeNumberWithParentheses: true, groupFormatterPrefix: 'min: ', groupFormatterSuffix: '/item' } - } as Column + } as Column, {} as SlickGrid ); expect(output1).toBe('min: 123.46'); @@ -151,13 +151,13 @@ describe('minTotalsFormatter', () => { it('should display a sum number with prefix, suffix and thousand separator', () => { const totals = { min: { column1: 12345678.45678, column2: 345678.2, column3: -345678.45 } }; - const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2, groupFormatterPrefix: 'Min: ', decimalSeparator: ',', thousandSeparator: '_' } } as Column, {}); - const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0, groupFormatterSuffix: ' (min)', decimalSeparator: ',', thousandSeparator: '_' } } as Column, {}); + const output1 = minTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2, groupFormatterPrefix: 'Min: ', decimalSeparator: ',', thousandSeparator: '_' } } as Column, {} as SlickGrid); + const output2 = minTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0, groupFormatterSuffix: ' (min)', decimalSeparator: ',', thousandSeparator: '_' } } as Column, {} as SlickGrid); const output3 = minTotalsFormatter( totals, { id: 'column3', field: 'column3', params: { minDecimal: 3, displayNegativeNumberWithParentheses: true, groupFormatterPrefix: 'Min: ', groupFormatterSuffix: '/item', decimalSeparator: ',', thousandSeparator: '_' } - } as Column); + } as Column, {} as SlickGrid); expect(output1).toBe('Min: 12_345_678,46'); expect(output2).toBe('345_678,2 (min)'); diff --git a/packages/common/src/grouping-formatters/__tests__/sumTotalsFormatter.spec.ts b/packages/common/src/grouping-formatters/__tests__/sumTotalsFormatter.spec.ts index 32e605187..9ae7a12e2 100644 --- a/packages/common/src/grouping-formatters/__tests__/sumTotalsFormatter.spec.ts +++ b/packages/common/src/grouping-formatters/__tests__/sumTotalsFormatter.spec.ts @@ -8,37 +8,37 @@ describe('sumTotalsFormatter', () => { } as unknown as SlickGrid; it('should display an empty string when no value is provided', () => { - const output = sumTotalsFormatter({}, {} as Column); + const output = sumTotalsFormatter({}, {} as Column, {} as SlickGrid); expect(output).toBe(''); }); it('should display an empty string when the "sum" does not find the field property in its object', () => { const columnDef = { id: 'column3', field: 'column3' } as Column; const totals = { sum: { column1: 123, column2: 345 } }; - const output = sumTotalsFormatter(totals, columnDef, {}); + const output = sumTotalsFormatter(totals, columnDef, {} as SlickGrid); expect(output).toBe(''); }); it('should display an empty string when the sum property is null', () => { const columnDef = { id: 'column1', field: 'column1' } as Column; const totals = { sum: { column1: null } }; - const output = sumTotalsFormatter(totals, columnDef, {}); + const output = sumTotalsFormatter(totals, columnDef, {} as SlickGrid); expect(output).toBe(''); }); it('should display an empty string when the average input is not a number', () => { const columnDef = { id: 'column1', field: 'column1' } as Column; const totals = { sum: { column1: 'abc' } }; - const output = sumTotalsFormatter(totals, columnDef, {}); + const output = sumTotalsFormatter(totals, columnDef, {} as SlickGrid); expect(output).toBe(''); }); it('should display a negative sum when its input is negative', () => { const totals = { sum: { column1: -123, column2: -34.5678, column3: -2.4 } }; - const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1' } as Column, {}); - const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2 } } as Column, {}); - const output3 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, decimalSeparator: ',' } } as Column, {}); + const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1' } as Column, {} as SlickGrid); + const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2 } } as Column, {} as SlickGrid); + const output3 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, decimalSeparator: ',' } } as Column, {} as SlickGrid); expect(output1).toBe('-123'); expect(output2).toBe('-34.57'); @@ -48,9 +48,9 @@ describe('sumTotalsFormatter', () => { it('should display a negative sum and thousand separator when its input is negative', () => { const totals = { sum: { column1: -12345678, column2: -345678.5678, column3: -2.4 } }; - const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { thousandSeparator: ',' } } as Column, {}); - const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, thousandSeparator: ',' } } as Column, {}); - const output3 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, decimalSeparator: ',', thousandSeparator: '_' } } as Column, {}); + const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { thousandSeparator: ',' } } as Column, {} as SlickGrid); + const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, thousandSeparator: ',' } } as Column, {} as SlickGrid); + const output3 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, decimalSeparator: ',', thousandSeparator: '_' } } as Column, {} as SlickGrid); expect(output1).toBe('-12,345,678'); expect(output2).toBe('-345,678.57'); @@ -60,8 +60,8 @@ describe('sumTotalsFormatter', () => { it('should display a negative sum with parentheses instead of the negative sign when its input is negative', () => { const totals = { sum: { column1: -123, column2: -34.5678, column3: -2.4 } }; - const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true } } as Column, {}); - const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true } } as Column, {}); + const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true } } as Column, {} as SlickGrid); + const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true } } as Column, {} as SlickGrid); expect(output1).toBe('(123)'); expect(output2).toBe('(34.57)'); @@ -70,9 +70,9 @@ describe('sumTotalsFormatter', () => { it('should display a negative sum with thousand separator and parentheses instead of the negative sign when its input is negative', () => { const totals = { sum: { column1: -12345678, column2: -345678.5678, column3: -2.4 } }; - const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true, thousandSeparator: ',' } } as Column, {}); - const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true, thousandSeparator: ',' } } as Column, {}); - const output3 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true, decimalSeparator: ',', thousandSeparator: ' ' } } as Column, {}); + const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { displayNegativeNumberWithParentheses: true, thousandSeparator: ',' } } as Column, {} as SlickGrid); + const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true, thousandSeparator: ',' } } as Column, {} as SlickGrid); + const output3 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { maxDecimal: 2, displayNegativeNumberWithParentheses: true, decimalSeparator: ',', thousandSeparator: ' ' } } as Column, {} as SlickGrid); expect(output1).toBe('(12,345,678)'); expect(output2).toBe('(345,678.57)'); @@ -90,8 +90,8 @@ describe('sumTotalsFormatter', () => { it('should display the same sum value when a number with decimals is provided', () => { const totals = { sum: { column1: 123.55678, column2: 345.2, column3: -2.45 } }; - const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1' } as Column, {}); - const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2' } as Column, {}); + const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1' } as Column, {} as SlickGrid); + const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2' } as Column, {} as SlickGrid); expect(output1).toBe('123.55678'); expect(output2).toBe('345.2'); @@ -100,9 +100,9 @@ describe('sumTotalsFormatter', () => { it('should display a sum number with user defined minimum & maximum decimal count', () => { const totals = { sum: { column1: 123.45678, column2: 345.2, column3: -2.45 } }; - const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2 } } as Column, {}); - const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0 } } as Column, {}); - const output3 = sumTotalsFormatter(totals, { id: 'column3', field: 'column3', params: { minDecimal: 3, displayNegativeNumberWithParentheses: true } } as Column, {}); + const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2 } } as Column, {} as SlickGrid); + const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0 } } as Column, {} as SlickGrid); + const output3 = sumTotalsFormatter(totals, { id: 'column3', field: 'column3', params: { minDecimal: 3, displayNegativeNumberWithParentheses: true } } as Column, {} as SlickGrid); expect(output1).toBe('123.46'); expect(output2).toBe('345.2'); @@ -125,14 +125,14 @@ describe('sumTotalsFormatter', () => { it('should display a sum number a prefix and suffix', () => { const totals = { sum: { column1: 123.45678, column2: 345.2, column3: -2.45 } }; - const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2, groupFormatterPrefix: 'sum: ' } } as Column, {}); - const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0, groupFormatterSuffix: ' (max)' } } as Column, {}); + const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2, groupFormatterPrefix: 'sum: ' } } as Column, {} as SlickGrid); + const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0, groupFormatterSuffix: ' (max)' } } as Column, {} as SlickGrid); const output3 = sumTotalsFormatter( totals, { id: 'column3', field: 'column3', params: { minDecimal: 3, displayNegativeNumberWithParentheses: true, groupFormatterPrefix: 'sum: ', groupFormatterSuffix: '/item' } - } as Column + } as Column, {} as SlickGrid ); expect(output1).toBe('sum: 123.46'); @@ -143,13 +143,13 @@ describe('sumTotalsFormatter', () => { it('should display a sum number with prefix, suffix and thousand separator', () => { const totals = { sum: { column1: 12345678.45678, column2: 345678.2, column3: -345678.45 } }; - const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2, groupFormatterPrefix: 'Sum: ', decimalSeparator: ',', thousandSeparator: '_' } } as Column, {}); - const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0, groupFormatterSuffix: ' (sum)', decimalSeparator: ',', thousandSeparator: '_' } } as Column, {}); + const output1 = sumTotalsFormatter(totals, { id: 'column1', field: 'column1', params: { maxDecimal: 2, groupFormatterPrefix: 'Sum: ', decimalSeparator: ',', thousandSeparator: '_' } } as Column, {} as SlickGrid); + const output2 = sumTotalsFormatter(totals, { id: 'column2', field: 'column2', params: { minDecimal: 0, groupFormatterSuffix: ' (sum)', decimalSeparator: ',', thousandSeparator: '_' } } as Column, {} as SlickGrid); const output3 = sumTotalsFormatter( totals, { id: 'column3', field: 'column3', params: { minDecimal: 3, displayNegativeNumberWithParentheses: true, groupFormatterPrefix: 'Sum: ', groupFormatterSuffix: '/item', decimalSeparator: ',', thousandSeparator: '_' } - } as Column); + } as Column, {} as SlickGrid); expect(output1).toBe('Sum: 12_345_678,46'); expect(output2).toBe('345_678,2 (sum)'); diff --git a/packages/common/src/grouping-formatters/avgTotalsDollarFormatter.ts b/packages/common/src/grouping-formatters/avgTotalsDollarFormatter.ts index 17cea0145..3e5cf6798 100644 --- a/packages/common/src/grouping-formatters/avgTotalsDollarFormatter.ts +++ b/packages/common/src/grouping-formatters/avgTotalsDollarFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; export const avgTotalsDollarFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,14 +8,16 @@ export const avgTotalsDollarFormatter: GroupTotalsFormatter = (totals: any, colu const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, 2); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, 4); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'dollar', 'group'); if (val !== null && !isNaN(+val)) { - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '$', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '$', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/grouping-formatters/avgTotalsFormatter.ts b/packages/common/src/grouping-formatters/avgTotalsFormatter.ts index 0b3e4e54d..8baee2e33 100644 --- a/packages/common/src/grouping-formatters/avgTotalsFormatter.ts +++ b/packages/common/src/grouping-formatters/avgTotalsFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { decimalFormatted, thousandSeparatorFormatted } from '../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const avgTotalsFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,16 +8,18 @@ export const avgTotalsFormatter: GroupTotalsFormatter = (totals: any, columnDef: const params = columnDef?.params; let prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'regular', 'group'); if (val !== null && !isNaN(+val)) { if (val < 0) { val = Math.abs(val); - if (!displayNegativeNumberWithParentheses) { + if (!wrapNegativeNumber) { prefix += '-'; } else { if (isNaN(minDecimal) && isNaN(maxDecimal)) { diff --git a/packages/common/src/grouping-formatters/avgTotalsPercentageFormatter.ts b/packages/common/src/grouping-formatters/avgTotalsPercentageFormatter.ts index e15a2c6f8..b82ee9d38 100644 --- a/packages/common/src/grouping-formatters/avgTotalsPercentageFormatter.ts +++ b/packages/common/src/grouping-formatters/avgTotalsPercentageFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { decimalFormatted, thousandSeparatorFormatted } from '../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const avgTotalsPercentageFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,16 +8,18 @@ export const avgTotalsPercentageFormatter: GroupTotalsFormatter = (totals: any, const params = columnDef?.params; let prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'percent', 'group'); if (val !== null && !isNaN(+val)) { if (val < 0) { val = Math.abs(val); - if (!displayNegativeNumberWithParentheses) { + if (!wrapNegativeNumber) { prefix += '-'; } else { if (isNaN(minDecimal) && isNaN(maxDecimal)) { diff --git a/packages/common/src/grouping-formatters/maxTotalsFormatter.ts b/packages/common/src/grouping-formatters/maxTotalsFormatter.ts index 63eb7bfa3..b436cdb51 100644 --- a/packages/common/src/grouping-formatters/maxTotalsFormatter.ts +++ b/packages/common/src/grouping-formatters/maxTotalsFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { formatNumber } from '../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const maxTotalsFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,14 +8,16 @@ export const maxTotalsFormatter: GroupTotalsFormatter = (totals: any, columnDef: const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'regular', 'group'); if (val !== null && !isNaN(+val)) { - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/grouping-formatters/minTotalsFormatter.ts b/packages/common/src/grouping-formatters/minTotalsFormatter.ts index 875ff44e6..579ec1777 100644 --- a/packages/common/src/grouping-formatters/minTotalsFormatter.ts +++ b/packages/common/src/grouping-formatters/minTotalsFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { formatNumber } from '../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const minTotalsFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,14 +8,16 @@ export const minTotalsFormatter: GroupTotalsFormatter = (totals: any, columnDef: const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'regular', 'group'); if (val !== null && !isNaN(+val)) { - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/grouping-formatters/sumTotalsBoldFormatter.ts b/packages/common/src/grouping-formatters/sumTotalsBoldFormatter.ts index 044a7f0dc..28f22cb65 100644 --- a/packages/common/src/grouping-formatters/sumTotalsBoldFormatter.ts +++ b/packages/common/src/grouping-formatters/sumTotalsBoldFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { formatNumber } from '../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const sumTotalsBoldFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,14 +8,16 @@ export const sumTotalsBoldFormatter: GroupTotalsFormatter = (totals: any, column const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'regular', 'group'); if (val !== null && !isNaN(+val)) { - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/grouping-formatters/sumTotalsColoredFormatter.ts b/packages/common/src/grouping-formatters/sumTotalsColoredFormatter.ts index 2c7bacc75..e497d71ac 100644 --- a/packages/common/src/grouping-formatters/sumTotalsColoredFormatter.ts +++ b/packages/common/src/grouping-formatters/sumTotalsColoredFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { formatNumber } from '../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const sumTotalsColoredFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,15 +8,17 @@ export const sumTotalsColoredFormatter: GroupTotalsFormatter = (totals: any, col const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'regular', 'group'); if (val !== null && !isNaN(+val)) { const colorStyle = (val >= 0) ? 'green' : 'red'; - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/grouping-formatters/sumTotalsDollarBoldFormatter.ts b/packages/common/src/grouping-formatters/sumTotalsDollarBoldFormatter.ts index 6e44f9e04..ff993aeb1 100644 --- a/packages/common/src/grouping-formatters/sumTotalsDollarBoldFormatter.ts +++ b/packages/common/src/grouping-formatters/sumTotalsDollarBoldFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const sumTotalsDollarBoldFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,14 +8,16 @@ export const sumTotalsDollarBoldFormatter: GroupTotalsFormatter = (totals: any, const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, 2); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, 4); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'dollar', 'group'); if (val !== null && !isNaN(+val)) { - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '$', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '$', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/grouping-formatters/sumTotalsDollarColoredBoldFormatter.ts b/packages/common/src/grouping-formatters/sumTotalsDollarColoredBoldFormatter.ts index bddc96455..33b2e27f9 100644 --- a/packages/common/src/grouping-formatters/sumTotalsDollarColoredBoldFormatter.ts +++ b/packages/common/src/grouping-formatters/sumTotalsDollarColoredBoldFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const sumTotalsDollarColoredBoldFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,15 +8,17 @@ export const sumTotalsDollarColoredBoldFormatter: GroupTotalsFormatter = (totals const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, 2); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, 4); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'dollar', 'group'); if (val !== null && !isNaN(+val)) { const colorStyle = (val >= 0) ? 'green' : 'red'; - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '$', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '$', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/grouping-formatters/sumTotalsDollarColoredFormatter.ts b/packages/common/src/grouping-formatters/sumTotalsDollarColoredFormatter.ts index b5644329c..582c584e7 100644 --- a/packages/common/src/grouping-formatters/sumTotalsDollarColoredFormatter.ts +++ b/packages/common/src/grouping-formatters/sumTotalsDollarColoredFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const sumTotalsDollarColoredFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,15 +8,17 @@ export const sumTotalsDollarColoredFormatter: GroupTotalsFormatter = (totals: an const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, 2); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, 4); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'dollar', 'group'); if (val !== null && !isNaN(+val)) { const colorStyle = (val >= 0) ? 'green' : 'red'; - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '$', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '$', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/grouping-formatters/sumTotalsDollarFormatter.ts b/packages/common/src/grouping-formatters/sumTotalsDollarFormatter.ts index edbbee213..3a5da155a 100644 --- a/packages/common/src/grouping-formatters/sumTotalsDollarFormatter.ts +++ b/packages/common/src/grouping-formatters/sumTotalsDollarFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { formatNumber } from './../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const sumTotalsDollarFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,14 +8,16 @@ export const sumTotalsDollarFormatter: GroupTotalsFormatter = (totals: any, colu const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid, 2); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid, 4); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'dollar', 'group'); if (val !== null && !isNaN(+val)) { - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '$', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '$', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/grouping-formatters/sumTotalsFormatter.ts b/packages/common/src/grouping-formatters/sumTotalsFormatter.ts index 859d00db0..d070229b5 100644 --- a/packages/common/src/grouping-formatters/sumTotalsFormatter.ts +++ b/packages/common/src/grouping-formatters/sumTotalsFormatter.ts @@ -1,6 +1,6 @@ import { Column, GroupTotalsFormatter, SlickGrid } from './../interfaces/index'; import { formatNumber } from '../services/utilities'; -import { getValueFromParamsOrFormatterOptions } from '../formatters/formatterUtilities'; +import { retrieveFormatterOptions } from '../formatters/formatterUtilities'; export const sumTotalsFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column, grid: SlickGrid) => { const field = columnDef.field ?? ''; @@ -8,14 +8,16 @@ export const sumTotalsFormatter: GroupTotalsFormatter = (totals: any, columnDef: const params = columnDef?.params; const prefix = params?.groupFormatterPrefix || ''; const suffix = params?.groupFormatterSuffix || ''; - const minDecimal = getValueFromParamsOrFormatterOptions('minDecimal', columnDef, grid); - const maxDecimal = getValueFromParamsOrFormatterOptions('maxDecimal', columnDef, grid); - const decimalSeparator = getValueFromParamsOrFormatterOptions('decimalSeparator', columnDef, grid, '.'); - const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, ''); - const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false); + const { + minDecimal, + maxDecimal, + decimalSeparator, + thousandSeparator, + wrapNegativeNumber + } = retrieveFormatterOptions(columnDef, grid, 'regular', 'group'); if (val !== null && !isNaN(+val)) { - const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '', '', decimalSeparator, thousandSeparator); + const formattedNumber = formatNumber(val, minDecimal, maxDecimal, wrapNegativeNumber, '', '', decimalSeparator, thousandSeparator); return `${prefix}${formattedNumber}${suffix}`; } return ''; diff --git a/packages/common/src/interfaces/column.interface.ts b/packages/common/src/interfaces/column.interface.ts index 48a1a03b1..0861db19b 100644 --- a/packages/common/src/interfaces/column.interface.ts +++ b/packages/common/src/interfaces/column.interface.ts @@ -115,6 +115,13 @@ export interface Column { */ exportWithFormatter?: boolean; + /** + * Defaults to true, which leads to ExcelExportService trying to detect the best possible Excel format for each cell. + * The difference the other flag is that "exportWithFormatter" will always export as a string, while this option here will try to detect the best Excel format. + * NOTE: Date will still be exported as string, the numbers are the ones taking the best advantage from this option. + */ + exportWithExcelFormat?: boolean; + /** * Do we want to force the cell value to be a string? * When set to True, it will wrap the cell value in double quotes and add an equal sign (=) at the beginning of the cell to force Excel to evaluate it as a string and not change it's format. diff --git a/packages/common/src/interfaces/excelExportOption.interface.ts b/packages/common/src/interfaces/excelExportOption.interface.ts index 449d1ca35..186665d6d 100644 --- a/packages/common/src/interfaces/excelExportOption.interface.ts +++ b/packages/common/src/interfaces/excelExportOption.interface.ts @@ -15,6 +15,13 @@ export interface ExcelExportOption { /** 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; + /** + * Defaults to true, which leads to ExcelExportService trying to detect the best possible Excel format for each cell. + * The difference the other flag is that "exportWithFormatter" will always export as a string, while this option here will try to detect the best Excel format. + * NOTE: Date will still be exported as string, the numbers are the ones taking the best advantage from this option. + */ + exportWithExcelFormat?: boolean; + /** filename (without extension) */ filename?: string; diff --git a/packages/common/src/services/utilities.ts b/packages/common/src/services/utilities.ts index e0930418b..1d0af98de 100644 --- a/packages/common/src/services/utilities.ts +++ b/packages/common/src/services/utilities.ts @@ -242,13 +242,13 @@ export function decimalFormatted(input: number | string, minDecimal?: number, ma * @param input * @param minDecimal * @param maxDecimal - * @param displayNegativeNumberWithParentheses + * @param wrapNegativeNumberInBraquets * @param symbolPrefix * @param symbolSuffix * @param decimalSeparator * @param thousandSeparator */ -export function formatNumber(input: number | string, minDecimal?: number, maxDecimal?: number, displayNegativeNumberWithParentheses?: boolean, symbolPrefix = '', symbolSuffix = '', decimalSeparator: '.' | ',' = '.', thousandSeparator: ',' | '_' | '.' | ' ' | '' = ''): string { +export function formatNumber(input: number | string, minDecimal?: number, maxDecimal?: number, wrapNegativeNumberInBraquets?: boolean, symbolPrefix = '', symbolSuffix = '', decimalSeparator: '.' | ',' = '.', thousandSeparator: ',' | '_' | '.' | ' ' | '' = ''): string { if (isNaN(+input)) { return input as string; } @@ -257,7 +257,7 @@ export function formatNumber(input: number | string, minDecimal?: number, maxDec if (calculatedValue < 0) { const absValue = Math.abs(calculatedValue); - if (displayNegativeNumberWithParentheses) { + if (wrapNegativeNumberInBraquets) { if (!isNaN(minDecimal as number) || !isNaN(maxDecimal as number)) { return `(${symbolPrefix}${decimalFormatted(absValue, minDecimal, maxDecimal, decimalSeparator, thousandSeparator)}${symbolSuffix})`; } diff --git a/packages/excel-export/src/excelExport.service.spec.ts b/packages/excel-export/src/excelExport.service.spec.ts index 192814398..bdfbe938c 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 from 'moment-mini'; +import 'jest-extended'; import { BasePubSubService } from '@slickgrid-universal/event-pub-sub'; import { Column, @@ -191,7 +191,7 @@ describe('ExcelExportService', () => { const optionExpectation = { filename: 'export.xlsx', format: FileType.xlsx }; (navigator as any).msSaveOrOpenBlob = jest.fn(); const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); - const spyMsSave = jest.spyOn(navigator, 'msSaveOrOpenBlob'); + const spyMsSave = jest.spyOn(navigator as any, 'msSaveOrOpenBlob'); service.init(gridStub, container); const result = await service.exportToExcel(mockExportExcelOptions); @@ -206,7 +206,7 @@ describe('ExcelExportService', () => { let mockCollection: any[]; it(`should have the Order exported correctly with multiple formatters which have 1 of them returning an object with a text property (instead of simple string)`, async () => { - mockCollection = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }]; + mockCollection = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10 }]; jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length); jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection[0]); const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); @@ -229,7 +229,7 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Position', }, { metadata: { style: 1, }, value: 'Order', }, ], - ['1E06', 'John', 'Z', 'SALES_REP', '10'], + ['1E06', 'John', 'X', 'SALES_REP', '10'], ] }); }); @@ -498,14 +498,14 @@ describe('ExcelExportService', () => { let mockCollection: any[]; beforeEach(() => { - mockGridOptions.excelExportOptions = { sanitizeDataExport: true }; + mockGridOptions.excelExportOptions = { sanitizeDataExport: true, exportWithExcelFormat: true, }; mockColumns = [ { id: 'id', field: 'id', excludeFromExport: true }, { id: 'userId', field: 'userId', name: 'User Id', width: 100 }, - { id: 'firstName', field: 'firstName', width: 100, formatter: myBoldHtmlFormatter }, - { id: 'lastName', field: 'lastName', width: 100, sanitizeDataExport: true, exportWithFormatter: true }, + { id: 'firstName', field: 'firstName', width: 100, formatter: myBoldHtmlFormatter, exportWithExcelFormat: false }, + { id: 'lastName', field: 'lastName', width: 100, sanitizeDataExport: true, exportWithFormatter: true, exportWithExcelFormat: false }, { id: 'position', field: 'position', width: 100 }, - { id: 'startDate', field: 'startDate', type: FieldType.dateIso, width: 100, exportWithFormatter: false }, + { id: 'startDate', field: 'startDate', type: FieldType.dateIso, width: 100, exportWithFormatter: false, }, { id: 'endDate', field: 'endDate', width: 100, formatter: Formatters.dateIso, type: FieldType.dateUtc, exportWithFormatter: true, outputType: FieldType.dateIso }, ] as Column[]; @@ -515,7 +515,7 @@ describe('ExcelExportService', () => { 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: 0, userId: '1E06', firstName: 'John', lastName: 'X', 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' }, ]; jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length); @@ -541,8 +541,8 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'StartDate', }, { metadata: { style: 1, }, value: 'EndDate', }, ], - ['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'], + ['1E06', 'John', 'X', 'SALES_REP', '2005-12-20', ''], + ['1E09', 'Jane', 'Doe', 'HUMAN_RESOURCES', '2010-10-09', '2024-01-02'], ] }); }); @@ -555,7 +555,7 @@ describe('ExcelExportService', () => { { id: 'id', field: 'id', excludeFromExport: true }, { id: 'firstName', field: 'user.firstName', name: 'First Name', width: 100, formatter: Formatters.complexObject, exportWithFormatter: true }, { id: 'lastName', field: 'user.lastName', name: 'Last Name', width: 100, formatter: Formatters.complexObject, exportWithFormatter: true }, - { id: 'position', field: 'position', width: 100 }, + { id: 'position', field: 'position', width: 100, type: FieldType.number, exportWithExcelFormat: true }, ] as Column[]; jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns); @@ -564,7 +564,7 @@ describe('ExcelExportService', () => { let mockCollection: any[]; it(`should export correctly with complex object formatters`, async () => { - mockCollection = [{ id: 0, user: { firstName: 'John', lastName: 'Z' }, position: 'SALES_REP', order: 10 }]; + mockCollection = [{ id: 0, user: { firstName: 'John', lastName: 'X' }, position: 'SALES_REP', order: 10 }]; jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length); jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection[0]); const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); @@ -585,15 +585,15 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Last Name', }, { metadata: { style: 1, }, value: 'Position', }, ], - ['John', 'Z', 'SALES_REP'], + ['John', 'X', { value: 'SALES_REP', metadata: { style: 3 } }], ] }); }); it(`should skip lines that have an empty Slick DataView structure like "getItem" that is null and is part of the item object`, async () => { mockCollection = [ - { id: 0, user: { firstName: 'John', lastName: 'Z' }, position: 'SALES_REP', order: 10 }, - { id: 1, getItem: null, getItems: null, __parent: { id: 0, user: { firstName: 'John', lastName: 'Z' }, position: 'SALES_REP', order: 10 } } + { id: 0, user: { firstName: 'John', lastName: 'X' }, position: 'SALES_REP', order: 10 }, + { id: 1, getItem: null, getItems: null, __parent: { id: 0, user: { firstName: 'John', lastName: 'X' }, position: 'SALES_REP', order: 10 } } ]; jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length); jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection[0]).mockReturnValueOnce(mockCollection[1]); @@ -615,7 +615,7 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Last Name', }, { metadata: { style: 1, }, value: 'Position', }, ], - ['John', 'Z', 'SALES_REP'], + ['John', 'X', { value: 'SALES_REP', metadata: { style: 3 } }], ] }); }); @@ -633,7 +633,7 @@ describe('ExcelExportService', () => { { id: 'userId', field: 'userId', name: 'User Id', width: 100 }, { id: 'firstName', nameKey: 'FIRST_NAME', width: 100, formatter: myBoldHtmlFormatter }, { id: 'lastName', field: 'lastName', nameKey: 'LAST_NAME', width: 100, formatter: myBoldHtmlFormatter, exportCustomFormatter: myUppercaseFormatter, sanitizeDataExport: true, exportWithFormatter: true }, - { id: 'position', field: 'position', name: 'Position', width: 100, formatter: Formatters.translate, exportWithFormatter: true }, + { id: 'position', field: 'position', name: 'Position', type: FieldType.number, width: 100, formatter: Formatters.translate, exportWithFormatter: true }, { id: 'order', field: 'order', width: 100, exportWithFormatter: true, formatter: Formatters.multiple, params: { formatters: [myBoldHtmlFormatter, myCustomObjectFormatter] } }, ] as Column[]; @@ -641,7 +641,7 @@ describe('ExcelExportService', () => { }); it(`should have the LastName header title translated when defined as a "nameKey" and "translater" is set in grid option`, async () => { - mockCollection = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }]; + mockCollection = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10 }]; jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length); jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection[0]); const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); @@ -664,7 +664,7 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Position', }, { metadata: { style: 1, }, value: 'Order', }, ], - ['1E06', 'John', 'Z', 'Sales Rep.', '10'], + ['1E06', 'John', 'X', 'Sales Rep.', '10'], ] }); }); @@ -713,7 +713,7 @@ describe('ExcelExportService', () => { predefinedValues: [], }; - mockItem1 = { id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }; + mockItem1 = { id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10 }; mockItem2 = { id: 1, userId: '2B02', firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 10 }; mockGroup1 = { collapsed: 0, count: 2, groupingKey: '10', groups: null, level: 0, selectChecked: false, @@ -757,7 +757,7 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Order', }, ], ['⮟ Order: 20 (2 items)'], - ['', '1E06', 'John', 'Z', 'SALES_REP', '10'], + ['', '1E06', 'John', 'X', 'SALES_REP', '10'], ['', '2B02', 'Jane', 'DOE', 'FINANCE_MANAGER', '10'], ['', '', '', '', '', 'Custom: 20'], ] @@ -765,6 +765,103 @@ describe('ExcelExportService', () => { }); }); + describe('with Grouping and export with Excel custom format', () => { + let mockCollection: any[]; + let mockOrderGrouping; + let mockItem1; + let mockItem2; + let mockGroup1; + + beforeEach(() => { + mockGridOptions.enableGrouping = true; + mockGridOptions.enableTranslate = false; + mockGridOptions.excelExportOptions = { sanitizeDataExport: true, addGroupIndentation: true, exportWithExcelFormat: true }; + + mockColumns = [ + { id: 'id', field: 'id', excludeFromExport: true }, + { id: 'userId', field: 'userId', name: 'User Id', width: 100 }, + { id: 'firstName', field: 'firstName', width: 100, formatter: myBoldHtmlFormatter }, + { id: 'lastName', field: 'lastName', width: 100, formatter: myBoldHtmlFormatter, exportCustomFormatter: myUppercaseFormatter, sanitizeDataExport: true, exportWithFormatter: true }, + { + id: 'position', field: 'position', width: 100, + groupTotalsFormatter: GroupTotalFormatters.avgTotalsDollar, + }, + { + id: 'order', field: 'order', type: FieldType.number, + exportWithFormatter: true, + formatter: Formatters.multiple, params: { formatters: [myBoldHtmlFormatter, myCustomObjectFormatter] }, + groupTotalsFormatter: GroupTotalFormatters.sumTotals, + }, + ] as Column[]; + + mockOrderGrouping = { + aggregateChildGroups: false, + aggregateCollapsed: false, + aggregateEmpty: false, + aggregators: [{ _count: 2, _field: 'order', _nonNullCount: 2, _sum: 4, }], + collapsed: false, + comparer: (a, b) => SortComparers.numeric(a.value, b.value, SortDirectionNumber.asc), + compiledAccumulators: [jest.fn(), jest.fn()], + displayTotalsRow: true, + formatter: (g) => `Order: ${g.value} (${g.count} items)`, + getter: 'order', + getterIsAFn: false, + lazyTotalsCalculation: true, + predefinedValues: [], + }; + + mockItem1 = { id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10 }; + mockItem2 = { id: 1, userId: '2B02', firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 10 }; + mockGroup1 = { + collapsed: 0, count: 2, groupingKey: '10', groups: null, level: 0, selectChecked: false, + rows: [mockItem1, mockItem2], + title: `Order: 20 (2 items)`, + totals: { value: '10', __group: true, __groupTotals: true, group: {}, initialized: true, sum: { order: 20 } }, + }; + + jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns); + mockCollection = [mockGroup1, mockItem1, mockItem2, { __groupTotals: true, initialized: true, sum: { order: 20 }, group: mockGroup1 }]; + jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length); + jest.spyOn(dataViewStub, 'getItem') + .mockReturnValue(null) + .mockReturnValueOnce(mockCollection[0]) + .mockReturnValueOnce(mockCollection[1]) + .mockReturnValueOnce(mockCollection[2]) + .mockReturnValueOnce(mockCollection[3]); + jest.spyOn(dataViewStub, 'getGrouping').mockReturnValue([mockOrderGrouping]); + }); + + it(`should have a xlsx export with grouping (same as the grid, WYSIWYG) when "enableGrouping" is set in the grid options and grouping are defined`, async () => { + const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); + const spyUrlCreate = jest.spyOn(URL, 'createObjectURL'); + const spyDownload = jest.spyOn(service, 'startDownloadFile'); + + const optionExpectation = { filename: 'export.xlsx', format: 'xlsx' }; + + service.init(gridStub, container); + await service.exportToExcel(mockExportExcelOptions); + + expect(pubSubSpy).toHaveBeenCalledWith(`onAfterExportToExcel`, optionExpectation); + expect(spyUrlCreate).toHaveBeenCalledWith(mockExcelBlob); + expect(spyDownload).toHaveBeenCalledWith({ + ...optionExpectation, blob: new Blob(), data: [ + [ + { metadata: { style: 1, }, value: 'Group By', }, + { metadata: { style: 1, }, value: 'User Id', }, + { metadata: { style: 1, }, value: 'FirstName', }, + { metadata: { style: 1, }, value: 'LastName', }, + { metadata: { style: 1, }, value: 'Position', }, + { metadata: { style: 1, }, value: 'Order', }, + ], + ['⮟ Order: 20 (2 items)'], + ['', '1E06', 'John', 'X', 'SALES_REP', '10'], + ['', '2B02', 'Jane', 'DOE', 'FINANCE_MANAGER', '10'], + ['', '', '', '', '', { value: 20, metadata: { style: 4, type: 'number' } }], + ] + }); + }); + }); + describe('with Grouping and Translation', () => { let mockCollection: any[]; let mockOrderGrouping; @@ -782,7 +879,7 @@ describe('ExcelExportService', () => { { id: 'userId', field: 'userId', name: 'User Id', width: 100 }, { id: 'firstName', field: 'firstName', nameKey: 'FIRST_NAME', width: 100, formatter: myBoldHtmlFormatter }, { id: 'lastName', field: 'lastName', nameKey: 'LAST_NAME', width: 100, formatter: myBoldHtmlFormatter, exportCustomFormatter: myUppercaseFormatter, sanitizeDataExport: true, exportWithFormatter: true }, - { id: 'position', field: 'position', name: 'Position', width: 100, formatter: Formatters.translate, exportWithFormatter: true }, + { id: 'position', field: 'position', name: 'Position', type: FieldType.number, width: 100, formatter: Formatters.translate, exportWithFormatter: true }, { id: 'order', field: 'order', type: FieldType.number, exportWithFormatter: true, @@ -807,7 +904,7 @@ describe('ExcelExportService', () => { predefinedValues: [], }; - mockItem1 = { id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }; + mockItem1 = { id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10 }; mockItem2 = { id: 1, userId: '2B02', firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 10 }; mockGroup1 = { collapsed: 0, count: 2, groupingKey: '10', groups: null, level: 0, selectChecked: false, @@ -851,7 +948,7 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Order', }, ], ['⮟ Order: 20 (2 items)'], - ['', '1E06', 'John', 'Z', 'Sales Rep.', '10'], + ['', '1E06', 'John', 'X', 'Sales Rep.', '10'], ['', '2B02', 'Jane', 'DOE', 'Finance Manager', '10'], ['', '', '', '', '', '20'], ] @@ -904,7 +1001,7 @@ describe('ExcelExportService', () => { predefinedValues: [], }; - mockItem1 = { id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }; + mockItem1 = { id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10 }; mockItem2 = { id: 1, userId: '2B02', firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 10 }; mockGroup1 = { collapsed: false, count: 2, groupingKey: '10', groups: null, level: 0, selectChecked: false, @@ -913,9 +1010,9 @@ describe('ExcelExportService', () => { totals: { value: '10', __group: true, __groupTotals: true, group: {}, initialized: true, sum: { order: 20 } }, }; mockGroup2 = { - collapsed: false, count: 2, groupingKey: '10:|:Z', groups: null, level: 1, selectChecked: false, + collapsed: false, count: 2, groupingKey: '10:|:X', groups: null, level: 1, selectChecked: false, rows: [mockItem1, mockItem2], - title: `Last Name: Z (1 items)`, + title: `Last Name: X (1 items)`, totals: { value: '10', __group: true, __groupTotals: true, group: {}, initialized: true, sum: { order: 10 } }, }; mockGroup3 = { @@ -974,8 +1071,8 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Order', }, ], ['⮟ Order: 20 (2 items)'], - ['⮟ Last Name: Z (1 items)'], // expanded - ['', '1E06', 'John', 'Z', 'Sales Rep.', '10'], + ['⮟ Last Name: X (1 items)'], // expanded + ['', '1E06', 'John', 'X', 'Sales Rep.', '10'], ['⮟ Last Name: Doe (1 items)'], // expanded ['', '2B02', 'Jane', 'DOE', 'Finance Manager', '10'], ['⮞ Last Name: null (0 items)'], // collapsed @@ -1011,10 +1108,10 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Order', }, ], ['Order: 20 (2 items)'], - ['Last Name: Z (1 items)'], - ['', '1E06', 'John', 'Z', 'Sales Rep.', { metadata: { style: 3, type: 'number' }, value: 10, }], + ['Last Name: X (1 items)'], + ['', '1E06', 'John', 'X', 'Sales Rep.', "10"], ['Last Name: Doe (1 items)'], - ['', '2B02', 'Jane', 'DOE', 'Finance Manager', { metadata: { style: 3, type: 'number' }, value: 10, }], + ['', '2B02', 'Jane', 'DOE', 'Finance Manager', "10"], ['Last Name: null (0 items)'], ['', '', '', '', '', '20'], ['', '', '', '', '', '10'], @@ -1025,279 +1122,13 @@ describe('ExcelExportService', () => { describe('useCellFormatByFieldType method', () => { 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); + const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, column, gridStub); - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - 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 = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, input, column, gridStub); - - expect(output).toEqual({ metadata: { style: 4 }, value: expectedDate }); + expect(output).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); }); @@ -1322,7 +1153,7 @@ describe('ExcelExportService', () => { }); it('should export with grouped header titles showing up on first row', async () => { - mockCollection2 = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }]; + mockCollection2 = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10 }]; jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection2.length); jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection2[0]); const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); @@ -1352,7 +1183,7 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Position', }, { metadata: { style: 1, }, value: 'Order', }, ], - ['John', 'Z', '1E06', 'SALES_REP', '10'], + ['John', 'X', '1E06', 'SALES_REP', '10'], ] }); }); @@ -1381,7 +1212,7 @@ describe('ExcelExportService', () => { it(`should have the LastName header title translated when defined as a "headerKey" and "translater" is set in grid option`, async () => { mockGridOptions.excelExportOptions!.sanitizeDataExport = false; - mockTranslateCollection = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }]; + mockTranslateCollection = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10 }]; jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockTranslateCollection.length); jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockTranslateCollection[0]); const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); @@ -1411,7 +1242,7 @@ describe('ExcelExportService', () => { { metadata: { style: 1, }, value: 'Position', }, { metadata: { style: 1, }, value: 'Order', }, ], - ['John', 'Z', '1E06', 'Sales Rep.', '10'], + ['John', 'X', '1E06', 'Sales Rep.', '10'], ] }); }); @@ -1429,13 +1260,14 @@ describe('ExcelExportService', () => { mockGridOptions.excelExportOptions = {}; mockGridOptions.createPreHeaderPanel = false; mockGridOptions.showPreHeaderPanel = false; + mockGridOptions.excelExportOptions.exportWithFormatter = true; mockGridOptions.colspanCallback = (item: any) => (item.id % 2 === 1) ? evenMetatadata : oddMetatadata; mockColumns = [ { id: 'userId', field: 'userId', name: 'User Id', width: 100 }, - { id: 'firstName', nameKey: 'FIRST_NAME', width: 100, formatter: myBoldHtmlFormatter }, - { id: 'lastName', field: 'lastName', nameKey: 'LAST_NAME', width: 100, formatter: myBoldHtmlFormatter, exportCustomFormatter: myUppercaseFormatter, sanitizeDataExport: true, exportWithFormatter: true }, - { id: 'position', field: 'position', name: 'Position', width: 100, formatter: Formatters.translate, exportWithFormatter: true }, + { id: 'firstName', nameKey: 'FIRST_NAME', width: 100, formatter: myBoldHtmlFormatter, exportWithFormatter: false }, + { id: 'lastName', field: 'lastName', nameKey: 'LAST_NAME', width: 100, formatter: myBoldHtmlFormatter, exportCustomFormatter: myUppercaseFormatter, sanitizeDataExport: true }, + { id: 'position', field: 'position', name: 'Position', width: 100, formatter: Formatters.translate }, { id: 'order', field: 'order', width: 100, }, ] as Column[]; @@ -1460,7 +1292,7 @@ describe('ExcelExportService', () => { it(`should export same colspan in the export excel as defined in the grid`, async () => { mockCollection = [ - { id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }, + { id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', order: 10 }, { id: 1, userId: '1E09', firstName: 'Jane', lastName: 'Doe', position: 'DEVELOPER', order: 15 }, { id: 2, userId: '2ABC', firstName: 'Sponge', lastName: 'Bob', position: 'IT_ADMIN', order: 33 }, ]; diff --git a/packages/excel-export/src/excelExport.service.ts b/packages/excel-export/src/excelExport.service.ts index 157988d27..9bff88241 100644 --- a/packages/excel-export/src/excelExport.service.ts +++ b/packages/excel-export/src/excelExport.service.ts @@ -1,5 +1,4 @@ import * as ExcelBuilder from 'excel-builder-webpacker'; - import { // utility functions exportWithFormatterWhenDefined, @@ -11,7 +10,11 @@ import { Constants, ContainerService, ExcelExportService as BaseExcelExportService, + ExcelExportOption, ExternalResource, + ExcelWorkbook, + ExcelWorksheet, + FieldType, FileType, GridOption, KeyTitlePair, @@ -23,16 +26,12 @@ import { } from '@slickgrid-universal/common'; import { addWhiteSpaces, deepCopy, titleCase } from '@slickgrid-universal/utils'; - import { ExcelCellFormat, - ExcelExportOption, ExcelMetadata, ExcelStylesheet, - ExcelWorkbook, - ExcelWorksheet, } from './interfaces/index'; -import { useCellFormatByFieldType } from './excelUtils'; +import { getGroupTotalValue, getExcelFormatFromGridFormatter, useCellFormatByFieldType, ExcelFormatter, GetDataValueCallback } from './excelUtils'; const DEFAULT_EXPORT_OPTIONS: ExcelExportOption = { filename: 'export', @@ -55,11 +54,13 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ protected _translaterService: TranslaterService | undefined; protected _workbook!: ExcelWorkbook; + // references of each detected cell and/or group total formats + protected _regularCellExcelFormats: { [fieldId: string]: { stylesheetFormatterId?: number; getDataValueCallback: GetDataValueCallback; }; } = {}; + protected _groupTotalExcelFormats: { [fieldId: string]: { groupType: string; stylesheetFormatter?: ExcelFormatter; }; } = {}; + /** ExcelExportService class name which is use to find service instance in the external registered services */ readonly className = 'ExcelExportService'; - constructor() { } - protected get _datasetIdPropName(): string { return this._gridOptions?.datasetIdPropertyName ?? 'id'; } @@ -123,6 +124,10 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ this._excelExportOptions = deepCopy({ ...DEFAULT_EXPORT_OPTIONS, ...this._gridOptions.excelExportOptions, ...options }); this._fileFormat = this._excelExportOptions.format || FileType.xlsx; + // reset references of detected Excel formats + this._regularCellExcelFormats = {}; + this._groupTotalExcelFormats = {}; + // prepare the Excel Workbook & Sheet // we can use ExcelBuilder constructor with WebPack but we need to use function calls with RequireJS/SystemJS const worksheetOptions = { name: this._excelExportOptions.sheetName || 'Sheet1' }; @@ -430,7 +435,7 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ /** * Get all the grid row data and return that as an output string */ - protected pushAllGridRowDataToArray(originalDaraArray: Array, columns: Column[]): Array { + protected pushAllGridRowDataToArray(originalDaraArray: Array>, columns: Column[]): Array> { const lineCount = this._dataView.getLength(); // loop through all the grid rows of data @@ -472,6 +477,7 @@ 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) { @@ -527,20 +533,30 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ colspan = prevColspan--; } } else { + let itemData: Date | number | string | ExcelCellFormat = ''; + // -- Read Data & Push to Data Array - // get the output by analyzing if we'll pull the value from the cell or from a formatter - let itemData: ExcelCellFormat | string = exportWithFormatterWhenDefined(row, col, columnDef, itemObj, this._grid, this._excelExportOptions); + // user might want to export with Formatter, and/or auto-detect Excel format, and/or export as regular cell data + if (columnDef.exportWithFormatter || columnDef.exportCustomFormatter) { + // get the output by analyzing if we'll pull the value from the cell or from a formatter + itemData = exportWithFormatterWhenDefined(row, col, columnDef, itemObj, this._grid, this._excelExportOptions); + } else if (this.isExportingWithExcelFormat(columnDef)) { + // auto-detect best possible Excel format, we only do this check once per column (everything after that will be pull from temp ref) + if (!this._regularCellExcelFormats.hasOwnProperty(columnDef.id)) { + this._regularCellExcelFormats[columnDef.id] = useCellFormatByFieldType(this._stylesheet, this._stylesheetFormats, columnDef, this._grid); + } + const { stylesheetFormatterId, getDataValueCallback } = this._regularCellExcelFormats[columnDef.id]; + itemData = getDataValueCallback(itemObj[columnDef.field], stylesheetFormatterId, fieldType); + } else { + // at this point user might still have enabled exportWithFormatter in grid options or else cell will be exported "as is" + itemData = exportWithFormatterWhenDefined(row, col, columnDef, itemObj, this._grid, this._excelExportOptions); + } // does the user want to sanitize the output data (remove HTML tags)? - if (columnDef.sanitizeDataExport || this._excelExportOptions.sanitizeDataExport) { + if (!this.isExportingWithExcelFormat(columnDef) && typeof itemData === 'string' && (columnDef.sanitizeDataExport || this._excelExportOptions.sanitizeDataExport)) { itemData = sanitizeHtmlToText(itemData as string); } - // use different Excel Stylesheet Format as per the Field Type - if (!columnDef.exportWithFormatter) { - itemData = useCellFormatByFieldType(this._stylesheet, this._stylesheetFormats, itemData as string, columnDef, this._grid); - } - rowOutputStrings.push(itemData); idx++; } @@ -549,6 +565,20 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ return rowOutputStrings as string[]; } + protected isExportingWithExcelFormat(columnDef: Column) { + let isExportWithExcelFormatEnabled = false; + + // first check if there are any export options provided (as Grid Options) + if (this._excelExportOptions?.hasOwnProperty('exportWithExcelFormat')) { + isExportWithExcelFormatEnabled = !!this._excelExportOptions.exportWithExcelFormat; + } + // second check if "exportWithExcelFormat" is provided in the column definition, if so it will have precendence over the Grid Options exportOptions + if (columnDef?.hasOwnProperty('exportWithExcelFormat')) { + isExportWithExcelFormatEnabled = !!columnDef.exportWithExcelFormat; + } + return isExportWithExcelFormatEnabled; + } + /** * Get the grouped title(s) and its group title formatter, for example if we grouped by salesRep, the returned result would be:: 'Sales Rep: John Dow (2 items)' * @param itemObj @@ -570,26 +600,42 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ * For example if we grouped by "salesRep" and we have a Sum Aggregator on "sales", then the returned output would be:: ["Sum 123$"] * @param itemObj */ - protected readGroupedTotalRows(columns: Column[], itemObj: any): string[] { + protected readGroupedTotalRows(columns: Column[], itemObj: any): Array { const groupingAggregatorRowText = this._excelExportOptions.groupingAggregatorRowText || ''; - const outputStrings = [groupingAggregatorRowText]; + const outputStrings: Array = [groupingAggregatorRowText]; columns.forEach((columnDef) => { - let itemData = ''; - + let itemData: number | string | ExcelCellFormat = ''; + const fieldType = columnDef.outputType || columnDef.type || FieldType.string; const skippedField = columnDef.excludeFromExport || false; - // 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, this._grid); + // use different Excel Stylesheet Format as per the Field Type + if (this.isExportingWithExcelFormat(columnDef) && fieldType === FieldType.number) { + let groupCellFormat = this._groupTotalExcelFormats[columnDef.id]; + if (!groupCellFormat?.groupType) { + groupCellFormat = getExcelFormatFromGridFormatter(this._stylesheet, this._stylesheetFormats, columnDef, this._grid, 'group'); + this._groupTotalExcelFormats[columnDef.id] = groupCellFormat; + } + + if (itemObj[groupCellFormat.groupType]?.[columnDef.field] !== undefined) { + itemData = { + value: getGroupTotalValue(itemObj, groupCellFormat.groupType, columnDef.field), + metadata: { style: groupCellFormat.stylesheetFormatter?.id } + }; + } } else { - if (columnDef.groupTotalsFormatter) { - itemData = columnDef.groupTotalsFormatter(itemObj, columnDef, this._grid); + // 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, this._grid); + } else { + if (columnDef.groupTotalsFormatter) { + itemData = columnDef.groupTotalsFormatter(itemObj, columnDef, this._grid); + } } } // does the user want to sanitize the output data (remove HTML tags)? - if (columnDef.sanitizeDataExport || this._excelExportOptions.sanitizeDataExport) { + if (typeof itemData === 'string' && (columnDef.sanitizeDataExport || this._excelExportOptions.sanitizeDataExport)) { itemData = sanitizeHtmlToText(itemData); } diff --git a/packages/excel-export/src/excelUtils.spec.ts b/packages/excel-export/src/excelUtils.spec.ts index b98758f93..eeff7432e 100644 --- a/packages/excel-export/src/excelUtils.spec.ts +++ b/packages/excel-export/src/excelUtils.spec.ts @@ -1,7 +1,6 @@ -import { Column, ExcelStylesheet, FieldType, Formatters, GridOption, SlickGrid } from '@slickgrid-universal/common'; -import moment from 'moment-mini'; +import { Column, ExcelStylesheet, FieldType, Formatters, GridOption, GroupTotalFormatters, SlickGrid } from '@slickgrid-universal/common'; -import { useCellFormatByFieldType } from './excelUtils'; +import { getExcelFormatFromGridFormatter, getNumericFormatterOptions, useCellFormatByFieldType } from './excelUtils'; const mockGridOptions = { enableExcelExport: true, @@ -39,209 +38,231 @@ describe('excelUtils', () => { 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTime); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD HH:mm:ss' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28 15:07:59' }); + expect(output).toEqual('2012-02-28 15:07:59'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeIso); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD HH:mm:ss' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28 15:07:59' }); + expect(output).toEqual('2012-02-28 15:07:59'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeShortIso); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD HH:mm' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28 15:07' }); + expect(output).toEqual('2012-02-28 15:07'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeIsoAmPm); - 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' }); + expect(output).toEqual('2012-02-28 03:07:59 pm'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeIsoAM_PM); - 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' }); + expect(output).toEqual('2012-02-28 03:07:59 PM'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateEuro); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'DD/MM/YYYY' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/02/2012' }); + expect(output).toEqual('28/02/2012'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateEuroShort); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'D/M/YY' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/2/12' }); + expect(output).toEqual('28/2/12'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeEuro); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'DD/MM/YYYY HH:mm:ss' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/02/2012 15:07:59' }); + expect(output).toEqual('28/02/2012 15:07:59'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeShortEuro); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'DD/MM/YYYY HH:mm' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/02/2012 15:07' }); + expect(output).toEqual('28/02/2012 15:07'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeEuroAmPm); - 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' }); + expect(output).toEqual('28/02/2012 03:07:59 pm'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:59'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeEuroAM_PM); - 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' }); + expect(output).toEqual('28/02/2012 03:07:59 PM'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:46'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeEuroShort); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'D/M/YY H:m:s' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '28/2/12 15:7:46' }); + expect(output).toEqual('28/2/12 15:7:46'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const input = new Date('2012-02-28 15:07:46'); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeEuroShortAmPm); - 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' }); + expect(output).toEqual('28/2/12 3:7:46 pm'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateUs); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'MM/DD/YYYY' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '02/28/2012' }); + expect(output).toEqual('02/28/2012'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateUsShort); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'M/D/YY' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2/28/12' }); + expect(output).toEqual('2/28/12'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeUs); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'MM/DD/YYYY HH:mm:ss' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '02/28/2012 15:07:59' }); + expect(output).toEqual('02/28/2012 15:07:59'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeShortUs); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'MM/DD/YYYY HH:mm' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '02/28/2012 15:07' }); + expect(output).toEqual('02/28/2012 15:07'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeUsAmPm); - 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' }); + expect(output).toEqual('02/28/2012 03:07:59 pm'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeUsAM_PM); - 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' }); + expect(output).toEqual('02/28/2012 03:07:59 PM'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeUsShort); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'M/D/YY H:m:s' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2/28/12 15:7:59' }); + expect(output).toEqual('2/28/12 15:7:59'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); 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); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateTimeUsShortAmPm); - 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' }); + expect(output).toEqual('2/28/12 3:7:59 pm'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); - 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); + // 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, {}, 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' }); - }); + // 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); + const result = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); + const output = result.getDataValueCallback(input, undefined, FieldType.dateIso); - expect(createFormatSpy).toHaveBeenCalledWith({ format: 'YYYY-MM-DD' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: '2012-02-28' }); + expect(output).toEqual('2012-02-28'); + expect(result).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: undefined }); }); }); @@ -252,51 +273,605 @@ describe('excelUtils', () => { 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); + const output = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); - expect(createFormatSpy).toHaveBeenCalledWith({ format: '###0.00' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + expect(createFormatSpy).toHaveBeenCalledWith({ format: '0.00;"-"0.00' }); + expect(output).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: 135 }); }); - it('should call createFormat with a format of "###0.0##" when a number is provided minDecimal & maxDecimal formatter options', () => { + 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); + const output = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); - expect(createFormatSpy).toHaveBeenCalledWith({ format: '###0.0##' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + expect(createFormatSpy).toHaveBeenCalledWith({ format: '0.0##;"-"0.0##' }); + expect(output).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: 135 }); }); - 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); + 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, {}, column, gridStub); - expect(createFormatSpy).toHaveBeenCalledWith({ format: '€ ###0.00' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + expect(createFormatSpy).toHaveBeenCalledWith({ format: '"€"0.00;"-€"0.00' }); + expect(output).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: 135 }); }); 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); + const output = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); - expect(createFormatSpy).toHaveBeenCalledWith({ format: '#,##0.00' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + expect(createFormatSpy).toHaveBeenCalledWith({ format: '#,##0.00;"-"#,##0.00' }); + expect(output).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: 135 }); }); - it('should call createFormat with a format of "# ##0.00 USD" when a number is provided thousandSeparator & numberSuffix formatter options', () => { + it('should call createFormat with a format of "# ##0.00 USD" when a number is provided with 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); + const output = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); - expect(createFormatSpy).toHaveBeenCalledWith({ format: '# ##0.00 USD' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + expect(createFormatSpy).toHaveBeenCalledWith({ format: '# ##0.00" USD";"-"# ##0.00" USD"' }); + expect(output).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: 135 }); }); 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); + const output = useCellFormatByFieldType(stylesheetStub, {}, column, gridStub); - expect(createFormatSpy).toHaveBeenCalledWith({ format: '#,##0.00 USD;(#,##0.00 USD)' }); - expect(output).toEqual({ metadata: { style: mockedFormatId }, value: 12 }); + expect(createFormatSpy).toHaveBeenCalledWith({ format: '#,##0.00" USD";(#,##0.00" USD")' }); + expect(output).toEqual({ getDataValueCallback: expect.toBeFunction(), stylesheetFormatterId: 135 }); }); }); + describe('getNumericFormatterOptions() method', () => { + describe('with GroupTotalFormatters', () => { + it('should get formatter options for GroupTotalFormatters.avgTotalsDollar', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.avgTotalsDollar, + params: { displayNegativeNumberWithParentheses: true, thousandSeparator: ',', numberSuffix: ' USD' } + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 4, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: ',', + wrapNegativeNumber: true, + }); + }); + + it('should get formatter options for GroupTotalFormatters.sumTotalsDollarColoredBold', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollarColoredBold, + params: { thousandSeparator: ' ', decimalSeparator: ',', numberSuffix: ' USD' } + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: ',', + maxDecimal: 4, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: ' ', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.sumTotalsDollarColored', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollarColored, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 4, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.sumTotalsDollarBold', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollarBold, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 4, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.sumTotalsDollar', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollar, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 4, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.avgTotalsPercentage', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.avgTotalsPercentage, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: undefined, + minDecimal: undefined, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.avgTotals', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.avgTotals, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 2, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.minTotals', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.minTotals, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 2, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.maxTotals', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.maxTotals, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 2, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.sumTotalsColored', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsColored, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 2, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.sumTotals', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, + groupTotalsFormatter: GroupTotalFormatters.sumTotals, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 2, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for GroupTotalFormatters.sumTotalsBold', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, + groupTotalsFormatter: GroupTotalFormatters.sumTotalsBold, + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'group'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 2, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: '', + wrapNegativeNumber: false, + }); + }); + }); + + describe('with regular Formatters', () => { + it('should get formatter options for Formatters.dollarColoredBold', () => { + const column = { + type: FieldType.number, formatter: Formatters.dollarColoredBold, + params: { displayNegativeNumberWithParentheses: true, thousandSeparator: ',' } + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'cell'); + + expect(output).toEqual({ + 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, + params: { displayNegativeNumberWithParentheses: false, thousandSeparator: ' ' } + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'cell'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 4, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: ' ', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for Formatters.dollar', () => { + const column = { + type: FieldType.number, formatter: Formatters.dollar, + params: { displayNegativeNumberWithParentheses: false, thousandSeparator: ' ' } + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'cell'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 4, + minDecimal: 2, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: ' ', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for Formatters.percent', () => { + const column = { + type: FieldType.number, formatter: Formatters.percent, + params: { displayNegativeNumberWithParentheses: false, thousandSeparator: ' ' } + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'cell'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: undefined, + minDecimal: undefined, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: ' ', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for Formatters.percentComplete', () => { + const column = { + type: FieldType.number, formatter: Formatters.percentComplete, + params: { displayNegativeNumberWithParentheses: false, thousandSeparator: ' ' } + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'cell'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: undefined, + minDecimal: undefined, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: ' ', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for Formatters.percentSymbol', () => { + const column = { + type: FieldType.number, formatter: Formatters.percentSymbol, + params: { displayNegativeNumberWithParentheses: false, thousandSeparator: ' ' } + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'cell'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: undefined, + minDecimal: undefined, + numberPrefix: '', + numberSuffix: '', + thousandSeparator: ' ', + wrapNegativeNumber: false, + }); + }); + + it('should get formatter options for Formatters.decimal', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, + params: { displayNegativeNumberWithParentheses: false, thousandSeparator: ' ', numberPrefix: 'Dollar ', numberSuffix: ' USD' } + } as Column; + const output = getNumericFormatterOptions(column, gridStub, 'cell'); + + expect(output).toEqual({ + decimalSeparator: '.', + maxDecimal: 2, + minDecimal: 2, + numberPrefix: 'Dollar ', + numberSuffix: ' USD', + thousandSeparator: ' ', + wrapNegativeNumber: false, + }); + }); + }); + }); + + describe('getExcelFormatFromGridFormatter() method', () => { + describe('with GroupTotalFormatters', () => { + it('should get excel excel metadata style format for GroupTotalFormatters.avgTotalsPercentage', () => { + const column = { + type: FieldType.number, + formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.avgTotalsPercentage, + params: { displayNegativeNumberWithParentheses: true, thousandSeparator: ',', numberSuffix: ' USD' } + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'avg', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.avgTotalsDollar', () => { + const column = { + type: FieldType.number, + formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.avgTotalsDollar, + params: { thousandSeparator: ' ', decimalSeparator: ',', numberSuffix: ' USD' } + } as Column; + + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'avg', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.sumTotalsDollarColored', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.avgTotals, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'avg', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.minTotals', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.minTotals, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'min', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.maxTotals', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.maxTotals, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'max', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.sumTotalsColored', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsColored, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'sum', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.sumTotalsDollarColoredBold', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollarColoredBold, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'sum', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.sumTotalsDollarColored', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollarColored, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'sum', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.sumTotalsDollarBold', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollarBold, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'sum', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.sumTotalsDollar', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollar, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'sum', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.sumTotals', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotals, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'sum', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style format for GroupTotalFormatters.sumTotalsBold', () => { + const column = { + type: FieldType.number, formatter: Formatters.decimal, groupTotalsFormatter: GroupTotalFormatters.sumTotalsBold, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'group'); + + expect(output).toEqual({ groupType: 'sum', stylesheetFormatter: { id: 135 } }); + }); + + 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}`, + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, { numberFormatter: { id: 3 } }, columnDef, gridStub, 'group'); + + expect(output).toEqual({ groupType: '', stylesheetFormatter: { id: 3 } }); + }); + }); + + describe('with regular Formatters', () => { + it('should get excel excel metadata style format for Formatters.dollarColoredBold', () => { + const column = { + type: FieldType.number, formatter: Formatters.dollarColoredBold, + params: { displayNegativeNumberWithParentheses: true, 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.dollarColored', () => { + const column = { + type: FieldType.number, + formatter: Formatters.dollarColored, + params: { 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', () => { + const column = { + type: FieldType.number, + formatter: Formatters.dollar, + params: { 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.percent', () => { + const column = { + type: FieldType.number, + formatter: Formatters.percent, + params: { 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.percentComplete', () => { + const column = { + type: FieldType.number, + formatter: Formatters.percentComplete, + params: { 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.percentSymbol', () => { + const column = { + type: FieldType.number, + formatter: Formatters.percentSymbol, + params: { 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.decimal', () => { + const column = { + type: FieldType.number, + formatter: Formatters.decimal, + params: { displayNegativeNumberWithParentheses: false, thousandSeparator: ' ', numberPrefix: 'Dollar ', numberSuffix: ' USD' } + } as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, {}, column, gridStub, 'cell'); + + expect(output).toEqual({ groupType: '', stylesheetFormatter: { id: 135 } }); + }); + + it('should get excel excel metadata style with regular number format when a custom Formatter is provided', () => { + const columnDef = { + type: FieldType.number, + formatter: () => `Something rendered`, + } as unknown as Column; + const output = getExcelFormatFromGridFormatter(stylesheetStub, { numberFormatter: { id: 3 } }, columnDef, gridStub, 'cell'); + + expect(output).toEqual({ groupType: '', stylesheetFormatter: { id: 3 } }); + }); + }); + }); }); \ No newline at end of file diff --git a/packages/excel-export/src/excelUtils.ts b/packages/excel-export/src/excelUtils.ts index 2156a1338..eaa7a072d 100644 --- a/packages/excel-export/src/excelUtils.ts +++ b/packages/excel-export/src/excelUtils.ts @@ -1,11 +1,45 @@ -import { Column, ExcelCellFormat, ExcelStylesheet, FieldType, Formatters, getValueFromParamsOrFormatterOptions, mapMomentDateFormatWithFieldType, SlickGrid } from '@slickgrid-universal/common'; +import { + Column, + ExcelCellFormat, + ExcelStylesheet, + FieldType, + Formatters, + FormatterType, + GroupTotalFormatters, + isNumber, + mapMomentDateFormatWithFieldType, + retrieveFormatterOptions, + sanitizeHtmlToText, + 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 +export type ExcelFormatter = object & { id: number; }; +export type GetDataValueCallback = (data: Date | string | number, excelFormatterId: number | undefined, fieldType: typeof FieldType[keyof typeof FieldType]) => Date | string | number | ExcelCellFormat; + +// define all type of potential excel data function callbacks +const getExcelInputDataCallback: GetDataValueCallback = (data) => data; +const getExcelNumberCallback: GetDataValueCallback = (data, excelFormatterId) => ({ + value: isNumber(data) ? +data : data, + metadata: { style: excelFormatterId } +}); +const getExcelDateCallback: GetDataValueCallback = (data, _excelFormatterId, fieldType) => { + let outputData: any; + if (data) { + const dateFormat: string = mapMomentDateFormatWithFieldType(fieldType); + const outputDate: moment_.Moment = moment(data, dateFormat, false); + if (outputDate.isValid()) { + outputData = outputDate.format(dateFormat); + } + } + return outputData ?? data; +}; + /** 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 { +export function useCellFormatByFieldType(stylesheet: ExcelStylesheet, stylesheetFormatters: any, columnDef: Column, grid: SlickGrid) { const fieldType = columnDef.outputType || columnDef.type || FieldType.string; - let outputData: ExcelCellFormat | string | Date | moment_.Moment = data; + let stylesheetFormatterId: number | undefined; switch (fieldType) { case FieldType.dateTime: @@ -32,61 +66,202 @@ export function useCellFormatByFieldType(stylesheet: ExcelStylesheet, stylesheet 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; + return { stylesheetFormatterId: undefined, getDataValueCallback: getExcelDateCallback }; 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; + stylesheetFormatterId = getExcelFormatFromGridFormatter(stylesheet, stylesheetFormatters, columnDef, grid, 'cell').stylesheetFormatter.id; + return { stylesheetFormatterId, getDataValueCallback: getExcelNumberCallback }; default: - outputData = data; + stylesheetFormatterId = undefined; + break; + } + return { stylesheetFormatterId, getDataValueCallback: getExcelInputDataCallback }; +} + +export function getGroupTotalValue(totals: any, groupType: string, colField: string) { + return totals?.[groupType]?.[colField] ?? 0; +} + +/** Get numeric formatter options when defined or use default values (minDecimal, maxDecimal, thousandSeparator, decimalSeparator, wrapNegativeNumber) */ +export function getNumericFormatterOptions(columnDef: Column, grid: SlickGrid, formatterType: FormatterType) { + let dataType: 'decimal' | 'dollar' | 'percent' | 'regular'; + + if (formatterType === 'group') { + switch (columnDef.groupTotalsFormatter) { + case GroupTotalFormatters.avgTotalsDollar: + case GroupTotalFormatters.sumTotalsDollarColoredBold: + case GroupTotalFormatters.sumTotalsDollarColored: + case GroupTotalFormatters.sumTotalsDollarBold: + case GroupTotalFormatters.sumTotalsDollar: + dataType = 'dollar'; + break; + case GroupTotalFormatters.avgTotalsPercentage: + dataType = 'percent'; + break; + case GroupTotalFormatters.avgTotals: + case GroupTotalFormatters.minTotals: + case GroupTotalFormatters.maxTotals: + case GroupTotalFormatters.sumTotalsColored: + case GroupTotalFormatters.sumTotals: + case GroupTotalFormatters.sumTotalsBold: + default: + // side note, formatters are using "regular" without any decimal limits (min, max), + // however in Excel export with custom format that doesn't work so well, we should use "decimal" to at least show optional decimals with "##" + dataType = 'decimal'; + break; + } + } else { + switch (columnDef.formatter) { + case Formatters.dollarColoredBold: + case Formatters.dollarColored: + case Formatters.dollar: + dataType = 'dollar'; + break; + case Formatters.percent: + case Formatters.percentCompleteBar: + case Formatters.percentCompleteBarWithText: + case Formatters.percentComplete: + 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 outputData as string; + return retrieveFormatterOptions(columnDef, grid, dataType, formatterType); } -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 +export function getExcelFormatFromGridFormatter(stylesheet: ExcelStylesheet, stylesheetFormatters: any, columnDef: Column, grid: SlickGrid, formatterType: FormatterType) { + let format = ''; + let groupType = ''; + let stylesheetFormatter: undefined | ExcelFormatter; + const fieldType = columnDef.outputType || columnDef.type || FieldType.string; + + if (formatterType === 'group') { + switch (columnDef.groupTotalsFormatter) { + case GroupTotalFormatters.avgTotals: + case GroupTotalFormatters.avgTotalsDollar: + case GroupTotalFormatters.avgTotalsPercentage: + groupType = 'avg'; + break; + case GroupTotalFormatters.minTotals: + groupType = 'min'; + break; + case GroupTotalFormatters.maxTotals: + groupType = 'max'; + break; + case GroupTotalFormatters.sumTotals: + case GroupTotalFormatters.sumTotalsBold: + case GroupTotalFormatters.sumTotalsColored: + case GroupTotalFormatters.sumTotalsDollar: + case GroupTotalFormatters.sumTotalsDollarColoredBold: + case GroupTotalFormatters.sumTotalsDollarColored: + case GroupTotalFormatters.sumTotalsDollarBold: + groupType = 'sum'; + break; + default: + stylesheetFormatter = stylesheetFormatters.numberFormatter; + break; + } + } else { + switch (fieldType) { + case FieldType.number: + switch (columnDef.formatter) { + case Formatters.dollarColoredBold: + case Formatters.dollarColored: + case Formatters.dollar: + case Formatters.percent: + case Formatters.percentComplete: + case Formatters.percentCompleteBar: + case Formatters.percentCompleteBarWithText: + case Formatters.percentSymbol: + case Formatters.decimal: + format = createExcelFormatFromGridFormatter(columnDef, grid, 'cell'); + break; + default: + stylesheetFormatter = stylesheetFormatters.numberFormatter; + break; + } + break; + } } - return stylesheetFormatters[format]; + + if (!stylesheetFormatter && (columnDef.formatter || columnDef.groupTotalsFormatter)) { + format = createExcelFormatFromGridFormatter(columnDef, grid, formatterType, groupType); + if (!stylesheetFormatters.hasOwnProperty(format)) { + stylesheetFormatters[format] = stylesheet.createFormat({ format }); // save new formatter with its format as a prop key + } + stylesheetFormatter = stylesheetFormatters[format] as ExcelFormatter; + } + return { stylesheetFormatter: stylesheetFormatter as ExcelFormatter, groupType }; +} + +// -- +// private functions +// ------------------ + +function createFormatFromNumber(formattedVal: string) { + // full number syntax can have up to 7 sections, for example:: + // Total: ($10,420.55 USD) Expensed + const [ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _, + prefix, + openBraquet, + symbolPrefix, + number, + symbolSuffix, + closingBraquet, + suffix + ] = formattedVal?.match(/^([^\d\(\-]*)([\(]?)([^\d]*)([\-]?[\w]]?[\d\s]*[.,\d]*[\d]*[^)\s\%]?)([^\d.,)]*)([\)]?)([^\d]*)$/i) || []; + + // replace 1's by 0's (required numbers) and replace 2's by "#" (optional numbers) + const replacedNumber = (number || '').replace(/1/g, '0').replace(/[2]/g, '#'); + + // console.log('createFormatFromNumber', formattedVal.trim(), '|prefix:', prefix ?? '', '|openBraquet:', openBraquet ?? '', '|symbolPrefix:', symbolPrefix ?? '', '|input:', replacedNumber, '|symbolSuffix:', symbolSuffix ?? '', '|closingBraquet:', closingBraquet ?? '', '|suffix:', suffix ?? ''); + + const format = [ + escapeQuotes(prefix ?? ''), + openBraquet ?? '', + escapeQuotes(symbolPrefix ?? ''), + replacedNumber, + escapeQuotes(symbolSuffix ?? ''), + closingBraquet ?? '', + escapeQuotes(suffix ?? '') + ].join(''); + return format.replace(',', '\,'); +} + +function createExcelFormatFromGridFormatter(columnDef: Column, grid: SlickGrid, formatterType: FormatterType, groupType = '') { + let outputFormat = ''; + let positiveFormat = ''; + let negativeFormat = ''; + const { minDecimal, maxDecimal, thousandSeparator } = getNumericFormatterOptions(columnDef, grid, formatterType); + const leftInteger = thousandSeparator ? '2220' : '0'; + const testingNo = parseFloat(`${leftInteger}.${excelTestingDecimalNumberPadding(minDecimal, maxDecimal)}`); + + if (formatterType === 'group' && columnDef.groupTotalsFormatter) { + positiveFormat = sanitizeHtmlToText(columnDef.groupTotalsFormatter({ [groupType]: { [columnDef.field]: testingNo } }, columnDef, grid)); + negativeFormat = sanitizeHtmlToText(columnDef.groupTotalsFormatter({ [groupType]: { [columnDef.field]: -testingNo } }, columnDef, grid)); + } else if (columnDef.formatter) { + positiveFormat = sanitizeHtmlToText(columnDef.formatter(0, 0, testingNo, columnDef, {}, grid) as string); + negativeFormat = sanitizeHtmlToText(columnDef.formatter(0, 0, -testingNo, columnDef, {}, grid) as string); + } + if (positiveFormat && negativeFormat) { + outputFormat = createFormatFromNumber(positiveFormat) + ';' + createFormatFromNumber(negativeFormat); + } + return outputFormat; +} + +function escapeQuotes(val: string) { + return val ? `"${val}"` : val; } /** 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 excelTestingDecimalNumberPadding(minDecimal: number, maxDecimal: number) { + return textPadding('1', minDecimal) + textPadding('2', maxDecimal - minDecimal); } function textPadding(numberStr: string, count: number): string { diff --git a/packages/excel-export/src/interfaces/excelExportOption.interface.ts b/packages/excel-export/src/interfaces/excelExportOption.interface.ts deleted file mode 100644 index a23f1a5d3..000000000 --- a/packages/excel-export/src/interfaces/excelExportOption.interface.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { FileType } from '@slickgrid-universal/common'; -import { ExcelWorksheet } from './excelWorksheet.interface'; -import { ExcelWorkbook } from './excelWorkbook.interface'; - -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; - - /** filename (without extension) */ - filename?: string; - - /** file type format, .xls/.xlsx (this will provide the extension) */ - format?: FileType.xls | FileType.xlsx; - - /** The column header title (at A0 in Excel) of the Group by. If nothing is provided it will use "Group By" (which is a translated value of GROUP_BY i18n) */ - groupingColumnHeaderTitle?: string; - - /** The default text to display in 1st column of the File Export, which will identify that the current row is a Grouping Aggregator */ - groupingAggregatorRowText?: string; - - /** Symbol use to show that the group title is collapsed (you can use unicode like '⮞' or '\u25B7') */ - groupCollapsedSymbol?: string; - - /** Symbol use to show that the group title is expanded (you can use unicode like '⮟' or '\u25BD') */ - groupExpandedSymbol?: string; - - /** Defaults to false, which leads to Sanitizing all data (striping out any HTML tags) when being evaluated on export. */ - sanitizeDataExport?: boolean; - - /** Defaults to "Sheet1", Excel Sheet Name */ - sheetName?: string; - - /** Add a Custom Excel Header on first row of the Excel Sheet */ - customExcelHeader?: (workbook: ExcelWorkbook, sheet: ExcelWorksheet) => void; -} diff --git a/packages/excel-export/src/interfaces/excelWorkbook.interface.ts b/packages/excel-export/src/interfaces/excelWorkbook.interface.ts deleted file mode 100644 index 58ad6bd53..000000000 --- a/packages/excel-export/src/interfaces/excelWorkbook.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ExcelWorksheet } from './excelWorksheet.interface'; -import { ExcelStylesheet } from './excelStylesheet.interface'; - -export interface ExcelWorkbook { - addDrawings: (drawings: any) => any; - addTable: (table: any) => any; - addWorksheet: (worksheet: any) => any; - addMedia: (type: any, fileName: any, fileData: any, contentType: any) => any; - createContentTypes: () => any; - createWorksheet: (config: any) => ExcelWorksheet; - createWorkbookRelationship: () => any; - generateFiles: () => any; - getStyleSheet: () => ExcelStylesheet; - setPrintTitleTop: (inSheet: any, inRowCount: any) => any; - setPrintTitleLeft: (inSheet: any, inRowCount: any) => any; - toXML: () => any; -} diff --git a/packages/excel-export/src/interfaces/excelWorksheet.interface.ts b/packages/excel-export/src/interfaces/excelWorksheet.interface.ts deleted file mode 100644 index c7782e1d2..000000000 --- a/packages/excel-export/src/interfaces/excelWorksheet.interface.ts +++ /dev/null @@ -1,36 +0,0 @@ -export interface ExcelWorksheet { - relations: any; - columnFormats: any[]; - data: any[]; - mergedCells: any[]; - columns: any[]; - sheetProtection: boolean; - hyperlinks: any[]; - sheetView: any; - showZeros: boolean; - - initialize: (config: any) => any; - compilePageDetailPackage: (data: any) => any; - compilePageDetailPiece: (data: any) => any; - exportFooter: (doc: any) => any; - exportHeader: (doc: any) => any; - collectSharedStrings: () => any[]; - exportColumns: (doc: any) => any; - exportPageSettings: (doc: any, worksheet: any) => any; - exportData: () => any; - addDrawings: (drawings: any) => void; - addTable: (table: any) => void; - freezePane: (column: number, row: number, cell: string) => void; - importData: (data: any) => void; - mergeCells: (cell1: string, cell2: string) => void; - setColumns: (columns: any[]) => void; - setColumnFormats: (columnFormats: any[]) => void; - setData: (data: any[]) => void; - setFooter: (footers: any) => void; - setHeader: (headers: any) => void; - setPageOrientation: (orientation: any) => void; - setPageMargin: (input: any) => void; - setSharedStringCollection: (collection: string[]) => void; - setRowInstructions: (row: number, instructions: any) => void; - toXML: () => any; -} diff --git a/packages/excel-export/src/interfaces/index.ts b/packages/excel-export/src/interfaces/index.ts index c18629845..839a812ea 100644 --- a/packages/excel-export/src/interfaces/index.ts +++ b/packages/excel-export/src/interfaces/index.ts @@ -1,6 +1,3 @@ export * from './excelCellFormat.interface'; -export * from './excelExportOption.interface'; export * from './excelMetadata.interface'; export * from './excelStylesheet.interface'; -export * from './excelWorkbook.interface'; -export * from './excelWorksheet.interface';