Skip to content

Commit

Permalink
[DataGridPremium] Fix Excel export Web Worker demo not working in dev…
Browse files Browse the repository at this point in the history
… mode (#16517)
  • Loading branch information
cherniavskii authored and web-flow committed Feb 11, 2025
1 parent 3b96643 commit a443c8b
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 189 deletions.
2 changes: 1 addition & 1 deletion docs/data/data-grid/export/ExcelExportWithWebWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function ExcelExportWithWebWorker() {

const { data, loading } = useDemoData({
dataSet: 'Commodity',
rowLength: 10000,
rowLength: 50_000,
editable: true,
});

Expand Down
2 changes: 1 addition & 1 deletion docs/data/data-grid/export/ExcelExportWithWebWorker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function ExcelExportWithWebWorker() {

const { data, loading } = useDemoData({
dataSet: 'Commodity',
rowLength: 10000,
rowLength: 50_000,
editable: true,
});

Expand Down
2 changes: 1 addition & 1 deletion docs/data/data-grid/export/excelExportWorker.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Used in ExcelExportWithWebWorker demo
import { setupExcelExportWebWorker } from '@mui/x-data-grid-premium';
import { setupExcelExportWebWorker } from '@mui/x-data-grid-premium/setupExcelExportWebWorker';

setupExcelExportWebWorker();
2 changes: 1 addition & 1 deletion docs/data/data-grid/export/excelExportWorker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Used in ExcelExportWithWebWorker demo
import { setupExcelExportWebWorker } from '@mui/x-data-grid-premium';
import { setupExcelExportWebWorker } from '@mui/x-data-grid-premium/setupExcelExportWebWorker';

setupExcelExportWebWorker();
2 changes: 1 addition & 1 deletion docs/data/data-grid/export/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ This file will be later used as the worker script, so it must be accessible by a

```tsx
// in file ./worker.ts
import { setupExcelExportWebWorker } from '@mui/x-data-grid-premium';
import { setupExcelExportWebWorker } from '@mui/x-data-grid-premium/setupExcelExportWebWorker';

setupExcelExportWebWorker();
```
Expand Down
3 changes: 0 additions & 3 deletions docs/worker.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './gridExcelExportInterface';

export { setupExcelExportWebWorker } from './serializer/excelSerializer';
export { setupExcelExportWebWorker } from './serializer/setupExcelExportWebWorker';
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@ import {
GridStateColDef,
GridSingleSelectColDef,
isObject,
GridColumnGroupLookup,
isSingleSelectColDef,
gridHasColSpanSelector,
} from '@mui/x-data-grid/internals';
import { warnOnce } from '@mui/x-internals/warning';
import { ColumnsStylesInterface, GridExcelExportOptions } from '../gridExcelExportInterface';
import { GridPrivateApiPremium } from '../../../../models/gridApiPremium';
import {
addColumnGroupingHeaders,
addSerializedRowToWorksheet,
createValueOptionsSheetIfNeeded,
getExcelJs,
SerializedColumns,
SerializedRow,
ValueOptionsData,
} from './utils';

const getExcelJs = async () => {
const excelJsModule = await import('exceljs');
return excelJsModule.default ?? excelJsModule;
};
export type { ExcelExportInitEvent } from './utils';

const getFormattedValueOptions = (
colDef: GridSingleSelectColDef,
Expand All @@ -51,13 +56,6 @@ const getFormattedValueOptions = (
);
};

interface SerializedRow {
row: Record<string, undefined | number | boolean | string | Date>;
dataValidation: Record<string, Excel.DataValidation>;
outlineLevel: number;
mergedCells: { leftIndex: number; rightIndex: number }[];
}

/**
* 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
Expand Down Expand Up @@ -241,77 +239,13 @@ export const serializeColumn = (column: GridColDef, columnsStyles: ColumnsStyles
};
};

const addColumnGroupingHeaders = (
worksheet: Excel.Worksheet,
columns: SerializedColumns,
columnGroupPaths: Record<string, string[]>,
columnGroupDetails: GridColumnGroupLookup,
) => {
const maxDepth = Math.max(...columns.map(({ key }) => columnGroupPaths[key]?.length ?? 0));
if (maxDepth === 0) {
return;
}

for (let rowIndex = 0; rowIndex < maxDepth; rowIndex += 1) {
const row = columns.map(({ key }) => {
const groupingPath = columnGroupPaths[key];
if (groupingPath.length <= rowIndex) {
return { groupId: null, parents: groupingPath };
}
return {
...columnGroupDetails[groupingPath[rowIndex]],
parents: groupingPath.slice(0, rowIndex),
};
});

const newRow = worksheet.addRow(
row.map((group) => (group.groupId === null ? null : (group?.headerName ?? group.groupId))),
);

// use `rowCount`, since worksheet can have additional rows added in `exceljsPreProcess`
const lastRowIndex = newRow.worksheet.rowCount;
let leftIndex = 0;
let rightIndex = 1;
while (rightIndex < columns.length) {
const { groupId: leftGroupId, parents: leftParents } = row[leftIndex];
const { groupId: rightGroupId, parents: rightParents } = row[rightIndex];

const areInSameGroup =
leftGroupId === rightGroupId &&
leftParents.length === rightParents.length &&
leftParents.every((leftParent, index) => rightParents[index] === leftParent);
if (areInSameGroup) {
rightIndex += 1;
} else {
if (rightIndex - leftIndex > 1) {
worksheet.mergeCells(lastRowIndex, leftIndex + 1, lastRowIndex, rightIndex);
}
leftIndex = rightIndex;
rightIndex += 1;
}
}
if (rightIndex - leftIndex > 1) {
worksheet.mergeCells(lastRowIndex, leftIndex + 1, lastRowIndex, rightIndex);
}
}
};

type SerializedColumns = Array<{
key: string;
width: number;
style: Partial<Excel.Style>;
headerText: string;
}>;

export function serializeColumns(
columns: GridStateColDef[],
styles: ColumnsStylesInterface,
): SerializedColumns {
return columns.map((column) => serializeColumn(column, styles));
}

type ValueOptionsData = Record<string, { values: (string | number)[]; address: string }>;

export async function getDataForValueOptionsSheet(
columns: GridStateColDef[],
valueOptionsSheetName: string,
Expand Down Expand Up @@ -350,47 +284,6 @@ export async function getDataForValueOptionsSheet(
{},
);
}

function addSerializedRowToWorksheet(serializedRow: SerializedRow, worksheet: Excel.Worksheet) {
const { row, dataValidation, outlineLevel, mergedCells } = serializedRow;

const newRow = worksheet.addRow(row);

Object.keys(dataValidation).forEach((field) => {
newRow.getCell(field).dataValidation = {
...dataValidation[field],
};
});

if (outlineLevel) {
newRow.outlineLevel = outlineLevel;
}

// use `rowCount`, since worksheet can have additional rows added in `exceljsPreProcess`
const lastRowIndex = newRow.worksheet.rowCount;
mergedCells.forEach((mergedCell) => {
worksheet.mergeCells(lastRowIndex, mergedCell.leftIndex, lastRowIndex, mergedCell.rightIndex);
});
}

async function createValueOptionsSheetIfNeeded(
valueOptionsData: ValueOptionsData,
sheetName: string,
workbook: Excel.Workbook,
) {
if (Object.keys(valueOptionsData).length === 0) {
return;
}

const valueOptionsWorksheet = workbook.addWorksheet(sheetName);

valueOptionsWorksheet.columns = Object.keys(valueOptionsData).map((key) => ({ key }));

Object.entries(valueOptionsData).forEach(([field, { values }]) => {
valueOptionsWorksheet.getColumn(field).values = values;
});
}

interface BuildExcelOptions
extends Pick<GridExcelExportOptions, 'exceljsPreProcess' | 'exceljsPostProcess'>,
Pick<
Expand Down Expand Up @@ -472,66 +365,3 @@ export async function buildExcel(

return workbook;
}

export interface ExcelExportInitEvent {
serializedColumns: SerializedColumns;
serializedRows: SerializedRow[];
valueOptionsSheetName: string;
columnGroupPaths: Record<string, string[]>;
columnGroupDetails: GridColumnGroupLookup;
valueOptionsData: ValueOptionsData;
options: Omit<
GridExcelExportOptions,
'exceljsPreProcess' | 'exceljsPostProcess' | 'columnsStyles' | 'valueOptionsSheetName'
>;
}

export function setupExcelExportWebWorker(
workerOptions: Pick<GridExcelExportOptions, 'exceljsPostProcess' | 'exceljsPreProcess'> = {},
) {
// eslint-disable-next-line no-restricted-globals
addEventListener('message', async (event: MessageEvent<ExcelExportInitEvent>) => {
const {
serializedColumns,
serializedRows,
options,
valueOptionsSheetName,
valueOptionsData,
columnGroupDetails,
columnGroupPaths,
} = event.data;

const { exceljsPostProcess, exceljsPreProcess } = workerOptions;

const excelJS = await getExcelJs();
const workbook: Excel.Workbook = new excelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');

worksheet.columns = serializedColumns;

if (exceljsPreProcess) {
await exceljsPreProcess({ workbook, worksheet });
}

if (options.includeColumnGroupsHeaders) {
addColumnGroupingHeaders(worksheet, serializedColumns, columnGroupPaths, columnGroupDetails);
}

const includeHeaders = options.includeHeaders ?? true;
if (includeHeaders) {
worksheet.addRow(serializedColumns.map((column) => column.headerText));
}

createValueOptionsSheetIfNeeded(valueOptionsData, valueOptionsSheetName, workbook);

serializedRows.forEach((serializedRow) => {
addSerializedRowToWorksheet(serializedRow, worksheet);
});

if (exceljsPostProcess) {
await exceljsPostProcess({ workbook, worksheet });
}

postMessage(await workbook.xlsx.writeBuffer());
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type * as Excel from 'exceljs';
import type { GridExcelExportOptions } from '../gridExcelExportInterface';
import {
addColumnGroupingHeaders,
addSerializedRowToWorksheet,
createValueOptionsSheetIfNeeded,
ExcelExportInitEvent,
getExcelJs,
} from './utils';

export function setupExcelExportWebWorker(
workerOptions: Pick<GridExcelExportOptions, 'exceljsPostProcess' | 'exceljsPreProcess'> = {},
) {
// eslint-disable-next-line no-restricted-globals
addEventListener('message', async (event: MessageEvent<ExcelExportInitEvent>) => {
const {
serializedColumns,
serializedRows,
options,
valueOptionsSheetName,
valueOptionsData,
columnGroupDetails,
columnGroupPaths,
} = event.data;

const { exceljsPostProcess, exceljsPreProcess } = workerOptions;

const excelJS = await getExcelJs();
const workbook: Excel.Workbook = new excelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');

worksheet.columns = serializedColumns;

if (exceljsPreProcess) {
await exceljsPreProcess({ workbook, worksheet });
}

if (options.includeColumnGroupsHeaders) {
addColumnGroupingHeaders(worksheet, serializedColumns, columnGroupPaths, columnGroupDetails);
}

const includeHeaders = options.includeHeaders ?? true;
if (includeHeaders) {
worksheet.addRow(serializedColumns.map((column) => column.headerText));
}

createValueOptionsSheetIfNeeded(valueOptionsData, valueOptionsSheetName, workbook);

serializedRows.forEach((serializedRow) => {
addSerializedRowToWorksheet(serializedRow, worksheet);
});

if (exceljsPostProcess) {
await exceljsPostProcess({ workbook, worksheet });
}

postMessage(await workbook.xlsx.writeBuffer());
});
}
Loading

0 comments on commit a443c8b

Please sign in to comment.