- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
256 lines
7.0 KiB
JavaScript
256 lines
7.0 KiB
JavaScript
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');
|
|
}
|
|
} |