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

Two Variables Off One Dropdown Option

So I'm going to start asking the question theoretically, and based on your answers, I'll try to whip together a solution and see if I have a follow-up. In the L5R sheet, the author had two drop-down menus for the ring (think stat) used to cast a spell. That puzzled me until I got into the code. The first drop-down set the ring that character uses for the purposes of rolling dice. The second sets "affinity--basically a bonus or penalty the caster gets with certain rings. I think it's kludgy to have two ring dropdowns for every spell. So, my options: 1) Ideally, I could select the option in a single drop down and have it set the value of two variables, but I don't think that's actually possible. 2) As a workaround, instead of having the character select their affinities and deficiencies and have the sheet automatically calculate the result, I could have them manually enter a die offset that the roller then handles through straight math. Not as pretty, but only ugly in one spot, instead of ugly in every spell. 3) I could make a sheet worker that fires off every time the ring is set, reads which of the five rings it's set to, and populates the affinity variable for that spell with the value of the affinity variable of the ring. That would require some kind of conditional, though, to interpret the result of the selection. Is that possible in a sheet worker, or only in an API? If the answer is 1 or 3, could you give me somewhere to start on the syntax? :) Thanks.
1495763421
Lithl
Pro
Sheet Author
API Scripter
#1 is only possible with a small set of calculations, so it's likely not useful here. #2 and #3 are both possible, and #3 is likely the most user-friendly. Here's an example of that kind of sheet worker: <select name="attr_example-select"> <option value="1">First option</option> <option value="2">Second option</option> </select> <button type="roll" name="roll_example-roll" value="@{calc1} + @{calc2}"></button> <script type="text/worker"> on('sheet:opened change:example-select', () => { getAttrs(['example-select'], (values) => { const val = parseInt(values['example-select']); let calc1; let calc2; switch (val) { case 1: calc1 = 5; calc2 = 6; break; case 2: calc1 = 10; calc2 = 12; break; default: console.warn('Unknown value of @{example-select}'); calc1 = 0; calc2 = 0; break; } setAttrs({calc1, calc2}); }); }); </script>
Cool. I'll run out to w3schools, try to figure out what all that means, and throw something together that fails, but that you can critique. :)
Okay. Here's my run on that equation. As a background, a dropdown has set the variable @{spellRing}, which sets the value of the stat relevant to the spell. The field I'm trying to eliminate takes the value of the variable for the affinity of the ring (@{earthAffinity}, @{airAffinity}) and assign them to @{spellAffinity}. The @{shugenja_rank} is set generally above for all the spells. This allows us to make this roll: [[@{spellRing}+@{spellAffinity}+@{shugenja_rank}]]d10k[[@{spellRing}]]!! So I'm trying to make it so that setting that @{spellRing} also sets the @{spellAffinity} variable, since they are tied to the same stat, and setting it twice makes my players shout, "This is stupid" to me across Google hangouts. I think I need to change that that dropdown the change the stat to the by a unique identifier, if I'm reading this right, instead of a value. So I'll make a new variable call @{spellRingID} that will acually be set by the dropdown. It will be: Air=1 Earth=2 Fire=3 Water=4 Void=5 <script type=”text/worker”> on('sheet:opened change:spellRing change:airAffinity change:earthAffinity change:waterAffinity change:fireAffinity change:voidAffinity change:<<Anything else I need>>', () => { getAttrs(['spellRingID', 'airspell', 'foo_Air', 'foo_Earth', 'foo_Fire', 'foo_Water', 'foo_Void', 'airAffinity', 'earthAffinity' 'waterAffinity', 'fireAffinity', 'voidAffinity'], (values) => { const ringval = parseInt(values['spellRingID']); let spellRing; let spellAffinity; switch (ringval) { case 1: spellRing = values.foo_Air; spellAffinity = values.airAffinity; break; case 2: spellRing = values.foo_Earth; spellAffinity = values.earthAffinity; break; case 3: spellRing = values.foo_Fire; spellAffinity = values.fireAffinity; break; case 4: spellRing = values.foo_Water; spellAffinity = values.waterAffinity; break; case 5: spellRing = values.foo_Void; spellAffinity = values.voidAffinity; break; default: console.warn('Unknown value of @{spellRing}'); spellRing = 0; spellAffinity = 0; break; } setAttrs({spellRing, spellAffinity}); }); }); </script> Does that make sense? I'm not sure how those spellRings and spellAffinities would look inside a case, or if my uniqueID connects to the case like I assume, or if the ringval is a valid thing to call it or if this makes any sense at all. But it's a start. :)
1495921353
Lithl
Pro
Sheet Author
API Scripter
LGTM. The real question is if it solves your problem! =)
Thanks. I'll try it today. I'll have to rework the surrounding stuff just a bit to support it. I did not think for even a second I'd get the syntax anywhere near right on my first stab. :)
It's NOT working. I looks like if the spell previously had a value set, it's retained that value. if I create a new spell with a new character, it's using the default values I set when I create att_spellRing and att_spellAffinity. It's not assigning values when I switch the drop down. I'm not seeing any errors. Any problems with my variables or how I'm passing the data out of the script and back to the sheet?
Also, this is inside a repeating section, if that changes the way the variables are called.
1495960203
Jakob
Sheet Author
API Scripter
2 remarks: 1) Why don't you let the dropdown set spellRing and modify spellAffinity as a result? There doesn't seem to be a need for the spellRingID. 2) This produces a syntax error in line 3, since you forgot a comma in the array literal. So this sheet worker won't ever run. For the future: run a syntax checker over your sheet worker code (or any code, really). This is why it's not even producing any errors in the console.
1495989746

