importReact, { Component } from'react'; import logo from'./logo.svg'; import'./App.css';
classAppextendsComponent { render() { return ( <divclassName="App"> <headerclassName="App-header"> <imgsrc={logo}className="App-logo"alt="logo" /> <h1className="App-title">Welcome to React</h1> </header> <pclassName="App-intro"> To get started, edit <code>src/App.js</code> and save to reload. </p> </div> ); } }
/** * Add listener to the `unselectAll` event used to broadcast the * unselect all event. */ componentDidMount() { window.document.addEventListener('unselectAll', this.handleUnselectAll) }
/** * Before updating, execute the formula on the Cell value to * calculate the `display` value. Especially useful when a * redraw is pushed upon this cell when editing another cell * that this might depend upon. */ componentWillUpdate() { this.display = this.determineDisplay( { x: this.props.x, y: this.props.y }, this.state.value ) }
/** * Remove the `unselectAll` event listener added in * `componentDidMount()`. */ componentWillUnmount() { window.document.removeEventListener( 'unselectAll', this.handleUnselectAll ) }
/** * When a Cell value changes, re-determine the display value * by calling the formula calculation. */ onChange = (e) => { this.setState({ value: e.target.value }) this.display = this.determineDisplay( { x: this.props.x, y: this.props.y }, e.target.value ) }
/** * Handle pressing a key when the Cell is an input element. */ onKeyPressOnInput = (e) => { if (e.key === 'Enter') { this.hasNewValue(e.target.value) } }
/** * Handle pressing a key when the Cell is a span element, * not yet in editing mode. */ onKeyPressOnSpan = () => { if (!this.state.editing) { this.setState({ editing: true }) } }
/** * Handle moving away from a cell, stores the new value. */ onBlur = (e) => { this.hasNewValue(e.target.value) }
/** * Used by `componentDid(Un)Mount`, handles the `unselectAll` * event response. */ handleUnselectAll = () => { if (this.state.selected || this.state.editing) { this.setState({ selected: false, editing: false }) } }
/** * Called by the `onBlur` or `onKeyPressOnInput` event handlers, * it escalates the value changed event, and restore the editing * state to `false`. */ hasNewValue = (value) => { this.props.onChangedValue( { x: this.props.x, y: this.props.y, }, value ) this.setState({ editing: false }) }
/** * Emits the `unselectAll` event, used to tell all the other * cells to unselect. */ emitUnselectAllEvent = () => { const unselectAllEvent = newEvent('unselectAll') window.document.dispatchEvent(unselectAllEvent) }
/** * Handle clicking a Cell. */ clicked = () => { // Prevent click and double click to conflict this.timer = setTimeout(() => { if (!this.prevent) { // Unselect all the other cells and set the current // Cell state to `selected` this.emitUnselectAllEvent() this.setState({ selected: true }) } this.prevent = false }, this.delay) }
/** * Handle doubleclicking a Cell. */ doubleClicked = () => { // Prevent click and double click to conflict clearTimeout(this.timer) this.prevent = true
// Unselect all the other cells and set the current // Cell state to `selected` & `editing` this.emitUnselectAllEvent() this.setState({ editing: true, selected: true }) }
determineDisplay = ({ x, y }, value) => { return value }
this.parser.on('callCellValue', (cellCoord, done) => { const x = cellCoord.column.index + 1 const y = cellCoord.row.index + 1
if (x > this.props.x || y > this.props.y) { throwthis.parser.Error(this.parser.ERROR_NOT_AVAILABLE) }
if (this.parser.cell.x === x && this.parser.cell.y === y) { throwthis.parser.Error(this.parser.ERROR_REF) }
if (!this.state.data[y] || !this.state.data[y][x]) { returndone('') }
returndone(this.state.data[y][x]) })
this.parser.on('callRangeValue', (startCellCoord, endCellCoord, done) => { const sx = startCellCoord.column.index + 1 const sy = startCellCoord.row.index + 1 const ex = endCellCoord.column.index + 1 const ey = endCellCoord.row.index + 1 const fragment = []
for (let y = sy; y <= ey; y += 1) { const row = this.state.data[y] if (!row) { continue }
const colFragment = []
for (let x = sx; x <= ex; x += 1) { let value = row[x] if (!value) { value = '' }
if (value.slice(0, 1) === '=') { const res = this.executeFormula({ x, y }, value.slice(1)) if (res.error) { throwthis.parser.Error(res.error) } value = res.result }
executeFormula = (cell, value) => { this.parser.cell = cell let res = this.parser.parse(value) if (res.error != null) { return res } if (res.result.toString() === '') { return res } if (res.result.toString().slice(0, 1) === '=') { res = this.executeFormula(cell, res.result.slice(1)) }
/** * Performance saver as the cell not touched by a change can * decide to avoid a rerender */ shouldComponentUpdate(nextProps, nextState) { // Has a formula value? could be affected by any change. Update if (this.state.value !== '' && this.state.value.slice(0, 1) === '=') { returntrue }
// Its own state values changed? Update // Its own value prop changed? Update if ( nextState.value !== this.state.value || nextState.editing !== this.state.editing || nextState.selected !== this.state.selected || nextProps.value !== this.props.value ) { returntrue }