2
0
mirror of https://github.com/tenrok/bootstrap.git synced 2026-06-17 19:21:23 +03:00

Make a form validation handler | handle form messages

This commit is contained in:
GeoSot
2021-05-19 18:37:26 +03:00
parent b6d27899fc
commit d18697cb5e
9 changed files with 311 additions and 103 deletions
+1
View File
@@ -10,6 +10,7 @@ export { default as Button } from './src/button'
export { default as Carousel } from './src/carousel' export { default as Carousel } from './src/carousel'
export { default as Collapse } from './src/collapse' export { default as Collapse } from './src/collapse'
export { default as Dropdown } from './src/dropdown' 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 Modal } from './src/modal'
export { default as Offcanvas } from './src/offcanvas' export { default as Offcanvas } from './src/offcanvas'
export { default as Popover } from './src/popover' export { default as Popover } from './src/popover'
+2
View File
@@ -9,6 +9,7 @@ import Alert from './src/alert'
import Button from './src/button' import Button from './src/button'
import Carousel from './src/carousel' import Carousel from './src/carousel'
import Collapse from './src/collapse' import Collapse from './src/collapse'
import Form from './src/forms/form'
import Dropdown from './src/dropdown' import Dropdown from './src/dropdown'
import Modal from './src/modal' import Modal from './src/modal'
import Offcanvas from './src/offcanvas' import Offcanvas from './src/offcanvas'
@@ -23,6 +24,7 @@ export default {
Button, Button,
Carousel, Carousel,
Collapse, Collapse,
Form,
Dropdown, Dropdown,
Modal, Modal,
Offcanvas, Offcanvas,
+127
View 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
View 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 direction: rtl
extra_css: extra_css:
- "../checkout/form-validation.css" - "../checkout/form-validation.css"
extra_js:
- src: "../checkout/form-validation.js"
body_class: "bg-light" body_class: "bg-light"
--- ---
@@ -67,7 +65,7 @@ body_class: "bg-light"
</div> </div>
<div class="col-md-7 col-lg-8"> <div class="col-md-7 col-lg-8">
<h4 class="mb-3">عنوان الفوترة</h4> <h4 class="mb-3">عنوان الفوترة</h4>
<form class="needs-validation" novalidate> <form data-bs-toggle="form-validation">
<div class="row g-3"> <div class="row g-3">
<div class="col-sm-6"> <div class="col-sm-6">
<label for="firstName" class="form-label">الاسم الأول</label> <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 title: Checkout example
extra_css: extra_css:
- "form-validation.css" - "form-validation.css"
extra_js:
- src: "form-validation.js"
body_class: "bg-light" body_class: "bg-light"
--- ---
@@ -66,7 +64,7 @@ body_class: "bg-light"
</div> </div>
<div class="col-md-7 col-lg-8"> <div class="col-md-7 col-lg-8">
<h4 class="mb-3">Billing address</h4> <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="row g-3">
<div class="col-sm-6"> <div class="col-sm-6">
<label for="firstName" class="form-label">First name</label> <label for="firstName" class="form-label">First name</label>
+18 -59
View File
@@ -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. description: Provide valuable, actionable feedback to your users with HTML5 form validation, via browser default behaviors or custom styles and JavaScript.
group: forms group: forms
toc: true toc: true
extra_js:
- src: "/docs/5.2/assets/js/validate-forms.js"
async: true
--- ---
{{< callout warning >}} {{< callout warning >}}
@@ -30,80 +27,59 @@ With that in mind, consider the following demos for our custom form validation s
## Custom styles ## 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`. 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 >}} {{< example >}}
<form class="row g-3 needs-validation" novalidate> <form class="row g-3" data-bs-toggle="form-validation">
<div class="col-md-4"> <div class="col-md-4">
<label for="validationCustom01" class="form-label">First name</label> <label for="validationCustom01" class="form-label">First name</label>
<input type="text" class="form-control" id="validationCustom01" value="Mark" required> <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 class="valid-feedback">
Looks good!
</div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label for="validationCustom02" class="form-label">Last name</label> <label for="validationCustom02" class="form-label">Last name</label>
<input type="text" class="form-control" id="validationCustom02" value="Otto" required> <input type="text" class="form-control" id="validationCustom02" value="Otto" required data-bs-valid="Looks good!">
<div class="valid-feedback">
Looks good!
</div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label for="validationCustomUsername" class="form-label">Username</label> <label for="validationCustomUsername" class="form-label">Username</label>
<div class="input-group has-validation"> <div class="input-group has-validation">
<span class="input-group-text" id="inputGroupPrepend">@</span> <span class="input-group-text" id="inputGroupPrepend">@</span>
<input type="text" class="form-control" id="validationCustomUsername" aria-describedby="inputGroupPrepend" required> <input type="text" class="form-control" id="validationCustomUsername" aria-describedby="inputGroupPrepend" required data-bs-invalid="Please choose a username.">
<div class="invalid-feedback">
Please choose a username.
</div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label for="validationCustom03" class="form-label">City</label> <label for="validationCustom03" class="form-label">City</label>
<input type="text" class="form-control" id="validationCustom03" required> <input type="text" class="form-control" id="validationCustom03" required data-bs-invalid="Please provide a valid city.">
<div class="invalid-feedback">
Please provide a valid city.
</div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label for="validationCustom04" class="form-label">State</label> <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 selected disabled value="">Choose...</option>
<option>...</option> <option>...</option>
</select> </select>
<div class="invalid-feedback">
Please select a valid state.
</div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label for="validationCustom05" class="form-label">Zip</label> <label for="validationCustom05" class="form-label">Zip</label>
<input type="text" class="form-control" id="validationCustom05" required> <input type="text" class="form-control" id="validationCustom05" required data-bs-invalid="Please provide a valid zip.">
<div class="invalid-feedback">
Please provide a valid zip.
</div>
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="form-check"> <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"> <label class="form-check-label" for="invalidCheck">
Agree to terms and conditions Agree to terms and conditions
</label> </label>
<div class="invalid-feedback">
You must agree before submitting.
</div>
</div> </div>
</div> </div>
<div class="col-12"> <div class="col-12">
<button class="btn btn-primary" type="submit">Submit form</button> <button class="btn btn-primary" type="submit">Submit form</button>
<button class="btn btn-danger" type="reset">Reset form</button>
</div> </div>
</form> </form>
{{< /example >}} {{< /example >}}
{{< example lang="js" show_preview="false" >}} {{< example lang="js" show_preview="false" >}}
{{< js.inline >}} {{< js.inline >}}
{{- readFile (path.Join "site/static/docs" .Site.Params.docs_version "assets/js/validate-forms.js") -}}
{{< /js.inline >}} {{< /js.inline >}}
{{< /example >}} {{< /example >}}
@@ -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. 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 >}} {{< 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"> <div class="col-md-4 position-relative">
<label for="validationTooltip01" class="form-label">First name</label> <label for="validationTooltip01" class="form-label">First name</label>
<input type="text" class="form-control" id="validationTooltip01" value="Mark" required> <input type="text" class="form-control" id="validationTooltip01" value="Mark" required data-bs-valid="Looks good!">
<div class="valid-tooltip">
Looks good!
</div>
</div> </div>
<div class="col-md-4 position-relative"> <div class="col-md-4 position-relative">
<label for="validationTooltip02" class="form-label">Last name</label> <label for="validationTooltip02" class="form-label">Last name</label>
<input type="text" class="form-control" id="validationTooltip02" value="Otto" required> <input type="text" class="form-control" id="validationTooltip02" value="Otto" required data-bs-valid="Looks good!">
<div class="valid-tooltip">
Looks good!
</div>
</div> </div>
<div class="col-md-4 position-relative"> <div class="col-md-4 position-relative">
<label for="validationTooltipUsername" class="form-label">Username</label> <label for="validationTooltipUsername" class="form-label">Username</label>
<div class="input-group has-validation"> <div class="input-group has-validation">
<span class="input-group-text" id="validationTooltipUsernamePrepend">@</span> <span class="input-group-text" id="validationTooltipUsernamePrepend">@</span>
<input type="text" class="form-control" id="validationTooltipUsername" aria-describedby="validationTooltipUsernamePrepend" required> <input type="text" class="form-control" id="validationTooltipUsername" aria-describedby="validationTooltipUsernamePrepend" required data-bs-invalid="Please choose a username.">
<div class="invalid-tooltip">
Please choose a unique and valid username.
</div>
</div> </div>
</div> </div>
<div class="col-md-6 position-relative"> <div class="col-md-6 position-relative">
<label for="validationTooltip03" class="form-label">City</label> <label for="validationTooltip03" class="form-label">City</label>
<input type="text" class="form-control" id="validationTooltip03" required> <input type="text" class="form-control" id="validationTooltip03" required data-bs-invalid="Please provide a valid city.">
<div class="invalid-tooltip">
Please provide a valid city.
</div>
</div> </div>
<div class="col-md-3 position-relative"> <div class="col-md-3 position-relative">
<label for="validationTooltip04" class="form-label">State</label> <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 selected disabled value="">Choose...</option>
<option>...</option> <option>...</option>
</select> </select>
<div class="invalid-tooltip">
Please select a valid state.
</div>
</div> </div>
<div class="col-md-3 position-relative"> <div class="col-md-3 position-relative">
<label for="validationTooltip05" class="form-label">Zip</label> <label for="validationTooltip05" class="form-label">Zip</label>
<input type="text" class="form-control" id="validationTooltip05" required> <input type="text" class="form-control" id="validationTooltip05" required data-bs-invalid="Please provide a valid zip.">
<div class="invalid-tooltip">
Please provide a valid zip.
</div>
</div> </div>
<div class="col-12"> <div class="col-12">
<button class="btn btn-primary" type="submit">Submit form</button> <button class="btn btn-primary" type="submit">Submit form</button>
<button class="btn btn-danger" type="reset">Reset form</button>
</div> </div>
</form> </form>
{{< /example >}} {{< /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)
})
})()