Edited 1496014605
Robert D.
Sheet Author
EDITED because I remembered why I needed a spellRingID. I fixed the typo but it still isn't working. It compiles. Thanks, though, that was sloppy. That'll teach me to post during a game. I WANTED to set affinity off the spellRing, but the spellRing is the value of the stat, for rolling purposes, not the name. It isn't a unique identifier. For starting characters, many people will all have 2 in all their spellRing's.
Okay, I've spent hours trying different permutations now, and I just can't get it to make any change at all. I don't really understand it. My best theory is that maybe it's not doing the variables right and not passing the values out of the worker. Whatever the case, whatever's happening to spellRing and spellAffinity inside the worker has no effect on spellRing and spellAffinity in the roll command outside the worker. It's in a repeating section. Here's the complete code of the section: <div> <fieldset> <table style="width:50%;"> <tr><td><strong data-i18n="name-u">Name:</strong><input type="text" name="attr_spellName" /></td> <td><strong data-i18n="ring-u">Ring:</strong><select name="attr_spellRingID"> <option data-i18n="air-u" value="1">Air</option> <option data-i18n="earth-u" value="2">Earth</option> <option data-i18n="fire-u" value="3">Fire</option> <option data-i18n="water-u" value="4">Water</option> <option data-i18n="void-u" value="5">Void</option></select></td> <td><strong data-i18n="level-u">Level:</strong><input type="number" name="attr_spellLevel" /></td></tr> <input type="hidden" name="attr_spellRing" value="2" /> <input type="hidden" name="attr_spellAffinity" value="0" /> <script type=”text/worker”> on('sheet:opened change:spellRing change:spellRingID change:airAffinity change:earthAffinity change:waterAffinity change:fireAffinity change:voidAffinity change:<<Anything else I need>>', () => { getAttrs(['spellRingID', 'airspell', 'foo_Air', 'foo_Earth', 'foo_Fire', 'foo_Water', 'foo_Void', 'airAffinity', 'earthAffinity', 'waterAffinity', 'fireAffinity', 'voidAffinity'], (values) => { const ringval = parseInt(values['spellRingID']); let spellRing; let spellAffinity; switch (ringval) { case 1: spellRing = values.foo_Air; spellAffinity = values.airAffinity; break; case 2: spellRing = values.foo_Earth; spellAffinity = values.earthAffinity; break; case 3: spellRing = values.foo_Fire; spellAffinity = values.fireAffinity; break; case 4: spellRing = values.foo_Water; spellAffinity = values.waterAffinity; break; case 5: spellRing = values.foo_Void; spellAffinity = values.voidAffinity; break; default: console.warn('Unknown value of @{spellRing}'); spellRing = 0; spellAffinity = 0; break; } setAttrs({spellRing, spellAffinity}); }); }); </script> <tr><td><strong data-i18n="range-u">Range:</strong><input type="text" name="attr_spellRange"/></td> <td><strong data-i18n="aoe-u">AoE:</strong><input type="text" name="attr_spellAoE"></td></tr> <tr><td><strong data-i18n="duration-u">Duration</strong><input type="text" name="attr_spellDuration" /></td> <td><strong data-i18n="raises-u">Raises</strong><input type="text" name="attr_spellRaises"></td> <td><input type="hidden" name="attr_spellTN" value="5 + @{spellLevel} * 5"><strong data-i18n="cast-u">Cast</strong><button type="roll" value = "/em @{Name} casts @{spellName}!\n\n/roll [[@{spellRing}+@{spellAffinity}+@{shugenja_rank}]]d10k[[@{spellRing}]]!!\n\n vs TN [[@{spellTN}]]"></button></td></tr> </table> </fieldset> <span data-i18n="sentence-spell-warning-u">Make sure that both Rings have the same value. Otherwise, Affinities will not display properly.</span> </div> </div> </div>
1496094527

