Skip to content

Commit

Permalink
feat(exports): add Excel auto-detect format by field types & formatters
Browse files Browse the repository at this point in the history
- this new feature is especially useful with cell as numbers, which prior to this PR was exporting wasn't always exporting cell as numbers, this PR fixes this
- note that Date fields will NOT be exported as Date format but rather text fields using MomentJS detected format, dealing with Dates in Excel is just too hard to deal with in code while it's easy enough for the user to convert text fields to dates afterward
- Group Total will also be exported as numbers via custom format, for example: `"Total: $"0.00##;"Total: "($0.00##)` so the Total text will show in Excel but the value will actually be saved as a number
  • Loading branch information
ghiscoding committed Dec 18, 2022
1 parent 0055f8a commit 53d7753
Show file tree
Hide file tree
Showing 37 changed files with 1,423 additions and 796 deletions.
10 changes: 4 additions & 6 deletions examples/webpack-demo-vanilla-bundle/src/examples/example02.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<b>Total</b>: ' /* , groupFormatterSuffix: ' USD' */ }
params: { displayNegativeNumberWithParentheses: true, numberPrefix: '€ ', minDecimal: 2, maxDecimal: 4, groupFormatterPrefix: '<b>Total</b>: ' /* , groupFormatterSuffix: ' USD' */ },
},
{
id: 'effortDriven', name: 'Effort Driven',
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ export class Example7 {
showCustomFooter: true,
enableExcelExport: true,
excelExportOptions: {
exportWithFormatter: true,
sanitizeDataExport: true
},
enableCellMenu: true,
Expand Down
9 changes: 9 additions & 0 deletions packages/common/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 11 additions & 9 deletions packages/common/src/formatters/decimalFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@ 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.
* You can pass "minDecimal" and/or "maxDecimal" to the "params" property.
* 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;
};
16 changes: 9 additions & 7 deletions packages/common/src/formatters/dollarColoredBoldFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<span style="color:${colorStyle}; font-weight:bold;">${formattedNumber}</span>`;
}
return value;
Expand Down
16 changes: 9 additions & 7 deletions packages/common/src/formatters/dollarColoredFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<span style="color:${colorStyle}">${formattedNumber}</span>`;
}
return value;
Expand Down
16 changes: 9 additions & 7 deletions packages/common/src/formatters/dollarFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
44 changes: 42 additions & 2 deletions packages/common/src/formatters/formatterUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -96,12 +136,12 @@ export function exportWithFormatterWhenDefined<T = any>(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;
}

Expand Down
16 changes: 9 additions & 7 deletions packages/common/src/formatters/percentCompleteFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<span style="color:${colorStyle}">${outputFormattedValue}</span>`;
}
Expand Down
16 changes: 9 additions & 7 deletions packages/common/src/formatters/percentFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
16 changes: 9 additions & 7 deletions packages/common/src/formatters/percentSymbolFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
1 change: 1 addition & 0 deletions packages/common/src/global-grid-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export const GlobalGridOptions: GridOption = {
excelExportOptions: {
addGroupIndentation: true,
exportWithFormatter: false,
exportWithExcelFormat: true,
filename: 'export',
format: FileType.xlsx,
groupingColumnHeaderTitle: 'Group By',
Expand Down
Loading

0 comments on commit 53d7753

Please sign in to comment.