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] Implement CSV export #1030

Merged
merged 33 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f16ca59
Finish setup
DanailH Feb 11, 2021
548a417
First stable draft version
DanailH Feb 11, 2021
3752bb0
Resolve conflicts
DanailH Feb 11, 2021
1760a62
Remove unneded prop from new story
DanailH Feb 11, 2021
57da33b
Make getLocalization helper accept partial translations
DanailH Feb 12, 2021
d5ccdbc
wip
DanailH Feb 12, 2021
7ae1326
Fix typings
DanailH Feb 12, 2021
0656387
Rework exportAs util
DanailH Feb 12, 2021
258a427
Get latest changes
DanailH Feb 12, 2021
37c11db
Fix formatting
DanailH Feb 12, 2021
835ee07
Remove commented out code
DanailH Feb 12, 2021
55caa1b
Update return type
DanailH Feb 12, 2021
5f5c10a
Add docs
DanailH Feb 12, 2021
b538322
Add test
DanailH Feb 12, 2021
ff07421
Fix accessibility on density selector and export selector
DanailH Feb 12, 2021
edc32b5
Update docs
DanailH Feb 12, 2021
213a173
Update packages/grid/_modules_/grid/models/api/localeTextApi.ts
DanailH Feb 12, 2021
1e10f5d
Update packages/grid/_modules_/grid/constants/localeTextConstants.ts
DanailH Feb 12, 2021
a905d9e
Update packages/grid/_modules_/grid/components/toolbar/ExportSelector…
DanailH Feb 12, 2021
b4783f1
Fix PR comments
DanailH Feb 12, 2021
fa8fef7
Update docs
DanailH Feb 12, 2021
6e096e7
Fix focus issue for GridMenu
DanailH Feb 15, 2021
26dfc1b
remove ids and labledby for now
DanailH Feb 15, 2021
4635975
Fix PR comments
DanailH Feb 15, 2021
e7f0bfa
Fix formatting
DanailH Feb 15, 2021
40e7991
Remove new grid prop
DanailH Feb 16, 2021
5054556
Trigger Build
DanailH Feb 16, 2021
f9df9b8
give a try without the div, it works, it will also help catch wrong u…
oliviertassinari Feb 16, 2021
89b0b72
fix my typo, use prev to give more semantic to what the ref is about
oliviertassinari Feb 16, 2021
4f8c602
abstract logic and rename api
DanailH Feb 18, 2021
b5b26ba
Merge branch 'feature/DataGrid-197-csv-export' of github.com:DanailH/…
DanailH Feb 18, 2021
f27af51
Resolve conflicts
DanailH Feb 19, 2021
30488c1
Fix tests
DanailH Feb 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/pages/api-docs/data-grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { DataGrid } from '@material-ui/data-grid';
| <span class="prop-name">density</span> | <span class="prop-type">Density</span> | standard | Sets the density of the grid. |
| <span class="prop-name">disableColumnMenu</span> | <span class="prop-type">boolean</span> | false | If `true`, the column menu is disabled. |
| <span class="prop-name">disableColumnSelector</span> | <span class="prop-type">boolean</span> | false | If `true`, the column selector is disabled. |
| <span class="prop-name">disableCsvExport</span> | <span class="prop-type">boolean</span> | false | If `true`, the CSV export option in the export selector is disabled. |
| <span class="prop-name">disableExtendRowFullWidth</span> | <span class="prop-type">boolean</span> | false | If `true`, rows will not be extended to fill the full width of the grid container. |
| <span class="prop-name">disableSelectionOnClick</span> | <span class="prop-type">boolean</span> | false | If `true`, the selection on click on a row or cell is disabled. |
| <span class="prop-name">error</span> | <span class="prop-type">any</span> | | An error that will turn the grid into its error state and display the error component. |
Expand Down Expand Up @@ -99,6 +100,7 @@ Api of the `components` props of type `GridSlotsComponent`
| <span class="prop-name">DensityCompactIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">ViewHeadlineIcon</span> | Icon displayed on the compact density option in the toolbar. |
| <span class="prop-name">DensityStandardIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">TableRowsIcon</span> | Icon displayed on the standard density option in the toolbar. |
| <span class="prop-name">DensityComfortableIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">ViewStreamIcon</span> | Icon displayed on the comfortable density option in the toolbar. |
| <span class="prop-name">ExportIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">SaveAltIcon</span> | Icon displayed on the export button in the toolbar. |
| <span class="prop-name">OpenFilterButtonIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">FilterListIcon</span> | Icon displayed on the open filter button present in the toolbar by default. |