Edited 1496095082
Jakob
Sheet Author
API Scripter
Okay, first, very important style guide: don't write your sheet workers inline, put them all into a section at the end. For readability. Second, even more important: the quotes around your text/worker are NOT straight quotes, they are slightly slanted. What text editor are you writing this in? Anyway, due to this, no sheet workers will function at all. In any case, there are several problems with your implementation due to funky interactions with how repeating sections work. I don't have time to explain all the intricacies, but let me give you a minimal working example. Note that having a repeating_blabla class for the fieldset is mandatory. <input type="number" name="attr_foo_air" value="0" /> <input type="number" name="attr_foo_earth" value="0" /> <input type="number" name="attr_air_affinity" value="0" /> <input type="number" name="attr_earth_affinity" value="0" /> <fieldset class="repeating_spell"> <select name="attr_spell_ring_type"> <option value="" selected>-</option> <option value="air">Air</option> <option value="earth">Earth</option> </select><br> <input type="hidden" name="attr_spell_affinity" value="0" /> <input type="hidden" name="attr_spell_ring" value="0" /> <span>Spell ring is: </span><span name=attr_spell_ring></span><br> <span>Spell affinity is: </span><span name=attr_spell_affinity></span> </fieldset> <script type="text/worker"> var elements = ['air', 'earth'],   elementAttrs = [...elements.map(s => `foo_${s}`), ...elements.map(s => `${s}_affinity`)]; // Handle changes for foo_bla and bla_affinity: process all repeating sections on(elementAttrs.map(s => `change:${s}`).join(' '), function () {   getSectionIDs('repeating_spell', function (idArray) {     let allAttrs = idArray.map(id => `repeating_spell_${id}_spell_ring_type`).concat(elementAttrs);     getAttrs(allAttrs, function (values) {       let setting = {};       idArray.forEach(function (id) {         switch (values[`repeating_spell_${id}_spell_ring_type`]) {         case 'air':           setting[`repeating_spell_${id}_spell_ring`] = values.foo_air;           setting[`repeating_spell_${id}_spell_affinity`] = values.air_affinity;           break;         case 'earth':           setting[`repeating_spell_${id}_spell_ring`] = values.foo_earth;           setting[`repeating_spell_${id}_spell_affinity`] = values.earth_affinity;           break;         default:           setting[`repeating_spell_${id}_spell_ring`] = 0;           setting[`repeating_spell_${id}_spell_affinity`] = 0;         }       });       setAttrs(setting);     });   }); }); // Only do stuff within one row whenever spell_ring_type changes on('change:repeating_spell:spell_ring_type', function (e) {   getAttrs([...elementAttrs, 'repeating_spell_spell_ring_type'], function (values) {     let setting = {};     switch (values[`repeating_spell_spell_ring_type`]) {     case 'air':       setting[`repeating_spell_spell_ring`] = values.foo_air;       setting[`repeating_spell_spell_affinity`] = values.air_affinity;       break;     case 'earth':       setting[`repeating_spell_spell_ring`] = values.foo_earth;       setting[`repeating_spell_spell_affinity`] = values.earth_affinity;       break;     default:       setting[`repeating_spell_spell_ring`] = 0;       setting[`repeating_spell_spell_affinity`] = 0;     }     setAttrs(setting);   }); }); </script> One could make the whole interior part somewhat more elegant by leaving out the switch and reduce needless code duplication, but I'll leave it like this for simplicity.
Thanks! I'm using notepad++ but I've copied the sheetworkers out of threads before as I tried different implementations, so the curly quotes are probably from someone else's editor. I'll try this. I figured the repeating section might be part of the problem.
1496103806

Edited 1496103930
Robert D.
Sheet Author
Hmm. Doesn't seem to be working. They are all set to the same ratings they were before I started. I've selected their tokens and typed @{selected|foo_earth}, etc, into chat to make sure I got back actual values. :) I tried the version you put up (filled out with the other elements), as well as a version where I replaced spell_ring with spellRing and _affinity with Affinity in case that wasn't an intentional change. But spellRing and spellAffinity aren't changing when I set the drop down.
1496212202
Jakob
Sheet Author
API Scripter
If I just put in my version as-is, it works, so I guess the problem is with filling it out with other elements. The replacement was intentional; sheet worker events are always triggered in lower case no matter the attribute name, so change:spellRing never occurs, change:spellring is triggered instead. So I would lowercase all attribute names just to reduce confusion when dealing with sheet workers, and then underscores come in to make things readable.
1496213578

Edited 1496214145
Robert D.
Sheet Author
Okay, tomorrow I'll strip it down to a straight copy and paste of yours and report what I get. And thanks so much for your help. I thought counting the repeating ranks would be the hardest thing I did. I have no idea this one would be so much more difficult. One thing I did notice is that your... Spell ring is: Spell affinity is: ...spans didn't return anything on my sheet until I took out the underscores in spell_ring and spell_affinity. Then they returned the variables that the dice are rolling. So that's why I thought they had to go. But again, I'll test tomorrow. Just heading off to bed. Thank you so much for your patience on this. If you see this before I test, what variables are you looking at to know it worked, spellRing and spellAfifinity?
1496217060

Edited 1496217087
Jakob
Sheet Author
API Scripter
I use precisely the  Spell ring is: Spell affinity is: to check what the values of spell_ring and spell_affinity (in that repeating row) are. (You just need to select something in the dropdown for the values to populate, due to how repeating rows don't really exist until you've set an attribute). So when I've set foo_air to 3 and air_affinity to 7 and I change the dropdown to Air, it becomes Spell ring is: 3 Spell affinity is: 7 and when I then change foo_air to 4, it does the expected thing too.
1496241222

Edited 1496241783
Robert D.
Sheet Author
I figured that out at the very end (and by the very end I mean lying in bed last night after posting). I've been using the rolls to determine if it worked. Didn't know that during my early tests, when it might have been working, before I started trying different things to fix it. Am I suppose to be changing my variables elsewhere in my code to match the ones you outlined here? Is it possible that our breakdown is just that I assumed you would write the variable to work with the ones I told you I was using and you assumed I'd rewrite the other instances in the sheet to match the ones you wrote here? Just woke up. :)
1496244781
Jakob
Sheet Author
API Scripter
Yes, that would be the problem, sorry, that's why I said "minimal example" instead of "good solution to your problem". Sorry.  Different attribute names (for the affinities, for example) would be a problem.  Still, I  implore you to have good attribute names before you release the sheet, harder to change those afterwards :).
I'm cool with it. I inherited almost all of them. foo_earth and the like are mine. spellRing and the like are legacy. My plan for lunch is to rip out the whole section, plug in your code, and when I get it working, start building the section back up a few elements at a time.
So far so good. Hit a few challenges and bugs, but I've troubleshot my way past them all.
1496282385

