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

Sheetworkers and Repeating sections help

I'm very new to working with Roll20 character sheets and am currently working on making some modifications to the WFRP2e sheet to modernise it a bit. As part of that I'm looking to turn the weapon inventory section into a repeating section (which has worked well enough) but changing to a repeating section means using a Sheetworker to calculate the section's inventory weight which isn't playing ball. My sheetworker, see below, doesn't appear to be running at all as none of the inbuilt console.log statements are appearing when the sheet is opened or the relevant attributes are edited. Any insight the can be offered would be appreciated! Sheetworker: <script type="text/worker"> <!-- Melee Weapon Total Weight script --> on("sheet:opened change:meleeenc remove:repeating_meleeweapon", function() { console.log("Weapon-enc script firing") getSectionIDs("meleeweapon", function(idarray) { console.log(idarray) let enc_total = 0 for(let i=0; i < idarray.length; i++) { let rowname = "repeating_meleeweapon_-" + idarray[i] + "_meleeenc" getAttrs([rowname], function(values) { console.log(rowname) let enc = parseInt(values.rowname)||0; console.log(enc) enc_total += enc }); console.log(totalmeleeenc) }); }); setAttrs({ totalmeleeenc: enc_total }); }); </script> There's another script block ahead of this section that seems to come from TheAaronSheet and a third script block after it too. I had previously placed the worker in the TheAaronSheet script block to see if that helped at all but it was equally non-functional there too. I can post the repeating section referenced if it would help but given that the script isn't logging when a sheet is opened I'm not sure if that would be the issue. Any help is appreciated!
1730054924

Edited 1730055067
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Welcome to sheet creation! There's a few things here: You shouldn't have the sheet:opened in this listener. This is functionality that should not change between openings of the sheet, and just adds extra processing time to the sheet start up. In general, all sheetworkers should be in the same script tag. This allows you to reuse functions. You are using html comment notation inside the script tag, but the script tag uses js notation, this will cause an error that you can probably see in the browser console. your change event listener for the meleeenc attribute needs to be specific to the repeating section ( change:repeating_meleeweapon:meleeenc ). Not related, but I would recommend using full words for attribute names as they will make more sense to your users you are doing a getAttrs in a loop and then trying to setAttrs outside of that getAttrs based on things that happen in the getAttrs. getAttrs, setAttrs, and nearly all other sheetworker functions are asynchronous. This means that they must be nested in order to work. Putting getAttrs and setAttrs inside a loop that is going to be executed in response to an event is something that should be avoided at nearly all costs: Doing these in a loop results in many requests being sent to the server, which will add lag to your sheet. These functions take the same amount of time to resolve whether you are working on one attribute or a hundred thousand attributes; the network latency is far longer than any computation time difference on the server end. Doing these in a loop also means that you can't use what you are doing inside any given run of the function for anything else that is being done in response to the event. when you are assembling the full repeating section attribute name, you are adding an extra dash in. the id includes the dash and is already part of the data in the idarray. With all of that taken into account, your sheetworker should look like this: // Melee Weapon Total Weight script // Melee Weapon Total Weight script on("change:repeating_meleeweapon:meleeenc remove:repeating_meleeweapon", function() { console.log("Weapon-enc script firing") getSectionIDs("meleeweapon", function(idarray) { console.log(idarray) // assemble the array of attributes to get the values of const getArray = idarray.reduce((arr,id) => { // using template literal syntax for the string concatenation // as it's easier to read arr.push(`repeating_meleeweapon_${id}_meleeenc`); return arr; },[]); // get the attribute values for all our rows getAttrs(getArray,values => { // an empty object to store our changes in. // This is a good practice so that you can work with multiple changes at once. // the keys of this object will be the attribute names and the // values will be what those attributes should be set to. const setObj = {}; // calculate the total encumbrance setObj.totalmeleeenc = idArray.reduce((total,id) => { const encValue = +values[`repeating_meleeweapon_${id}_meleeenc`] || 0; total += encValue; return total; },0); console.log(setObj); // apply our changes setAttrs(setObj); }); }); });
1730063909

