mirror of
https://github.com/overleaf/overleaf.git
synced 2025-03-01 04:53:03 +00:00
Merge pull request #5086 from overleaf/jpa-rework-de-ng-validation
[web] input-validator: rework of content and behavior GitOrigin-RevId: 276c23c651d3954d7e82415b5315907600c8e0e1
This commit is contained in:
parent
6cece12369
commit
895c93d8f2
2 changed files with 59 additions and 60 deletions
|
@ -1,6 +1,7 @@
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { FetchError, postJSON } from '../../infrastructure/fetch-json'
|
import { FetchError, postJSON } from '../../infrastructure/fetch-json'
|
||||||
import { validateCaptchaV2 } from './captcha'
|
import { validateCaptchaV2 } from './captcha'
|
||||||
|
import inputValidator from './input-validator'
|
||||||
|
|
||||||
// Form helper(s) to handle:
|
// Form helper(s) to handle:
|
||||||
// - Attaching to the relevant form elements
|
// - Attaching to the relevant form elements
|
||||||
|
@ -145,6 +146,15 @@ export function hydrateForm(el) {
|
||||||
formSubmitHelper(el)
|
formSubmitHelper(el)
|
||||||
formInflightHelper(el)
|
formInflightHelper(el)
|
||||||
formSentHelper(el)
|
formSentHelper(el)
|
||||||
|
|
||||||
|
el.querySelectorAll('input').forEach(inputEl => {
|
||||||
|
if (
|
||||||
|
inputEl.willValidate &&
|
||||||
|
!inputEl.hasAttribute('data-ol-no-custom-form-validation-messages')
|
||||||
|
) {
|
||||||
|
inputValidator(inputEl)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll(`[data-ol-form]`).forEach(form => hydrateForm(form))
|
document.querySelectorAll(`[data-ol-form]`).forEach(form => hydrateForm(form))
|
||||||
|
|
|
@ -1,72 +1,61 @@
|
||||||
export default function inputValidator(options) {
|
export default function inputValidator(inputEl) {
|
||||||
const { selector } = options
|
const messageEl = document.createElement('span')
|
||||||
|
messageEl.className =
|
||||||
const inputEl = document.querySelector(selector)
|
inputEl.getAttribute('data-ol-validation-message-classes') ||
|
||||||
|
'small text-danger'
|
||||||
inputEl.addEventListener('input', markDirty)
|
messageEl.hidden = true
|
||||||
inputEl.addEventListener('change', markDirty)
|
|
||||||
inputEl.addEventListener('blur', insertInvalidMessage)
|
|
||||||
|
|
||||||
// Mark an input as "dirty": the user has typed something in at some point
|
|
||||||
function markDirty() {
|
|
||||||
// Note: this is used for the input styling as well as checks when inserting invalid
|
|
||||||
// message below
|
|
||||||
inputEl.dataset.olDirty = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertInvalidMessage() {
|
|
||||||
if (!inputEl.validity.valid) {
|
|
||||||
// Already have a invalid message, don't insert another
|
|
||||||
if (inputEl._invalid_message_el) return
|
|
||||||
|
|
||||||
// Only show the message if the input is "dirty"
|
|
||||||
if (!inputEl.dataset.olDirty) return
|
|
||||||
|
|
||||||
const messageEl = createMessageEl({
|
|
||||||
message: getMessage(inputEl),
|
|
||||||
...options,
|
|
||||||
})
|
|
||||||
inputEl.insertAdjacentElement('afterend', messageEl)
|
inputEl.insertAdjacentElement('afterend', messageEl)
|
||||||
|
|
||||||
// Add a reference so we can remove the element when the input becomes valid
|
// Hide messages until the user leaves the input field or submits the form.
|
||||||
inputEl._invalid_message_el = messageEl
|
let canDisplayErrorMessages = false
|
||||||
|
|
||||||
|
// Handle all kinds of inputs.
|
||||||
|
inputEl.addEventListener('input', handleUpdate)
|
||||||
|
inputEl.addEventListener('change', handleUpdate)
|
||||||
|
|
||||||
|
// The user has left the input field.
|
||||||
|
inputEl.addEventListener('blur', displayValidationMessages)
|
||||||
|
|
||||||
|
// The user has submitted the form and the current field has errors.
|
||||||
|
inputEl.addEventListener('invalid', e => {
|
||||||
|
// Block the display of browser error messages.
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
// Force the display of messages.
|
||||||
|
inputEl.setAttribute('data-ol-dirty', '')
|
||||||
|
|
||||||
|
displayValidationMessages()
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleUpdate() {
|
||||||
|
// Mark an input as "dirty": the user has typed something in at some point
|
||||||
|
inputEl.setAttribute('data-ol-dirty', '')
|
||||||
|
|
||||||
|
// Provide live updates to content sensitive error message like this:
|
||||||
|
// Please include an '@' in the email address. 'foo' is missing an '@'.
|
||||||
|
// We should not leave a stale message as the user types.
|
||||||
|
updateValidationMessageContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayValidationMessages() {
|
||||||
|
// Display all the error messages and highlight fields with red border.
|
||||||
|
canDisplayErrorMessages = true
|
||||||
|
|
||||||
|
updateValidationMessageContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateValidationMessageContent() {
|
||||||
|
if (!canDisplayErrorMessages) return
|
||||||
|
if (!inputEl.hasAttribute('data-ol-dirty')) return
|
||||||
|
|
||||||
|
if (inputEl.validity.valid) {
|
||||||
|
messageEl.hidden = true
|
||||||
|
|
||||||
|
// Require another blur before displaying errors again.
|
||||||
|
canDisplayErrorMessages = false
|
||||||
} else {
|
} else {
|
||||||
if (!inputEl._invalid_message_el) return
|
messageEl.textContent = inputEl.validationMessage
|
||||||
|
messageEl.hidden = false
|
||||||
// Remove the message element
|
|
||||||
inputEl._invalid_message_el.remove()
|
|
||||||
// Clean up the reference
|
|
||||||
delete inputEl._invalid_message_el
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanUp() {
|
|
||||||
inputEl.removeEventListener('input change', markDirty)
|
|
||||||
inputEl.removeEventListener('blue', insertInvalidMessage)
|
|
||||||
delete inputEl._invalid_message_el
|
|
||||||
delete inputEl.dataset.olDirty
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleanUp
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMessageEl({ message, messageClasses = [] }) {
|
|
||||||
const el = document.createElement('span')
|
|
||||||
// From what I understand, using textContent means that we're safe from XSS
|
|
||||||
el.textContent = message
|
|
||||||
el.classList.add(...messageClasses)
|
|
||||||
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMessage(el) {
|
|
||||||
// Could be extended to all ValidityState properties: https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
|
|
||||||
const { valueMissing, typeMismatch } = el.validity
|
|
||||||
if (valueMissing) {
|
|
||||||
return el.dataset.olInvalidValueMissing || 'Missing required value'
|
|
||||||
} else if (typeMismatch) {
|
|
||||||
return el.dataset.olInvalidTypeMismatch || 'Invalid type' // FIXME: Bad default
|
|
||||||
} else {
|
|
||||||
return 'Invalid'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue