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

Javascript question: Using multiple values as one

1631637542
Rondragos
Pro
Sheet Author
I'm building a character sheet (and this isn't my first question), where I have radios disguised as checkboxes that fill from left to right. Works pretty well, unless I try to use the same attribute twice (or more) on the sheet (since I want to have the bloodpool ressource in different places). I figured that it could be possible to have the pools in different places save in different Attributes (e.g. bloodpool1 and bloodpool2) and then use a sheetworker whenever one changes, to change the other one as well as the "right" bloodpool linked to a hidden input (for use with tokens). I'm not a good programmer and have only a little experience with Java from years ago, but I wrote (copied together) simple sheetworker scripts. So I tried my idea and came up to this: const bloodlist = ["bloodpool1, bloodpool2"]; bloodlist.forEach(pool => { on(`change:${pool}`, function() { getAttr([pool], function(values) { let bp = values[pool]; bloodlist.forEach(function(bpool) { setAttr({ bpool: bp }); }); setAttr({ bloodpool: bp }); }); }); }); (yeah I'm not happy with variable names either, but it isn't so long...) And.. it doesn't work. Since I have almost no experience in webdevelopment, my debugging skills are also almost nonexistant for this application.  Since I know here are wizards and mighty geniuses, I'm bothering you, so please help! P.S.: I'm not quite sure, but might it be that this little script creates an endless loop? If so there must be a failsafe since my browser didn't implode
1631639487
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Hi Rondragos, Welcome to the world of sheetworkers! You are indeed correct that you are creating an endless loop, although without knowing what exactly isn't working, I'm not actually sure if that's what's causing your undesired behavior. But, let's at least get that endless loop fixed. So, the cause of the endless loop is that you are using setAttrs without telling it to be silent. This means that any changes it makes to attributes will trigger a change event that will be picked up by listeners. Some around the forums disagree with me, but my personal opinion is that, except in some very specific situations, setAttrs should always be used with the silent option enabled; in fact I use an alias for setAttrs in my own sheets that auto enables that: //invokes setAttrs, checks the validity of setObj property values. If vocal is truthy, will not use the silent option. The callback variable should almost never be used const set = function(obj,vocal,callback){ let setObj = verifyObj(obj); setAttrs(setObj,{silent:!vocal},callback); }; //verifies that the property/keys of the setObj are valid const verifyObj = function(obj){ return Object.keys(obj).reduce((m,p)=>{ if(obj[p]===undefined || obj[p]===null || obj[p] === NaN){ debug(`WARNING: setObj[${p}] = ${obj[p]}`); }else{ m[p]=obj[p]; } return m; },{}) }; Now, all of that said, the problem that you're running into and needing this solution is that radios do not propagate their check state to other instances of the radio button that have the same check value. To get around this, instead of using the sheetworker method, and duplicating many attributes, you could instead switch the radios to hiddens and then style for checked/unchecked based off of the value of the hidden. This method does have some downsides, mostly that you need to specify the values to look for in the hidden instead of just the :checked flag, but I wanted to mention it as attribute bloat is one of the primary contributors to sheet (and game) lag and should be avoided when possible.
1631643752
Rondragos
Pro
Sheet Author
Thank you for your answer, Scott! Your code snippet looks (as I can judge with my poor programming skills) very useful, but I figured that I could just use the silent option in SetAttr. Just to be sure, that should end the loop: const bloodlist = ["bloodpool1, bloodpool2"]; bloodlist.forEach(pool => { on(`change:${pool}`, function() { getAttr([pool], function(values) { let bp = values[pool]; bloodlist.forEach(function(bpool) { setAttr({ bpool: bp }, silent); }); setAttr({ bloodpool: bp }); }); }); }); or am I mistaken? If that fixes the loop, something else is amiss since none of the attributes updates on changing one of them. To your information: The attribute bloat shouldn't be too bad, since I think it may be 4 or 5 pools in 2 or 3 places (I will see when I'm finished) but I tried to avoid writing the css for up to 50 "fake" checkboxes...
1631645396

Edited 1631646212
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Hmm, yes, using the silent option will end the loop. My function is just a way not to have to write in {silent:true} every time I call setAttrs. So, the next question is what does the html for these look like? EDIT:  A few things I noticed after I posted. You do still have a loop going on here because of an extra setAttrs. The silent option needs to be passed as part of an object, e.g. {silent:true} instead of silent In your forEach, you are trying to set an attribute named bpool, not bloodpool1/bloodpool2. In order to dynamically set a property of an object, you need to use object literal syntax. For this reason, I like to define an empty object to aggregate my changes in even for simple changes. This has a couple benefits of making it easier to log what it is being set to troubleshoot behaviors allowing us to use only a single setAttrs call instead of multiple. So, taking all of that, your code would look like: const bloodlist = ["bloodpool1, bloodpool2"]; bloodlist.forEach(pool => { on(`change:${pool}`, function() { getAttr([pool], function(values) { const setObj = {};//Empty object to collect our changes in. let bp = values[pool]; bloodlist.forEach(function(bpool) { setObj[bpool] = bp; }); setAttrs(setObj,{silent:true}); }); }); });
1631704726
Rondragos
Pro
Sheet Author
After I finally found the problem (typos - missing s in getAttrs and the bloodlist should be "bloodpool1", "bloodpool2"), it really worked and that very smoothly! Thank you Scott for helping this complete moron.
1631817924
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
No worries! and you aren't a moron. We've all been there. Heck, I'm in that position at least once a week.
1631825941
GiGs
Pro
Sheet Author
API Scripter
Scott C. said: No worries! and you aren't a moron. We've all been there. Heck, I'm in that position at least once a week. I was going to say, "Only once a week? What a slacker!" then saw the "at least", which is more relatable :)
1631826319
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Heh, to be more accurate, I suppose I should actually say "at least once a day"