mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.02k stars 1.24k forks source link

[data grid] Add column type to view and edit long text #2851

Open zzossig opened 2 years ago

zzossig commented 2 years ago

Duplicates

Latest version

Summary πŸ’‘

As far as I know, data-grid-pro doesn't have a feature to edit a cell with a very long text. The text size could be few KB to MB and enter key should be preserved for a line feed(\n).

MUI document provide [Expand cell renderer] example for a long text that will allow seeing the full content of the cell in the grid. So to say you guys already knows that a long text is a common use case for grid component, right? Then, why not provide a way to edit a cell with a long text?

And I wonder what is the best way to edit a cell with a long text within current version? Can you please document for it?

Examples 🌈

I expect a new type(text) for a long text

<DataGridPro
  rows={[...]}
  columns={[
    field: 'full_content',
    type: 'text',
  ]}
/>

Motivation πŸ”¦

One of the use case for the mui-x grid is to display my database tables. The database tables can have a very long text column so I need this feature.

Benchmark

Screenshot 2022-12-25 at 12 32 16

Order ID πŸ’³ (optional)

27772

DanailH commented 2 years ago

@zzossig thanks for raising this. I guess what you are looking for is a new column type of textarea, or <TextField multiline />, correct?

To unblock you for now you can check how to create a custom column type here https://mui.com/components/data-grid/columns/#custom-column-types.

zzossig commented 2 years ago

@DanailH Hi. I guess you may misunderstood what I'm asking. Introducing a new column type is just what I'm suggesting. What I'm asking is How to edit a cell that has very long text

DanailH commented 2 years ago

But when you create a new column type you can provide renderEditCell where you can handle that case. Granted because of the fixed height of the rows some additional logic needs to be added.

zzossig commented 2 years ago

Thank you for the answer. I have tried renderEditCell with multiline TextField. But there are some problems when editing a long text

  1. Enter key is not preserved for line feed. So go to the next line in multiline TextField needs unnecessary extra works.
  2. Scrollbar should appear in a cell(currently, it is not appear). I can't scroll down within a cell.
  3. It is very difficult to edit a cell just within one line.

I'm pretty sure renderEditCell is not the best option to handle long text. Why not provide more handy option for that?

m4theushw commented 2 years ago

I created a CodeSandbox showing how to create a custom edit component with a textarea. You need to use a Portal to allow the input to exceed the cell boundaries. It should fix most of the issues you mentioned. The example can be further improved by debouncing api.setEditCellValue.

import InputBase from "@mui/material/InputBase";
import Popper from "@mui/material/Popper";
import Paper from "@mui/material/Paper";

const EditTextarea = (props) => {
  const { id, field, value, colDef, api } = props;
  const [valueState, setValueState] = React.useState(value);
  const [anchorEl, setAnchorEl] = React.useState<HTMLDivElement | null>();

  const handleRef = (el) => {
    setAnchorEl(el);
  };

  const handleChange = React.useCallback(
    (event) => {
      const newValue = event.target.value;
      setValueState(newValue);
      api.setEditCellValue({ id, field, value: newValue }, event);
    },
    [api, field, id]
  );

  return (
    <div>
      <div
        ref={handleRef}
        style={{
          height: 1,
          width: colDef.computedWidth,
          display: "block",
          position: "absolute",
          top: 0
        }}
      />
      {anchorEl && (
        <Popper open anchorEl={anchorEl} placement="top-start">
          <Paper elevation={1} sx={{ p: 1, minWidth: colDef.computedWidth }}>
            <InputBase
              multiline
              rows={4}
              value={valueState}
              sx={{ textarea: { resize: "both" }, width: "100%" }}
              onChange={handleChange}
            />
          </Paper>
        </Popper>
      )}
    </div>
  );
};

const renderEditTextarea = (params) => <EditTextarea {...params} />;
zzossig commented 2 years ago

@m4theushw Yes, the example works. Thank you.

Anyway, is there any chance to support this features natively?

  1. mui team do more quality code(better performance) than normal users.
  2. I think long text is a common use-case.

For future readers, this is the debounced version. This fixes input lagging

...
import { debounce } from "lodash";

