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] Add methods to import and export the state #3593

Merged
merged 39 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9ff83b3
[DataGrid] Add methods to important and export the state
flaviendelangle Jan 11, 2022
c984c27
Add examples
flaviendelangle Jan 11, 2022
ef0ac05
Work
flaviendelangle Jan 11, 2022
03d10d6
Work
flaviendelangle Jan 11, 2022
efe8919
[core] Add typing to the pre-processors methods
flaviendelangle Jan 11, 2022
c323569
Merge branch 'master' into type-pre-processors
flaviendelangle Jan 11, 2022
15fb5d6
Prettier
flaviendelangle Jan 11, 2022
450dcce
Fix
flaviendelangle Jan 11, 2022
170749a
Work
flaviendelangle Jan 11, 2022
3ebdbbd
Merge
flaviendelangle Jan 11, 2022
e3530f7
Work
flaviendelangle Jan 12, 2022
54e1798
Work
flaviendelangle Jan 13, 2022
0742e73
Work
flaviendelangle Jan 13, 2022
09df648
Work
flaviendelangle Jan 14, 2022
63c44ab
Work
flaviendelangle Jan 17, 2022
ca3d460
Merge
flaviendelangle Jan 18, 2022
8ac284c
Work
flaviendelangle Jan 18, 2022
3dbf93f
Work
flaviendelangle Jan 18, 2022
7a7ebd1
Work
flaviendelangle Jan 18, 2022
d078600
Merge
flaviendelangle Jan 19, 2022
18722fd
Work
flaviendelangle Jan 19, 2022
91fd926
Merge branch 'master' into state-import-export
flaviendelangle Jan 20, 2022
d179776
Add columnVisibilityModel and rowGroupingModel
flaviendelangle Jan 20, 2022
73e3800
Work
flaviendelangle Jan 25, 2022
5808f09
Code review: Matheus
flaviendelangle Jan 25, 2022
63bb319
Merge
flaviendelangle Jan 25, 2022
8e3a7c3
Update docs/src/pages/components/data-grid/state/state.md
flaviendelangle Jan 26, 2022
477167b
Update docs/src/pages/components/data-grid/state/state.md
flaviendelangle Jan 26, 2022
6c9c425
Merge branch 'master' into state-import-export
flaviendelangle Jan 26, 2022
bd6eb77
Code review: Matheus
flaviendelangle Jan 26, 2022
89f5899
Fix
flaviendelangle Jan 26, 2022
eee711d
Merge
flaviendelangle Jan 26, 2022
7250734
Merge
flaviendelangle Jan 28, 2022
3b3689a
Merge branch 'master' into state-import-export
flaviendelangle Feb 1, 2022
f58ad6d
Code review: Matheus
flaviendelangle Feb 1, 2022
56e304f
Fix
flaviendelangle Feb 1, 2022
8053e8f
Code review: Matheus
flaviendelangle Feb 1, 2022
793a2dc
Merge
flaviendelangle Feb 1, 2022
02e7534
Hide doc
flaviendelangle Feb 2, 2022
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
190 changes: 96 additions & 94 deletions docs/pages/api-docs/data-grid/grid-api.md

Large diffs are not rendered by default.

338 changes: 338 additions & 0 deletions docs/src/pages/components/data-grid/state/RestoreStateApiRef.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { DataGridPro, useGridApiContext, useGridApiRef } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import TextField from '@mui/material/TextField';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/Delete';
import DoneIcon from '@mui/icons-material/Done';
import Divider from '@mui/material/Divider';
import Fade from '@mui/material/Fade';
import Popper from '@mui/material/Popper';

const demoReducer = (state, action) => {
switch (action.type) {
case 'createView': {
const id = Math.random().toString();

return {
...state,
activeViewId: id,
newViewLabel: '',
views: {
...state.views,
[id]: { label: state.newViewLabel, value: action.value },
},
};
}

case 'deleteView': {
const views = Object.fromEntries(
Object.entries(state.views).filter(([id]) => id !== action.id),
);

let activeViewId;
if (state.activeViewId !== action.id) {
activeViewId = state.activeViewId;
} else {
const viewIds = Object.keys(state.views);

if (viewIds.length === 0) {
activeViewId = null;
} else {
activeViewId = viewIds[0];
}
}

return {
...state,
views,
activeViewId,
};
}

case 'setActiveView': {
return {
...state,
activeViewId: action.id,
isMenuOpened: false,
};
}

case 'setNewViewLabel': {
return {
...state,
newViewLabel: action.label,
};
}

case 'togglePopper': {
return {
...state,
isMenuOpened: !state.isMenuOpened,
menuAnchorEl: action.element,
};
}

case 'closePopper': {
return {
...state,
isMenuOpened: false,
};
}

default: {
return state;
}
}
};

const DEMO_INITIAL_STATE = {
views: {},
newViewLabel: '',
isMenuOpened: false,
menuAnchorEl: null,
activeViewId: null,
};

