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/Modify Repeating Section Attrs from Inside an API?

I have an API script that updates a token character sheet's attribute: const myAggressionObj = findObjs({ _type: 'attribute', &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_characterid: myCharacter.id, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name: 'aggre ssion'}) [0]; myAggressionObj.set('current', getAttrByName(myCharacter.id, 'aggression', 'max')); This approach works fine for stand-alone attributes on the custom character sheet. How do I do something similar to an attribute within a repeating section? For example, this does not work: const myActionCooldowns = findObjs({ &nbsp;&nbsp;&nbsp;&nbsp;_type: 'attribute', &nbsp;&nbsp;&nbsp;&nbsp;_charac terid: myCha racter.id, &nbsp;&nbsp;&nbsp;&nbsp; name: MY_ATTR_NAME }); log('value: ' + JSON.stringify(myActionCooldowns)); I've tried using the name of the repeating attribute and the name of the repeating section. I've also tried just returning a single row within the repeating section, such as 'repeating_action_$0_actionCooldown'. All three return 'undefined'. Is there a way to access the repeating collection so I can iterate through its attributes? <a href="https://help.roll20.net/hc/en-us/articles/360037256714-Roll20-Mods-API" rel="nofollow">https://help.roll20.net/hc/en-us/articles/360037256714-Roll20-Mods-API</a> &nbsp;seems to have little/no documentation pertaining to repeating sections...
1751840825