const EditTextarea = (props: GridRenderCellParams) => {
  const { id, field, value, colDef, api } = props;
  const [valueState, setValueState] = React.useState(value);
  const [anchorEl, setAnchorEl] = React.useState<HTMLDivElement | null>();
  const minWidth = React.useMemo(() => {
    if (colDef.computedWidth) {
      return colDef.computedWidth + 400;
    }
    return 0;
  }, [colDef.computedWidth]);

  const setEditCellValue = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>, newValue: string) => {
      api.setEditCellValue({ id, field, value: newValue }, event);
    },
    [api, field, id]
  );

  const debouncedSetEditCellValue = React.useMemo(
    () => debounce(setEditCellValue, 60),
    [setEditCellValue]
  );

  const handleRef = (el: HTMLDivElement) => {
    setAnchorEl(el);
  };

  const handleChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const newValue = event.target.value;
      setValueState(newValue);
      debouncedSetEditCellValue(event, newValue);
    },
    [debouncedSetEditCellValue]
  );

  return (
    <div>
      <div
        ref={handleRef}
        style={{
          height: 1,
          width: minWidth,
          display: "block",
          position: "absolute",
        }}
      />
      {anchorEl && (
        <Popper open anchorEl={anchorEl} placement="bottom-end">
          <Paper elevation={1} sx={{ p: 1, width: minWidth }}>
            <InputBase
              multiline
              rows={10}
              value={valueState}
              sx={{ textarea: { resize: "both" }, width: "100%" }}
              onChange={handleChange}
            />
          </Paper>
        </Popper>
      )}
    </div>
  );
};

const renderEditTextarea = (params: any) => <EditTextarea {...params} />;
DanailH commented 2 years ago

We can look at having this column type out of the box. The tricky part is how do you know when to do the normal text input and when to do the text area.

@m2mathew maybe we can update the docks for now with this example now.

zzossig commented 2 years ago

I also have another question. How can I bind Escape(esc) key to close Popper component? I have tried this.

<Popper open={Boolean(anchorEl)}  ...>
    ...
    <InputBase
      onKeyDown={(e) => {
        if (e.key === "Escape") {
          setAnchorEl(null);
        }
      }}
    />
</Popper> 

Unfortunately, this is not working. Then, How can I close the Popper component with esc key?

zzossig commented 2 years ago

@m4theushw Also, there is another problem. When there are many EditTextarea in grid columns and editMode="row" say

<DataGridPro
    editMode="row"
    rows=[...]
    columns=[
        { ..., renderEditCell: EditTextarea },
        { ..., renderEditCell: EditTextarea },
        { ..., renderEditCell: EditTextarea },
        ...
    ]
/>

Then, edit mode looks like this. I used EditTextarea component 20 times so the 20 components overlap each other. Screen Shot 2021-10-15 at 3 53 47 PM

for sure this is not what I wanted. The custom EditTextarea is not fit well with editMode="row" prop.

With this problem, I decide not to use editMode="row" with the custom EditTextarea component. But while I followed the full-featured CRUD example, this happened again. If I click the ADD RECORD button, new row is inserted to the grid, and the row is set to editMode="rows" as default even though I didn't set it in my grid component.

So, I'm considering to move CRUD feature outside of the grid component.

So, What's the problem?: I'm just telling you what I'm struggling with now.

m4theushw commented 2 years ago

In my example, the Popper is immediately open when the custom edit component is rendered. You could instead control its open prop and only set it to true when the user clicks the cell. It would work like this: user double clicks a cell to enter into edit mode -> all cells become editable -> user clicks in one cell with long-text -> open the Popper for that cell. You could also use a Modal so only one Popper can be active at the same time.

cherniavskii commented 2 years ago

Here's an updated demo: https://mui.com/x/react-data-grid/recipes-editing/#multiline-editing

yaredtsy commented 1 year ago

The custom EditTextarea is not fit well with editMode="row" prop.

You can utilize the onBlur and onFocus event handlers to determine when to open or close the multi-line text editor.

Demo: https://codesandbox.io/s/heuristic-booth-2bi5xs?file=/demo.tsx:3861-3876