Skip to content

Commit

Permalink
[DataGridPremium] Fix excel export causing column with wrong width (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
romgrk authored May 27, 2024
1 parent 91cbec2 commit f705941
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
isObject,
GridColumnGroupLookup,
isSingleSelectColDef,
gridHasColSpanSelector,
} from '@mui/x-data-grid/internals';
import { ColumnsStylesInterface, GridExcelExportOptions } from '../gridExcelExportInterface';
import { GridPrivateApiPremium } from '../../../../models/gridApiPremium';
Expand Down Expand Up @@ -61,30 +62,41 @@ interface SerializedRow {
mergedCells: { leftIndex: number; rightIndex: number }[];
}

export const serializeRow = (
/**
* 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.
*/
export const serializeRowUnsafe = (
id: GridRowId,
columns: GridStateColDef[],
api: GridPrivateApiPremium,
apiRef: React.MutableRefObject<GridPrivateApiPremium>,
defaultValueOptionsFormulae: { [field: string]: { address: string } },
options: Pick<BuildExcelOptions, 'escapeFormulas'>,
): SerializedRow => {
const row: SerializedRow['row'] = {};
const dataValidation: SerializedRow['dataValidation'] = {};
const mergedCells: SerializedRow['mergedCells'] = [];

const firstCellParams = api.getCellParams(id, columns[0].field);
const firstCellParams = apiRef.current.getCellParams(id, columns[0].field);
const outlineLevel = firstCellParams.rowNode.depth;

// `colSpan` is only calculated for rendered rows, so we need to calculate it during export for every row
api.calculateColSpan({
rowId: id,
minFirstColumn: 0,
maxLastColumn: columns.length,
columns,
});
const hasColSpan = 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({
rowId: id,
minFirstColumn: 0,
maxLastColumn: columns.length,
columns,
});
}

columns.forEach((column, colIndex) => {
const colSpanInfo = api.unstable_getCellColSpanInfo(id, colIndex);
const colSpanInfo = hasColSpan
? apiRef.current.unstable_getCellColSpanInfo(id, colIndex)
: undefined;
if (colSpanInfo && colSpanInfo.spannedByColSpan) {
return;
}
Expand All @@ -95,7 +107,7 @@ export const serializeRow = (
});
}

const cellParams = api.getCellParams(id, column.field);
const cellParams = apiRef.current.getCellParams(id, column.field);

let cellValue: string | undefined;

Expand All @@ -114,7 +126,7 @@ export const serializeRow = (
castColumn,
row,
valueOptions,
api,
apiRef.current,
);
dataValidation[castColumn.field] = {
type: 'list',
Expand All @@ -136,7 +148,7 @@ export const serializeRow = (
};
}

const formattedValue = api.getCellParams(id, castColumn.field).formattedValue;
const formattedValue = apiRef.current.getCellParams(id, castColumn.field).formattedValue;
if (process.env.NODE_ENV !== 'production') {
if (String(cellParams.formattedValue) === '[object Object]') {
warnInvalidFormattedValue();
Expand All @@ -151,14 +163,14 @@ export const serializeRow = (
}
case 'boolean':
case 'number':
cellValue = api.getCellParams(id, column.field).value as any;
cellValue = apiRef.current.getCellParams(id, column.field).value as any;
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 = api.getCellParams<any, Date>(id, column.field).value;
const value = apiRef.current.getCellParams<any, Date>(id, column.field).value;
// value may be `undefined` in auto-generated grouping rows
if (!value) {
break;
Expand All @@ -179,7 +191,7 @@ export const serializeRow = (
case 'actions':
break;
default:
cellValue = api.getCellParams(id, column.field).formattedValue as any;
cellValue = apiRef.current.getCellParams(id, column.field).formattedValue as any;
if (process.env.NODE_ENV !== 'production') {
if (String(cellParams.formattedValue) === '[object Object]') {
warnInvalidFormattedValue();
Expand Down Expand Up @@ -390,7 +402,7 @@ interface BuildExcelOptions

export async function buildExcel(
options: BuildExcelOptions,
api: GridPrivateApiPremium,
apiRef: React.MutableRefObject<GridPrivateApiPremium>,
): Promise<Excel.Workbook> {
const {
columns,
Expand Down Expand Up @@ -419,29 +431,35 @@ export async function buildExcel(

if (includeColumnGroupsHeaders) {
const columnGroupPaths = columns.reduce<Record<string, string[]>>((acc, column) => {
acc[column.field] = api.getColumnGroupPath(column.field);
acc[column.field] = apiRef.current.getColumnGroupPath(column.field);
return acc;
}, {});

addColumnGroupingHeaders(
worksheet,
serializedColumns,
columnGroupPaths,
api.getAllGroupDetails(),
apiRef.current.getAllGroupDetails(),
);
}

if (includeHeaders) {
worksheet.addRow(columns.map((column) => column.headerName ?? column.field));
}

const valueOptionsData = await getDataForValueOptionsSheet(columns, valueOptionsSheetName, api);
const valueOptionsData = await getDataForValueOptionsSheet(
columns,
valueOptionsSheetName,
apiRef.current,
);
createValueOptionsSheetIfNeeded(valueOptionsData, valueOptionsSheetName, workbook);

apiRef.current.resetColSpan();
rowIds.forEach((id) => {
const serializedRow = serializeRow(id, columns, api, valueOptionsData, options);
const serializedRow = serializeRowUnsafe(id, columns, apiRef, valueOptionsData, options);
addSerializedRowToWorksheet(serializedRow, worksheet);
});
apiRef.current.resetColSpan();

if (exceljsPostProcess) {
await exceljsPostProcess({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
ExcelExportInitEvent,
getDataForValueOptionsSheet,
serializeColumns,
serializeRow,
serializeRowUnsafe,
} from './serializer/excelSerializer';
import { GridExcelExportMenuItem } from '../../../components';

Expand Down Expand Up @@ -62,7 +62,7 @@ export const useGridExcelExport = (
exceljsPostProcess: options?.exceljsPostProcess,
escapeFormulas: options.escapeFormulas ?? true,
},
apiRef.current,
apiRef,
);
},
[logger, apiRef],
Expand Down Expand Up @@ -141,11 +141,13 @@ export const useGridExcelExport = (

const serializedColumns = serializeColumns(exportedColumns, options.columnsStyles || {});

apiRef.current.resetColSpan();
const serializedRows = exportedRowIds.map((id) =>
serializeRow(id, exportedColumns, apiRef.current, valueOptionsData, {
serializeRowUnsafe(id, exportedColumns, apiRef, valueOptionsData, {
escapeFormulas: options.escapeFormulas ?? true,
}),
);
apiRef.current.resetColSpan();

const columnGroupPaths = exportedColumns.reduce<Record<string, string[]>>((acc, column) => {
acc[column.field] = apiRef.current.getColumnGroupPath(column.field);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export const useGridColumnSpanning = (apiRef: React.MutableRefObject<GridPrivate
return lookup.current[rowId]?.[columnIndex];
};

const resetColSpan: GridColumnSpanningPrivateApi['resetColSpan'] = () => {
lookup.current = {};
};

// Calculate `colSpan` for each cell in the row
const calculateColSpan = React.useCallback<GridColumnSpanningPrivateApi['calculateColSpan']>(
({ rowId, minFirstColumn, maxLastColumn, columns }) => {
Expand All @@ -52,18 +56,14 @@ export const useGridColumnSpanning = (apiRef: React.MutableRefObject<GridPrivate
};

const columnSpanningPrivateApi: GridColumnSpanningPrivateApi = {
resetColSpan,
calculateColSpan,
};

useGridApiMethod(apiRef, columnSpanningPublicApi, 'public');
useGridApiMethod(apiRef, columnSpanningPrivateApi, 'private');

const handleColumnReorderChange = React.useCallback(() => {
// `colSpan` needs to be recalculated after column reordering
lookup.current = {};
}, []);

useGridApiEventHandler(apiRef, 'columnOrderChange', handleColumnReorderChange);
useGridApiEventHandler(apiRef, 'columnOrderChange', resetColSpan);
};

function calculateCellColSpan(params: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,14 @@ const createScrollCache = (
});
type ScrollCache = ReturnType<typeof createScrollCache>;

const isJSDOM = typeof window !== 'undefined' ? /jsdom/.test(window.navigator.userAgent) : false;
let isJSDOM = false;
try {
if (typeof window !== 'undefined') {
isJSDOM = /jsdom/.test(window.navigator.userAgent);
}
} catch (_) {
/* ignore */
}

export const useGridVirtualScroller = () => {
const apiRef = useGridPrivateApiContext() as React.MutableRefObject<PrivateApiWithInfiniteLoader>;
Expand Down
2 changes: 2 additions & 0 deletions packages/x-data-grid/src/models/api/gridColumnSpanning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface GridColumnSpanningApi {
}

export interface GridColumnSpanningPrivateApi {
/** Reset the colspan cache */
resetColSpan: () => void;
/**
* Calculate column spanning for each cell in the row
* @param {Object} options The options to apply on the calculation.
Expand Down

0 comments on commit f705941

Please sign in to comment.