Try to make Draft.js support simple table
Tags: react
Published: 2020-08-27 05:27:03

## Introduction

My requirement is to implement an online paper typesetting editor and export the pdf generated by LaTeX.

-- Updated --
https://github.com/facebook/draft-js/pull/2590
-- Orignial --

Project link：[Eorg](https://github.com/zhyd1997/Eorg)

Relative projects are Overleaf and [Resumake](https://github.com/saadq/resumake.io).

I prefer to React.js, so I chose Draft.js, a rich text editor that was also developed for Facebook. Paper writing can't hide from table insertion, but Draft.js does not have a ready-made table plug-in. There are also a few tools for generating tables. Totally unnecessary to write another table support by my own, but it is convenient for users, and the table does not need to be too complicated. You can export a `booktabs`-like table, so I plan to try to implement table support by myself

## Content
Idea：

[A robust table](https://github.com/EvanMorrison/draft-js-rte) is to use `new ContentBlock` to write metadata into `block`，which is    an approach closed to Draft.js !
![draft-js-rte](https://dev-to-uploads.s3.amazonaws.com/i/zfame4h3gg9c9rapbqnv.png)

Relatively speaking, my implementation is more trick, which borrow from [offical TeX example](https://github.com/facebook/draft-js/tree/master/examples/draft-0-10-0/tex) ，using `AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, ' ')` API to add metadata to React.js props：

1. rows and columns
```js
const contentStateWithEntity = contentState.createEntity(
    'TABLE',
    'IMMUTABLE',
    {
        row, column, caption,  // data
    },
)

// ...

const { row, column, caption } = props // Table Component
```
2. table cell
```js
// createTable.js

/**
 * cell = {
 *     0: ["cell-0,0", "cell-0,1", ..., "cell-0,m"],
 *     1: ["cell-1,0", "cell-1,1", ..., "cell-1,m"],
 *     ...,
 *     n: ["cell-n,0", "cell-n,1", ..., "cell-n,m"],
 * }
 */
const cell = Object.fromEntries(Array.from(
    { length: row },
    (_, i) => [
        i,
        Array.from({ length: column }, (_, j) => `cell-${i},${j}`)
    ])
)

const contentStateWithEntity = contentState.createEntity(
    'TABLE',
    'IMMUTABLE',
    {
        ..., cell, // data
    },
)

// ...

const { ..., cell } = props // Table Component
```
and initialize a table：
```js
// TableBlock.js

// tbody -- version 1
const coordinate = []

if (row > 1) {
    for (let i = 1; i < row; i += 1) {
    const cols = []
	for (let j = 0; j < column; j += 1) {
	    cols.push(
		<td key={i + j} >
		    {cell[i][j]}
		</td>,
	    )
	}
	rows.push(<tr key={i}>{cols}</tr>)
    }
}
```
![store row, column, caption, cell to props](https://dev-to-uploads.s3.amazonaws.com/i/7tuu1i9kckxyqkhmicq4.png)
> store `row`, `column`, `caption`, `cell` to React.js `props`

2. obtain cell coordinate：

The first idea is to calculate the Dom Node location，which means find the index of `<tr>` at `closest('td')` at `closest('table')`

Later, more better approach is retrive the `key` value in `<tr>` and `<td>`, and its coordinate is (x1, y1):

```js  
// TableBlock.js

// tbody -- version 2
const coordinate = []

if (row > 1) { // thead need to calculate separately
    for (let i = 1; i < row; i += 1) {
    const cols = []
	for (let j = 0; j < column; j += 1) {
	    cols.push(
		<td
		    key={i + j} // TODO key-1
		    onDoubleClick={() => coordinate.push([i, j])}
		>
		    {cell[i][j]}
		</td>,
	    )
	}
	rows.push(<tr key={i}>{cols}</tr>)
    }
}
```
Above key-1 is not stable，we can work with [nanoid](https://github.com/ai/nanoid) library:
```js
key = {`i+j+${nanoid()}`}
```
Now it's stable and we can store cell values:
```js
// find the coordinate of the node clicked
const x1 = coordinate[coordinate.length - 1][0]
const y1 = coordinate[coordinate.length - 1][1]

// update cell[i][j]
cell[x1][y1] = evt.target.innerHTML
```
![cell value will be stored into props](https://dev-to-uploads.s3.amazonaws.com/i/0j8lhzre4axfrh0e0yqv.png)
> cell-2,1 can be stored to props
  
## Summary
The table is not yet complete, for example:
1. how to process cursor
2. it's mutable

## Next
To support adding and deleting cell.