From aa6a1ece56c1e9109980934da12ac696ae7dba36 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 19 May 2021 18:37:26 +0300 Subject: [PATCH 1/3] Make a form validation handler | handle form messages add "aria-describedby" attribute on "supported elements" section add "aria-describedby" attribute on server side succeed validation messages --- js/index.esm.js | 1 + js/index.umd.js | 2 + js/src/forms/form-field.js | 127 ++++++++++++++ js/src/forms/form.js | 161 ++++++++++++++++++ .../docs/5.2/examples/checkout-rtl/index.html | 4 +- .../docs/5.2/examples/checkout/checkout.js | 19 --- .../docs/5.2/examples/checkout/index.html | 4 +- site/content/docs/5.2/forms/validation.md | 107 ++++-------- .../docs/5.2/assets/js/validate-forms.js | 19 --- 9 files changed, 326 insertions(+), 118 deletions(-) create mode 100644 js/src/forms/form-field.js create mode 100644 js/src/forms/form.js diff --git a/js/index.esm.js b/js/index.esm.js index 062b25408f09..1b3806c5edc4 100644 --- a/js/index.esm.js +++ b/js/index.esm.js @@ -10,6 +10,7 @@ export { default as Button } from './src/button' export { default as Carousel } from './src/carousel' export { default as Collapse } from './src/collapse' export { default as Dropdown } from './src/dropdown' +export { default as Form } from './src/forms/form' export { default as Modal } from './src/modal' export { default as Offcanvas } from './src/offcanvas' export { default as Popover } from './src/popover' diff --git a/js/index.umd.js b/js/index.umd.js index c63d7c2079ae..8e054aabae7d 100644 --- a/js/index.umd.js +++ b/js/index.umd.js @@ -9,6 +9,7 @@ import Alert from './src/alert' import Button from './src/button' import Carousel from './src/carousel' import Collapse from './src/collapse' +import Form from './src/forms/form' import Dropdown from './src/dropdown' import Modal from './src/modal' import Offcanvas from './src/offcanvas' @@ -23,6 +24,7 @@ export default { Button, Carousel, Collapse, + Form, Dropdown, Modal, Offcanvas, diff --git a/js/src/forms/form-field.js b/js/src/forms/form-field.js new file mode 100644 index 000000000000..e8fd5223c519 --- /dev/null +++ b/js/src/forms/form-field.js @@ -0,0 +1,127 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.3.0): forms/field.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { getUID, isElement } from '../util/index' +import EventHandler from '../dom/event-handler' +import BaseComponent from '../base-component' +import SelectorEngine from '../dom/selector-engine' +import TemplateFactory from '../util/template-factory' + +const NAME = 'formField' +const DATA_KEY = 'bs.field' +const EVENT_KEY = `.${DATA_KEY}` +const EVENT_INPUT = `input${EVENT_KEY}` +const CLASS_FIELD_ERROR = 'is-invalid' +const CLASS_FIELD_SUCCESS = 'is-valid' + +const ARIA_DESCRIBED_BY = 'aria-describedby' +const Default = { + invalid: '', // invalid message to add + name: null, + valid: '', // valid message to add + type: 'feedback' // or tooltip +} + +const DefaultType = { + invalid: 'string', + name: 'string', + valid: 'string', + type: 'string' +} + +const MessageTypes = { + ERROR: { prefix: 'invalid', class: CLASS_FIELD_ERROR }, + INFO: { prefix: 'info', class: '' }, + SUCCESS: { prefix: 'valid', class: CLASS_FIELD_SUCCESS } +} + +class FormField extends BaseComponent { + constructor(element, config) { + super(element, config) + if (!isElement(this._element)) { + throw new TypeError(`field "${this._config.name}" not found`) + } + + this._tipId = getUID(`${this._config.name}-formTip-`) + this._initialDescribedBy = this._element.getAttribute(ARIA_DESCRIBED_BY) || '' + + EventHandler.on(this._element, EVENT_INPUT, () => { + this.clearAppended() + }) + } + + static get NAME() { + return NAME + } + + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get MessageTypes() { + return MessageTypes + } + + getElement() { + return this._element + } + + clearAppended() { + const appendedFeedback = SelectorEngine.findOne(`#${this._tipId}`, this._element.parentNode) + if (!appendedFeedback) { + return + } + + appendedFeedback.remove() + + this._element.classList.remove(CLASS_FIELD_ERROR, CLASS_FIELD_SUCCESS) + + if (this._initialDescribedBy) { + this._element.setAttribute(ARIA_DESCRIBED_BY, this._initialDescribedBy) + return + } + + this._element.removeAttribute(ARIA_DESCRIBED_BY) + } + + appendError(message = this._config.invalid) { + return this.appendFeedback(message, this.constructor.MessageTypes.ERROR) + } + + appendSuccess(message = this._config.valid) { + return this.appendFeedback(message, this.constructor.MessageTypes.SUCCESS) + } + + appendFeedback(feedback, classes = this.constructor.MessageTypes.INFO) { + if (!feedback) { + return false + } + + this.clearAppended() + + const config = { + extraClass: `${classes.prefix}-${this._config.type} ${classes.class}`, + content: { div: feedback } + } + feedback = new TemplateFactory(config) + + const feedbackElement = feedback.toHtml() + feedbackElement.id = this._tipId + + this._element.parentNode.append(feedbackElement) + + const describedBy = `${this._initialDescribedBy} ${feedbackElement.id}`.trim() + this._element.setAttribute(ARIA_DESCRIBED_BY, describedBy) + return true + } +} + +export default FormField diff --git a/js/src/forms/form.js b/js/src/forms/form.js new file mode 100644 index 000000000000..da47037890c3 --- /dev/null +++ b/js/src/forms/form.js @@ -0,0 +1,161 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.3.0): util/form-validation.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ +import BaseComponent from '../base-component' +import EventHandler from '../dom/event-handler' +import FormField from './form-field' +import SelectorEngine from '../dom/selector-engine' + +const NAME = 'formValidation' +const DATA_KEY = 'bs.formValidation' +const EVENT_KEY = `.${DATA_KEY}` +const EVENT_LOAD_DATA_API = `load${EVENT_KEY}` +const EVENT_SUBMIT = `submit${EVENT_KEY}` +const EVENT_RESET = `reset${EVENT_KEY}` + +const CLASS_VALIDATED = 'was-validated' +const SELECTOR_DATA_TOGGLE = 'form[data-bs-toggle="form-validation"]' + +const Default = { + type: 'feedback', // or 'tooltip' + validateCallback: null +} + +const DefaultType = { + type: 'string', validateCallback: '(function|null)' +} + +class Form extends BaseComponent { + constructor(element, config) { + if (element.tagName !== 'FORM') { + throw new TypeError(`Need to be initialized in form elements. "${element.tagName}" given`) + } + + super(element, config) + + this._formFields = null // form field instances + } + + static get NAME() { + return NAME + } + + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + getFields() { + if (!this._formFields) { + this._formFields = this._initializeFields() + } + + return this._formFields + } + + getField(name) { + return this.getFields().get(name) + } + + clear() { + this._element.classList.remove(CLASS_VALIDATED) + // eslint-disable-next-line no-unused-vars + for (const [name, field] of this.getFields()) { + field.clearAppended() + } + } + + validate() { + this.clear() + const fetchedErrors = this._fetchErrors() + if (this._element.checkValidity() && !Object.keys(fetchedErrors).length) { + return true + } + + for (const [name, field] of this.getFields()) { + this._appendErrorToField(field, fetchedErrors[name] || null) + } + + this._element.classList.add(CLASS_VALIDATED) + return false + } + + getDataForSubmission() { + return new FormData(this._element) + } + + _appendErrorToField(field, givenMessage) { + const element = field.getElement() + + if (givenMessage) { // if field is invalid check and return for default message + field.appendError(givenMessage) + return + } + + if (element.checkValidity()) { // if field is valid, return first success message + field.appendSuccess() + return + } + + if (field.appendError()) { // if field is invalid check and return for default message + return + } + + field.appendError(element.validationMessage) + } + + _initializeFields() { + const fields = new Map() + const formElements = Array.from(this._element.elements) // the DOM elements + for (const element of formElements) { + const name = element.name || element.id + + const field = FormField.getOrCreateInstance(element, { + name, type: this._config.type + }) + fields.set(name, field) + } + + return fields + } + + _fetchErrors() { + return typeof this._config.validateCallback === 'function' ? this._config.validateCallback(this.getDataForSubmission()) : {} + } +} + +// On submit we want to auto-validate form +EventHandler.on(document, EVENT_SUBMIT, SELECTOR_DATA_TOGGLE, event => { + const { target } = event + const instance = Form.getOrCreateInstance(target) + if (!target.checkValidity()) { + event.preventDefault() + event.stopPropagation() + } + + if (instance.validate()) { + target.submit() + } +}) + +EventHandler.on(document, EVENT_RESET, SELECTOR_DATA_TOGGLE, event => { + const { target } = event + const instance = Form.getOrCreateInstance(target) + + instance.clear() +}) + +// On load, add `novalidate` attribute to avoid browser validation +EventHandler.on(window, EVENT_LOAD_DATA_API, () => { + for (const el of SelectorEngine.find(SELECTOR_DATA_TOGGLE)) { + el.setAttribute('novalidate', true) + } +}) +export default Form + diff --git a/site/content/docs/5.2/examples/checkout-rtl/index.html b/site/content/docs/5.2/examples/checkout-rtl/index.html index e2a7971c1a19..44120b272b60 100644 --- a/site/content/docs/5.2/examples/checkout-rtl/index.html +++ b/site/content/docs/5.2/examples/checkout-rtl/index.html @@ -4,8 +4,6 @@ direction: rtl extra_css: - "../checkout/checkout.css" -extra_js: - - src: "../checkout/checkout.js" body_class: "bg-light" --- @@ -67,7 +65,7 @@
رمز ترويجي

عنوان الفوترة

-
+
diff --git a/site/content/docs/5.2/examples/checkout/checkout.js b/site/content/docs/5.2/examples/checkout/checkout.js index 30ea0aa6b1e0..e69de29bb2d1 100644 --- a/site/content/docs/5.2/examples/checkout/checkout.js +++ b/site/content/docs/5.2/examples/checkout/checkout.js @@ -1,19 +0,0 @@ -// Example starter JavaScript for disabling form submissions if there are invalid fields -(() => { - 'use strict' - - // Fetch all the forms we want to apply custom Bootstrap validation styles to - const forms = document.querySelectorAll('.needs-validation') - - // Loop over them and prevent submission - Array.from(forms).forEach(form => { - form.addEventListener('submit', event => { - if (!form.checkValidity()) { - event.preventDefault() - event.stopPropagation() - } - - form.classList.add('was-validated') - }, false) - }) -})() diff --git a/site/content/docs/5.2/examples/checkout/index.html b/site/content/docs/5.2/examples/checkout/index.html index ba415f0d9f7a..87b03634bd52 100644 --- a/site/content/docs/5.2/examples/checkout/index.html +++ b/site/content/docs/5.2/examples/checkout/index.html @@ -3,8 +3,6 @@ title: Checkout example extra_css: - "checkout.css" -extra_js: - - src: "checkout.js" body_class: "bg-light" --- @@ -66,7 +64,7 @@
Promo code

Billing address

- +
diff --git a/site/content/docs/5.2/forms/validation.md b/site/content/docs/5.2/forms/validation.md index f8c2200c00b2..aa789676ec0e 100644 --- a/site/content/docs/5.2/forms/validation.md +++ b/site/content/docs/5.2/forms/validation.md @@ -4,9 +4,6 @@ title: Validation description: Provide valuable, actionable feedback to your users with HTML5 form validation, via browser default behaviors or custom styles and JavaScript. group: forms toc: true -extra_js: - - src: "/docs/5.2/assets/js/validate-forms.js" - async: true --- {{< callout warning >}} @@ -30,80 +27,59 @@ With that in mind, consider the following demos for our custom form validation s ## Custom styles -For custom Bootstrap form validation messages, you'll need to add the `novalidate` boolean attribute to your ``. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you. When attempting to submit, you'll see the `:invalid` and `:valid` styles applied to your form controls. +For custom Bootstrap form validation messages, you'll need to add the data-bs-toggle="form-validation" ``. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you. When attempting to submit, you'll see the `:invalid` and `:valid` styles applied to your form controls. Custom feedback styles apply custom colors, borders, focus styles, and background icons to better communicate feedback. Background icons for ` -
- Looks good! -
+
- -
- Looks good! -
+
@ - -
- Please choose a username. -
+
- -
- Please provide a valid city. -
+
- -
- Please select a valid state. -
- -
- Please provide a valid zip. -
+
- + -
- You must agree before submitting. -
+
{{< /example >}} {{< example lang="js" show_preview="false" >}} {{< js.inline >}} -{{- readFile (path.Join "site/static/docs" .Site.Params.docs_version "assets/js/validate-forms.js") -}} {{< /js.inline >}} {{< /example >}} @@ -171,15 +147,15 @@ To fix [issues with border radius](https://github.com/twbs/bootstrap/issues/2511
- -
+ +
Looks good!
- -
+ +
Looks good!
@@ -246,41 +222,41 @@ Validation styles are available for the following form controls and components:
- -
+ +
Please enter a message in the textarea.
- + -
Example invalid feedback text
+
Example invalid feedback text
- +
- + -
More example invalid feedback text
+
More example invalid feedback text
- -
Example invalid select feedback
+
Example invalid select feedback
- -
Example invalid form file feedback
+ +
Example invalid form file feedback
@@ -294,57 +270,40 @@ Validation styles are available for the following form controls and components: If your form layout allows it, you can swap the `.{valid|invalid}-feedback` classes for `.{valid|invalid}-tooltip` classes to display validation feedback in a styled tooltip. Be sure to have a parent with `position: relative` on it for tooltip positioning. In the example below, our column classes have this already, but your project may require an alternative setup. {{< example >}} - +
- -
- Looks good! -
+
- -
- Looks good! -
+
@ - -
- Please choose a unique and valid username. -
+
- -
- Please provide a valid city. -
+
- -
- Please select a valid state. -
- -
- Please provide a valid zip. -
+
+
{{< /example >}} diff --git a/site/static/docs/5.2/assets/js/validate-forms.js b/site/static/docs/5.2/assets/js/validate-forms.js index 30ea0aa6b1e0..e69de29bb2d1 100644 --- a/site/static/docs/5.2/assets/js/validate-forms.js +++ b/site/static/docs/5.2/assets/js/validate-forms.js @@ -1,19 +0,0 @@ -// Example starter JavaScript for disabling form submissions if there are invalid fields -(() => { - 'use strict' - - // Fetch all the forms we want to apply custom Bootstrap validation styles to - const forms = document.querySelectorAll('.needs-validation') - - // Loop over them and prevent submission - Array.from(forms).forEach(form => { - form.addEventListener('submit', event => { - if (!form.checkValidity()) { - event.preventDefault() - event.stopPropagation() - } - - form.classList.add('was-validated') - }, false) - }) -})() From 27e5375912b84ea9ccc18e902a0cdd2d73d5b702 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Mon, 4 Jul 2022 01:39:14 +0100 Subject: [PATCH 2/3] Update documentation text * remove the warning about custom errors and tooltips not being accessible ... they now mostly are * change the phrasing for server-side validation, so that *all* feedback (whether valid or invalid) should have `aria-describedby` --- site/content/docs/5.2/forms/validation.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/site/content/docs/5.2/forms/validation.md b/site/content/docs/5.2/forms/validation.md index aa789676ec0e..9e8674d59309 100644 --- a/site/content/docs/5.2/forms/validation.md +++ b/site/content/docs/5.2/forms/validation.md @@ -6,10 +6,6 @@ group: forms toc: true --- -{{< callout warning >}} -We are aware that currently the client-side custom validation styles and tooltips are not accessible, since they are not exposed to assistive technologies. While we work on a solution, we'd recommend either using the server-side option or the default browser validation method. -{{< /callout >}} - ## How it works Here's how form validation works with Bootstrap: @@ -139,7 +135,7 @@ While these feedback styles cannot be styled with CSS, you can still customize t We recommend using client-side validation, but in case you require server-side validation, you can indicate invalid and valid form fields with `.is-invalid` and `.is-valid`. Note that `.invalid-feedback` is also supported with these classes. -For invalid fields, ensure that the invalid feedback/error message is associated with the relevant form field using `aria-describedby` (noting that this attribute allows more than one `id` to be referenced, in case the field already points to additional form text). +Ensure that the feedback/error message is associated with the relevant form field using `aria-describedby` (noting that this attribute allows more than one `id` to be referenced, in case the field already points to additional form text). To fix [issues with border radius](https://github.com/twbs/bootstrap/issues/25110), input groups require an additional `.has-validation` class. From 7a4a3e9fe15970bbc127ebf21efeee4804285f95 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sat, 23 Jul 2022 21:57:36 +0300 Subject: [PATCH 3/3] add docs & some changes --- .bundlewatch.config.json | 12 +-- js/src/base-component.js | 4 + js/src/forms/form-field.js | 14 ++-- js/src/forms/form.js | 18 ++--- .../docs/5.2/examples/checkout-rtl/index.html | 2 +- .../docs/5.2/examples/checkout/checkout.js | 0 .../docs/5.2/examples/checkout/index.html | 2 +- site/content/docs/5.2/forms/validation.md | 76 ++++++++++++++++++- .../docs/5.2/assets/js/validate-forms.js | 0 9 files changed, 98 insertions(+), 30 deletions(-) delete mode 100644 site/content/docs/5.2/examples/checkout/checkout.js delete mode 100644 site/static/docs/5.2/assets/js/validate-forms.js diff --git a/.bundlewatch.config.json b/.bundlewatch.config.json index 8c390c6e0e27..fd5f3f2000d6 100644 --- a/.bundlewatch.config.json +++ b/.bundlewatch.config.json @@ -34,27 +34,27 @@ }, { "path": "./dist/js/bootstrap.bundle.js", - "maxSize": "43.25 kB" + "maxSize": "44.55 kB" }, { "path": "./dist/js/bootstrap.bundle.min.js", - "maxSize": "22.75 kB" + "maxSize": "23.75 kB" }, { "path": "./dist/js/bootstrap.esm.js", - "maxSize": "28.0 kB" + "maxSize": "29.75 kB" }, { "path": "./dist/js/bootstrap.esm.min.js", - "maxSize": "18.5 kB" + "maxSize": "19.25 kB" }, { "path": "./dist/js/bootstrap.js", - "maxSize": "28.75 kB" + "maxSize": "30.5 kB" }, { "path": "./dist/js/bootstrap.min.js", - "maxSize": "16.25 kB" + "maxSize": "16.75 kB" } ], "ci": { diff --git a/js/src/base-component.js b/js/src/base-component.js index dba5e0742ac3..12acd2f86c10 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -36,6 +36,10 @@ class BaseComponent extends Config { } // Public + getElement() { + return this._element + } + dispose() { Data.remove(this._element, this.constructor.DATA_KEY) EventHandler.off(this._element, this.constructor.EVENT_KEY) diff --git a/js/src/forms/form-field.js b/js/src/forms/form-field.js index e8fd5223c519..f2c3a59b536c 100644 --- a/js/src/forms/form-field.js +++ b/js/src/forms/form-field.js @@ -20,15 +20,13 @@ const CLASS_FIELD_SUCCESS = 'is-valid' const ARIA_DESCRIBED_BY = 'aria-describedby' const Default = { - invalid: '', // invalid message to add - name: null, - valid: '', // valid message to add + invalid: '', // invalid message to append + valid: '', // valid message to append type: 'feedback' // or tooltip } const DefaultType = { invalid: 'string', - name: 'string', valid: 'string', type: 'string' } @@ -70,10 +68,6 @@ class FormField extends BaseComponent { return MessageTypes } - getElement() { - return this._element - } - clearAppended() { const appendedFeedback = SelectorEngine.findOne(`#${this._tipId}`, this._element.parentNode) if (!appendedFeedback) { @@ -122,6 +116,10 @@ class FormField extends BaseComponent { this._element.setAttribute(ARIA_DESCRIBED_BY, describedBy) return true } + + name() { + return this._element.name || this._element.id + } } export default FormField diff --git a/js/src/forms/form.js b/js/src/forms/form.js index da47037890c3..6b854281d20b 100644 --- a/js/src/forms/form.js +++ b/js/src/forms/form.js @@ -8,6 +8,7 @@ import BaseComponent from '../base-component' import EventHandler from '../dom/event-handler' import FormField from './form-field' import SelectorEngine from '../dom/selector-engine' +import { execute } from '../util/index' const NAME = 'formValidation' const DATA_KEY = 'bs.formValidation' @@ -17,7 +18,7 @@ const EVENT_SUBMIT = `submit${EVENT_KEY}` const EVENT_RESET = `reset${EVENT_KEY}` const CLASS_VALIDATED = 'was-validated' -const SELECTOR_DATA_TOGGLE = 'form[data-bs-toggle="form-validation"]' +const SELECTOR_DATA_TOGGLE = 'form[data-bs-toggle="form"]' const Default = { type: 'feedback', // or 'tooltip' @@ -25,7 +26,8 @@ const Default = { } const DefaultType = { - type: 'string', validateCallback: '(function|null)' + type: 'string', + validateCallback: '(function|null)' } class Form extends BaseComponent { @@ -86,10 +88,6 @@ class Form extends BaseComponent { return false } - getDataForSubmission() { - return new FormData(this._element) - } - _appendErrorToField(field, givenMessage) { const element = field.getElement() @@ -114,19 +112,17 @@ class Form extends BaseComponent { const fields = new Map() const formElements = Array.from(this._element.elements) // the DOM elements for (const element of formElements) { - const name = element.name || element.id - const field = FormField.getOrCreateInstance(element, { - name, type: this._config.type + type: this._config.type }) - fields.set(name, field) + fields.set(field.name(), field) } return fields } _fetchErrors() { - return typeof this._config.validateCallback === 'function' ? this._config.validateCallback(this.getDataForSubmission()) : {} + return execute(this._config.validateCallback, [this], {}) } } diff --git a/site/content/docs/5.2/examples/checkout-rtl/index.html b/site/content/docs/5.2/examples/checkout-rtl/index.html index 44120b272b60..b22ef01f43d1 100644 --- a/site/content/docs/5.2/examples/checkout-rtl/index.html +++ b/site/content/docs/5.2/examples/checkout-rtl/index.html @@ -65,7 +65,7 @@
رمز ترويجي

عنوان الفوترة

-
+
diff --git a/site/content/docs/5.2/examples/checkout/checkout.js b/site/content/docs/5.2/examples/checkout/checkout.js deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/site/content/docs/5.2/examples/checkout/index.html b/site/content/docs/5.2/examples/checkout/index.html index 87b03634bd52..29abe7672f39 100644 --- a/site/content/docs/5.2/examples/checkout/index.html +++ b/site/content/docs/5.2/examples/checkout/index.html @@ -64,7 +64,7 @@
Promo code

Billing address

- +
diff --git a/site/content/docs/5.2/forms/validation.md b/site/content/docs/5.2/forms/validation.md index 9e8674d59309..1df8536c561c 100644 --- a/site/content/docs/5.2/forms/validation.md +++ b/site/content/docs/5.2/forms/validation.md @@ -23,12 +23,12 @@ With that in mind, consider the following demos for our custom form validation s ## Custom styles -For custom Bootstrap form validation messages, you'll need to add the data-bs-toggle="form-validation" ``. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you. When attempting to submit, you'll see the `:invalid` and `:valid` styles applied to your form controls. +For custom Bootstrap form validation messages, you'll need to add the data-bs-toggle="form" ``. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you. When attempting to submit, you'll see the `:invalid` and `:valid` styles applied to your form controls. Custom feedback styles apply custom colors, borders, focus styles, and background icons to better communicate feedback. Background icons for ` @@ -266,7 +266,7 @@ Validation styles are available for the following form controls and components: If your form layout allows it, you can swap the `.{valid|invalid}-feedback` classes for `.{valid|invalid}-tooltip` classes to display validation feedback in a styled tooltip. Be sure to have a parent with `position: relative` on it for tooltip positioning. In the example below, our column classes have this already, but your project may require an alternative setup. {{< example >}} - +
@@ -333,3 +333,73 @@ Used to iterate over `$form-validation-states` map values to generate our valida ### Customizing Validation states can be customized via Sass with the `$form-validation-states` map. Located in our `_variables.scss` file, this Sass map is how we generate the default `valid`/`invalid` validation states. Included is a nested map for customizing each state's color, icon, tooltip color, and focus shadow. While no other states are supported by browsers, those using custom styles can easily add more complex form feedback. + + +## Usage +### Via data attributes + +To easily add form validation behavior to you form, add `data-bs-toggle="form"` attribute to the `` element. + +### Via JavaScript + +Enable manually with: + +```js +const formElementList = document.querySelectorAll('form') +const formList = [...formElementList].map(formEl => new bootstrap.Form(formEl)) +``` + +### Options + +#### Form +{{< bs-table "table" >}} +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| `type` | string | `feedback` | You may pick the kind of feedback. Acceptable values `tooltip` or `feedback` | +| `validateCallback` | function, null | `null` | A callback to execute while trying to check for errors. Can be use for ajax submissions/validations. | +{{< /bs-table >}} + + +#### Field +{{< bs-table "table" >}} +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| `invalid` | string | `''` | invalid message to append | +| `valid` | string | `''` | valid message to append | +{{< /bs-table >}} + +### Methods + + +You can create a form instance using the constructor, for example: + +```js +const bsForm = new bootstrap.Form('#myForm', { type: 'tooltip' }) +``` + +#### Form +{{< bs-table "table" >}} +| Method | Description | +| --- | --- | +| `getInstance` | *Static* method which allows you to get the form instance associated with a DOM element. | +| `getOrCreateInstance` | *Static* method which allows you to get the form instance associated with a DOM element, or create a new one in case it wasn't initialized. | +| `getElement` | Returns the DOM element of the instance. | +| `getFields` | Returns all form-fields instances of the form. Array\. | +| `getField('name')` | Searches and return the requested FormField instance or undefined. | +| `clear` | Clears all the form validation results. | +| `validate` | Activates validation process. | +{{< /bs-table >}} + +#### Field +{{< bs-table "table" >}} +| Method | Description | +| --- | --- | +| `getInstance` | *Static* method which allows you to get the form Field instance associated with a DOM element | +| `getOrCreateInstance` | *Static* method which allows you to get the form Field instance associated with a DOM element, or create a new one in case it wasn't initialized | +| `getElement` | Returns the DOM element of the instance. | +| `clearAppended` | Clears any appended messages from field. | +| `appendError(message)` | Appends the given message as an error feedback. In case we do not provide a message, it fallbacks to the configuration given `invalid` message. | +| `appendSuccess(message)` | Appends the given message as a success feedback. In case we do not provide a message, it fallbacks to the configuration given `valid` message. | +| `appendFeedback(message)` | Appends the given message as a simple info feedback. | +| `name` | returns the name (fallback to id) of the field as unique identifier between form elements. | +{{< /bs-table >}} diff --git a/site/static/docs/5.2/assets/js/validate-forms.js b/site/static/docs/5.2/assets/js/validate-forms.js deleted file mode 100644 index e69de29bb2d1..000000000000