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

Sheetworker miscalculating when adding up totals from multiple different repeating row fields

1699323506

Edited 1699323640
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
I'm using sheetworkers to add up the total of an 'All Combat' row, the repeating rows beneath that, then a 'Melee Combat' row with repeating rows beneath that, and finally a 'Ranged Combat' row with repeating rows beneath that.  All of this gets added into a 'Points total' field below. This works fine when adding up the not-repeating attr_ values. It even works with 1 added row in 1 of the categories. However, as soon as I add a specialization in a different row, the math gets strange, even getting smaller sometimes. I've been troubleshooting and finagling with the sheetworkers all day and just not even sure where to begin with this. Usually it will just not work if there's an issue, this is an unexpected result.  Here's the sheetworker code in question:   on("change:allcombatpoints change:repeating_allcombatSpecz:allcombatSpeczpoints change:meleecombatpoints change:repeating_meleecombatSpecz:meleeCombatSpeczpoints change:rangedcombatpoints change:repeating_rangedcombatSpecz:rangedCombatSpeczpoints sheet:opened", function () {       getAttrs([           "allcombatpoints", "repeating_allcombatSpecz_allcombatSpeczpoints", "meleecombatpoints", "repeating_meleecombatSpecz_meleeCombatSpeczpoints", "rangedcombatpoints", "repeating_rangedcombatSpecz_rangedCombatSpeczpoints",       ], function (values) {           let cppt = +values.allcombatpoints || 0;           let cpszpt = +values.repeating_allcombatSpecz_allcombatSpeczpoints || 0;           let mlpt = +values.meleecombatpoints || 0;           let mtszpt = +values.repeating_meleecombatSpecz_meleeCombatSpeczpoints || 0;           let rgpt = +values.rangedcombatpoints || 0;           let rgszpt = +values.repeating_rangedcombatSpecz_rangedCombatSpeczpoints || 0;           let total = cppt + cpszpt + mlpt + mtszpt + rgpt + rgszpt;           setAttrs({pbCombatSkillsTotals: total});       });   }); And the relevant html: <!-- combat skills --> <div class="skillsfillrepeating"> <!-- class is a misnomer leftover from before --> <span class="skillnamefieldprimary" name="attr_allcombat">All Combat</span> <input type="text" class="skilltotalfield" name="attr_allcombatskilltotal" value=""> <span class="skillrollbutton"> <button class="sheet-d6-dice" type="roll" value= '&{template:dodas} {{name=@{character_name} @{allcombat} }} {{roll +@{allcombatskilltotal} = =[[2d6 + @{allcombatskilltotal}]]}}' name="roll_skilltotalcolumn"></button> </span> <input type="text" class="tiercostcolumn" name="attr_allcombattiercost" value="5"> <input type="text" class="costcolumn" name="attr_allcombatpoints" value=""> <input type="number" class="tiersevenfield" name="attr_allcombattierfive" value=""> <input type="number" class="tierfourfield" name="attr_allcombattierfour" value=""> <input type="number" class="tiertwofield" name="attr_allcombattierthree" value=""> <input type="number" class="tieronefield" name="attr_allcombattiertwo" value=""> <input type="number" class="tierstarfield" name="attr_allcombattierone" value=""> <input type="number" class="tierabovesubfield" name="attr_allcombattierabovesub" value=""> <input type="number" class="synergybonusfield" name="attr_allcombatsynergybonus" value=""> <input type="number" class="synergyoffsetfield" name="attr_allcombatsynergyoffset" value=""> <input type="number" class="subtotalfield" name="attr_allcombatgroup" value=""> <input type="text" class="firstskillATRfield" name="attr_allcombatattribute" value="AP"> <input type="text" class="firstATRmodField" name="attr_allcombatattributemodifier" value=""> <!-- sheetworker --> <input type="text" class="secondATRField" name="attr_allcombatattributetwo" value=""> <input type="text" class="secondATRmod" name="attr_allcombatattributemodifiertwo" value=""> <input type="text" class="otherskillfield" name="attr_allcombatothermodifiersfill" value=""> </div> <!-- all combat SPECIALIZATIONS --> <fieldset class="repeating_allcombatSpecz"> <div class="skillsfillrepeatingsecondary"> <input type="text" class="skillnamefield" name="attr_allcombatSpeczskillsandabilities" value=""> <input type="text" class="skilltotalfield" name="attr_allcombatSpeczskilltotal" value=""> <span class="skillrollbutton"> <button class="sheet-d6-dice" type="roll" value= '&{template:dodas} {{name=@{character_name} @{allcombatSpeczskillsandabilities} }} {{roll +@{allcombatSpeczskilltotal} = =[[2d6 + @{allcombatSpeczskilltotal}]]}}' name="roll_skilltotalcolumn"></button> </span> <input type="text" class="tiercostcolumn" name="attr_allcombatSpecztiercost" value=""> <input type="text" class="costcolumn" name="attr_allcombatSpeczpoints" value=""> <input type="number" class="tierfivefield" name="attr_allcombatSpecztierfive" value=""> <input type="number" class="tierfourfield" name="attr_allcombatSpecztierfour" value=""> <input type="number" class="tierthreefield" name="attr_allcombatSpecztierthree" value=""> <input type="number" class="tiertwofield" name="attr_allcombatSpecztiertwo" value=""> <input type="number" class="tieronefield" name="attr_allcombatSpecztierone" value=""> <input type="number" class="tierabovesubfield" name="attr_allcombatSpecztierabovesub" value=""> <input type="number" class="synergybonusfield" name="attr_allcombatSpeczsynergybonus" value=""> <input type="number" class="synergyoffsetfield" name="attr_allcombatSpeczsynergyoffset" value=""> <input type="number" class="subtotalfield" name="attr_allcombatSpeczgroup" value=""> <input type="text" class="firstskillATRfield" name="attr_allcombatSpeczattribute" value=""> <input type="text" class="firstATRmodField" name="attr_allcombatSpeczattributemodifier" value=""> <input type="text" class="secondATRField" name="attr_allcombatSpeczattributetwo" value=""> <input type="text" class="secondATRmod" name="attr_allcombatSpeczattributemodifiertwo" value=""> <input type="text" class="otherskillfield" name="attr_allcombatSpeczothermodifiersfill" value=""> </div> </fieldset> <!-- melee combat --> <div class="skillsfillrepeatingsecondary"> <!-- class is a misnomer leftover from before --> <span class="skillnamefield" name="attr_meleecombat">Melee Combat</span> <input type="text" class="skilltotalfield" name="attr_meleecombatskilltotal" value=""> <span class="skillrollbutton"> <button class="sheet-d6-dice" type="roll" value= '&{template:dodas} {{name=@{character_name} @{meleecombat} }} {{roll +@{meleecombatskilltotal} = =[[2d6 + @{meleecombatskilltotal}]]}}' name="roll_skilltotalcolumn"></button> </span> <input type="text" class="tiercostcolumn" name="attr_meleecombattiercost" value="4"> <input type="text" class="costcolumn" name="attr_meleecombatpoints" value=""> <input type="number" class="tierfivefield" name="attr_meleecombattierfive" value=""> <input type="number" class="tierfourfield" name="attr_meleecombattierfour" value=""> <input type="number" class="tierthreefield" name="attr_meleecombattierthree" value=""> <input type="number" class="tiertwofield" name="attr_meleecombattiertwo" value=""> <input type="number" class="tieronefield" name="attr_meleecombattierone" value=""> <input type="number" class="tierabovesubfield" name="attr_meleecombattierabovesub" value=""> <input type="number" class="synergybonusfield" name="attr_meleecombatsynergybonus" value=""> <input type="number" class="synergyoffsetfield" name="attr_meleecombatsynergyoffset" value=""> <input type="number" class="subtotalfield" name="attr_meleecombatgroup" value=""> <input type="text" class="firstskillATRfield" name="attr_meleecombatattribute" value="AP"> <input type="text" class="firstATRmodField" name="attr_meleecombatattributemodifier" value=""> <!-- pull sheetworker value --> <input type="text" class="secondATRField" name="attr_meleecombatattributetwo" value=""> <input type="text" class="secondATRmod" name="attr_meleecombatattributemodifiertwo" value=""> <input type="text" class="otherskillfield" name="attr_meleecombatothermodifiersfill" value=""> </div> <!-- Melee Combat SPECIALIZATIONS --> <fieldset class="repeating_meleecombatSpecz"> <div class="skillsfillrepeatingtertiary"> <input type="text" class="skillnamefield" name="attr_meleeCombatSpeczskillsandabilities" value=""> <input type="text" class="skilltotalfield" name="attr_meleeCombatSpeczskilltotal" value=""> <span class="skillrollbutton"> <button class="sheet-d6-dice" type="roll" value= '&{template:dodas} {{name=@{character_name} @{meleeCombatSpeczskillsandabilities} }} {{roll +@{meleeCombatSpeczskilltotal} = =[[2d6 + @{meleeCombatSpeczskilltotal}]]}}' name="roll_skilltotalcolumn"></button> </span> <input type="text" class="tiercostcolumn" name="attr_meleeCombatSpecztiercost" value=""> <input type="text" class="costcolumn" name="attr_meleeCombatSpeczpoints" value=""> <input type="number" class="tierfivefield" name="attr_meleeCombatSpecztierfive" value=""> <input type="number" class="tierfourfield" name="attr_meleeCombatSpecztierfour" value=""> <input type="number" class="tierthreefield" name="attr_meleeCombatSpecztierthree" value=""> <input type="number" class="tiertwofield" name="attr_meleeCombatSpecztiertwo" value=""> <input type="number" class="tieronefield" name="attr_meleeCombatSpecztierone" value=""> <input type="number" class="tierabovesubfield" name="attr_meleeCombatSpecztierabovesub" value=""> <input type="number" class="synergybonusfield" name="attr_meleeCombatSpeczsynergybonus" value=""> <input type="number" class="synergyoffsetfield" name="attr_meleeCombatSpeczsynergyoffset" value=""> <input type="number" class="subtotalfield" name="attr_meleeCombatSpeczgroup" value=""> <input type="text" class="firstskillATRfield" name="attr_meleeCombatSpeczattribute" value=""> <input type="text" class="firstATRmodField" name="attr_meleeCombatSpeczattributemodifier" value=""> <input type="text" class="secondATRField" name="attr_meleeCombatSpeczattributetwo" value=""> <input type="text" class="secondATRmod" name="attr_meleeCombatSpeczattributemodifiertwo" value=""> <input type="text" class="otherskillfield" name="attr_meleeCombatSpeczothermodifiersfill" value=""> </div> </fieldset> <!-- ranged combat --> <div class="skillsfillrepeatingsecondary"> <!-- class is a misnomer leftover from before --> <span class="skillnamefield" name="attr_rangedcombat">Ranged Combat</span> <input type="text" class="skilltotalfield" name="attr_rangedcombatskilltotal" value=""> <span class="skillrollbutton"> <button class="sheet-d6-dice" type="roll" value= '&{template:dodas} {{name=@{character_name} @{rangedcombat} }} {{roll +@{rangedcombatskilltotal} = =[[2d6 + @{rangedcombatskilltotal}]]}}' name="roll_skilltotalcolumn"></button> </span> <input type="text" class="tiercostcolumn" name="attr_rangedcombattiercost" value="4"> <input type="text" class="costcolumn" name="attr_rangedcombatpoints" value=""> <input type="number" class="tierfivefield" name="attr_rangedcombattierfive" value=""> <input type="number" class="tierfourfield" name="attr_rangedcombattierfour" value=""> <input type="number" class="tierthreefield" name="attr_rangedcombattierthree" value=""> <input type="number" class="tiertwofield" name="attr_rangedcombattiertwo" value=""> <input type="number" class="tieronefield" name="attr_rangedcombattierone" value=""> <input type="number" class="tierabovesubfield" name="attr_rangedcombattierabovesub" value=""> <input type="number" class="synergybonusfield" name="attr_rangedcombatsynergybonus" value=""> <input type="number" class="synergyoffsetfield" name="attr_rangedcombatsynergyoffset" value=""> <input type="number" class="subtotalfield" name="attr_rangedcombatgroup" value=""> <input type="text" class="firstskillATRfield" name="attr_rangedcombatattribute" value="AP"> <input type="text" class="firstATRmodField" name="attr_rangedcombatattributemodifier" value=""> <input type="text" class="secondATRField" name="attr_rangedcombatattributetwo" value=""> <input type="text" class="secondATRmod" name="attr_rangedcombatattributemodifiertwo" value=""> <input type="text" class="otherskillfield" name="attr_rangedcombatothermodifiersfill" value=""> </div> <!-- Ranged Combat SPECIALIZATIONS --> <fieldset class="repeating_rangedcombatSpecz"> <div class="skillsfillrepeatingtertiary"> <input type="text" class="skillnamefield" name="attr_rangedCombatSpeczskillsandabilities" value=""> <input type="text" class="skilltotalfield" name="attr_rangedCombatSpeczskilltotal" value=""> <span class="skillrollbutton"> <button class="sheet-d6-dice" type="roll" value= '&{template:dodas} {{name=@{character_name} @{rangedCombatSpeczskillsandabilities} }} {{roll +@{rangedCombatSpeczskilltotal} = =[[2d6 + @{rangedCombatSpeczskilltotal}]]}}' name="roll_skilltotalcolumn"></button> </span> <input type="text" class="tiercostcolumn" name="attr_rangedCombatSpecztiercost" value=""> <input type="text" class="costcolumn" name="attr_rangedCombatSpeczpoints" value=""> <input type="number" class="tierfivefield" name="attr_rangedCombatSpecztierfive" value=""> <input type="number" class="tierfourfield" name="attr_rangedCombatSpecztierfour" value=""> <input type="number" class="tierthreefield" name="attr_rangedCombatSpecztierthree" value=""> <input type="number" class="tiertwofield" name="attr_rangedCombatSpecztiertwo" value=""> <input type="number" class="tieronefield" name="attr_rangedCombatSpecztierone" value=""> <input type="number" class="tierabovesubfield" name="attr_rangedCombatSpecztierabovesub" value=""> <input type="number" class="synergybonusfield" name="attr_rangedCombatSpeczsynergybonus" value=""> <input type="number" class="synergyoffsetfield" name="attr_rangedCombatSpeczsynergyoffset" value=""> <input type="number" class="subtotalfield" name="attr_rangedCombatSpeczgroup" value=""> <input type="text" class="firstskillATRfield" name="attr_rangedCombatSpeczattribute" value=""> <input type="text" class="firstATRmodField" name="attr_rangedCombatSpeczattributemodifier" value=""> <input type="text" class="secondATRField" name="attr_rangedCombatSpeczattributetwo" value=""> <input type="text" class="secondATRmod" name="attr_rangedCombatSpeczattributemodifiertwo" value=""> <input type="text" class="otherskillfield" name="attr_rangedCombatSpeczothermodifiersfill" value=""> </div> </fieldset> <!-- much later....--> <div class="pointbuytablerow"> <span class="pbtSkillTable">Combat Skills</span> <!-- COMBAT SKILLS --> <input type="text" name="attr_pbCombatSkillsTotals" value=""> <!-- TOTALS --> <input type="text" name="attr_pbCombatSkillsUnspent" value=""> <input type="text" name="attr_pbCombatSkillsSpent" value=""> <input type="text" name="attr_pbCombatSkillsNegative" value=""> <input type="text" name="attr_pbSkillTableNotes" value=""> </div> Apologies for the long bits of code. I think there's also probably a better way to do all of this with Universal Sheetworkers. I'm not sure if switching to something like that would reduce these issues, or if there's just some dumb typo somewhere. But couldn't find one so here I am. Thanks for any help!
1699338638

