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

change multiple radio values based on a checkbox

1559180927

Edited 1559181040
vÍnce
Pro
Sheet Author
Question: I have multiple radio inputs(rows) that have 3 different text values(columns).  Each radio(row) has a default/checked attribute for a radio input that contains a null value.  Users can manually select 3 values; "" null(default/checked), "foo1", or "foo2" for each radio/row.  I would like to add a checkbox that if checked will change all radio inputs to their "foo1" values.  Basically a checkbox at the top of the column that selects or un-selects all rows in that column... I'm guessing that I can use a sheetworker to listen for changes on the checkbox attribute and in turn set all radio inputs to their "foo1" value.   What about the radio state?  If I use a sheetworker to set a value, the radio state will not change, correct?  Is there any way to change the radio state as well?  Should I approach this from another direction?  TIA
1559183978
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
The radio state (checked/unchecked) is dependent on the attributes value, so changing the value will check mark the corresponding radio button. The method you outline should work.
1559201080
vÍnce
Pro
Sheet Author
Thanks Scott.  Coded it out and it works perfect.  
1559370677

Edited 1559370923
vÍnce
Pro
Sheet Author
My code is working as an all or nothing toggle, but I would like to prevent making a change to any header image attribute's that are using a custom URL for the header_image.&nbsp; Background: each roll template has 3 header_image values available on the sheet (radio); #1. null,&nbsp; #2. default URL as coded #3. custom URL.&nbsp; As described above, I've added a new checkbox attribute(header_images_toggle) that sets all header image values to #2(default URL) or resets to #1(null)(do not use header images).&nbsp; I need to update my code below to only toggle the header_image attribute if the value is currently #1(null) or #2(default URL) and ignore if #3(custom URL). Is there a way to add this extra check after my initial if statement and within the setAttrs function?&nbsp; example (even with my novice js skills I'm 99% sure this improper syntax but just serves as an example of the additional check I would like to perform before setting a new value)&nbsp; TIA setAttrs({ &nbsp;&nbsp;&nbsp;&nbsp; if (['header_image-pf_spell'] === ' ') { ['header_image-pf_spell']&nbsp; : "[default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>)" }; }); my current "all or nothing code" /* toggles default header images */ on("change:header_images_toggle", function() { getAttrs(["header_images_toggle"], function(values) { var tempToggle = values.header_images_toggle; TAS.debug("Header Images Toggle new value = " + tempToggle); if (tempToggle &gt; 0) { setAttrs({ ['header_image-pf_spell']: "[default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>)", ['header_image-pf_attack-melee']: "[default](<a href="http://i.imgur.com/AGq5VBG.png" rel="nofollow">http://i.imgur.com/AGq5VBG.png</a>)", ['header_image-pf_attack-dual']: "[default](<a href="http://i.imgur.com/Eh243RO.png" rel="nofollow">http://i.imgur.com/Eh243RO.png</a>)", ['header_image-pf_attack-ranged']: "[default](<a href="http://imgur.com/58j2e8P.png" rel="nofollow">http://imgur.com/58j2e8P.png</a>)", ['header_image-pf_attack-cmb']: "[default](<a href="http://i.imgur.com/Si4vfts.png" rel="nofollow">http://i.imgur.com/Si4vfts.png</a>)", ['header_image-pf_defense']: "[default](<a href="http://imgur.com/02fV6wh.png" rel="nofollow">http://imgur.com/02fV6wh.png</a>)", ['header_image-pf_generic']: "[default](<a href="http://imgur.com/phw1eFB.png" rel="nofollow">http://imgur.com/phw1eFB.png</a>)", ['header_image-pf_ability']: "[default](<a href="http://i.imgur.com/UxYSva8.png" rel="nofollow">http://i.imgur.com/UxYSva8.png</a>)", ['header_image-pf_block']: "[default](<a href="http://imgur.com/nBnv4DL.png" rel="nofollow">http://imgur.com/nBnv4DL.png</a>)", ['header_image-pf_generic-skill']: "[default](<a href="http://imgur.com/8dCkRtG.png" rel="nofollow">http://imgur.com/8dCkRtG.png</a>)", ['header_image-pf_generic-init']: "[default](<a href="http://i.imgur.com/pjS6HVJ.png" rel="nofollow">http://i.imgur.com/pjS6HVJ.png</a>)", ['header_image-pf_block-item']: "[default](<a href="http://i.imgur.com/4FgQuqS.png" rel="nofollow">http://i.imgur.com/4FgQuqS.png</a>)", ['header_image-pf_block-check']: "[default](<a href="http://i.imgur.com/a6O3ZGB.png" rel="nofollow">http://i.imgur.com/a6O3ZGB.png</a>)" }); } else { setAttrs({ ['header_image-pf_spell']: '', ['header_image-pf_attack-melee']: '', ['header_image-pf_attack-dual']: '', ['header_image-pf_attack-ranged']: '', ['header_image-pf_attack-cmb']: '', ['header_image-pf_defense']: '', ['header_image-pf_generic']: '', ['header_image-pf_ability']: '', ['header_image-pf_block']: '', ['header_image-pf_generic-skill']: '', ['header_image-pf_generic-init']: '', ['header_image-pf_block-item']: '', ['header_image-pf_block-check']: '' }); } }); }); . . .
1559375827

