Support for "$" syntax to indicate where the serialization logic should collect values into arrays.

Use a dollar sign to indicate where tp-form should collect values as an
array. For example `myfield.$list.value` collects { myfield: { list: [ {
value: '...' } ] } }
This commit is contained in:
2025-06-12 12:53:09 +02:00
parent 727f3ecf11
commit f34c764391
2 changed files with 80 additions and 6 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@tp/tp-form",
"version": "1.1.1",
"version": "1.2.0",
"description": "",
"main": "tp-form.js",
"scripts": {

View File

@ -184,12 +184,86 @@ class TpForm extends LitElement {
// Check if the object syntax is used in the elements name.
if (el.name.indexOf('.') > -1) {
const parts = el.name.split('.');
parts.reduce((json, field, idx) => {
if (idx < parts.length - 1) {
json[field] = json[field] || {};
return json[field];
// Check if any part has a $ prefix to indicate array collection
const arrayIndex = parts.findIndex(part => part.startsWith('$'));
if (arrayIndex !== -1) {
// Remove the $ from the part name
parts[arrayIndex] = parts[arrayIndex].substring(1);
// Build the path to the array location
let current = json;
for (let i = 0; i < arrayIndex; i++) {
current[parts[i]] = current[parts[i]] || {};
current = current[parts[i]];
}
// The key where we want the array
const arrayKey = parts[arrayIndex];
// Initialize as array if needed
if (!current[arrayKey]) {
current[arrayKey] = [];
} else if (!Array.isArray(current[arrayKey])) {
// If it was previously a non-array, convert it
current[arrayKey] = [current[arrayKey]];
}
// Create the path for the remaining parts
const remainingParts = parts.slice(arrayIndex + 1);
// Find an object in the array that doesn't have this value set yet
// or create a new one
let targetObj = current[arrayKey].find(item => {
if (remainingParts.length === 0) {
// If no remaining parts, we're adding directly to the array
return false; // Always add a new item
}
// Check if this object doesn't have the property yet
let obj = item;
for (let i = 0; i < remainingParts.length - 1; i++) {
if (!obj[remainingParts[i]]) {
return true; // This object doesn't have the path, use it
}
obj = obj[remainingParts[i]];
}
return !obj[remainingParts[remainingParts.length - 1]];
});
// If no suitable object found, create a new one
if (!targetObj) {
targetObj = {};
current[arrayKey].push(targetObj);
}
// Add the value to the target object
if (remainingParts.length === 0) {
// Direct array of values
// Just push the value (we already created a new entry above)
current[arrayKey][current[arrayKey].length - 1] = el.value !== null && el.value !== undefined ? el.value : '';
} else {
this._addToJson(field, el.value, json);
// Add the value at the nested path
let objCursor = targetObj;
for (let i = 0; i < remainingParts.length - 1; i++) {
objCursor[remainingParts[i]] = objCursor[remainingParts[i]] || {};
objCursor = objCursor[remainingParts[i]];
}
objCursor[remainingParts[remainingParts.length - 1]] = el.value !== null && el.value !== undefined ? el.value : '';
}
return;
}
// Default behavior (no $ in the path)
parts.reduce((currentJson, field, idx) => {
if (idx < parts.length - 1) {
currentJson[field] = currentJson[field] || {};
return currentJson[field];
} else {
this._addToJson(field, el.value, currentJson);
}
}, json);