From 8a3a21bba1e6706295cc22e1b3a876a7a86cb705 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 21 Jul 2016 01:14:55 -0700 Subject: web: fix ValueEditor, clean up code --- web/src/js/components/ValueEditor/EditorBase.jsx | 165 -------------------- .../js/components/ValueEditor/ValidateEditor.jsx | 44 +++--- web/src/js/components/ValueEditor/ValueEditor.jsx | 168 +++++++++++++++++++++ 3 files changed, 190 insertions(+), 187 deletions(-) delete mode 100755 web/src/js/components/ValueEditor/EditorBase.jsx create mode 100644 web/src/js/components/ValueEditor/ValueEditor.jsx (limited to 'web/src/js/components/ValueEditor') diff --git a/web/src/js/components/ValueEditor/EditorBase.jsx b/web/src/js/components/ValueEditor/EditorBase.jsx deleted file mode 100755 index aa09dad5..00000000 --- a/web/src/js/components/ValueEditor/EditorBase.jsx +++ /dev/null @@ -1,165 +0,0 @@ -import React, { Component, PropTypes } from 'react' -import ReactDOM from 'react-dom' -import {Key} from '../../utils.js' - -export default class EditorBase extends Component { - - static propTypes = { - content: PropTypes.string.isRequired, - onDone: PropTypes.func.isRequired, - contentToHtml: PropTypes.func, - nodeToContent: PropTypes.func, - onStop: PropTypes.func, - submitOnEnter: PropTypes.bool, - className: PropTypes.string, - tag: PropTypes.string, - } - - static defaultProps = { - contentToHtml: content => _.escape(content), - nodeToContent: node => node.textContent, - submitOnEnter: true, - className: '', - tag: 'div', - onStop: _.noop, - onMouseDown: _.noop, - onBlur: _.noop, - onInput: _.noop, - } - - constructor(props) { - super(props) - this.state = {editable: false} - - this.onPaste = this.onPaste.bind(this) - this.onMouseDown = this.onMouseDown.bind(this) - this.onMouseUp = this.onMouseUp.bind(this) - this.onFocus = this.onFocus.bind(this) - this.onClick = this.onClick.bind(this) - this.stop = this.stop.bind(this) - this.onBlur = this.onBlur.bind(this) - this.reset = this.reset.bind(this) - this.onKeyDown = this.onKeyDown.bind(this) - this.onInput = this.onInput.bind(this) - } - - stop() { - // a stop would cause a blur as a side-effect. - // but a blur event must trigger a stop as well. - // to fix this, make stop = blur and do the actual stop in the onBlur handler. - ReactDOM.findDOMNode(this).blur() - this.props.onStop() - } - - render() { - return ( - - ) - } - - onPaste(e) { - e.preventDefault() - var content = e.clipboardData.getData('text/plain') - document.execCommand('insertHTML', false, content) - } - - onMouseDown(e) { - this._mouseDown = true - window.addEventListener('mouseup', this.onMouseUp) - this.props.onMouseDown(e) - } - - onMouseUp() { - if (this._mouseDown) { - this._mouseDown = false - window.removeEventListener('mouseup', this.onMouseUp) - } - } - - onClick(e) { - this.onMouseUp() - this.onFocus(e) - } - - onFocus(e) { - if (this._mouseDown || this._ignore_events || this.state.editable) { - return - } - - // contenteditable in FireFox is more or less broken. - // - we need to blur() and then focus(), otherwise the caret is not shown. - // - blur() + focus() == we need to save the caret position before - // Firefox sometimes just doesn't set a caret position => use caretPositionFromPoint - const sel = window.getSelection() - let range - if (sel.rangeCount > 0) { - range = sel.getRangeAt(0) - } else if (document.caretPositionFromPoint && e.clientX && e.clientY) { - const pos = document.caretPositionFromPoint(e.clientX, e.clientY) - range = document.createRange() - range.setStart(pos.offsetNode, pos.offset) - } else if (document.caretRangeFromPoint && e.clientX && e.clientY) { - range = document.caretRangeFromPoint(e.clientX, e.clientY) - } else { - range = document.createRange() - range.selectNodeContents(ReactDOM.findDOMNode(this)) - } - - this._ignore_events = true - this.setState({ editable: true }, () => { - const node = ReactDOM.findDOMNode(this) - node.blur() - node.focus() - this._ignore_events = false - }) - } - - onBlur(e) { - if (this._ignore_events) { - return - } - window.getSelection().removeAllRanges() //make sure that selection is cleared on blur - this.setState({ editable: false }) - this.props.onDone(this.props.nodeToContent(ReactDOM.findDOMNode(this))) - this.props.onBlur(e) - } - - reset() { - ReactDOM.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content) - } - - onKeyDown(e) { - e.stopPropagation() - switch (e.keyCode) { - case Key.ESC: - e.preventDefault() - this.reset() - this.stop() - break - case Key.ENTER: - if (this.props.submitOnEnter && !e.shiftKey) { - e.preventDefault() - this.stop() - } - break - default: - break - } - } - - onInput() { - this.props.onInput(this.props.nodeToContent(ReactDOM.findDOMNode(this))) - } -} diff --git a/web/src/js/components/ValueEditor/ValidateEditor.jsx b/web/src/js/components/ValueEditor/ValidateEditor.jsx index 2f362986..7415c1b8 100755 --- a/web/src/js/components/ValueEditor/ValidateEditor.jsx +++ b/web/src/js/components/ValueEditor/ValidateEditor.jsx @@ -1,57 +1,57 @@ import React, { Component, PropTypes } from 'react' -import ReactDOM from 'react-dom' -import EditorBase from './EditorBase' +import ValueEditor from './ValueEditor' +import classnames from 'classnames' + export default class ValidateEditor extends Component { static propTypes = { content: PropTypes.string.isRequired, + readonly: PropTypes.bool, onDone: PropTypes.func.isRequired, - onInput: PropTypes.func, - isValid: PropTypes.func, className: PropTypes.string, + isValid: PropTypes.func.isRequired, } constructor(props) { super(props) - this.state = { currentContent: props.content } + this.state = { valid: props.isValid(props.content) } this.onInput = this.onInput.bind(this) this.onDone = this.onDone.bind(this) } componentWillReceiveProps(nextProps) { - this.setState({ currentContent: nextProps.content }) + this.setState({ valid: nextProps.isValid(nextProps.content) }) } - onInput(currentContent) { - this.setState({ currentContent }) - this.props.onInput && this.props.onInput(currentContent) + onInput(content) { + this.setState({ valid: this.props.isValid(content) }) } onDone(content) { - if (this.props.isValid && !this.props.isValid(content)) { - this.refs.editor.reset() + if (!this.props.isValid(content)) { + this.editor.reset() content = this.props.content } this.props.onDone(content) } render() { - let className = this.props.className || '' - if (this.props.isValid) { - if (this.props.isValid(this.state.currentContent)) { - className += ' has-success' - } else { - className += ' has-warning' + let className = classnames( + this.props.className, + { + 'has-success': this.state.valid, + 'has-warning': !this.state.valid } - } + ) return ( - this.editor = e} /> ) } diff --git a/web/src/js/components/ValueEditor/ValueEditor.jsx b/web/src/js/components/ValueEditor/ValueEditor.jsx new file mode 100644 index 00000000..dd9c2cde --- /dev/null +++ b/web/src/js/components/ValueEditor/ValueEditor.jsx @@ -0,0 +1,168 @@ +import React, { Component, PropTypes } from 'react' +import _ from "lodash" +import classnames from 'classnames' + +import { Key } from '../../utils' + +export default class ValueEditor extends Component { + + static propTypes = { + content: PropTypes.string.isRequired, + readonly: PropTypes.bool, + onDone: PropTypes.func.isRequired, + className: PropTypes.string, + onInput: PropTypes.func, + onKeyDown: PropTypes.func, + } + + static defaultProps = { + onInput: () => {}, + onKeyDown: () => {}, + } + + constructor(props) { + super(props) + this.state = { editable: false } + + this.onPaste = this.onPaste.bind(this) + this.onMouseDown = this.onMouseDown.bind(this) + this.onMouseUp = this.onMouseUp.bind(this) + this.onFocus = this.onFocus.bind(this) + this.onClick = this.onClick.bind(this) + this.blur = this.blur.bind(this) + this.onBlur = this.onBlur.bind(this) + this.reset = this.reset.bind(this) + this.onKeyDown = this.onKeyDown.bind(this) + this.onInput = this.onInput.bind(this) + } + + blur() { + // a stop would cause a blur as a side-effect. + // but a blur event must trigger a stop as well. + // to fix this, make stop = blur and do the actual stop in the onBlur handler. + this.input.blur() + } + + reset() { + this.input.innerHTML = _.escape(this.props.content) + } + + render() { + let className = classnames( + 'inline-input', + { + 'readonly': this.props.readonly, + 'editable': !this.props.readonly + }, + this.props.className + ) + return ( +
this.input = input} + tabIndex={!this.props.readonly && "0"} + className={className} + contentEditable={this.state.editable || undefined} + onFocus={this.onFocus} + onMouseDown={this.onMouseDown} + onClick={this.onClick} + onBlur={this.onBlur} + onKeyDown={this.onKeyDown} + onInput={this.onInput} + onPaste={this.onPaste} + dangerouslySetInnerHTML={{ __html: _.escape(this.props.content) }} + >
+ ) + } + + onPaste(e) { + e.preventDefault() + var content = e.clipboardData.getData('text/plain') + document.execCommand('insertHTML', false, content) + } + + onMouseDown(e) { + this._mouseDown = true + window.addEventListener('mouseup', this.onMouseUp) + } + + onMouseUp() { + if (this._mouseDown) { + this._mouseDown = false + window.removeEventListener('mouseup', this.onMouseUp) + } + } + + onClick(e) { + this.onMouseUp() + this.onFocus(e) + } + + onFocus(e) { + if (this._mouseDown || this._ignore_events || this.state.editable || this.props.readonly) { + return + } + + // contenteditable in FireFox is more or less broken. + // - we need to blur() and then focus(), otherwise the caret is not shown. + // - blur() + focus() == we need to save the caret position before + // Firefox sometimes just doesn't set a caret position => use caretPositionFromPoint + const sel = window.getSelection() + let range + if (sel.rangeCount > 0) { + range = sel.getRangeAt(0) + } else if (document.caretPositionFromPoint && e.clientX && e.clientY) { + const pos = document.caretPositionFromPoint(e.clientX, e.clientY) + range = document.createRange() + range.setStart(pos.offsetNode, pos.offset) + } else if (document.caretRangeFromPoint && e.clientX && e.clientY) { + range = document.caretRangeFromPoint(e.clientX, e.clientY) + } else { + range = document.createRange() + range.selectNodeContents(this.input) + } + + this._ignore_events = true + this.setState({ editable: true }, () => { + this.input.blur() + this.input.focus() + this._ignore_events = false + range.selectNodeContents(this.input) + sel.removeAllRanges(); + sel.addRange(range); + }) + } + + onBlur(e) { + if (this._ignore_events || this.props.readonly) { + return + } + window.getSelection().removeAllRanges() //make sure that selection is cleared on blur + this.setState({ editable: false }) + this.props.onDone(this.input.textContent) + } + + + onKeyDown(e) { + e.stopPropagation() + switch (e.keyCode) { + case Key.ESC: + e.preventDefault() + this.reset() + this.blur() + break + case Key.ENTER: + if (!e.shiftKey) { + e.preventDefault() + this.blur() + } + break + default: + break + } + this.props.onKeyDown(e) + } + + onInput() { + this.props.onInput(this.input.textContent) + } +} -- cgit v1.2.3