mirror of https://github.com/vector-im/riot-web
Add validation feedback helper
This adds a general validation feedback mechanism for checking input values. An initial example is wired up for the username input on registration.pull/21833/head
parent
37ecf2a623
commit
338d83ab55
|
@ -100,6 +100,7 @@
|
|||
@import "./views/elements/_ToggleSwitch.scss";
|
||||
@import "./views/elements/_ToolTipButton.scss";
|
||||
@import "./views/elements/_Tooltip.scss";
|
||||
@import "./views/elements/_Validation.scss";
|
||||
@import "./views/globals/_MatrixToolbar.scss";
|
||||
@import "./views/groups/_GroupPublicityToggle.scss";
|
||||
@import "./views/groups/_GroupRoomList.scss";
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright 2019 New Vector 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.
|
||||
*/
|
||||
|
||||
.mx_Validation_details {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.mx_Validation_detail {
|
||||
font-weight: normal;
|
||||
|
||||
// TODO: Check / cross images
|
||||
&.mx_Validation_valid {
|
||||
color: $input-valid-border-color;
|
||||
}
|
||||
|
||||
&.mx_Validation_invalid {
|
||||
color: $input-invalid-border-color;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import Modal from '../../../Modal';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import { SAFE_LOCALPART_REGEX } from '../../../Registration';
|
||||
import withValidation from '../elements/Validation';
|
||||
|
||||
const FIELD_EMAIL = 'field_email';
|
||||
const FIELD_PHONE_NUMBER = 'field_phone_number';
|
||||
|
@ -86,6 +87,7 @@ module.exports = React.createClass({
|
|||
// is the one from the first invalid field.
|
||||
// It's not super ideal that this just calls
|
||||
// onValidationChange once for each invalid field.
|
||||
// TODO: Change this to trigger new-style validation for an invalid fields.
|
||||
this.validateField(FIELD_PHONE_NUMBER, ev.type);
|
||||
this.validateField(FIELD_EMAIL, ev.type);
|
||||
this.validateField(FIELD_PASSWORD_CONFIRM, ev.type);
|
||||
|
@ -152,6 +154,8 @@ module.exports = React.createClass({
|
|||
const pwd2 = this.state.passwordConfirm.trim();
|
||||
const allowEmpty = eventType === "blur";
|
||||
|
||||
// TODO: Remove rules here as they are converted to new-style validation
|
||||
|
||||
switch (fieldID) {
|
||||
case FIELD_EMAIL: {
|
||||
const email = this.state.email;
|
||||
|
@ -173,12 +177,6 @@ module.exports = React.createClass({
|
|||
const username = this.state.username;
|
||||
if (allowEmpty && username === '') {
|
||||
this.markFieldValid(fieldID, true);
|
||||
} else if (!SAFE_LOCALPART_REGEX.test(username)) {
|
||||
this.markFieldValid(
|
||||
fieldID,
|
||||
false,
|
||||
"RegistrationForm.ERR_USERNAME_INVALID",
|
||||
);
|
||||
} else if (username == '') {
|
||||
this.markFieldValid(
|
||||
fieldID,
|
||||
|
@ -232,11 +230,14 @@ module.exports = React.createClass({
|
|||
this.setState({
|
||||
fieldErrors,
|
||||
});
|
||||
// TODO: Remove outer validation handling once all fields converted to new-style
|
||||
// validation in the form.
|
||||
this.props.onValidationChange(fieldErrors);
|
||||
},
|
||||
|
||||
_classForField: function(fieldID, ...baseClasses) {
|
||||
let cls = baseClasses.join(' ');
|
||||
// TODO: Remove this from fields as they are converted to new-style validation.
|
||||
if (this.state.fieldErrors[fieldID]) {
|
||||
if (cls) cls += ' ';
|
||||
cls += 'error';
|
||||
|
@ -291,10 +292,6 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onUsernameBlur(ev) {
|
||||
this.validateField(FIELD_USERNAME, ev.type);
|
||||
},
|
||||
|
||||
onUsernameChange(ev) {
|
||||
this.setState({
|
||||
username: ev.target.value,
|
||||
|
@ -325,6 +322,33 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
renderUsername() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
|
||||
const onValidate = withValidation({
|
||||
description: _t("Use letters, numbers, dashes and underscores only"),
|
||||
rules: [
|
||||
{
|
||||
key: "safeLocalpart",
|
||||
regex: SAFE_LOCALPART_REGEX,
|
||||
invalid: _t("Some characters not allowed"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return <Field
|
||||
className={this._classForField(FIELD_USERNAME)}
|
||||
id="mx_RegistrationForm_username"
|
||||
type="text"
|
||||
autoFocus={true}
|
||||
label={_t("Username")}
|
||||
defaultValue={this.props.defaultUsername}
|
||||
value={this.state.username}
|
||||
onChange={this.onUsernameChange}
|
||||
onValidate={onValidate}
|
||||
/>;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
|
||||
|
@ -412,17 +436,7 @@ module.exports = React.createClass({
|
|||
</h3>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
<Field
|
||||
className={this._classForField(FIELD_USERNAME)}
|
||||
id="mx_RegistrationForm_username"
|
||||
type="text"
|
||||
autoFocus={true}
|
||||
label={_t("Username")}
|
||||
defaultValue={this.props.defaultUsername}
|
||||
value={this.state.username}
|
||||
onBlur={this.onUsernameBlur}
|
||||
onChange={this.onUsernameChange}
|
||||
/>
|
||||
{this.renderUsername()}
|
||||
</div>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
<Field
|
||||
|
|
|
@ -94,11 +94,11 @@ export default class Field extends React.PureComponent {
|
|||
mx_Field_invalid: onValidate && this.state.valid === false,
|
||||
});
|
||||
|
||||
// handle displaying feedback on validity
|
||||
// Handle displaying feedback on validity
|
||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||
let feedback;
|
||||
let tooltip;
|
||||
if (this.state.feedback) {
|
||||
feedback = <Tooltip
|
||||
tooltip = <Tooltip
|
||||
tooltipClassName="mx_Field_tooltip"
|
||||
label={this.state.feedback}
|
||||
/>;
|
||||
|
@ -108,7 +108,7 @@ export default class Field extends React.PureComponent {
|
|||
{prefixContainer}
|
||||
{fieldInput}
|
||||
<label htmlFor={this.props.id}>{this.props.label}</label>
|
||||
{feedback}
|
||||
{tooltip}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Copyright 2019 New Vector 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 classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Creates a validation function from a set of rules describing what to validate.
|
||||
*
|
||||
* @param {String} description
|
||||
* Summary of the kind of value that will meet the validation rules. Shown at
|
||||
* the top of the validation feedback.
|
||||
* @param {Object} rules
|
||||
* An array of rules describing how to check to input value. Each rule in an object
|
||||
* and may have the following properties:
|
||||
* - `key`: A unique ID for the rule. Required.
|
||||
* - `regex`: A regex used to determine the rule's current validity. Required.
|
||||
* - `valid`: Text to show when the rule is valid. Only shown if set.
|
||||
* - `invalid`: Text to show when the rule is invalid. Only shown if set.
|
||||
* @returns {Function}
|
||||
* A validation function that takes in the current input value and returns
|
||||
* the overall validity and a feedback UI that can be rendered for more detail.
|
||||
*/
|
||||
export default function withValidation({ description, rules }) {
|
||||
return function onValidate(value) {
|
||||
// TODO: Hide on blur
|
||||
// TODO: Re-run only after ~200ms of inactivity
|
||||
if (!value) {
|
||||
return {
|
||||
valid: null,
|
||||
feedback: null,
|
||||
};
|
||||
}
|
||||
|
||||
const results = [];
|
||||
let valid = true;
|
||||
if (rules && rules.length) {
|
||||
for (const rule of rules) {
|
||||
if (!rule.key || !rule.regex) {
|
||||
continue;
|
||||
}
|
||||
const ruleValid = rule.regex.test(value);
|
||||
valid = valid && ruleValid;
|
||||
if (ruleValid && rule.valid) {
|
||||
// If the rule's result is valid and has text to show for
|
||||
// the valid state, show it.
|
||||
results.push({
|
||||
key: rule.key,
|
||||
valid: true,
|
||||
text: rule.valid,
|
||||
});
|
||||
} else if (!ruleValid && rule.invalid) {
|
||||
// If the rule's result is invalid and has text to show for
|
||||
// the invalid state, show it.
|
||||
results.push({
|
||||
key: rule.key,
|
||||
valid: false,
|
||||
text: rule.invalid,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let details;
|
||||
if (results && results.length) {
|
||||
details = <ul className="mx_Validation_details">
|
||||
{results.map(result => {
|
||||
const classes = classNames({
|
||||
"mx_Validation_detail": true,
|
||||
"mx_Validation_valid": result.valid,
|
||||
"mx_Validation_invalid": !result.valid,
|
||||
});
|
||||
return <li key={result.key} className={classes}>
|
||||
{result.text}
|
||||
</li>;
|
||||
})}
|
||||
</ul>;
|
||||
}
|
||||
|
||||
const feedback = <div className="mx_Validation">
|
||||
<div>{description}</div>
|
||||
{details}
|
||||
</div>;
|
||||
|
||||
return {
|
||||
valid,
|
||||
feedback,
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1322,6 +1322,8 @@
|
|||
"Change": "Change",
|
||||
"Sign in with": "Sign in with",
|
||||
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
|
||||
"Use letters, numbers, dashes and underscores only": "Use letters, numbers, dashes and underscores only",
|
||||
"Some characters not allowed": "Some characters not allowed",
|
||||
"Create your Matrix account": "Create your Matrix account",
|
||||
"Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s",
|
||||
"Email (optional)": "Email (optional)",
|
||||
|
|
Loading…
Reference in New Issue