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

Difficulty dynamically changing Element Style/Class in Repeating Section via Sheet Worker

May 20 (2 months ago)

Hi everyone,

I'm working on a custom character sheet and encountering a persistent issue with dynamically changing the style (specifically background color) of a div within a repeating section based on a dropdown selection in that same row.

Goal:
When a user selects an option from a dropdown (e.g., "Type A", "Type B") within a repeating row, I want the header div of that specific row to change its background color.

What I've Tried & Observed:

  1. Method 1: {{attribute}} in class string:

    • HTML: <div class="header-base {{attr_dynamic_class_name}}">...</div>

    • Sheet Worker: On dropdown change, I update attr_dynamic_class_name to something like "style-type-a".

    • Observation: Console logs (and getAttrs in callback) confirm attr_dynamic_class_name is correctly updated in Roll20's data model. However, the actual class attribute of the div in the live DOM often does not update after the initial render, or updates unreliably. The {{...}} interpolation doesn't seem to re-evaluate consistently on attribute change for the class string.

  2. Method 2: sheet-switch with multiple hidden divs:

    • HTML: A hidden attribute attr_style_trigger in the repeating section. Multiple pre-styled header divs, each shown/hidden by CSS targeting classes that Roll20 should add to the .repitem (e.g., .repitem.sheet-attr_style_trigger-type-a .header-for-type-a { display: block; }).

    • Sheet Worker: On dropdown change, I update attr_style_trigger to "type-a", "type-b", etc.

    • Observation: Console logs confirm attr_style_trigger is correctly updated in the data model. However, when inspecting the live DOM, the parent .repitem div does not consistently get the expected helper class (e.g., sheet-attr_style_trigger-type-a) added/updated by Roll20's sheet-switch mechanism. Thus, the CSS to show the correct styled div doesn't trigger.

Minimal Reproducible Example (Illustrating Method 2 attempt):

HTML:

      <fieldset class="repeating_myitems">
  <input type="hidden" name="attr_style_trigger" value="default"> <!-- Updated by worker -->

  <div class="item-content"> <!-- Wrapper for content within the repitem -->
    <!-- Variant A -->
    <div class="style-variant style-a-active"> <!-- class 'style-a-active' added by sheet-switch -->
      <div class="my-header style-a">Header A</div>
    </div>
    <!-- Variant B -->
    <div class="style-variant style-b-active"> <!-- class 'style-b-active' added by sheet-switch -->
      <div class="my-header style-b">Header B</div>
    </div>

    <select name="attr_type_selector">
      <option value="default">Default</option>
      <option value="type_a">Type A</option>
      <option value="type_b">Type B</option>
    </select>
  </div>
</fieldset>
<button type="action" name="act_add_item">Add Item</button>
    

CSS (Conceptual for Method 2):

      .item-content .style-variant { display: none; } /* Hide all variants by default */

/* Show based on class added to .repitem by sheet-switch */
.repitem.sheet-attr_style_trigger-type_a .style-a-active { display: block; }
.repitem.sheet-attr_style_trigger-type_b .style-b-active { display: block; }
/* ... and for default ... */
.repitem.sheet-attr_style_trigger-default .style-default-active { display: block; } /* Assuming a .style-default-active div */


.my-header.style-a { background-color: lightblue; }
.my-header.style-b { background-color: lightgreen; }
    

Sheet Worker (Conceptual for Method 2):

      on('change:repeating_myitems:type_selector', function(eventinfo) {
    const rowId = eventinfo.sourceAttribute.split('_')[2];
    const newType = eventinfo.newValue || "default";
    let attrs = {};
    attrs[`repeating_myitems_${rowId}_style_trigger`] = newType;
    setAttrs(attrs, {}, () => {
        console.log(`Set style_trigger to ${newType} for row ${rowId}`);
        // getAttrs confirms it's set in data model
    });
});

on('clicked:add_item', function() { /* standard add row logic */ });
    

My Core Question:

Given that the attributes are being updated correctly in the data model (confirmed by getAttrs in setAttrs callbacks and console logs), why might Roll20's DOM update mechanism (either {{...}} class interpolation or sheet-switch helper class generation on .repitem) be failing to reflect these changes visually in a repeating section?

  • Are there known caveats or best practices for forcing DOM re-evaluation of classes in repeating sections after a sheet worker update?

  • Is there a more robust pattern for achieving this kind of dynamic styling per row in a repeating section that I'm missing?

I've tried using !important in CSS, ensuring high specificity, and simplifying the structure as much as possible. The data changes, but the visual DOM representation of classes doesn't follow suit reliably for updates.

Any insights or alternative approaches would be greatly appreciated!

Thanks!

May 20 (2 months ago)

Edited May 20 (2 months ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

Neither of your two options are how this would work. The interpolation you mention for option 1 is only possible in roll templates, it doesn't do anything in the sheet itself. Option 2 is a little closer, but is missing some crucial bits.

Essentially, you want to use an html pattern like you have for option two, but just use the same attribute name for the hidden and select attributes. Then use attribute selectors to style items based on the value of the hidden attribute, I'd recommend using css variables to easily propagate changes:

HTML

<fieldset class="repeating_myitems">
  <input type="hidden" name="attr_type_selector" class="type-control" value="default"> <!-- Updated by worker -->
<div class="item-content"> <!-- Wrapper for content within the repitem --> <div class="my-header">Header A</div> <select name="attr_type_selector"> <option value="default">Default</option> <option value="type_a">Type A</option> <option value="type_b">Type B</option> </select> </div> </fieldset> <button type="action" name="act_add_item">Add Item</button>

CSS

.repcontainer[data-groupname="repeating_myitems"]{
  .repitem{
    --_headerBackColor: transparent; /* Set default settings */
    &:has(.type-control[value="type_a"]){
      --_headerBackColor: blue;
    }
    &:has(.type-control[value="type_b"]){
      --_headerBackColor: green;
    }
    .my-header{
      background-color: var(--_headerBackColor);
    }
  }
}

I'm using the new nested CSS syntax here. It can be used as is and doesn't require a preprocessor. Note that the leading underscore in the css variable names is just a naming convention I follow for css variables that are defined in a given component and only used within that component.