## CSS
Expand Down
2 changes: 2 additions & 0 deletions docs/pages/api-docs/x-grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { XGrid } from '@material-ui/x-grid';
| <span class="prop-name">disableColumnMenu</span> | <span class="prop-type">boolean</span> | false | If `true`, the column menu is disabled. |
| <span class="prop-name">disableColumnSelector</span> | <span class="prop-type">boolean</span> | false | If `true`, the column selector is disabled. |
| <span class="prop-name">disableColumnResize</span> | <span class="prop-type">boolean</span> | false | If `true`, resizing columns is disabled. |
| <span class="prop-name">disableCsvExport</span> | <span class="prop-type">boolean</span> | false | If `true`, the CSV export option in the export selector is disabled. |
| <span class="prop-name">disableExtendRowFullWidth</span> | <span class="prop-type">boolean</span> | false | If `true`, rows will not be extended to fill the full width of the grid container. |
| <span class="prop-name">disableMultipleColumnsSorting</span> | <span class="prop-type">boolean</span> | false | If `true`, sorting with multiple columns is disabled. |
| <span class="prop-name">disableMultipleSelection</span> | <span class="prop-type">boolean</span> | false | If `true`, multiple selection using the CTRL or CMD key is disabled. |
Expand Down Expand Up @@ -104,6 +105,7 @@ Api of the `components` props of type `GridSlotsComponent`
| <span class="prop-name">DensityCompactIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">ViewHeadlineIcon</span> | Icon displayed on the compact density option in the toolbar. |
| <span class="prop-name">DensityStandardIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">TableRowsIcon</span> | Icon displayed on the standard density option in the toolbar. |
| <span class="prop-name">DensityComfortableIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">ViewStreamIcon</span> | Icon displayed on the comfortable density option in the toolbar. |
| <span class="prop-name">ExportIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">SaveAltIcon</span> | Icon displayed on the export button in the toolbar. |
| <span class="prop-name">OpenFilterButtonIcon</span> | <span class="prop-type">React.ElementType </span> | <span class="prop-type">FilterListIcon</span> | Icon displayed on the open filter button present in the toolbar by default. |

## CSS
Expand Down
4 changes: 2 additions & 2 deletions docs/src/pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ datagrid.children =
{ pathname: '/components/data-grid/selection' },
{ pathname: '/components/data-grid/editing', title: '🚧 Editing' },
{ pathname: '/components/data-grid/rendering' },
{ pathname: '/components/data-grid/export', title: '🚧 Export & Import' },
{ pathname: '/components/data-grid/export', title: 'Export & Import' },
{ pathname: '/components/data-grid/localization', title: 'Localization' },
{ pathname: '/components/data-grid/group-pivot', title: '🚧 Group & Pivot' },
{ pathname: '/components/data-grid/accessibility' },
Expand All @@ -53,7 +53,7 @@ datagrid.children =
{ pathname: '/components/data-grid/selection' },
{ pathname: '/components/data-grid/editing', title: '🚧 Editing' },
{ pathname: '/components/data-grid/rendering' },
{ pathname: '/components/data-grid/export', title: '🚧 Export & Import' },
{ pathname: '/components/data-grid/export', title: 'Export & Import' },
{ pathname: '/components/data-grid/localization', title: 'Localization' },
{ pathname: '/components/data-grid/group-pivot', title: '🚧 Group & Pivot' },
{ pathname: '/components/data-grid/accessibility' },
Expand Down
34 changes: 34 additions & 0 deletions docs/src/pages/components/data-grid/export/ExportSelectorGrid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import {
DataGrid,
GridToolbarContainer,
GridToolbarExport,
} from '@material-ui/data-grid';
import { useDemoData } from '@material-ui/x-grid-data-generator';

function CustomToolbar() {
return (
<GridToolbarContainer>
<GridToolbarExport />
</GridToolbarContainer>
);
}

export default function ExportSelectorGrid() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 4,
maxColumns: 6,
});

return (
<div style={{ height: 300, width: '100%' }}>
<DataGrid
{...data}
components={{
Toolbar: CustomToolbar,
}}
/>
</div>
);
}
34 changes: 34 additions & 0 deletions docs/src/pages/components/data-grid/export/ExportSelectorGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import {
DataGrid,
GridToolbarContainer,
GridToolbarExport,
} from '@material-ui/data-grid';
import { useDemoData } from '@material-ui/x-grid-data-generator';