Edited 1730659179
GiGs
Pro
Sheet Author
API Scripter
I agree with everything Scott has said. The only change I'd recommend is changing this line getSectionIDs("meleeweapon", function(idarray) { to getSectionIDs("repeating_meleeweapon", function(idarray) { I remember that - way back when I first started coding here - there were very occasional issues if you left off the repeating_ part. I know the documentation says you can leave it off, but in my experience it was unwise. Things may have changed now, but I still do it, and I see no harm in it.
1730129936
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Ooh, good catch Gigs! I missed that in my critique.
1730147885
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
ah, actually this is the one that needs it without the repeating.
Hi guys, Thanks so much for the guidance, it's been a massive help! As you may have spotted I'm still pretty new to Javascript and trying to get a feel for what's what. With that said can I ask what's going on in the section below: const encValue = +values[`repeating_meleeweapon_${id}_meleeenc`] || 0; This looks like calling a key from a python dictionary but it doesn't seem to be working the same way. I've tried adding a small calculation to allow for a "carried" box to negate the encumbrance value of uncarried items: const encValue = +(values[`repeating_meleeweapon_${id}_meleeenc`] * values['repeating_meleeweapon_${id}_weaponcarried']) || 0;   But this seems to be causing an error and triggering the OR option instead. I'm not totally sure what sort of object the getAttr call is returning and I'm also not completely clear on the = +values notation either. Any help you can give would be appreciated. Full modified script (in case it's relevant): <script type="text/worker"> //Melee Weapon Total weight script //Listening for changes to weapon encumberance on("change:repeating_meleeweapon:meleeenc change:repeating_meleeweapon:weaponcarried remove:repeating_meleeweapon", function() { console.log("Weapon-enc script firing") getSectionIDs("meleeweapon", function(idarray) { console.log(idarray) //assemble the array of attributes const getArray = idarray.reduce((arr,id) => { arr.push(`repeating_meleeweapon_${id}_meleeenc`); arr.push(`repeating_meleeweapon_${id}_weaponcarried`); return arr; }, []); console.log(getArray) getAttrs(getArray, values => { //An empty object to store changes in. const setObj = {}; //calculate encumberance setObj.totalmeleeenc = idarray.reduce((total, id) =>{ const encValue = +(values[`repeating_meleeweapon_${id}_meleeenc`] * values['repeating_meleeweapon_${id}_weaponcarried']) || 0; total += encValue; return total; },0); console.log(setObj); //apply the changes setAttrs(setObj); }); }); }); </script>
1730613516

Edited 1730614927
GiGs
Pro
Sheet Author
API Scripter
You asked a question which I'll get to, but first: You have to be careful when handling OR operators in JS calculations. This is because if any error is returned, the OR will be returned. So assuming you have this: const encValue = +(values[`repeating_meleeweapon_${id}_meleeenc`] * values['repeating_meleeweapon_${id}_weaponcarried']) || 0;&nbsp;&nbsp; an error in either the the values properties will cause the OR be used. This would be better written like: const encValue = (+values[`repeating_meleeweapon_${id}_meleeenc`] || 0) * (+values['repeating_meleeweapon_${id}_weaponcarried']) || 0);&nbsp;&nbsp; Personally, I wouldn't do this - its hard to keep track of all the brackets. I'd store them in separate variables, like this: const melee_enc = +values[`repeating_meleeweapon_${id}_meleeenc`] || 0; const weapon_carried = +values['repeating_meleeweapon_${id}_weaponcarried']) || 0; const encValue = melee_enc * weapon_carried;&nbsp;&nbsp; This kind of thing is easier to debug, too. And once I've done that a few times, I'd end up creating a function to put right after the script="text/worker" line: const num = score =&gt; +score || 0; That way I could replace those lines above with something closer to your original line: const encValue = num(values[`repeating_meleeweapon_${id}_meleeenc`]) * num(values['repeating_meleeweapon_${id}_weaponcarried']); The OR statements are built into the function, which means they are handled properly when doing things like adding or multiplying. Mort E. said: With that said can I ask what's going on in the section below: const encValue = +values[`repeating_meleeweapon_${id}_meleeenc`] || 0; There are several things happening here. First, look back at the getAttrs line. That looks something like: getAttrs(['something', 'another'], attributes =&gt; { If its more familiar, that can also be written as: getAttrs(['something', 'another'], function(attributes) { What is happening here: you supply an array of attribute names. Roll20 collects those names, then looks at the character sheet and gets the values of those attributes, and store both the attributes in their values in an object you name. So you end up with something like this: attributes = { something: "10", another: "18" }; The thing I've called attributes here is an object variable, and it is often called values , for obvious reasons. Whenever you use getAttrs, an object like this is created - so most of your sheet workers will have an object included. Then you have something like this: const something = +values[`something`] || 0; Here we are extracting something from the values object and storing it in a simpler variable. This isn't strictly necessary, and to a degree obfuscates what is really going on in the sheet worker, but it makes things simpler, so let's do it. So values['something'] gets the value of that attribute. But a quirk of roll20 is that attributes in character sheets are (almost) always stored as strings. This means you can't reliably perform calculations with them - you need to coerce them into numbers. Putting a + at the start of an expression does exactly that in Javascipt, so +values['something'] means you get a number. A problem occurs then if that attribute is not actually convertable into a number, and then another quirk of javascript comes into play. If you add || 0 to te end, the 0 will be used when an error occurs. This is because in javascript, an error before the OR property will be skipped, and the OR value is used instead. This is very handy! I haven't explained this part: `repeating_meleeweapon_${id}_meleeenc` because I wasn't sure if that was necessary. Ask again if it is. PS: you'll find a lot of help here: <a href="https://cybersphere.me/roll20/" rel="nofollow">https://cybersphere.me/roll20/</a>
1730615408

Edited 1730659820
GiGs
Pro
Sheet Author
API Scripter
The example Scott gave above is great, but I generally do not use reduce in helpful tips. It is a very advanced function, and the effort of learning how to use it can get in the way of doing other things. I am going to post another way to do the same thing, but if you find Scott's code easy to use, stick with that - Scott is very highly skilled in this area (more so than me!). In this code, I use the forEach functiion, which is basically a for loop, but a bit more advanced (and honestly more elegant) than a traditional for loop. You can see the basic approach is the same: in one loop, collect all the attributes to be used in getAttrs, and in a second loop inside getAttrs, perform the actual calculation. &lt; script type = "text/worker" &gt; &lt;/script&gt; &nbsp; &nbsp; // custom functions to be used by sheet workers whenever needed &nbsp; &nbsp; const num = ( score , fallback = 0) =&gt; + score || fallback ; &nbsp; &nbsp; const int = ( score , fallback = 0) =&gt; parseInt( score ) || fallback ; &nbsp; &nbsp; const section_name = ( section , id , field ) =&gt; `repeating_ ${ section } _ ${ id } _ ${ field } ` ; &nbsp; &nbsp; //Melee Weapon Total weight script &nbsp; &nbsp; //Listening for changes to weapon encumberance &nbsp; &nbsp; on ( "change:repeating_meleeweapon:meleeenc change:repeating_meleeweapon:weaponcarried remove:repeating_meleeweapon" , function () { &nbsp; &nbsp; &nbsp; &nbsp; console . log ( "Weapon-enc script firing" ); &nbsp; &nbsp; &nbsp; &nbsp; getSectionIDs ( "meleeweapon" , idarray =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console . log ( idarray ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //assemble the array of attributes &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let fields = []; // loop through each row, and push the relevant attributes from that row to the array. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; idarray . forEach ( id =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fields . push ( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; section_name ( 'meleeweapon' , id , 'meleeenc' ), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; section_name ( 'meleeweapon' , id , 'weaponcarried' ) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console . log ( fields ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getAttrs ( fields , values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //An empty object to store changes in. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const output = {}; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //a placeholder to hold the total as it is calculated &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let total = 0 ; //loop through each row, and multiply properties together. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; idarray . forEach ( id =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const melee_enc = num ( values [ section_name ( 'meleeweapon' , id , 'meleeenc' ) ]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const weapon_carried = num ( values [ section_name ( 'meleeweapon' , id , 'weaponcarried' ) ]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; total += melee_enc * weapon_carried ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; output . totalmeleeenc = total ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console . log ( output ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //apply the changes &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ( output ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); &lt;/ script &gt; Some variable names are different - you might find the variation helpful, it'll draw attention to the things you can change to match your own preference.