Edited 1496282442
Robert D.
Sheet Author
Hey, Jakob, I'd love your advice. That section of the sheet is going well, but I realized as it when that changing the variable naming on every variable in that section is going to break every spell for every person that uses this sheet. Not a huge deal for me and my fledgling campaign, but potentially a huge deal for people with long-standing campaigns. Do you have advice or suggestions? (I'm sure I'm not the only person to face this sort of dilemma and no need to reinvent the wheel).
1496303196

Edited 1496306150
Jakob
Sheet Author
API Scripter
It depends on a few things. I can make you a conversion script for just straight renaming variables; but I suspect that it's going to be a bit more complicated in the spellRing case since you want to transfer the data that is currently there to the new select somehow, right? Anyway, here's the simple renaming version. You can also use the version attribute to track when some upgrades need to be applied to the sheet data. Make a conversion script: var convertFromOldSheet = function () {   // Non-repeating attributes to rename   let conversionData = {       'airAffinity': 'air_affinity',       'earthAffinity': 'earth_affinity'     },     // Repeating attributes to rename; this assumes that the section name, e.g.     // repeating_spell, does NOT change, so make sure that that is the case or     // it will need quite a bit mor work     conversionDataRepeating = {       'repeating_spell': {         'spellRing': 'spell_ring'       }     };   // Non-repeating attribute renaming   getAttrs(Object.keys(conversionData), function (values) {     let setting = {};     Object.keys(conversionData).forEach(function (oldAttr) {       if (values[oldAttr] != null) {         // set value of new attribute to the old one         setting[conversionData[oldAttr]] = values[oldAttr];         // blank old attribute         setting[oldAttr] = '';       }     });     setAttrs(setting, {silent: true}); // set things silently to make sure we do not trigger unwanted changes   });   // Repeating attribute renaming - loop through all sections, and loop through all ids   // and all attributes for every section   Object.keys(conversionDataRepeating).forEach(function (sectionName) {     let data = conversionDataRepeating[sectionName];     getSectionIDs(sectionName, function (idArray) {       let oldAttrs = idArray.reduce(function (m, id) {         return m.concat(Object.keys(data).map(name => `${sectionName}_${id}_${name}`));       }, []);       getAttrs(oldAttrs, function (values) {         let setting = {};         idArray.forEach(function (id) {           Object.keys(data).forEach(function (oldAttr) {             if (values[`${sectionName}_${id}_${oldAttr}`] != null) {               // set value of new attribute to the old one               setting[`${sectionName}_${id}_${data[oldAttr]}`] = values[`${sectionName}_${id}_${oldAttr}`];               // blank old attribute               setting[`${sectionName}_${id}_${oldAttr}`] = '';             }           });         });         setAttrs(setting, {silent: true}); // set things silently to make sure we do not trigger unwanted changes       });     });   }); }; on('sheet:opened', function () {   let currentVersion = 1.0;   getAttrs(['version'], function (v) {     if (!v.version) {       convertFromOldSheet();     };     setAttrs({       version: currentVersion     });   }); }); This will only run on sheets which do not have a version attribute (I am assuming that the previous sheet version doesn't have sheet workers that set a version - if it does, you'll need to increment it and include some logic for comparing it to yours. Since the script sets the version attribute itself, this will only run once for every sheet (unless you delete the version attribute again). This is untested, so I would check it for the inevitable errors.
1496327655

Edited 1496327670
Robert D.
Sheet Author
So this isn't copy the data from the old variable to the new? It looks to me like it is. Yeah, if I can make a script that copies the data over, I'll fix all the variables in the sheet.
This seems to be working. The only thing I think it WON'T do is handle the spellRing and spellAffinity. The reason is the old version of the variable just had the stat's value in it (and most stats are just "2"), not which stat is had derived the value from. The affinity also just had a value. In the majority of characters, you will have at least two rings where the value of the stat is 2 and the affinity is 0, so I don't think you can even build a logic to reverse calculate what had been used to select those values. I think I'm just going to need to force people to reselect that drop down. Unless there's a way to tell which item of a dropdown is selected and make the decision off that. The sheet remembers and keeps that drop down selected, that's just not the value that's in the actual variable. At some point I'll need to talk to you about what the logic for checking the version might look like, but that is not a "today" conversation. :)
1496401424

