function bindLogic(controlsById, layouts) {
const cellOrder = layouts.reduce((acc, layout, index) => {
acc[layout.id] = index
return acc;
}, {})
const layoutByCodeByType = d3.group(expandSets(layouts), d => d['set'], d => d['role']);
[...layoutByCodeByType.keys()].forEach(code => {
const layoutByType = layoutByCodeByType.get(code);
let conditionQuestion = undefined;
let answer = undefined;
if (layoutByType.has("yes")) {
if (layoutByType.get("yes").length > 1) throw new Error("Only one yes per set code")
const id = layoutByType.get("yes")[0].id;
conditionQuestion = controlsById.get(id);
answer = (v) => v ? "yes" : "no"
}
if (layoutByType.has("yesno")) {
if (conditionQuestion) throw new Error("Only one condition question per set code")
const id = layoutByType.get("yes")[0].id;
conditionQuestion = controlsById.get(id);
answer = (v) => {
if (/^yes/i.exec(v)) return "yes";
if (/^no/i.exec(v)) return "no";
return "maybe"
}
}
if (layoutByType.has("yesnomaybe")) {
if (conditionQuestion) throw new Error("Only one condition question per set code")
const id = layoutByType.get("yesnomaybe")[0].id;
conditionQuestion = controlsById.get(id);
answer = (v) => {
if (/^yes/i.exec(v)) return "yes";
if (/^no/i.exec(v)) return "no";
return "maybe"
}
}
// conditionals
if (conditionQuestion) {
if (layoutByType.has("ifyes")) {
layoutByType.get("ifyes").forEach(layout => {
const question = controlsById.get(layout.id)
if (question) bindOneWay(question.hidden, conditionQuestion.control, {
transform: (v) => !(answer(v) === "yes")
});
})
}
if (layoutByType.has("ifno")) {
layoutByType.get("ifno").forEach(layout => {
const question = controlsById.get(layout.id)
if (question) bindOneWay(question.hidden, conditionQuestion.control, {
transform: (v) => !(answer(v) === "no")
});
})
}
}
// wire up calculations
(layoutByType.get("calculation") || []).forEach(calculationLayout => {
const calculationQuestion = controlsById.get(calculationLayout.id);
const scoredQuestions = [...layoutByType.entries()]
.filter(([type, layouts]) => type !== "calculation")
.flatMap(([type, layouts]) => layouts.map(l => {
const q = controlsById.get(l.id)
q.args.id = l.id
return q;
}));
const calculate = () => {
const scores = scoredQuestions.map(q => scoreQuestion(q));
calculationQuestion.control.calculate(scores);
};
try {
calculate();
} catch (err) {
console.error(`Problem updating calculation '${calculationLayout.id}', reason: ${err.message}`)
throw err
}
// If the calculation is numbered, back write numbering to the inputs
if (calculationQuestion.control.numbering) {
try {
const firstIndex = Math.min(...scoredQuestions.map(q => cellOrder[q.args.id]));
const firstId = layouts[firstIndex].id;
const question = controlsById.get(firstId);
question.numbering.value = calculationQuestion.control.numbering.value;
} catch (err) {
debugger;
}
}
scoredQuestions.forEach(q => {
q.control.addEventListener('input', calculate);
invalidation.then(() => q.control.removeEventListener('input', calculate))
})
})
})
}