UI: Form validation improvements

This commit is contained in:
Tony Air 2020-05-13 23:05:34 +07:00
parent 5730c9bbff
commit 513a5f1774
7 changed files with 116 additions and 128 deletions

2
dist/js/app.js vendored

File diff suppressed because one or more lines are too long

2
dist/js/app.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"name": "@a2nt/ss-bootstrap-ui-webpack-boilerplate", "name": "@a2nt/ss-bootstrap-ui-webpack-boilerplate",
"version": "2.0.3", "version": "2.0.4",
"author": "Tony Air <tony@twma.pro>", "author": "Tony Air <tony@twma.pro>",
"license": "MIT", "license": "MIT",
"description": "This UI Kit allows you to build Bootstrap 4 webapp with some extra UI features. It's easy to extend and easy to convert HTML templates to CMS templates.", "description": "This UI Kit allows you to build Bootstrap 4 webapp with some extra UI features. It's easy to extend and easy to convert HTML templates to CMS templates.",

View File

@ -77,7 +77,11 @@ const FormBasics = (($) => {
}); });
$el.on('submit', (e) => { $el.on('submit', (e) => {
setTimeout(() => {
if (!$el.find('.error').length) {
SpinnerUI.show(); SpinnerUI.show();
}
}, 100);
}); });
$el.addClass(`${NAME}-active`); $el.addClass(`${NAME}-active`);

View File

