Commit a449b9b8 authored by Bryce Johnson's avatar Bryce Johnson

Refactor gl field errors for simpler state management.

parent 768cd071
...@@ -13,79 +13,123 @@ ...@@ -13,79 +13,123 @@
* *
* */ * */
const fieldErrorClass = 'gl-field-error'; const errorMessageClass = 'gl-field-error';
const fieldErrorSelector = `.${fieldErrorClass}`;
const inputErrorClass = 'gl-field-error-outline'; const inputErrorClass = 'gl-field-error-outline';
class GlFieldErrors { class GlFieldError {
constructor(form) { constructor({ input, form }) {
this.form = $(form); this.inputElement = $(input);
this.initValidators(); this.inputDomElement = this.inputElement.get(0);
this.form = form;
this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${ this.errorMessage }</p>`);
this.state = {
valid: false,
empty: true
};
this.initFieldValidation();
} }
initValidators () { initFieldValidation() {
this.inputs = this.form.find(':input:not([type=hidden])').toArray(); // hidden when injected into DOM
this.inputs.forEach((input) => { $input.after(this.fieldErrorElement);
$(input).off('invalid').on('invalid', this.handleInvalidInput.bind(this)); this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this));
});
this.form.on('submit', this.catchInvalidFormSubmit);
} }
/* Neccessary because Safari & iOS quietly allow form submission when form is invalid */ renderValidity() {
catchInvalidFormSubmit (event) { this.setClearState();
if (!event.currentTarget.checkValidity()) {
event.preventDefault(); if (this.state.valid) {
// Prevents disabling of invalid submit button by application.js this.setValidState();
event.stopPropagation(); }
if (this.state.empty) {
this.setEmptyState();
}
if (!this.state.valid) {
this.setInvalidState();
} }
this.form.focusOnFirstInvalid.apply(this);
} }
handleInvalidInput (event) { handleInvalidInput(event) {
event.preventDefault(); event.preventDefault();
this.updateFieldValidityState(event);
const $input = $(event.currentTarget); this.state.valid = true;
this.state.empty = false;
this.renderValidity();
// For UX, wait til after first invalid submission to check each keyup // For UX, wait til after first invalid submission to check each keyup
$input.off('keyup.field_validator') this.inputElement.off('keyup.field_validator')
.on('keyup.field_validator', this.updateFieldValidityState.bind(this)); .on('keyup.field_validator', this.updateValidityState.bind(this));
} }
displayFieldValidity (target, isValid) { getInputValidity() {
const $input = $(target).removeClass(inputErrorClass); return this.inputDomElement.validity.valid;
const $existingError = $input.siblings(fieldErrorSelector); }
const alreadyInvalid = !!$existingError.length;
const implicitErrorMessage = $input.attr('title');
const $errorToDisplay = alreadyInvalid ? $existingError.detach() : $(`<p class="${fieldErrorClass}">${implicitErrorMessage}</p>`);
if (!isValid) { updateValidityState() {
$input.after($errorToDisplay); const inputVal = this.inputElement.val();
$input.addClass(inputErrorClass); this.state.empty = !!inputVal.length;
} this.state.valid = this.getInputValidity;
this.updateFieldSiblings($errorToDisplay, isValid); this.renderValidity();
} }
updateFieldSiblings($target, isValid) { setValidState() {
const siblings = $target.siblings(`p${fieldErrorSelector}`); return this.setClearState();
return isValid ? siblings.show() : siblings.hide(); }
setEmptyState() {
return this.setClearState();
}
setInvalidState() {
$input.addClass(inputErrorClass);
return this.$fieldErrorElement.show();
}
setClearState() {
$input.removeClass(inputErrorClass);
return this.fieldErrorElement.hide();
} }
checkFieldValidity(target) { checkFieldValidity(target) {
return target.validity.valid; return target.validity.valid;
} }
}
updateFieldValidityState(event) { class GlFieldErrors {
const target = event.currentTarget; constructor(form) {
const isKeyup = event.type === 'keyup'; this.form = $(form);
const isValid = this.checkFieldValidity(target); this.initValidators();
}
this.displayFieldValidity(target, isValid); initValidators () {
// select all non-hidden inputs in form
const form = this.form;
this.inputs = this.form.find(':input:not([type=hidden])')
.toArray()
.map((input) => new GlFieldError({ input, form }));
this.form.on('submit', this.catchInvalidFormSubmit);
}
// prevent changing focus while user is typing. /* Neccessary to prevent intercept and override invalid form submit
if (!isKeyup) { * because Safari & iOS quietly allow form submission when form is invalid
this.focusOnFirstInvalid.apply(this); * and prevents disabling of invalid submit button by application.js */
catchInvalidFormSubmit (event) {
if (!event.currentTarget.checkValidity()) {
event.preventDefault();
event.stopPropagation();
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment