From 8040674c99de87d9d177081a8a39023d67e3bc03 Mon Sep 17 00:00:00 2001 From: Tony Air Date: Sat, 25 Jul 2020 01:13:25 +0700 Subject: [PATCH] IMPR: Minor updates --- package.json | 2 +- src/js/_components/_ui.form.fields.toggle.js | 34 +-- src/js/_components/_ui.form.validate.field.js | 215 ++++++++++-------- src/js/_components/_ui.multislider.js | 4 +- src/scss/_components/_ui.multislider.scss | 68 +++--- 5 files changed, 177 insertions(+), 146 deletions(-) diff --git a/package.json b/package.json index d81ca6e..0cf06df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@a2nt/ss-bootstrap-ui-webpack-boilerplate", - "version": "2.3.4", + "version": "2.3.5", "author": "Tony Air ", "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.", diff --git a/src/js/_components/_ui.form.fields.toggle.js b/src/js/_components/_ui.form.fields.toggle.js index ed5f279..1c83cbd 100755 --- a/src/js/_components/_ui.form.fields.toggle.js +++ b/src/js/_components/_ui.form.fields.toggle.js @@ -12,7 +12,6 @@ const FormToggleUI = (($) => { const FieldUI = 'jsFormFieldUI'; class FormToggleUI { - constructor($el) { const ui = this; ui.$el = $el; @@ -24,7 +23,7 @@ const FormToggleUI = (($) => { ui.toggle(); - ui.$el.on(`change shown.${ FieldUI } hidden.${ FieldUI}`, (e) => { + ui.$el.on(`change shown.${FieldUI} hidden.${FieldUI}`, (e) => { ui.toggle(); }); @@ -38,8 +37,10 @@ const FormToggleUI = (($) => { const ui = this; const $el = ui.$el; - return ($el.is('[type="radio"],[type="checkbox"]') && $el.parents('.optionset,.checkboxset').length) ? - $el.parents('.optionset,.checkboxset') : $el; + return $el.is('[type="radio"],[type="checkbox"]') && + $el.parents('.optionset,.checkboxset').length + ? $el.parents('.optionset,.checkboxset') + : $el; } getCondition() { @@ -61,7 +62,7 @@ const FormToggleUI = (($) => { } if ($el.attr('type') === 'radio') { - return $Html.find(`[name="${ $el.attr('name') }"]:checked`).val(); + return $Html.find(`[name="${$el.attr('name')}"]:checked`).val(); } return $el.val(); @@ -94,8 +95,7 @@ const FormToggleUI = (($) => { } getElement(target) { - return target && target.length && $(target).length ? - $(target) : false; + return target && target.length && $(target).length ? $(target) : false; } toggle() { @@ -112,15 +112,17 @@ const FormToggleUI = (($) => { } // yes/no toggler - const yesNoVal = ( + const yesNoVal = (condition === true && val && val !== '' && val !== '0') || condition === val - ) ? true : false; + ? true + : false; const $yesTarget = ui.getTrueTarget(); const $noTarget = ui.getFalseTarget(); + const elUI = $el.data(FieldUI); - if (!$el.data(FieldUI).shown || typeof val === 'undefined') { + if ((elUI && !elUI.shown) || typeof val === 'undefined') { ui.toggleElement($yesTarget, false); ui.toggleElement($noTarget, false); @@ -150,7 +152,7 @@ const FormToggleUI = (($) => { $el.collapse(action); }); - $el.trigger(`${action }.${ NAME}`); + $el.trigger(`${action}.${NAME}`); } dispose() { @@ -180,8 +182,10 @@ const FormToggleUI = (($) => { const $el = $(el); const name = $el.attr('name'); - if ($(`[name="${ name }"]`).length > 1) { - console.warn(`${NAME }: Module malfunction duplicate "${ name }" elements found`); + if ($(`[name="${name}"]`).length > 1) { + console.warn( + `${NAME}: Module malfunction duplicate "${name}" elements found`, + ); } }); } @@ -190,13 +194,13 @@ const FormToggleUI = (($) => { // jQuery interface $.fn[NAME] = FormToggleUI._jQueryInterface; $.fn[NAME].Constructor = FormToggleUI; - $.fn[NAME].noConflict = function() { + $.fn[NAME].noConflict = function () { $.fn[NAME] = JQUERY_NO_CONFLICT; return FormToggleUI._jQueryInterface; }; // auto-apply - $(W).on(`${Events.AJAX} ${Events.LOADED}`, () => { + $(W).on(`${Events.LODEDANDREADY}`, () => { //FormToggleUI.validate(); $(Events.FORM_FIELDS).filter('[data-value-toggle]').jsFormToggleUI(); diff --git a/src/js/_components/_ui.form.validate.field.js b/src/js/_components/_ui.form.validate.field.js index a59fedd..8673f9e 100755 --- a/src/js/_components/_ui.form.validate.field.js +++ b/src/js/_components/_ui.form.validate.field.js @@ -1,142 +1,165 @@ import $ from 'jquery'; import Events from '../_events'; -import FormValidateField from './_ui.form.validate.field'; -import SpinnerUI from './_ui.spinner'; -const FormValidate = (($) => { +const FormValidateField = (($) => { // Constants - const NAME = 'jsFormValidate'; + const NAME = 'jsFormValidateField'; const DATA_KEY = NAME; const $Html = $('html, body'); - class FormValidate { - constructor(element) { + const FieldUI = 'jsFormFieldUI'; + + class FormValidateField { + constructor(el) { const ui = this; - const $element = $(element); - const $fields = $element.find('input,textarea,select'); + const $el = $(el); - ui._element = element; - $element.data(DATA_KEY, this); + ui.$el = $el; - ui._fields = $fields; - ui._stepped_form = $element.data('jsSteppedForm'); + ui._actions = $el.parents('form').children('.btn-toolbar,.form-actions'); + $el.data(DATA_KEY, this); // prevent browsers checks (will do it using JS) - $element.attr('novalidate', 'novalidate'); + $el.attr('novalidate', 'novalidate'); - $element.on(Events.FORM_INIT_STEPPED, () => { - ui._stepped_form = $element.data('jsSteppedForm'); + $el.on('change focusout', (e) => { + ui.validate(false); }); - // init fields validation - $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); + $el.addClass(`${NAME}-active`); + $el.trigger(Events.FORM_INIT_VALIDATE_FIELD); } // Public methods dispose() { - const $element = $(this._element); + const $el = ui.$el; - $element.removeClass(`${NAME}-active`); - $.removeData(this._element, DATA_KEY); - this._element = null; + $el.removeClass(`${NAME}-active`); + $.removeData(ui.$el[0], DATA_KEY); + ui.$el = null; } - validate(scrollTo = true, badCallback = false) { - console.log('Checking the field ...'); + validate(scrollTo = true) { const ui = this; + const $el = ui.$el; + + const $field = $el.closest('.field'); + const extraChecks = $el.data(`${NAME}-extra`); let valid = true; + let msg = null; - ui._fields.each((i, el) => { - const $el = $(el); - const fieldUI = $el.data('jsFormValidateField'); + const val = $el.val(); - if (fieldUI && !fieldUI.validate()) { - SpinnerUI.hide(); + // browser checks + required + if ( + !ui.$el[0].checkValidity() || + ($el.hasClass('required') && + (!val.length || + !val.trim().length || + (ui.isHtml(val) && !$(val).text().length))) + ) { + valid = false; + } - console.log('Invalid field data:'); - console.log($el); + // 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/'; + } - if (badCallback) { - badCallback(); - } + this.removeError(); - valid = false; - return false; - } - }); + // extra checks + if (extraChecks) { + extraChecks.forEach((check) => { + valid = valid && check(); + }); + } - return valid; + if (valid) { + return true; + } + + this.setError(scrollTo, msg); + + return false; + } + + isHtml(str) { + 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() { - return this.each(function() { - // attach functionality to element - const $element = $(this); - let data = $element.data(DATA_KEY); + return this.each(function () { + // attach functionality to el + const $el = $(this); + let data = $el.data(DATA_KEY); if (!data) { - data = new FormValidate(this); - $element.data(DATA_KEY, data); + data = new FormValidateField(this); + $el.data(DATA_KEY, data); } }); } } // jQuery interface - $.fn[NAME] = FormValidate._jQueryInterface; - $.fn[NAME].Constructor = FormValidate; - $.fn[NAME].noConflict = function() { + $.fn[NAME] = FormValidateField._jQueryInterface; + $.fn[NAME].Constructor = FormValidateField; + $.fn[NAME].noConflict = function () { $.fn[NAME] = JQUERY_NO_CONFLICT; - return FormValidate._jQueryInterface; + return FormValidateField._jQueryInterface; }; - // 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; + return FormValidateField; })($); -export default FormValidate; +export default FormValidateField; diff --git a/src/js/_components/_ui.multislider.js b/src/js/_components/_ui.multislider.js index 7b01676..0495382 100755 --- a/src/js/_components/_ui.multislider.js +++ b/src/js/_components/_ui.multislider.js @@ -216,7 +216,9 @@ const MultiSlider = (($) => { $(W).on(Events.LODEDANDREADY, () => { console.log(`Initializing: ${NAME}`); - $(`.${NAME}`).jsMultiSlider(); + $(`.${NAME}`).each((i,el) => { + $(el).jsMultiSlider(); + }); }); return MultiSlider; diff --git a/src/scss/_components/_ui.multislider.scss b/src/scss/_components/_ui.multislider.scss index 328fef4..493cc41 100755 --- a/src/scss/_components/_ui.multislider.scss +++ b/src/scss/_components/_ui.multislider.scss @@ -6,48 +6,50 @@ $grid-gutter-element-height: 2rem !default; display: flex; margin-bottom: $grid-gutter-element-height/2; + align-items: center; + justify-content: center; + min-width: 100%; + &-active { margin-bottom: 0; } - &-container { - position: relative; - margin-bottom: $grid-gutter-element-height/2; - - .slider-actions { - .act { - position: absolute; - top: 0; - bottom: 0; - left: 0; - display: flex; - align-items: center; - justify-content: center; - text-decoration: none; - &-slider-prev { - } - &-slider-next { - left: auto; - right: 0; - } - } - } - } - - &-slides-container { - overflow: hidden; - margin: 0 2rem; - - > .slider-nav { - position: relative; - } - } - .slide { position: relative; padding: 0 0.5rem; } +} + +.jsMultiSlider-container { + position: relative; + margin-bottom: $grid-gutter-element-height/2; + .slider-actions { font-size: 2rem; + .act { + position: absolute; + top: 0; + bottom: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; + text-decoration: none; + &-slider-prev { + } + &-slider-next { + left: auto; + right: 0; + } + } + } +} + +.jsMultiSlider-slides-container { + overflow: hidden; + margin: 0 2rem; + + > .slider-nav { + position: relative; } }