js: add live field validation (#75724)
gitea/wcs/pipeline/head This commit looks good Details

This commit is contained in:
Thomas Jund 2023-04-13 15:43:44 +02:00 committed by Frédéric Péters
parent a4a7b4c97f
commit 2d784e000a
2 changed files with 166 additions and 1 deletions

View File

@ -756,3 +756,168 @@ $(function() {
});
}
});
/*
* Live Field Validation
*/
const LiveValidation = (function(){
const excludedField = function (field) {
if (field.disabled ) return true
const excludedType = [ 'button', 'reset', 'submit' ]
if (excludedType.includes(field.type)) return true
return false
}
/*
* Check validity of field by HTML attributes
* cf JS constraint validation API
* return first error found
*/
const hasAttrError = function (field) {
const validityState = field.validity
if (validityState.valid) return
let errorType
for (const key in validityState) {
if (validityState[key]) {
errorType = key
break
}
}
return errorType
}
/*
* Check validity of field by request to server
*/
const hasServerError = async function (name, field, form, url) {
const response = await fetch( url+name, {
method: 'POST',
body: new FormData(form)
})
json = await response.json()
if (json.err !== 1) {
return
} else {
let errorType
for (const key in json) {
if (json[key] === true) {
errorType = key
break
}
}
return [errorType, json.msg]
}
}
class FieldLiveValidation {
constructor (widget, formDatas) {
this.widget = widget
this.name = widget.dataset.widgetNameForId
this.errorClass = "widget-with-error"
this.errorEl = this.setErrorEl(formDatas.errorTpl.content.children[0])
this.checkUrl = formDatas.checkUrl
this.hasError = false
this.init()
}
setErrorEl = function(errorTpl) {
const errorEl = document.importNode(errorTpl)
errorEl.id = errorEl.id.replace('fieldname', this.name)
return errorEl
}
async toggleStatus(field) {
if (excludedField(field)) return
const attrError = hasAttrError(field)
if(attrError) {
this.showError(field, attrError)
}
else {
const serverError = this.widget.dataset.supportsLiveValidation
? await hasServerError(this.name, field, field.form, this.checkUrl)
: false
if (serverError) {
const [error, overrideMsg] = serverError
this.showError(field, error, overrideMsg)
}
else {
this.removeError(field)
}
}
}
showError(field, error, overrideMsg) {
if(!this.hasError) {
this.widget.classList.add(this.errorClass)
this.widget.appendChild(this.errorEl)
field.setAttribute("aria-invalid", "true")
field.setAttribute("aria-describedby", this.errorEl.id)
}
const errorElMessage = document.getElementById(`error_${this.name}_${error}`).innerHTML
this.errorEl.innerHTML = errorElMessage
if(overrideMsg) {
const errorMessageContainer = this.errorEl.querySelector(`#error_${this.name}_${error}_message`)
errorMessageContainer.innerHTML = overrideMsg
}
this.hasError = error
}
removeError(field) {
if(!this.hasError) {
return
}
this.errorEl.remove()
field.setAttribute("aria-invalid", "false")
field.setAttribute("aria-describedby", this.errorEl.id)
this.widget.classList.remove(this.errorClass)
this.hasError = false
}
init() {
// Check if field is already on error
if (this.widget.classList.contains(this.errorClass)) {
this.hasError = true;
// Check if error element exist already
const existingErrorEl = document.getElementById(this.errorEl.id)
if (existingErrorEl)
this.errorEl = existingErrorEl;
}
// Events
this.widget.addEventListener('blur', (event) => {
this.toggleStatus(event.target)
}, true);
// If field has Error, check when it changes with debounce
let timeout;
this.widget.addEventListener('input', (event) => {
if (this.hasError) {
clearTimeout(timeout)
timeout = setTimeout(() => {
this.toggleStatus(event.target)
}, 500)
}
}, true);
}
}
return FieldLiveValidation
})()
document.addEventListener('DOMContentLoaded', function(){
const form = document.querySelector('form[data-live-validation-url]')
const formWidgets = form.querySelectorAll('.widget:not(.BlockWidget):not(.BlockSubWidget)')
let formDatas = {
errorTpl: document.getElementById('form_error_tpl'),
checkUrl: form.dataset.liveValidationUrl + '?field=',
}
formWidgets.forEach((widget) => {
new LiveValidation(widget, formDatas)
})
})

View File

@ -53,7 +53,7 @@
{% if widget.supports_live_validation and not widget.readonly %}
{% block widget-error-templates %}
{% for error_message in widget.get_error_messages %}
<template id="error_{{ widget.get_name_for_id }}_{{ error_message.camel_code }}"><p>{{ error_message.message }}</p></template>
<template id="error_{{ widget.get_name_for_id }}_{{ error_message.camel_code }}"><p id="error_{{ widget.get_name_for_id }}_{{ error_message.camel_code }}_message">{{ error_message.message }}</p></template>
{% endfor %}
{% endblock %}
{% endif %}