Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Sheetworkers] Make a checkbox in a repeating section uncheck all other checkboxes in that section

Salutations Hivemind! I've been pulling my hair out on what is probably a simple problem but unfortunately Javascript isn't my forte. I'm currently redesigning a Dnd sheet to accommodate psionic characters and I've been trying to make a list with toggles in which only one can be selected at a time. Unfortunately, I can't use a radio input since it is in a repeating section, so I'm stuck using checkboxes and it is becoming a nightmare. Here are the relevant HTML and Script for my issue, if you have any pointers, I would be grateful! HTML Segment <fieldset class="repeating_focuses">     <div class="sheet-fieldset-item"> <div class="sheet-checkbox-cog"> <input type="checkbox" name="attr_options-flag" checked="checked"><span></span> <span class="sheet-toggle-icon"></span> </div> <input type="checkbox" name="attr_options-flag" checked="checked" class="sheet-toggle" hidden> <div class="sheet-toggle-checked"> <div class="sheet-form"> <div class="sheet-form-header">Edit Focuses & Masteries</div> <div class="sheet-form-body"> <div class="sheet-form-group"> <input type="text" name="attr_focus_description"> </div> </div> </div> </div> <div class="sheet-toggle-unchecked"> <div class="sheet-pc-focus"> <div class="sheet-checkbox-image"> <input type="checkbox" name="attr_checkfocus" value="1" > <input type="hidden" name="attr_usedfocus" value="0"/> <span></span> </div> <span name="attr_focus_description"></span> </div> </div> </div> </fieldset> Script Segment on("change:repeating_focuses:checkfocus", function(event) { getAttrs(["repeating_focuses_usedfocus"], function(v){ setAttrs({repeating_focuses_usedfocus: "1"}); //Flags this repeating item as the one currently active const test = event.newValue; if(test == "1"){ getSectionIDs("repeating_focuses", function(ids){         for(var i=0; i< ids.length; i++){     updateFocus(ids[i]);         }     });         } }); }); const updateFocus = function(focusID){ const focusElements = ["repeating_focuses_"+focusID+"_checkfocus", "repeating_focuses_"+focusID+"_usedfocus"]; getAttrs(focusElements, function(v){ var focusCheck = v["repeating_focuses_"+focusID+"_checkfocus"]; var focusUsed = v["repeating_focuses_"+focusID+"_focus_usedfocus"]; if(focusCheck == "1" && focusUsed == "0"){ setAttrs({ repeating_focuses_checkfocus: 0 //Unchecks all other focuses within the repeating section }); }else if(focusCheck == "1" && focusUsed == "1"){ setAttrs({ repeating_focuses_usedfocus: 0 //Resets the the flag for the item being modified }); } }); };
1589617741

Edited 1589619328
GiGs
Pro
Sheet Author
API Scripter
When you want to change all rows of a repeating section, you have to use the getSectionIDs function. I see you have tried to use it, but you've got the required structure backwards. You should always go     getSectionIDs =>            create array of all names you need         getAttrs(get all attributes you need)                 do the work                 setAttrs You never ever want getAttrs and setAttrs inside loops. They are very slow operations, this significantly reduces the performance of your sheet. Try this streamlined version of the worker: on("change:repeating_focuses:checkfocus", function (event) {     // get the row id that the play clicked. the id is always between the second and third '_'     const playerclickedid = event.sourceAttribute.split('_')[2] || '';     getSectionIDs("repeating_focuses", function (ids) {         // create an object to hold all the attributes we are bout to change         const output = {};                  // loop through the row ids          ids.forEach(id => {             // check if the id is the on the player clicked; if not, set to 0, if it is, set to 1.             output[`repeating_focuses_${id}_checkfocus`] = (playerclickedid.toLowerCase() == id.toLowerCase()) ? 1 : 0;         });         // now all attributes have been updated, save them         setAttrs(output, {             silent: true         });         // we use silent:true, so this change doesnt trigger the sheet worker again.     }); }); With this approach you dont need a separate usedfocus attribute. roll20's eventinfo gives you the row the player clicked in the sourceAttribute name, so you can use that.  This worker emulates a radio button. Players cannot unclick the active button by clicking it - they have to click a different one. Because of that we don't need to use getAttrs. We dont need to read the attributes values. All we care about is which one was clicked, and we always set its value to 1 and all the others to 0.
1589648398

Edited 1589648572
Hey Gigs ! I was hoping you would be seeing this as I've been following your posts around here to try and fix this issue, so thank you for stopping by. Using your provided version creates a few issues: When clicking on the first checkbox in the list, it immediately clears it and checks the last box instead. With three items in the list, my sheet is currently swapping between the second and third box, and it doesn't stop, it's just bouncing back and forth and it prevents me from adding any new items to the list. Basically, if the list as more than one item, it enters an infinite loop. Besides that issue, I actually need the players to be able to deselect the currently checked box as they can lose that focus and have nothing selected. Thank you again for taking the time to help with this issue, I've never had my sheet play by itself and just loop checkbox toggles like that, so that's fun !
Here is what's happening currently: Crazy focus loop
1589677328
GiGs
Pro
Sheet Author
API Scripter
Thats weird. I just made a new sheet, with just the html you posted above and the sheet worker, and it is working properly for me. You can test the code below in a sandbox. So something else is interfering. Can you post your full html and css in pastebin.com or gist.github.com, so I can see what is interefering. Here's the code thats working for me if you want to test it: <fieldset class="repeating_focuses">       <div class="sheet-fieldset-item">    <div class="sheet-checkbox-cog">     <input type="checkbox" name="attr_options-flag" checked="checked"><span></span>     <span class="sheet-toggle-icon"></span>    </div>    <input type="checkbox" name="attr_options-flag" checked="checked" class="sheet-toggle" hidden>    <div class="sheet-toggle-checked">     <div class="sheet-form">      <div class="sheet-form-header">Edit Focuses & Masteries</div>      <div class="sheet-form-body">       <div class="sheet-form-group">        <input type="text" name="attr_focus_description">       </div>      </div>     </div>    </div>   <div class="sheet-toggle-unchecked">    <div class="sheet-pc-focus">     <div class="sheet-checkbox-image">      <input type="checkbox" name="attr_checkfocus" value="1" >      <input type="hidden" name="attr_usedfocus" value="0"/>      <span></span>     </div>     <span name="attr_focus_description"></span>    </div>   </div>  </div> </fieldset> <script type="text/worker"> on("change:repeating_focuses:checkfocus", function (event) {     // get the row id that the play clicked. the id is always between the second and third '_'      const playerclickedid = event.sourceAttribute.split('_')[2] || '';     getSectionIDs("repeating_focuses", function (ids) {         // create an object to hold all the attributes we are bout to change         const output = {};                  // loop through the row ids          ids.forEach(id => {             // check if the id is the on the player clicked; if not, set to 0, if it is, set to 1.             output[`repeating_focuses_${id}_checkfocus`] = (playerclickedid.toLowerCase() == id.toLowerCase()) ? 1 : 0;         });         // now all attributes have been updated, save them         setAttrs(output, {             silent: true         });         // we use silent:true, so this change doesnt trigger the sheet worker again.     }); }); </script>
Oh boy, that's a lot of lines to share! Custom sheet code Currently messing with another page so you probably wanna stick with the "Core Tab". Thanks again !
1589679698

Edited 1589679748
GiGs
Pro
Sheet Author
API Scripter
Can you post the CSS too? Trying to work with a big sheet without the layout is a real pain. Never mind, i see you did :)
1589680555

Edited 1589680597
GiGs
Pro
Sheet Author
API Scripter
Aha, once I loaded it up I realised what was happening. {silent:true} didnt stop it triggering itself. The problem is when the sheet worker unchecked the previously selected box, that triggered a new run of the worker, and since I wasnt checking for the value, it was treated as a check to set the value not unset it. There are multiple ways to avoid this, like using getAttrs to check the value changed is to set the checkbox not unset it. But this is probably the best: if (event.sourceType === 'sheetworker') return; With eventInfo you can detect whether a change is caused by a player or a sheetworker. If its a sheet worker change, the worker just stops. Here's the scripts updated code: on("change:repeating_focuses:checkfocus", function (event) {                 // makes sure the sheet worker doesnt trigger itself                 if (event.sourceType === 'sheetworker') return;                 // get the row id that the player clicked. the id is always between the second and third '_'                 const playerclickedid = event.sourceAttribute.split('_')[2] || '';                                  getSectionIDs("repeating_focuses", function (ids) {                     // create an object to hold all the attributes we are bout to change                     const output = {};                     // loop through the row ids                     ids.forEach(id => {                         // check if the id is the one the player clicked; if not, set to 0, if it is, set to 1.                         output[`repeating_focuses_${id}_checkfocus`] = (playerclickedid.toLowerCase() == id.toLowerCase()) ? 1 : 0;                     });                     // now all attributes have been updated, save them                     setAttrs(output, {silent: true});                     // we use silent:true, so this change doesnt trigger the sheet worker again.                 });             });
A round of applause for Gigs, saving the day once again! It now works exactly as intended, you are an asset to this forum and I thank you deeply for taking some of your time to help me out!
1589682686
GiGs
Pro
Sheet Author
API Scripter
*blush* hehe, thanks a lot.
1589710309
GiGs
Pro
Sheet Author
API Scripter
I was grabbing the code from this thread and read an earlier comment that I'd missed: you need players to be able to deselect an entry and have nothing selected. To enable that, change this line  if (event.sourceType === 'sheetworker') return; to  if (event.sourceType === 'sheetworker' || event.newValue === '0' ) return; This means if the value is changing from 1 to 0 (being deselected), the sheet worker will end and not run the rest of the code.
Ah yes, I had modified the "forEach" check on the box's current value but your version is much simpler! Thanks for circling back on that.
1589772017
GiGs
Pro
Sheet Author
API Scripter
You're welcome :)