Edited 1699338950
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
because your listener is reacting to both repeating and non repeating events, and is reacting to events in several different repeating sections, you need to explicitly get all of those rows instead of using the implicit repeating row syntax (this is one of many reasons why I recommend not using the implicit row syntax). To do this, you need to run several rounds of getSectionIDs, and then finally a getAttrs in the final getSectionIDs. You'll then need to iterate over all the repeating section values to total them up. It will look something like this (untested): /** * Alias for [getSectionIDs()](<a href="https://wiki.roll20.net/Sheet_Worker_Scripts#getSectionIDs.28section_name.2Ccallback.29" rel="nofollow">https://wiki.roll20.net/Sheet_Worker_Scripts#getSectionIDs.28section_name.2Ccallback.29</a>) that allows you to iterate through several functions at once. Also assembles an array of repeating attributes to get. * @memberof Sheetworker Aliases * @param {object[]} sectionDetails - Array of details about a section to get the IDs for and attributes that need to be gotten. * @param {string} sectionDetails.section - The full name of the repeating section including the `repeating_` prefix. * @param {string[]} sectionDetails.fields - Array of field names that need to be gotten from the repeating section * @param {function(string[],sectionObj)} callback - The function to call once all IDs have been gotten and the array of repating attributes to get has been assembled. The callback is passed the array of repating attributes to get and a {@link sectionObj}. * @example * // Get some section details * const sectionDetails = [ * {section:'repeating_equipment',fields:['name','weight','cost']}, * {section:'repeating_weapon',fields:['name','attack','damage']} * ]; * getSections(sectionDetails,(attributeNames,sections)=&gt;{ * console.log(attributeNames);// =&gt; Array containing all row specific attribute names * console.log(sections);// =&gt; Object with arrays containing the row ids. Indexed by section name (e.g. repeating_eqiupment) * }) */ const repeatingDetails = [ {section:'repeating_allcombatSpecz',fields:['allcombatspeczpoints']}, {section:'repeating_meleecombatSpecz',fields:['meleecombatspeczpoints']}, {section:'repeating_rangedcombatSpecz',fields:['rangedcombatspeczpoints']} ]; const getSections = function(sectionDetails,callback){ let queueClone = _.clone(sectionDetails); const worker = (queue,repeatAttrs=[],sections={})=&gt;{ let detail = queue.shift(); getSectionIDs(detail.section,(IDs)=&gt;{ sections[detail.section] = IDs; IDs.forEach((id)=&gt;{ detail.fields.forEach((f)=&gt;{ repeatAttrs.push(`${detail.section}_${id}_${f}`); }); }); if(queue.length){ worker(queue,repeatAttrs,sections); }else{ callback(repeatAttrs,sections); } }); }; if(!queueClone[0]){ callback([],{}); }else{ worker(queueClone); } }; const watchAttrs = ['allcombatpoints','repeating_allcombatSpecz:allcombatSpeczpoints','meleecombatpoints','repeating_meleecombatSpecz:meleeCombatSpeczpoints','rangedcombatpoints','repeating_rangedcombatSpecz:rangedCombatSpeczpoints']; watchAttrs.forEach(attr =&gt; { on(`change:${attr}`,() =&gt; { getSections(repeatingDetails,(repeatAttrs,sections) =&gt; { const nonRepeatAttrs = ['allcombatpoints', 'meleecombatpoints', 'rangedcombatpoints']; getAttrs([...repeatAttrs,...nonRepeatAttrs],(attributes) =&gt; { const total = [...nonRepeatAttrs,repeatAttrs].reduce((tot,attrName) =&gt; { return tot + (+attributes[attrName] || 0); },0); setAttrs({pbcombatskillstotals:total}); }) }) }) }) And some code notes: I would highly recommend changing your naming scheme. the sheetworker listener requires attribute names to be all lower case in the on statement. Using an all lowercase casing system like snake_case &nbsp;makes your attribute names easier to read when they are in a complicated macro or other complicated code names of repeating sections and action button names should use kebab-case . For repeating section attributes, I would recommend making the attribute names of repeating attributes so that you don't repeat the section name in the attribute name, e.g. repeating_allcombatspecz_$ID_allcombatspeczpoints would become repeating_allcombatspecz_$ID_points . Together these changes would allow the above javascript to be streamlined and will improve the overall maintainability of your code. EDIT: I think there's also probably a better way to do all of this with Universal Sheetworkers. I'm not sure if switching to something like that would reduce these issues, or if there's just some dumb typo somewhere. But couldn't find one so here I am. I personally think that going to a universal sheetworker option is the best option. If your sheet is going to be even moderately complex, using a sheet infrastructure will simplify your life. I am of course biased to the framework I built, the K-scaffold &nbsp;(note that the above js code is adapted from the source of the K-scaffold).
1699339361