@ -1,158 +1,142 @@
import $ from 'jquery'; import $ from 'jquery';
import Events from "../_events"; import Events from '../_events';
import FormValidateField from './_ui.form.validate.field';
import SpinnerUI from './_ui.spinner';
const FormValidateField = (($) => { const FormValidate = (($) => {
// Constants // Constants
const NAME = 'jsFormValidateField'; const NAME = 'jsFormValidate';
const DATA_KEY = NAME; const DATA_KEY = NAME;
const $Html = $('html, body'); const $Html = $('html, body');
const FieldUI = 'jsFormFieldUI'; class FormValidate {
constructor(element) {
class FormValidateField {
constructor(el) {
const ui = this; const ui = this;
const $el = $(el); const $element = $(element);
const $fields = $element.find('input,textarea,select');
ui.$el = $el; ui._element = element;
$element.data(DATA_KEY, this);
ui._actions = $el.parents('form').children('.btn-toolbar,.form-actions'); ui._fields = $fields;
$el.data(DATA_KEY, this); ui._stepped_form = $element.data('jsSteppedForm');
// prevent browsers checks (will do it using JS) // prevent browsers checks (will do it using JS)
$el.attr('novalidate', 'novalidate'); $element.attr('novalidate', 'novalidate');
$el.on('change focusout', (e) => { $element.on(Events.FORM_INIT_STEPPED, () => {
ui.validate(false); ui._stepped_form = $element.data('jsSteppedForm');
}); });
$el.addClass(`${NAME}-active`); // init fields validation
$el.trigger(Events.FORM_INIT_VALIDATE_FIELD); $fields.each((i, el) => {
// skip some fields here
if ($(el).attr('role') === 'combobox') {
return;
}
new FormValidateField(el);
});
$element.removeClass('error');
// check form
$element.on('submit', (e) => {
ui.validate(true, () => {
e.preventDefault();
// switch to step
if (ui._stepped_form) {
const $el = $element.find('.error').first();
if ($el.length) {
ui._stepped_form.step($el.parents('.step'));
}
}
$element.addClass('error');
SpinnerUI.hide();
$element.trigger(Events.FORM_VALIDATION_FAILED);
});
});
$element.addClass(`${NAME}-active`);
$element.trigger(Events.FORM_INIT_VALIDATE);
} }
// Public methods // Public methods
dispose() { dispose() {
const $el = ui.$el; const $element = $(this._element);
$el.removeClass(`${NAME}-active`); $element.removeClass(`${NAME}-active`);
$.removeData(ui.$el[0], DATA_KEY); $.removeData(this._element, DATA_KEY);
ui.$el = null; this._element = null;
} }
validate(scrollTo = true) { validate(scrollTo = true, badCallback = false) {
console.log('Checking the field ...');
const ui = this; const ui = this;
const $el = ui.$el;
const $field = $el.closest('.field');
const extraChecks = $el.data(`${NAME}-extra`);
let valid = true; let valid = true;
let msg = null;
const val = $el.val(); ui._fields.each((i, el) => {
const $el = $(el);
const fieldUI = $el.data('jsFormValidateField');
if (fieldUI && !fieldUI.validate()) {
SpinnerUI.hide();
console.log('Invalid field data:');
console.log($el);
if (badCallback) {
badCallback();
}
// browser checks + required
if (!ui.$el[0].checkValidity() ||
($el.hasClass('required') && (!val.length || !val.trim().length ||
ui.isHtml(val) && !$(val).text().length
))
) {
valid = false; valid = false;
}
// validate URL
if ($el.hasClass('url') && val.length && !this.valideURL(val)) {
valid = false;
msg = 'URL must start with http:// or https://. For example: https://your-domain.com/';
}
this.removeError();
// extra checks
if (extraChecks) {
extraChecks.forEach((check) => {
valid = valid && check();
});
}
if (valid) {
return true;
}
this.setError(scrollTo, msg);
return false; return false;
} }
});
isHtml(str) { return valid;
const doc = new DOMParser().parseFromString(str, "text/html");
return Array.from(doc.body.childNodes).some((node) => node.nodeType === 1);
}
valideURL(str) {
const pattern = new RegExp('^(https?:\\/\\/){1}' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
return pattern.test(str);
}
setError(scrollTo = true, msg = null) {
const ui = this;
const fieldUI = ui.$el.data(FieldUI);
const $field = ui.$el.closest('.field');
const pos = $field.offset().top;
ui.removeError();
$field.addClass('error');
if (msg) {
fieldUI.addMessage(msg, 'alert-error alert-danger', scrollTo);
} else if (scrollTo) {
$field.focus();
$Html.scrollTop(pos - 100);
}
}
removeError() {
const ui = this;
const fieldUI = ui.$el.data(FieldUI);
const $field = ui.$el.closest('.field');
$field.removeClass('error');
$field.removeClass('holder-error');
$field.removeClass('holder-validation');
$field.find('.alert-error').remove();
} }
static _jQueryInterface() { static _jQueryInterface() {
return this.each(function() { return this.each(function() {
// attach functionality to el // attach functionality to element
const $el = $(this); const $element = $(this);
let data = $el.data(DATA_KEY); let data = $element.data(DATA_KEY);
if (!data) { if (!data) {
data = new FormValidateField(this); data = new FormValidate(this);
$el.data(DATA_KEY, data); $element.data(DATA_KEY, data);
} }
}); });
} }
} }
// jQuery interface // jQuery interface
$.fn[NAME] = FormValidateField._jQueryInterface; $.fn[NAME] = FormValidate._jQueryInterface;
$.fn[NAME].Constructor = FormValidateField; $.fn[NAME].Constructor = FormValidate;
$.fn[NAME].noConflict = function() { $.fn[NAME].noConflict = function() {
$.fn[NAME] = JQUERY_NO_CONFLICT; $.fn[NAME] = JQUERY_NO_CONFLICT;
return FormValidateField._jQueryInterface; return FormValidate._jQueryInterface;
}; };
return FormValidateField; // auto-apply
$(window).on(`${Events.AJAX} ${Events.LOADED}`, () => {
$('form').each((i, el) => {
const $el = $(el);
// skip some forms
if ($el.hasClass('no-validation')) {
return true;
}
$el.jsFormValidate();
});
});
return FormValidate;
})($); })($);
export default FormValidateField; export default FormValidate;

View File

@ -1,6 +1,8 @@
import $ from 'jquery'; import $ from 'jquery';
import Events from "../_events"; import Events from '../_events';
import FormValidateField from "./_ui.form.validate.field";
import FormBasics from './_ui.form.basics';
import FormValidateField from './_ui.form.validate.field';
import SpinnerUI from './_ui.spinner'; import SpinnerUI from './_ui.spinner';
const FormValidate = (($) => { const FormValidate = (($) => {
@ -10,13 +12,13 @@ const FormValidate = (($) => {
const $Html = $('html, body'); const $Html = $('html, body');
class FormValidate { class FormValidate {
constructor(element) { constructor(element) {
const ui = this; const ui = this;
const $element = $(element); const $element = $(element);
const $fields = $element.find('input,textarea,select'); const $fields = $element.find('input,textarea,select');
ui._element = element; ui._element = element;
ui.$element = $element;
$element.data(DATA_KEY, this); $element.data(DATA_KEY, this);
ui._fields = $fields; ui._fields = $fields;
@ -80,14 +82,16 @@ const FormValidate = (($) => {
const fieldUI = $el.data('jsFormValidateField'); const fieldUI = $el.data('jsFormValidateField');
if (fieldUI && !fieldUI.validate()) { if (fieldUI && !fieldUI.validate()) {
SpinnerUI.hide();
ui.$element.addClass('error');
console.log('Invalid form data:');
console.log($el);
if (badCallback) { if (badCallback) {
badCallback(); badCallback();
} }
console.log('Invalid form data:');
console.log($el);
SpinnerUI.hide();
valid = false; valid = false;
return false; return false;
} }

View File

@ -14,13 +14,9 @@ const HeaderUI = (($) => {
const ui = this; const ui = this;
ui.dispose(); ui.dispose();
if (!$(`.${CLASSNAME}`).length) { //console.log(`Initializing: ${NAME}`);
return;
}
console.log(`Initializing: ${NAME}`); const $header = $(`#Header,.js${NAME}`);
const $header = $('#Header,.jsHeaderUI');
const updateHeader = () => { const updateHeader = () => {
const h = $header.height(); const h = $header.height();
const s = $Body.scrollTop(); const s = $Body.scrollTop();