|
1 | 1 | # Inline Editing |
2 | 2 |
|
3 | | -WIP |
| 3 | +`Inline Editing` is a very common feature in a table, but it's highly coupled with specific ui libraries, so it won't be a part of `BaseTable` itself. |
| 4 | + |
| 5 | +## The Problem |
| 6 | + |
| 7 | +`Inline Editing` would be a bit tricky in `BaseTable` because of it's using the `virtualization` technology to render the rows, so there are `overflow: hidden` for the table and rows and cells, so if your editing content is larger then the cell area, the content would be cut, you genius would find that you could override the `overflow: hidden` via style to prevent the content to be cut, but **PLEASE DON"T DO THAT**, as there would be always problems with this solution, e.g. what would happens if it's in the last row? |
| 8 | + |
| 9 | +## How |
| 10 | + |
| 11 | +The suggested solution is using something like `Portal` to render the editing content out of the cell, then it won't be constrained in the cell. As `Portal` needs a container to attach the target to, most of the custom renderers provide a param `container` to be used in this case, the `container` is table itself. |
| 12 | + |
| 13 | +Internally we are using the `Overlay` component from [react-overlays](https://github.com/react-bootstrap/react-overlays) to do that, `react-overlays` is based on [Popper.js](https://github.com/FezVrasta/popper.js) which provides excellent positioning mechanism. |
| 14 | + |
| 15 | +If you are using fixed mode with frozen columns, there will be a problem with the `Popper.js`. As the default `boundariesElement` for `preventOverflow` is `scrollParent`, but there would be three tables internal tables to mimic the frozen feature, and those tables are all scrollable, then the positioning could be not expected, you could change the `boundariesElement` to `viewport` or the `container` to fix that. |
| 16 | + |
| 17 | +## API Design |
| 18 | + |
| 19 | +`Column.editable: object | fun({ cellData, column, columnIndex, rowData, rowIndex })` |
| 20 | + |
| 21 | +You can use `callOrReturn` exported from this package to get the result, the result could be a `boolean` to indicate the specific cell is editable or not, or an object which include the options like `{ disabled, ...editorProps }`, the result would be used in your custom `TableCell` component. |
| 22 | + |
| 23 | +## Recipe |
| 24 | + |
| 25 | +_The following is really a rough one, will improve it later_ |
| 26 | + |
| 27 | +```jsx |
| 28 | +// import { Overlay } from 'react-overlays' |
| 29 | +const { Overlay } = ReactOverlays |
| 30 | + |
| 31 | +const CellContainer = styled.div` |
| 32 | + display: flex; |
| 33 | + flex: 1 0 100%; |
| 34 | + align-items: center; |
| 35 | + height: 100%; |
| 36 | + overflow: hidden; |
| 37 | + margin: 0 -5px; |
| 38 | + padding: 5px; |
| 39 | + border: 1px dashed transparent; |
| 40 | +` |
| 41 | + |
| 42 | +const GlobalStyle = createGlobalStyle` |
| 43 | + .BaseTable__row:hover, |
| 44 | + .BaseTable__row--hover { |
| 45 | + ${CellContainer} { |
| 46 | + border: 1px dashed #ccc; |
| 47 | + } |
| 48 | + } |
| 49 | +` |
| 50 | + |
| 51 | +const Select = styled.select` |
| 52 | + width: 100%; |
| 53 | + height: 30px; |
| 54 | + margin-top: 10px; |
| 55 | +` |
| 56 | + |
| 57 | +class EditableCell extends React.PureComponent { |
| 58 | + state = { |
| 59 | + value: this.props.cellData, |
| 60 | + editing: false, |
| 61 | + } |
| 62 | + |
| 63 | + setTargetRef = ref => (this.targetRef = ref) |
| 64 | + |
| 65 | + getTargetRef = () => this.targetRef |
| 66 | + |
| 67 | + handleClick = () => this.setState({ editing: true }) |
| 68 | + |
| 69 | + handleHide = () => this.setState({ editing: false }) |
| 70 | + |
| 71 | + handleChange = e => |
| 72 | + this.setState({ |
| 73 | + value: e.target.value, |
| 74 | + editing: false, |
| 75 | + }) |
| 76 | + |
| 77 | + render() { |
| 78 | + const { container, rowIndex, columnIndex } = this.props |
| 79 | + const { value, editing } = this.state |
| 80 | + |
| 81 | + return ( |
| 82 | + <CellContainer ref={this.setTargetRef} onClick={this.handleClick}> |
| 83 | + {!editing && value} |
| 84 | + {editing && this.targetRef && ( |
| 85 | + <Overlay |
| 86 | + show |
| 87 | + flip |
| 88 | + rootClose |
| 89 | + container={container} |
| 90 | + target={this.getTargetRef} |
| 91 | + onHide={this.handleHide} |
| 92 | + > |
| 93 | + {({ props, placement }) => ( |
| 94 | + <div |
| 95 | + {...props} |
| 96 | + style={{ |
| 97 | + ...props.style, |
| 98 | + width: this.targetRef.offsetWidth, |
| 99 | + top: |
| 100 | + placement === 'top' |
| 101 | + ? this.targetRef.offsetHeight |
| 102 | + : -this.targetRef.offsetHeight, |
| 103 | + }} |
| 104 | + > |
| 105 | + <Select value={value} onChange={this.handleChange}> |
| 106 | + <option value="grapefruit">Grapefruit</option> |
| 107 | + <option value="lime">Lime</option> |
| 108 | + <option value="coconut">Coconut</option> |
| 109 | + <option value="mango">Mango</option> |
| 110 | + </Select> |
| 111 | + </div> |
| 112 | + )} |
| 113 | + </Overlay> |
| 114 | + )} |
| 115 | + </CellContainer> |
| 116 | + ) |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +const columns = generateColumns(5) |
| 121 | +const data = generateData(columns, 100) |
| 122 | + |
| 123 | +columns[0].cellRenderer = EditableCell |
| 124 | +columns[0].width = 300 |
| 125 | + |
| 126 | +export default () => ( |
| 127 | + <> |
| 128 | + <GlobalStyle /> |
| 129 | + <Table fixed columns={columns} data={data} /> |
| 130 | + </> |
| 131 | +) |
| 132 | +``` |
0 commit comments