function CustomToolbar() {
return (
<GridToolbarContainer>
<GridToolbarExport />
</GridToolbarContainer>
);
}

export default function ExportSelectorGrid() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 4,
maxColumns: 6,
});

return (
<div style={{ height: 300, width: '100%' }}>
<DataGrid
{...data}
components={{
Toolbar: CustomToolbar,
}}
/>
</div>
);
}
12 changes: 7 additions & 5 deletions docs/src/pages/components/data-grid/export/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ components: DataGrid, XGrid

<p class="description">Easily export the rows in various file formats such as CSV, Excel, or PDF.</p>

## 🚧 CSV export
## CSV export

> ⚠️ This feature isn't implemented yet. It's coming.
>
> 👍 Upvote [issue #197](https://github.com/mui-org/material-ui-x/issues/197) if you want to see it land faster.
You are able to export the displayed data to CSV with an API call, or using the grid UI.

To enable the CSV export you need to compose a toolbar containing the `GridToolbarExport` component, and apply it using the `Toolbar` key in the grid `components` prop.

{{"demo": "pages/components/data-grid/export/ExportSelectorGrid.js", "bg": "inline"}}

You will be able to export the displayed data to CSV with an API call, or using the grid UI.
To hide the CSV export add the `disableCsvExport` prop to the data grid.

## 🚧 Print

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ The following table summarizes the features available in the community `DataGrid
| [Row edition](/components/data-grid/editing/#row-editing) | 🚧 | 🚧 | 🚧 |
| [Cell editing](/components/data-grid/editing/#cell-editing) | 🚧 | 🚧 | 🚧 |
| **Import & export** | | | |
| [CSV export](/components/data-grid/export/#csv-export) | 🚧 | 🚧 | 🚧 |
| [CSV export](/components/data-grid/export/#csv-export) | | | |
| [Print](/components/data-grid/export/#print) | 🚧 | 🚧 | 🚧 |
| [Excel export](/components/data-grid/export/#excel-export) | ❌ | ❌ | 🚧 |
| [Clipboard](/components/data-grid/export/#clipboard) | ❌ | 🚧 | 🚧 |
Expand Down
2 changes: 2 additions & 0 deletions packages/grid/_modules_/grid/GridComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { RootContainerRef } from './models/rootContainerRef';
import { ApiContext } from './components/api-context';
import { useFilter } from './hooks/features/filter/useFilter';
import { useLocaleText } from './hooks/features/localeText/useLocaleText';
import { useCsvExport } from './hooks/features/export';

export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps>(
function GridComponent(props, ref) {
Expand Down Expand Up @@ -85,6 +86,7 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
useVirtualRows(columnsHeaderRef, windowRef, renderingZoneRef, apiRef);
useColumnReorder(apiRef);
useColumnResize(columnsHeaderRef, apiRef);
useCsvExport(apiRef);
usePagination(apiRef);

const components = useComponents(props.components, props.componentsProps, apiRef);
Expand Down
5 changes: 5 additions & 0 deletions packages/grid/_modules_/grid/components/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,8 @@ export const DragIcon = createSvgIcon(
<path d="M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" />,
'Drag',
);

export const SaveAltIcon = createSvgIcon(
<path d="M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zm-6 .67l2.59-2.58L17 11.5l-5 5-5-5 1.41-1.41L11 12.67V3h2z" />,
'SaveAlt',
);
14 changes: 13 additions & 1 deletion packages/grid/_modules_/grid/components/menu/GridMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type MenuPosition =
export interface MenuProps extends Omit<PopperProps, 'onKeyDown'> {
open: boolean;
target: React.ReactNode;
onClickAway: (event: React.MouseEvent<Document, MouseEvent>) => void;
onClickAway: (event?: React.MouseEvent<Document, MouseEvent>) => void;
position?: MenuPosition;
}

Expand All @@ -39,6 +39,18 @@ export const GridMenu: React.FC<MenuProps> = ({
position,
...other
}) => {
const targetRef = React.useRef(target);
const openRef = React.useRef(open);

React.useEffect(() => {
if (openRef.current && targetRef.current) {
(targetRef.current as HTMLElement).focus();
}

openRef.current = open;
targetRef.current = target;
}, [open, target]);

return (
<Popper open={open} anchorEl={target as any} transition placement={position} {...other}>
{({ TransitionProps, placement }) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export function DensitySelector() {
startIcon={getSelectedDensityIcon()}
onClick={handleDensitySelectorOpen}
aria-label={apiRef!.current.getLocaleText('toolbarDensityLabel')}
aria-haspopup="true"
aria-expanded={anchorEl ? 'true' : undefined}
aria-haspopup="listbox"
>
{apiRef!.current.getLocaleText('toolbarDensity')}
</Button>
Expand All @@ -98,7 +99,7 @@ export function DensitySelector() {
onClickAway={handleDensitySelectorClose}
position="bottom-start"
>
<MenuList id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuList role="listbox" onKeyDown={handleListKeyDown} autoFocusItem={Boolean(anchorEl)}>
{renderDensityOptions}
</MenuList>
</GridMenu>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GridToolbarContainer } from '../containers/GridToolbarContainer';
import { ColumnsToolbarButton } from './ColumnsToolbarButton';
import { DensitySelector } from './DensitySelector';
import { FilterToolbarButton } from './FilterToolbarButton';
import { GridToolbarExport } from './GridToolbarExport';

export function GridToolbar() {
const apiRef = useContext(ApiContext);
Expand All @@ -15,7 +16,8 @@ export function GridToolbar() {
if (
options.disableColumnFilter &&
options.disableColumnSelector &&
options.disableDensitySelector
options.disableDensitySelector &&
options.disableCsvExport
) {
return null;
}
Expand All @@ -25,6 +27,7 @@ export function GridToolbar() {
<ColumnsToolbarButton />
<FilterToolbarButton />
<DensitySelector />
<GridToolbarExport />
</GridToolbarContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from 'react';
import MenuList from '@material-ui/core/MenuList';
import Button from '@material-ui/core/Button';
import MenuItem from '@material-ui/core/MenuItem';
import { ApiContext } from '../api-context';
import { useGridSelector } from '../../hooks/features/core/useGridSelector';
import { optionsSelector } from '../../hooks/utils/optionsSelector';
import { GridMenu } from '../menu/GridMenu';
import { ExportOption } from '../../models';

export function GridToolbarExport() {
const apiRef = React.useContext(ApiContext);
const options = useGridSelector(apiRef, optionsSelector);
const [anchorEl, setAnchorEl] = React.useState(null);
const ExportIcon = apiRef!.current.components!.ExportIcon!;

const ExportOptions: Array<ExportOption> = [
{
label: apiRef!.current.getLocaleText('toolbarExportCSV'),
format: 'csv',
},
];

const handleExportSelectorOpen = (event) => setAnchorEl(event.currentTarget);
const handleExportSelectorClose = () => setAnchorEl(null);
const handleExport = (format) => {
if (format === 'csv') {
apiRef!.current.exportDataAsCsv();
}

setAnchorEl(null);
};

const handleListKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Tab' || event.key === 'Escape') {
event.preventDefault();
handleExportSelectorClose();
}
};

// Disable the button if the corresponding is disabled
if (options.disableCsvExport) {
return null;
}

const renderExportOptions: Array<React.ReactElement> = ExportOptions.map((option, index) => (
<MenuItem key={index} onClick={() => handleExport(option.format)}>
{option.label}
</MenuItem>
));

return (
<React.Fragment>
<Button
color="primary"
size="small"
startIcon={<ExportIcon />}
onClick={handleExportSelectorOpen}
aria-expanded={Boolean(anchorEl)}
aria-haspopup="true"
>
{apiRef!.current.getLocaleText('toolbarExport')}
</Button>
<GridMenu
open={Boolean(anchorEl)}
target={anchorEl}
onClickAway={handleExportSelectorClose}
position="bottom-start"
>
<MenuList onKeyDown={handleListKeyDown} autoFocusItem={Boolean(anchorEl)}>
{renderExportOptions}
</MenuList>
</GridMenu>
</React.Fragment>
);
}
1 change: 1 addition & 0 deletions packages/grid/_modules_/grid/components/toolbar/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './ColumnsToolbarButton';
export * from './DensitySelector';
export * from './GridToolbarExport';
export * from './FilterToolbarButton';
export * from './GridToolbar';
5 changes: 5 additions & 0 deletions packages/grid/_modules_/grid/constants/localeTextConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export const DEFAULT_LOCALE_TEXT: LocaleText = {
toolbarFiltersTooltipShow: 'Show Filters',
toolbarFiltersTooltipActive: (count) => `${count} active filter(s)`,

// Export selector toolbar button text
toolbarExport: 'Export',
toolbarExportLabel: 'Export',
toolbarExportCSV: 'Download as CSV',

// Columns panel text
columnsPanelTextFieldLabel: 'Find column',
columnsPanelTextFieldPlaceholder: 'Column title',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useCsvExport';
Loading