const ViewListItem = (props) => {
const { view, viewId, selected, onDelete, onSelect } = props;

return (
<ListItem
disablePadding
selected={selected}
onClick={() => onSelect(viewId)}
secondaryAction={
<IconButton
edge="end"
aria-label="delete"
size="small"
onClick={(event) => {
event.stopPropagation();
onDelete(viewId);
}}
>
<DeleteIcon />
</IconButton>
}
>
<ListItemButton>
<ListItemText primary={view.label} />
</ListItemButton>
</ListItem>
);
};

ViewListItem.propTypes = {
onDelete: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
selected: PropTypes.bool.isRequired,
view: PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.shape({
columns: PropTypes.shape({
columnVisibilityModel: PropTypes.object,
}),
filter: PropTypes.shape({
filterModel: PropTypes.object,
}),
pagination: PropTypes.shape({
page: PropTypes.number,
pageSize: PropTypes.number,
}),
pinnedColumns: PropTypes.shape({
left: PropTypes.arrayOf(PropTypes.string),
right: PropTypes.arrayOf(PropTypes.string),
}),
preferencePanel: PropTypes.shape({
open: PropTypes.bool.isRequired,
openedPanelValue: PropTypes.oneOf(['columns', 'filters']),
}),
rowGrouping: PropTypes.shape({
model: PropTypes.arrayOf(PropTypes.string),
}),
sorting: PropTypes.shape({
sortModel: PropTypes.arrayOf(PropTypes.object),
}),
}).isRequired,
}).isRequired,
viewId: PropTypes.string.isRequired,
};

const NewViewListItem = (props) => {
const { label, onLabelChange, onSubmit, isValid } = props;
const [isAddingView, setIsAddingView] = React.useState(false);

if (isAddingView) {
return (
<ListItem
secondaryAction={
<IconButton
edge="end"
aria-label="delete"
size="small"
onClick={() => {
onSubmit();
setIsAddingView(false);
}}
disabled={!isValid}
>
<DoneIcon />
</IconButton>
}
>
<TextField
value={label}
onChange={onLabelChange}
size="small"
label="Custom view label"
variant="standard"
/>
</ListItem>
);
}

return (
<ListItem>
<Button
size="small"
startIcon={<AddIcon />}
onClick={() => setIsAddingView(true)}
>
Add a custom view
</Button>
</ListItem>
);
};

NewViewListItem.propTypes = {
isValid: PropTypes.bool.isRequired,
label: PropTypes.string.isRequired,
onLabelChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
};

const CustomToolbar = () => {
const apiRef = useGridApiContext();
const [state, dispatch] = React.useReducer(demoReducer, DEMO_INITIAL_STATE);

const createNewView = () => {
dispatch({
type: 'createView',
value: apiRef.current.exportState(),
});
};

const handleNewViewLabelChange = (e) => {
dispatch({ type: 'setNewViewLabel', label: e.target.value });
};

const handleDeleteView = React.useCallback((viewId) => {
dispatch({ type: 'deleteView', id: viewId });
}, []);

const handleSetActiveView = (viewId) => {
apiRef.current.restoreState(state.views[viewId].value);
dispatch({ type: 'setActiveView', id: viewId });
};

const handlePopperAnchorClick = (event) => {
dispatch({ type: 'togglePopper', element: event.currentTarget });
event.stopPropagation();
};

const handleClosePopper = () => {
dispatch({ type: 'closePopper' });
};

const isNewViewLabelValid = React.useMemo(() => {
if (state.newViewLabel.length === 0) {
return false;
}

return Object.values(state.views).every(
(view) => view.label !== state.newViewLabel,
);
}, [state.views, state.newViewLabel]);

const canBeMenuOpened = state.isMenuOpened && Boolean(state.menuAnchorEl);
const popperId = canBeMenuOpened ? 'transition-popper' : undefined;

return (
<React.Fragment>
<Button
aria-describedby={popperId}
type="button"
onClick={handlePopperAnchorClick}
>
Custom views
</Button>
<ClickAwayListener onClickAway={handleClosePopper}>
<Popper
id={popperId}
open={state.isMenuOpened}
anchorEl={state.menuAnchorEl}
transition
placement="bottom-start"
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper>
<List>
{Object.entries(state.views).map(([viewId, view]) => (
<ViewListItem
key={viewId}
view={view}
viewId={viewId}
selected={viewId === state.activeViewId}
onDelete={handleDeleteView}
onSelect={handleSetActiveView}
/>
))}

<Divider />
<NewViewListItem
label={state.newViewLabel}
onLabelChange={handleNewViewLabelChange}
onSubmit={createNewView}
isValid={isNewViewLabelValid}
/>
</List>
</Paper>
</Fade>
)}
</Popper>
</ClickAwayListener>
</React.Fragment>
);
};

export default function RestoreStateApiRef() {
const apiRef = useGridApiRef();
const { data, loading } = useDemoData({
dataSet: 'Commodity',
rowLength: 500,
});

return (
<Box sx={{ width: '100%', height: 400, bgcolor: 'background.paper' }}>
<DataGridPro
components={{ Toolbar: CustomToolbar }}
loading={loading}
apiRef={apiRef}
pagination
{...data}
/>
</Box>
);
}