1class AccessibleFormValidator{constructor(form){this.inputSelector='input:not([type="hidden"]),select,textarea';this.form=form;this.fields=[...form.querySelectorAll('[data-validators]')];this.summary=this._createSummary();this._onSummaryClick=this._onSummaryClick.bind(this);this.summary.addEventListener('click',this._onSummaryClick);this.translations={de:{errorMessageDefault:'Ungültige Eingabe',summaryTitle:'Bitte korrigieren Sie die folgenden Eingaben:',valueMissing:'Bitte füllen Sie das Pflichtfeld â{label}â aus.',typeMismatch_email:'Bitte geben Sie eine gültige EâMailâAdresse ein (z.â¯B. [email protected]).',typeMismatch_url:'Bitte geben Sie eine gültige URL ein (z.â¯B. https://beispiel.de).',patternMismatch:'Das Format von â{label}â ist ungültig. Erwartet: {patternHint}.',tooShort:'â{label}â ist zu kurz â mindestens {minLength} Zeichen (aktuell {length}).',tooLong:'â{label}â ist zu lang â höchstens {maxLength} Zeichen (aktuell {length}).',rangeUnderflow:'Der Wert von â{label}â muss mindestens {min} betragen.',rangeOverflow:'Der Wert von â{label}â darf höchstens {max} betragen.',stepMismatch:'Bitte verwenden Sie für â{label}â eine Schrittweite von {step}.',badInput:'Bitte geben Sie in â{label}â nur numerische Werte ein.',customError:'Ungültige Eingabe.',integerInvalid:'â{label}â darf nur ganze Zahlen enthalten.',floatInvalid:'â{label}â muss eine Zahl sein (Dezimaltrennzeichen â.â oder â,â).',numberRangeInvalid:'Der Wert von â{label}â muss zwischen {min} und {max} liegen.',dateInvalid:'â{label}â muss ein gültiges Datum im Format JJJJâMMâTT sein.',dateRangeInvalid:'Das Datum in â{label}â muss zwischen {min} und {max} liegen.',alphanumericInvalid:'â{label}â darf nur Buchstaben und Ziffern enthalten (keine Leerâ oder Sonderzeichen).'},de_du:{errorMessageDefault:'Ungültige Eingabe',summaryTitle:'Bitte korrigiere die folgenden Eingaben:',valueMissing:'Bitte fülle das Pflichtfeld â{label}â aus.',typeMismatch_email:'Bitte gib eine gültige EâMailâAdresse ein (z.â¯B. [email protected]).',typeMismatch_url:'Bitte gib eine gültige URL ein (z.â¯B. https://beispiel.de).',patternMismatch:'Das Format von â{label}â ist ungültig. Erwartet: {patternHint}.',tooShort:'â{label}â ist zu kurz â mindestens {minLength} Zeichen (aktuell {length}).',tooLong:'â{label}â ist zu lang â maximal {maxLength} Zeichen (aktuell {length}).',rangeUnderflow:'Der Wert von â{label}â muss mindestens {min} sein.',rangeOverflow:'Der Wert von â{label}â darf höchstens {max} sein.',stepMismatch:'Bitte verwende für â{label}â einen zulässigen Schritt von {step}.',badInput:'Bitte gib in â{label}â nur numerische Werte ein.',customError:'Ungültige Eingabe.',integerInvalid:'â{label}â darf nur ganze Zahlen enthalten.',floatInvalid:'â{label}â muss eine Zahl sein (Punkt oder Komma als Dezimaltrennzeichen).',numberRangeInvalid:'Der Wert von â{label}â muss zwischen {min} und {max} liegen.',dateInvalid:'â{label}â muss ein gültiges Datum im Format YYYYâMMâDD sein.',dateRangeInvalid:'Das Datum in â{label}â muss zwischen {min} und {max} liegen.',alphanumericInvalid:'â{label}â darf nur Buchstaben und Ziffern enthalten (keine Leerâ oder Sonderzeichen).'},en:{errorMessageDefault:'Invalid input',summaryTitle:'Please correct the following fields:',valueMissing:'Please fill in the required field â{label}â.',typeMismatch_email:'Enter a valid eâmail address (e.g. [email protected]).',typeMismatch_url:'Enter a valid URL (e.g. https://example.com).',patternMismatch:'The format of â{label}â is invalid. Expected: {patternHint}.',tooShort:'â{label}â is too short â minimum {minLength} characters (currently {length}).',tooLong:'â{label}â is too long â maximum {maxLength} characters (currently {length}).',rangeUnderflow:'The value of â{label}â must be at least {min}.',rangeOverflow:'The value of â{label}â must not exceed {max}.',stepMismatch:'Please use a valid step of {step} for â{label}â.',badInput:'Please enter numeric values only in â{label}â.',customError:'Invalid input.',integerInvalid:'â{label}â must contain whole numbers only.',floatInvalid:'â{label}â has to be a number (decimal separator â.â or â,â).',numberRangeInvalid:'The value of â{label}â must be between {min} and {max}.',dateInvalid:'â{label}â must be a valid date in YYYYâMMâDD format.',dateRangeInvalid:'The date in â{label}â must be between {min} and {max}.',alphanumericInvalid:'â{label}â may contain letters and digits only (no spaces or special chars).'},fr:{errorMessageDefault:'Entrée non valide',summaryTitle:'Veuillez corriger les informations suivantes :',valueMissing:'Veuillez remplir le champ obligatoire « {label} ».',typeMismatch_email:'Veuillez saisir une adresse e-mail valide (par exemple, [email protected]).',typeMismatch_url:'
1Veuillez saisir une URL valide (par exemple https://beispiel.de).',patternMismatch:'Le format de « {label} » n\'est pas valide. Attendu : {patternHint}.',tooShort:'« {label} » est trop court â au moins {minLength} caractères (actuellement {length}).',tooLong:'« {label} » est trop long â {maxLength} caractères maximum (actuellement {length}).',rangeUnderflow:'La valeur de « {label} » doit être au moins égale à {min}.',rangeOverflow:'La valeur de « {label} » ne doit pas dépasser {max}.',stepMismatch:'Veuillez utiliser un incrément de {step} pour « {label} ».',badInput:'Veuillez saisir uniquement des valeurs numériques dans « {label} ».',customError:'Entrée non valide.',integerInvalid:'« {label} » ne peut contenir que des nombres entiers.',floatInvalid:'« {label} » doit être un nombre (séparateur décimal « . » ou « , »).',numberRangeInvalid:'La valeur de « {label} » doit être comprise entre {min} et {max}.',dateInvalid:'« {label} » doit être une date valide au format AAAA-MM-JJ.',dateRangeInvalid:'La date dans « {label} » doit être comprise entre {min} et {max}.',alphanumericInvalid:'« {label} » ne doit contenir que des lettres et des chiffres (pas d\'espaces ni de caractères spéciaux).'},ru:{errorMessageDefault:'ÐевеÑнÑй ввод',summaryTitle:'ÐожалÑйÑÑа, иÑпÑавÑÑе ÑледÑÑÑие даннÑе:',valueMissing:'ÐожалÑйÑÑа, заполниÑе обÑзаÑелÑное поле «{label}».',typeMismatch_email:'ÐведиÑе дейÑÑвиÑелÑнÑй адÑÐµÑ ÑлекÑÑонной поÑÑÑ (напÑимеÑ, [email protected]).',typeMismatch_url:'ÐведиÑе дейÑÑвиÑелÑнÑй URL-адÑÐµÑ (напÑимеÑ, https://beispiel.de).',patternMismatch:'ФоÑÐ¼Ð°Ñ Â«{label}» недейÑÑвиÑелен. ÐжидаеÑÑÑ: {patternHint}.',tooShort:'«{label}» ÑлиÑком коÑоÑкий â не менее {minLength} Ñимволов (в наÑÑоÑÑее вÑÐµÐ¼Ñ {length}).',tooLong:'«{label}» ÑлиÑком длиннÑй â не более {maxLength} Ñимволов (в наÑÑоÑÑее вÑÐµÐ¼Ñ {length}).',rangeUnderflow:'ÐнаÑение «{label}» должно бÑÑÑ Ð½Ðµ менее {min}.',rangeOverflow:'ÐнаÑение «{label}» не должно пÑевÑÑаÑÑ {max}.',stepMismatch:'ÐожалÑйÑÑа, иÑполÑзÑйÑе Ð´Ð»Ñ Â«{label}» Ñаг {step}.',badInput:'ÐожалÑйÑÑа, введиÑе в поле «{label}» ÑолÑко ÑиÑловÑе знаÑениÑ.',customError:'ÐевеÑнÑй ввод.',integerInvalid:'«{label}» Ð¼Ð¾Ð¶ÐµÑ ÑодеÑжаÑÑ ÑолÑко ÑелÑе ÑиÑла.',floatInvalid:'«{label}» должен бÑÑÑ ÑиÑлом (деÑÑÑиÑнÑй ÑазделиÑÐµÐ»Ñ Â«.» или «,»).',numberRangeInvalid:'ÐнаÑение «{label}» должно бÑÑÑ Ð² диапазоне Ð¾Ñ {min} до {max}.',dateInvalid:'«{label}» должен бÑÑÑ Ð´ÐµÐ¹ÑÑвиÑелÑной даÑой в ÑоÑмаÑе ÐÐÐÐ-ÐÐ-ÐÐ.',dateRangeInvalid:'ÐаÑа в «{label}» должна наÑ
одиÑÑÑÑ Ð² диапазоне Ð¾Ñ {min} до {max}.',alphanumericInvalid:'«{label}» Ð¼Ð¾Ð¶ÐµÑ ÑодеÑжаÑÑ ÑолÑко бÑÐºÐ²Ñ Ð¸ ÑиÑÑÑ (без пÑобелов и ÑпеÑиалÑнÑÑ
Ñимволов).'},pt:{errorMessageDefault:'Entrada inválida',summaryTitle:'Por favor, corrija os seguintes dados:',valueMissing:'Preencha o campo obrigatório «{label}».',typeMismatch_email:'Insira um endereço de e-mail válido (por exemplo, [email protected]).',typeMismatch_url:'Insira um URL válido (por exemplo, https://beispiel.de).',patternMismatch:'O formato de «{label}» não é válido. Esperado: {patternHint}.',tooShort:'«{label}» é demasiado curto â pelo menos {minLength} caracteres (atualmente {length}).',tooLong:'«{label}» é demasiado longo â no máximo {maxLength} caracteres (atualmente {length}).',rangeUnderflow:'O valor de «{label}» deve ser pelo menos {min}.',rangeOverflow:'O valor de «{label}» não pode ser superior a {max}.',stepMismatch:'Bitte verwenden Sie für â{label}â eine Schrittweite von {step}.',badInput:'Por favor, insira apenas valores numéricos em «{label}».',customError:'Entrada inválida.',integerInvalid:'«{label}» só pode conter números inteiros.',floatInvalid:'«{label}» deve ser um número (separador decimal «.» ou «,»).',numberRangeInvalid:'O valor de «{label}» deve estar entre {min} e {max}.',dateInvalid:'«{label}» deve ser uma data válida no formato AAAA-MM-DD.',dateRangeInvalid:'A data em «{label}» deve estar entre {min} e {max}.',alphanumericInvalid:'«{label}» só pode conter letras e números (sem espaços ou caracteres especiais).'},es:{errorMessageDefault:'Entrada no válida',summaryTitle:'Por favor, corrija los siguientes datos:',valueMissing:'Por favor, rellene el c
1ampo obligatorio «{label}».',typeMismatch_email:'Introduzca una dirección de correo electrónico válida (por ejemplo, [email protected]).',typeMismatch_url:'Introduzca una URL válida (por ejemplo, https://beispiel.de).',patternMismatch:'El formato de «{label}» no es válido. Se espera: {patternHint}.',tooShort:'«{label}» es demasiado corto: debe tener al menos {minLength} caracteres (actualmente {length}).',tooLong:'«{label}» es demasiado largo: como máximo {maxLength} caracteres (actualmente {length}).',rangeUnderflow:'El valor de «{label}» debe ser como mÃnimo {min}.',rangeOverflow:'El valor de «{label}» no puede ser superior a {max}.',stepMismatch:'Por favor, utilice un incremento de {step} para «{label}».',badInput:'Por favor, introduzca solo valores numéricos en «{label}».',customError:'Entrada no válida.',integerInvalid:'«{label}» solo puede contener números enteros.',floatInvalid:'«{label}» debe ser un número (separador decimal «.» o «,»).',numberRangeInvalid:'El valor de «{label}» debe estar comprendido entre {min} y {max}.',dateInvalid:'«{label}» debe ser una fecha válida en formato AAAA-MM-DD.',dateRangeInvalid:'La fecha en «{label}» debe estar comprendida entre {min} y {max}.',alphanumericInvalid:'«{label}» solo puede contener letras y números (sin espacios ni caracteres especiales).'},};this._bindEvents();this._processErrorsFromServer()} 2validateAll(){const invalid=this.fields.map(group=>this._validateGroup(group,!1)).filter(Boolean);this._renderSummary(invalid);return invalid.length===0} 3_bindEvents(){this.fields.forEach(group=>{const inputs=group.querySelectorAll(this.inputSelector);inputs.forEach(el=>{const validate=()=>this._validateGroup(group);if(el.tagName==='SELECT'){el.addEventListener('change',validate);return} 4switch(el.type){case 'radio':case 'checkbox':case 'file':case 'color':case 'date':case 'time':case 'datetime-local':case 'month':case 'week':el.addEventListener('change',validate);break;case 'range':el.addEventListener('input',validate);el.addEventListener('change',validate);break;default:el.addEventListener('blur',validate);el.addEventListener('change',validate)}})});this.form.addEventListener('submit',e=>
4{if(!this.validateAll())e.preventDefault();});const submitBtn=this.form.querySelector('button[type="submit"], input[type="submit"]');if(submitBtn){submitBtn.addEventListener('click',e=>{if(!this.validateAll())e.preventDefault();})}} 5_createSummary(){const s=document.createElement('div');s.className='visually-hidden error-summary';s.setAttribute('aria-hidden','true');s.id=`${this.form.id || 'form'}-error-summary`;s.tabIndex=-1;this.form.prepend(s);return s} 6_validateGroup(group,errorIsAlert=!0){const input=group.querySelector(this.inputSelector);if(!input)return null;const errorId=`${input.id}-error`;let msg='';let valid=!0;if(!input.checkValidity()){valid=!1;msg=this._mapValidityToMessage(input)}else{const customValidation=this._checkCustomValidators(group);if(!customValidation.valid){valid=!1;msg=customValidation.message}} 7let msgEl=group.querySelector(`#${errorId}`);if(!valid){if(!msgEl){msgEl=this._buildErrorNode(errorId);group.appendChild(msgEl)} 8if(errorIsAlert){if(msgEl.getAttribute('role')!=='alert'){msgEl.setAttribute('role','alert')}}else{msgEl.removeAttribute('role')} 9msgEl.textContent=msg;msgEl.hidden=!1;if(input.type==='radio'){const radios=group.querySelectorAll('input[type="radio"]');radios.forEach(radio=>{radio.setAttribute('aria-invalid','true');this._addDescribedBy(radio,errorId)});return{input:radios[0],errorMessage:msg}}else{input.setAttribute('aria-invalid','true');input.classList.add('error');this._addDescribedBy(input,errorId);return{input,errorMessage:msg}}} 10if(msgEl)msgEl.hidden=!0;if(input.type==='radio'){const radios=group.querySelectorAll('input[type="radio"]');radios.forEach(radio=>{radio.removeAttribute('aria-invalid');this._removeDescribedBy(radio,errorId)})}else{input.removeAttribute('aria-invalid');input.classList.remove('error');this._removeDescribedBy(input,errorId)} 11return null} 12_getLabel(el){const lblEl=el.form.querySelector(`label[for="${el.id}"]`);if(!lblEl)return el.name||el.id||'Feld';let text=lblEl.textContent.replace(/[*â±ï¼]+/g,'').replace(/\(\s*\)/g,'').trim();return text.length?text:(el.name||el.id||'Feld')} 13_interpolate(str,obj={}){return str.replace(/\{(\w+)}/g,(_,k)=>(k in obj?obj[k]:''))} 14_mapValidityToMessage(el){const v=el.validity;const t=key=>this._translate(key);const lbl=this._getLabel(el);if(v.valueMissing) 15return this._interpolate(t('valueMissing'),{label:lbl});if(v.typeMismatch){if(el.type==='email') 16return this._interpolate(t('typeMismatch_email'),{label:lbl});if(el.type==='url') 17return this._interpolate(t('typeMismatch_url'),{label:lbl});return t('errorMessageDefault')} 18if(v.patternMismatch){const patternHint=el.getAttribute('title')||el.dataset.patternHint||el.getAttribute('pattern')||'';return this._interpolate(t('patternMismatch'),{label:lbl,patternHint})} 19if(v.tooShort) 20return this._interpolate(t('tooShort'),{label:lbl,minLength:el.minLength,length:el.value.length});if(v.tooLong) 21return this._interpolate(t('tooLong'),{label:lbl,maxLength:el.maxLength,length:el.value.length});if(v.rangeUnderflow) 22return this._interpolate(t('rangeUnderflow'),{label:lbl,min:el.min});if(v.rangeOverflow) 23return this._interpolate(t('rangeOverflow'),{label:lbl,max:el.max});if(v.stepMismatch) 24return this._interpolate(t('stepMismatch'),{label:lbl,step:el.step});if(v.badInput) 25return this._interpolate(t('badInput'),{label:lbl});if(v.customError) 26return el.validationMessage||t('customError');return null} 27_checkCustomValidators(group){const input=group.querySelector(this.inputSelector);const raw=input.value;const value=raw.trim();const vReq=(group.dataset.validators||'').split(',').map(v=>v.trim());const lbl=this._getLabel(input);const t=key=>this._translate(key);const msg=(key,obj={})=>this._interpolate(t(key),{label:lbl,...obj});const num=v=>parseFloat(v.replace(',','.'));const validators={IntegerValidator:{test:v=>/^-?\d+$/.test(v),err:()=>msg('integerInvalid')},FloatValidator:{test:v=>/^-?\d+([.,]\d+)?$/.test(v),err:()=>msg('floatInvalid')},NumberRangeValidator:{test:v=>{const n=num(v);if(Number.isNaN(n))return!1;const min=num(input.getAttribute('min')||input.dataset.min||-Infinity);const max=num(input.getAttribute('max')||input.dataset.max||Infinity);return n>=min&&n<=max},err:()=>msg('numberRangeInvalid',{min:input.getAttribute('min')||input.dataset.min,max:input.getAttribute('max')||input.dataset.max})},DateValidator:{test:v=>{if(input.type==='date')return input.checkValidity();return/^\d{4}-\d{2}-\d{2}$/.test(v)&&!Number.isNaN(Date.parse(v))},err:()=>msg('dateInvalid')},DateRangeValidator:{test:v=>{const okDate=validators.DateValidator.test(v);
27if(!okDate)return!1;const ts=Date.parse(v);const minTs=input.getAttribute('min')?Date.parse(input.getAttribute('min')):-Infinity;const maxTs=input.getAttribute('max')?Date.parse(input.getAttribute('max')):Infinity;return ts>=minTs&&ts<=maxTs},err:()=>msg('dateRangeInvalid',{min:input.getAttribute('min')||'â',max:input.getAttribute('max')||'â'})},AlphanumericValidator:{test:v=>v===''||/^[0-9a-zA-Z]+$/.test(v),err:()=>msg('alphanumericInvalid')},NotEmptyValidator:{test:()=>!0},StringLengthValidator:{test:()=>!0},EmailAddressValidator:{test:()=>!0},RegularExpressionValidator:{test:()=>!0},UrlValidator:{test:()=>!0}};if(value!==''){for(const name of vReq){const rule=validators[name];if(rule&&!rule.test(value)){return{valid:!1,message:rule.err?rule.err():t('errorMessageDefault')}}}} 28return{valid:!0,message:null}} 29_buildErrorNode(errorId){let errorNode=document.createElement('p');errorNode.id=errorId;errorNode.className='error help-block';errorNode.setAttribute('role','alert');errorNode.hidden=!0;return errorNode} 30_addDescribedBy(el,id){const ids=new Set((el.getAttribute('aria-describedby')||'').split(/\s+/).filter(Boolean));ids.add(id);el.setAttribute('aria-describedby',[...ids].join(' '))} 31_removeDescribedBy(el,id){const ids=(el.getAttribute('aria-describedby')||'').split(/\s+/).filter(i=>i&&i!==id);ids.length?el.setAttribute('aria-describedby',ids.join(' ')):el.removeAttribute('aria-describedby')} 32_renderSummary(invalid){if(!invalid.length){this.summary.classList.add('visually-hidden');this.summary.setAttribute('aria-hidden','true');this.summary.innerHTML='';return} 33const items=invalid.map(({input,errorMessage})=>`<li><a data-summary-link href="#${input.id}">${errorMessage}</a></li>`).join('') 34this.summary.innerHTML=`<h2 class="h5 mb-2">${this._translate('summaryTitle')}</h2><ul>${items}</ul>`;this.summary.classList.remove('visually-hidden');this.summary.setAttribute('aria-hidden','false');this.summary.focus()} 35_processErrorsFromServer(){let errorsFromServer=[];this.fields.forEach(group=>{if(group.classList.contains('has-error')){const input=group.querySelector(this.inputSelector);if(!input)return;let errorNode=group.querySelector('.error.help-block');if(!errorNode){errorNode=this._buildErrorNode(`${input.id}-error`);errorNode.textContent=input.dataset.errorMessage||'Ungültige Eingabe';group.appendChild(errorNode)}else{errorNode.id=`${input.id}-error`} 36errorNode.hidden=!1;if(input.type==='radio'){const radios=group.querySelectorAll('input[type="radio"]');radios.forEach(radio=>{radio.setAttribute('aria-invalid','true');this._addDescribedBy(radio,errorNode.id)})}else{input.setAttribute('aria-invalid','true');this._addDescribedBy(input,errorNode.id)} 37errorsFromServer.push({input,errorMessage:errorNode.textContent})}});if(errorsFromServer.length>0){this._renderSummary(errorsFromServer)}} 38_translate(key){const doc=document.documentElement;const baseLang=doc.lang?doc.lang.split('-')[0]:'en';let tone=(doc.dataset.tone||'').toLowerCase();if(baseLang==='de'){if(!tone)tone='sie';if(tone==='informal')tone='du';if(tone==='formal')tone='sie'}else{tone=''} 39const langKey=tone&&this.translations[`${baseLang}_${tone}`]?`${baseLang}_${tone}`:baseLang;if(this.translations[langKey]&&this.translations[langKey][key]){return this.translations[langKey][key]} 40if(this.translations[baseLang]&&this.translations[baseLang][key]){return this.translations[baseLang][key]} 41return key} 42_onSummaryClick(e){const link=e.target.closest('[data-summary-link]');if(!link)return;e.preventDefault();const id=link.getAttribute('href').slice(1);const el=document.getElementById(id);if(!el)return;el.focus()}} 43document.addEventListener('DOMContentLoaded',()=>{document.querySelectorAll('form').forEach(f=>new AccessibleFormValidator(f))})
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.