Edited 1699339443
vÍnce
Pro
Sheet Author
I think you need to use getSectionIDs when accessing multiple row's attributes otherwise you are only able to get the one row that is triggering the event. GiGs has written some wonderful tutorials on his site for building roll20 sheets.&nbsp; Here's a page dealing specifically with multiple repeating sections: <a href="https://cybersphere.me/multiple-repeating-sections-introduction/" rel="nofollow">https://cybersphere.me/multiple-repeating-sections-introduction/</a> )&nbsp; There is also a technique of nesting getSectionIDs when working with multiple repeating sections: <a href="https://cybersphere.me/multiple-repeating-sections-russian-doll/" rel="nofollow">https://cybersphere.me/multiple-repeating-sections-russian-doll/</a> lol. What Scott said. ;-P
1699339770
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Thanks both of you. May take me some time to try both of these, but I'll let you know how it goes.
1699367296

Edited 1699392966
GiGs
Pro
Sheet Author
API Scripter
Listen to Scott, everything he says here is spot on, and he provides a code solution. If you want to create your own code solution, I'll add to what the extremely wise Vince has said. Personally I'd use this method for multiple repeating sections: <a href="https://cybersphere.me/multiple-repeating-sections-custom-function/" rel="nofollow">https://cybersphere.me/multiple-repeating-sections-custom-function/</a> It's generalisable so you can start using it instead of getSectionIDs for single repeating sections, and can use the same method everywhere. It's what I do now. It's very similar to Scott's method, if you rewrite it a bit. If however, you want a really simple-to-write system, use repeatingSum . You'd install the function there just after your script block starts, then have a hidden attribute for each repating section. Then a sheet worker that uses sumRepeating to calculate a total for one repeating section at a time and fills the lone attribute. Then you have a sheet worker that totals up the non-repeating stats, and include those 3 stats in it. It's a lot less efficient than other methods proposed here, but it works and is easy to write.
1699377519

