Upgrade gl_field_errors to support more use cases.
This commit is contained in:
parent
aba94f65f0
commit
93bd3dd8a8
|
@ -4,18 +4,56 @@
|
||||||
* This class overrides the browser's validation error bubbles, displaying custom
|
* This class overrides the browser's validation error bubbles, displaying custom
|
||||||
* error messages for invalid fields instead. To begin validating any form, add the
|
* error messages for invalid fields instead. To begin validating any form, add the
|
||||||
* class `show-gl-field-errors` to the form element, and ensure error messages are
|
* class `show-gl-field-errors` to the form element, and ensure error messages are
|
||||||
* declared in each inputs' title attribute.
|
* declared in each inputs' `title` attribute. If no title is declared for an invalid
|
||||||
|
* field the user attempts to submit, "This field is required." will be shown by default.
|
||||||
*
|
*
|
||||||
* Example:
|
* Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input.
|
||||||
|
*
|
||||||
|
* Set a custom error anchor for error message to be injected after with the class `gl-field-error-anchor`
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* Basic:
|
||||||
*
|
*
|
||||||
* <form class='show-gl-field-errors'>
|
* <form class='show-gl-field-errors'>
|
||||||
* <input type='text' name='username' title='Username is required.'/>
|
* <input type='text' name='username' title='Username is required.'/>
|
||||||
*</form>
|
* </form>
|
||||||
*
|
*
|
||||||
|
* Ignore specific inputs (e.g. UsernameValidator):
|
||||||
|
*
|
||||||
|
* <form class='show-gl-field-errors'>
|
||||||
|
* <div class="form-group>
|
||||||
|
* <input type='text' class='gl-field-errors-ignore' pattern='[a-zA-Z0-9-_]+'/>
|
||||||
|
* </div>
|
||||||
|
* <div class="form-group">
|
||||||
|
* <input type='text' name='username' title='Username is required.'/>
|
||||||
|
* </div>
|
||||||
|
* </form>
|
||||||
|
*
|
||||||
|
* Custom Error Anchor (allows error message to be injected after specified element):
|
||||||
|
*
|
||||||
|
* <form class='show-gl-field-errors'>
|
||||||
|
* <div class="form-group gl-field-error-anchor">
|
||||||
|
* <input type='text' name='username' title='Username is required.'/>
|
||||||
|
* // Error message typically injected here
|
||||||
|
* </div>
|
||||||
|
* // Error message now injected here
|
||||||
|
* </form>
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Regex Patterns in use:
|
||||||
|
*
|
||||||
|
* Only alphanumeric: : "[a-zA-Z0-9]+"
|
||||||
|
* No special characters : "[a-zA-Z0-9-_]+",
|
||||||
|
*
|
||||||
* */
|
* */
|
||||||
|
|
||||||
const errorMessageClass = 'gl-field-error';
|
const errorMessageClass = 'gl-field-error';
|
||||||
const inputErrorClass = 'gl-field-error-outline';
|
const inputErrorClass = 'gl-field-error-outline';
|
||||||
|
const errorAnchorSelector = '.gl-field-error-anchor';
|
||||||
|
const ignoreInputSelector = '.gl-field-error-ignore';
|
||||||
|
|
||||||
class GlFieldError {
|
class GlFieldError {
|
||||||
constructor({ input, formErrors }) {
|
constructor({ input, formErrors }) {
|
||||||
|
@ -34,16 +72,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
initFieldValidation() {
|
initFieldValidation() {
|
||||||
|
const customErrorAnchor = this.inputElement.parents(errorAnchorSelector);
|
||||||
|
const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement;
|
||||||
|
|
||||||
// hidden when injected into DOM
|
// hidden when injected into DOM
|
||||||
this.inputElement.after(this.fieldErrorElement);
|
errorAnchor.after(this.fieldErrorElement);
|
||||||
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
|
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
|
||||||
this.scopedSiblings = this.safelySelectSiblings();
|
this.scopedSiblings = this.safelySelectSiblings();
|
||||||
}
|
}
|
||||||
|
|
||||||
safelySelectSiblings() {
|
safelySelectSiblings() {
|
||||||
// Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity
|
// Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity
|
||||||
const ignoreSelector = '.validation-ignore';
|
const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`);
|
||||||
const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`);
|
|
||||||
const parentContainer = this.inputElement.parent('.form-group');
|
const parentContainer = this.inputElement.parent('.form-group');
|
||||||
|
|
||||||
// Only select siblings when they're scoped within a form-group with one input
|
// Only select siblings when they're scoped within a form-group with one input
|
||||||
|
@ -125,7 +165,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const customValidationFlag = 'no-gl-field-errors';
|
const customValidationFlag = 'gl-field-error-ignore';
|
||||||
|
|
||||||
class GlFieldErrors {
|
class GlFieldErrors {
|
||||||
constructor(form) {
|
constructor(form) {
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
|
|
||||||
this.inputElement.on('keyup.username_check', () => {
|
this.inputElement.on('keyup.username_check', () => {
|
||||||
const username = this.inputElement.val();
|
const username = this.inputElement.val();
|
||||||
|
|
||||||
this.state.valid = this.inputDomElement.validity.valid;
|
this.state.valid = this.inputDomElement.validity.valid;
|
||||||
this.state.empty = !username.length;
|
this.state.empty = !username.length;
|
||||||
|
|
||||||
|
|
|
@ -136,3 +136,35 @@ label {
|
||||||
color: $red-normal;
|
color: $red-normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.show-gl-field-errors {
|
||||||
|
.gl-field-success-outline {
|
||||||
|
border: 1px solid $green-normal;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
|
||||||
|
border: 0 none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gl-field-error-outline {
|
||||||
|
border: 1px solid $red-normal;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
|
||||||
|
border: 0 none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gl-field-success-message {
|
||||||
|
color: $green-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gl-field-error-message {
|
||||||
|
color: $red-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gl-field-hint {
|
||||||
|
color: $gl-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,43 +75,17 @@
|
||||||
.login-body {
|
.login-body {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
||||||
|
|
||||||
input + p {
|
input + p {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gl-field-success-outline {
|
.username .validation-success {
|
||||||
border: 1px solid $green-normal;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
|
|
||||||
border: 0 none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.gl-field-error-outline {
|
|
||||||
border: 1px solid $red-normal;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
|
|
||||||
border: 0 none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.username .validation-success,
|
|
||||||
.gl-field-success-message {
|
|
||||||
color: $green-normal;
|
color: $green-normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.username .validation-error,
|
.username .validation-error {
|
||||||
.gl-field-error-message {
|
|
||||||
color: $red-normal;
|
color: $red-normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gl-field-hint {
|
|
||||||
color: $gl-text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
|
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
|
||||||
%div.username.form-group
|
%div.username.form-group
|
||||||
= f.label :username
|
= f.label :username
|
||||||
= f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
|
= f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
|
||||||
%p.validation-error.hide Username is already taken.
|
%p.validation-error.hide Username is already taken.
|
||||||
%p.validation-success.hide Username is available.
|
%p.validation-success.hide Username is available.
|
||||||
%p.validation-pending.hide Checking username availability...
|
%p.validation-pending.hide Checking username availability...
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
.form-group
|
.form-group
|
||||||
%input.hidden{ type:'hidden' }
|
%input.hidden{ type:'hidden' }
|
||||||
.form-group
|
.form-group
|
||||||
%input.custom.no-gl-field-errors{ type:'text' } Custom, do not validate
|
%input.custom.gl-field-error-ignore{ type:'text' } Custom, do not validate
|
||||||
.form-group
|
.form-group
|
||||||
%input.submit{type: 'submit'} Submit
|
%input.submit{type: 'submit'} Submit
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore elements with custom error handling', function() {
|
it('should ignore elements with custom error handling', function() {
|
||||||
const customErrorFlag = 'no-gl-field-errors';
|
const customErrorFlag = 'gl-field-error-ignore';
|
||||||
const customErrorElem = $(`.${customErrorFlag}`);
|
const customErrorElem = $(`.${customErrorFlag}`);
|
||||||
|
|
||||||
expect(customErrorElem.length).toBe(1);
|
expect(customErrorElem.length).toBe(1);
|
||||||
|
|
Loading…
Reference in New Issue