Edited 1496401435
Jakob
Sheet Author
API Scripter
No. The value in the variable is (for example) "@{Air}", not "2". So you can write a script to convert that.
So I'll tackle this in two steps. Here's my first stab at just the conversion process. I would plug this into your greater conversion piece you helped me with. I can't stress enough that this doesn't work. :) I'm pretty sure I'm not understanding the variables right and I know that when I stripped out the "on" commands (since I think that will be called by your code) I broke the functions at least one of them called. But I wanted to try to meet you halfway on this one. Tell me how close I came. var elements = ['@{air}', '@{earth}', '@{fire}', '@{water}', '@{void}'], elementAttrs = [...elements.map(s => `foo_${s}`), ...elements.map(s => `${s}_affinity`)]; // Handle changes for foo_bla and bla_affinity: process all repeating sections elementAttrs.map(s => `change:${s}`).join(' '), function () { getSectionIDs('repeating_spells', function (idArray) { let allAttrs = idArray.map(id => `repeating_spells_${id}_spell_ring_type`).concat(elementAttrs); getAttrs(["allAttrs", "spellRing"], function (values) { let setting = {}; idArray.forEach(function (id) { switch (values[`repeating_spells_${id}_spellRing`]) { case '@{air}': setting[`repeating_spells_${id}_spell_ring`] = values.foo_air; setting[`repeating_spells_${id}_spell_affinity`] = values.air_affinity; setting[`repeating_spells_${id}_spell_ring_type`] = 'air'; break; case '@{earth}': setting[`repeating_spells_${id}_spell_ring`] = values.foo_earth; setting[`repeating_spells_${id}_spell_affinity`] = values.earth_affinity; setting[`repeating_spells_${id}_spell_ring_type`] = 'earth'; break; case '@{fire}': setting[`repeating_spells_${id}_spell_ring`] = values.foo_fire; setting[`repeating_spells_${id}_spell_affinity`] = values.fire_affinity; setting[`repeating_spells_${id}_spell_ring_type`] = 'fire'; break; case '@{water}': setting[`repeating_spells_${id}_spell_ring`] = values.foo_water; setting[`repeating_spells_${id}_spell_affinity`] = values.water_affinity; setting[`repeating_spells_${id}_spell_ring_type`] = 'water'; break; case '@{void}': setting[`repeating_spells_${id}_spell_ring`] = values.foo_void; setting[`repeating_spells_${id}_spell_affinity`] = values.void_affinity; setting[`repeating_spells_${id}_spell_ring_type`] = 'void'; break; default: setting[`repeating_spells_${id}_spell_ring`] = 0; setting[`repeating_spells_${id}_spell_affinity`] = 0; } }); setAttrs(setting); }); }); }; // Only do stuff within one row whenever spell_ring_type changes function (e) { getAttrs([...elementAttrs, 'repeating_spells_spell_ring_type'], function (values) { let setting = {}; switch (values[`repeating_spells_spellRing`]) { case '@{air}': setting[`repeating_spells_spell_ring`] = values.foo_air; setting[`repeating_spells_spell_affinity`] = values.air_affinity; setting[`repeating_spells_spell_ring_type] = 'air'; break; case '@{earth}': setting[`repeating_spells_spell_ring`] = values.foo_earth; setting[`repeating_spells_spell_affinity`] = values.earth_affinity; setting[`repeating_spells_spell_ring_type] = 'earth'; break; case '@{fire}': setting[`repeating_spells_spell_ring`] = values.foo_fire; setting[`repeating_spells_spell_affinity`] = values.fire_affinity; setting[`repeating_spells_spell_ring_type] = 'fire'; break; case '@{water}': setting[`repeating_spells_spell_ring`] = values.foo_water; setting[`repeating_spells_spell_affinity`] = values.water_affinity; setting[`repeating_spells_spell_ring_type] = 'fire'; break; case '@{void}': setting[`repeating_spells_spell_ring`] = parseInt(values.foo_void); setting[`repeating_spells_spell_affinity`] = values.void_affinity; setting[`repeating_spells_spell_ring_type] = 'void'; break; default: setting[`repeating_spells_spell_ring`] = 0; setting[`repeating_spells_spell_affinity`] = 0; setting[`repeating_spells_spell_ring_type] = '-'; } setAttrs(setting); }); };
1496741362
Jakob
Sheet Author
API Scripter
OK. There's a lot to fix here, and I don't have time to get into every detail, but: I trust that you can fit it into the code in a way that works, because currently it doesn't make any sense standing by itself elements should be ['air', 'earth',...] if you look at it closely. getAttrs(["allAttrs", "spellRing"] makes no sense. You probably want [...allAttrs, "spellRing"], but that is still wrong, because you want to get the spellRing for every repeating row, not a global attribute named spellRing. So you'll want [...allAttrs, ...idArray.map(id =>  `repeating_spells_${id}_spellRing`)]. Case matters for attribute values. The value of spellRing will be @{Air}, not @{air}, because that's the way the values were defined in the sheet. You do not need to set spell_ring and spell_affinity manually, they will be set by the other worker scripts once you set spell_ring_type. The whole second part (after "// Only do stuff within one row whenever spell_ring_type changes") seems completely unnecessary. General remark. Why don't you copy the model of the original sheet like this: <select name="attr_spell_ring"> <option value="" selected>-</option> <option value="@{foo_air}>Air</option> <option value="@{foo_earth}>Earth</option> </select> + Changes in the sheet workers to set affinity correctly. There's no need for spell_ring_type. It's just an extra attribute that doesn't to anything.
Quick question, before I take another pass at it. I was setting the spell_ring_type to set the drop down menu. If I don't do that, won't the drop down be set to '-' even if the variables are set correctly? The player needs to be able to see, looking at the spell, what the conversion set it to. After your response above, I'm guessing your answer is no, that it's being set in another way, I just don't know how. :)
1496767634
Jakob
Sheet Author
API Scripter
Robert D. said: Quick question, before I take another pass at it. I was setting the spell_ring_type to set the drop down menu. If I don't do that, won't the drop down be set to '-' even if the variables are set correctly? The player needs to be able to see, looking at the spell, what the conversion set it to. After your response above, I'm guessing your answer is no, that it's being set in another way, I just don't know how. :) I'm not sure if you're talking about the fifth or the last bullet point. I'm assuming it is the fifth. If not, the following sentence will make no sense. No, what I was saying is that you already have other scripts that respond to a change in spell_ring_type and set spell_ring/spell_affinity, so for conversion purposes, you only  need to set spell_ring_type. If instead you meant the last bullet point, what I meant here is that you could completely leave out the spell_ring_type in the design of the sheet and instead structure the HTML like I said there. In that case, you would convert from spellRing to spell_ring, changing @{Air} to @{foo_air} etc.
I added the spell_ring_type to have a unique identifier. The was setting it to a variable, but I didn't know until this conversation that it was the variable as well as the variable value. However, if I set @foo_air to Air, how does that translate into both a unique identifier I can use for sheet worker changes and a direct value that foo_air contains for equations. That's the problem spell_ring_type is solving. There's probably an easy answer to that I don't know, but the answer to your question is, "The easy answer to that question is unknown to me." :) Here's my new take on the worker: var elements = ['air', 'earth', 'fire', 'water', 'void'], elementAttrs = [...elements.map(s => `foo_${s}`), ...elements.map(s => `${s}_affinity`)]; // Handle changes for foo_bla and bla_affinity: process all repeating sections elementAttrs.map(s => `change:${s}`).join(' '), function () { getSectionIDs('repeating_spells', function (idArray) { let allAttrs = idArray.map(id => `repeating_spells_${id}_spell_ring_type`).concat(elementAttrs); getAttrs([...allAttrs, ...idArray.map(id => `repeating_spells_${id}_spellRing`)], function (values) { let setting = {}; idArray.forEach(function (id) { switch (values[`repeating_spells_${id}_spellRing`]) { case '@{Air}': setting[`repeating_spells_${id}_spell_ring_type`] = 'air'; break; case '@{Earth}': setting[`repeating_spells_${id}_spell_ring_type`] = 'earth'; break; case '@{Fire}': setting[`repeating_spells_${id}_spell_ring_type`] = 'fire'; break; case '@{Water}': setting[`repeating_spells_${id}_spell_ring_type`] = 'water'; break; case '@{Void}': setting[`repeating_spells_${id}_spell_ring_type`] = 'void'; break; default: setting[`repeating_spells_${id}_spell_ring)type`] = '-'; } }); setAttrs(setting); }); }); };
1496996283

