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

Getting and changing repeating attributes in a fieldset

December 07 (4 years ago)
Lova
Pro

Is this something that can be done? I've been racking my brain trying to figure this out...

When someone changes an attribute in a row I'd like to get and automatically set other attributes in that row... But...

How do I do that? How does that work when an attribute hasn't been set yet?

December 07 (4 years ago)
Oosh
Sheet Author
API Scripter

Is this from a sheet authoring perspective, or an API script perspective?

A sheetworker can respond to a change in a repeating section, and make changes in the row that triggers the event. This requires writing the actual character sheet.

An API script can get repeating rows, mess about with them, then set them again - but it won't respond to a sheet change. It needs a chat or canvas event to trigger it.

Can you go into some more detail about what you'd like to achieve?

December 07 (4 years ago)
Lova
Pro

Sheet or API, either! I honestly didn't think this kind of thing would be possible in the sheet... The sheet is one I've modded myself so I have all the code for it and could change it if that's a possability.

Basically, I want two dropdown menus, and based on what you pick text appears/changes next to them in that same row.

Semi-related, could I also calculate a total number from all the rows in the fieldset?

December 07 (4 years ago)
Lova
Pro

I got it! Thanks a lot for the help! I found it on the wiki at last!

For anyone else who searches for how to do it in the sheet here it is

December 07 (4 years ago)

Edited December 07 (4 years ago)
timmaugh
Forum Champion
API Scripter

Hi Lova... I think your two goals are best carried out in different domains. Having dropdowns on the sheet that modify another field in the same repeating element... that is something best handled with sheetworkers.

Getting the total of all rows (or a subset) is possible from either the sheet side or the API side... you just have to go through a few steps. Let's say you have a repeating section for "Weapon Skills". You probably know, from building your sheet, that such a section would have a name like:

repeating_weaponskills

Let's imagine every "entry" in that list would have several elements under it:

weapon_name
material_bonus
encumbrance_mod
quality_bonus offhand_penalty
description

Those are each individual attributes with their own unique ID. And even though we reference them by how they relate to the element that links them, to the API they are stored in a flat set. It isn't like we identify an item in the repeating list, then drill down off of that to a tree of properties with dot notation. Instead, we get them by the way they include an ID for the item set in their name:

repeating_section_repID_suffix    
    --for instance...
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_weapon_name

All of the attributes connected to the single item in the repeating list will have the same form (including the same repID), up to the suffix:

Attribute Name                                                 |    Current    
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_weapon_name        |    Wet Carp
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_material_bonus     |    0
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_encumbrance_mod    |    -1
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_quality_bonus     |    1
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_offhand_penalty    |    -1
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_description        |    Blessed by Ba'ag the Lesser, discovered at the end of the last age                                                                     Orange. Smelly. Approximately 5lbs. Speaks occasionally.

That means to total what we want to total, we have to be able to 1) identify the item from the list, 2) extract the repID from the name, 3) get all of the element attributes associated with the item, and 4) total the appropriate element attributes (based on the suffix(es) supplied).

Regex

The following regex represents the breakdown of each of those components:

let fullrx = /^repeating_([^_]*?)_([^_]*?)_(.+)$/;
// FROM   : repeating_section_repID_suffix // group 1: section
// group 2: repID
// group 3: suffix
If you have any given portion, you can substitute it into the regex as necessary. Here is a regex where you know the section (in a variable called "section"):
let sectionrx = new RegExp(`^repeating_${section}_([^_]*?)_(.+)$`, 'g');
// FROM : repeating_section_repID_suffix // group 1: repID
// group 2: suffix

And another where you know the suffix of the element that is naming the item in the list (in a variable called "sfxn"):

let itemrx = new RegExp(`^repeating_${section}_([^_]*?)_${sfxn}$`, 'g');
// FROM : repeating_section_repID_suffix // group 1: repID

Identification and Extraction of repID

Use one of those regex statements to isolate the repeating element and extract the ID. Here, I pretend I have the section and the naming suffix, so I'll use the itemrx (above), and I'll filter where it equals "Wet Carp"... (I also have the character in an object named "chr"):
let attr = findObjs({ type: 'attribute', characterid: chr.id })
    .filter(a => itemrx.test(a.get('name')))[0];
if (!attr) // ... error handling goes here
let repID = itemrx.exec(attr.get('name'))[1];

Get Element Attributes and Total Where Appropriate

Now that you have the repID, you can return all of the attributes for this character from that list where they match the repID (in other words, all of the element attributes for this item in the repeating list).

