mirror of
https://github.com/twbs/bootstrap.git
synced 2025-08-23 13:43:11 +02:00
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
This commit is contained in:
@@ -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'
|
||||
|
@@ -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,
|
||||
|
127
js/src/forms/form-field.js
Normal file
127
js/src/forms/form-field.js
Normal file
@@ -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
|
161
js/src/forms/form.js
Normal file
161
js/src/forms/form.js
Normal file
@@ -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
|
||||
|
@@ -4,8 +4,6 @@ title: مثال إتمام الشراء
|
||||
direction: rtl
|
||||
extra_css:
|
||||
- "../checkout/checkout.css"
|
||||
extra_js:
|
||||
- src: "../checkout/checkout.js"
|
||||
body_class: "bg-light"
|
||||
---
|
||||
|
||||
@@ -67,7 +65,7 @@ body_class: "bg-light"
|
||||
</div>
|
||||
<div class="col-md-7 col-lg-8">
|
||||
<h4 class="mb-3">عنوان الفوترة</h4>
|
||||
<form class="needs-validation" novalidate>
|
||||
<form data-bs-toggle="form-validation">
|
||||
<div class="row g-3">
|
||||
<div class="col-sm-6">
|
||||
<label for="firstName" class="form-label">الاسم الأول</label>
|
||||
|
@@ -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)
|
||||
})
|
||||
})()
|
||||
|
@@ -3,8 +3,6 @@ layout: examples
|
||||
title: Checkout example
|
||||
extra_css:
|
||||
- "checkout.css"
|
||||
extra_js:
|
||||
- src: "checkout.js"
|
||||
body_class: "bg-light"
|
||||
---
|
||||
|
||||
@@ -66,7 +64,7 @@ body_class: "bg-light"
|
||||
</div>
|
||||
<div class="col-md-7 col-lg-8">
|
||||
<h4 class="mb-3">Billing address</h4>
|
||||
<form class="needs-validation" novalidate>
|
||||
<form data-bs-toggle="form-validation">
|
||||
<div class="row g-3">
|
||||
<div class="col-sm-6">
|
||||
<label for="firstName" class="form-label">First name</label>
|
||||
|
@@ -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 `<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.
|
||||
For custom Bootstrap form validation messages, you'll need to add the data-bs-toggle="form-validation" `<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 `<select>`s are only available with `.form-select`, and not `.form-control`.
|
||||
|
||||
{{< example >}}
|
||||
<form class="row g-3 needs-validation" novalidate>
|
||||
<form class="row g-3" data-bs-toggle="form-validation">
|
||||
<div class="col-md-4">
|
||||
<label for="validationCustom01" class="form-label">First name</label>
|
||||
<input type="text" class="form-control" id="validationCustom01" value="Mark" required>
|
||||
<div class="valid-feedback">
|
||||
Looks good!
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationCustom01" value="Mark" required data-bs-valid="Looks good!" data-bs-invalid="Please, provide a valid Name!">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="validationCustom02" class="form-label">Last name</label>
|
||||
<input type="text" class="form-control" id="validationCustom02" value="Otto" required>
|
||||
<div class="valid-feedback">
|
||||
Looks good!
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationCustom02" value="Otto" required data-bs-valid="Looks good!">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="validationCustomUsername" class="form-label">Username</label>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text" id="inputGroupPrepend">@</span>
|
||||
<input type="text" class="form-control" id="validationCustomUsername" aria-describedby="inputGroupPrepend" required>
|
||||
<div class="invalid-feedback">
|
||||
Please choose a username.
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationCustomUsername" aria-describedby="inputGroupPrepend" required data-bs-invalid="Please choose a username.">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="validationCustom03" class="form-label">City</label>
|
||||
<input type="text" class="form-control" id="validationCustom03" required>
|
||||
<div class="invalid-feedback">
|
||||
Please provide a valid city.
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationCustom03" required data-bs-invalid="Please provide a valid city.">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="validationCustom04" class="form-label">State</label>
|
||||
<select class="form-select" id="validationCustom04" required>
|
||||
<select class="form-select" id="validationCustom04" required data-bs-invalid="Please select a valid state.">
|
||||
<option selected disabled value="">Choose...</option>
|
||||
<option>...</option>
|
||||
</select>
|
||||
<div class="invalid-feedback">
|
||||
Please select a valid state.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="validationCustom05" class="form-label">Zip</label>
|
||||
<input type="text" class="form-control" id="validationCustom05" required>
|
||||
<div class="invalid-feedback">
|
||||
Please provide a valid zip.
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationCustom05" required data-bs-invalid="Please provide a valid zip.">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="" id="invalidCheck" required>
|
||||
<input class="form-check-input" type="checkbox" value="" id="invalidCheck" required data-bs-invalid="You must agree before submitting.">
|
||||
<label class="form-check-label" for="invalidCheck">
|
||||
Agree to terms and conditions
|
||||
</label>
|
||||
<div class="invalid-feedback">
|
||||
You must agree before submitting.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" type="submit">Submit form</button>
|
||||
<button class="btn btn-danger" type="reset">Reset form</button>
|
||||
</div>
|
||||
</form>
|
||||
{{< /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
|
||||
<form class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label for="validationServer01" class="form-label">First name</label>
|
||||
<input type="text" class="form-control is-valid" id="validationServer01" value="Mark" required>
|
||||
<div class="valid-feedback">
|
||||
<input type="text" class="form-control is-valid" id="validationServer01" value="Mark" aria-describedby="validationServerNameFeedback" required>
|
||||
<div id="validationServerNameFeedback" class="valid-feedback">
|
||||
Looks good!
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="validationServer02" class="form-label">Last name</label>
|
||||
<input type="text" class="form-control is-valid" id="validationServer02" value="Otto" required>
|
||||
<div class="valid-feedback">
|
||||
<input type="text" class="form-control is-valid" id="validationServer02" aria-describedby="validationServerLastNameFeedback" value="Otto" required>
|
||||
<div id="validationServerLastNameFeedback" class="valid-feedback">
|
||||
Looks good!
|
||||
</div>
|
||||
</div>
|
||||
@@ -246,41 +222,41 @@ Validation styles are available for the following form controls and components:
|
||||
<form class="was-validated">
|
||||
<div class="mb-3">
|
||||
<label for="validationTextarea" class="form-label">Textarea</label>
|
||||
<textarea class="form-control" id="validationTextarea" placeholder="Required example textarea" required></textarea>
|
||||
<div class="invalid-feedback">
|
||||
<textarea class="form-control" id="validationTextarea" placeholder="Required example textarea" aria-describedby="validationSupportedElementsTextArea" required></textarea>
|
||||
<div id="validationSupportedElementsTextArea" class="invalid-feedback">
|
||||
Please enter a message in the textarea.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="validationFormCheck1" required>
|
||||
<input type="checkbox" class="form-check-input" id="validationFormCheck1" aria-describedby="validationSupportedElementsCheckBox" required>
|
||||
<label class="form-check-label" for="validationFormCheck1">Check this checkbox</label>
|
||||
<div class="invalid-feedback">Example invalid feedback text</div>
|
||||
<div id="validationSupportedElementsCheckBox" class="invalid-feedback">Example invalid feedback text</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input type="radio" class="form-check-input" id="validationFormCheck2" name="radio-stacked" required>
|
||||
<input type="radio" class="form-check-input" id="validationFormCheck2" name="radio-stacked" aria-describedby="validationSupportedElementsRadio" required>
|
||||
<label class="form-check-label" for="validationFormCheck2">Toggle this radio</label>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input type="radio" class="form-check-input" id="validationFormCheck3" name="radio-stacked" required>
|
||||
<input type="radio" class="form-check-input" id="validationFormCheck3" name="radio-stacked" aria-describedby="validationSupportedElementsRadio" required>
|
||||
<label class="form-check-label" for="validationFormCheck3">Or toggle this other radio</label>
|
||||
<div class="invalid-feedback">More example invalid feedback text</div>
|
||||
<div id="validationSupportedElementsRadio" class="invalid-feedback">More example invalid feedback text</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<select class="form-select" required aria-label="select example">
|
||||
<select class="form-select" required aria-label="select example" aria-describedby="validationSupportedElementsSelect">
|
||||
<option value="">Open this select menu</option>
|
||||
<option value="1">One</option>
|
||||
<option value="2">Two</option>
|
||||
<option value="3">Three</option>
|
||||
</select>
|
||||
<div class="invalid-feedback">Example invalid select feedback</div>
|
||||
<div id="validationSupportedElementsSelect" class="invalid-feedback">Example invalid select feedback</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<input type="file" class="form-control" aria-label="file example" required>
|
||||
<div class="invalid-feedback">Example invalid form file feedback</div>
|
||||
<input type="file" class="form-control" aria-label="file example" required aria-describedby="validationSupportedElementsFile">
|
||||
<div id="validationSupportedElementsFile" class="invalid-feedback">Example invalid form file feedback</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -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 >}}
|
||||
<form class="row g-3 needs-validation" novalidate>
|
||||
<form class="row g-3" data-bs-toggle="form-validation" data-bs-type="tooltip" >
|
||||
<div class="col-md-4 position-relative">
|
||||
<label for="validationTooltip01" class="form-label">First name</label>
|
||||
<input type="text" class="form-control" id="validationTooltip01" value="Mark" required>
|
||||
<div class="valid-tooltip">
|
||||
Looks good!
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationTooltip01" value="Mark" required data-bs-valid="Looks good!">
|
||||
</div>
|
||||
<div class="col-md-4 position-relative">
|
||||
<label for="validationTooltip02" class="form-label">Last name</label>
|
||||
<input type="text" class="form-control" id="validationTooltip02" value="Otto" required>
|
||||
<div class="valid-tooltip">
|
||||
Looks good!
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationTooltip02" value="Otto" required data-bs-valid="Looks good!">
|
||||
</div>
|
||||
<div class="col-md-4 position-relative">
|
||||
<label for="validationTooltipUsername" class="form-label">Username</label>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text" id="validationTooltipUsernamePrepend">@</span>
|
||||
<input type="text" class="form-control" id="validationTooltipUsername" aria-describedby="validationTooltipUsernamePrepend" required>
|
||||
<div class="invalid-tooltip">
|
||||
Please choose a unique and valid username.
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationTooltipUsername" aria-describedby="validationTooltipUsernamePrepend" required data-bs-invalid="Please choose a username.">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 position-relative">
|
||||
<label for="validationTooltip03" class="form-label">City</label>
|
||||
<input type="text" class="form-control" id="validationTooltip03" required>
|
||||
<div class="invalid-tooltip">
|
||||
Please provide a valid city.
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationTooltip03" required data-bs-invalid="Please provide a valid city.">
|
||||
</div>
|
||||
<div class="col-md-3 position-relative">
|
||||
<label for="validationTooltip04" class="form-label">State</label>
|
||||
<select class="form-select" id="validationTooltip04" required>
|
||||
<select class="form-select" id="validationTooltip04" required data-bs-invalid="Please select a valid state.">
|
||||
<option selected disabled value="">Choose...</option>
|
||||
<option>...</option>
|
||||
</select>
|
||||
<div class="invalid-tooltip">
|
||||
Please select a valid state.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 position-relative">
|
||||
<label for="validationTooltip05" class="form-label">Zip</label>
|
||||
<input type="text" class="form-control" id="validationTooltip05" required>
|
||||
<div class="invalid-tooltip">
|
||||
Please provide a valid zip.
|
||||
</div>
|
||||
<input type="text" class="form-control" id="validationTooltip05" required data-bs-invalid="Please provide a valid zip.">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" type="submit">Submit form</button>
|
||||
<button class="btn btn-danger" type="reset">Reset form</button>
|
||||
</div>
|
||||
</form>
|
||||
{{< /example >}}
|
||||
|
@@ -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)
|
||||
})
|
||||
})()
|
||||
|
Reference in New Issue
Block a user