Edited 1699377768
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
As GiGs says, doing it in stages would definitely make for a simpler overall function, but you'd be approaching the point at which the cascading getAttrs/setAttrs would cause human noticeable lag on the sheet. I'll also point out that the getSections function in my code example is extremely general and can be used with anywhere from 0 to 1000 repeating sections. This means that while my example looks like a lot of code, about 3/4 of that is just infrastructure you'd use for any other listeners that dealt with repeating sections. But, the real answer on which to use, is which one makes sense to you so you can maintain it. Even though the cascading setAttrs of the simplified version might introduce some lag, it will be at the low end of what might be noticed by a user and so won't be that bad.
1699409063
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Hmmm good to know. Lag isn't a huge concern as this part of the sheet is mostly only calculated during character creation. I'd be somewhat concerned if sheet loading times increased, but it sounds like that's not something that would happen?
1699417993
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Probably not to a point where it'd be an issue. This is very much at the bottom end of possible lag.
1699488115
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Okay I finally got a chance to go through some of this. 1. From what I understand, if I were to implement the k-scaffold, I would kind of need to start over much of the sheet in order to accommodate this? I'm very new to javascript. Like, I went through the beginning third maybe of the codecademy course on it, but don't have much practical experience beyond the sheetworkers in Roll20. Implementing this seems very daunting to me. It sounds like something worth looking into in the long term, but not sure I can make that transition quickly. 1a. Do you have any resources you might recommend for understanding those files and folder structures before getting into it? Maybe go through freecodecamp or something? 2. A far as repeating section names goes. I gave each section unique names because I thought the input fields each needed to be unique. But perhaps I misunderstood. If an input field is inside a "repeating_section", then the field is unique? Correct? So "repeating_combatskills_pointstotal" would be different than "repeating_adventuringskills_pointstotal" for example? In that case yeah I'd definitely want to cleanup that code.&nbsp; 3. I did read through the k-scaffold, and tried to parse together what a lot of it meant. But mostly it's things I don't understand. Would you be able to summarize the benefits of switching over to using it in language for noobs? 3a. I have had some issues with tab buttons not working after adding additional sheetworkers. Perhaps k-scaffold is more reliable for tab buttons and alternate layouts? Otherwise perhaps a topic for another time. I think next when I get the chance, I'll try the 'repeatingSum' solution. It seems like simpler add on for my existing code infrastructure, at least as a short term solution.
1699504109

Edited 1699504392
GiGs
Pro
Sheet Author
API Scripter
I personally wouldn't recomment k-scaffold to anyone who wasn't an expert (sorry, Scott) It is very good, but if you use that, you'll have to learn a different syntax, and will have very little help to use it (the wiki and my site won't be much help). You need to be very familiar with JavaScript if you have any issues (and PUG!). That said, the specific code Scott provided should be easy to drop into your existing script block and use as is. I'll give a bit more detail on using repeatingSum below as an alternative. So "repeating_combatskills_pointstotal" would be different than "repeating_adventuringskills_pointstotal" for example? I This is accurate. I believe Scott was talking about avoiding things like "repeating_combat_combatpoints", because the word combat is repeated there. So some repeatingSum help: first thing, using your existing code, add 3 inputs - not in repeating sections. Since they are hidden, and aren't displayed anyway, they can go anywhere. You can put each one after its fieldset, or put all 3 together at the start. It doesn't matter. &lt;input type="hidden" name="attr_total_ranged" value="0"&gt; &lt;input type="hidden" name="attr_total_melee" value="0"&gt; &lt;input type="hidden" name="attr_total_allcombat" value="0"&gt; I'd also recommend changing the names of your repeating sections to make them simpler (and all lower case, as Scott mentioned), like repeating_ranged, repeating_melee, repeating_allcombat Notice that the name after the repeating_ matches the hidden input names after total_. That will be important later. Then drop in the repeatingSum function at the start of script block. Make no changes to this code . /* ===== PARAMETERS ========== destinations = the name of the attribute that stores the total quantity can be a single attribute, or an array: ['total_cost', 'total_weight'] If more than one, the matching fields must be in the same order. section = name of repeating fieldset, without the repeating_ fields = the name of the attribute field to be summed destination and fields both can be a single attribute: 'weight' or an array of attributes: ['weight','number','equipped'] */ const repeatingSum = (destinations, section, fields) = &gt; { if ( ! Array . isArray (destinations)) destinations = [destinations. replace ( / \s / g , '' ). split ( ',' )]; if ( ! Array . isArray (fields)) fields = [fields. replace ( / \s / g , '' ). split ( ',' )]; getSectionIDs (`repeating_${section}`, idArray = &gt; { const attrArray = idArray. reduce ((m, id) = &gt; [...m, ...(fields. map (field = &gt; `repeating_${section}_${id}_${field}`))], []); getAttrs ([...attrArray], v = &gt; { const getValue = (section, id, field) = &gt; v[`repeating_${section}_${id}_${field}`] = = = 'on' &nbsp;? 1 &nbsp;: parseFloat (v[`repeating_${section}_${id}_${field}`]) | | 0 ; const commonMultipliers = (fields. length &lt; = destinations. length )&nbsp;? []&nbsp;: fields. splice (destinations. length , fields. length - destinations. length ); const output = {}; destinations. forEach ((destination, index) = &gt; { output[destination] = idArray. reduce ((total, id) = &gt; total + getValue (section, id, fields[index]) * commonMultipliers. reduce ((subtotal, mult) = &gt; subtotal * getValue (section, id, mult), 1 ), 0 ); }); setAttrs (output); }); }); }; Then, add this sheet worker (actually three sheet workers) const sections =&nbsp; ['ranged', 'melee', 'allcombat'] sections.forEach(section =&gt; { on (` change:repeating_${section}:points remove:repeating_${section}` , function () { repeatingSum ( `${section}_total` ,section,"points"); }); }); This assumes the column in each section has the name "points". Change the word "points" the two places it is listed above if necessary. This code creates uses a forEach loop to create three sheet workers. Each one looks like this: on (` change:repeating_melee:points remove:repeating_melee` , function () { repeatingSum ( "melee_total" ,"melee","points"); }); The beauty of the forEach loop is it's easily expanded for any number of repeating sections, assuming you are adding up the points column in each - and makes creating those first 3 woorkers a snap, but here each is pretty short so it's maybe overkill. Finally, modify your original sheet worker to replace references to repeating sections with those hidden inputs. on("change:allcombatpoints change:meleecombatpoints change:rangedcombatpoints change:total_ranged change:total_melee change:total_allcombat sheet:opened", function () { &nbsp; &nbsp; &nbsp; getAttrs([ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "allcombatpoints", "meleecombatpoints", "rangedcombatpoints", "total_ranged", "total_melee", "total_allcombat", &nbsp; &nbsp; &nbsp; ], function (values) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let cppt = +values.allcombatpoints || 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let cpszpt = +values.total_allcombat || 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let mlpt = +values.meleecombatpoints || 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let mtszpt = +values.tptal_melee || 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let rgpt = +values.rangedcombatpoints || 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let rgszpt = +values.total_ranged || 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let total = cppt + cpszpt + mlpt + mtszpt + rgpt + rgszpt; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs({pbCombatSkillsTotals: total}); &nbsp; &nbsp; &nbsp; }); &nbsp; }); And that should do it. It's a bit laborious to set up, but is pretty straightforward to understand whats happening. repeating_sections -&gt; totals -&gt; add up all attributes.
1699505807
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
That said, the specific code Scott provided should be easy to drop into your existing script block and use as is Ah. Thanks for clearing that up! I may give it a shot then. Thanks for being so thorough. I'll give these a try and see if I can't get it to work, and report back. Also in hindsight, I did check the articles, including this one...&nbsp; <a href="https://cybersphere.me/multiple-repeating-sections-introduction/" rel="nofollow">https://cybersphere.me/multiple-repeating-sections-introduction/</a> previously when attempting to get ready for this. But I must have glossed over or missed that what I'm attempting to do is the thing you mentioned in that link as the thing that you can't do haha.&nbsp;
1699514618