Edited 1559462160
GiGs
Pro
Sheet Author
API Scripter
You are better off doing all the checking before you get to the setAttrs function, and using just one setAttrs. You will need to add all your&nbsp;header_image-pf_spell, et al &nbsp; to the getAttrs line. You could manually add them all, but this is a great opportunity for setting a variable to hold your values, and then constructing the function from them. You could convert your default values into an object like this const header_images = { 'header_image-pf_spell': "[default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>)", 'header_image-pf_attack-melee': "[default](<a href="http://i.imgur.com/AGq5VBG.png" rel="nofollow">http://i.imgur.com/AGq5VBG.png</a>)", 'header_image-pf_attack-dual': "[default](<a href="http://i.imgur.com/Eh243RO.png" rel="nofollow">http://i.imgur.com/Eh243RO.png</a>)", 'header_image-pf_attack-ranged': "[default](<a href="http://imgur.com/58j2e8P.png" rel="nofollow">http://imgur.com/58j2e8P.png</a>)", 'header_image-pf_attack-cmb': "[default](<a href="http://i.imgur.com/Si4vfts.png" rel="nofollow">http://i.imgur.com/Si4vfts.png</a>)", 'header_image-pf_defense': "[default](<a href="http://imgur.com/02fV6wh.png" rel="nofollow">http://imgur.com/02fV6wh.png</a>)", 'header_image-pf_generic': "[default](<a href="http://imgur.com/phw1eFB.png" rel="nofollow">http://imgur.com/phw1eFB.png</a>)", 'header_image-pf_ability': "[default](<a href="http://i.imgur.com/UxYSva8.png" rel="nofollow">http://i.imgur.com/UxYSva8.png</a>)", 'header_image-pf_block': "[default](<a href="http://imgur.com/nBnv4DL.png" rel="nofollow">http://imgur.com/nBnv4DL.png</a>)", 'header_image-pf_generic-skill': "[default](<a href="http://imgur.com/8dCkRtG.png" rel="nofollow">http://imgur.com/8dCkRtG.png</a>)", 'header_image-pf_generic-init': "[default](<a href="http://i.imgur.com/pjS6HVJ.png" rel="nofollow">http://i.imgur.com/pjS6HVJ.png</a>)", 'header_image-pf_block-item': "[default](<a href="http://i.imgur.com/4FgQuqS.png" rel="nofollow">http://i.imgur.com/4FgQuqS.png</a>)", 'header_image-pf_block-check': "[default](<a href="http://i.imgur.com/a6O3ZGB.png" rel="nofollow">http://i.imgur.com/a6O3ZGB.png</a>)" }; Then you need to build the getAttribute line. You can do what I'm about to do in much more compact code, but I'm doing it step by step for clarity. There's a function Object.keys(variable name) which will give you an array of the names on the left side&nbsp; of : in the above object. so we can create a variable to hold that. const header_image_names =&nbsp;Object.keys(header_images); Now there's a problem here, in that we'll also need the&nbsp;"header_images_toggle". We could add that into the array above using push, but that will cause us problems later. So we'll manually add it as we need below. We can use that array directly on the getAttr line, like so getAttrs(header_image_names, function(values) { // no need for [ ] since this is already an array. Since we also need header_images_toggle, we'll push it in here getAttrs(header_image_names.push('header_images_toggle'), function(values) { // no need for [ ] since this is already an array. We could also use concat (combine two arrays), if we convery header_images_toggle into an array, like so getAttrs(header_image_names.concat(['header_images_toggle']), function(values) { There's always multiple ways to do things. I'm assuming you only want this worker to run when you change the header_images_toggle, and not when any of the individual images are changed, so we don't need to change the on('change row. So we can start building the sheet worker. Our starting code looks like this: const header_images = { 'header_image-pf_spell': "[default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>)", 'header_image-pf_attack-melee': "[default](<a href="http://i.imgur.com/AGq5VBG.png" rel="nofollow">http://i.imgur.com/AGq5VBG.png</a>)", 'header_image-pf_attack-dual': "[default](<a href="http://i.imgur.com/Eh243RO.png" rel="nofollow">http://i.imgur.com/Eh243RO.png</a>)", 'header_image-pf_attack-ranged': "[default](<a href="http://imgur.com/58j2e8P.png" rel="nofollow">http://imgur.com/58j2e8P.png</a>)", 'header_image-pf_attack-cmb': "[default](<a href="http://i.imgur.com/Si4vfts.png" rel="nofollow">http://i.imgur.com/Si4vfts.png</a>)", 'header_image-pf_defense': "[default](<a href="http://imgur.com/02fV6wh.png" rel="nofollow">http://imgur.com/02fV6wh.png</a>)", 'header_image-pf_generic': "[default](<a href="http://imgur.com/phw1eFB.png" rel="nofollow">http://imgur.com/phw1eFB.png</a>)", 'header_image-pf_ability': "[default](<a href="http://i.imgur.com/UxYSva8.png" rel="nofollow">http://i.imgur.com/UxYSva8.png</a>)", 'header_image-pf_block': "[default](<a href="http://imgur.com/nBnv4DL.png" rel="nofollow">http://imgur.com/nBnv4DL.png</a>)", 'header_image-pf_generic-skill': "[default](<a href="http://imgur.com/8dCkRtG.png" rel="nofollow">http://imgur.com/8dCkRtG.png</a>)", 'header_image-pf_generic-init': "[default](<a href="http://i.imgur.com/pjS6HVJ.png" rel="nofollow">http://i.imgur.com/pjS6HVJ.png</a>)", 'header_image-pf_block-item': "[default](<a href="http://i.imgur.com/4FgQuqS.png" rel="nofollow">http://i.imgur.com/4FgQuqS.png</a>)", 'header_image-pf_block-check': "[default](<a href="http://i.imgur.com/a6O3ZGB.png" rel="nofollow">http://i.imgur.com/a6O3ZGB.png</a>)" }; const header_image_names =&nbsp;Object.keys(header_images); on(change:header_images_toggle , function() {&nbsp;&nbsp;&nbsp;&nbsp; getAttrs(header_image_names.push('header_images_toggle'), function(values) { Now we can look at building the actual worker itself. We need to get the values of all the header images, and we can create a new object for that purpose. const&nbsp; tempToggle = values.header_images_toggle; let tempImages = {}; header_image_names .forEach( image =&gt; { &nbsp; &nbsp; tempImages[image] = values[image]; } What we are doing here is looping through each&nbsp;attribute in the&nbsp; header_image_names&nbsp; (which, if you remember, is a list of all the header image attribute names). We get their values and add them to our tempImages object. It'll look a lot like the header_images object from the start of the code, except it will have the current value of each item. The tricky part is probably &nbsp; &nbsp; tempImages[image] = values[image]; On the first loop, image will be 'header_image-pf_spell'. So this is equivalent to: &nbsp; &nbsp; tempImages['header_image-pf_spell'] = values['header_image-pf_spell']; It's basically saying, get the value of header_image-pf_spell, and create a new item in the tenpImages object whose name is header_image-pf_spell, and whose value is that attribute's value. And at the end of the loop, you end up with an object which has all the current attribute values, stored with their name. If you're more comfortable with for loops, the above is equivalent to this, but forEach is easier to write once youre familiar with it. const&nbsp;tempToggle = values.header_images_toggle; let tempImages = {}; for(let i = 0; i &lt; header_image_names.length; i++) { &nbsp; &nbsp; tempImages[header_image_names[i]] = values[header_image_names[i]]; } Now, something I want to draw your attention to, if you're not already aware of it. Look at the first setAttrs in your original code. Compare it to the header_images object i created at the start of this post. Notice how they are almost identical? This means if I did this setAttrs(header_images); it would be a valid instruction, and would set all those values, because header_images is in the format setAttrs needs.&nbsp; This lets us do something clever. We have already built an object in that format, using the loop above. What can do is build that object in a different way - to only include the attributes that need to change. it would look like this: const&nbsp;tempToggle = values.header_images_toggle; let tempImages = {}; header_image_names.forEach( image =&gt; { // we loop through all the attribute names &nbsp; &nbsp; const currentImage = values[image]; // in each loop, we grab the current attribute value, using its name if(currentImage === header_images[image] || currentImage === '') { // compare the image, and if it is neither the default or '', skip it &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(tempToggle &gt; 0) { // if tempToggle is 0, store the default value, otherwise store the null value. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;tempImages[image] = values[image]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;tempImages[image] = ''; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp; } } setAttrs(tempImages); // we have a list of the objects that can change, and their values, and we assign them. There's a lot that happened in this above code. We loop through all the attribute names, and we grab each one in turn and look at its value. if(currentImage === header_images[image] || currentImage === '') { If it's value is the default or null, we proceed, otherwise we skip it. If we proceed,&nbsp; if(tempToggle &gt; 0) { &nbsp; &nbsp;&nbsp;tempImages[image] = values[image]; } else { &nbsp; &nbsp;&nbsp;tempImages[image] = ''; } You can also write this code in more compact form, like this tempImages[image] = (tempToggle &gt; 0 ? values[image] : ''); The conclusion is, when we run setAttrs, it only updates the attributes we want it to, and ignores the rest. Here is the resulting complete code: const header_images = { 'header_image-pf_spell': "[default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>)", 'header_image-pf_attack-melee': "[default](<a href="http://i.imgur.com/AGq5VBG.png" rel="nofollow">http://i.imgur.com/AGq5VBG.png</a>)", 'header_image-pf_attack-dual': "[default](<a href="http://i.imgur.com/Eh243RO.png" rel="nofollow">http://i.imgur.com/Eh243RO.png</a>)", 'header_image-pf_attack-ranged': "[default](<a href="http://imgur.com/58j2e8P.png" rel="nofollow">http://imgur.com/58j2e8P.png</a>)", 'header_image-pf_attack-cmb': "[default](<a href="http://i.imgur.com/Si4vfts.png" rel="nofollow">http://i.imgur.com/Si4vfts.png</a>)", 'header_image-pf_defense': "[default](<a href="http://imgur.com/02fV6wh.png" rel="nofollow">http://imgur.com/02fV6wh.png</a>)", 'header_image-pf_generic': "[default](<a href="http://imgur.com/phw1eFB.png" rel="nofollow">http://imgur.com/phw1eFB.png</a>)", 'header_image-pf_ability': "[default](<a href="http://i.imgur.com/UxYSva8.png" rel="nofollow">http://i.imgur.com/UxYSva8.png</a>)", 'header_image-pf_block': "[default](<a href="http://imgur.com/nBnv4DL.png" rel="nofollow">http://imgur.com/nBnv4DL.png</a>)", 'header_image-pf_generic-skill': "[default](<a href="http://imgur.com/8dCkRtG.png" rel="nofollow">http://imgur.com/8dCkRtG.png</a>)", 'header_image-pf_generic-init': "[default](<a href="http://i.imgur.com/pjS6HVJ.png" rel="nofollow">http://i.imgur.com/pjS6HVJ.png</a>)", 'header_image-pf_block-item': "[default](<a href="http://i.imgur.com/4FgQuqS.png" rel="nofollow">http://i.imgur.com/4FgQuqS.png</a>)", 'header_image-pf_block-check': "[default](<a href="http://i.imgur.com/a6O3ZGB.png" rel="nofollow">http://i.imgur.com/a6O3ZGB.png</a>)" }; const header_image_names =&nbsp;Object.keys(header_images); on(change:header_images_toggle , function() {&nbsp;&nbsp;&nbsp;&nbsp; getAttrs(header_image_names.push('header_images_toggle'), function(values) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;tempToggle = values.header_images_toggle; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let tempImages = {}; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;header_image_names.forEach( image =&gt; { &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;const currentImage = values[image]; &nbsp;&nbsp;&nbsp;&nbsp; if(currentImage === header_images[image] || currentImage === '') { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempImages[image] = (tempToggle &gt; 0 ? currentImage : ''); &nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;setAttrs(tempImages); &nbsp;&nbsp;&nbsp;&nbsp;}); }); Now I hit send, and hope I havent made a typo somewhere... Edit: &nbsp;changed array name from&nbsp; header_worker_array to&nbsp; header_image_names for clarity. Edit 2:&nbsp; and fixed the Object.keys line and getAttrs line! &nbsp;
1559414272
vÍnce
Pro
Sheet Author
GiG's you are awesome.&nbsp; I can barely give directions to the corner store and you deliver the moonshot project(and describe each step)...&nbsp; lol Thank you.&nbsp;&nbsp;
1559416674

Edited 1559416926
GiGs
Pro
Sheet Author
API Scripter
Hehe, you're welcome, and thank you :) I just noticed some of the edits I made immediately after that post hadnt saved, weirldy. They were minor things and didnt affect the script's function, but I've restored them anyway.&nbsp;
1559418544

Edited 1559418722
vÍnce
Pro
Sheet Author
I used your final (edited/updated) code GiGs, and needed to make a few corrections for mismatched { and ), and such as detected by jshint. No biggy... I made those adjustments, but I'm getting an undefined error for&nbsp; tempImages&nbsp; on the setter line.&nbsp; Do I need to define&nbsp;tempImages outside of getAttrs or ...? latest change to the on("change") function snippet on("change:header_images_toggle" , function() { getAttrs(header_image_names.push('header_images_toggle'), function(values) { const tempToggle = values.header_images_toggle; let tempImages = {}; header_image_names.forEach( image =&gt; { const currentImage = values[image]; if(currentImage === header_images[image] || currentImage === '') { tempImages[image] = (tempToggle &gt; 0 ? currentImage : ''); } }); }); setAttrs( tempImages ); }); Thanks for your help.
1559418856
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
The seattrs needs to be inside the getattrs.
1559419070
vÍnce
Pro
Sheet Author
Scott C. said: The seattrs needs to be inside the getattrs. Aha.&nbsp; ;-)
1559420112
GiGs
Pro
Sheet Author
API Scripter
Oh yes, formatting code on the forums is so hard, I should have written it externally and copied it here when finished.&nbsp;
1559420301