Edited 1751841012
I did a dump of getAllObjs() and this is the property I want to get, change, and set: {\"name\":\"repeating_actions_-OUWdxt9KU0sDE41DQh0_actionCooldown\",\"current\":\"2\",\"max\":\"\",\"_id\":\"-OUWpuoHygeFfPpA1dUG\",\"_type\":\"attribute\",\"_characterid\":\"-OUVvQNmWPSFrEajFuYj\"} Since, at the time the script runs, I don't know the repeating id, how can I reference the attribute?
1751854126
GiGs
Pro
Sheet Author
API Scripter
You can't do this without the repeating ID to identify the row to change. So you need a way to identify which row to target. Are you wanting to change all rows in a section, or are you changing a single item and if so, how do you choose it?
1751876838

Edited 1751877166
The code needs to parse/lookup the ids and names for the repeating_actions first and then access the right attribute by composing the attribute name based on the ID. Or brute force search it based on the name. Somehting like let target; const attrs = findObjs({ type: "attribute", _characterid: cid }); for (const a of attrs) { &nbsp; &nbsp;if (a.get("name").startsWith("repeating_actions_") &amp;&amp; a.get("name").endsWith("_actionCooldown")) { &nbsp; &nbsp; &nbsp; &nbsp; target = a; &nbsp; &nbsp;) } Since the sandbox has all attributes in memory, that would normally not pose a performance issue (unless you do this for level 20 wizards). You are probably already aware, but this will not work with new (beacon is that name right?) charactersheets.
Thanks, Martijn! That worked. Don't know why I didn't think of that approach before. I just want to change the value of a row attribute that's already been set by the player, so no worries about default values and such. I could do this for any given character with a sheet worker, but I want to be able to select multiple tokens and - bam! - decrease the cooldowns by one turn for every cooldown set in every location. Problem solved! :D&nbsp;
A follow-up question... My API script looks for all cooldown attributes and reduces their current value by 1. If/when this occurs, a sheet-worker is triggered on('change') to read the new value and then set a flag on a hidden text field. The flag is then read by CSS to determine whether to restyle (highlight) the cooldown field. This makes it easy for the player/GM to see which items are on cooldown and which are not. &lt;input type='hidden' value='0' name='attr_actionIsOnCooldown' class='actionisoncooldown' /&gt; &lt;input type='number' class='actioncooldown' value='0' min='0' name='attr_actionCooldown'/&gt; Here's the CSS: .ui-dialog .charsheet input.actionisoncooldown[value='1'] ~ .actioncooldown { background-color: lemonchiffon; border-color: coral; color: coral; font-weight: bold; } Here's the sheet-worker: // COOLDOWN value is changed on('change:repeating_actions:actionCooldown', function(eventInfo) { getAttrs(['repeating_actions_actionCooldown'], function(values) { const myCooldown = parseInt(values.repeating_actions_actionCooldown) || 0; const output = {}; output['repeating_actions_actionIsOnCooldown'] = (myCooldown &gt; 0 ? 1 : 0); setAttrs(output); }); }); When the cooldown value is changed by a player/GM, this all works as expected. However, when the API script makes the change(s), it works once. After that, script changes to the field values doesn't seem to "register" with the sheet-worker event model until I click in the applicable cooldown field and tab away. &nbsp;Then, the flag "takes" and it works. I notice this also "resets" the API to allow it to work again (but only one time, as before.) Is this behavior related to the following note found in the documentation ? Note: &nbsp;Events are only triggered by changes made by players/the GM playing the game. Events will not be triggered by API changes. So if a player moves a piece on the tabletop, you would receive a "change:graphic" event. If you modify the same graphic's property in a Mod (API) script, there will not be a "change:graphic" event triggered. Does this mean I'm going to have to explicitly set those flags from within my script? Or is this not the problem? Here's the code of the API script: on('chat:message', function(msg) { if(msg.type == 'api' &amp;&amp; msg.selected &amp;&amp; msg.content.indexOf('!Cooldown') == 0){ const selectedObjs = msg.selected; _.each(selectedObjs, function(obj) { if(obj._type == 'graphic'){ const token = getObj('graphic', obj._id); if(token.get('_subtype') == 'token' &amp;&amp; token.get('represents') != '') { const myCharacter = getObj('character', token.get('represents')); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const attrs = findObjs({ _type: 'attribute', _characterid: myCharacter.id }); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (const a of attrs) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (a.get('name').startsWith('repeating_actions_') &amp;&amp; a.get('name').endsWith('_actionCooldown')) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const myCooldown = a.get('current'); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a.set('current', Math.max(myCooldown - 1, 0)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} } }; }); }; }); Thanks, again.
1751903043
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
you need to use a set method that is compatible with sheetworkers (.setWithWorker or the API version of setAttrs). See the wiki for full details.
Hi, Scott. .setWithWorker achieves the same result.&nbsp; Regarding setAttrs, this doesn't work: if (a.get('name').startsWith('repeating_actions_') &amp;&amp; a.get('name').endsWith('_actionCooldown')) { &nbsp;&nbsp;&nbsp;&nbsp;const myName = a.get('name'); &nbsp;&nbsp;&nbsp;&nbsp;const myCooldown = a.get('current'); setAttrs(myCharacter.id, {myName: Math.max(myCooldown - 1, 0)}); } The function creates an attribute called 'myName' instead of evaluating the contents of the var. I found &nbsp;very little information &nbsp;regarding the API version of setAttrs. Do you have an example you could share, or a link to better documentation?
1751924669
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
you are using setAttrs correctly, the issue in your code is just a basic javascript misunderstanding. To use a dynamic key in an object, you have to use bracket notation: if (a.get('name').startsWith('repeating_actions_') &amp;&amp; a.get('name').endsWith('_actionCooldown')) { &nbsp;&nbsp;&nbsp;&nbsp;const myName = a.get('name'); &nbsp;&nbsp;&nbsp;&nbsp;const myCooldown = a.get('current'); setAttrs(myCharacter.id, {[myName]: Math.max(myCooldown - 1, 0)}); } However, if .setWithWorker is giving the same issue where the sheetworker triggers once and then not again until you restart the sandbox the issue likely an issue with something trying to set an attribute value to an invalid value (e.g. undefined, NaN, or null). It could be your api script, or the sheetworker. Some things to check: you should parse the result of a.get('current') to make sure it is a number so that Math.max() will parse correctly Put a console.log in your sheetworker at several places and watch for the output in the api console to see where things are stopping. (e.g. just inside the listener, just inside the getAttrs, and then just before the setAttrs).
1751928658

Edited 1751928765
Okay, brackets worked on setAttrs, but this approach fails just the same. Restarting the sandbox makes no difference. The script always works correctly to reduce the counts, and the sheet-worker responds as expected, but only on the first call to the API script. On subsequent API script calls&nbsp; the sheet-worker no longer executes (verified w. console.log). Meanwhile, the other sheet-workers on the sheet continue to function as expected (also verified with console.log). If I click into one of the affected fields and tab away, *then* the afflicted sheet-worker fires as expected, just as if the value were changed. No errors are reported in the sandbox or in devtools.
1751944327
GiGs
Pro
Sheet Author
API Scripter
My (possibly uninformed) guess would be that your problem id in this syntax: getAttrs(['repeating_actions_actionCooldown'], function(values) { Do you know how to use getSectionIDs to insert the row id in your sheet worker, and have you tried that? (you'd need changes to setAttrs too)
1751947483
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
GiGs said: My (possibly uninformed) guess would be that your problem id in this syntax: getAttrs(['repeating_actions_actionCooldown'], function(values) { Do you know how to use getSectionIDs to insert the row id in your sheet worker, and have you tried that? (you'd need changes to setAttrs too) Since it's in a row specific listener, it shouldn't &nbsp;need it. But I also prefer just using the explicit id syntax.
Thanks, GiGs. Using getSectionIDs in the sheet-worker function doesn't resolve the issue either. :( To replicate the issue: On a character's sheet, in a repeating-actions row, set the value of actionCooldown (input type=number) to 2. Theet-worker function works as expected; actionIsOnCooldown is set to 1 and the correct CSS is applied to the actionCooldown field. Select that character's token and execute the API script. Result: actionCooldown value is reduced by 1 and the sheet-worker fires as expected. However, because actionCooldown's value is still larger than 0, the net result is no change to the row's actionIsOnCooldown value. Run the API script again. Again, the value is reduced by 1, this time to 0. However, the sheet-worker function doesn't fire AT ALL. It's like it doesn't even detect the on:change &nbsp;event to begin with. Running the API script multiple, additional times, continues to reduce actionCooldown values down to 0, as expected, but the sheet worker function never fires and field styles are never updated. NOW, click into an afflicted actionCooldown field on the character sheet and then tab out of it. BAM! The sheet-worker function executes and works as expected. It's as if the sheet-worker function's "trigger" is queued (or its result cached?) someplace on the back end, but because the user isn't directly interacting with the character sheet, it's not processed. Here's the original sheet-worker function again, including the diagnostic console.log: // COOLDOWN value is changed for ACTION on('change:repeating_actions:actionCooldown', function(eventInfo) { console.log('here!'); // THIS NEVER HAPPENS AFTER THE FIRST TIME THE API IS RUN UNLESS I CLICK INTO AND TAB OUT OF THE FIELD getAttrs(['repeating_actions_actionCooldown'], function(values) { const myCooldown = parseInt(values.repeating_actions_actionCooldown) || 0; const output = {}; output['repeating_actions_actionIsOnCooldown'] = (myCooldown &gt; 0 ? 1 : 0); setAttrs(output); }); }); And here's the getSectionIDs version that GiGs recommended: // COOLDOWN value is changed for ACTION on('change:repeating_actions:actionCooldown', function(eventInfo) { getSectionIDs('repeating_actions', function (ids) { const actionFieldnames = []; ids.forEach(id =&gt; { actionFieldnames.push(`repeating_actions_${id}_actionCooldown`); }); getAttrs([...actionFieldnames], function (values) { const output = {}; ids.forEach(id =&gt; { const myCooldown = parseInt(values[`repeating_actions_${id}_actionCooldown`]) || 0; output[`repeating_actions_${id}_actionIsOnCooldown`] = (myCooldown &gt; 0 ? 1 : 0); }); setAttrs(output); }); }); });
One more observation: The purpose of the API script is to be able to select a bunch of tokens, click a macro button, and have all the cooldown values in several sections (repeating_actions, repeating_conditions, repeating_talents) on several character sheets decrement by 1 at the same time (to a min value of 0). As long as any cooldown field's value is above 0, the flag is set to the change the field's CSS style so that it's easier to spot. I notice now that if I change the value in a single cooldown field to trigger the highlight, and then I call the API script to auto-decrement it back to a value of 0, the sheet-worker function works as expected . It only fails when more than one cooldown field has a value other than 0, and it fails if those values exist in the same section of the same sheet, different sections of the same sheet, or even on different sheets. It *does* seem like there's some sort of indexing/id issue happening somewhere that prevents the sheet-worker function from firing until a player interacts with the sheet.
1751994240

Edited 1751994313
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Can you post your current version of the API code? and a current version of one of the repeating sections that it should work with (HTML, CSS, and sheetworkers)? We've pretty much hit all the common issues, so it's likely either something weird, or something really tiny that's difficult to realize is a problem without looking at the whole code.
1752010066

Edited 1752010350
Thanks for taking a more detailed look. Here's the repeating section: &lt;section class='actions'&gt; &lt;fieldset class='repeating_actions'&gt; &lt;input type='hidden' value='0' name='attr_actionIsOnCooldown' class='actionisoncooldown' /&gt; &lt;input type='number' class='actioncooldown' value='0' min='0' name='attr_actionCooldown'/&gt; &lt;input type='text' class='bolded' name='attr_actionName' placeholder='Action Name...'/&gt; &lt;select class='widthshort' name='attr_actionType'&gt; &lt;option&gt;Type...&lt;/option&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- more options --&gt; &lt;/select&gt; &lt;input type='text' class='widthwide' name='attr_actionRequirements' placeholder='Requirements...'/&gt; &lt;input type='text' name='attr_actionDifficulty' class='widthshortest' placeholder='Difficulty...'/&gt; &lt;button type='action' class='btn' name='act_actionseed'&gt;Seed&lt;/button&gt; &lt;input type='checkbox' class='toggle-show'/&gt;&lt;br/&gt; &lt;div class='details'&gt; &lt;input type='number' value='0' min='0' name='attr_actionRecharge'/&gt; &lt;input type='text' name='attr_actionTraits' placeholder='Traits...'/&gt; &lt;input type='text' name='attr_actionCheck' placeholder='Skill Check...'/&gt; &lt;select class='widthshort' name='attr_actionSkill'&gt; &lt;option value=''&gt;Skill...&lt;/option&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- more options --&gt; &lt;/select&gt; &lt;select class='widthshort' name='attr_actionChar'&gt; &lt;option&gt;Characteristic...&lt;/option&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- more options --&gt; &lt;/select&gt; &lt;br/&gt; &lt;input type='text' name='attr_actionEffect' class='widthwidest' placeholder='Effect...'/&gt; &lt;input type='text' name='attr_actionSpecial' class='widthwidest' placeholder='Special...'/&gt; &lt;div class='actionresults'&gt; &lt;textarea name='attr_actionResultsConservative' placeholder='Conservative Results...' &gt;&lt;/textarea&gt; &lt;textarea name='attr_actionResultsReckless' placeholder='Reckless Results...' &gt;&lt;/textarea&gt; &lt;hr/&gt; &lt;/div&gt; &lt;/div&gt; &lt;/fieldset&gt; &lt;/section&gt; The highlight CSS: /* Highlight */ .ui-dialog .charsheet input.actioncooldown { background-color: palegreen; border-color: forestgreen; color: forestgreen; font-weight: bold; } /* Alerts */ .ui-dialog .charsheet input.actionisoncooldown[value='1'] ~ .actioncooldown { background-color: lemonchiffon; border-color: coral; color: coral; font-weight: bold; } The sheet-worker that responds to a change in the actionCooldown field: // COOLDOWN value is changed for ACTION on('change:repeating_actions:actionCooldown', function(eventInfo) { getAttrs(['repeating_actions_actionCooldown'], function(values) { const myCooldown = parseInt(values.repeating_actions_actionCooldown) || 0; const output = {}; output['repeating_actions_actionIsOnCooldown'] = (myCooldown &gt; 0 ? 1 : 0); setAttrs(output); }); }); And the API script, run from the sandbox using the '!Cooldown' chat command: // name: Cooldown.js // author: omonubi (<a href="mailto:omonubi@hotmail.com" rel="nofollow">omonubi@hotmail.com</a>) // game: WFRP 3rd edition // A Roll20 API script to reduce the cooldowns of all eligable repeating items by 1. on('chat:message', function(msg) { if(msg.type == 'api' &amp;&amp; msg.selected &amp;&amp; msg.content.indexOf('!Cooldown') == 0){ const selectedObjs = msg.selected; _.each(selectedObjs, function(obj) { if(obj._type == 'graphic'){ const token = getObj('graphic', obj._id); if(token.get('_subtype') == 'token' &amp;&amp; token.get('represents') != '') { const myCharacter = getObj('character', token.get('represents')); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const attrs = findObjs({ _type: 'attribute', _characterid: myCharacter.id }); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (const a of attrs) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (a.get('name').startsWith('repeating_actions_') &amp;&amp; a.get('name').endsWith('_actionCooldown')) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const myCooldown = a.get('current'); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a.set('current', Math.max(myCooldown - 1, 0)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} } }; }); }; });
1752010534

Edited 1752010582
Here, I've set four of the cooldown fields to different values (and the sheet-worker responds as expected): And here, I've called the API script the first time (and the sheet-worker responds as expected): And here, I've called the script three more times (and the sheet-worker did not respond): Click into one of those orange fields and then tab out and the sheet-worker then fires (but only for that field).
1752010685
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Oh, damn, can't believe I didn't notice this before. Sheetworker listeners need to be all lowercase. Intermittent failure of the listener is the problem that happens with a capitalized listener. Try lowercasing on('change:repeating_actions:actionCooldown' Note that this should be lowercased regardless of what the casing of the actual attribute in the html is. But this requirement is the reason why best practice for attribute naming is to use snake_case instead of camel case or PascalCase.
1752011859

Edited 1752011893
I changed both fields to snake case (action_cooldown and action_is_on_cooldown), in both the HTML/sheet-worker and the API script. The same behaviors persist.
1752014383
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Ok, there is something odd going on. It looks like the sheetworker/api interaction has been changed and I think that is what is causing the issue. Unfortunately, if this is what's causing the issue; it's a Roll20 problem, not something we can fix. That said, I do have a workaround that is actually more simple than what you were doing. You can get the behavior you want without a sheetworker using just html/css: HTML First we just make the hidden attribute a copy of the actual input. &lt;section class='actions'&gt; &lt;fieldset class='repeating_actions'&gt; &lt;input type='hidden' value='0' name='attr_actionCooldown' class='actionisoncooldown' /&gt; &lt;input type='number' class='actioncooldown' value='0' min='0' name='attr_actionCooldown'/&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- The rest of your section here as normal --&gt; &lt;/fieldset&gt; &lt;/section&gt; CSS Then in the css, we style based on whether the value of the hidden attribute is 0 (or empty) instead of whether it is 1 or not: /* Highlight */ .ui-dialog .charsheet .actioncooldown { background-color: lemonchiffon; border-color: coral; color: coral; font-weight: bold; } /* Alerts */ .ui-dialog .charsheet .actionisoncooldown:is([value='0'],[value=""],:not([value])) ~ .actioncooldown { background-color: palegreen; border-color: forestgreen; color: forestgreen; font-weight: bold; }
Hot patootie, bless my soul...that worked! Thank you!
1752032853
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Glad to hear! Sorry it was such a roller coaster of debugging.