Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
190
resources/js/modules/form-handling/FormValidator.js
Normal file
190
resources/js/modules/form-handling/FormValidator.js
Normal file
@@ -0,0 +1,190 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user