Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Fix cell value formatting on copy #12357

Merged
merged 6 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ export const useGridCellSelection = (
cellData = serializeCellValue(cellParams, {
delimiterCharacter: clipboardCopyCellDelimiter,
ignoreValueFormatter,
shouldAppendQuotes: true,
});
} else {
cellData = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export const useGridClipboard = (
includeHeaders: false,
// TODO: make it configurable
delimiter: clipboardCopyCellDelimiter,
shouldAppendQuotes: false,
});
} else {
const focusedCell = gridFocusCellSelector(apiRef);
Expand All @@ -105,6 +106,7 @@ export const useGridClipboard = (
textToCopy = serializeCellValue(cellParams, {
delimiterCharacter: clipboardCopyCellDelimiter,
ignoreValueFormatter,
shouldAppendQuotes: false,
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import type { GridStateColDef } from '../../../../models/colDef/gridColDef';
import type { GridApiCommunity } from '../../../../models/api/gridApiCommunity';
import { buildWarning } from '../../../../utils/warning';

function sanitizeCellValue(value: any, delimiterCharacter: string) {
function sanitizeCellValue(value: any, delimiterCharacter: string, shouldAppendQuotes: boolean) {
if (typeof value === 'string') {
// Make sure value containing delimiter or line break won't be split into multiple rows
if ([delimiterCharacter, '\n', '\r', '"'].some((delimiter) => value.includes(delimiter))) {
return `"${value.replace(/"/g, '""')}"`;
if (shouldAppendQuotes) {
return `"${value.replace(/"/g, '""')}"`;
}
return `${value.replace(/"/g, '""')}`;
}

return value;
Expand All @@ -20,9 +23,13 @@ function sanitizeCellValue(value: any, delimiterCharacter: string) {

export const serializeCellValue = (
cellParams: GridCellParams,
options: { delimiterCharacter: string; ignoreValueFormatter: boolean },
options: {
delimiterCharacter: string;
ignoreValueFormatter: boolean;
shouldAppendQuotes: boolean;
},
) => {
const { delimiterCharacter, ignoreValueFormatter } = options;
const { delimiterCharacter, ignoreValueFormatter, shouldAppendQuotes } = options;
let value: any;
if (ignoreValueFormatter) {
const columnType = cellParams.colDef.type;
Expand All @@ -39,7 +46,7 @@ export const serializeCellValue = (
value = cellParams.formattedValue;
}

return sanitizeCellValue(value, delimiterCharacter);
return sanitizeCellValue(value, delimiterCharacter, shouldAppendQuotes);
};

const objectFormattedValueWarning = buildWarning([
Expand All @@ -49,7 +56,8 @@ const objectFormattedValueWarning = buildWarning([

type CSVRowOptions = {
delimiterCharacter: string;
sanitizeCellValue?: (value: any, delimiterCharacter: string) => any;
sanitizeCellValue?: (value: any, delimiterCharacter: string, shouldAppendQuotes: boolean) => any;
shouldAppendQuotes: boolean;
};
class CSVRow {
options: CSVRowOptions;
Expand All @@ -69,7 +77,11 @@ class CSVRow {
if (value === null || value === undefined) {
this.rowString += '';
} else if (typeof this.options.sanitizeCellValue === 'function') {
this.rowString += this.options.sanitizeCellValue(value, this.options.delimiterCharacter);
this.rowString += this.options.sanitizeCellValue(
value,
this.options.delimiterCharacter,
this.options.shouldAppendQuotes,
);
} else {
this.rowString += value;
}
Expand All @@ -87,14 +99,16 @@ const serializeRow = ({
getCellParams,
delimiterCharacter,
ignoreValueFormatter,
shouldAppendQuotes,
}: {
id: GridRowId;
columns: GridStateColDef[];
getCellParams: (id: GridRowId, field: string) => GridCellParams;
delimiterCharacter: string;
ignoreValueFormatter: boolean;
shouldAppendQuotes: boolean;
}) => {
const row = new CSVRow({ delimiterCharacter });
const row = new CSVRow({ delimiterCharacter, shouldAppendQuotes });

columns.forEach((column) => {
const cellParams = getCellParams(id, column.field);
Expand All @@ -103,7 +117,13 @@ const serializeRow = ({
objectFormattedValueWarning();
}
}
row.addValue(serializeCellValue(cellParams, { delimiterCharacter, ignoreValueFormatter }));
row.addValue(
serializeCellValue(cellParams, {
delimiterCharacter,
ignoreValueFormatter,
shouldAppendQuotes,
}),
);
});

return row.getRowString();
Expand All @@ -117,6 +137,7 @@ interface BuildCSVOptions {
includeColumnGroupsHeaders: NonNullable<GridCsvExportOptions['includeColumnGroupsHeaders']>;
ignoreValueFormatter: boolean;
apiRef: React.MutableRefObject<GridApiCommunity>;
shouldAppendQuotes: boolean;
}

export function buildCSV(options: BuildCSVOptions): string {
Expand All @@ -128,6 +149,7 @@ export function buildCSV(options: BuildCSVOptions): string {
includeColumnGroupsHeaders,
ignoreValueFormatter,
apiRef,
shouldAppendQuotes,
} = options;

const CSVBody = rowIds
Expand All @@ -139,6 +161,7 @@ export function buildCSV(options: BuildCSVOptions): string {
getCellParams: apiRef.current.getCellParams,
delimiterCharacter,
ignoreValueFormatter,
shouldAppendQuotes,
})}\r\n`,
'',
)
Expand Down Expand Up @@ -168,7 +191,11 @@ export function buildCSV(options: BuildCSVOptions): string {
}, {});

for (let i = 0; i < maxColumnGroupsDepth; i += 1) {
const headerGroupRow = new CSVRow({ delimiterCharacter, sanitizeCellValue });
const headerGroupRow = new CSVRow({
delimiterCharacter,
sanitizeCellValue,
shouldAppendQuotes,
});
headerRows.push(headerGroupRow);
filteredColumns.forEach((column) => {
const columnGroupId = (columnGroupPathsLookup[column.field] || [])[i];
Expand All @@ -178,7 +205,7 @@ export function buildCSV(options: BuildCSVOptions): string {
}
}

const mainHeaderRow = new CSVRow({ delimiterCharacter, sanitizeCellValue });
const mainHeaderRow = new CSVRow({ delimiterCharacter, sanitizeCellValue, shouldAppendQuotes });
filteredColumns.forEach((column) => {
mainHeaderRow.addValue(column.headerName || column.field);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const useGridCsvExport = (
includeColumnGroupsHeaders: options.includeColumnGroupsHeaders ?? true,
ignoreValueFormatter,
apiRef,
shouldAppendQuotes: options.shouldAppendQuotes ?? true,
});
},
[logger, apiRef, ignoreValueFormatter],
Expand Down
6 changes: 6 additions & 0 deletions packages/x-data-grid/src/models/gridExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ export interface GridCsvExportOptions extends GridFileExportOptions {
* @returns {GridRowId[]} The list of row ids to export.
*/
getRowsToExport?: (params: GridCsvGetRowsToExportParams) => GridRowId[];
/**
* @ignore
* If `false`, the quotes will not be appended to the cell value.
* @default true
*/
shouldAppendQuotes?: boolean;
Comment on lines +98 to +103
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the property feels a bit weird to me. What's the purpose of should here? It feels like this would work as well.

Suggested change
/**
* @ignore
* If `false`, the quotes will not be appended to the cell value.
* @default true
*/
shouldAppendQuotes?: boolean;
/**
* @ignore
* If `false`, the quotes will not be appended to the cell value.
* @default true
*/
appendQuotes?: boolean;

}

/**
Expand Down