diff options
Diffstat (limited to 'js/src/forms/field.js')
| -rw-r--r-- | js/src/forms/field.js | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/js/src/forms/field.js b/js/src/forms/field.js new file mode 100644 index 000000000..287d69126 --- /dev/null +++ b/js/src/forms/field.js @@ -0,0 +1,160 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.0): forms/field.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { isElement, typeCheckConfig } from '../util/index' +import Messages from './messages' +import Manipulator from '../dom/manipulator' +import EventHandler from '../dom/event-handler' +import BaseComponent from '../base-component' +import SelectorEngine from '../dom/selector-engine' + +const NAME = 'field' +const DATA_KEY = 'bs.field' +const EVENT_KEY = `.${DATA_KEY}` +const EVENT_INPUT = `input${EVENT_KEY}` +const CLASS_PREFIX_ERROR = 'invalid' +const CLASS_PREFIX_INFO = 'info' +const CLASS_PREFIX_SUCCESS = 'valid' +const CLASS_FIELD_ERROR = 'is-invalid' +const CLASS_FIELD_SUCCESS = 'is-valid' + +const ARIA_DESCRIBED_BY = 'aria-describedby' +const Default = { + name: null, + type: 'feedback', // or tooltip + valid: '', // valid message to add + invalid: '' // invalid message to add +} + +const DefaultType = { + name: 'string', + type: 'string', + valid: 'string', + invalid: 'string' +} + +class Field extends BaseComponent { + constructor(element, config) { + super(element) + if (!isElement(this._element)) { + throw new TypeError(`field "${this._config.name}" not found`) + } + + this._config = this._getConfig(config) + + this._errorMessages = this._getNewMessagesCollection(CLASS_PREFIX_ERROR, CLASS_FIELD_ERROR) + this._helpMessages = this._getNewMessagesCollection(CLASS_PREFIX_INFO, '') + this._successMessages = this._getNewMessagesCollection(CLASS_PREFIX_SUCCESS, CLASS_FIELD_SUCCESS) + + this._initializeMessageCollections() + EventHandler.on(this._element, EVENT_INPUT, () => { + this.clearAppended() + }) + } + + static get NAME() { + return NAME + } + + getElement() { + return this._element + } + + clearAppended() { + const appendedFeedback = SelectorEngine.findOne(`[class*=-${this._config.type}], ${this._getId()}`, this._element.parentNode) + if (!appendedFeedback) { + return + } + + appendedFeedback.remove() + + this._element.classList.remove(CLASS_FIELD_ERROR, CLASS_FIELD_SUCCESS) + + const initialDescribedBy = this._initialDescribedBy() + if (initialDescribedBy) { + this._element.setAttribute(ARIA_DESCRIBED_BY, initialDescribedBy) + } else { + this._element.removeAttribute(ARIA_DESCRIBED_BY) + } + } + + dispose() { + EventHandler.off(this._element, EVENT_KEY) + Object.getOwnPropertyNames(this).forEach(propertyName => { + this[propertyName] = null + }) + } + + errorMessages() { + return this._errorMessages + } + + helpMessages() { + return this._helpMessages + } + + successMessages() { + return this._successMessages + } + + _getConfig(config) { + config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' ? config : {}) + } + + typeCheckConfig(NAME, config, DefaultType) + return config + } + + _appendFeedback(htmlElement, elementClass) { + if (!isElement(htmlElement)) { + return + } + + this.clearAppended() + + const feedbackElement = htmlElement + + this._element.parentNode.append(feedbackElement) + feedbackElement.id = this._getId() + + this._element.classList.add(elementClass) + const initialDescribedBy = this._initialDescribedBy() + const describedBy = initialDescribedBy ? `${initialDescribedBy} ` : '' + this._element.setAttribute(ARIA_DESCRIBED_BY, `${describedBy}${feedbackElement.id}`) + } + + _getId() { + return `${this._config.name}-formTip` + } + + _getNewMessagesCollection(classPrefix, elementClass) { + const config = { + appendFunction: html => this._appendFeedback(html, elementClass), + extraClass: `${classPrefix}-${this._config.type}` + } + return new Messages(config) + } + + _initialDescribedBy() { + return (this._element.getAttribute(ARIA_DESCRIBED_BY) || '').replaceAll(this._getId(), '').trim() + } + + _initializeMessageCollections() { + if (this._config.invalid) { + this.errorMessages().set('default', this._config.invalid) + } + + if (this._config.valid) { + this.successMessages().set('default', this._config.valid) + } + } +} + +export default Field |
