Add Field validation to TextInputDialog

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2020-03-13 17:32:02 +00:00
parent 4de0e21a4b
commit ee1659625c
3 changed files with 81 additions and 11 deletions

View file

@ -18,6 +18,7 @@ import React, {createRef} from 'react';
import createReactClass from 'create-react-class'; import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import Field from "../elements/Field";
export default createReactClass({ export default createReactClass({
displayName: 'TextInputDialog', displayName: 'TextInputDialog',
@ -33,6 +34,7 @@ export default createReactClass({
focus: PropTypes.bool, focus: PropTypes.bool,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
hasCancel: PropTypes.bool, hasCancel: PropTypes.bool,
validator: PropTypes.func, // result of withValidation
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -45,25 +47,57 @@ export default createReactClass({
}; };
}, },
getInitialState: function() {
return {
value: this.props.value,
valid: false,
};
},
UNSAFE_componentWillMount: function() { UNSAFE_componentWillMount: function() {
this._textinput = createRef(); this._field = createRef();
}, },
componentDidMount: function() { componentDidMount: function() {
if (this.props.focus) { if (this.props.focus) {
// Set the cursor at the end of the text input // Set the cursor at the end of the text input
this._textinput.current.value = this.props.value; // this._field.current.value = this.props.value;
this._field.current.focus();
} }
}, },
onOk: function() { onOk: async function(ev) {
this.props.onFinished(true, this._textinput.current.value); ev.preventDefault();
if (this.props.validator) {
await this._field.current.validate({ allowEmpty: false });
if (!this._field.current.state.valid) {
this._field.current.focus();
this._field.current.validate({ allowEmpty: false, focused: true });
return;
}
}
this.props.onFinished(true, this.state.value);
}, },
onCancel: function() { onCancel: function() {
this.props.onFinished(false); this.props.onFinished(false);
}, },
onChange: function(ev) {
this.setState({
value: ev.target.value,
});
},
onValidate: async function(fieldState) {
const result = await this.props.validator(fieldState);
this.setState({
valid: result.valid,
});
return result;
},
render: function() { render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@ -77,14 +111,17 @@ export default createReactClass({
<label htmlFor="textinput"> { this.props.description } </label> <label htmlFor="textinput"> { this.props.description } </label>
</div> </div>
<div> <div>
<input <Field
id="textinput" id="mx_TextInputDialog_field"
ref={this._textinput}
className="mx_TextInputDialog_input" className="mx_TextInputDialog_input"
defaultValue={this.props.value} ref={this._field}
autoFocus={this.props.focus} type="text"
placeholder={this.props.placeholder} label={this.props.placeholder}
size="64" /> value={this.state.value}
onChange={this.onChange}
onValidate={this.props.validator ? this.onValidate : undefined}
size="64"
/>
</div> </div>
</div> </div>
</form> </form>

View file

@ -35,6 +35,7 @@ import {useSettingValue} from "../../../hooks/useSettings";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import withValidation from "../elements/Validation";
export const ALL_ROOMS = Symbol("ALL_ROOMS"); export const ALL_ROOMS = Symbol("ALL_ROOMS");
@ -47,6 +48,34 @@ const inPlaceOf = (elementRect) => ({
chevronFace: "none", chevronFace: "none",
}); });
const validServer = withValidation({
rules: [
{
key: "required",
test: async ({ value }) => !!value,
invalid: () => _t("Enter a server address"),
}, {
key: "available",
final: true,
test: async ({ value }) => {
try {
const opts = {
limit: 1,
server: value,
};
// check if we can successfully load this server's room directory
await MatrixClientPeg.get().publicRooms(opts);
return true;
} catch (e) {
return false;
}
},
valid: () => _t("Looks good"),
invalid: () => _t("Can't find this server or its room list"),
},
],
});
// This dropdown sources homeservers from three places: // This dropdown sources homeservers from three places:
// + your currently connected homeserver // + your currently connected homeserver
// + homeservers in config.json["roomDirectory"] // + homeservers in config.json["roomDirectory"]
@ -188,6 +217,7 @@ const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, se
button: _t("Add"), button: _t("Add"),
hasCancel: false, hasCancel: false,
placeholder: _t("Server address"), placeholder: _t("Server address"),
validator: validServer,
}); });
const [ok, newServer] = await finished; const [ok, newServer] = await finished;

View file

@ -1437,6 +1437,9 @@
"And %(count)s more...|other": "And %(count)s more...", "And %(count)s more...|other": "And %(count)s more...",
"ex. @bob:example.com": "ex. @bob:example.com", "ex. @bob:example.com": "ex. @bob:example.com",
"Add User": "Add User", "Add User": "Add User",
"Enter a server address": "Enter a server address",
"Looks good": "Looks good",
"Can't find this server or its room list": "Can't find this server or its room list",
"All rooms": "All rooms", "All rooms": "All rooms",
"Your server": "Your server", "Your server": "Your server",
"Are you sure you want to remove <b>%(serverName)s</b>": "Are you sure you want to remove <b>%(serverName)s</b>", "Are you sure you want to remove <b>%(serverName)s</b>": "Are you sure you want to remove <b>%(serverName)s</b>",