Edited 1559420669
vÍnce
Pro
Sheet Author
Hmm.&nbsp; I updated the sheet with the latest code and checking the&nbsp;header_images_toggle checkbox on the sheet doesn't seem to have any effect on the radio buttons now.&nbsp; The&nbsp;header_images_toggle always reverts back to an unchecked state after checking however, but that is the only apparent change.&nbsp; Image below, expected behavior after checking header_images_toggle, pf_spell should be changed to column 2(default). pf_attack(melee) would remain unchanged since it's already set to the default image. pf_attack(dual) wouldn't change since it's using a custom URL.&nbsp; And of course, the header_images_toggle checkbox would remain checked. latest /* toggles default header images */ const header_images = { 'header_image-pf_spell': "[default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>)", 'header_image-pf_attack-melee': "[default](<a href="http://i.imgur.com/AGq5VBG.png" rel="nofollow">http://i.imgur.com/AGq5VBG.png</a>)", 'header_image-pf_attack-dual': "[default](<a href="http://i.imgur.com/Eh243RO.png" rel="nofollow">http://i.imgur.com/Eh243RO.png</a>)", 'header_image-pf_attack-ranged': "[default](<a href="http://imgur.com/58j2e8P.png" rel="nofollow">http://imgur.com/58j2e8P.png</a>)", 'header_image-pf_attack-cmb': "[default](<a href="http://i.imgur.com/Si4vfts.png" rel="nofollow">http://i.imgur.com/Si4vfts.png</a>)", 'header_image-pf_defense': "[default](<a href="http://imgur.com/02fV6wh.png" rel="nofollow">http://imgur.com/02fV6wh.png</a>)", 'header_image-pf_generic': "[default](<a href="http://imgur.com/phw1eFB.png" rel="nofollow">http://imgur.com/phw1eFB.png</a>)", 'header_image-pf_ability': "[default](<a href="http://i.imgur.com/UxYSva8.png" rel="nofollow">http://i.imgur.com/UxYSva8.png</a>)", 'header_image-pf_block': "[default](<a href="http://imgur.com/nBnv4DL.png" rel="nofollow">http://imgur.com/nBnv4DL.png</a>)", 'header_image-pf_generic-skill': "[default](<a href="http://imgur.com/8dCkRtG.png" rel="nofollow">http://imgur.com/8dCkRtG.png</a>)", 'header_image-pf_generic-init': "[default](<a href="http://i.imgur.com/pjS6HVJ.png" rel="nofollow">http://i.imgur.com/pjS6HVJ.png</a>)", 'header_image-pf_block-item': "[default](<a href="http://i.imgur.com/4FgQuqS.png" rel="nofollow">http://i.imgur.com/4FgQuqS.png</a>)", 'header_image-pf_block-check': "[default](<a href="http://i.imgur.com/a6O3ZGB.png" rel="nofollow">http://i.imgur.com/a6O3ZGB.png</a>)" }; const header_image_names = Object.keys('header_images'); on("change:header_images_toggle", function() { getAttrs(header_image_names.push('header_images_toggle'), function(values) { const tempToggle = values.header_images_toggle; let tempImages = {}; header_image_names.forEach( image =&gt; { const currentImage = values[image]; if(currentImage === header_images[image] || currentImage === '') { tempImages[image] = (tempToggle &gt; 0 ? currentImage : ''); } }); setAttrs(tempImages); }); }); .
1559422423

Edited 1559422494
GiGs
Pro
Sheet Author
API Scripter
You're saying you click the header_images_toggle, and it reverts to an unchecked state after clicking? Gah, there's a mistake in the code. This line getAttrs(header_image_names.push('header_images_toggle'), adds the header_images_toggle to the header_image_names array, so that it gets looped in to the setAttrs. Change it to&nbsp; getAttrs(header_image_names.concat(['header_images_toggle']), That way it isnt permanently added to the array, it's just combined for that one line.
1559423207

Edited 1559423436
vÍnce
Pro
Sheet Author
Thanks GiGs.&nbsp; Now the checkbox stays put. Cool. No other radio boxes change.&nbsp; Errors in log. ;-) CLICKED: radio/checkbox &lt;input type=​"checkbox" name=​"attr_header_images_toggle" value=​"1" title=​"@{header_images_toggle}​"&gt;​ c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 Really updating character sheet values 12c4301b0d-b37a-4abb-98cb-dff049d6d37b:570 Foudn a pre-defined key order! c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 Setting up repeating sections took until 74ms c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 Finding list of dirty attributes took until 74ms c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 Querytest took until 80ms c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 Attribute cache compliation took until 83ms c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 Set values (including auto-calcuating variables) took until 106ms c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 Took 111ms VM3437:3 Uncaught TypeError: Cannot read property 'toString' of undefined at Function.jqotenc (c8b936a8-999c-419a-ad0c-1139090a6687:20) at T.i.eval (eval at jqotec (c8b936a8-999c-419a-ad0c-1139090a6687:20), &lt;anonymous&gt;:3:407) at HTMLScriptElement.&lt;anonymous&gt; (c8b936a8-999c-419a-ad0c-1139090a6687:20) at Function.each (fcf0e829-d7c2-4523-9edd-457ff1d02f5e:650) at jQuery.fn.init.each (fcf0e829-d7c2-4523-9edd-457ff1d02f5e:270) at jQuery.fn.init.jqote (c8b936a8-999c-419a-ad0c-1139090a6687:20) at T.i.render (c4301b0d-b37a-4abb-98cb-dff049d6d37b:569) at T.i.&lt;anonymous&gt; (c4301b0d-b37a-4abb-98cb-dff049d6d37b:569) at T.i.trigger (c8b936a8-999c-419a-ad0c-1139090a6687:7) at T.i.change (c8b936a8-999c-419a-ad0c-1139090a6687:7) jqotenc @ c8b936a8-999c-419a-ad0c-1139090a6687:20 (anonymous) @ VM3437:3 (anonymous) @ c8b936a8-999c-419a-ad0c-1139090a6687:20 each @ fcf0e829-d7c2-4523-9edd-457ff1d02f5e:650 each @ fcf0e829-d7c2-4523-9edd-457ff1d02f5e:270 jqote @ c8b936a8-999c-419a-ad0c-1139090a6687:20 render @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 trigger @ c8b936a8-999c-419a-ad0c-1139090a6687:7 change @ c8b936a8-999c-419a-ad0c-1139090a6687:7 set @ c8b936a8-999c-419a-ad0c-1139090a6687:7 save @ c8b936a8-999c-419a-ad0c-1139090a6687:7 syncedSave @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:587 C.each.C.forEach @ c8b936a8-999c-419a-ad0c-1139090a6687:6 u.&lt;computed&gt; @ c8b936a8-999c-419a-ad0c-1139090a6687:7 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:587 64d4a69b-a936-42df-ab07-f07db3ed2ee8:45 FIREBASE WARNING: Exception was thrown by user callback. Error: Firebase.update failed: First argument contains undefined in property 'campaign-2954082-7vnGopGlg-8ESZgmjSX0Pg.char-attribs.char.-LDcRPgB_tKI6tkkZGdF.-LgJch7IcXXXskYLChD-.current' at hg (blob:<a href="https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:123:67" rel="nofollow">https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:123:67</a>) at blob:<a href="https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:126:168" rel="nofollow">https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:126:168</a> at Fb (blob:<a href="https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:28:656" rel="nofollow">https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:28:656</a>) at jg (blob:<a href="https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:126:134" rel="nofollow">https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:126:134</a>) at X.update (blob:<a href="https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:258:369" rel="nofollow">https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:258:369</a>) at blob:<a href="https://app.roll20.net/c4301b0d-b37a-4abb-98cb-dff049d6d37b:81:1876" rel="nofollow">https://app.roll20.net/c4301b0d-b37a-4abb-98cb-dff049d6d37b:81:1876</a> at c (blob:<a href="https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:240:58" rel="nofollow">https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:240:58</a>) at blob:<a href="https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:201:710" rel="nofollow">https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:201:710</a> at gc (blob:<a href="https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:52:165" rel="nofollow">https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:52:165</a>) at cc (blob:<a href="https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:30:216" rel="nofollow">https://app.roll20.net/64d4a69b-a936-42df-ab07-f07db3ed2ee8:30:216</a>) S @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:45 (anonymous) @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:52 setTimeout (async) gc @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:52 cc @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:30 bc @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:29 Qi @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:224 h.Ib @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:238 h.Og @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:240 Backbone.sync @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:81 save @ c8b936a8-999c-419a-ad0c-1139090a6687:7 syncedSave @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:587 C.each.C.forEach @ c8b936a8-999c-419a-ad0c-1139090a6687:6 u.&lt;computed&gt; @ c8b936a8-999c-419a-ad0c-1139090a6687:7 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:587 64d4a69b-a936-42df-ab07-f07db3ed2ee8:123 Uncaught Error: Firebase.update failed: First argument contains undefined in property 'campaign-2954082-7vnGopGlg-8ESZgmjSX0Pg.char-attribs.char.-LDcRPgB_tKI6tkkZGdF.-LgJch7IcXXXskYLChD-.current' at hg (64d4a69b-a936-42df-ab07-f07db3ed2ee8:123) at 64d4a69b-a936-42df-ab07-f07db3ed2ee8:126 at Fb (64d4a69b-a936-42df-ab07-f07db3ed2ee8:28) at jg (64d4a69b-a936-42df-ab07-f07db3ed2ee8:126) at X.update (64d4a69b-a936-42df-ab07-f07db3ed2ee8:258) at c4301b0d-b37a-4abb-98cb-dff049d6d37b:81 at c (64d4a69b-a936-42df-ab07-f07db3ed2ee8:240) at 64d4a69b-a936-42df-ab07-f07db3ed2ee8:201 at gc (64d4a69b-a936-42df-ab07-f07db3ed2ee8:52) at cc (64d4a69b-a936-42df-ab07-f07db3ed2ee8:30) hg @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:123 (anonymous) @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:126 Fb @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:28 jg @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:126 X.update @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:258 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:81 c @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:240 (anonymous) @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:201 gc @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:52 cc @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:30 bc @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:29 Qi @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:224 h.Ib @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:238 h.Og @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:240 Backbone.sync @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:81 save @ c8b936a8-999c-419a-ad0c-1139090a6687:7 syncedSave @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:587 C.each.C.forEach @ c8b936a8-999c-419a-ad0c-1139090a6687:6 u.&lt;computed&gt; @ c8b936a8-999c-419a-ad0c-1139090a6687:7 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:587 setTimeout (async) gc @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:52 cc @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:30 bc @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:29 Qi @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:224 h.Ib @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:238 h.Og @ 64d4a69b-a936-42df-ab07-f07db3ed2ee8:240 Backbone.sync @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:81 save @ c8b936a8-999c-419a-ad0c-1139090a6687:7 syncedSave @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:569 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:587 C.each.C.forEach @ c8b936a8-999c-419a-ad0c-1139090a6687:6 u.&lt;computed&gt; @ c8b936a8-999c-419a-ad0c-1139090a6687:7 (anonymous) @ c4301b0d-b37a-4abb-98cb-dff049d6d37b:587 Latest code /* toggles default header images */ const header_images = { 'header_image-pf_spell': "[default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>)", 'header_image-pf_attack-melee': "[default](<a href="http://i.imgur.com/AGq5VBG.png" rel="nofollow">http://i.imgur.com/AGq5VBG.png</a>)", 'header_image-pf_attack-dual': "[default](<a href="http://i.imgur.com/Eh243RO.png" rel="nofollow">http://i.imgur.com/Eh243RO.png</a>)", 'header_image-pf_attack-ranged': "[default](<a href="http://imgur.com/58j2e8P.png" rel="nofollow">http://imgur.com/58j2e8P.png</a>)", 'header_image-pf_attack-cmb': "[default](<a href="http://i.imgur.com/Si4vfts.png" rel="nofollow">http://i.imgur.com/Si4vfts.png</a>)", 'header_image-pf_defense': "[default](<a href="http://imgur.com/02fV6wh.png" rel="nofollow">http://imgur.com/02fV6wh.png</a>)", 'header_image-pf_generic': "[default](<a href="http://imgur.com/phw1eFB.png" rel="nofollow">http://imgur.com/phw1eFB.png</a>)", 'header_image-pf_ability': "[default](<a href="http://i.imgur.com/UxYSva8.png" rel="nofollow">http://i.imgur.com/UxYSva8.png</a>)", 'header_image-pf_block': "[default](<a href="http://imgur.com/nBnv4DL.png" rel="nofollow">http://imgur.com/nBnv4DL.png</a>)", 'header_image-pf_generic-skill': "[default](<a href="http://imgur.com/8dCkRtG.png" rel="nofollow">http://imgur.com/8dCkRtG.png</a>)", 'header_image-pf_generic-init': "[default](<a href="http://i.imgur.com/pjS6HVJ.png" rel="nofollow">http://i.imgur.com/pjS6HVJ.png</a>)", 'header_image-pf_block-item': "[default](<a href="http://i.imgur.com/4FgQuqS.png" rel="nofollow">http://i.imgur.com/4FgQuqS.png</a>)", 'header_image-pf_block-check': "[default](<a href="http://i.imgur.com/a6O3ZGB.png" rel="nofollow">http://i.imgur.com/a6O3ZGB.png</a>)" }; const header_image_names = Object.keys('header_images'); on("change:header_images_toggle", function() { getAttrs(header_image_names.concat(['header_images_toggle']), function(values) { const tempToggle = values.header_images_toggle; let tempImages = {}; header_image_names.forEach( image =&gt; { const currentImage = values[image]; if(currentImage === header_images[image] || currentImage === '') { tempImages[image] = (tempToggle &gt; 0 ? currentImage : ''); } }); setAttrs(tempImages); }); }); .
1559424641

Edited 1559425020
GiGs
Pro
Sheet Author
API Scripter
wow, that's quite a chain of errors. I have no idea what would be causing that at the moment. I'd be testing it myself, but can't because roll20 have messed up my subscription and dropped me to a Free user. (Just before my first scheduled game session in months, too.) I'd insert a bunch of log statements, to check everything is what it should be. Something like: const header_images = { &nbsp; &nbsp; 'header_image-pf_spell': "[default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>)", &nbsp; &nbsp; 'header_image-pf_attack-melee': "[default](<a href="http://i.imgur.com/AGq5VBG.png" rel="nofollow">http://i.imgur.com/AGq5VBG.png</a>)", &nbsp; &nbsp; 'header_image-pf_attack-dual': "[default](<a href="http://i.imgur.com/Eh243RO.png" rel="nofollow">http://i.imgur.com/Eh243RO.png</a>)", &nbsp; &nbsp; 'header_image-pf_attack-ranged': "[default](<a href="http://imgur.com/58j2e8P.png" rel="nofollow">http://imgur.com/58j2e8P.png</a>)", &nbsp; &nbsp; 'header_image-pf_attack-cmb': "[default](<a href="http://i.imgur.com/Si4vfts.png" rel="nofollow">http://i.imgur.com/Si4vfts.png</a>)", &nbsp; &nbsp; 'header_image-pf_defense': "[default](<a href="http://imgur.com/02fV6wh.png" rel="nofollow">http://imgur.com/02fV6wh.png</a>)", &nbsp; &nbsp; 'header_image-pf_generic': "[default](<a href="http://imgur.com/phw1eFB.png" rel="nofollow">http://imgur.com/phw1eFB.png</a>)", &nbsp; &nbsp; 'header_image-pf_ability': "[default](<a href="http://i.imgur.com/UxYSva8.png" rel="nofollow">http://i.imgur.com/UxYSva8.png</a>)", &nbsp; &nbsp; 'header_image-pf_block': "[default](<a href="http://imgur.com/nBnv4DL.png" rel="nofollow">http://imgur.com/nBnv4DL.png</a>)", &nbsp; &nbsp; 'header_image-pf_generic-skill': "[default](<a href="http://imgur.com/8dCkRtG.png" rel="nofollow">http://imgur.com/8dCkRtG.png</a>)", &nbsp; &nbsp; 'header_image-pf_generic-init': "[default](<a href="http://i.imgur.com/pjS6HVJ.png" rel="nofollow">http://i.imgur.com/pjS6HVJ.png</a>)", &nbsp; &nbsp; 'header_image-pf_block-item': "[default](<a href="http://i.imgur.com/4FgQuqS.png" rel="nofollow">http://i.imgur.com/4FgQuqS.png</a>)", &nbsp; &nbsp; 'header_image-pf_block-check': "[default](<a href="http://i.imgur.com/a6O3ZGB.png" rel="nofollow">http://i.imgur.com/a6O3ZGB.png</a>)" }; const header_image_names = Object.keys('header_images');&nbsp; on("change:header_images_toggle", function() {&nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;getAttrs(header_image_names.concat(['header_images_toggle']), function(values) { &nbsp; &nbsp; &nbsp; &nbsp; const tempToggle = values.header_images_toggle; &nbsp; &nbsp; &nbsp; &nbsp; let tempImages = {}; &nbsp; &nbsp; &nbsp; &nbsp; header_image_names.forEach( image =&gt; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const currentImage = values[image]; log('=================== LOOP'); log('=================== image: ' + image); log('=========== values[Image]: ' + currentImage); log('==== header_images[Image]: ' + header_images[image]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(currentImage === header_images[image] || currentImage === '') {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tempImages[image] = (tempToggle &gt; 0 ? currentImage : '');&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } log('======= tempImages[image]: ' + tempImages[image]); &nbsp; &nbsp; &nbsp; &nbsp; }); log(' FINAL OBJECT: ' + JSON.stringify(tempImages)); &nbsp; &nbsp; &nbsp; &nbsp; setAttrs(tempImages); &nbsp; &nbsp; }); }); Also in that big error report, it gives a clue: campaign-2954082-7vnGopGlg-8ESZgmjSX0Pg.char-attribs.char.-LDcRPgB_tKI6tkkZGdF.-LgJch7IcXXXskYLChD-.current This is character id&nbsp;-LDcRPgB_tKI6tkkZGdF and attribute id&nbsp;-LgJch7IcXXXskYLChD-. So if you have an editable script that you use, you could insert a log statement finding out which attribute that is (the character will obviously be the one&nbsp; you're testing on). I cant help any more tonight, it's bedtime for me. I'm genuinely stumped over this error though (for the moment).
1559424770

Edited 1559424778
vÍnce
Pro
Sheet Author
Thanks for your help GiGs.&nbsp; No worries.&nbsp; Get some sleep.
1559425447
vÍnce
Pro
Sheet Author
Latest log CLICKED: radio/checkbox &lt;input type=​"checkbox" name=​"attr_header_images_toggle" value=​"1" title=​"@{header_images_toggle}​"&gt;​ e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 Really updating character sheet values 12e71981cd-0e78-4bd9-98bf-3ec4c9889855:570 Foudn a pre-defined key order! e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 Setting up repeating sections took until 74ms e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 Finding list of dirty attributes took until 74ms e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 Querytest took until 80ms e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 Attribute cache compliation took until 84ms e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 Set values (including auto-calcuating variables) took until 108ms e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 Took 113ms VM12:1 =================== LOOP VM12:1 =================== image: 0 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 1 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 2 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 3 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 4 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 5 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 6 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 7 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 8 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 9 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 10 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 11 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 =================== LOOP VM12:1 =================== image: 12 VM12:1 =========== values[Image]: undefined VM12:1 ==== header_images[Image]: undefined VM12:1 ======= tempImages[image]: undefined VM12:1 FINAL OBJECT: {} VM4333:3 Uncaught TypeError: Cannot read property 'toString' of undefined at Function.jqotenc (c5325ec3-8552-43a0-84da-ff8cdab9050c:20) at T.i.eval (eval at jqotec (c5325ec3-8552-43a0-84da-ff8cdab9050c:20), &lt;anonymous&gt;:3:407) at HTMLScriptElement.&lt;anonymous&gt; (c5325ec3-8552-43a0-84da-ff8cdab9050c:20) at Function.each (3de2b10b-ad98-4eea-938f-e7b664c50523:650) at jQuery.fn.init.each (3de2b10b-ad98-4eea-938f-e7b664c50523:270) at jQuery.fn.init.jqote (c5325ec3-8552-43a0-84da-ff8cdab9050c:20) at T.i.render (e71981cd-0e78-4bd9-98bf-3ec4c9889855:569) at T.i.&lt;anonymous&gt; (e71981cd-0e78-4bd9-98bf-3ec4c9889855:569) at T.i.trigger (c5325ec3-8552-43a0-84da-ff8cdab9050c:7) at T.i.change (c5325ec3-8552-43a0-84da-ff8cdab9050c:7) jqotenc @ c5325ec3-8552-43a0-84da-ff8cdab9050c:20 (anonymous) @ VM4333:3 (anonymous) @ c5325ec3-8552-43a0-84da-ff8cdab9050c:20 each @ 3de2b10b-ad98-4eea-938f-e7b664c50523:650 each @ 3de2b10b-ad98-4eea-938f-e7b664c50523:270 jqote @ c5325ec3-8552-43a0-84da-ff8cdab9050c:20 render @ e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 (anonymous) @ e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 trigger @ c5325ec3-8552-43a0-84da-ff8cdab9050c:7 change @ c5325ec3-8552-43a0-84da-ff8cdab9050c:7 set @ c5325ec3-8552-43a0-84da-ff8cdab9050c:7 save @ c5325ec3-8552-43a0-84da-ff8cdab9050c:7 syncedSave @ e71981cd-0e78-4bd9-98bf-3ec4c9889855:569 (anonymous) @ e71981cd-0e78-4bd9-98bf-3ec4c9889855:587 C.each.C.forEach @ c5325ec3-8552-43a0-84da-ff8cdab9050c:6 u.&lt;computed&gt; @ c5325ec3-8552-43a0-84da-ff8cdab9050c:7 (anonymous) @ e71981cd-0e78-4bd9-98bf-3ec4c9889855:587 .
1559439425
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
From previous experience, this error (can't tostring of undefined) means that you have created an "undefined" property in the setObj. Note, that I put undefined in quotes deliberately. The actual value wouldn't be undefined, as then the key doesn't exist in the object, but it is something similar. I'd recommend doing a log of your setObj as an object, not by JSON.stringifying it.
1559462050

Edited 1559462376
GiGs
Pro
Sheet Author
API Scripter
Aha, the solution was so simple. That log was really handy and helped me find the issue.&nbsp; Change this line const header_image_names = Object.keys('header_images');&nbsp; to const header_image_names = Object.keys(header_images);&nbsp; Oops! Javascript has so many ways to trip you up that are soooo hard to spot once you make the mistake hehe.
1559501513

Edited 1559501934
vÍnce
Pro
Sheet Author
Thanks for your help guys.&nbsp; I'm never sure when I need to wrap an attribute name in quotes... ;-(&nbsp; We are really close now .&nbsp; Checking the toggle doesn't change the null based header_images value to the coded URL, but un-checking the toggle changes the values of the coded URL back to null (as expected). So, maybe there's an issue in the if statement ? if(currentImage === header_images[image] || currentImage === '') { &nbsp;&nbsp;&nbsp;&nbsp;tempImages[image] = (tempToggle &gt; 0 ? currentImage : ''); } here's the log of pf_spell after I check the toggle to set all header_images to the coded URL's.&nbsp; pf_spell is currently set to null (and remains so after checking) =================== LOOP VM12:1 =================== image: header_image-pf_spell VM12:1 =========== values[Image]: VM12:1 ==== header_images[Image]: [default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>) VM12:1 ======= tempImages[image]: .
1559505803

Edited 1559505928
GiGs
Pro
Sheet Author
API Scripter
You're right, the if statement is the problem. the currentImage in there should actually be&nbsp;header_images[image] currentImage is the value already in the cell. We want to overwrite that with the equivalent value from the header_images object. Try this: on("change:header_images_toggle", function () { &nbsp; &nbsp; getAttrs(header_image_names.concat(['header_images_toggle']), function (values) { &nbsp; &nbsp; &nbsp; &nbsp; const tempToggle = +values.header_images_toggle || 0; &nbsp; &nbsp; &nbsp; &nbsp; let tempImages = {}; &nbsp; &nbsp; &nbsp; &nbsp; header_image_names.forEach(image =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const currentImage = values[image]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (currentImage === header_images[image] || currentImage === '') { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tempImages[image] = tempToggle &gt; 0 ? header_images[image] : ''; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; if(tempImages) setAttrs(tempImages); &nbsp; &nbsp; }); }); While testing this I noticed there's a potential error with the setAttrs line. We also have to account for the case where tempImages is empty, and no setAttrs is needed. Also, noticed there was no parseInt on the checkbox value, so added that. (Probably not needed, but after all this I didnt want to take any chances! Writing this script has not been my finest hour!)
1559506930
vÍnce
Pro
Sheet Author
GiGs said: Writing this script has not been my finest hour!) Oh please. lol Works like a champ now GiGs! Thank you for your help as always.&nbsp; I learn a little more each time I post for help. Thanks final working code for anyone following /* toggles default header images */ const header_images = { 'header_image-pf_spell': "[default](<a href="http://imgur.com/9yjOsAD.png" rel="nofollow">http://imgur.com/9yjOsAD.png</a>)", 'header_image-pf_attack-melee': "[default](<a href="http://i.imgur.com/AGq5VBG.png" rel="nofollow">http://i.imgur.com/AGq5VBG.png</a>)", 'header_image-pf_attack-dual': "[default](<a href="http://i.imgur.com/Eh243RO.png" rel="nofollow">http://i.imgur.com/Eh243RO.png</a>)", 'header_image-pf_attack-ranged': "[default](<a href="http://imgur.com/58j2e8P.png" rel="nofollow">http://imgur.com/58j2e8P.png</a>)", 'header_image-pf_attack-cmb': "[default](<a href="http://i.imgur.com/Si4vfts.png" rel="nofollow">http://i.imgur.com/Si4vfts.png</a>)", 'header_image-pf_defense': "[default](<a href="http://imgur.com/02fV6wh.png" rel="nofollow">http://imgur.com/02fV6wh.png</a>)", 'header_image-pf_generic': "[default](<a href="http://imgur.com/phw1eFB.png" rel="nofollow">http://imgur.com/phw1eFB.png</a>)", 'header_image-pf_ability': "[default](<a href="http://i.imgur.com/UxYSva8.png" rel="nofollow">http://i.imgur.com/UxYSva8.png</a>)", 'header_image-pf_block': "[default](<a href="http://imgur.com/nBnv4DL.png" rel="nofollow">http://imgur.com/nBnv4DL.png</a>)", 'header_image-pf_generic-skill': "[default](<a href="http://imgur.com/8dCkRtG.png" rel="nofollow">http://imgur.com/8dCkRtG.png</a>)", 'header_image-pf_generic-init': "[default](<a href="http://i.imgur.com/pjS6HVJ.png" rel="nofollow">http://i.imgur.com/pjS6HVJ.png</a>)", 'header_image-pf_block-item': "[default](<a href="http://i.imgur.com/4FgQuqS.png" rel="nofollow">http://i.imgur.com/4FgQuqS.png</a>)", 'header_image-pf_block-check': "[default](<a href="http://i.imgur.com/a6O3ZGB.png" rel="nofollow">http://i.imgur.com/a6O3ZGB.png</a>)" }; const header_image_names = Object.keys(header_images); on("change:header_images_toggle", function () { getAttrs(header_image_names.concat(['header_images_toggle']), function (values) { const tempToggle = +values.header_images_toggle || 0; let tempImages = {}; header_image_names.forEach(image =&gt; { const currentImage = values[image]; if (currentImage === header_images[image] || currentImage === '') { tempImages[image] = tempToggle &gt; 0 ? header_images[image] : ''; } }); if(tempImages) setAttrs(tempImages); }); }); .
1559507089
GiGs
Pro
Sheet Author
API Scripter
Yay! we got there in the end :)