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

Repeating Sections

March 05 (4 years ago)

Ok, I'm stumped.  Probably because I haven't coded in 10 years and I don't get all the javascript shorthand like arrow notation :-P

I have a repeating section of weapons.  I want to spin through the list and see if the weapon uses a certain skill.  If so, then I'm going to change a different value on that weapon.  Here's what I'm doing:

        getSectionIDs("weapons", function (idarray) {
            for (var i=0; i<idarray.length; i++) {
                getAttrs(["repeating_weapons_" + idarray[i] + "_weapon_melee_skill"], function(repeatingvalues) {
                    console.log(repeatingvalues);
                    console.log(repeatingvalues("repeating_weapons_" + idarray[i] + "_weapon_melee_skill"));
                });
            }
        });
I get that getAttrs is async and i is out of range before the callback happens so the second console.log is undefined.  repeatingvalues has what I need.  I just have no idea how to reference it inside the getAttrs callback.  
Here is the result of the first console.log:
{repeating_weapons_-mubbnijfifbgj-fiqii_weapon_melee_skill: "1H Edged"}

How in the world do I get that -mubbnijfifbgj-fiqii so i can reference the field I want to update?

Many thanks in advanced.

March 05 (4 years ago)

Edited March 05 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

There's not enough information in your post to give you the code you need. But in principle, you dont want to have getAttrs (or setAttrs) inside a loop. The way to do this is (pseudo code):

getSectionIDs
    have a loop to create an array of the attributes 
    getAttrs([the array you just created], vales => {
        create a variable to hold the stats you want to update
        loop to go through idarray again
            all your work is done inside this loop, saving any changed attributes and values to the variable just created
        loop ends
        setAttrs(save the variable you created above)

So yes, you essentially do a loop through idarray twice: once before getAttrs, to build the names of all the attributes in the repeating section, so getAttrs can retrieve them, then loop again inside getAttrs to do the work you need, so the context isnt lost.

Here's partial code for what that would look like:

getSectionIDs('repeating_weapons'idarray => {
    const fieldnames = [];
    idarray.forEach(id => {
        fieldnames.push('repeating_weapons_' + id + '_weapon_melee_skill');
    });
    // at this point fieldnames includes the full name of weapon_melee_skill for all rows of the repeating section
    getAttrs(fieldnamesvalues => {
        //create an empty object variable into which you'll save any changed values
        const output = {};
        // the idarray array still exists, so you can use it to loop through the repeating section rows again
        idarray.forEach(id => {
            // as you loop through through the rows, get the value of the weapon_skill
            const skill = values['repeating_weapons_' + id + '_weapon_melee_skill'];
            // do whatever you need here.
        });

        // once you have done all the work you need to do, save any changed attributes to the sheet.
        setAttrs(output);
    });
});


March 05 (4 years ago)

Makes sense.  Will give it a shot.  

March 05 (4 years ago)
timmaugh
Pro
API Scripter

Hey, Cary... the quick/dirty answer is you apply a regex like this:

let fullrx = /^repeating_([^_]*?)_([^_]*?)_(.+)$/;
let parts = fullrx.exec(repeatingvalues);
log(parts[1]);    // outputs weapons
log(parts[2]);    // outputs sectionid
log(parts[3]);    // outputs suffix

The longer answer is... I'm not sure why you're going that far to get the info you want...

This will get you the list of repeating element ids for a given section on a given character:

let [section, character_id] = ['weapons', '-M2nd0vns0zf-sa-1']; // could be passed into a function or retrieved dynamically
let sectionrx = new RegExp(`repeating_${section}_([^_]+)_.*$`);
let elemIDs = [...new Set(findObjs({ type: 'attribute', characterid: character_id })
    .filter(a => sectionrx.test(a.get('name')))
    .map(a => sectionrx.exec(a.get('name'))[1]))];

Then you can keep connecting array functions to the end. The next one would be a map, to reconstruct the full name using the suffix  you want and return an object bearing that name... or to return both things you're looking for (then we'll filter):

elemIDs.map(e => {
    return {
        melee: findObjs({ type: 'attribute', characterid: character_id, name: `repeating_weapons_${e}_weapon_melee_skill`})[0],
        secondary: findObjs({ type: 'attribute', characterid: character_id, name: `repeating_weapons_${e}_weapon_secondary_attr`})[0]
    };)
    .filter(a => a.melee && a.secondary && a.melee.get('current') === 'skill you are looking for')
    .forEach(a => {
        // set the values you need to set on a.secondary
    });

You don't have to go asynchronous to get what you're looking for.

March 05 (4 years ago)

Edited March 05 (4 years ago)

Ok, I have everything working.  console log shows everything good.  I have this code at the bottom

        var attrs = {};
        attrs["repeating_weapons_" + id + "_weapon_melee_bonus"] = mbonus;
        attrs["repeating_weapons_" + id + "_weapon_ranged_bonus"] = rbonus;
        console.log(attrs);
        setAttrs({
            attrs
        });
When I show attrs, it looks like I want:

{repeating_weapons_-mubbnijfifbgj-fiqii_weapon_melee_bonus: 72, repeating_weapons_-mubbnijfifbgj-fiqii_weapon_ranged_bonus: 11}

But when I call setAttrs, it doesn't update my repeating section.  I've double checked all my variable names.  What am I missing?  Is my attrs variable formatted correctly for setAttrs?
March 05 (4 years ago)

Edited March 05 (4 years ago)

Grr... brackets..  Sometimes just typing the question helps you see it.  :-)

For those that might look this up in the future.

setAttrs({
            attrs
        });

Should have been:

setAttrs(attrs);

Since attrs was already an object.  

March 05 (4 years ago)

Edited March 05 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter


timmaugh said:

Hey, Cary... the quick/dirty answer is you apply a regex like this:

let fullrx = /^repeating_([^_]*?)_([^_]*?)_(.+)$/;
let parts = fullrx.exec(repeatingvalues);
log(parts[1]);    // outputs weapons
log(parts[2]);    // outputs sectionid
log(parts[3]);    // outputs suffix

The longer answer is... I'm not sure why you're going that far to get the info you want...

This will get you the list of repeating element ids for a given section on a given character:

let [section, character_id] = ['weapons', '-M2nd0vns0zf-sa-1']; // could be passed into a function or retrieved dynamically
let sectionrx = new RegExp(`repeating_${section}_([^_]+)_.*$`);
let elemIDs = [...new Set(findObjs({ type: 'attribute', characterid: character_id })
    .filter(a => sectionrx.test(a.get('name')))
    .map(a => sectionrx.exec(a.get('name'))[1]))];

Then you can keep connecting array functions to the end. The next one would be a map, to reconstruct the full name using the suffix  you want and return an object bearing that name... or to return both things you're looking for (then we'll filter):

