From 8a23eba85f26345c750f7c8627a899be3da1062a Mon Sep 17 00:00:00 2001 From: Michal Lukowski Date: Tue, 18 May 2021 14:43:04 +0200 Subject: [PATCH 1/5] [DataGrid] Add optional exportConfiguration props to GridToolbarExport #1440 This feature allows to: - set the file name - set flag utf8WithBom to generate csv file as UTF-8 with BOM It may be extended with further options in the future --- .../components/toolbar/GridToolbarExport.tsx | 45 +++++++++++++------ .../features/export/useGridCsvExport.tsx | 20 ++++++--- .../grid/models/api/gridCsvExportApi.ts | 4 +- .../grid/_modules_/grid/models/gridExport.ts | 16 ++++++- .../grid/_modules_/grid/utils/exportAs.ts | 4 +- 5 files changed, 65 insertions(+), 24 deletions(-) diff --git a/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx b/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx index b8029d608198..6b2d7f7ebcc4 100644 --- a/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx +++ b/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx @@ -7,28 +7,44 @@ import MenuItem from '@material-ui/core/MenuItem'; import { isHideMenuKey, isTabKey } from '../../utils/keyboardUtils'; import { GridApiContext } from '../GridApiContext'; import { GridMenu } from '../menu/GridMenu'; -import { GridExportOption } from '../../models'; +import { GridExportOption, GridExportFormat, GridExportConfiguration } from '../../models'; -export const GridToolbarExport = React.forwardRef( - function GridToolbarExport(props, ref) { +type GridToolbarExportProps = ButtonProps & { exportConfiguration?: GridExportConfiguration }; + +export const GridToolbarExport = React.forwardRef( + function GridToolbarExport({ exportConfiguration, ...buttonProps }, ref) { const apiRef = React.useContext(GridApiContext); const exportButtonId = useId(); const exportMenuId = useId(); const [anchorEl, setAnchorEl] = React.useState(null); const ExportIcon = apiRef!.current.components!.ExportIcon!; - const ExportOptions: Array = [ - { + const ExportOptions: Array = []; + + if (exportConfiguration) { + if (!exportConfiguration.csv?.disabled) { + ExportOptions.push({ + label: apiRef!.current.getLocaleText('toolbarExportCSV'), + format: { + name: 'csv', + options: exportConfiguration.csv, + }, + }); + } + } else { + ExportOptions.push({ label: apiRef!.current.getLocaleText('toolbarExportCSV'), - format: 'csv', - }, - ]; + format: { + name: 'csv', + }, + }); + } const handleExportSelectorOpen = (event) => setAnchorEl(event.currentTarget); const handleExportSelectorClose = () => setAnchorEl(null); - const handleExport = (format) => { - if (format === 'csv') { - apiRef!.current.exportDataAsCsv(); + const handleExport = (format: GridExportFormat, fileName?: string) => { + if (format.name === 'csv') { + apiRef!.current.exportDataAsCsv(format.options, fileName); } setAnchorEl(null); @@ -44,7 +60,10 @@ export const GridToolbarExport = React.forwardRef = ExportOptions.map((option, index) => ( - handleExport(option.format)}> + handleExport(option.format, exportConfiguration?.fileName)} + > {option.label} )); @@ -61,7 +80,7 @@ export const GridToolbarExport = React.forwardRef {apiRef!.current.getLocaleText('toolbarExport')} diff --git a/packages/grid/_modules_/grid/hooks/features/export/useGridCsvExport.tsx b/packages/grid/_modules_/grid/hooks/features/export/useGridCsvExport.tsx index a788eaafa7e9..467aa5ccc950 100644 --- a/packages/grid/_modules_/grid/hooks/features/export/useGridCsvExport.tsx +++ b/packages/grid/_modules_/grid/hooks/features/export/useGridCsvExport.tsx @@ -5,7 +5,7 @@ import { useGridSelector } from '../core/useGridSelector'; import { visibleGridColumnsSelector } from '../columns'; import { visibleSortedGridRowsSelector } from '../filter'; import { gridSelectionStateSelector } from '../selection'; -import { GridCsvExportApi } from '../../../models'; +import { GridCsvExportApi, GridExportCsvOptions } from '../../../models'; import { useLogger } from '../../utils/useLogger'; import { exportAs } from '../../../utils'; import { buildCSV } from './seralizers/csvSeraliser'; @@ -22,13 +22,19 @@ export const useGridCsvExport = (apiRef: GridApiRef): void => { return buildCSV(visibleColumns, visibleSortedRows, selection, apiRef.current.getCellValue); }, [logger, visibleColumns, visibleSortedRows, selection, apiRef]); - const exportDataAsCsv = React.useCallback((): void => { - logger.debug(`Export data as CSV`); - const csv = getDataAsCsv(); - const blob = new Blob([csv], { type: 'text/csv' }); + const exportDataAsCsv = React.useCallback( + (options?: GridExportCsvOptions, fileName: string = 'data'): void => { + logger.debug(`Export data as CSV`); + const csv = getDataAsCsv(); - exportAs(blob, 'csv', 'data'); - }, [logger, getDataAsCsv]); + const blob = new Blob([options?.utf8WithBom ? new Uint8Array([0xef, 0xbb, 0xbf]) : '', csv], { + type: 'text/csv', + }); + + exportAs(blob, 'csv', fileName.length ? fileName : 'data'); + }, + [logger, getDataAsCsv], + ); const csvExportApi: GridCsvExportApi = { getDataAsCsv, diff --git a/packages/grid/_modules_/grid/models/api/gridCsvExportApi.ts b/packages/grid/_modules_/grid/models/api/gridCsvExportApi.ts index e3fe81214025..e68774acab51 100644 --- a/packages/grid/_modules_/grid/models/api/gridCsvExportApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridCsvExportApi.ts @@ -1,3 +1,5 @@ +import { GridExportCsvOptions } from '../gridExport'; + /** * The csv export API interface that is available in the grid [[apiRef]]. */ @@ -10,5 +12,5 @@ export interface GridCsvExportApi { /** * Exports the grid data as CSV and sends it to the user. */ - exportDataAsCsv: () => void; + exportDataAsCsv: (options?: GridExportCsvOptions, fileName?: string) => void; } diff --git a/packages/grid/_modules_/grid/models/gridExport.ts b/packages/grid/_modules_/grid/models/gridExport.ts index cd8e73764dac..90ed438ef686 100644 --- a/packages/grid/_modules_/grid/models/gridExport.ts +++ b/packages/grid/_modules_/grid/models/gridExport.ts @@ -1,7 +1,21 @@ +export interface GridExportCsvOptions { + utf8WithBom?: boolean; + disabled?: boolean; +} +export interface GridExportFormatCsv { + name: 'csv'; + options?: GridExportCsvOptions; +} + /** * Available export formats. To be extended in future. */ -export type GridExportFormat = 'csv'; +export type GridExportFormatExtension = 'csv'; +export type GridExportFormat = GridExportFormatCsv; +export interface GridExportConfiguration { + fileName?: string; + csv?: GridExportCsvOptions; +} /** * Export option interface diff --git a/packages/grid/_modules_/grid/utils/exportAs.ts b/packages/grid/_modules_/grid/utils/exportAs.ts index dde478cadd98..cfe4e2ed30ff 100644 --- a/packages/grid/_modules_/grid/utils/exportAs.ts +++ b/packages/grid/_modules_/grid/utils/exportAs.ts @@ -1,4 +1,4 @@ -import { GridExportFormat } from '../models/gridExport'; +import { GridExportFormatExtension } from '../models/gridExport'; /** * I have hesitate to use https://github.com/eligrey/FileSaver.js. @@ -12,7 +12,7 @@ import { GridExportFormat } from '../models/gridExport'; */ export function exportAs( blob: Blob, - extension: GridExportFormat = 'csv', + extension: GridExportFormatExtension = 'csv', filename: string = document.title, ): void { const fullName = `${filename}.${extension}`; From a248e6c56219f8dce423bcc64ea1b62694522907 Mon Sep 17 00:00:00 2001 From: Michal Lukowski Date: Thu, 20 May 2021 22:59:37 +0200 Subject: [PATCH 2/5] Changes after review --- .../components/toolbar/GridToolbarExport.tsx | 38 ++++++------------- .../features/export/useGridCsvExport.tsx | 4 +- .../grid/_modules_/grid/models/gridExport.ts | 8 ++-- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx b/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx index 6b2d7f7ebcc4..bf1408ac66b9 100644 --- a/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx +++ b/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx @@ -12,7 +12,7 @@ import { GridExportOption, GridExportFormat, GridExportConfiguration } from '../ type GridToolbarExportProps = ButtonProps & { exportConfiguration?: GridExportConfiguration }; export const GridToolbarExport = React.forwardRef( - function GridToolbarExport({ exportConfiguration, ...buttonProps }, ref) { + function GridToolbarExport({ exportConfiguration, ...other }, ref) { const apiRef = React.useContext(GridApiContext); const exportButtonId = useId(); const exportMenuId = useId(); @@ -21,30 +21,17 @@ export const GridToolbarExport = React.forwardRef = []; - if (exportConfiguration) { - if (!exportConfiguration.csv?.disabled) { - ExportOptions.push({ - label: apiRef!.current.getLocaleText('toolbarExportCSV'), - format: { - name: 'csv', - options: exportConfiguration.csv, - }, - }); - } - } else { - ExportOptions.push({ - label: apiRef!.current.getLocaleText('toolbarExportCSV'), - format: { - name: 'csv', - }, - }); - } + ExportOptions.push({ + label: apiRef!.current.getLocaleText('toolbarExportCSV'), + format: 'csv', + options: exportConfiguration?.csv, + }); const handleExportSelectorOpen = (event) => setAnchorEl(event.currentTarget); const handleExportSelectorClose = () => setAnchorEl(null); - const handleExport = (format: GridExportFormat, fileName?: string) => { - if (format.name === 'csv') { - apiRef!.current.exportDataAsCsv(format.options, fileName); + const handleExport = (option: GridExportFormat, fileName?: string) => { + if (option.format === 'csv') { + apiRef!.current.exportDataAsCsv(option.options, fileName); } setAnchorEl(null); @@ -60,10 +47,7 @@ export const GridToolbarExport = React.forwardRef = ExportOptions.map((option, index) => ( - handleExport(option.format, exportConfiguration?.fileName)} - > + handleExport(option, exportConfiguration?.fileName)}> {option.label} )); @@ -80,7 +64,7 @@ export const GridToolbarExport = React.forwardRef {apiRef!.current.getLocaleText('toolbarExport')} diff --git a/packages/grid/_modules_/grid/hooks/features/export/useGridCsvExport.tsx b/packages/grid/_modules_/grid/hooks/features/export/useGridCsvExport.tsx index 467aa5ccc950..65da0c4d7047 100644 --- a/packages/grid/_modules_/grid/hooks/features/export/useGridCsvExport.tsx +++ b/packages/grid/_modules_/grid/hooks/features/export/useGridCsvExport.tsx @@ -23,7 +23,7 @@ export const useGridCsvExport = (apiRef: GridApiRef): void => { }, [logger, visibleColumns, visibleSortedRows, selection, apiRef]); const exportDataAsCsv = React.useCallback( - (options?: GridExportCsvOptions, fileName: string = 'data'): void => { + (options?: GridExportCsvOptions, fileName?: string): void => { logger.debug(`Export data as CSV`); const csv = getDataAsCsv(); @@ -31,7 +31,7 @@ export const useGridCsvExport = (apiRef: GridApiRef): void => { type: 'text/csv', }); - exportAs(blob, 'csv', fileName.length ? fileName : 'data'); + exportAs(blob, 'csv', fileName?.length ? fileName : undefined); }, [logger, getDataAsCsv], ); diff --git a/packages/grid/_modules_/grid/models/gridExport.ts b/packages/grid/_modules_/grid/models/gridExport.ts index 90ed438ef686..56dc6e28067e 100644 --- a/packages/grid/_modules_/grid/models/gridExport.ts +++ b/packages/grid/_modules_/grid/models/gridExport.ts @@ -1,9 +1,8 @@ export interface GridExportCsvOptions { utf8WithBom?: boolean; - disabled?: boolean; } export interface GridExportFormatCsv { - name: 'csv'; + format: 'csv'; options?: GridExportCsvOptions; } @@ -20,7 +19,6 @@ export interface GridExportConfiguration { /** * Export option interface */ -export interface GridExportOption { +export type GridExportOption = GridExportFormat & { label: React.ReactNode; - format: GridExportFormat; -} +}; From 49cfcf0990018fb889da0ec4c0fbbb8e977d70c4 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Mon, 31 May 2021 18:24:05 +0200 Subject: [PATCH 3/5] convention --- .../components/toolbar/GridToolbarExport.tsx | 65 +++++++++++-------- .../features/export/useGridCsvExport.tsx | 22 ++++--- .../grid/models/api/gridCsvExportApi.ts | 16 +++-- .../grid/_modules_/grid/models/gridExport.ts | 22 ++----- .../grid/_modules_/grid/utils/exportAs.ts | 4 +- 5 files changed, 69 insertions(+), 60 deletions(-) diff --git a/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx b/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx index bf1408ac66b9..a3c2f2c0a2b5 100644 --- a/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx +++ b/packages/grid/_modules_/grid/components/toolbar/GridToolbarExport.tsx @@ -7,31 +7,44 @@ import MenuItem from '@material-ui/core/MenuItem'; import { isHideMenuKey, isTabKey } from '../../utils/keyboardUtils'; import { GridApiContext } from '../GridApiContext'; import { GridMenu } from '../menu/GridMenu'; -import { GridExportOption, GridExportFormat, GridExportConfiguration } from '../../models'; +import { GridExportCsvOptions } from '../../models/gridExport'; -type GridToolbarExportProps = ButtonProps & { exportConfiguration?: GridExportConfiguration }; +interface GridExportFormatCsv { + format: 'csv'; + formatOptions?: GridExportCsvOptions; +} + +type GridExportFormatOption = GridExportFormatCsv; + +type GridExportOption = GridExportFormatOption & { + label: React.ReactNode; +}; + +export interface GridToolbarExportProps extends ButtonProps { + csvOptions?: GridExportCsvOptions; +} export const GridToolbarExport = React.forwardRef( - function GridToolbarExport({ exportConfiguration, ...other }, ref) { + function GridToolbarExport(props, ref) { + const { csvOptions, ...other } = props; const apiRef = React.useContext(GridApiContext); - const exportButtonId = useId(); - const exportMenuId = useId(); + const buttonId = useId(); + const menuId = useId(); const [anchorEl, setAnchorEl] = React.useState(null); const ExportIcon = apiRef!.current.components!.ExportIcon!; - const ExportOptions: Array = []; - - ExportOptions.push({ + const exportOptions: Array = []; + exportOptions.push({ label: apiRef!.current.getLocaleText('toolbarExportCSV'), format: 'csv', - options: exportConfiguration?.csv, + formatOptions: csvOptions, }); - const handleExportSelectorOpen = (event) => setAnchorEl(event.currentTarget); - const handleExportSelectorClose = () => setAnchorEl(null); - const handleExport = (option: GridExportFormat, fileName?: string) => { + const handleMenuOpen = (event) => setAnchorEl(event.currentTarget); + const handleMenuClose = () => setAnchorEl(null); + const handleExport = (option: GridExportFormatOption) => () => { if (option.format === 'csv') { - apiRef!.current.exportDataAsCsv(option.options, fileName); + apiRef!.current.exportDataAsCsv(option.formatOptions); } setAnchorEl(null); @@ -42,16 +55,10 @@ export const GridToolbarExport = React.forwardRef = ExportOptions.map((option, index) => ( - handleExport(option, exportConfiguration?.fileName)}> - {option.label} - - )); - return (