@mui/x-data-grid-premium
Version:
The Premium plan edition of the MUI X Data Grid Components.
277 lines (274 loc) • 10.8 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildExcel = buildExcel;
exports.getDataForValueOptionsSheet = getDataForValueOptionsSheet;
exports.serializeColumn = void 0;
exports.serializeColumns = serializeColumns;
exports.serializeRowUnsafe = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _xDataGridPro = require("@mui/x-data-grid-pro");
var _internals = require("@mui/x-data-grid/internals");
var _warning = require("@mui/x-internals/warning");
var _utils = require("./utils");
const getFormattedValueOptions = (colDef, row, valueOptions, api, callback) => {
if (!colDef.valueOptions) {
return;
}
const valueFormatter = colDef.valueFormatter;
for (let i = 0; i < valueOptions.length; i += 1) {
const option = valueOptions[i];
let value;
if (valueFormatter) {
if (typeof option === 'object') {
value = option.label;
} else {
value = String(colDef.valueFormatter(option, row, colDef, {
current: api
}));
}
} else {
value = typeof option === 'object' ? option.label : option;
}
callback(value, i);
}
};
const commaRegex = /,/g;
const commaReplacement = 'CHAR(44)';
/**
* FIXME: This function mutates the colspan info, but colspan info assumes that the columns
* passed to it are always consistent. In this case, the exported columns may differ from the
* actual rendered columns.
* The caller of this function MUST call `resetColSpan()` before and after usage.
*/
const serializeRowUnsafe = (id, columns, apiRef, defaultValueOptionsFormulae, options) => {
const serializedRow = {};
const dataValidation = {};
const mergedCells = [];
const row = apiRef.current.getRow(id);
const rowNode = apiRef.current.getRowNode(id);
if (!row || !rowNode) {
throw new Error(`No row with id #${id} found`);
}
const outlineLevel = rowNode.depth;
const hasColSpan = (0, _internals.gridHasColSpanSelector)(apiRef);
if (hasColSpan) {
// `colSpan` is only calculated for rendered rows, so we need to calculate it during export for every row
apiRef.current.calculateColSpan(id, 0, columns.length, columns);
}
columns.forEach((column, colIndex) => {
const colSpanInfo = hasColSpan ? apiRef.current.unstable_getCellColSpanInfo(id, colIndex) : undefined;
if (colSpanInfo && colSpanInfo.spannedByColSpan) {
return;
}
if (colSpanInfo && colSpanInfo.cellProps.colSpan > 1) {
mergedCells.push({
leftIndex: colIndex + 1,
rightIndex: colIndex + colSpanInfo.cellProps.colSpan
});
}
let cellValue;
switch (column.type) {
case 'singleSelect':
{
const castColumn = column;
if (typeof castColumn.valueOptions === 'function') {
// If value option depends on the row, set specific options to the cell
// This dataValidation is buggy with LibreOffice and does not allow to have coma
const valueOptions = castColumn.valueOptions({
id,
row,
field: column.field
});
let formulae = '"';
getFormattedValueOptions(castColumn, row, valueOptions, apiRef.current, (value, index) => {
const formatted = value.toString().replace(commaRegex, commaReplacement);
formulae += formatted;
if (index < valueOptions.length - 1) {
formulae += ',';
}
});
formulae += '"';
dataValidation[castColumn.field] = {
type: 'list',
allowBlank: true,
formulae: [formulae]
};
} else {
const address = defaultValueOptionsFormulae[column.field].address;
// If value option is defined for the column, refer to another sheet
dataValidation[castColumn.field] = {
type: 'list',
allowBlank: true,
formulae: [address]
};
}
const formattedValue = apiRef.current.getRowFormattedValue(row, castColumn);
if (process.env.NODE_ENV !== 'production') {
if (String(formattedValue) === '[object Object]') {
(0, _warning.warnOnce)(['MUI X: When the value of a field is an object or a `renderCell` is provided, the Excel export might not display the value correctly.', 'You can provide a `valueFormatter` with a string representation to be used.']);
}
}
if ((0, _internals.isObject)(formattedValue)) {
serializedRow[castColumn.field] = formattedValue?.label;
} else {
serializedRow[castColumn.field] = formattedValue;
}
break;
}
case 'boolean':
case 'number':
cellValue = apiRef.current.getRowValue(row, column);
break;
case 'date':
case 'dateTime':
{
// Excel does not do any timezone conversion, so we create a date using UTC instead of local timezone
// Solution from: https://github.com/exceljs/exceljs/issues/486#issuecomment-432557582
// About Date.UTC(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC#exemples
const value = apiRef.current.getRowValue(row, column);
// value may be `undefined` in auto-generated grouping rows
if (!value) {
break;
}
const utcDate = new Date(Date.UTC(value.getFullYear(), value.getMonth(), value.getDate(), value.getHours(), value.getMinutes(), value.getSeconds()));
serializedRow[column.field] = utcDate;
break;
}
case 'actions':
break;
default:
cellValue = apiRef.current.getRowFormattedValue(row, column);
if (process.env.NODE_ENV !== 'production') {
if (String(cellValue) === '[object Object]') {
(0, _warning.warnOnce)(['MUI X: When the value of a field is an object or a `renderCell` is provided, the Excel export might not display the value correctly.', 'You can provide a `valueFormatter` with a string representation to be used.']);
}
}
break;
}
if (typeof cellValue === 'string' && options.escapeFormulas) {
// See https://owasp.org/www-community/attacks/CSV_Injection
if (['=', '+', '-', '@', '\t', '\r'].includes(cellValue[0])) {
cellValue = `'${cellValue}`;
}
}
if (typeof cellValue !== 'undefined') {
serializedRow[column.field] = cellValue;
}
});
return {
row: serializedRow,
dataValidation,
outlineLevel,
mergedCells
};
};
exports.serializeRowUnsafe = serializeRowUnsafe;
const defaultColumnsStyles = {
[_xDataGridPro.GRID_DATE_COL_DEF.type]: {
numFmt: 'dd.mm.yyyy'
},
[_xDataGridPro.GRID_DATETIME_COL_DEF.type]: {
numFmt: 'dd.mm.yyyy hh:mm'
}
};
const serializeColumn = (column, columnsStyles) => {
const {
field,
type
} = column;
return {
key: field,
headerText: column.headerName ?? column.field,
// Excel width must stay between 0 and 255 (https://support.microsoft.com/en-us/office/change-the-column-width-and-row-height-72f5e3cc-994d-43e8-ae58-9774a0905f46)
// From the example of column width behavior (https://docs.microsoft.com/en-US/office/troubleshoot/excel/determine-column-widths#example-of-column-width-behavior)
// a value of 10 corresponds to 75px. This is an approximation, because column width depends on the font-size
width: Math.min(255, column.width ? column.width / 7.5 : 8.43),
style: (0, _extends2.default)({}, type && defaultColumnsStyles?.[type], columnsStyles?.[field])
};
};
exports.serializeColumn = serializeColumn;
function serializeColumns(columns, styles) {
return columns.map(column => serializeColumn(column, styles));
}
async function getDataForValueOptionsSheet(columns, valueOptionsSheetName, api) {
// Creates a temp worksheet to obtain the column letters
const excelJS = await (0, _utils.getExcelJs)();
const workbook = new excelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
const record = {};
const worksheetColumns = [];
for (let i = 0; i < columns.length; i += 1) {
const column = columns[i];
const isCandidateColumn = (0, _internals.isSingleSelectColDef)(column) && Array.isArray(column.valueOptions);
if (!isCandidateColumn) {
continue;
}
worksheetColumns.push({
key: column.field
});
worksheet.columns = worksheetColumns;
const header = column.headerName ?? column.field;
const values = [header];
getFormattedValueOptions(column, {}, column.valueOptions, api, value => {
values.push(value);
});
const letter = worksheet.getColumn(column.field).letter;
const address = `${valueOptionsSheetName}!$${letter}$2:$${letter}$${values.length}`;
record[column.field] = {
values,
address
};
}
return record;
}
async function buildExcel(options, apiRef) {
const {
columns,
rowIds,
includeHeaders,
includeColumnGroupsHeaders,
valueOptionsSheetName = 'Options',
exceljsPreProcess,
exceljsPostProcess,
columnsStyles = {}
} = options;
const excelJS = await (0, _utils.getExcelJs)();
const workbook = new excelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
const serializedColumns = serializeColumns(columns, columnsStyles);
worksheet.columns = serializedColumns;
if (exceljsPreProcess) {
await exceljsPreProcess({
workbook,
worksheet
});
}
if (includeColumnGroupsHeaders) {
const columnGroupPaths = columns.reduce((acc, column) => {
acc[column.field] = apiRef.current.getColumnGroupPath(column.field);
return acc;
}, {});
(0, _utils.addColumnGroupingHeaders)(worksheet, serializedColumns, columnGroupPaths, apiRef.current.getAllGroupDetails());
}
if (includeHeaders) {
worksheet.addRow(columns.map(column => column.headerName ?? column.field));
}
const valueOptionsData = await getDataForValueOptionsSheet(columns, valueOptionsSheetName, apiRef.current);
(0, _utils.createValueOptionsSheetIfNeeded)(valueOptionsData, valueOptionsSheetName, workbook);
apiRef.current.resetColSpan();
rowIds.forEach(id => {
const serializedRow = serializeRowUnsafe(id, columns, apiRef, valueOptionsData, options);
(0, _utils.addSerializedRowToWorksheet)(serializedRow, worksheet);
});
apiRef.current.resetColSpan();
if (exceljsPostProcess) {
await exceljsPostProcess({
workbook,
worksheet
});
}
return workbook;
}