I tried that on my file, but the checkboxes didn’t update. I’ll give you the kind of file I’m working with:
#+title: Performance test #+begin_export html <script type="text/javascript"> function updateCookiesIn(div) { const headline = div.querySelector("h1, h2, h3, h4, h5, h6"); if (!headline) return; const cookies = Array.from(headline.querySelectorAll("code")) .filter(c => c.innerText.startsWith("[") && c.innerText.endsWith("]")); const fracCookies = cookies.filter(c => c.innerText.includes("/")); const pctCookies = cookies.filter(c => c.innerText.includes("%")); // The ugly query strings here restrict the selection to checkboxes at *this* level of the hierarchy const allTasks = div.querySelectorAll(`#${div.id} > div > ul input[type=checkbox], #${div.id} > div > ol input[type=checkbox]`); const completedTasks = div.querySelectorAll(`#${div.id} > div > ul input[type=checkbox]:checked, #${div.id} > div > ol input[type=checkbox]:checked`); const newFrac = `[${completedTasks.length}/${allTasks.length}]`; const newPctText = allTasks.length ? (100 * completedTasks.length / allTasks.length).toFixed(0) : "100"; // Org shows 100% for a cookie when there are no checkboxes const newPct = `[${newPctText}%]`; fracCookies.forEach(c => c.innerText = newFrac); pctCookies.forEach(c => c.innerText = newPct); } function replaceWithCheckbox(code) { const isChecked = code.innerText.includes("X"); const checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); if (isChecked) checkbox.setAttribute("checked", "checked"); checkbox.onclick = function (e) { const container = findContainingSection(e.target); if (!container) return; updateCookiesIn(container); }; code.replaceWith(checkbox); } function findContainingSection(el) { if (!el.parentElement) return null; const parent = el.parentElement; const classes = Array.from(parent.classList); if (classes.some(cl => cl.startsWith("outline") && !cl.startsWith("outline-text"))) { return parent; } else { return findContainingSection(parent); } } const orgCheckboxes = document.querySelectorAll(".off > code, .on > code"); orgCheckboxes.forEach(replaceWithCheckbox); const orgSections = document.querySelectorAll("div.outline-1, div.outline-2, div.outline-3, div.outline-4, div.outline-5, div.outline-6"); orgSections.forEach(updateCookiesIn); </script> #+end_export * Performance test ** Student Name [0/10] [0%] Date The student will perform the following features: 1. [ ] 2. [ ] 3. [ ] 4. [ ] 5. [ ] 6. [ ] 7. [ ] 8. [ ] 9. [ ] 10. [ ] When I do C-E h o, and check one of the boxes, the grade isn’t updated. > On Jun 14, 2020, at 4:02 AM, Richard Lawrence <wyle...@gmail.com> wrote: > > Hi Devin and all, > > Devin Prater <r.d.t.pra...@gmail.com> writes: > >> Yeah, I was hoping to just have an HTML page or something that could >> be put on Github or just sent in an email attachment, where checking >> checkboxes would update the fraction cookie. > > I hacked together a quick solution for this. You should be able to just > drop this Javascript into an Org file. When it is included in HTML that > has been exported via the standard Org exporter, it replaces the static > "[ ]" and "[X]" code elements with actual HTML checkboxes, and updates > any progress cookies in the relevant headlines when those boxes are > checked or unchecked. > > I haven't tested it extensively, and the code can surely be improved, > but it works for the cases I could think to test. > > #+begin_export html > <script type="text/javascript"> > function updateCookiesIn(div) { > const headline = div.querySelector("h1, h2, h3, h4, h5, h6"); > if (!headline) return; > const cookies = Array.from(headline.querySelectorAll("code")) > .filter(c => c.innerText.startsWith("[") && > c.innerText.endsWith("]")); > const fracCookies = cookies.filter(c => c.innerText.includes("/")); > const pctCookies = cookies.filter(c => c.innerText.includes("%")); > > // The ugly query strings here restrict the selection to checkboxes at > *this* level of the hierarchy > const allTasks = div.querySelectorAll(`#${div.id} > div > ul > input[type=checkbox], #${div.id} > div > ol input[type=checkbox]`); > const completedTasks = div.querySelectorAll(`#${div.id} > div > ul > input[type=checkbox]:checked, #${div.id} > div > ol > input[type=checkbox]:checked`); > > const newFrac = `[${completedTasks.length}/${allTasks.length}]`; > const newPctText = allTasks.length > ? (100 * completedTasks.length / allTasks.length).toFixed(0) > : "100"; // Org shows 100% for a cookie when there are no > checkboxes > const newPct = `[${newPctText}%]`; > > fracCookies.forEach(c => c.innerText = newFrac); > pctCookies.forEach(c => c.innerText = newPct); > } > > function replaceWithCheckbox(code) { > const isChecked = code.innerText.includes("X"); > > const checkbox = document.createElement("input"); > checkbox.setAttribute("type", "checkbox"); > if (isChecked) checkbox.setAttribute("checked", "checked"); > checkbox.onclick = function (e) { > const container = findContainingSection(e.target); > if (!container) return; > updateCookiesIn(container); > }; > > code.replaceWith(checkbox); > } > > function findContainingSection(el) { > if (!el.parentElement) return null; > > const parent = el.parentElement; > const classes = Array.from(parent.classList); > if (classes.some(cl => cl.startsWith("outline") && > !cl.startsWith("outline-text"))) { > return parent; > } else { > return findContainingSection(parent); > } > } > > const orgCheckboxes = document.querySelectorAll(".off > code, .on > code"); > orgCheckboxes.forEach(replaceWithCheckbox); > > const orgSections = document.querySelectorAll("div.outline-1, div.outline-2, > div.outline-3, div.outline-4, div.outline-5, div.outline-6"); > orgSections.forEach(updateCookiesIn); > </script> > #+end_export > > Hope that helps! > > -- > Best, > Richard