Here's what I do in the Blades sheet. Here, the repeating attribute `repeating_cohort_${id}_roll_formula` depends on both the non-repeating crew_tier attribute and a bunch of row-specific repeating attributes. (I changed it slightly to reduce stuff that's not important for this).
calculateCohortDice = prefixes => {
const sourceAttrs = [
"crew_tier",
...prefixes.map(p => `${p}_impaired`),
...prefixes.map(p => `${p}_type`),
...prefixes.map(p => `${p}_roll_formula`),
];
getAttrs(sourceAttrs, v => {
const setting = prefixes.reduce((m, prefix) => {
const dice = (parseInt(v.crew_tier) || 0) - (parseInt(v[`${prefix}_impaired`]) || 0) +
((v[`${prefix}_type`] === "elite" || v[`${prefix}_type`] === "expert") ? 1 : 0);
const formula = buildRollFormula(dice);
m[`${prefix}_roll_formula`] = formula;
return m;
}, {});
setAttrs(setting);
});
};
How exactly dice is constructed doesn't matter that much, what's important is how the repeating attributes are handled here. The function has to be called in a different way, depending on if you want to run it for a single section or all sections at once.
Here are the event handlers:
// Handler for repeating attributes
on("change:repeating_cohort", () => calculateCohortDice(["repeating_cohort"]));
// Handler for non-repeating attributes
on("change:crew_tier", () => {
getSectionIDs("repeating_cohort", a => calculateCohortDice(a.map(id => `repeating_cohort_${id}`)));
});