From 4d580f5acad76a7333efa2f5c0c012f91786b346 Mon Sep 17 00:00:00 2001 From: Massimo Marsan Date: Wed, 23 Nov 2022 17:47:10 +0100 Subject: [PATCH] fix: dynamic bind input, input number and input password --- docs/form/input.md | 115 +++++++++++----------------- src/js/plugins/input-label.js | 4 + src/js/plugins/input-number.js | 43 +++++++++-- src/js/plugins/input-password.js | 77 +++++++++++++++---- src/js/plugins/input.js | 28 ++++++- src/js/plugins/util/observer.js | 32 +++++--- src/scss/custom/_form-password.scss | 7 ++ 7 files changed, 203 insertions(+), 103 deletions(-) diff --git a/docs/form/input.md b/docs/form/input.md index 65cb125cb7..154f9b02ed 100644 --- a/docs/form/input.md +++ b/docs/form/input.md @@ -64,7 +64,7 @@ Si può abbinare all'etichetta un _placeholder_ (testo di esempio) per ulteriore {% capture example %}
- +
{% endcapture %}{% include example.html content=example %} @@ -82,7 +82,7 @@ Il testo di aiuto deve essere esplicitamente associato agli elementi del mudulo {% capture example %}
- +
- + @@ -132,10 +132,29 @@ Il testo di aiuto deve essere esplicitamente associato agli elementi del mudulo ### Input password -Per rendere più semplice l'inserimento della password, l'elemento è stato dotato di un visualizzatore dei caratteri digitati. Inoltre è possibile abbinare un controllo per segnalare quanto la password che si sta inserendo sia sicura con l'aggiunta della classe `input-password-strength-meter`. +Per rendere più semplice l'inserimento della password, l'elemento è stato dotato di un visualizzatore dei caratteri digitati. Inoltre è possibile abbinare un controllo per segnalare quanto la password che si sta inserendo sia sicura con l'aggiunta dell'HTML necessario. È possibile personalizzare la componente `strength meter` usando gli attributi data. + + + + + + + + + + + + + + + +
Attributo dataDescrizioneDefault
data-bs-minimum-lengthLunghezza minima per il calcolo della forza della password (soglia password molto debole)4
+ +E' possibile personalizzare i testi dei messaggi riguardanti la robustezza della password usando gli attributi data dell'elemento `small.form-text`. + @@ -164,27 +183,7 @@ Per rendere più semplice l'inserimento della password, l'elemento è stato dota - - - - - - - - - - - - - - - - - - - - - +
data-bs-strong-pass Testo per il punteggio di forza della password massimo Password molto sicura
data-bs-enter-passTesto di aiutoInserisci almeno 8 caratteri e una lettera maiuscola
data-bs-alert-capsTesto per avvertire che il CAPS LOCK è inseritoCAPS LOCK inserito
data-bs-show-textAttiva/disattiva la visibilità dei messaggi di erroretrue
data-bs-minimum-lengthLunghezza minima per il calcolo della forza della password (soglia password molto debole)4
@@ -202,11 +201,29 @@ Per rendere più semplice l'inserimento della password, l'elemento è stato dota
- + +
+ Inserisci almeno 8 caratteri e una lettera maiuscola +
+
+
+
+
+
+
+
+
+
+ CAPS LOCK inserito
{% endcapture %}{% include example.html content=example %} @@ -218,13 +235,6 @@ Abilitarlo manualmente con: ```js var inputElement = document.querySelector('#exampleInputPassword')) var passwordComponent = new bootstrap.InputPassword(inputElement, { - shortPass: 'Password molto debole', - badPass: 'Password debole', - goodPass: 'Password sicura', - strongPass: 'Password molto sicura', - enterPass: 'Inserisci almeno 8 caratteri e una lettera maiuscola', - alertCaps: 'CAPS LOCK inserito', - showText: true, minimumLength: 4, }) ``` @@ -240,46 +250,11 @@ var passwordComponent = new bootstrap.InputPassword(inputElement, { - - shortPass - Testo per il punteggio di forza della password minimo - Password molto debole - - - badPass - Testo per punteggio di forza della password basso - Password debole - - - goodPass - Testo per punteggio di forza della password buono - Password sicura - - - strongPass - Testo per il punteggio di forza della password massimo - Password molto sicura - - - enterPass - Testo di aiuto - Inserisci almeno 8 caratteri e una lettera maiuscola - - - alertCaps - Testo per avvertire che il CAPS LOCK è inserito - CAPS LOCK inserito - minimumLength Lunghezza minima per il calcolo della forza della password (soglia password molto debole) 4 - - showText - Attiva/disattiva la visibilità dei messaggi di errore - true - @@ -302,7 +277,7 @@ Aggiungi l'attributo `readonly` ad un input per impedire la modifica del valore {% capture example %}
- +
{% endcapture %}{% include example.html content=example %} @@ -315,7 +290,7 @@ Se per qualche motivo vuoi avere gli elementi `` nella forma sti
- +
diff --git a/src/js/plugins/input-label.js b/src/js/plugins/input-label.js index a2712ae342..1ecb9cc4aa 100644 --- a/src/js/plugins/input-label.js +++ b/src/js/plugins/input-label.js @@ -25,6 +25,10 @@ class InputLabel { return NAME } + static getInputFromLabel = (labelElement) => { + return document.querySelector('#' + labelElement.getAttribute('for')) + } + // Public // Private diff --git a/src/js/plugins/input-number.js b/src/js/plugins/input-number.js index 36357b8104..19154af21f 100644 --- a/src/js/plugins/input-number.js +++ b/src/js/plugins/input-number.js @@ -12,8 +12,11 @@ const DATA_API_KEY = '.data-api' const EVENT_CLICK = `click${EVENT_KEY}` const EVENT_CHANGE = `change${EVENT_KEY}` -const EVENT_FOCUS_DATA_API = `focus${EVENT_KEY}${DATA_API_KEY}` + +//const EVENT_FOCUS_DATA_API = `focus${EVENT_KEY}${DATA_API_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` +const EVENT_MOUSEDOWN_DATA_API = `mousedown${EVENT_KEY}${DATA_API_KEY}` +const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}` const CLASS_NAME_ADAPTIVE = 'input-number-adaptive' const CLASS_NAME_PERCENTAGE = 'input-number-percentage' @@ -129,14 +132,8 @@ class InputNumber extends BaseComponent { * ------------------------------------------------------------------------ */ -const inputs = SelectorEngine.find(SELECTOR_INPUT) +/*const inputs = SelectorEngine.find(SELECTOR_INPUT) inputs.forEach((input) => { - /*const prevInst = BaseComponent.getInstance(input) - if (prevInst.NAME === 'input') { - prevInst.dispose() - } - InputNumber.getOrCreateInstance(input)*/ - EventHandler.one(input, EVENT_FOCUS_DATA_API, (evt) => { evt.preventDefault() InputNumber.getOrCreateInstance(input) @@ -162,6 +159,36 @@ inputsButtons.forEach((button) => { } } }) +})*/ + +const createInput = (element) => { + if (element && element.matches(SELECTOR_INPUT)) { + return InputNumber.getOrCreateInstance(element) + } + return null +} + +EventHandler.on(document, EVENT_MOUSEDOWN_DATA_API, SELECTOR_INPUT + ', label', function () { + const target = InputLabel.getInputFromLabel(this) || this + createInput(target) +}) +EventHandler.on(document, EVENT_KEYUP_DATA_API, SELECTOR_INPUT + ', label', function () { + const target = InputLabel.getInputFromLabel(this) || this + const element = createInput(target) + if (element && element._label) { + element._label._labelOut() + } +}) +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_WRAPPER + ' ' + SELECTOR_BTN, function () { + if (this.classList.contains(CLASS_NAME_INCREMENT) || this.classList.contains(CLASS_NAME_DECREMENT)) { + const wrapper = this.closest(SELECTOR_WRAPPER) + if (wrapper) { + const input = SelectorEngine.findOne(SELECTOR_INPUT, wrapper) + if (input) { + InputNumber.getOrCreateInstance(input) + } + } + } }) export default InputNumber diff --git a/src/js/plugins/input-password.js b/src/js/plugins/input-password.js index 387b645441..59fc935fe4 100644 --- a/src/js/plugins/input-password.js +++ b/src/js/plugins/input-password.js @@ -9,7 +9,7 @@ import InputLabel from './input-label' const NAME = 'inputpassword' const DATA_KEY = 'bs.inputpassword' const EVENT_KEY = `.${DATA_KEY}` -//const DATA_API_KEY = '.data-api' +const DATA_API_KEY = '.data-api' const Default = { shortPass: 'Password molto debole', @@ -29,11 +29,21 @@ const EVENT_KEYPRESS = `keypress${EVENT_KEY}` const EVENT_SCORE = `score${EVENT_KEY}` const EVENT_TEXT = `text${EVENT_KEY}` +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` +const EVENT_MOUSEDOWN_DATA_API = `mousedown${EVENT_KEY}${DATA_API_KEY}` +const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}` + const CLASS_NAME_PASSWORD = 'input-password' -const CLASS_NAME_METER = 'input-password-strength-meter' +//const CLASS_NAME_METER = 'input-password-strength-meter' +const CLASS_NAME_SHOW = 'show' const SELECTOR_PASSWORD = 'input[data-bs-input][type="password"]' const SELECTOR_BTN_SHOW_PWD = '.password-icon' +const SELECTOR_METER = '.password-strength-meter' +const SELECTOR_METER_GRAYBAR = '.password-meter' +const SELECTOR_METER_COLBAR = '.progress-bar' +const SELECTOR_CAPS = '.password-caps' +const SELECTOR_TEXT = '.form-text' class InputPassword extends BaseComponent { constructor(element, config) { @@ -41,7 +51,7 @@ class InputPassword extends BaseComponent { this._config = this._getConfig(config) this._isCustom = this._element.classList.contains(CLASS_NAME_PASSWORD) - this._hasMeter = this._element.classList.contains(CLASS_NAME_METER) + this._meter = this._element.parentNode.querySelector(SELECTOR_METER) this._isShiftPressed = false this._isCapsOn = false @@ -51,6 +61,8 @@ class InputPassword extends BaseComponent { this._capsElement = null this._showPwdElement = null + this._text = {} + this._label = new InputLabel(element) this._init() @@ -76,8 +88,8 @@ class InputPassword extends BaseComponent { } _init() { - if (this._hasMeter) { - this._grayBarElement = document.createElement('div') + if (this._meter) { + /*this._grayBarElement = document.createElement('div') this._grayBarElement.classList.add('password-meter', 'progress', 'rounded-0', 'position-absolute') this._grayBarElement.innerHTML = `
@@ -107,22 +119,32 @@ class InputPassword extends BaseComponent { wrapper.appendChild(this._grayBarElement) - this._element.parentNode.insertBefore(wrapper, this._element.nextSibling) + this._element.parentNode.insertBefore(wrapper, this._element.nextSibling)*/ + + this._grayBarElement = this._meter.querySelector(SELECTOR_METER_GRAYBAR) + this._colorBarElement = this._meter.querySelector(SELECTOR_METER_COLBAR) + this._textElement = this._meter.querySelector(SELECTOR_TEXT) + + if (this._textElement) { + this._config = Object.assign({}, this._config, { ...Manipulator.getDataAttributes(this._textElement) }, { enterPass: this._textElement.innerText }) + } } if (this._isCustom) { - this._capsElement = document.createElement('small') + /*this._capsElement = document.createElement('small') this._capsElement.style.display = 'none' this._capsElement.classList.add('password-caps', 'form-text', 'text-warning', 'position-absolute', 'bg-white', 'w-100') this._capsElement.innerHTML = this._config.alertCaps - this._element.parentNode.appendChild(this._capsElement) + this._element.parentNode.appendChild(this._capsElement)*/ + + this._capsElement = this._element.parentNode.querySelector(SELECTOR_CAPS) } this._showPwdElement = SelectorEngine.findOne(SELECTOR_BTN_SHOW_PWD, this._element.parentNode) } _bindEvents() { - if (this._hasMeter) { + if (this._meter) { EventHandler.on(this._element, EVENT_KEYUP, () => this._checkPassword()) } @@ -163,10 +185,14 @@ class InputPassword extends BaseComponent { } _showCapsMsg() { - this._capsElement.style.display = 'block' + if (this._capsElement) { + this._capsElement.classList.add(CLASS_NAME_SHOW) + } } _hideCapsMsg() { - this._capsElement.style.display = 'none' + if (this._capsElement) { + this._capsElement.classList.remove(CLASS_NAME_SHOW) + } } _toggleShowPassword() { @@ -194,7 +220,7 @@ class InputPassword extends BaseComponent { EventHandler.trigger(this._element, EVENT_SCORE) - if (this._config.showText) { + if (this._textElement) { let text = this._scoreText(score) if (this._element.value.length === 0 && score <= 0) { text = this._config.enterPass @@ -373,9 +399,34 @@ class InputPassword extends BaseComponent { * ------------------------------------------------------------------------ */ -const inputs = SelectorEngine.find(SELECTOR_PASSWORD) +/*const inputs = SelectorEngine.find(SELECTOR_PASSWORD) inputs.forEach((input) => { InputPassword.getOrCreateInstance(input) +})*/ + +const createInput = (element) => { + if (element && element.matches(SELECTOR_PASSWORD)) { + return InputPassword.getOrCreateInstance(element) + } + return null +} + +EventHandler.on(document, EVENT_MOUSEDOWN_DATA_API, SELECTOR_PASSWORD + ', label', function () { + const target = InputLabel.getInputFromLabel(this) || this + createInput(target) +}) +EventHandler.on(document, EVENT_KEYUP_DATA_API, SELECTOR_PASSWORD + ', label', function () { + const target = InputLabel.getInputFromLabel(this) || this + const element = createInput(target) + if (element && element._label) { + element._label._labelOut() + } +}) +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_BTN_SHOW_PWD, function () { + const target = this.parentNode && this.parentNode.querySelector(SELECTOR_PASSWORD) + if (target) { + InputPassword.getOrCreateInstance(target) + } }) export default InputPassword diff --git a/src/js/plugins/input.js b/src/js/plugins/input.js index 0910a56cde..fa2c8163e0 100644 --- a/src/js/plugins/input.js +++ b/src/js/plugins/input.js @@ -8,9 +8,10 @@ import InputLabel from './input-label' const NAME = 'input' const DATA_KEY = 'bs.input' const EVENT_KEY = `.${DATA_KEY}` -//const DATA_API_KEY = '.data-api' +const DATA_API_KEY = '.data-api' -//const SELECTOR_INPUT = 'input[data-bs-input]' +const EVENT_MOUSEDOWN_DATA_API = `mousedown${EVENT_KEY}${DATA_API_KEY}` +const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}` const EVENT_CHANGE = `change${EVENT_KEY}` @@ -68,11 +69,12 @@ const excludes = [ 'select', 'input[data-bs-input][type="number"]', 'input[data-bs-input][type="password"]', + 'input.input-password[data-bs-input]', 'input[data-bs-autocomplete][type="search"]', 'input[type="time"]', ] -const inputs = SelectorEngine.find('input, textarea').filter((input) => { +/*const inputs = SelectorEngine.find('input, textarea').filter((input) => { let result = true excludes.forEach((selector) => { if (input.matches(selector)) { @@ -83,6 +85,26 @@ const inputs = SelectorEngine.find('input, textarea').filter((input) => { }) inputs.forEach((input) => { Input.getOrCreateInstance(input) +})*/ + +const createInput = (element) => { + const toExclude = !!excludes.find((selector) => element.matches(selector)) + if (!toExclude) { + return Input.getOrCreateInstance(element) + } + return null +} + +EventHandler.on(document, EVENT_MOUSEDOWN_DATA_API, 'input, textarea, label', function () { + const target = InputLabel.getInputFromLabel(this) || this + createInput(target) +}) +EventHandler.on(document, EVENT_KEYUP_DATA_API, 'input, textarea, label', function () { + const target = InputLabel.getInputFromLabel(this) || this + const element = createInput(target) + if (element && element._label) { + element._label._labelOut() + } }) export default Input diff --git a/src/js/plugins/util/observer.js b/src/js/plugins/util/observer.js index e511f64f35..21a01c4918 100644 --- a/src/js/plugins/util/observer.js +++ b/src/js/plugins/util/observer.js @@ -36,8 +36,10 @@ class CssClassObserver { if (this.ignoreToggle || this.lastClassState !== currentClassState) { this.lastClassState = currentClassState if (currentClassState) { - this.classAddedCallback() - } else { + if (typeof this.classAddedCallback === 'function') { + this.classAddedCallback() + } + } else if (typeof this.classRemovedCallback === 'function') { this.classRemovedCallback() } } @@ -61,13 +63,15 @@ class ContentObserver { this.init() } + //public + init() { this.observer = new MutationObserver((mutationsList) => this.mutationCallback(mutationsList)) this.observe() } observe() { - this.observer.observe(this.targetNode, { childList: true }) + this.observer.observe(this.targetNode, { childList: true, subtree: true }) } disconnect() { @@ -78,18 +82,28 @@ class ContentObserver { for (let mutation of mutationsList) { if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { - if (node.matches(this.contentSelector)) { - this.contentAddedCallback() - } + this._callbackExec(node) }) mutation.removedNodes.forEach((node) => { - if (node.matches(this.contentSelector)) { - this.contentRemovedCallback() - } + this._callbackExec(node, true) }) } } } + + //private + _callbackExec(node, actionRemove) { + const foundNodes = node.matches && node.matches(this.contentSelector) ? [node] : node.querySelectorAll ? node.querySelectorAll(this.contentSelector) : null + const callback = + actionRemove && typeof this.contentRemovedCallback === 'function' + ? this.contentRemovedCallback + : typeof this.contentAddedCallback === 'function' + ? this.contentAddedCallback + : null + if (foundNodes && callback) { + foundNodes.forEach((node) => callback(node)) + } + } } export { CssClassObserver, ContentObserver } diff --git a/src/scss/custom/_form-password.scss b/src/scss/custom/_form-password.scss index 5151514318..a9daac988f 100644 --- a/src/scss/custom/_form-password.scss +++ b/src/scss/custom/_form-password.scss @@ -20,3 +20,10 @@ height: 4px; } } + +.password-caps { + display: none; + &.show { + display: block; + } +}