Edited 1699514691
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
I didn't mean to necessarily say you should switch to the K-scaffold. From what you've already got it looks like you've got most of a sheet. Switching to pug, scss, and the scaffold would be a lot of work. It was more meant as an example of what a framework can provide to a sheet. The downsides to frameworks though is that converting to them mid project is a PITA. The code I put in my post was adapted from the K-scaffold so that it works outside of the scaffold and should be good to be used as a drop in. I personally wouldn't recomment k-scaffold to anyone who wasn't an expert (sorry, Scott) Heh, it is a very big hammer that is sometimes overkill!
1699545111
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Okay cool. Yeah glad that's cleared up.
1699558592
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Switching to kebab-case for the repeating section names appears to have broken the sheetworker. For example: &lt;!-- all combat SPECIALIZATIONS --&gt; &lt;fieldset class="repeating_all-combat-specz"&gt; &lt;div class="skillsfillrepeatingsecondary"&gt; &lt;input type="text" class="skillnamefield" name="attr_skillsandabilities" value=""&gt; &lt;input type="text" class="skilltotalfield" name="attr_skilltotal" value=""&gt; &lt;span class="skillrollbutton"&gt; &lt;button class="sheet-d6-dice" type="roll" value= '&amp;{template:dodas} {{name=@{character_name} @{skillsandabilities} }} {{roll +@{skilltotal} = =[[2d6 + @{skilltotal}]]}}' name="roll_skilltotalcolumn"&gt;&lt;/button&gt; &lt;/span&gt; &lt;input type="text" class="tiercostcolumn" name="attr_tiercost" value=""&gt; &lt;input type="text" class="costcolumn" name="attr_points" value=""&gt; &lt;input type="number" class="tierfivefield" name="attr_tierfive" value=""&gt; &lt;input type="number" class="tierfourfield" name="attr_tierfour" value=""&gt; &lt;input type="number" class="tierthreefield" name="attr_tierthree" value=""&gt; &lt;input type="number" class="tiertwofield" name="attr_tiertwo" value=""&gt; &lt;input type="number" class="tieronefield" name="attr_tierone" value=""&gt; &lt;input type="number" class="tierabovesubfield" name="attr_tierabovesub" value=""&gt; &lt;input type="number" class="synergybonusfield" name="attr_synergybonus" value=""&gt; &lt;input type="number" class="synergyoffsetfield" name="attr_synergyoffset" value=""&gt; &lt;input type="number" class="subtotalfield" name="attr_group" value=""&gt; &lt;input type="text" class="firstskillATRfield" name="attr_attribute" value=""&gt; &lt;input type="text" class="firstATRmodField" name="attr_attributemodifier" value=""&gt; &lt;input type="text" class="secondATRField" name="attr_attributetwo" value=""&gt; &lt;input type="text" class="secondATRmod" name="attr_attributemodifiertwo" value=""&gt; &lt;input type="text" class="otherskillfield" name="attr_othermodifiersfill" value=""&gt; &lt;/div&gt; &lt;/fieldset&gt; //all combat specz tier 4 on("change:repeating_all-combat-specz:tierfour sheet:opened", function () { getAttrs(["repeating_all-combat-specz_tierfour", ], function (values) { let columntierfour = +values.repeating_all-combat-specz_tierfour || 0; let total = columntierfour * 4; setAttrs({repeating_all-combat-specz_points: total}); }); }); Causes all the sheetworkers to stop working. If I change it back to non-kebab-case it works normally again. ("repeating_allcombatspecz") Perhaps you were referring to a different part of the sheet that is supposed to use kebab-case?
1699560008

