import { Logger } from '../../core/logger.js'; export class FormState { constructor(form) { this.form = form; this.pristineValues = new Map(); this.touchedFields = new Set(); this.dirtyFields = new Set(); this.init(); } static create(form) { return new FormState(form); } init() { // Store initial values this.captureInitialValues(); // Track field interactions this.bindEvents(); Logger.info(`[FormState] Initialized for form: ${this.form.id || 'unnamed'}`); } captureInitialValues() { const fields = this.form.querySelectorAll('input, textarea, select'); fields.forEach(field => { let value; switch (field.type) { case 'checkbox': case 'radio': value = field.checked; break; default: value = field.value; } this.pristineValues.set(field.name, value); }); } bindEvents() { const fields = this.form.querySelectorAll('input, textarea, select'); fields.forEach(field => { // Skip hidden fields for interaction tracking (they can't be touched/interacted with) if (field.type === 'hidden') return; // Mark field as touched on first interaction field.addEventListener('focus', () => { this.markAsTouched(field.name); }); // Track changes for dirty state field.addEventListener('input', () => { this.checkDirtyState(field); }); field.addEventListener('change', () => { this.checkDirtyState(field); }); }); } markAsTouched(fieldName) { this.touchedFields.add(fieldName); this.updateFormClasses(); } checkDirtyState(field) { const fieldName = field.name; const currentValue = this.getFieldValue(field); const pristineValue = this.pristineValues.get(fieldName); if (currentValue !== pristineValue) { this.dirtyFields.add(fieldName); } else { this.dirtyFields.delete(fieldName); } this.updateFormClasses(); } getFieldValue(field) { switch (field.type) { case 'checkbox': case 'radio': return field.checked; default: return field.value; } } updateFormClasses() { // Update form-level classes based on state if (this.isDirty()) { this.form.classList.add('form-dirty'); this.form.classList.remove('form-pristine'); } else { this.form.classList.add('form-pristine'); this.form.classList.remove('form-dirty'); } if (this.hasTouchedFields()) { this.form.classList.add('form-touched'); } else { this.form.classList.remove('form-touched'); } } // State check methods isPristine() { return this.dirtyFields.size === 0; } isDirty() { return this.dirtyFields.size > 0; } hasTouchedFields() { return this.touchedFields.size > 0; } isFieldTouched(fieldName) { return this.touchedFields.has(fieldName); } isFieldDirty(fieldName) { return this.dirtyFields.has(fieldName); } isFieldPristine(fieldName) { return !this.dirtyFields.has(fieldName); } // Get field states getFieldState(fieldName) { return { pristine: this.isFieldPristine(fieldName), dirty: this.isFieldDirty(fieldName), touched: this.isFieldTouched(fieldName), pristineValue: this.pristineValues.get(fieldName), currentValue: this.getCurrentFieldValue(fieldName) }; } getCurrentFieldValue(fieldName) { const field = this.form.querySelector(`[name="${fieldName}"]`); return field ? this.getFieldValue(field) : undefined; } // Form-level state getFormState() { return { pristine: this.isPristine(), dirty: this.isDirty(), touched: this.hasTouchedFields(), dirtyFields: Array.from(this.dirtyFields), touchedFields: Array.from(this.touchedFields), totalFields: this.pristineValues.size }; } // Reset methods reset() { this.touchedFields.clear(); this.dirtyFields.clear(); // Re-capture values after form reset setTimeout(() => { this.captureInitialValues(); this.updateFormClasses(); }, 0); Logger.info('[FormState] State reset'); } resetField(fieldName) { this.touchedFields.delete(fieldName); this.dirtyFields.delete(fieldName); // Reset field to pristine value const field = this.form.querySelector(`[name="${fieldName}"]`); const pristineValue = this.pristineValues.get(fieldName); if (field && pristineValue !== undefined) { switch (field.type) { case 'checkbox': case 'radio': field.checked = pristineValue; break; default: field.value = pristineValue; } } this.updateFormClasses(); Logger.info(`[FormState] Field "${fieldName}" reset to pristine state`); } // Event handling for external state changes triggerStateEvent(eventName, detail = {}) { const event = new CustomEvent(eventName, { detail: { ...detail, formState: this.getFormState() }, bubbles: true, cancelable: true }); this.form.dispatchEvent(event); } // Utility methods hasChanges() { return this.isDirty(); } getChangedFields() { const changes = {}; this.dirtyFields.forEach(fieldName => { changes[fieldName] = { pristineValue: this.pristineValues.get(fieldName), currentValue: this.getCurrentFieldValue(fieldName) }; }); return changes; } // Warning for unsaved changes enableUnsavedChangesWarning() { window.addEventListener('beforeunload', (e) => { if (this.isDirty()) { e.preventDefault(); e.returnValue = 'Sie haben ungespeicherte Änderungen. Möchten Sie die Seite wirklich verlassen?'; return e.returnValue; } }); } destroy() { this.pristineValues.clear(); this.touchedFields.clear(); this.dirtyFields.clear(); Logger.info('[FormState] Destroyed'); } }