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

Can I dynamically populate options from a fieldset?

March 25 (1 week ago)
Joel
Pro

I think normally I could do this with some java script or template engine, but roll20 seems to not work intuitively with fieldsets. I'm building a character sheet and in the skills area, the user can add specialty skills in a fieldset called repeating_guncombatspec. Within that fieldset they can name their skill in the <input> called attr_skillSpeciality-GunCombatspec and their total dice modifier gets calculated in attr_skilltotal-GunCombatspec. That all works fine.

In a different section of the character sheet, I want them to be able to choose from those skills in a dropdown <select> menu. So if they had entered three guns skills into the fieldset, named Pistols, Rifles, and Antique, for example, I would love for the dropdown to automatically populate with the options for Pistols, Rifles, and Antique. If they changed the skill name of Antique to Antiquated, the dropdown would automatically update the option to reflect the new value of attr_skillSpeciality-GunCombatspec. If they had only two skills, or if they added a fourth or fifth, the <select> menu would automatically change to reflect that, populating the options with all the skills they have entered into the fieldset.

Each value of the dropdown options would be equal to their total dice modifier in 
attr_skilltotal-GunCombatspec. For a single option, it might look something like this (I haven't proofed all this yet, I'm still brainstorming and working out my errors, so take it with a grain of salt):

<option value="@{repeating_guncombatspec_RowFoo_skilltotal-GunCombatspec}">@{repeating_guncombatspec_RowFoo_skillSpeciality-GunCombatspec}</option>
And then I would need a way to iterate over all rows in the repeating fieldset to build the list. So that's my concept. Early tests showed promise but no success. I'm certainly not versed enough in this stuff to do it without some guidance, or to find out sooner rather than later that it is literally impossible and I'll need to hard-code every skill and every <option>.