Edited 1699560301
GiGs
Pro
Sheet Author
API Scripter
I personally don't use kebab case very often, because it messes up sheet workers if you don't change your syntax. You cant use values.kebab-case , because kebab-case is not a valid attribute name in javascript. You'd have to use values['kebab-vase'] (the syntax that lets you wrap the name in quotes). This line is a problem, for example: let columntierfour = +values.repeating_all-combat-specz_tierfour || 0; that should be let columntierfour = +values['repeating_all-combat-specz_tierfour'] || 0; the setAttrs would need changing too (putting square brackets and quotes around the attribute name). That said, you have to think carefully everytime you use the repeating section syntax that allows you to avoid using row Ids. It looks simpler (and i do use it, sometimes), but often causes problems.
1699561031
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
That's really good to know. Yeah I think avoiding brackets would be preferred, though that's doable. That said, you have to think carefully everytime you use the repeating section syntax that allows you to avoid using row Ids. It&nbsp; looks&nbsp; simpler (and i do use it, sometimes), but often causes problems. Not entirely sure what you mean? For now just trying to rename things to get rid of capital letters I've used and redundancies.
1699561922
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Like GiGs, I avoid using kebab-case whenever I can, but there are two places where it's the only option; repeating section names, and action button names. This is because repeating sections expect a very specific pattern of underscores, so you can't use underscores in the section name. And I use it for action button names because underscores in action button names in a repeating section cause them to not trigger events. Also, like GiGs, I highly recommend not using the shortcut syntax for accessing repeating section data without the row ID. It just causes misunderstandings about what's actually going on in the code. The bracket notation isn't that much extra to use, and is something that you will probably wind up using quite frequently as you dig into more and more sheetworkers; it's actually become nearly my default syntax mode just because I have to use it so often for various methods. For your sheetworker that is causing issues after the repeating section name change, I'd change it to this: //all combat specz tier 4 on("change:repeating_all-combat-specz:tierfour sheet:opened", function (event) { const [,section,rowID] = event.triggerName.match(/(repeating_.+?)_(.+?)_/); const row = `${section}_${rowID}`; getAttrs([`${row}_tierfour`], function (values) { let columntierfour = +values[`${row}_tierfour`] || 0; let total = columntierfour * 4; setAttrs({ [`${row}_points`]: total}); }); }); This is two extra lines of code, but it means that all of the sheetworkers will be using the same types of reference to data. This makes your code more consistent, and allows you to eventually write generic functions that work on your data instead of needing to write the same code multiple times and different ways. You can also make these two extra lines of code a separate function that you can call as needed to get the rowID and row.
1699564156
GiGs
Pro
Sheet Author
API Scripter
Scott C. said: Like GiGs, I avoid using kebab-case whenever I can, but there are two places where it's the only option; repeating section names, and action button names. I get what you're saying, but there is an option: You can avoid using any punctuation :) All lower case and just run two words together if you need to use them (or just use one word).
1699567485
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Yes, but to me that is worse than having to do bracket notation. It makes reading complicated macros and code more difficult in my opinion.
1699581951
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Hmm. Yes. To my newbie learning code brain, I was thinking that kebab-case was required for some reason based on that language. It is a bit chaotic to read with all "one word", but it's okay for me if I don't string too many words together. Suppose a lot of reading code is somewhat subjective.
1699585505

