From 3194c5c61d0e55f0964eaf5d16912548fc9b2f96 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 29 Jul 2016 17:35:48 +0100 Subject: [PATCH] Factor EditableTextContainer out of ChangeDisplayName Take the non-displayname-specific bits out of ChangeDisplayName into a new EditableTextContainer, so that we can reuse the logic elsewhere. --- src/component-index.js | 1 + src/components/views/elements/EditableText.js | 10 +- .../views/elements/EditableTextContainer.js | 147 ++++++++++++++++++ .../views/settings/ChangeDisplayName.js | 91 ++--------- 4 files changed, 169 insertions(+), 80 deletions(-) create mode 100644 src/components/views/elements/EditableTextContainer.js diff --git a/src/component-index.js b/src/component-index.js index 5fadb18b6a..e250838cc4 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -51,6 +51,7 @@ module.exports.components['views.dialogs.QuestionDialog'] = require('./component module.exports.components['views.dialogs.SetDisplayNameDialog'] = require('./components/views/dialogs/SetDisplayNameDialog'); module.exports.components['views.dialogs.TextInputDialog'] = require('./components/views/dialogs/TextInputDialog'); module.exports.components['views.elements.EditableText'] = require('./components/views/elements/EditableText'); +module.exports.components['views.elements.EditableTextContainer'] = require('./components/views/elements/EditableTextContainer'); module.exports.components['views.elements.PowerSelector'] = require('./components/views/elements/PowerSelector'); module.exports.components['views.elements.ProgressBar'] = require('./components/views/elements/ProgressBar'); module.exports.components['views.elements.TintableSvg'] = require('./components/views/elements/TintableSvg'); diff --git a/src/components/views/elements/EditableText.js b/src/components/views/elements/EditableText.js index 9218fe820e..15118f249e 100644 --- a/src/components/views/elements/EditableText.js +++ b/src/components/views/elements/EditableText.js @@ -49,6 +49,8 @@ module.exports = React.createClass({ label: '', placeholder: '', editable: true, + className: "mx_EditableText", + placeholderClassName: "mx_EditableText_placeholder", }; }, @@ -92,7 +94,7 @@ module.exports = React.createClass({ this.refs.editable_div.textContent = this.value; this.refs.editable_div.setAttribute("class", this.props.className); this.placeholder = false; - } + } }, getValue: function() { @@ -101,7 +103,7 @@ module.exports = React.createClass({ setValue: function(value) { this.value = value; - this.showPlaceholder(!this.value); + this.showPlaceholder(!this.value); }, edit: function() { @@ -125,7 +127,7 @@ module.exports = React.createClass({ onKeyDown: function(ev) { // console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); - + if (this.placeholder) { this.showPlaceholder(false); } @@ -173,7 +175,7 @@ module.exports = React.createClass({ var range = document.createRange(); range.setStart(node, 0); range.setEnd(node, node.length); - + var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); diff --git a/src/components/views/elements/EditableTextContainer.js b/src/components/views/elements/EditableTextContainer.js new file mode 100644 index 0000000000..9e863629f2 --- /dev/null +++ b/src/components/views/elements/EditableTextContainer.js @@ -0,0 +1,147 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import sdk from '../../../index'; +import q from 'q'; + +/** + * A component which wraps an EditableText, with a spinner while updates take + * place. + * + * Parent components should supply an 'onSubmit' callback which returns a + * promise; a spinner is shown until the promise resolves. + * + * The parent can also supply a 'getIntialValue' callback, which works in a + * similarly asynchronous way. If this is not provided, the initial value is + * taken from the 'initialValue' property. + */ +export default class EditableTextContainer extends React.Component { + constructor(props, context) { + super(props, context); + + this._unmounted = false; + this.state = { + busy: false, + errorString: null, + value: props.initialValue, + }; + this._onValueChanged=this._onValueChanged.bind(this); + } + + componentWillMount() { + if (this.props.getInitialValue === undefined) { + // use whatever was given in the initialValue property. + return; + } + + this.setState({busy: true}); + + this.props.getInitialValue().done( + (result) => { + if (this._unmounted) { return; } + this.setState({ + busy: false, + value: result, + }); + }, + (error) => { + if (this._unmounted) { return; } + this.setState({ + errorString: error.toString(), + busy: false, + }); + } + ); + } + + componentWillUnmount() { + this._unmounted = true; + } + + _onValueChanged(value, shouldSubmit) { + if (!shouldSubmit) { + return; + } + + this.setState({ + busy: true, + errorString: null, + }); + + this.props.onSubmit(value).done( + () => { + if (this._unmounted) { return; } + this.setState({ + busy: false, + value: value, + }); + }, + (error) => { + if (this._unmounted) { return; } + this.setState({ + errorString: error.toString(), + busy: false, + }); + } + ); + } + + render() { + if (this.state.busy) { + var Loader = sdk.getComponent("elements.Spinner"); + return ( + + ); + } else if (this.state.errorString) { + return ( +
{this.state.errorString}
+ ); + } else { + var EditableText = sdk.getComponent('elements.EditableText'); + return ( + + ); + } + } + +} + +EditableTextContainer.propTypes = { + /* callback to retrieve the initial value. */ + getInitialValue: React.PropTypes.func, + + /* initial value; used if getInitialValue is not given */ + initialValue: React.PropTypes.string, + + /* placeholder text to use when the value is empty (and not being + * edited) */ + placeholder: React.PropTypes.string, + + /* callback to update the value. Called with a single argument: the new + * value. */ + onSubmit: React.PropTypes.func, +}; + + +EditableTextContainer.defaultProps = { + initialValue: "", + placeholder: "", + onSubmit: function(v) {return q(); }, +}; diff --git a/src/components/views/settings/ChangeDisplayName.js b/src/components/views/settings/ChangeDisplayName.js index 799a8b9634..26b6c2f830 100644 --- a/src/components/views/settings/ChangeDisplayName.js +++ b/src/components/views/settings/ChangeDisplayName.js @@ -21,29 +21,10 @@ var MatrixClientPeg = require("../../../MatrixClientPeg"); module.exports = React.createClass({ displayName: 'ChangeDisplayName', - propTypes: { - onFinished: React.PropTypes.func - }, - getDefaultProps: function() { - return { - onFinished: function() {}, - }; - }, - - getInitialState: function() { - return { - busy: false, - errorString: null - } - }, - - componentWillMount: function() { + _getDisplayName: function() { var cli = MatrixClientPeg.get(); - this.setState({busy: true}); - var self = this; - cli.getProfileInfo(cli.credentials.userId).done(function(result) { - + return cli.getProfileInfo(cli.credentials.userId).then(function(result) { var displayname = result.displayname; if (!displayname) { if (MatrixClientPeg.get().isGuest()) { @@ -53,68 +34,26 @@ module.exports = React.createClass({ displayname = MatrixClientPeg.get().getUserIdLocalpart(); } } - - self.setState({ - displayName: displayname, - busy: false - }); + return displayname; }, function(error) { - self.setState({ - errorString: "Failed to fetch display name", - busy: false - }); + throw new Error("Failed to fetch display name"); }); }, - changeDisplayname: function(new_displayname) { - this.setState({ - busy: true, - errorString: null, - }) - - var self = this; - MatrixClientPeg.get().setDisplayName(new_displayname).then(function() { - self.setState({ - busy: false, - displayName: new_displayname - }); - }, function(error) { - self.setState({ - busy: false, - errorString: "Failed to set display name" - }); + _changeDisplayName: function(new_displayname) { + var cli = MatrixClientPeg.get(); + return cli.setDisplayName(new_displayname).catch(function(e) { + throw new Error("Failed to set display name"); }); }, - edit: function() { - this.refs.displayname_edit.edit() - }, - - onValueChanged: function(new_value, shouldSubmit) { - if (shouldSubmit) { - this.changeDisplayname(new_value); - } - }, - render: function() { - if (this.state.busy) { - var Loader = sdk.getComponent("elements.Spinner"); - return ( - - ); - } else if (this.state.errorString) { - return ( -
{this.state.errorString}
- ); - } else { - var EditableText = sdk.getComponent('elements.EditableText'); - return ( - - ); - } + var EditableTextContainer = sdk.getComponent('elements.EditableTextContainer'); + return ( + + ); } });