Thank you so much for your help, I deeply appreciate it.
March 25 (1 week ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

yes, you use the populateListOptions sheetworker. Note that it needs to be run on sheet: opened as well because the modification is not persistent.

March 25 (1 week ago)
Joel
Pro

Thank you, Scott. That is almost working, the thing I am struggling with is persistence. I thought by running it on sheet: opened that meant it would detect the most recent selection from the options and therefore that option would appear to already be selected when I re-opened the sheet. But currently when I re-open the sheet, the displayed <select> option is always just the first option in the list, not the one that was last selected. It persists as I flip through the tabs of the sheet, but not when I close and re-open the sheet. I'm trying a dozen or more ways to problem solve this, or store the selection in another variable to compare to when opened, nothing is working so far. Are you saying the selected option CANNOT be made to persist? The idea is that every time they open their sheet, the <select> would already be displaying the last skill they chose from that menu.

March 25 (1 week ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

populateListOptions is not a well designed function. After calling it, you need to use setAttrs to reset the attributes value to the value that was set for it.

March 25 (1 week ago)
Joel
Pro

Alright, I think I'm doing that, but who knows. I've tried a hundred iterations and I'm basically just throwing spaghetti at the wall at this point, but nothing is sticking. I started reading the entire sheet workers wiki to see if there is some other issue not mentioned in the populateListOptions. The only thing I have seen is that I could change all my variables to all lowercase and remove all dashes. I think I have to leave the underscores in the repeating section attributes, but I can start removing every other dash and capital letter. My concern is that the issue is something else, something so simple, and I'm going to waste a lot of time changing names when that is not the culprit. If anyone has time and is interested to look this over, this is the Frankenstein's monster I have ended up with. It was built up in pieces and probably has wrong or unnecessary bits at this point, but it does correctly populate the <select> options from the fieldset. I just can't seem to get it to register a change when I select one of the options, or for any change to persist once the sheet is closed.

<select class="sheet-input dynamicWeaponSkillMenu" name="attr_dynamicWeaponSkillMenu"></select>
<script type="text/worker">
  const updateDynamicWeaponSkillMenu = () => {
    // First, retrieve the current saved value of attr_dynamicWeaponSkillMenu.
    getAttrs(["attr_dynamicWeaponSkillMenu"], function(currentVals) {
      const currentSelectValue = String(currentVals.attr_dynamicWeaponSkillMenu || "");
      log("Current stored value: " + currentSelectValue);
      
      // Get all the row IDs from the repeating_guncombatspec section.
      getSectionIDs("repeating_guncombatspec", function(ids) {
        let attrNames = [];
        ids.forEach(id => {
          attrNames.push("repeating_guncombatspec_" + id + "_skillSpeciality-GunCombatspec");
        });
        
        // Retrieve the attribute values for each row.
        getAttrs(attrNames, function(values) {
          let optionsArray = [];
          ids.forEach(function(id) {
            let specialityAttr = "repeating_guncombatspec_" + id + "_skillSpeciality-GunCombatspec";
            let labelValue = values[specialityAttr] || "";
            
            let optionObj = {
              label: labelValue,
              value: labelValue  // using the name as both label and value
            };
            
            // If the stored value matches, mark this option as selected.
            if (labelValue === currentSelectValue && currentSelectValue !== "") {
              optionObj.selected = true;
            }
            optionsArray.push(optionObj);
          });
          
          log("Options Array: " + JSON.stringify(optionsArray));
          
          // Populate the select element with the dynamically built options.
          populateListOptions({
            elemSelector: '.dynamicWeaponSkillMenu',
            optionsArray: optionsArray,
            overwrite: true,
            callback: function() {
              log("Dynamic Weapon Skill Menu options updated. Current value: " + currentSelectValue);
              // Optionally, force persistence by resetting the attribute:
              if (currentSelectValue !== "") {
                setAttrs({ attr_dynamicWeaponSkillMenu: currentSelectValue }, function() {
                  log("attr_dynamicWeaponSkillMenu set to: " + currentSelectValue);
                });
              }
            }
          });
        });
      });
    });
  };

  // Run the update function when the sheet is opened and when the repeating section changes.
  on("sheet:opened", updateDynamicWeaponSkillMenu);
  on("change:repeating_guncombatspec", updateDynamicWeaponSkillMenu);
  // Try commenting out the change:attr_dynamicWeaponSkillMenu updater
  // on("change:attr_dynamicWeaponSkillMenu", updateDynamicWeaponSkillMenu);  
</script>
I wondered if I need, mandatory, to use a button to get the changes to stick, but my google-foo suggests not, unless it's a roll20-specific issue.
Again, I thank you (everyone) for your time and help and most importantly, patience. 
March 25 (1 week ago)
vÍnce
Pro
Sheet Author

Not sure if it's related to your issue, but do not use "attr_" in your getters and setters.  Just use the attribute name.

March 25 (1 week ago)
Joel
Pro


vÍnce said:

Not sure if it's related to your issue, but do not use "attr_" in your getters and setters.  Just use the attribute name.


ah man, I thought I tried that hours ago! LOL, I will re-check / try again. thank you very much.

March 26 (1 week ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

Heh, that'd do it. Ilyou can't set reset the value properly because the getAttrs doesn't actually get anything.

March 26 (6 days ago)
Joel
Pro
I have also realized a really stupid mistake that I made from the outset. The select element and its options are in their own field set of weapons. So each row of weapon will have its own individual menu populated from the skills. While the options within each row should all match each other, the selected option needs to persist independently, one for each row. Yet I failed to use row IDs when trying to update the selection. So that will be my big task today. Again thank you for your help.
March 26 (6 days ago)
Joel
Pro

I finally realized that that is one of my errors when I manually set the selection using the script. Then it would stick and be persistent after closing the sheet, but it applied that hardcoded selection to every weapon in the field set, not just one. Big oops.

March 26 (6 days ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

I've been digging into this as well since I need to use it for an upcoming project, and it looks like the ability to override the select display via resetting the attribute value is no longer working.

March 26 (6 days ago)
Joel
Pro

At least I feel a little better knowing that I am not alone in my struggle. At this point my brain is mush and I'm going to take a break. Here is the last snippet I have been working with, but ever since trying to target each weapon's <select> menu by its row ID, the whole thing has stopped working.

<fieldset class="repeating_guncombatspec">
...
<input type="text" class="sheet-input" name="attr_skillSpeciality-GunCombatspec" placeholder="Specialty Name"/>
...
</fieldset>
<fieldset class="repeating_weapons">
...
<select class="sheet-input dynamicWeaponSkillMenu" name="attr_dynamicWeaponSkillMenu"></select>
...
</fieldset>
<script type="text/worker">
  /**
   * updateWeaponSkillMenus
   * 
   * This function intends to gather all the gun combat skill names from the Skills section
   * and use those names to populate the options in the <select> menu of the Weapons section.
   * In this way, each weapon row will have a <select> menu that is only populated with gun skills
   * that the PC actually has, they can choose the appropriate skill from the list, and if that skill's
   * level is changed in the future, or they gain new gun skills, they only need update it on the Skills page
   * and the weapon rows will be automatically updated (rather than need to manually update in two places).
   *
   * The skill names are stored as repeating_guncombatspec_{rowID}_skillSpeciality-GunCombatspec
   * and the <select> element that we want to populate is repeating_weapons_{rowID}_dynamicWeaponSkillMenu.
   * Once a skill name has been selected for any weapon row, it should persist so it can later be used
   * to automatically calculate their skill bonus for that weapon.
   *
   * It assumes:
   *  - Each row in the Skills fieldset "repeating_guncombatspec" has an input with the name:
   *       repeating_guncombatspec_{rowID}_skillSpeciality-GunCombatspec
   *  - Each row in the Weapons fieldset "repeating_weapons" has a <select> element with class "dynamicWeaponSkillMenu"
   *    and name "attr_dynamicWeaponSkillMenu", and the row container’s id is of the form:
   *       repeating_weapons_{rowID}
   *
   * Note: In sheet worker calls, attribute names are referenced without the "attr_" prefix.
   * 
   * This approach had worked for populating a more global <select> menu, it correctly gathered the skill names
   * and populated all the weapon rows' <select> menu with those skill names. However, having realized that
   * this was creating one global <select> menu rather than one for each weapon row, I am now trying to populate
   * each <select> menu by using its repeating_weapons_{rowID} but that has failed. It no longer populates any 
   * <select> menu options at all.
   *
   * Note: I found a tip to remove all capital letters and dashes, however, as this had been working 
   * globally before I tried to target <select> row IDs, and all other references to row IDs have worked,
   * I'm not sure that is the issue. Something to keep in mind if all else fails, and for future naming conventions.
   */
   
  const updateWeaponSkillMenus = () => {
    // Step 1: Build a global options array from the gun combat skills.
    getSectionIDs("repeating_guncombatspec", function(skillIDs) {
      let skillAttrNames = [];
      skillIDs.forEach(function(id) {
        // Build full attribute name as it appears on your sheet.
        skillAttrNames.push("repeating_guncombatspec_" + id + "_skillSpeciality-GunCombatspec");
      });
      
      getAttrs(skillAttrNames, function(skillValues) {
        let globalOptions = [];
        skillIDs.forEach(function(id) {
          let attrName = "repeating_guncombatspec_" + id + "_skillSpeciality-GunCombatspec";
          let skillName = skillValues[attrName] || "";
          // Only add non-empty skill names.
          if (skillName) {
            globalOptions.push({
              label: skillName,
              value: skillName
            });
          }
        });
        log("Global Options Array: " + JSON.stringify(globalOptions));
        
        // Step 2: Update each row in the repeating_weapons section.
        getSectionIDs("repeating_weapons", function(weaponIDs) {
          weaponIDs.forEach(function(weaponID) {
            // Build the attribute name for this row's dynamic menu.
            const menuAttr = "repeating_weapons_" + weaponID + "_dynamicWeaponSkillMenu";
            // Retrieve the current stored value for this row.
            getAttrs([menuAttr], function(weaponVals) {
              const currentValue = String(weaponVals[menuAttr] || "");
              log("Row " + weaponID + " current stored value: " + currentValue);
              
              // Create a row-specific options array.
              // Mark an option as selected if it equals the current stored value.
              let rowOptions = globalOptions.map(function(opt) {
                return {
                  label: opt.label,
                  value: opt.value,
                  selected: (opt.value === currentValue && currentValue !== "")
                };
              });
              
              // Construct a selector for the <select> element in this row.
              // We assume the row container's id is "repeating_weapons_" + weaponID.
              const sel = "#repeating_weapons_" + weaponID + " .dynamicWeaponSkillMenu";
              log("Updating row " + weaponID + " using selector: " + sel);
              
              // Populate the select element using populateListOptions.
              populateListOptions({
                elemSelector: sel,
                optionsArray: rowOptions,
                overwrite: true,
                callback: function() {
                  log("Row " + weaponID + " dynamicWeaponSkillMenu updated. Current value: " + currentValue);
                }
              });
            });
          });
        });
      });
    });
  };

  // Run the update function when the sheet is opened and when the repeating_weapons section changes.
  on("sheet:opened", updateWeaponSkillMenus);
  on("change:repeating_weapons", updateWeaponSkillMenus);
</script>