Edited 1699585709
GiGs
Pro
Sheet Author
API Scripter
The main issue with stringing words together is it's not very friendly to accessibility aids. Some people use accessibility tools, and they don't know how to handle where one word ends and other begins. But if the code you're using is just for yourself, that doesn't matter. My preference is to use single words, so this issue never crops up, but I do sometimes string two words together in code that is just for my use. Scott mentions the readability of code, which is very important, considering if its a public sheet, others will take over from you at some point and need to know how to read your sheet. i think this is solved with a comment explaining what the two words are. Once you know what the two words are, reading them becomes second nature. But the accessibility issue is also important.
1699587127
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Hmm very good point. Having the sheet be usable by possible future editors is a good thing to keep in mind.
1699590668
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Well, so far I updated the naming convention to be all lower case, and got rid of repeating section redundancies (so I don't having something like "repeating_allcombatspecz_allcombatspeczpoints" and instead have "repeating_allcombatspecz_points" for example. Then (Scott) I copy pasted the first code you provided, with adjustments for the new naming thing. I also tried adding the extra code lines instead of the repeating section shortcut from before, for one of the sections. But so far it's not working. Not sure enough about what's going on to know what to adjust. Will probably try GiG's method next.&nbsp; For reference, looks like this now (omitted the explainer lines above in the copy paste below): const repeatingDetails = [ {section:'repeating_allcombatspecz',fields:['points']}, {section:'repeating_meleecombatspec',fields:['points']}, {section:'repeating_rangedcombatspec',fields:['points']} ]; const getSections = function(sectionDetails,callback){ let queueClone = _.clone(sectionDetails); const worker = (queue,repeatAttrs=[],sections={})=&gt;{ let detail = queue.shift(); getSectionIDs(detail.section,(IDs)=&gt;{ sections[detail.section] = IDs; IDs.forEach((id)=&gt;{ detail.fields.forEach((f)=&gt;{ repeatAttrs.push(`${detail.section}_${id}_${f}`); }); }); if(queue.length){ worker(queue,repeatAttrs,sections); }else{ callback(repeatAttrs,sections); } }); }; if(!queueClone[0]){ callback([],{}); }else{ worker(queueClone); } }; const watchAttrs = ['allcombatpoints','repeating_allcombatspecz:points','meleecombatpoints','repeating_meleecombatspec:points','rangedcombatpoints','repeating_rangedcombatspec:points']; watchAttrs.forEach(attr =&gt; { on(`change:${attr}`,() =&gt; { getSections(repeatingDetails,(repeatAttrs,sections) =&gt; { const nonRepeatAttrs = ['allcombatpoints', 'meleecombatpoints', 'rangedcombatpoints']; getAttrs([...repeatAttrs,...nonRepeatAttrs],(attributes) =&gt; { const total = [...nonRepeatAttrs,repeatAttrs].reduce((tot,attrName) =&gt; { return tot + (+attributes[attrName] || 0); },0); setAttrs({pbcombatskillstotals:total}); }) }) }) }) Also quadruple checked that the attribute names I put in were accurate with the html.
1699594506

Edited 1699594714
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Well GiG I tried your method some more, and so far haven't got it to work. I troubleshooted for typos and am fairly confident I got all the names to match and stuff. I want to leave the "spec" on the naming convention as it helps me remember it's a skill specialization row, not the group skill. In the system, they're an important distinction and it helps me remember. But I did shorten them to "allcombatspec, meleespec, rangedspec" repectively. So it looks like this now. Up top, at the start of the combat skills html section: &lt;input type="hidden" name="attr_total_allcombatspec" value="0"&gt; &lt;input type="hidden" name="attr_total_meleespec" value="0"&gt; &lt;input type="hidden" name="attr_total_rangedspec" value="0"&gt; And down below, the block you included and said not to touch, as well as... const sections = ['allcombatspec','meleespec', 'rangedspec'] sections.forEach(section =&gt; { on(`change:repeating_${section}:points remove:repeating_${section}`, function() { repeatingSum(`${section}_total`,section,"points"); }); }); on("change:allcombatpoints change:meleecombatpoints change:rangedcombatpoints change:total_rangedspec change:total_meleespec change:total_allcombatspec sheet:opened", function () { getAttrs([ "allcombatpoints", "meleecombatpoints", "rangedcombatpoints", "total_rangedspec", "total_meleespec", "total_allcombatspec", ], function (values) { let cppt = +values.allcombatpoints || 0; let cpszpt = +values.total_allcombatspec || 0; let mlpt = +values.meleecombatpoints || 0; let mtszpt = +values.total_meleespec || 0; let rgpt = +values.rangedcombatpoints || 0; let rgszpt = +values.total_rangedspec || 0; let total = cppt + cpszpt + mlpt + mtszpt + rgpt + rgszpt; setAttrs({pbcombatskillstotals: total}); }); }); Not sure if I missed anything. It's worth mentioned the sheetworkers in general are still working, so I didn't include some line that broke everything. I'll try troubleshooting some more tomorrow. Went through it pretty thoroughly though. If you're curious to look, here's the whole sheet. The repeating combat section starts at line 851, and the sheetworkers are at the end. The start of which is line 3128, and the end 4656 (where the repeating calculations are).&nbsp; <a href="https://github.com/danimagaming/dodas-sheet_troubleshooting/blob/main/dodas_troubleshoot.html" rel="nofollow">https://github.com/danimagaming/dodas-sheet_troubleshooting/blob/main/dodas_troubleshoot.html</a> Edit: I should probably add that it is working when it comes to adding up the totals of the not-repeating fields. But it's not changing (good or bad) when the value changes in the "points" field of any of the repeating sections.
1699596569

Edited 1699596682
GiGs
Pro
Sheet Author
API Scripter
I am about to go to bed, so I cant check it now. It's certainly possible thatI have some subtle error in my code, but I'd also double-check syntax and names. In principle it should work. The fact that you are incorporating new code, but also changing the names of things at the same time is a recipe for disaster. It's so easy to make a mistake. Personally, I'd revert all name changes then try Scott and/or my code, changing only what you need to make them work (Ithink for mine, you only needed to change the points attribute and the repeating section names, and for scott you din't need to hcange anything IIRC). When you have them working, only then make the rest of your name changes - changing html, css, and sheet worker together. and changing only one name that is used on sheet workers at a time. Error checking can be really hard. For complex stuff, it can also be worth creating a blank sheet with nothing else in it but what you are testing, to get rid of other sources of error.
1699597007

Edited 1699640490
GiGs
Pro
Sheet Author
API Scripter
I couldn't resist a quick look before I went, and I spotted something. That was my error The repeatingSum total attribute name has _total at the end repeatingSum(`${section}_total`,section,"points"); But the other sheet worker has total_ at the start: on("change:allcombatpoints change:meleecombatpoints change:rangedcombatpoints change:total_rangedspec change:total_meleespec change:total_allcombatspec sheet:opened", function () { getAttrs([ "allcombatpoints", "meleecombatpoints", "rangedcombatpoints", "total_rangedspec", "total_meleespec", "total_allcombatspec", ], They should be the same way around, and match the names in html. It looks like you can fix this just by changing the code in one place: const sections = ['allcombatspec','meleespec', 'rangedspec'] sections.forEach(section =&gt; { on(`change:repeating_${section}:points remove:repeating_${section}`, function() { repeatingSum(` total_ ${section}`,section,"points"); }); }); I probably made it harder for you to spot this by using a forEach loop and this string syntax (` total_ ${section}`).Sorry about that!
1699638886
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
I made that change, and it works like a charm! Awesome! &nbsp;Thank you both so much! I was very careful and thorough with the naming changes, and made sure all the existing sheetworkers still worked with the name changes. I thought that some capital letters might be messing things up, so felt I had to do it now. But yeah, it is a recipe for disaster. Thankfully that change was all that was needed! I did make some sense of the logic behind what the javascript is doing based on what you explained, but something like total being before ${section} isn't something that registered as significant for me, since I don't fully understand the javascript being used. But glad you caught it! Now on to try this for a second section and see how it goes.
1699643850

Edited 1699643888
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Okay! I succesfully repeated this process for a second area. However, in the process I discovered something interesting (perhaps just to me). Initially I made a copy of the 'forEach' loop code, and put the new attributes in it. But that broke the sheetworkers. Instead, I had to add the new repeating section attribute names to the previous 'forEach' loop code, and then add a separate auto calculation chunk. So it ended up looking like this. const sections = ['allcombatspec','meleespec', 'rangedspec', 'knowledgespec', 'mobilityspec', 'mechanicaldevicesspec', 'medicinespec', 'perceptionspec', 'professionspec', 'socialwitspec', 'socialpersonalityspec', 'strengthskillsspec', 'survivaloutdoorsspec', 'survivalurbanspec' ] sections.forEach(section =&gt; { on(`change:repeating_${section}:points remove:repeating_${section}`, function() { repeatingSum(`total_${section}`,section,"points"); }); }); on("change:allcombatpoints change:meleecombatpoints change:rangedcombatpoints change:total_rangedspec change:total_meleespec change:total_allcombatspec sheet:opened", function () { getAttrs([ "allcombatpoints", "meleecombatpoints", "rangedcombatpoints", "total_rangedspec", "total_meleespec", "total_allcombatspec", ], function (values) { let cppt = +values.allcombatpoints || 0; let cpszpt = +values.total_allcombatspec || 0; let mlpt = +values.meleecombatpoints || 0; let mtszpt = +values.total_meleespec || 0; let rgpt = +values.rangedcombatpoints || 0; let rgszpt = +values.total_rangedspec || 0; let total = cppt + cpszpt + mlpt + mtszpt + rgpt + rgszpt; setAttrs({pbcombatskillstotals: total}); }); }); //ADVENTURING skills repeating sections on("change:knowledgepoints change:mobilitypoints change:mechanicaldevicespoints change:medicinepoints change:perceptionpoints change:professionpoints change:socialpoints change:socialwitpoints change:socialpersonalitypoints change:strengthskillspoints change:survivalpoints change:survivaloutdoorspoints change:survivalurbanpoints change:total_knowledgespec change:total_mobilityspec change:total_mechanicaldevicesspec change:total_medicinespec change:total_perceptionspec change:total_professionspec change:total_socialwitspec change:total_socialpersonalityspec change:total_strengthskillsspec change:total_survivaloutdoorsspec change:total_survivalurbanspec sheet:opened", function () { getAttrs([ "knowledgepoints", "mobilitypoints", "mechanicaldevicespoints", "perceptionpoints", "professionpoints", "socialpoints", "socialwitpoints", "socialpersonalitypoints", "strengthskillspoints", "survivalpoints", "survivaloutdoorspoints", "survivalurbanpoints", "total_knowledgespec", "total_mobilityspec", "total_mechanicaldevicesspec", "total_medicinespec", "total_perceptionspec", "total_professionspec", "total_socialwitspec", "total_socialpersonalityspec", "total_strengthskillsspec", "total_survivaloutdoorsspec", "total_survivalurbanspec", ], function (values) { let k = +values.knowledgepoints || 0; let mb = +values.mobilitypoints || 0; let mc = +values.mechanicaldevicespoints || 0; let me = +values.medicinepoints || 0; let per = +values.perceptionpoints || 0; let pf = +values.professionpoints || 0; let s = +values.socialpoints || 0; let sw = +values.socialwitpoints || 0; let sp = +values.socialpersonalitypoints || 0; let st = +values.strengthskillspoints || 0; let sv = +values.survivalpoints || 0; let svo = +values.survivaloutdoorspoints || 0; let svu = +values.survivalurbanpoints || 0; let ksp = +values.total_knowledgespec || 0; let mbsp = +values.total_mobilityspec || 0; let mcsp = +values.total_mechanicaldevicesspec || 0; let mesp = +values.total_medicinespec || 0; let pesp = +values.total_perceptionspec || 0; let pfsp = +values.total_professionspec || 0; let swsp = +values.total_socialwitspec || 0; let spsp = +values.total_socialpersonalityspec || 0; let stsp = +values.total_strengthskillsspec || 0; let svosp = +values.total_survivaloutdoorsspec || 0; let svusp = +values.total_survivalurbanspec || 0; let total = k + mb + mc + me + per + pf + s + sw + sp + st + sv + svo + svu + ksp + mbsp + mcsp + mesp + pesp + pfsp + swsp + spsp +stsp + svosp + svusp; setAttrs({pbadventuringskillstotals: total}); }); }); Good to know it works like that for the future. I'm not sure why running another forEach loop later breaks things, but makes enough sense to use it for now.
1699647591

Edited 1699650500
GiGs
Pro
Sheet Author
API Scripter
There's probably a clash in variable names somewhere. I meant to say, if you want to add extra repeatingSum sheet workers, you just need to expand that sections variable. By the way you can streamline code like this: //ADVENTURING skills repeating sections on("change:knowledgepoints change:mobilitypoints change:mechanicaldevicespoints change:medicinepoints change:perceptionpoints change:professionpoints change:socialpoints change:socialwitpoints change:socialpersonalitypoints change:strengthskillspoints change:survivalpoints change:survivaloutdoorspoints change:survivalurbanpoints change:total_knowledgespec change:total_mobilityspec change:total_mechanicaldevicesspec change:total_medicinespec change:total_perceptionspec change:total_professionspec change:total_socialwitspec change:total_socialpersonalityspec change:total_strengthskillsspec change:total_survivaloutdoorsspec change:total_survivalurbanspec sheet:opened", function () { getAttrs([ "knowledgepoints", "mobilitypoints", "mechanicaldevicespoints", "perceptionpoints", "professionpoints", "socialpoints", "socialwitpoints", "socialpersonalitypoints", "strengthskillspoints", "survivalpoints", "survivaloutdoorspoints", "survivalurbanpoints", "total_knowledgespec", "total_mobilityspec", "total_mechanicaldevicesspec", "total_medicinespec", "total_perceptionspec", "total_professionspec", "total_socialwitspec", "total_socialpersonalityspec", "total_strengthskillsspec", "total_survivaloutdoorsspec", "total_survivalurbanspec", ], function (values) { let k = +values.knowledgepoints || 0; let mb = +values.mobilitypoints || 0; let mc = +values.mechanicaldevicespoints || 0; let me = +values.medicinepoints || 0; let per = +values.perceptionpoints || 0; let pf = +values.professionpoints || 0; let s = +values.socialpoints || 0; let sw = +values.socialwitpoints || 0; let sp = +values.socialpersonalitypoints || 0; let st = +values.strengthskillspoints || 0; let sv = +values.survivalpoints || 0; let svo = +values.survivaloutdoorspoints || 0; let svu = +values.survivalurbanpoints || 0; let ksp = +values.total_knowledgespec || 0; let mbsp = +values.total_mobilityspec || 0; let mcsp = +values.total_mechanicaldevicesspec || 0; let mesp = +values.total_medicinespec || 0; let pesp = +values.total_perceptionspec || 0; let pfsp = +values.total_professionspec || 0; let swsp = +values.total_socialwitspec || 0; let spsp = +values.total_socialpersonalityspec || 0; let stsp = +values.total_strengthskillsspec || 0; let svosp = +values.total_survivaloutdoorsspec || 0; let svusp = +values.total_survivalurbanspec || 0; let total = k + mb + mc + me + per + pf + s + sw + sp + st + sv + svo + svu + ksp + mbsp + mcsp + mesp + pesp + pfsp + swsp + spsp +stsp + svosp + svusp; setAttrs({pbadventuringskillstotals: total}); }); }); If your goal is just to add everything in the values variable, you can do it very quickly, like this: const total = Object.values.reduce((sum,item) =&gt; sum + (+item || 0), 0); The reduce function is complicated to explain, but you can also do the same thing with an easier to understand loop: let total = 0; Object.values(values).forEach(item =&gt; { total += +item || 0; }); Object.values(name_of_object) creates an array of the values of an object (in this case, the confusingly named values object). Since all of the attribute values in there are being added together, you can do it with a loop. For the record, the values object is made up stuff like total_mobilityspec: "7", survivalurbanpoints: "3" This just grabs all the values and puts them in a single array, and ignores the attribute names. So your full worker should become: on("change:knowledgepoints change:mobilitypoints change:mechanicaldevicespoints change:medicinepoints change:perceptionpoints change:professionpoints change:socialpoints change:socialwitpoints change:socialpersonalitypoints change:strengthskillspoints change:survivalpoints change:survivaloutdoorspoints change:survivalurbanpoints change:total_knowledgespec change:total_mobilityspec change:total_mechanicaldevicesspec change:total_medicinespec change:total_perceptionspec change:total_professionspec change:total_socialwitspec change:total_socialpersonalityspec change:total_strengthskillsspec change:total_survivaloutdoorsspec change:total_survivalurbanspec sheet:opened", function () { getAttrs([ "knowledgepoints", "mobilitypoints", "mechanicaldevicespoints", "perceptionpoints", "professionpoints", "socialpoints", "socialwitpoints", "socialpersonalitypoints", "strengthskillspoints", "survivalpoints", "survivaloutdoorspoints", "survivalurbanpoints", "total_knowledgespec", "total_mobilityspec", "total_mechanicaldevicesspec", "total_medicinespec", "total_perceptionspec", "total_professionspec", "total_socialwitspec", "total_socialpersonalityspec", "total_strengthskillsspec", "total_survivaloutdoorsspec", "total_survivalurbanspec", ], function (values) { let total = 0; Object.values(values).forEach(item =&gt; { total += (+item || 0); }); setAttrs({pbadventuringskillstotals: total}); }); }); The event line (starting with on ) and getAttrs line don't change*, but the body of the function can be streamlined. * there are ways to streamline them, but that's not this topic :) I mention this because you can use the same process in every sheet worker where you are just totalling up every attribute you have collected in getAttrs.
1699648624
Daniel S.
Pro
Marketplace Creator
Sheet Author
Compendium Curator
Neat. Yeah that's way more concise, and saves a lot of steps!