Add support for validating more strictly at submit time
When submitting a form, we want to validate more strictly to check for empty values that might be required. A separate mode is used since we want to ignore this issue when visiting a field one by one to enter data. As an example, we convert the pre-existing logic for the username requirement using this new support.
This commit is contained in:
parent
a7c37733b8
commit
1cbb4be6f7
5 changed files with 36 additions and 38 deletions
|
@ -344,12 +344,6 @@ module.exports = React.createClass({
|
||||||
case "RegistrationForm.ERR_MISSING_PHONE_NUMBER":
|
case "RegistrationForm.ERR_MISSING_PHONE_NUMBER":
|
||||||
errMsg = _t('A phone number is required to register on this homeserver.');
|
errMsg = _t('A phone number is required to register on this homeserver.');
|
||||||
break;
|
break;
|
||||||
case "RegistrationForm.ERR_USERNAME_INVALID":
|
|
||||||
errMsg = _t("A username can only contain lower case letters, numbers and '=_-./'");
|
|
||||||
break;
|
|
||||||
case "RegistrationForm.ERR_USERNAME_BLANK":
|
|
||||||
errMsg = _t('You need to enter a username.');
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
console.error("Unknown error code: %s", errCode);
|
console.error("Unknown error code: %s", errCode);
|
||||||
errMsg = _t('An unknown error occurred.');
|
errMsg = _t('An unknown error occurred.');
|
||||||
|
|
|
@ -94,7 +94,6 @@ module.exports = React.createClass({
|
||||||
this.validateField(FIELD_EMAIL, ev.type);
|
this.validateField(FIELD_EMAIL, ev.type);
|
||||||
this.validateField(FIELD_PASSWORD_CONFIRM, ev.type);
|
this.validateField(FIELD_PASSWORD_CONFIRM, ev.type);
|
||||||
this.validateField(FIELD_PASSWORD, ev.type);
|
this.validateField(FIELD_PASSWORD, ev.type);
|
||||||
this.validateField(FIELD_USERNAME, ev.type);
|
|
||||||
|
|
||||||
const allFieldsValid = this.verifyFieldsBeforeSubmit();
|
const allFieldsValid = this.verifyFieldsBeforeSubmit();
|
||||||
if (!allFieldsValid) {
|
if (!allFieldsValid) {
|
||||||
|
@ -142,23 +141,38 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
verifyFieldsBeforeSubmit() {
|
verifyFieldsBeforeSubmit() {
|
||||||
if (this.allFieldsValid()) {
|
const fieldIDsInDisplayOrder = [
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const invalidField = this.findFirstInvalidField([
|
|
||||||
FIELD_USERNAME,
|
FIELD_USERNAME,
|
||||||
FIELD_PASSWORD,
|
FIELD_PASSWORD,
|
||||||
FIELD_PASSWORD_CONFIRM,
|
FIELD_PASSWORD_CONFIRM,
|
||||||
FIELD_EMAIL,
|
FIELD_EMAIL,
|
||||||
FIELD_PHONE_NUMBER,
|
FIELD_PHONE_NUMBER,
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
// Run all fields with stricter validation that no longer allows empty
|
||||||
|
// values for required fields.
|
||||||
|
for (const fieldID of fieldIDsInDisplayOrder) {
|
||||||
|
const field = this[fieldID];
|
||||||
|
if (!field) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
field.validate({ allowEmpty: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.allFieldsValid()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidField = this.findFirstInvalidField(fieldIDsInDisplayOrder);
|
||||||
|
|
||||||
if (!invalidField) {
|
if (!invalidField) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Focus the first invalid field and show feedback in the stricter mode
|
||||||
|
// that no longer allows empty values for required fields.
|
||||||
invalidField.focus();
|
invalidField.focus();
|
||||||
|
invalidField.validate({ allowEmpty: false, focused: true });
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -215,21 +229,6 @@ module.exports = React.createClass({
|
||||||
} else this.markFieldError(fieldID, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID");
|
} else this.markFieldError(fieldID, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FIELD_USERNAME: {
|
|
||||||
const username = this.state.username;
|
|
||||||
if (allowEmpty && username === '') {
|
|
||||||
this.markFieldError(fieldID, true);
|
|
||||||
} else if (username == '') {
|
|
||||||
this.markFieldError(
|
|
||||||
fieldID,
|
|
||||||
false,
|
|
||||||
"RegistrationForm.ERR_USERNAME_BLANK",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.markFieldError(fieldID, true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FIELD_PASSWORD:
|
case FIELD_PASSWORD:
|
||||||
if (allowEmpty && pwd1 === "") {
|
if (allowEmpty && pwd1 === "") {
|
||||||
this.markFieldError(fieldID, true);
|
this.markFieldError(fieldID, true);
|
||||||
|
@ -358,9 +357,14 @@ module.exports = React.createClass({
|
||||||
validateUsernameRules: withValidation({
|
validateUsernameRules: withValidation({
|
||||||
description: () => _t("Use letters, numbers, dashes and underscores only"),
|
description: () => _t("Use letters, numbers, dashes and underscores only"),
|
||||||
rules: [
|
rules: [
|
||||||
|
{
|
||||||
|
key: "required",
|
||||||
|
test: ({ value, allowEmpty }) => allowEmpty || !!value,
|
||||||
|
invalid: () => _t("Enter username"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "safeLocalpart",
|
key: "safeLocalpart",
|
||||||
regex: SAFE_LOCALPART_REGEX,
|
test: ({ value }) => !value || SAFE_LOCALPART_REGEX.test(value),
|
||||||
invalid: () => _t("Some characters not allowed"),
|
invalid: () => _t("Some characters not allowed"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -393,7 +397,6 @@ module.exports = React.createClass({
|
||||||
renderUsername() {
|
renderUsername() {
|
||||||
const Field = sdk.getComponent('elements.Field');
|
const Field = sdk.getComponent('elements.Field');
|
||||||
return <Field
|
return <Field
|
||||||
className={this._classForField(FIELD_USERNAME)}
|
|
||||||
id="mx_RegistrationForm_username"
|
id="mx_RegistrationForm_username"
|
||||||
ref={field => this[FIELD_USERNAME] = field}
|
ref={field => this[FIELD_USERNAME] = field}
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -87,14 +87,15 @@ export default class Field extends React.PureComponent {
|
||||||
this.input.focus();
|
this.input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
validate({ focused }) {
|
validate({ focused, allowEmpty = true }) {
|
||||||
if (!this.props.onValidate) {
|
if (!this.props.onValidate) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { value } = this.input;
|
const value = this.input ? this.input.value : null;
|
||||||
const { valid, feedback } = this.props.onValidate({
|
const { valid, feedback } = this.props.onValidate({
|
||||||
value,
|
value,
|
||||||
focused,
|
focused,
|
||||||
|
allowEmpty,
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
valid,
|
valid,
|
||||||
|
|
|
@ -26,7 +26,7 @@ import classNames from 'classnames';
|
||||||
* An array of rules describing how to check to input value. Each rule in an object
|
* An array of rules describing how to check to input value. Each rule in an object
|
||||||
* and may have the following properties:
|
* and may have the following properties:
|
||||||
* - `key`: A unique ID for the rule. Required.
|
* - `key`: A unique ID for the rule. Required.
|
||||||
* - `regex`: A regex used to determine the rule's current validity. Required.
|
* - `test`: A function used to determine the rule's current validity. Required.
|
||||||
* - `valid`: Function returning text to show when the rule is valid. Only shown if set.
|
* - `valid`: Function returning text to show when the rule is valid. Only shown if set.
|
||||||
* - `invalid`: Function returning text to show when the rule is invalid. Only shown if set.
|
* - `invalid`: Function returning text to show when the rule is invalid. Only shown if set.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
|
@ -34,9 +34,9 @@ import classNames from 'classnames';
|
||||||
* the overall validity and a feedback UI that can be rendered for more detail.
|
* the overall validity and a feedback UI that can be rendered for more detail.
|
||||||
*/
|
*/
|
||||||
export default function withValidation({ description, rules }) {
|
export default function withValidation({ description, rules }) {
|
||||||
return function onValidate({ value, focused }) {
|
return function onValidate({ value, focused, allowEmpty = true }) {
|
||||||
// TODO: Re-run only after ~200ms of inactivity
|
// TODO: Re-run only after ~200ms of inactivity
|
||||||
if (!value) {
|
if (!value && allowEmpty) {
|
||||||
return {
|
return {
|
||||||
valid: null,
|
valid: null,
|
||||||
feedback: null,
|
feedback: null,
|
||||||
|
@ -47,10 +47,10 @@ export default function withValidation({ description, rules }) {
|
||||||
let valid = true;
|
let valid = true;
|
||||||
if (rules && rules.length) {
|
if (rules && rules.length) {
|
||||||
for (const rule of rules) {
|
for (const rule of rules) {
|
||||||
if (!rule.key || !rule.regex) {
|
if (!rule.key || !rule.test) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const ruleValid = rule.regex.test(value);
|
const ruleValid = rule.test({ value, allowEmpty });
|
||||||
valid = valid && ruleValid;
|
valid = valid && ruleValid;
|
||||||
if (ruleValid && rule.valid) {
|
if (ruleValid && rule.valid) {
|
||||||
// If the rule's result is valid and has text to show for
|
// If the rule's result is valid and has text to show for
|
||||||
|
|
|
@ -1324,6 +1324,7 @@
|
||||||
"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?",
|
"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",
|
"Use letters, numbers, dashes and underscores only": "Use letters, numbers, dashes and underscores only",
|
||||||
"Some characters not allowed": "Some characters not allowed",
|
"Some characters not allowed": "Some characters not allowed",
|
||||||
|
"Enter username": "Enter username",
|
||||||
"Create your Matrix account": "Create your Matrix account",
|
"Create your Matrix account": "Create your Matrix account",
|
||||||
"Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s",
|
"Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s",
|
||||||
"Email (optional)": "Email (optional)",
|
"Email (optional)": "Email (optional)",
|
||||||
|
@ -1524,7 +1525,6 @@
|
||||||
"This doesn't look like a valid phone number.": "This doesn't look like a valid phone number.",
|
"This doesn't look like a valid phone number.": "This doesn't look like a valid phone number.",
|
||||||
"An email address is required to register on this homeserver.": "An email address is required to register on this homeserver.",
|
"An email address is required to register on this homeserver.": "An email address is required to register on this homeserver.",
|
||||||
"A phone number is required to register on this homeserver.": "A phone number is required to register on this homeserver.",
|
"A phone number is required to register on this homeserver.": "A phone number is required to register on this homeserver.",
|
||||||
"You need to enter a username.": "You need to enter a username.",
|
|
||||||
"An unknown error occurred.": "An unknown error occurred.",
|
"An unknown error occurred.": "An unknown error occurred.",
|
||||||
"Create your account": "Create your account",
|
"Create your account": "Create your account",
|
||||||
"Commands": "Commands",
|
"Commands": "Commands",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue