- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
190 lines
6.3 KiB
JavaScript
190 lines
6.3 KiB
JavaScript
import { Logger } from '../../core/logger.js';
|
|
|
|
export class FormValidator {
|
|
constructor(form) {
|
|
this.form = form;
|
|
this.errors = new Map();
|
|
}
|
|
|
|
static create(form) {
|
|
return new FormValidator(form);
|
|
}
|
|
|
|
validate() {
|
|
this.errors.clear();
|
|
const fields = this.form.querySelectorAll('input, textarea, select');
|
|
|
|
for (const field of fields) {
|
|
if (field.type === 'hidden' || field.disabled) continue;
|
|
|
|
this.validateField(field);
|
|
}
|
|
|
|
return this.errors.size === 0;
|
|
}
|
|
|
|
validateField(field) {
|
|
const value = field.value;
|
|
const fieldName = field.name;
|
|
|
|
// HTML5 required attribute
|
|
if (field.hasAttribute('required') && (!value || value.trim() === '')) {
|
|
this.errors.set(fieldName, this.getErrorMessage(field, 'valueMissing') || `${this.getFieldLabel(field)} ist erforderlich`);
|
|
return;
|
|
}
|
|
|
|
// Skip further validation if field is empty and not required
|
|
if (!value || value.trim() === '') return;
|
|
|
|
// HTML5 type validation
|
|
if (field.type === 'email' && !this.isValidEmail(value)) {
|
|
this.errors.set(fieldName, this.getErrorMessage(field, 'typeMismatch') || 'Bitte geben Sie eine gültige E-Mail-Adresse ein');
|
|
return;
|
|
}
|
|
|
|
if (field.type === 'url' && !this.isValidUrl(value)) {
|
|
this.errors.set(fieldName, this.getErrorMessage(field, 'typeMismatch') || 'Bitte geben Sie eine gültige URL ein');
|
|
return;
|
|
}
|
|
|
|
// HTML5 minlength attribute
|
|
const minLength = field.getAttribute('minlength');
|
|
if (minLength && value.length < parseInt(minLength)) {
|
|
this.errors.set(fieldName, this.getErrorMessage(field, 'tooShort') || `Mindestens ${minLength} Zeichen erforderlich`);
|
|
return;
|
|
}
|
|
|
|
// HTML5 maxlength attribute (usually enforced by browser, but for safety)
|
|
const maxLength = field.getAttribute('maxlength');
|
|
if (maxLength && value.length > parseInt(maxLength)) {
|
|
this.errors.set(fieldName, this.getErrorMessage(field, 'tooLong') || `Maximal ${maxLength} Zeichen erlaubt`);
|
|
return;
|
|
}
|
|
|
|
// HTML5 min/max for number inputs
|
|
if (field.type === 'number') {
|
|
const min = field.getAttribute('min');
|
|
const max = field.getAttribute('max');
|
|
const numValue = parseFloat(value);
|
|
|
|
if (min && numValue < parseFloat(min)) {
|
|
this.errors.set(fieldName, this.getErrorMessage(field, 'rangeUnderflow') || `Wert muss mindestens ${min} sein`);
|
|
return;
|
|
}
|
|
|
|
if (max && numValue > parseFloat(max)) {
|
|
this.errors.set(fieldName, this.getErrorMessage(field, 'rangeOverflow') || `Wert darf maximal ${max} sein`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// HTML5 pattern attribute
|
|
const pattern = field.getAttribute('pattern');
|
|
if (pattern) {
|
|
const regex = new RegExp(pattern);
|
|
if (!regex.test(value)) {
|
|
this.errors.set(fieldName, this.getErrorMessage(field, 'patternMismatch') || 'Ungültiges Format');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Custom validation via data-validate attribute
|
|
const customValidation = field.getAttribute('data-validate');
|
|
if (customValidation) {
|
|
const result = this.runCustomValidation(customValidation, value, field);
|
|
if (!result.valid) {
|
|
this.errors.set(fieldName, result.message);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
runCustomValidation(validationType, value, field) {
|
|
switch (validationType) {
|
|
case 'phone':
|
|
const phoneRegex = /^[\+]?[0-9\s\-\(\)]{10,}$/;
|
|
return {
|
|
valid: phoneRegex.test(value),
|
|
message: 'Bitte geben Sie eine gültige Telefonnummer ein'
|
|
};
|
|
|
|
case 'postal-code-de':
|
|
const postalRegex = /^[0-9]{5}$/;
|
|
return {
|
|
valid: postalRegex.test(value),
|
|
message: 'Bitte geben Sie eine gültige Postleitzahl ein'
|
|
};
|
|
|
|
case 'no-html':
|
|
const hasHtml = /<[^>]*>/g.test(value);
|
|
return {
|
|
valid: !hasHtml,
|
|
message: 'HTML-Code ist nicht erlaubt'
|
|
};
|
|
|
|
default:
|
|
Logger.warn(`Unknown custom validation: ${validationType}`);
|
|
return { valid: true, message: '' };
|
|
}
|
|
}
|
|
|
|
isValidEmail(email) {
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
}
|
|
|
|
isValidUrl(url) {
|
|
try {
|
|
new URL(url);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
getFieldLabel(field) {
|
|
const label = this.form.querySelector(`label[for="${field.id}"]`) ||
|
|
this.form.querySelector(`label:has([name="${field.name}"])`);
|
|
|
|
return label ? label.textContent.trim().replace(':', '') : field.name;
|
|
}
|
|
|
|
getErrorMessage(field, validityType) {
|
|
// Check for custom error messages via data-error-* attributes
|
|
const customMessage = field.getAttribute(`data-error-${validityType}`) ||
|
|
field.getAttribute('data-error');
|
|
|
|
return customMessage;
|
|
}
|
|
|
|
getErrors() {
|
|
return Object.fromEntries(this.errors);
|
|
}
|
|
|
|
getFieldError(fieldName) {
|
|
return this.errors.get(fieldName);
|
|
}
|
|
|
|
hasErrors() {
|
|
return this.errors.size > 0;
|
|
}
|
|
|
|
clearErrors() {
|
|
this.errors.clear();
|
|
}
|
|
|
|
// Check HTML5 validity API as fallback
|
|
validateWithHTML5() {
|
|
const isValid = this.form.checkValidity();
|
|
|
|
if (!isValid) {
|
|
const fields = this.form.querySelectorAll('input, textarea, select');
|
|
for (const field of fields) {
|
|
if (!field.validity.valid) {
|
|
this.errors.set(field.name, field.validationMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
} |