fix: JSON-Editor Round-Trip für verschachtelte Objekte/Arrays
- Rendering: innere Felder werden innerhalb der Objekt/Array-Container gerendert - Speichern: data-key-Pfade mit [index] werden korrekt zu Arrays rekonstruiert Fixes beide Symptome aus dem uncommitted Refactor.
This commit is contained in:
parent
3f35cff338
commit
79c3bb3a3e
1 changed files with 121 additions and 43 deletions
164
web/index.html
164
web/index.html
|
|
@ -597,45 +597,103 @@ $ python web/serve.py</div>
|
|||
const formDiv = document.createElement('div');
|
||||
formDiv.style.cssText = 'display: flex; flex-direction: column; gap: 16px; padding: 8px; background: var(--bg-card); border-radius: 4px; margin: 0; min-height: 300px; overflow-y: auto;';
|
||||
|
||||
// Rekursive Funktion zum Erstellen von Eingabefeldern für alle Properties
|
||||
function buildJsonForm(data, prefix = '', level = 0) {
|
||||
if (typeof data !== 'object' || data === null) return;
|
||||
|
||||
Object.keys(data).forEach(key => {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
const value = data[key];
|
||||
const isObject = typeof value === 'object' && value !== null;
|
||||
const isObject = typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
const isArray = Array.isArray(value);
|
||||
|
||||
if (isObject && !isArray) {
|
||||
// Für Objekte: Rekursiv alle inneren Properties anzeigen
|
||||
if (isObject) {
|
||||
const fieldContainer = document.createElement('div');
|
||||
fieldContainer.style.cssText = 'background: #1a1a1a; padding: 12px; border-radius: 4px; border-left: 3px solid #4CAF50;';
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.htmlFor = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
|
||||
label.textContent = fullKey + ' (Objekt) ♦';
|
||||
label.style.cssText = 'font-weight: 600; color: var(--text-primary); margin-bottom: 4px; font-size: 14px; margin-top: 8px;';
|
||||
formDiv.appendChild(label);
|
||||
label.textContent = fullKey + ' (Objekt)';
|
||||
label.style.cssText = 'font-weight: 600; color: #4CAF50; margin-bottom: 8px; display: block; font-size: 14px;';
|
||||
fieldContainer.appendChild(label);
|
||||
|
||||
const input = document.createElement('textarea');
|
||||
input.value = JSON.stringify(value, null, 2);
|
||||
input.dataset.key = fullKey;
|
||||
input.rows = Object.keys(value).length > 10 ? 10 : Object.keys(value).length;
|
||||
input.style.cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #cccccc; border-radius: 4px; font-family: var(--mono); font-size: 13px; resize: vertical;';
|
||||
formDiv.appendChild(input);
|
||||
// Rekursiv innere Properties in den Container einfügen
|
||||
const innerContainer = document.createElement('div');
|
||||
innerContainer.style.cssText = 'padding-left: 12px; margin-top: 4px;';
|
||||
buildJsonForm(value, fullKey, level + 1);
|
||||
const existingChildren = Array.from(innerContainer.children);
|
||||
while (innerContainer.firstChild) innerContainer.removeChild(innerContainer.firstChild);
|
||||
existingChildren.forEach(ch => innerContainer.appendChild(ch));
|
||||
fieldContainer.appendChild(innerContainer);
|
||||
|
||||
formDiv.appendChild(fieldContainer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Für Arrays: Rekursiv jedes Element als eigenes Feld anzeigen
|
||||
if (isArray) {
|
||||
const label = document.createElement('label');
|
||||
label.htmlFor = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
|
||||
label.textContent = fullKey + ' (Array) ♦';
|
||||
label.style.cssText = 'font-weight: 600; color: var(--text-primary); margin-bottom: 4px; font-size: 14px; margin-top: 8px;';
|
||||
formDiv.appendChild(label);
|
||||
const arrayContainer = document.createElement('div');
|
||||
arrayContainer.style.cssText = 'background: #1a1a1a; padding: 12px; border-radius: 4px; border-left: 3px solid #2196F3;';
|
||||
|
||||
const input = document.createElement('textarea');
|
||||
input.value = JSON.stringify(value);
|
||||
input.dataset.key = fullKey;
|
||||
input.rows = Math.min(value.length, 5);
|
||||
input.style.cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #cccccc; border-radius: 4px; font-family: var(--mono); font-size: 13px; resize: vertical;';
|
||||
formDiv.appendChild(input);
|
||||
const arrayLabel = document.createElement('label');
|
||||
arrayLabel.htmlFor = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
|
||||
arrayLabel.textContent = fullKey + ' (Array) - ' + value.length + ' Elemente';
|
||||
arrayLabel.style.cssText = 'font-weight: 600; color: #2196F3; margin-bottom: 8px; display: block; font-size: 14px;';
|
||||
arrayContainer.appendChild(arrayLabel);
|
||||
|
||||
// Für jedes Array-Element ein separates Eingabefeld
|
||||
const arrayItemsContainer = document.createElement('div');
|
||||
arrayItemsContainer.style.cssText = 'margin-top: 4px; padding-left: 12px;';
|
||||
|
||||
value.forEach((item, index) => {
|
||||
const itemKey = `${fullKey}[${index}]`;
|
||||
const itemContainer = document.createElement('div');
|
||||
itemContainer.style.cssText = 'background: #2a2a2a; padding: 8px; margin: 4px 0; border-radius: 3px; border-left: 2px solid #FF9800;';
|
||||
|
||||
const itemLabel = document.createElement('label');
|
||||
itemLabel.htmlFor = `edit-${itemKey.replace(/[.\s]/g, '-')}`;
|
||||
itemLabel.textContent = `Element [${index}]`;
|
||||
itemLabel.style.cssText = 'font-weight: 500; color: #FF9800; margin-bottom: 4px; display: block; font-size: 13px;';
|
||||
itemContainer.appendChild(itemLabel);
|
||||
|
||||
// Prüfen, ob das Array-Element selbst ein Objekt ist
|
||||
if (typeof item === 'object' && item !== null) {
|
||||
const innerObjContainer = document.createElement('div');
|
||||
innerObjContainer.style.cssText = 'padding-left: 12px; margin-top: 4px;';
|
||||
buildJsonForm(item, itemKey, level + 1);
|
||||
const existingObjChildren = Array.from(innerObjContainer.children);
|
||||
while (innerObjContainer.firstChild) innerObjContainer.removeChild(innerObjContainer.firstChild);
|
||||
existingObjChildren.forEach(ch => innerObjContainer.appendChild(ch));
|
||||
itemContainer.appendChild(innerObjContainer);
|
||||
} else {
|
||||
const itemFieldContainer = document.createElement('div');
|
||||
itemFieldContainer.style.cssText = 'background: var(--bg-input); padding: 8px; border-radius: 3px; margin-bottom: 4px;';
|
||||
|
||||
const input = document.createElement('input');
|
||||
const type = typeof item === 'boolean' ? 'checkbox' : typeof item === 'number' ? 'number' : 'text';
|
||||
input.type = type;
|
||||
input.value = item !== null && item !== undefined ? item : '';
|
||||
input.dataset.key = itemKey;
|
||||
input.dataset.type = typeof item;
|
||||
input.dataset.arrayIndex = index;
|
||||
|
||||
if (type === 'checkbox') input.checked = item;
|
||||
else input.value = String(item !== null && item !== undefined ? item : '');
|
||||
|
||||
input.style.cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px;';
|
||||
itemFieldContainer.appendChild(input);
|
||||
itemContainer.appendChild(itemFieldContainer);
|
||||
}
|
||||
|
||||
arrayItemsContainer.appendChild(itemContainer);
|
||||
});
|
||||
arrayContainer.appendChild(arrayItemsContainer);
|
||||
formDiv.appendChild(arrayContainer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Für primitive Werte: Standard-Eingabefeld erstellen
|
||||
const fieldContainer = document.createElement('div');
|
||||
fieldContainer.style.cssText = 'background: var(--bg-input); padding: 8px; border-radius: 4px; border: 1px solid transparent;';
|
||||
|
||||
|
|
@ -648,13 +706,14 @@ $ python web/serve.py</div>
|
|||
const type = typeof value === 'boolean' ? 'checkbox' : typeof value === 'number' ? 'number' : 'text';
|
||||
const input = document.createElement('input');
|
||||
input.type = type;
|
||||
input.value = value;
|
||||
input.value = value !== null && value !== undefined ? value : '';
|
||||
input.dataset.key = fullKey;
|
||||
input.dataset.type = typeof value;
|
||||
const displayValue = typeof value === 'boolean' ? value : String(value);
|
||||
|
||||
if (type === 'checkbox') input.checked = value;
|
||||
else input.value = displayValue;
|
||||
input.style.cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #cccccc; border-radius: 4px; font-family: var(--mono); font-size: 13px;';
|
||||
else input.value = String(value !== null && value !== undefined ? value : '');
|
||||
|
||||
input.style.cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px;';
|
||||
fieldContainer.appendChild(input);
|
||||
formDiv.appendChild(fieldContainer);
|
||||
});
|
||||
|
|
@ -686,26 +745,45 @@ $ python web/serve.py</div>
|
|||
}
|
||||
|
||||
function extractJsonFromForm(formDiv) {
|
||||
const result = {};
|
||||
// Map: full key -> { value, type }
|
||||
const inputEntries = [];
|
||||
const inputs = formDiv.querySelectorAll('[data-key]');
|
||||
|
||||
inputs.forEach(input => {
|
||||
const keyPath = input.dataset.key;
|
||||
const value = extractInputValue(input);
|
||||
|
||||
const keys = keyPath.split('.');
|
||||
let current = result;
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
if (i === keys.length - 1) {
|
||||
current[key] = value;
|
||||
} else {
|
||||
if (!current[key]) current[key] = {};
|
||||
current = current[key];
|
||||
}
|
||||
});
|
||||
const key = input.dataset.key || '';
|
||||
inputEntries.push({ key, value: extractInputValue(input) });
|
||||
});
|
||||
|
||||
// Sort by key to ensure stable parent-first construction
|
||||
inputEntries.sort((a,b) => a.key.localeCompare(b.key));
|
||||
|
||||
const result = {};
|
||||
inputEntries.forEach(({ key, value }) => {
|
||||
const parts = key.split('.');
|
||||
let cur = result;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
const isLast = i === parts.length - 1;
|
||||
const m = part.match(/^([^\[\]]+)\[(\d+)\]$/);
|
||||
if (m && !isLast) {
|
||||
const base = m[1];
|
||||
const idx = Number(m[2]);
|
||||
if (!cur[base]) cur[base] = [];
|
||||
if (!cur[base][idx]) cur[base][idx] = {};
|
||||
cur = cur[base][idx];
|
||||
} else if (m && isLast) {
|
||||
// Array leaf
|
||||
const base = m[1];
|
||||
const idx = Number(m[2]);
|
||||
if (!cur[base]) cur[base] = [];
|
||||
cur[base][idx] = value;
|
||||
} else if (isLast) {
|
||||
cur[part] = value;
|
||||
} else {
|
||||
if (!cur[part]) cur[part] = {};
|
||||
cur = cur[part];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue