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:
256
resources/js/modules/form-handling/FormState.js
Normal file
256
resources/js/modules/form-handling/FormState.js
Normal file
@@ -0,0 +1,256 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user