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

Summing values of repeating attributes - custom character sheet

Hello, I am almost finished my first custom character sheet after a lot of hard work I am stuck on one final problem. I don't really get repeating attributes! So I have some default skills and then I have repeating skills just in case players really want to add like 100 different languages or something. But I can't add the skill points invested in these extra skills to my regular skills (just to help with character creation so players don't have to manually sum the numbers). So here is my code. I have a repeating section called skills and each skill has an attribute called "modskill" which is the number I am trying to sum. Tbh I am not too experienced in javascript so yeah be nice, I am just doing this for a hobby. on("change:repeating_skills:modskill [etc.]         let repeatingtotal = 0;         getSectionIDs("repeating_skills", function(idarray) {             for (var i = 0; i < idarray.length; i++) {               getAttrs([idarray[i]], function(values){                 let mod = parseInt(values.modskill) || 0;                 repeatingtotal = repeatingtotal + mod;               });                              }             setAttrs({                     "Total_repeat": repeatingtotal             });         }); [then I sum all my other skills which is working fine and add the two together] Have I misunderstood something fundamental about repeating sections or something?
1690739870

Edited 1690740045
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Hi Hannah! Welcome to sheet authoring! There are a few issues in the code you shared.: The idarray that getSectionIDs provides to the callback, is just an array of rowIDs. It doesn't contain any attribute names. So, what your current code is doing is the equivalent of: getAttrs(['ID1','ID2','ID3',...'IDN'])) Once you have the idarray from getSectionIDs, you need to create the getArray for getAttrs. getAttrs, getSectionIDs, and setAttrs are asynchronous functions. This means that you need to do all your work with the values they pass to their callbacks inside those callbacks These asynchronous functions should never be wrapped in a loop where multiple of them will be fired at once. This can cause extreme latency, and more importantly a race condition as there is no guarantee that the first one called will be the first one returned. Additionally, a given event listener (e.g. on()) should only ever have one set of getAttrs/setAttrs. The reason for this is that using multiple getAttrs and/or setAttrs can cause noticeable latency on your sheet and can introduce race conditions into your sheet. So, taking all of that together, I'd rewrite your listener into one like so: const skillNames = ['acrobatics','athletics'];//replace with list of your actual skillNames const modNames = skillNames.map(skill => `${skill}_modskill`)//assemble the mod attribute name. I'm just assuming about your naming scheme here. // While this looks like it's breaking my advice to not wrap getAttrs/setAttrs in a loop, // this is ok because the actual running of getAttrs/setAttrs will only be run a single time for each listener, // and it is unlikely that multiple of these will fire at once. ['repeating_skills:modskill',...modNames].forEach(name=>{ // for each attribute name, setup a listener // this is the same as doing: // on('change:attr1 change:attr2 ... change:attrn') // but is more maintainable as we simply have to // add new attributes to the skillNames list above // instead of writing out the full listener for new // attributes on(`change:${name}`,(event) => { getSectionIDs("repeating_skills", function (idarray) { // create an array of all the repeating attribute names. e.g. repeating_skills_-0982343lk089-2_modskill const repeatNames = idarray.map(id => `repeating_skills_${id}_modskill`); // Assemble the full array of attribute names to get from the database const getArr = [...repeatNames,...modNames]; // get the attribute values from the database getAttrs(getArr,(attributes) => { // an empty object that we will store our changes in // overkill in this instance, but a good practice to get into const setObj = {}; // total up all the skill mods setObj.skill_total = getArr.reduce((total,attr)=>{           const attrValue = +attributes[attr] || 0; // this is the reduce method, a method on all arrays // it allows us to manipulate a final value in response to the values of an array. return total + attrValue; },0); // finally set the changed attributes setAttrs(setObj,{silent:true});//I always use the silent mode on setAttrs to prevent cascades }); }); }); })
1690741997

Edited 1690742364
GiGs
Pro
Sheet Author
API Scripter
Scott's is a great explanation, with two possible drawbacks: (a) I wouldnt use the reduce function in something designed to help people who aren't highly experienced, and (b) It looks like Hannah just wanted to total the cost in a single repeating section, so listing all the skills probably isn't needed. Hannah, as I see it, you have two ways to go. Either build the function yourself, or use a function someone else has build for exactly this purpose. For example of the latter, you could try repeatingSum . There's an explanation how to use it on that page, and another here: <a href="https://cybersphere.me/multiple-repeating-sections-repeatingsum/" rel="nofollow">https://cybersphere.me/multiple-repeating-sections-repeatingsum/</a> If you build the function yourself, you want to avoid putting getAttrs inside of a loop. Each separate getAttrs call has to ask Roll20's servers for an answer - that means travelling across the internet and waiting for a response. If your section has, say, 100 rows, that means it has to do that 100 times. That can easily create lag, espeically during busy times. You are best off following Scott's suggestion to build an array of the stat names you want and ask for them just once. That could look like this: on ( 'change:repeating_skills:modskill' , function () { &nbsp; &nbsp; getSectionIDs ( 'repeating_skills' , function ( id_array ) { &nbsp; &nbsp; &nbsp; &nbsp; const fields = []; // we need an array of attribute names for getAttrs. push is a function that adds to the end of an array. &nbsp; &nbsp; &nbsp; &nbsp; id_array . forEach ( id =&gt; fields . push ( `repeating_skills_ ${ id } _modskill` )); // Scott's example using .map is better and more concise than the above - you now have two ways of doing the same thibng to look at. &nbsp; &nbsp; &nbsp; &nbsp; // now that we have built an array of all the attribute names we want in all rows, we can use one getAttrs &nbsp; &nbsp; &nbsp; &nbsp; getAttrs ( fields , function ( values ) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let repeatingtotal = 0 ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // now we need to loop throw id_array again and use all the field names. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; id_array . forEach ( id =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; repeatingtotal += + v [ `repeating_skills_ ${ id } _modskill` ] || 0 ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "Total_repeat" : repeatingtotal &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); }); I've built this worker close to the techniques you've used, so you should be able to figure out what it's doing. The big difference is using forEach instead of for - its a loop function, like for , but doesn't need to to declare a counting variable (i). If you have more questions, ask away. Refer back to Scotts example to understand what's going on, and also check out my series ion repeating sections here: <a href="https://cybersphere.me/roll20-sheet-author-master-list/" rel="nofollow">https://cybersphere.me/roll20-sheet-author-master-list/</a>
Hi guys! This is great. I really appreciate all this. I feel a little silly seeing that there is a community wiki page. I wonder why I couldn't find it when I was really searching quite intently for this!