Last active
November 2, 2017 22:35
-
-
Save ccashwell/e2ceac25d7195e4715a93587da89d747 to your computer and use it in GitHub Desktop.
Building Proof-of-Concept Table Support for Quill.js using React Components
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Quill from 'quill' | |
import TableComponent from 'TableComponent' | |
import ReactDOM from 'react-dom' | |
const QuillEmbed = Quill.import('blots/block/embed'); | |
class QuillTable extends QuillEmbed { | |
static create(values) { | |
let node = super.create(values); | |
if (!Array.isArray(values)) { | |
try { | |
values = JSON.parse(values); | |
} catch (e) { | |
alert('Error: Unable to interpret supplied content'); | |
return node; | |
} | |
} | |
ReactDOM.render(<TableComponent data={values} />, node); | |
return node; | |
} | |
static value(node) { | |
let output = []; | |
let tableNode = node.firstChild.firstChild; | |
if(tableNode.tagName === 'TABLE') { | |
let rows = tableNode.rows; | |
for (let i = 0; i < rows.length; i++) { | |
output[i] = []; | |
for (let j = 0; j < rows[i].cells.length; j++) { | |
let cell = rows[i].cells[j]; | |
let cellAttributes = Object.assign({}, TableComponent.cellOptions); | |
cellAttributes.colSpan = cell.colSpan; | |
output[i].push({ | |
options: cellAttributes, | |
value: cell.innerHTML | |
}); | |
} | |
} | |
} | |
return output; | |
} | |
} | |
QuillTable.blotName = 'table'; | |
QuillTable.tagName = 'div'; | |
export { QuillTable as default }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import { randomHex } from 'lib/util' // this just returns a random 6-char hex string | |
export default class TableComponent extends React.Component { | |
constructor(props) { | |
super(props); | |
let { columnHeaders, rows } = this.preprocess(props.data); | |
this.state = { columnHeaders, rows } | |
} | |
static cellOptions = { | |
colSpan: 1, | |
style: { border: '1px dotted red', padding: '10px' } | |
} | |
render() { | |
let output = ( | |
<div className='financial-table'> | |
{ this.buildTable() } | |
<button onClick={this.onAddRow.bind(this)}>(+) Add Row</button> | |
<button onClick={this.onAddColumn.bind(this)}>(+) Add Column</button> | |
</div> | |
) | |
return(output) | |
} | |
buildTable() { | |
return( | |
<table contentEditable={false} style={{ border: '1px solid #ccc', padding: '10px', width: '100%' }}> | |
{ this.buildTableHeader(this.state.columnHeaders) } | |
{ this.buildTableBody(this.state.rows) } | |
</table> | |
) | |
} | |
buildTableHeader(headerColumns) { | |
return <thead>{this.buildTableRow(headerColumns, 'th')}</thead>; | |
} | |
buildTableRow(rowData, CellType = 'td') { | |
let columns = []; | |
for (let cell of rowData) { | |
let cellOptions = Object.assign({}, TableComponent.cellOptions, cell.options); | |
columns.push( | |
<CellType | |
key={randomHex()} | |
dangerouslySetInnerHTML={{__html: window.unescape(cell.value)}} | |
onClick={this.onEditCell.bind(this)} {...cellOptions} /> | |
) | |
} | |
return <tr key={randomHex()}>{columns}</tr>; | |
} | |
buildTableBody(tableRows) { | |
return <tbody>{tableRows.map((row) => this.buildTableRow(row))}</tbody> | |
} | |
onAddRow() { | |
let value = prompt('What default value should it have?', 'Default Value'); | |
this.setState({ | |
rows: this.state.rows.concat([ | |
new Array(this.state.columnHeaders.length).fill({ value }) | |
]) | |
}) | |
} | |
onAddColumn() { | |
let column = prompt('What is the name of the new column?', 'New Column'); | |
let value = prompt('What default value should it have?', 'Default Value'); | |
this.setState({ | |
columnHeaders: this.state.columnHeaders.concat([{ value: column }]), | |
rows: this.state.rows.map((row) => row.concat([{ value }])) | |
}) | |
} | |
onEditCell(e) { | |
if (e.target) e.target.innerHTML = prompt('Enter new value', e.target.innerHTML); | |
} | |
onUpdate() { | |
if (typeof this.props.onUpdate == 'function') this.props.onUpdate(this); | |
} | |
preprocess(data = []) { | |
let columnHeaders = data[0] || []; | |
let rows = data.slice(-(data.length - 1)) || []; | |
let applyProperties = (value) => { | |
let options = TableComponent.cellOptions; | |
let cell = { options: {} }; | |
if (typeof value === 'object' && value.hasOwnProperty('value')) { | |
Object.assign(cell.options, TableComponent.cellOptions, value.options); | |
cell.value = value.value; | |
} else { | |
cell.value = value; | |
} | |
return cell; | |
} | |
return { | |
columnHeaders: columnHeaders.map(applyProperties), | |
rows: rows.map((row) => row.map(applyProperties)) | |
} | |
} | |
} |
Hey,
I was trying to test your proof of concept, how would you actually implement this?
Cheers,
Lee
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is just a quick example of how to implement custom Embed elements using React components that'll work more or less as expected in Quill.js. In case it's unclear how the table structure is generated from JSON, here's some sample input: