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

How to access repeating item values via Sheet Worker getSectionIDs?

Using sheet worker functions on my character sheet, I'd like to use the same function to update any particular repeating item's calculated modifier, either from an on(change:repeating_) event handler on that repeating item, or from a getSectionIDs iteration in another event handler not related to the repeating item. For example, re-calculating every weapon's attack throw based on a change to its character's strength modifier. How do I do this?
1587315518
GiGs
Pro
Sheet Author
API Scripter
To answer questions like this, it really helps to have the code you've been working with, or at least the relevant attribute names. But here's a barebones example of how to change all the values of an attribute in a repeating section, when an external attribute changes. It looks pretty complicated, and i dont have time to go through and explain it right now. We can help hammer it down to exactly what you need with more specifics. Just ask questions! on('change:strength', function () {     getSectionIDs('repeating_weapons', function (ids) {         const fieldnames = [];         ids.forEach(id => {             fieldnames.push('repeating_weapons_' + id + '_weaponattackthrow');         });         getAttrs(['strength'], function (values) {             const output = {};             const str = parseInt(values.strength) || 0;             ids.forEach(id => {                 output['repeating_weapons_' + id + '_weaponattackthrow'] = str;             });             setAttrs(output);         });     }); });
Thanks for the prompt reply. You're right, it would help to see what I'm doing... First, I'm using a universal sheet worker to calculate the character's ability modifiers: <div class="gridcol3"> <h3 class="gridTitle">Abilities</h3> <span>STR</span> <input type="number" name="attr_strength"/> <span name="attr_strengthmodifier"></span> <span>INT</span> <input type="number" name="attr_intelligence"/> <span name="attr_intelligencemodifier"></span> <span>WIS</span> <input type="number" name="attr_wisdom"/> <span name="attr_wisdommodifier"></span> <span>DEX</span> <input type="number" name="attr_dexterity"/> <span name="attr_dexteritymodifier"></span> <span>CON</span> <input type="number" name="attr_constitution"/> <span name="attr_constitutionmodifier"></span> <span>CHR</span> <input type="number" name="attr_charisma"/> <span name="attr_charismamodifier"></span> </div> // Calculates ability modifiers on("change:strength change:intelligence change:wisdom change:dexterity change:constitution change:charisma", function(eventInfo) { var theAbility = eventInfo.sourceAttribute; getAttrs([theAbility], function(values) { var theAbilityValue = Object.values(values); var theAbilityMod = ""; switch (Number(theAbilityValue)) { case 3: theAbilityMod = "-3"; break; case 4: case 5: theAbilityMod = "-2"; break; case 6: case 7: case 8: theAbilityMod = "-1"; break; case 9: case 10: case 11: case 12: theAbilityMod = "+0"; break; case 13: case 14: case 15: theAbilityMod = "+1"; break; case 16: case 17: theAbilityMod = "+2"; break; case 18: theAbilityMod = "+3"; break; } setAttrs({ [theAbility + "modifier"] : theAbilityMod }); }); }); If any one of the character ability scores changes, the sheet worker updates the appropriate modifier field. Works great. :D  Now, I have a repeater section for weapons that looks like this: <div> <h3>Weapons</h3> <fieldset class="repeating_weapons"> <input type="text" name="attr_weaponName"/> <span>Damage:</span> <input class="shortText60" type="text" name="attr_weapondamage"/> <span>Base Ability:</span> <select class="weaponAbility" name="attr_weaponability"> <option value="strength">STR</option> <option value="dexterity">DEX</option> </select> <span>Throw:</span> <input type="text" name="attr_weaponthrow" readonly/> </fieldset> </div> If/when the value of attr_weaponability changes, I use the following sheet worker to update attr_weaponthrow to reflect the appropriate bonus (either STR or DEX): // Calculates a weapon's throw on("change:repeating_weapons:weaponability", function (eventInfo) { getAttrs(["repeating_weapons_weaponability", "attackthrow", "strengthmodifier", "dexteritymodifier"], function(values) { var theAbilityModifier = Number(values[values.repeating_weapons_weaponability + "modifier"]); var theAttackThrow = Number(values.attackthrow) + theAbilityModifier; setAttrs({ repeating_weapons_weaponthrow : theAttackThrow }); }); }); This also works great. :D Individually, each worker does its job. However, I need to now ensure that all repeating_weapons_weaponthrow values are re-calculated if/when the character's strength and/or dexterity modifiers are changed, as appropriate. I've been trying to use a helper function to serve both event handlers. This seems  to work when called singularly from the change:repeating_weapons  event handler, but I can't seem to get it to work from a getSectionIDs for each loop inside the universal change:[abilityname] event handler (by passing in object references to the helper function.) P.S. I noticed that the helper function seems oblivious to scope constraints - if I don't pass an object into the helper from change:repeating_weapons  event handler, it still has a reference to eventInfo.sourceAttribute of the parent handler, which isn't what I expected...
1587320898

Edited 1587321465
GiGs
Pro
Sheet Author
API Scripter
Thanks thats a very good description. here's a sheet worker to handle the strength/dex changes, with some notes. This doesnt replace your existing sheet worker, they are complimentary: one handles the str/dex changes, the other handles the weaponability changes. on('change:strengthmodifier change:dexteritymodifier change:attackthrow', function (event) {     getSectionIDs('repeating_weapons', function (ids) {         // make an array of all the attributes in the repeating section we want to examine         const fieldnames = [];         ids.forEach(id => fieldnames.push('repeating_weapons_' + id + '_weaponability'));         getAttrs([...fieldnames, 'strengthmodifier', 'dexteritymodifier'], function (values) {             // create a variable to hold all the attributes that will be sent to setAttrs later             const output = {};             // get the attribute that just changed             const which = event.sourceAttribute;             // get the score of that attribute             const score = Number(values[which + "modifier"]) || 0;             const attackthrow = Number(values.attackthrow) || 0;             ids.forEach(id => {                 // loop through the repeating section, and check which stat the weapon uses                 const thisability = values['repeating_weapons_' + id + '_weaponability'];                 // if the weapon's stat is the one that just changed, update its value                 if(thisability === which) {                     output['repeating_weapons_' + id + '_weaponthrow'] = score + attackthrow;                 }             });             // output contains only the attributes that changed             setAttrs(output);         });     }); }); Note that the structure keeps the getattrs and setattrs outside of the two loops. Thats important because getAttrs and setAttrs are very slow functions, and ideally you want to keep the number of times you use them to just once per worker.
1587321009
GiGs
Pro
Sheet Author
API Scripter
I just noticed I didnt buld the correct attack_throw, just entered the score. I'll adjust that in a bit, but you can see the structure.
I don't know why I didn't think of a *third* event handler to simply capture the STR/DEX changes and update the repeating weapon fields. There's also some other great optimizations in the sample you provided. THANK YOU.
1587321505
GiGs
Pro
Sheet Author
API Scripter
Youre welcome! I just fixed (hopefully) the worker.
BTW, if there were places in my published code that could be improved, I'd appreciate the feedback. Thanks, in advance!
1587323041
GiGs
Pro
Sheet Author
API Scripter
I can make a couple of observations. In your stat mod sheet worker, you have this var theAbility = eventInfo.sourceAttribute; getAttrs([theAbility], function(values) { but since you using event, you dont need to use getAttrs at all. event.newValue tells you the value of the stat. That said, there is a bug with that - if you plan to use API scripts to change attributes, newValue doesnt work so its better to stick with getAttrs in that case. This line might be overkill (though it is clever): var theAbilityValue = Object.values(values); You already know the name of the attribute from sourceAttribute, so you can use var theAbilityValue = Number(values[theAbility]) || 10; You might as well convert it to a number immediately. The || is a logical OR: if the Number attempt fails, it returns whatever is after the || instead. So its like setting a default value. I set the default to 10, so it'll calculate an average bonus (+0) if the input value is invalid. The calculation of the stat bonus is fine, but you can optimise it, like so. // create a function you can create a bunch of variables without cluttering the sheet worker function calcAbilityMod(theAbilityValue) {     // mods is the thresholds at which the ability mod changes.     const mods = [18, 16, 13, 9, 6, 4, 3];     // findIndex loops through the array in order, and stops at the FIRST index that matches the rule.     const bonus = 3 - mods.findIndex(element => theAbilityValue >= element);     // this is why mods is in descending order. We are looking for the first attribute where theAbilityValue is greater than or equal.     // that gives us the index, the first is 0, the second 1, etc.     // so by subtracting from 3 we get the correct bonus.     // this doesnt handle what happens if you have scores above 18 or below 3, so might need tweaking if those can happen     // thats why I set the default to 10, and not something like 0: 0 would break this function.     // finally we convert the number into a string.     const withplus = bonus >= 0 ? "+" + bonus : bonus;     return withplus; } on("change:strength change:intelligence change:wisdom change:dexterity change:constitution change:charisma", function(eventInfo) {     var theAbility = eventInfo.sourceAttribute;          getAttrs([theAbility], function(values) {         var theAbilityValue = Number(values[theAbility]) || 10;         var theAbilityMod = calcAbilityMod(theAbilityValue);                  setAttrs({             [theAbility + "modifier"] : theAbilityMod         });      }); });
One more question...here's the above example modified: // Re-calculate all weapons' attack throws when either a strength or dexterity modifier changes on('change:strengthmodifier change:dexteritymodifier', function (event) { getSectionIDs('repeating_weapons', function (ids) { // make an array of all the attributes in the repeating section we want to examine const fieldnames = []; ids.forEach(id => fieldnames.push('repeating_weapons_' + id + '_weaponattribute')); getAttrs([...fieldnames, 'attackthrow', 'strengthmodifier', 'dexteritymodifier'], function (values) { // create a variable to hold all the attributes that will be sent to setAttrs later const output = {}; // get the attribute that just changed const which = event.sourceAttribute; // get the modifier of that attribute const score = (parseInt(values.attackthrow) || 0) + (parseInt(values[which]) || 0); ids.forEach(id => { // loop through the repeating section, and check which stat the weapon uses const thisability = values['repeating_weapons_' + id + '_weaponattribute']; // if the weapon's stat is the one that just changed, update its value if(thisability === which) { output['repeating_weapons_' + id + '_weaponthrow'] = score; } }); // output contains only the attributes that changed setAttrs(output); }); }); }); In this case, the score is an amalgam of the appropriate attribute modifier and another, global modifier, attackthrow (each weapon's attack throw is equal to the character's base throw, plus the appropriate attribute bonus). Question: If the attackthrow on the sheet changes, how would I then iterate through the repeating weapons collection to apply this calculate to all weaponthrows? Basically, same problem, reverse direction... On change:attackthrow , getAttrs(attackthrow, strengthmodifier, dexteritymodifer) {     interate through weapons repeaters         determine appropriate attribute modifier (str or dex)         calculate new weapon throw = attackthrow + appropriate attribute modifier         setAttrs }
1587331247

Edited 1587331678
GiGs
Pro
Sheet Author
API Scripter
Oh right, of course the worker I supplied doesnt account for attackthrow changes. So in the worker, which gets the attribute that changed and you need to handle when it is attackthrow. My worker assumed the change would always be dec or str. Try this version on('change:strengthmodifier change:dexteritymodifier change:attackthrow', function (event) {     getSectionIDs('repeating_weapons', function (ids) {         // make an array of all the attributes in the repeating section we want to examine         const fieldnames = [];         ids.forEach(id => fieldnames.push('repeating_weapons_' + id + '_weaponability'));         getAttrs([...fieldnames, 'strengthmodifier', 'dexteritymodifier'], function (values) {             // create a variable to hold all the attributes that will be sent to setAttrs later             const output = {};             // get the attribute that just changed             const which = event.sourceAttribute;                          // create a variable to hold the scores of sterngth and dexterity.             // this format makes the forEach section below easier/             const stats = {                 dexterity: Number(values["dexteritymodifier"]) || 0,                 strength: Number(values["strengthmodifier"]) || 0             };             const attackthrow = Number(values.attackthrow) || 0;             ids.forEach(id => {                 // loop through the repeating section, and check which stat the weapon uses                 const thisability = values['repeating_weapons_' + id + '_weaponability'];                 // if the change was caused by attackthrow, or the weapon's stat is the one that just changed, update its value                 if(which === 'attackthrow' || thisability === which) {                     output['repeating_weapons_' + id + '_weaponthrow'] = stats[thisability] + attackthrow;                 }             });             // output contains only the attributes that changed             setAttrs(output);         });     }); });
Works perfectly, and very educational. Thanks, again!
1587423023
GiGs
Pro
Sheet Author
API Scripter
Great!