html <div class="equipment"> <h1>Container</h1> <span class="hidden repcontrol-button"></span> <fieldset class="repeating_container"> <label> <span>Name:</span> <input type="text" name="attr_container_name" /> </label> </fieldset> <button type="action" name="act_add-container" class="repcontrol-button repcontrol-button-add btn">+Add</button> <h1>Items</h1> <span class="hidden repcontrol-button"></span> <fieldset class="repeating_equipment"> <label> <span>Name:</span> <input type="text" name="attr_equipment_name" /> </label> </fieldset> <button type="action" name="act_add-equipment" class="repcontrol-button repcontrol-button-add btn">+Add</button> </div> <input type="text" class="hidden" name="attr_container_array" value="" /> <input type="text" class="hidden" name="attr_equipment_array" value="" /> <script type="text/worker"> // text/javascript // text/worker // GiGs custom handling for number type and logs const int = (score, fallback = 0) => parseInt(score) || fallback; const float = (score, fallback = 0) => parseFloat(score) || fallback; const clog = (text, color = 'LawnGreen') => { const message = `%c ${text}`; console.log(message, `color: ${color}; font-weight:bold;`); }; let addDeleteContainer = {}; // generate a unique ID 100 % of the time const uniqueids = {}; const generateUniqueRowID = () => { let generated = generateRowID(); while (uniqueids[generated] === true) { // clog(`generateUniqueRowID: ${generated} is not a unique ID, trying again.`); generated = generateRowID(); } clog(`generateUniqueRowID: ${generated} verified as unique, returning.`); uniqueids[generated] = true; return generated; }; // replace repcontrol_add button with action Scott's method // Array of repeating fieldsets (used for custom +add button) const repeatingSections = ['repeating_container', 'repeating_equipment']; // do stuff when the +add action button is executed const addItem = (section) => { const output = {}; // adds new container row AND adds a new equipment row if (section === 'repeating_container') { clog(`container created`); const containerID = generateUniqueRowID(); const equipmentID = generateUniqueRowID(); const containerRow = `repeating_container_${containerID}`; const equipmentRow = `repeating_equipment_${equipmentID}`; output[`${containerRow}_id`] = containerID; output[`${equipmentRow}_id`] = equipmentID; addDeleteContainer(containerID, equipmentID, 'true'); } // adds a new equipment row only if (section === 'repeating_equipment') { clog(`equipment created`); const rowID = generateUniqueRowID(); const row = `${section}_${rowID}`; output[`${row}_id`] = rowID; } setAttrs(output, {silent: true}); }; // event trigger for all +add action buttons repeatingSections.forEach((section) => { const buttonName = section.replace(/repeating_/, ''); on(`clicked:add-${buttonName}`, () => addItem(section)); }); on('remove:repeating_container', (eventInfo) => { const id = eventInfo.sourceAttribute.split('_')[2]; addDeleteContainer(id, '', 'false'); }); on('remove:repeating_equipment', (eventInfo) => { const id = eventInfo.sourceAttribute.split('_')[2]; addDeleteContainer('', id, 'false'); }); // handles adding/removing containers from the 2 arrays addDeleteContainer = (containerID, equipmentID, newRow) => { const containerArray = []; const equipmentArray = []; const output = {}; getAttrs(['container_array', 'equipment_array'], (v) => { // clog(`containerID:${containerID} equipmentID:${equipmentID} newRow:${newRow}`); containerArray.push(...v.container_array); equipmentArray.push(...v.equipment_array); // container added if (newRow === 'true' && containerID !== '') { containerArray.push(containerID); } if (newRow === 'true' && equipmentID !== '') { equipmentArray.push(equipmentID); } // container removed if (newRow === 'false') { const containerMatchedIndex = containerArray.indexOf(containerID); if (containerMatchedIndex !== -1) { const equipmentMatchedID = equipmentArray[containerMatchedIndex]; removeRepeatingRow(`repeating_equipment_${equipmentMatchedID}`); containerArray.splice(containerMatchedIndex, 1); equipmentArray.splice(containerMatchedIndex, 1); } else { const equipmentMatchedIndex = equipmentArray.indexOf(equipmentID); if (equipmentMatchedIndex !== -1) { const containerMatchedID = containerArray[equipmentMatchedIndex]; removeRepeatingRow(`repeating_container_${containerMatchedID}`); equipmentArray.splice(equipmentMatchedIndex, 1); containerArray.splice(equipmentMatchedIndex, 1); } } } clog(` containerArray:[${containerArray}] equipmentArray:[${equipmentArray}] `); output.container_array = containerArray; output.equipment_array = equipmentArray; setAttrs(output, {silent: true}); }); }; // sync changes between the container row it's equipment row on('change:repeating_container:container_name change:repeating_equipment:equipment_name', (eventInfo) => { const sourceAttributeParts = eventInfo.sourceAttribute.split('_'); // clog(`sourceAttribute:${eventInfo.sourceAttribute} sourceType:${eventInfo.sourceType}`); const id = sourceAttributeParts[2]; // row id const section = sourceAttributeParts[1]; // repeating section name const attr = sourceAttributeParts.slice(3).join('_'); // full attr name const name = attr.split('_').slice(1).join('_'); // just the last portion of the attr name const output = {}; const containerArray = []; const equipmentArray = []; // clog(`Section:${section} ID:${id} Attribute:${attr}`); getAttrs(['container_array', 'equipment_array', `repeating_${section}_${id}_${attr}`], (v) => { containerArray.push(...v.container_array); equipmentArray.push(...v.equipment_array); const lowerCaseId = id.toLowerCase(); // check id against the appropriate array const matchedIndex = section === 'container' ? containerArray.findIndex((item) => item.toLowerCase() === lowerCaseId) : equipmentArray.findIndex((item) => item.toLowerCase() === lowerCaseId); clog(` containerArray:[${containerArray}] equipmentArray:[${equipmentArray}] `); if (matchedIndex !== -1) { // grab the id from the opposite array const matchedID = section === 'container' ? equipmentArray[matchedIndex] : containerArray[matchedIndex]; const attrValue = v[`repeating_${section}_${id}_${attr}`]; // clog(`matchedID:${matchedID}`); // sync the changes if (section === 'container') { output[`repeating_equipment_${matchedID}_equipment_${name}`] = attrValue; // clog(`Setting equipment row with attrValue: ${attrValue}`); } else if (section === 'equipment') { output[`repeating_container_${matchedID}_container_${name}`] = attrValue; // clog(`Setting container row with attrValue: ${attrValue}`); } } setAttrs(output, {silent: true}); }); }); </script> css .charsheet .repcontrol-button { /* Style the button as needed (add your styling rules here) */ outline: 2px solid lime; } .charsheet .repcontrol-button ~ .repcontrol > .repcontrol_add { display: none !important; } I only have this hooked up with "container_name" and "equipment_name" between the two sections at the moment, but it "should" work for any "shared" attribute names. You have to look at how I'm extrapolating and concatenating the attr names in the last on(change) function. Doing it like this because of how the html names are currently used. This could be made much simpler if just using name. ie "repeating_container_id_name" and "repeating_equipment_id_name" instead of "repeating_container_id_container_name" and "repeating_equipment_id_equipment_name". Regardless, this is a POC which seems to be working up to this point. ;-)