Edited 1496996581
Jakob
Sheet Author
API Scripter
That looks essentially right, except for a typo (and the fact that it needs to be changed due to our other discussion). Regarding dual use of spell_ring, here's an (almost complete) example. The salient point is this: the actual attribute value of spell_ring is @{foo_air}. If you use it in a formula/roll, it will just plug in the actual value of attribute foo_air - on the other hand, in sheet worker scripts, the value will be read as "@{foo_air}", not "2".  <input type="number" name="attr_foo_air" value="0" /> <input type="number" name="attr_foo_earth" value="0" /> <input type="number" name="attr_air_affinity" value="0" /> <input type="number" name="attr_earth_affinity" value="0" /> <fieldset class="repeating_spells"> <select name="attr_spell_ring"> <option value="0" selected>-</option> <option value="@{foo_air}">Air</option> <option value="@{foo_earth}">Earth</option> </select><br> <input type="hidden" name="attr_spell_affinity" value="0" /> </fieldset> <script type="text/worker"> var elements = ['air', 'earth'],   elementAttrs = [...elements.map(s => `foo_${s}`), ...elements.map(s => `${s}_affinity`)],   getSpellAffinityByRing = function (prefix, setting, values) {     switch (values[`${prefix}_spell_ring`]) {     case '@{foo_air}':       setting[`${prefix}_spell_affinity`] = values.air_affinity;       break;     case '@{foo_earth}':       setting[`${prefix}_spell_affinity`] = values.earth_affinity;       break;     default:       setting[`${prefix}_spell_affinity`] = '0';     }   }; // Handle changes for foo_bla and bla_affinity: process all repeating sections on(elementAttrs.map(s => `change:${s}`).join(' '), function () {   getSectionIDs('repeating_spells', function (idArray) {     let allAttrs = idArray.map(id => `repeating_spells_${id}_spell_ring`).concat(elementAttrs);     getAttrs(allAttrs, function (values) {       let setting = {};       idArray.forEach(id => getSpellAffinityByRing(`repeating_spells_${id}`, setting, values));       setAttrs(setting);     });   }); }); // Only do stuff within one row whenever spell_ring_type changes on('change:repeating_spells:spell_ring', function (e) {   getAttrs([...elementAttrs, 'repeating_spells_spell_ring'], function (values) {     let setting = {};     getSpellAffinityByRing('repeating_spells', setting, values);     setAttrs(setting);   }); }); //Conversion code var convertFromOldSheet = function () {   // Non-repeating attributes to rename   let conversionData = {     'airAffinity': 'air_affinity',     'earthAffinity': 'earth_affinity'   };   // Non-repeating attribute renaming   getAttrs(Object.keys(conversionData), function (values) {     let setting = {};     Object.keys(conversionData).forEach(function (oldAttr) {       if (values[oldAttr] != null) {         // set value of new attribute to value of old one         setting[conversionData[oldAttr]] = values[oldAttr];         // blank old attribute         setting[oldAttr] = '';       }     });     setAttrs(setting);   });   // spellRingConversion   getSectionIDs('repeating_spells', function (idArray) {     getAttrs(idArray.map(id => `repeating_spells_${id}_spellRing`), function (values) {       let setting = {};       idArray.forEach(function (id) {         switch (values[`repeating_spells_${id}_spellRing`]) {         case '@{Air}':           setting[`repeating_spells_${id}_spell_ring`] = '@{foo_air}';           break;         case '@{Earth}':           setting[`repeating_spells_${id}_spell_ring`] = '@{foo_earth}';           break;         default:           setting[`repeating_spells_${id}_spell_ring`] = '0';         }       });       setAttrs(setting);     });   }); }; on('sheet:opened', function () {   let currentVersion = 1.0;   getAttrs(['version'], function (v) {     if (!v.version) {       convertFromOldSheet();     };     setAttrs({       version: currentVersion     });   }); }); </script>
Thanks! I'm teaching a bunch of would-be novelists how to be slightly better novelists at Fyrecon today and tomorrow. :) I'll play with this when the conveference is over and I finish passing out.
Okay, I tried implementing this. It appears the portions that set the affinity still work so using the drop down didn't break. However, it isn't converting the setting of spell ring from the old version. How do I go forward? I compiled the code and it worked. Do you need to see code, or is there a troubleshooting step you'd like me to do to check a step? What's the most helpful next step?
1497856847
Jakob
Sheet Author
API Scripter
It's hard to say from here. Maybe I can debug it. If you want to learn to debug it yourself, insert plenty of console.log(INTERESTING_OBJECT) along the way so you can see the console output if something goes wrong. Or, if there's a bug along the way that triggers a semantic error, you will also see it in the console. One thing I realized after looking at this again: in the conversion code from the old sheet version, you will have to default to Air in the conversion, since a variable isn't actually set until someone changes the value of a select. In other words, if someone created a spell in the old version, and never changed the select away from air, the variable would actually end up undefined when you try to convert it, and so you would wrongly get spell_ring = 0 instead of spell_ring = @{foo_air}.
I'll make the change on the default. Can you give me an example on how to use console.log(INTERESTING_OBJECT)? I don't know how to implement that and I'm not finding examples with a google search.
It tells me that all the @foo_<element> variables aren't valid numbers in the console when I open the sheet, but it does that on a campaign without the new worker code. I can't see any errors that seem related to the new spell_ring code.
1497942488
Jakob
Sheet Author
API Scripter
So, you insert those log statements whenever you want to have a look at the internals of how the code works and want to find out at which point the data goes wrong :). Example: var convertFromOldSheet = function () {   // Other stuff...   // spellRingConversion   getSectionIDs('repeating_spells', function (idArray) {     getAttrs(idArray.map(id => `repeating_spells_${id}_spellRing`), function (values) { console.log(values); // Logging all the spellRing key:value pairs from the old sheet console.log(idArray); // logging all row ids. Those missing from the values object above don't have a spellRing // attribute set, so they should default to air       let setting = {};       idArray.forEach(function (id) {         switch (values[`repeating_spells_${id}_spellRing`]) {         case '@{Void}':           setting[`repeating_spells_${id}_spell_ring`] = '@{foo_void}';           break;         case '@{Water}':           setting[`repeating_spells_${id}_spell_ring`] = '@{foo_water}';           break;         case '@{Fire}':           setting[`repeating_spells_${id}_spell_ring`] = '@{foo_fire}';           break;         case '@{Earth}':           setting[`repeating_spells_${id}_spell_ring`] = '@{foo_earth}';           break;         default:           setting[`repeating_spells_${id}_spell_ring`] = '@{foo_air}'; // defaulting to air in case the select was never changed         }       }); console.log(setting); // Logging the attributes we are about to set.       setAttrs(setting);     });   }); };
1498080007

Edited 1498080028
Robert D.
Sheet Author
So I'm getting a lot of errors like this: Uncaught TypeError: Cannot read property 'attribs' of undefined at <a href="https://app.roll20.net/assets/app.js?1494260306:4" rel="nofollow">https://app.roll20.net/assets/app.js?1494260306:4</a>... at Array.forEach (native) at Function.k.each.k.forEach ( <a href="https://app.roll20.net/assets/base.js?1492617178:1:1628" rel="nofollow">https://app.roll20.net/assets/base.js?1492617178:1:1628</a> ) at Worker.&lt;anonymous&gt; ( <a href="https://app.roll20.net/assets/app.js?1494260306:46:15783" rel="nofollow">https://app.roll20.net/assets/app.js?1494260306:46:15783</a> ) (anonymous) @ app.js:46 k.each.k.forEach @ base.js:1 (anonymous) @ app.js:46 app.js:46 Uncaught TypeError: Cannot read property 'attribs' of undefined at <a href="https://app.roll20.net/assets/app.js?1494260306:4" rel="nofollow">https://app.roll20.net/assets/app.js?1494260306:4</a>... at Array.forEach (native) at Function.k.each.k.forEach ( <a href="https://app.roll20.net/assets/base.js?1492617178:1:1628" rel="nofollow">https://app.roll20.net/assets/base.js?1492617178:1:1628</a> ) at Worker.&lt;anonymous&gt; ( <a href="https://app.roll20.net/assets/app.js?1494260306:46:15783" rel="nofollow">https://app.roll20.net/assets/app.js?1494260306:46:15783</a> ) (anonymous) @ app.js:46 k.each.k.forEach @ base.js:1 (anonymous) @ app.js:46 app.js:46 Uncaught TypeError: Cannot read property 'attribs' of undefined at <a href="https://app.roll20.net/assets/app.js?1494260306:4" rel="nofollow">https://app.roll20.net/assets/app.js?1494260306:4</a>... at Array.forEach (native) at Function.k.each.k.forEach ( <a href="https://app.roll20.net/assets/base.js?1492617178:1:1628" rel="nofollow">https://app.roll20.net/assets/base.js?1492617178:1:1628</a> ) at Worker.&lt;anonymous&gt; ( <a href="https://app.roll20.net/assets/app.js?1494260306:46:15783" rel="nofollow">https://app.roll20.net/assets/app.js?1494260306:46:15783</a> ) (anonymous) @ app.js:46 k.each.k.forEach @ base.js:1 (anonymous) @ app.js:46 app.js:46 Uncaught TypeError: Cannot read property 'attribs' of undefined at <a href="https://app.roll20.net/assets/app.js?1494260306:4" rel="nofollow">https://app.roll20.net/assets/app.js?1494260306:4</a>... at Array.forEach (native) at Function.k.each.k.forEach ( <a href="https://app.roll20.net/assets/base.js?1492617178:1:1628" rel="nofollow">https://app.roll20.net/assets/base.js?1492617178:1:1628</a> ) at Worker.&lt;anonymous&gt; ( <a href="https://app.roll20.net/assets/app.js?1494260306:46:15783" rel="nofollow">https://app.roll20.net/assets/app.js?1494260306:46:15783</a> )
1498117974
Jakob
Sheet Author
API Scripter
You have "foo.attribs" or "foo['attribs'] somewhere in your code, where foo is undefined.
The closest I can find is this: elementAttrs = [...elements.map(s =&gt; `foo_${s}`), ...elements.map(s =&gt; `${s}_affinity`)], Actually when I do a find for attribs I can't find that letter combination anywhere in my html.
1498156235
Jakob
Sheet Author
API Scripter
Oh, sorry for the misdirection; actually, the type error doesn't seem to be related to sheet workers, otherwise it would have something like sheetworkersandbox.js in the stack trace.
Ok, I've searched the sheet for every instance of "foo" since attribs isn't there. I can't find any that aren't defined unless I misunderstand how to define them. They are set up like this: &lt;input type="hidden" name="attr_foo_earth" value="2" /&gt; &lt;input type="number" value="@{foo_earth}" disabled="true" name="attr_Earth"/&gt; Or similarly. I don't have a foo. anything or a foo[anything] anywhere outside the workers. It's all foo_something. You think it's an undefined foo breaking the worker, though?
1498236682
Jakob
Sheet Author
API Scripter
No, my foo was just an example for an object (as in foo, bar). It doesn't have anything to do with the text "foo" in your code . I believe this error has nothing to do with your sheet at all and is not &nbsp;coming from a worker. It might be, but I don't see how. I recently discovered by chance that you actually get this error message when deleting a character while its sheet is open.
Okay. I think I found the problem, which is versioning. I hadn't finished implementing your change to take out spell_ring_type and it looks like your code assumes I have. So now I'm going back and trying to finalize that before I work on the other. The code to remove spell_ring_type sets affinity if I change the ring, but it doesn't reset the affinity on a spell if I change the affinity setting for a ring itself (outside of the spell).&nbsp;earth_affinity, etc. I don't think it's triggering on that change, maybe? Do you see the issue there?
1498501963

Edited 1498502018
Jakob
Sheet Author
API Scripter
I think it is triggering on change of air_affinity etc unless I misunderstand you: var elements = ['air', 'earth'], &nbsp; elementAttrs = [...elements.map(s =&gt; `foo_${s}`), ...elements.map(s =&gt; `${s}_affinity`)], &nbsp;[...] // Handle changes for foo_bla and bla_affinity: process all repeating sections on(elementAttrs.map(s =&gt; `change:${s}`).join(' '), function () { [...]
1498507223

Edited 1498507364
Robert D.
Sheet Author
Yeah, that appears to be the problem. Here's my version. Now it didn't compile because of a bad closing } so I added a });. Did I possibly add it in the wrong place? Was it supposed to include some of the other sections below it? So you're right. I change the affinity for any of the rings outside the repeating section, and this bit isn't changing the affinity in the spell itself then that change is made. // Handle changes for foo_bla and bla_affinity: process all repeating sections on(elementAttrs.map(s =&gt; `change:${s}`).join(' '), function () { getSectionIDs('repeating_spells', function (idArray) { let allAttrs = idArray.map(id =&gt; `repeating_spells_${id}_spell_ring`).concat(elementAttrs); getAttrs(allAttrs, function (values) { let setting = {}; idArray.forEach(function (id) { setAttrs(setting); }); }); }); });
1498549317
Jakob
Sheet Author
API Scripter
That code iterates over all repeating rows and does nothing every time, so it's not surprising that it doesn't work. It's not at all the same as what I wrote in my&nbsp; post , however.
Thank you! I had to do a document compare to see the line that's different. Must have been a copy paste error left from the old code. It is very hard to troubleshoot code you don't understand. :)
1498576091
Jakob
Sheet Author
API Scripter
Robert D. said: Thank you! I had to do a document compare to see the line that's different. Must have been a copy paste error left from the old code. It is very hard to troubleshoot code you don't understand. :) I'm sorry I'm giving you code you don't understand :(. But, yes, the only line in that code that does anything is the "setAttrs(setting)" one, and since setting always stays an empty object, this accomplishes nothing. Make sense?
Okay. I worked on it awhile. Those console log lines don't seem to be posting anymore, but I do see lines saying that it's setting the various repeating spell rings to default, but then when I query that variable off a selected token, the variable is blank, despite have the line set to set them to foo_air on default. I'm a little burned out for the day. What would help troubleshoot this next? No worries about me being in over the head. I was told at one point in all this that someone didn't have time to explain the code to me. From that point on I've just been plowing ahead. If I can fix this last thing, it will be ready for others to start testing for publishing. Then I can slow down and try to make sense of it all.