@adaptabletools/adaptable
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
779 lines (778 loc) • 37.7 kB
JavaScript
import { ExportModuleId } from '../Utilities/Constants/ModuleConstants';
import { waitForTimeout } from '../Utilities/waitForTimeout';
import { convertCSSAbsoluteFontSizeToPt, getVariableColor, sanitizeStyle, } from '../Utilities/Helpers/StyleHelper';
import { DateFormatter } from '../Utilities/Helpers/FormatHelper';
import tinycolor from 'tinycolor2';
import StringExtensions from '../Utilities/Extensions/StringExtensions';
import { createUuid } from '../AdaptableState/Uuid';
import { inferSchema, initParser } from 'udsv';
import { AG_GRID_GROUPED_COLUMN, AG_GRID_SELECTION_COLUMN, } from '../Utilities/Constants/GeneralConstants';
export class AgGridExportAdapter {
constructor(_adaptableInstance) {
this._adaptableInstance = _adaptableInstance;
/**
* !!!
* do NOT mutate this array reference, this is passed only initially to AG Grid and we can only change it's internal state
*/
this.DANGER_excelStyles = [];
this.originalExcelStyles = [];
this.excelStylesCache = {};
this.cellClassKey2excelStyleIdMap = {};
this.excelStylesWithFormattedDate = {};
}
get agGridApi() {
return this._adaptableInstance.agGridAdapter.getAgGridApi();
}
get adaptableApi() {
return this._adaptableInstance.api;
}
get exportOptions() {
return this._adaptableInstance.api.optionsApi.getExportOptions();
}
get logger() {
return this._adaptableInstance.logger;
}
static getExcelClassNameForCell(colId, primaryKeyValue, userDefinedCellClass) {
let excelClassName = `--excel-cell-${colId}-${primaryKeyValue}`;
if (excelClassName.indexOf(' ') > 0) {
excelClassName = excelClassName.replace(/\s/g, '_');
}
return userDefinedCellClass != null
? `${excelClassName}-${Array.isArray(userDefinedCellClass)
? userDefinedCellClass.join('-')
: userDefinedCellClass}`
: excelClassName;
}
destroy() {
this.excelStylesCache = null;
this.cellClassKey2excelStyleIdMap = null;
this.excelStylesWithFormattedDate = null;
}
async exportData(config) {
const { report, format, showProgressIndicator } = config;
try {
if (showProgressIndicator) {
this.adaptableApi.userInterfaceApi.showProgressIndicator({
text: `${report.Name} Export in progress...`,
});
// waitForTimeout required to give the ProgressIndicator rendering a head-start (see rAF in ProgressIndicator implementation)
// see #raf_progress_indicator
await waitForTimeout(16);
}
this.adaptableApi.exportApi.internalApi.setExportInProgress(config.report.Name, config.format, config.destination);
const { exportContext, exportParams } = this.buildExportProcessData(config);
if (exportContext.isVisualExcelReport) {
// FIXME AFL patch styles only for exported columns!
// or even better, only cells
this.patchExcelStyles();
}
// 1. easiest case, we download the file using AG Grid
// these methods will automatically handle the file download
if (exportContext.destination === 'Download' && exportContext.isExcelReport) {
this.agGridApi.exportDataAsExcel(exportParams);
return;
}
if (exportContext.destination === 'Download' && format === 'CSV') {
this.agGridApi.exportDataAsCsv(exportParams);
return;
}
// 2. we have to handle the export ourselves
// we get the report data and return it, so that the caller can handle it
if (exportContext.isExcelReport) {
return {
type: 'excel',
data: this.agGridApi.getDataAsExcel(exportParams),
};
}
const csvReportData = this.agGridApi.getDataAsCsv(exportParams);
if (exportContext.format === 'CSV') {
return {
type: 'csv',
data: csvReportData,
};
}
// convert the CSV data to JSON
const jsonReportData = this.mapCsvToJson(csvReportData, exportContext);
return {
type: 'json',
data: jsonReportData,
};
}
catch (error) {
this.logger.consoleError(`Error exporting ${report.Name} in ${format} format to ${config.destination}`, error);
}
finally {
/**
* Cleanup export process
*/
this.adaptableApi.exportApi.internalApi.setExportComplete();
if (format === 'VisualExcel') {
this.resetExcelStyles();
}
if (showProgressIndicator) {
this.adaptableApi.userInterfaceApi.hideProgressIndicator();
}
}
}
/**
* Creates export context and parameters for a given export configuration
*/
buildExportProcessData(config) {
const exportContext = this.buildExportProcessContext(config);
if (exportContext.isVisualExcelReport) {
this.patchExcelStyles();
}
const exportParams = this.buildExportParams(exportContext);
exportContext.exportedColumnIds = exportParams.columnKeys;
return {
exportContext,
exportParams,
};
}
buildExportParams(exportContext) {
const baseExportParams = this.buildBaseExportParams(exportContext);
if (exportContext.format === 'Excel' || exportContext.format === 'VisualExcel') {
const excelExportParams = baseExportParams;
excelExportParams.sheetName = 'Sheet 1';
return excelExportParams;
}
// for everything else we use the CSV export
const csvExportParams = baseExportParams;
csvExportParams.columnSeparator = this.getCsvSeparator(exportContext);
return csvExportParams;
}
buildBaseExportParams(exportContext) {
// TODO AFL: get custom info from user/ExportOptions for ExcelExportParams?
const params = {};
const { columnKeys, exportedRows, shouldRowBeSkipped, skipRowGroups, rowGroupExpandState } = this.getReportColsAndRows(exportContext);
params.columnKeys = columnKeys;
params.exportedRows = exportedRows;
params.shouldRowBeSkipped = shouldRowBeSkipped;
params.skipRowGroups = skipRowGroups;
// only Excel supports expanded state configuration, but setting it for all formats won't hurt, it will just be ignored
params.rowGroupExpandState = rowGroupExpandState;
params.fileName = this.adaptableApi.exportApi.internalApi.getReportFileName(exportContext.report.Name, exportContext.format, exportContext.destination);
params.skipColumnHeaders = this.computeSkipColumnHeaders(exportContext);
if (!exportContext.isExcelReport && exportContext.format !== 'CSV') {
// if we map CSV to JSON, we don't want to skip the column headers
params.skipColumnHeaders = false;
}
params.getCustomContentBelowRow = this.computeGetCustomContentBelowRow(exportContext);
params.processCellCallback = this.computeProcessCellCallback(exportContext);
params.processRowGroupCallback = this.computeProcessRowGroupCallback(exportContext);
return params;
}
computeProcessRowGroupCallback(exportContext) {
return (params) => {
// recreating the standard AG Grid styling for row groups: 'Parent -> Child'
// additionally the values are formatted
let rowGroupNode = params.node;
const isFooterRow = rowGroupNode.footer;
const rowGroupSummary = [
this.processRowGroupForExcelExport(rowGroupNode, exportContext) ?? '',
];
while (rowGroupNode.parent) {
rowGroupNode = rowGroupNode.parent;
const formattedParentNode = this.processRowGroupForExcelExport(rowGroupNode, exportContext);
if (formattedParentNode) {
rowGroupSummary.push(formattedParentNode);
}
}
let summary = rowGroupSummary.reverse().join(' -> ');
if (isFooterRow) {
summary = `Total: ${summary}`;
}
return summary;
};
}
computeProcessCellCallback(exportContext) {
return (params) => {
const { node, column, value } = params;
const colId = column.getColId();
if (exportContext.report.ReportRowScope === 'SelectedRows') {
// handle case when this cell(column) is not selected
if (
// see #isCellPartOfSelection
!exportContext.isCellPartOfSelection(params.node.id, colId))
return '';
}
const columnId = column.getColId();
if (node?.group &&
(this.adaptableApi.columnApi.isAutoRowGroupColumn(columnId) ||
// we would still need to process the cell if this is a group row with an aggregated value
node?.aggData?.[columnId] == undefined)) {
// skip processing of row groups, this was already handled in processRowGroupCallback()
return value;
}
return this.processCellForExcelExport(node, columnId, exportContext);
};
}
getReportColsAndRows(exportContext) {
const { report, exportedColumnIds, isExportingVisibleColumnToJSON } = exportContext;
/**
* 1. columnKeys
*/
const columnKeys = exportedColumnIds;
/**
* 2. exportedRows
*/
let exportedRows = 'all'; // 'AllRows' or 'ExpressionRows'
if (report.ReportRowScope === 'VisibleRows' || report.ReportRowScope === 'SelectedRows') {
exportedRows = 'filteredAndSorted';
}
/**
* 3. shouldRowBeSkipped
*/
let shouldRowBeSkipped;
// 'AllRows' or 'VisibleRows': no need to skip rows as we are exporting all rows
if (report.ReportRowScope === 'SelectedRows') {
// mapping of row ids to column ids which contain selected cells
// or true if the whole row is selected
const selectedRowColumnMapping = new Map();
// ids of selected rows
this.adaptableApi.gridApi.getSelectedRowInfo().gridRows?.forEach((row) => {
if (row?.rowNode?.id) {
selectedRowColumnMapping.set(row.rowNode.id, true);
}
});
// ids of rows which contain selected cells
this.adaptableApi.gridApi.getSelectedCellInfo().gridCells?.map((gridCell) => {
if (gridCell.rowNode?.id) {
const selectedColumns = selectedRowColumnMapping.get(gridCell.rowNode.id);
if (selectedColumns == null) {
selectedRowColumnMapping.set(gridCell.rowNode.id, new Set([gridCell.column.columnId]));
}
else if (selectedColumns instanceof Set) {
selectedColumns.add(gridCell.column.columnId);
}
// else no nothing, the entire row is selected
}
});
shouldRowBeSkipped = (params) => {
return !selectedRowColumnMapping.has(params.node.id);
};
// #isCellPartOfSelection
exportContext.isCellPartOfSelection = (rowId, columnId) => {
const selectedRow = selectedRowColumnMapping.get(rowId);
if (selectedRow === true) {
return true;
}
if (selectedRow instanceof Set) {
return selectedRow.has(columnId);
}
return false;
};
}
if (report.ReportRowScope === 'ExpressionRows') {
shouldRowBeSkipped = (params) => {
const rowNodeMatchesExpression = this.adaptableApi.internalApi
.getQueryLanguageService()
.evaluateBooleanExpression(report?.Query?.BooleanExpression, ExportModuleId, params.node);
return !rowNodeMatchesExpression;
};
}
/**
* 4. skipRowGroups
*/
let skipRowGroups;
if (report.ReportRowScope === 'AllRows' || isExportingVisibleColumnToJSON) {
// we want to return the entire data, without the groups
skipRowGroups = true;
}
if (this.isTreeDataGrid()) {
// we need the group rows for TreeData
skipRowGroups = false;
}
/**
* 5. rowGroupExpandState
*/
let rowGroupExpandState = 'expanded';
if (report.ReportRowScope === 'VisibleRows') {
rowGroupExpandState = 'match';
}
return { columnKeys, exportedRows, shouldRowBeSkipped, skipRowGroups, rowGroupExpandState };
}
isTreeDataGrid() {
return this.adaptableApi.gridApi.isTreeDataGrid();
}
buildExportProcessContext(config) {
const { report } = config;
const currentLayout = this.adaptableApi.layoutApi.getCurrentLayout();
const groupColumnIds = this.isTreeDataGrid()
? [AG_GRID_GROUPED_COLUMN]
: currentLayout.RowGroupedColumns ?? currentLayout.PivotGroupedColumns;
const pivotColumnIds = currentLayout.PivotColumns;
const isExportingVisibleColumnToJSON = config.format === 'JSON' && report.ReportColumnScope === 'VisibleColumns';
const exportableColumnIds = this.adaptableApi.columnApi
.getExportableColumns()
.map((col) => col.columnId);
const exportableColumnIdsSet = new Set(exportableColumnIds);
// we compute the exported column keys early, as we need them for other parts of the export process
const onlyExportableColumnIds = (columnId) => exportableColumnIdsSet.has(columnId);
const getVisibleColumnIds = () => {
return this.adaptableApi.layoutApi
.getCurrentVisibleColumnIdsForTableLayout()
.filter(onlyExportableColumnIds);
};
let exportedColumnIds = [];
switch (report.ReportColumnScope) {
case 'AllColumns':
exportedColumnIds = exportableColumnIds.filter((colId) => this.isTreeDataGrid() || !this.adaptableApi.columnApi.isAutoRowGroupColumn(colId));
break;
case 'VisibleColumns':
exportedColumnIds = getVisibleColumnIds();
if (isExportingVisibleColumnToJSON) {
// for JSON we will export CSV and then convert to JSON
// in this case we have to map the auto-group columns to the real keys
exportedColumnIds = [
...groupColumnIds,
...exportedColumnIds.filter((columnId) => this.isTreeDataGrid() || !this.adaptableApi.columnApi.isAutoRowGroupColumn(columnId)),
];
}
break;
case 'SelectedColumns':
const selectedRowInfo = this.adaptableApi.gridApi.getSelectedRowInfo();
if (selectedRowInfo?.gridRows?.length) {
// if there is at least one selected row, we have to export all visible columns
exportedColumnIds = getVisibleColumnIds();
}
else {
// otherwise, we can export only columns which contain selected cells
const selectedColumnIds = this.adaptableApi.gridApi
.getSelectedCellInfo()
.columns?.map((column) => column.columnId);
const selectedColumnIdsSet = new Set(selectedColumnIds.filter(onlyExportableColumnIds));
// ensure that column order is preserved
exportedColumnIds = getVisibleColumnIds().filter((colId) => selectedColumnIdsSet.has(colId));
}
break;
case 'ScopeColumns':
if ('ColumnIds' in report.Scope) {
exportedColumnIds = report.Scope.ColumnIds?.filter(onlyExportableColumnIds);
}
else {
exportedColumnIds = this.adaptableApi.columnScopeApi
.getColumnsInScope(report.Scope)
.map((col) => col.columnId)
.filter(onlyExportableColumnIds);
}
break;
default:
this.adaptableApi.logError('Invalid ReportColumnScope: ', report.ReportColumnScope);
// fallback to all columns
exportedColumnIds = exportableColumnIds;
}
return {
...config,
exportedColumnIds,
isExcelReport: config.format === 'Excel' || config.format === 'VisualExcel',
isVisualExcelReport: config.format === 'VisualExcel',
isExportingVisibleColumnToJSON,
// will be overriden later, see #isCellPartOfSelection
isCellPartOfSelection: () => false,
getCurrent: () => {
return {
layout: currentLayout,
groupColumnIds,
pivotColumnIds,
};
},
};
}
processCellForExcelExport(rowNode, columnId, exportContext) {
if (this.adaptableApi.exportApi.internalApi.isVisualDataExportInProgress()) {
const cellKey = AgGridExportAdapter.getExcelClassNameForCell(columnId, this.adaptableApi.gridApi.getPrimaryKeyValueForRowNode(rowNode));
const isoFormattedDate = this.getExcelStyleWithFormattedDate(cellKey);
if (isoFormattedDate) {
// this is a Date cell which will be formatted by Excel
return isoFormattedDate;
}
}
return this.adaptableApi.exportApi.internalApi.getCellExportValueFromRowNode(rowNode, columnId, exportContext.isVisualExcelReport);
}
processRowGroupForExcelExport(rowNode, exportContext) {
if (this.isTreeDataGrid()) {
return rowNode.key;
}
const columnId = rowNode.field ??
rowNode.rowGroupColumn?.getColId() ??
rowNode.rowGroupColumn?.getColDef()?.field;
if (!columnId || !rowNode.key) {
return;
}
let rawValue = rowNode.key;
const columnDataType = this.adaptableApi.columnApi.getColumnDataTypeForColumnId(columnId);
if ((columnDataType === 'date' || columnDataType === 'dateString') &&
typeof rawValue === 'string' &&
// rawValue is composed only of digits
/^\d+$/.test(rawValue)) {
// AG-Grid converts the value to string, we have to reconvert it back
const dateRawValue = parseInt(rawValue);
if (dateRawValue != undefined) {
// @ts-ignore
rawValue = dateRawValue;
}
}
return this.adaptableApi.exportApi.internalApi.getCellExportValueFromRawValue(rowNode, rawValue, columnId, exportContext.isVisualExcelReport);
}
patchExcelStyles() {
const exportExcelStyles = this.buildExcelStyles();
// set DANGER_excelStyles without changing the array reference
this.DANGER_excelStyles.splice(0, this.DANGER_excelStyles.length, ...exportExcelStyles);
}
resetExcelStyles() {
this.DANGER_excelStyles.splice(0, this.DANGER_excelStyles.length, ...this.originalExcelStyles);
this.excelStylesCache = {};
this.cellClassKey2excelStyleIdMap = {};
this.excelStylesWithFormattedDate = {};
}
buildExcelStyles() {
// we make sure that we start with a clean slate
// theoretically this should have happened at the end of the export process, but just in case
this.resetExcelStyles();
// we memoize as much as possible, as this is called quite A LOT
const adaptableColumnMemo = {};
const getAdaptableColumnWithColumnId = (columnId) => {
const memoizedColumn = adaptableColumnMemo[columnId];
if (memoizedColumn) {
return memoizedColumn;
}
const abColumn = this.adaptableApi.columnApi.getColumnWithColumnId(columnId);
adaptableColumnMemo[columnId] = abColumn;
return abColumn;
};
const formatColumnsWithDisplayFormatMemo = {};
const getFormatColumnsWithDisplayFormatForColumn = (columnId) => {
const memoizedFormatColumns = formatColumnsWithDisplayFormatMemo[columnId];
if (memoizedFormatColumns) {
return memoizedFormatColumns;
}
const abColumn = getAdaptableColumnWithColumnId(columnId);
const formatColumns = this.adaptableApi.formatColumnApi.internalApi.getFormatColumnsWithDisplayFormatForColumn(abColumn);
formatColumnsWithDisplayFormatMemo[columnId] = formatColumns;
return formatColumns;
};
const agGridDisplayedColumns = this.agGridApi
.getAllDisplayedColumns()
.filter((agCol) => agCol.getId() !== AG_GRID_SELECTION_COLUMN);
const colDefs = agGridDisplayedColumns.map((column) => {
return column.getColDef();
});
const forAllVisibleRowNodesDoConfig = { includeGroupRows: true };
const userExcelStyles = this.DANGER_excelStyles;
this.adaptableApi.internalApi.forAllVisibleRowNodesDo((node, rowIndex) => {
const rowParams = {
node,
data: node.data,
rowIndex,
api: this.agGridApi,
context: this.agGridApi.getGridOption('context') || {},
};
const getRowStyleFn = this.agGridApi.getGridOption('getRowStyle');
const rowStyle = getRowStyleFn ? getRowStyleFn(rowParams) : {};
agGridDisplayedColumns.forEach((column, columnIndex) => {
const colDef = colDefs[columnIndex];
const columnId = column.getId();
const adaptableColumn = getAdaptableColumnWithColumnId(columnId);
if (!adaptableColumn) {
this.logger.warn(`Export Styling: Column with id ${columnId} not found in Adaptable`);
return;
}
const isDateCellExportedAsFormattedValue = this.isDateCellExportedAsFormattedValue(adaptableColumn);
let cellClassParams;
const getLazyCellClassParams = () => {
if (!cellClassParams) {
cellClassParams = {
colDef,
node,
column,
data: node.data,
value: this.adaptableApi.gridApi.getRawValueFromRowNode(node, columnId),
rowIndex,
api: this.agGridApi,
context: {},
};
}
return cellClassParams;
};
const cellStyle = typeof colDef.cellStyle === 'function' ? colDef.cellStyle(getLazyCellClassParams()) : {};
const excelStyles = [];
// add user defined excel styles
let userColDefCellClass = this._adaptableInstance.agGridColumnAdapter.getUserColDefProperty(column.getColId(), 'cellClass');
const userDefinedCellClass = typeof userColDefCellClass === 'function'
? userColDefCellClass(getLazyCellClassParams())
: userColDefCellClass;
const userDefinedExcelStyle = userDefinedCellClass &&
userExcelStyles.find((excelStyle) => {
return typeof userDefinedCellClass === 'string'
? userDefinedCellClass === excelStyle.id
: userDefinedCellClass?.includes?.(excelStyle.id);
});
if (userDefinedExcelStyle) {
excelStyles.push(userDefinedExcelStyle);
}
// add adaptable derived styles (format column etc.)
const adaptableStyle = {
...rowStyle,
...Object.keys(cellStyle).reduce((result, key) => {
if (cellStyle[key] !== null) {
result[key] = cellStyle[key];
}
return result;
}, {}),
};
const sanitizedAdaptableStyle = sanitizeStyle(adaptableStyle);
if (Object.values(sanitizedAdaptableStyle).some((style) => style != null)) {
excelStyles.push(this.convertCSSToExcelStyle(sanitizedAdaptableStyle));
}
const excelDataType = this.getExcelDataType(adaptableColumn.dataType);
const rawValue = this.adaptableApi.gridApi.getRawValueFromRowNode(node, column.getId());
// don't add the cell style if it has no adaptable custom styles
if (!excelStyles.length &&
// if this is a formatted Date value, we still need to add the AG GRID specific type & numberFormat below
!(excelDataType === 'DateTime' && isDateCellExportedAsFormattedValue)) {
return;
}
const cellClassId = AgGridExportAdapter.getExcelClassNameForCell(column.getId(), this.adaptableApi.gridApi.getPrimaryKeyValueForRowNode(node), userDefinedCellClass);
const finalCellExcelStyle = Object.assign({}, ...excelStyles);
if (excelDataType === 'DateTime' && isDateCellExportedAsFormattedValue) {
let dateFormatPattern = this.exportOptions.exportDateFormat;
const abColumn = getAdaptableColumnWithColumnId(column.getColId());
if (!dateFormatPattern) {
const mostRelevantFormatColumn = this.adaptableApi.formatColumnApi.internalApi.getMostRelevantFormatColumnForColumn(getFormatColumnsWithDisplayFormatForColumn(column.getColId()), abColumn, { node, value: rawValue });
dateFormatPattern =
mostRelevantFormatColumn?.DisplayFormat?.Formatter === 'DateFormatter' &&
mostRelevantFormatColumn?.DisplayFormat?.Options?.Pattern;
}
if (dateFormatPattern) {
const normalisedValue = this._adaptableInstance.getNormalisedValueFromRawValue(rawValue, abColumn);
if (normalisedValue) {
// we have to pass the date in the ISO format to Excel
// see https://www.ag-grid.com/javascript-data-grid/excel-export-data-types/#dates
// we can NOT use Date.toISOString() because we don't want the timezone corrections to kick in
const isoFormattedValue = DateFormatter(normalisedValue, {
Pattern: `yyyy-MM-dd'T'HH:mm:ss.SSS`,
});
if (isoFormattedValue) {
finalCellExcelStyle.dataType = 'DateTime';
finalCellExcelStyle.numberFormat = { format: dateFormatPattern };
// create a new cell key to ensure any user provided className does not interfere
const cellKey = AgGridExportAdapter.getExcelClassNameForCell(column.getColId(), this.adaptableApi.gridApi.getPrimaryKeyValueForRowNode(node));
// we need to register so that later the cellProcessor will put the isoFormattedValue through (thus giving the formatting responsability to Excel)
this.registerExcelStyleWithFormattedDate(cellKey, isoFormattedValue);
}
}
}
}
this.registerExcelStyle(finalCellExcelStyle, cellClassId);
});
}, forAllVisibleRowNodesDoConfig);
return Object.values(this.excelStylesCache);
}
registerExcelStyle(excelStyle, cellClassKey) {
const excelStyleKey = JSON.stringify(excelStyle);
if (!this.excelStylesCache[excelStyleKey]) {
const excelStyleId = createUuid();
const excelStyleWithId = { ...excelStyle, id: excelStyleId };
this.excelStylesCache[excelStyleKey] = excelStyleWithId;
}
this.cellClassKey2excelStyleIdMap[cellClassKey] = this.excelStylesCache[excelStyleKey].id;
}
registerExcelStyleWithFormattedDate(cellClassId, isoFormattedValue) {
this.excelStylesWithFormattedDate[cellClassId] = isoFormattedValue;
}
isDateCellExportedAsFormattedValue(abColumn) {
return (!!this.exportOptions.exportDateFormat ||
// FIXME AFL move this method here
this.adaptableApi.exportApi.internalApi.getCellExportFormatType(abColumn, 'date') ===
'formattedValue');
}
convertCSSToExcelStyle(style) {
const getHexColor = (color) => {
const preparedColor = getVariableColor(color);
const t = tinycolor(preparedColor);
const a = t.getAlpha();
return tinycolor.mix(tinycolor('white'), t, a * 100).toHexString();
};
let result = {};
if (style.backgroundColor != null) {
result.interior = {
color: getHexColor(style.backgroundColor),
pattern: 'Solid',
};
}
if (style.borderColor != null) {
const excelBorder = {
color: style.borderColor,
lineStyle: 'Continuous',
weight: 1,
};
result.borders = {
borderBottom: excelBorder,
borderLeft: excelBorder,
borderRight: excelBorder,
borderTop: excelBorder,
};
}
if (style.textAlign) {
result.alignment = {
horizontal: StringExtensions.CapitaliseFirstLetter(style.textAlign),
};
}
if (style.color != null) {
if (!result.font) {
result.font = {};
}
result.font = {
color: getHexColor(style.color),
};
}
if (style.fontStyle === 'italic') {
if (!result.font) {
result.font = {};
}
result.font.italic = true;
}
if (style.fontWeight != null &&
(style.fontWeight === 'bold' || Number(style.fontWeight) >= 600)) {
if (!result.font) {
result.font = {};
}
result.font.bold = true;
}
if (style.fontSize != null) {
if (!result.font) {
result.font = {};
}
result.font.size = convertCSSAbsoluteFontSizeToPt(style.fontSize);
}
return result;
}
getExcelDataType(adaptableColumnDataType) {
switch (adaptableColumnDataType) {
case 'number':
return 'Number';
case 'boolean':
return 'Boolean';
case 'date':
case 'dateString':
return 'DateTime';
case 'text':
default:
return 'String';
}
}
getExcelStyleIdForCellClassKey(cellClassKey) {
return this.cellClassKey2excelStyleIdMap[cellClassKey];
}
getExcelStyleWithFormattedDate(cellClassId) {
return this.excelStylesWithFormattedDate[cellClassId];
}
mapCsvToJson(csvReportData, exportContext) {
const columns = exportContext.exportedColumnIds
.map((columnId) => this.adaptableApi.columnApi.getColumnWithColumnId(columnId))
.map((column) => ({
columnId: column.columnId,
field: column.field ?? column.columnId,
friendlyName: column.friendlyName,
dataType: column.dataType,
}));
const reportData = {
columns,
rows: [],
};
if (exportContext.report.ReportColumnScope === 'VisibleColumns') {
if (exportContext.getCurrent().groupColumnIds?.length) {
reportData.groupColumnIds = exportContext.getCurrent().groupColumnIds;
}
if (exportContext.getCurrent().pivotColumnIds?.length) {
reportData.pivotColumnIds = exportContext.getCurrent().pivotColumnIds;
}
}
// map Row Data
const schema = inferSchema(csvReportData, {
col: this.getCsvSeparator(exportContext),
header: (rows) => {
const headerRow = rows[0];
// replace headerNames with columnIds
headerRow.forEach((headerName, index) => {
const column = columns.find((col) => col.friendlyName === headerName);
if (column) {
headerRow[index] = column.columnId;
}
});
return [headerRow];
},
});
const parser = initParser(schema);
const jsonData = parser.typedObjs(csvReportData);
reportData.rows = jsonData;
return reportData;
}
getCsvSeparator(exportProcessContext) {
if (typeof this.exportOptions.csvSeparator === 'function') {
return this.exportOptions.csvSeparator(this.adaptableApi.exportApi.internalApi.buildBaseExportContext(exportProcessContext.report.Name, exportProcessContext.format, exportProcessContext.destination));
}
else {
return this.exportOptions.csvSeparator;
}
}
computeSkipColumnHeaders(exportProcessContext) {
if (typeof this.exportOptions.skipColumnHeaders === 'function') {
return this.exportOptions.skipColumnHeaders(this.adaptableApi.exportApi.internalApi.buildBaseExportContext(exportProcessContext.report.Name, exportProcessContext.format, exportProcessContext.destination));
}
else {
return this.exportOptions.skipColumnHeaders;
}
}
computeGetCustomContentBelowRow(exportContext) {
if (!this.adaptableApi.gridApi.isMasterDetailGrid()) {
return;
}
const getDetailsRowsFn = this.exportOptions.getDetailRows;
if (typeof getDetailsRowsFn !== 'function') {
this.adaptableApi.logInfo('ExportOptions.getDetailRows() is not defined, skipping detail rows export');
return;
}
return (params) => {
const { node } = params;
const shouldExportDetailRows = exportContext.report.ReportRowScope === 'AllRows' ||
(exportContext.report.ReportRowScope === 'VisibleRows' && node.expanded);
if (!shouldExportDetailRows) {
return;
}
const context = {
...this.adaptableApi.exportApi.internalApi.buildBaseExportContext(exportContext.report.Name, exportContext.format, exportContext.destination),
adaptableColumn: this.adaptableApi.columnApi.getColumnWithColumnId(params.column?.getId()),
masterRowNode: node,
masterRowData: node?.data,
isExpanded: node.expanded,
createCellCsv: (cellContent) => this.adaptableApi.exportApi.internalApi.createCellCsv(cellContent),
createCellExcel: (cellContent, cellType) => this.adaptableApi.exportApi.internalApi.createCellExcel(cellContent, cellType),
createCellHeader: (cellContent) => {
return {
...this.adaptableApi.exportApi.internalApi.createCellHeader(cellContent),
// see #masterDetailHeader
styleId: '_masterDetailHeader',
};
},
};
const detailRows = getDetailsRowsFn(context);
if (exportContext.isExcelReport) {
const anyExcelRow = detailRows[0];
if (anyExcelRow.cells == undefined) {
this.adaptableApi.logWarn(`Export ${exportContext.report.Name}: ExportOptions.getDetailRows() should return an array of ExcelRow objects`);
return;
}
}
else {
const anyCsvCell = detailRows[0]?.[0];
if (anyCsvCell.data == undefined) {
this.adaptableApi.logWarn(`Export ${exportContext.report.Name}: ExportOptions.getDetailRows() should return an array of CsvRow objects`);
return;
}
}
return detailRows;
};
}
}