Obviously you don't want to total the name or description fields, only the fields that have values appropriate to the number you are trying to represent. For instance, a weapon might have a "walking around" modifier of the total of the material mod, the encumbrance penalty, and the quality bonus. If a character happened to be trying to wield the weapon in her off-hand, then you would want to also include the off-hand penalty.

If you put the suffixes of the attributes you want to include (to arrive at the "walking around" mod) in an array, you can use the array to drive a test and sum as appropriate.

let sumsfxs = ['material_bonus', 'encumbrance_mod', 'quality_bonus'];    // array of suffixes to total
let elemrx = new RegExp(`^repeating_${s}_${repID}_.*`);                  // regex using the repID
let modtotal = findObjs({ type: 'attribute', character: chr.id })        // get all attributes for this character
    .filter(a => elemrx.test(a.get('name')))                             // filter for only those in this repeating item
    .reduce((m,a) => {                                                   
        return sumsfxs.some(b => a.get('name').includes(b)) ? m + Number(a.get('current')) : m;
    },0);

The reduce() function, above, builds a sum for those attributes who have a name that contains an element from the array of naming suffixes you supplied (sumsfxs). If the name passes, we include it in the sum.

December 07 (4 years ago)
Lova
Pro

Thank you so much, timmaugh. That helped so much. I'm relatively new to this so I'm kind of tearing my hair out trying to figure all this out...

I'm getting the sheet worker to do about half of what I want it to do. I just don't know how to make a dynamic text area where I can update the content...

December 07 (4 years ago)
Lova
Pro

I GOT IT. I DID IT. I'm so stupidly proud of myself... This is classic programming... Hours of frustration followed by seconds of absolute bliss

December 08 (4 years ago)

Edited December 08 (4 years ago)
Oosh
Sheet Author
API Scripter

Nice one!


Also....

timmaugh said:

All of the attributes connected to the single item in the repeating list will have the same form (including the same repID), up to the suffix:

Attribute Name                                                 |    Current    
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_weapon_name        |    Wet Carp
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_material_bonus     |    0
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_encumbrance_mod    |    -1
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_quality_bonus     |    1
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_offhand_penalty    |    -1
repeating_weaponskills_-M3n0XLcCSqTxaf4Ldq3_description        |    Blessed by Ba'ag the Lesser, discovered at the end of the last age                                                                     Orange. Smelly. Approximately 5lbs. Speaks occasionally.

Obligatory fish slapping dance gif:


December 08 (4 years ago)
timmaugh
Forum Champion
API Scripter

HaHA!

December 08 (4 years ago)
Lova
Pro

Hi, guys! It's me again...

I've encountered a strange problem... It's probably due to my code but it makes no sense to me.

So everything works great unless you want to delete the LAST row in the repeated section. Every other row can be deleted just fine, no problem. But if you try to delete the last row it just deletes all the saved attributes for that row and then adds it back again instantly, empty.

Does anyone have any idea why the last row behaves differently like this? (And by last I mean the one at the bottom. How many rows are above seems irrelevant to this behaviour)

December 09 (4 years ago)
Oosh
Sheet Author
API Scripter

Is this using the sheetworker function, removeRepeatingRow(rowId) ? Or are you using the API to do this?

December 09 (4 years ago)
Lova
Pro

I'm doing this through a sheetworker, I'm not using removeRepeatingRow. I'm only using the on("change:repeating_section", function() {

I would understand this if it happened EVERY time I tried to delete a row... Like... That counts as a change that runs the function, and somewhere in my code, something cancels the row being deleted... But why does it happen ONLY for the bottom row???

December 09 (4 years ago)
Lova
Pro

By the way, when I say deleting a row I mean manually on the sheet. Like, clicking "modify" and then "delete" on a row

December 09 (4 years ago)
Lova
Pro

Okay, I've tested some more... I've realised I only have problems with deleting the last row (using "modify") if I've done changes and my code has run. If I close down the sheet and open it back up again I don't have any problems deleting the last row. So it's definitely my code. Why? No clue...

December 09 (4 years ago)
Lova
Pro

Welp, I don't know exactly what was wrong or why it only affected the last row. But I fixed it by breaking up the code into something more specific. Instead of using ("change:repeating_section", function() { I am now using several ("change:repeating_section:attribute", function() { and that seems to have solved the issue!

December 09 (4 years ago)
Oosh
Sheet Author
API Scripter
Excellent! By the way, there is an on('remove:repeating_section') event if you specifically need something to trigger on deleting a row, just on the odd chance you weren't aware of it. Might not be relevant to what you're trying to do.
December 09 (4 years ago)
Lova
Pro

It's not relevant to what I'm doing, no, but it's good that you mentioned it! Someone else may google their way over to this thread and find it very useful :)