elemIDs.map(e => {
    return {
        melee: findObjs({ type: 'attribute', characterid: character_id, name: `repeating_weapons_${e}_weapon_melee_skill`})[0],
        secondary: findObjs({ type: 'attribute', characterid: character_id, name: `repeating_weapons_${e}_weapon_secondary_attr`})[0]
    };)
    .filter(a => a.melee && a.secondary && a.melee.get('current') === 'skill you are looking for')
    .forEach(a => {
        // set the values you need to set on a.secondary
    });

You don't have to go asynchronous to get what you're looking for.


I don't believe the code you're suggesting above will work in a sheet worker. The API and sheet workers are different environments. for example, findObjs is an API function - you cant use it in a sheet worker.

If you're getting a value from the character sheet in a sheet worker, you have to use getAttrs, and so you always have to go asynchronous.
That said, this question is in the API forum, so if Cary is actually using the API your answer is a much better one than mine. It's just the first post started with getSectionIDs, and referred to getAttrs, so I assumed it was a sheet worker question.


March 05 (4 years ago)

No APIs.  I'm working on a community sheet.

March 05 (4 years ago)

Edited March 05 (4 years ago)

Speaking of...  Can you send something to the chat besides a roll from a sheetworker?  I've got a worker to generate stats randomly (my randomInteger post in another thread).  Instead of randomly populating the character sheet, I was just going to output them to the chat window.  

I know I could just show them in a field on the sheet, but I wanted to broadcast it so everybody would see what they rolled.  I made a rolltemplate but can't figure out how to call it from the sheet.  Can you combine a sheetworker and a rollbutton?  Just thinking out loud.

And the reason I want to do it from a sheetworker is because of some of the formulas that can be applied to the rolls (e.g. roll 9, keep 6).  I have a roll button now that kind of works, but it only shows the result of the /r 9d100k6.  I want to show the individual rolls that are kept.

March 05 (4 years ago)
timmaugh
Pro
API Scripter


GiGs said:

I don't believe the code you're suggesting above will work in a sheet worker. The API and sheet workers are different environments. for example, findObjs is an API function - you cant use it in a sheet worker.

If you're getting a value from the character sheet in a sheet worker, you have to use getAttrs, and so you always have to go asynchronous.
That said, this question is in the API forum, so if Cary is actually using the API your answer is a much better one than mine. It's just the first post started with getSectionIDs, and referred to getAttrs, so I assumed it was a sheet worker question.


A ha. Yes, I missed that. Thanks.

March 05 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter


Cary said:

Speaking of...  Can you send something to the chat besides a roll from a sheetworker?  I've got a worker to generate stats randomly (my randomInteger post in another thread).  Instead of randomly populating the character sheet, I was just going to output them to the chat window.  

I know I could just show them in a field on the sheet, but I wanted to broadcast it so everybody would see what they rolled.  I made a rolltemplate but can't figure out how to call it from the sheet.  Can you combine a sheetworker and a rollbutton?  Just thinking out loud.

And the reason I want to do it from a sheetworker is because of some of the formulas that can be applied to the rolls (e.g. roll 9, keep 6).  I have a roll button now that kind of works, but it only shows the result of the /r 9d100k6.  I want to show the individual rolls that are kept.

Unfortunately you cant combine sheet workers and printing to chat directly.

You can have an action button that triggers a sheet worker, and updates in attribute. And then a roll button can print that contents of that attribute to chat. So this is a two step process. There's no way to combine them as one action without using an API script.