The One Line Repeating Section

In the last post we built a fairly complex repeating section to total an encumbrance section, and showcased some different things you can do with individual items. But sometimes you dont want to address an entire section. You might be operating entirely within a row.

Imagine you have a section for tracking attacks. You pick a weapon type, and it automatically fills in several values like damage roll and range. When you pick the weapon type, it only changes values for that weapon, and doesn’t affect any other rows of the section.

Roll20 has a syntax for this kind of thing. When you make a change within a row roll20 can supply the row id automatically. Since the row must be the same, your syntax can ignore it. Here’s how it’s done.

Attack HTML and CSS

Here we have the HTML. A fairly basic layout, with entries for weapon name (for people who like to name their weapons), type (the actual weapon), damage, range, and number hands.

The block ends with a button to attack – don’t look too closely at that for now. We’ll examine it later in the post, and in a later post see how to improve it.

<div class="attacks">
<fieldset class="repeating_attacks">
   <div class="attacks">
      <input type="text" name="attr_name" value="">
      <select name="type">
         <option selected>Unarmed</option>
      <input type="hidden" name="attr_stat" value="0">
      <input type="number" name="attr_bonus" value="0">
      <input type="text" name="attr_damage" value="">
      <input type="text" name="attr_range" value="">
      <input type="number" name="attr_hands" value="1">
      <button type="roll" name="roll_attack" value="&{template:default} {{name=@{name} Attack"}} {{attack=[[1d20+@{stat}+@{bonus}]] }} {{Damage=[[@{damage}+@{str_mod}]]}}"></button>
</fieldset>Code language: HTML, XML (xml)
.ui-dialog .charsheet input,
.ui-dialog .charsheet select {
    all: initial;
div.attacks {
   display: grid;
   grid-template-columns: repeat(2, 100px) repeat(4, 50px) 30px;
   column-gap: 5px;
}Code language: CSS (css)

Note: the start of the CSS block is to account for the fact that Roll20 adds a lot of styling to HTML elements. That declaration block on input and select resets everything to default, so the elements dont have fix width and height.

The div.attacks block is the important part – using CSS Grid to sort the HTML elements into nice columns.

The Sheet Worker

The sheet worker monitors the type attribute and fills other attributes whenever it changes. We’ll break down what the worker does below.

on('change:repeating_attacks:weapon', () => {
        getAttrs(['repeating_attacks_weapon'], values => {
           const weapons = {
              Unarmed: {stat: '@{str_mod}', damage: "d4", range: "Close", hands: 1},
              Dagger: {stat: '@{str_mod}', damage: "d6", range: "Close", hands: 1},
              Broadsword: {stat: '@{str_mod}', damage: "d8", range: "Medium", hands: 1},
              Greatsword: {stat: '@{str_mod}', damage: "2d6", range: "Long", hands: 2},
              Longbow: {stat: '@{dex_mod}', damage: "d8", range: "Ranged", hands: 2}
           const weapon = values.repeating_attacks_weapon;
           if(weapons.hasOwnProperty(weapon)) {
              const output = {};
              output.repeating_attacks_stat = weapons[weapon].stat;
              output.repeating_attacks_damage = weapons[weapon].damage;
              output.repeating_attacks_range = weapons[weapon].range;
              output.repeating_attacks_hands = weapons[weapon].hands;
     });Code language: JavaScript (javascript)

No getSectionIDs

Notice this worker doesn’t use getSectionIds. That means we are working with a single row only. This adds a gotcha that is often overlooked.

You cant add sheet:opened to the event line. Roll20 always needs to know which row or rows to operate on, and when the sheet is first opened, no row is selected, so no id can be supplied.

Event Line

on('change:repeating_attacks:weapon', () => {Code language: JavaScript (javascript)

Notice we once again use a second colon: that is needed when you want to monitor specific attributes in a repeating section. It will trigger when that attribute on any row of the section changes.

Collecting Attributes

   getAttrs(['repeating_attacks_weapon'], values => {Code language: JavaScript (javascript)

Here we skip the use of getSectonIDs by taking advantage of Roll20’s ability to insert a row id automatically. When an attribute changes, Roll20 knows which row it was on and so automatically supplies it here.

When you use this syntax, a row id is supplied, but it happens behind the scenes and you don’t have to think about it. But it mens you can only change one row at a time.

Weapons Data Table

      const weapons = {
         Unarmed: {stat: '@{str_mod}', damage: "d4", range: "Close", hands: 1},
         Dagger: {stat: '@{str_mod}', damage: "d6", range: "Close", hands: 1},
   <strong>      </strong>Broadsword: {stat: '@{str_mod}', damage: "d8", range: "Medium", hands: 1},
         Greatsword: {stat: '@{str_mod}', damage: "2d6", range: "Long", hands: 2},
         Longbow: {stat: '@{dex_mod}', damage: "d8", range: "Ranged", hands: 2}
      };Code language: JavaScript (javascript)

Here is a great example of a data table. When you have set of stats for something, and they don’t change, you can encoude them in an object variable. The keys, the indiex, are the first entries before that first colon. Then you put the data inside another object (the {} brackets), and can then extract them as below.

Getting The Weapon Type

      const weapon = values.repeating_weapons_weapon;
      if(weapons.hasOwnProperty(weapon)) {Code language: JavaScript (javascript)

When using a data table like this, you need to grab the specific values. Here we get the weapon (the type of the weapon), and then check if that item exists in the table (hasOwnProperty).

It almost always shoud, but this handles if the player enters a nll value (no item selected, for example).

Getting The Values

         const output = {};
         output.repeating_attacks_stat = weapons[type].stat;
         output.repeating_attacks_damage = weapons[type].damage;
         output.repeating_attacks_range = weapons[type].range;
         output.repeating_attacks_hands = weapons[type].hands;Code language: JavaScript (javascript)

Here we grab the times one by one. There are other ways to get the values.

If we had started the data table entries which matched the attribute names, like so:

         Unarmed: {repeating_attacks_stat: '@{str_mod}', repeating_attacks_damage: "d4", repeating_attacks_range: "Close", hands: 1},Code language: JavaScript (javascript)

We could simply have done this:

      const weapon = values.repeating_weapons_type;
      if(weapons.hasOwnProperty(weapon)) {
          output = weapon;
      }Code language: JavaScript (javascript)

This is because setAttrs accepts an object natively. Here the attributes and values will be in the correct format.

Or we could iterate through the found object and change the names to add repeating_weapons_ like so:

      const weapon = values.repeating_weapons_weapon;
      if(weapons.hasOwnProperty(weapon)) {
         Object.keys(weapon).forEach(name => {
            output[`repeating_attacks_${name}`] = weapon[name];
      }Code language: JavaScript (javascript)

In this version we start with an object that looks like

{stat: ‘@{str_mod}’, damage: “d4”, range: “Close”, hands: 1}

and transform it to

{repeating_attacks_stat: ‘@{str_mod}’, repeating_attacks_damage: “d4”, repeating_attacks_range: “Close”, hands: 1}

If we have a lot of entries, that is better than manually setting each one.

Saving To The Sheet

});Code language: JavaScript (javascript)

As always, when finishing the sheet worker, we need to make sure all our brackets are closed properly. Proper indenting makes this a snap.

Examining The Roll Button (A Gotcha)

The roll button in the HTML does a few things that should probably be explained.

      <button type="roll" name="roll_attack" value="&{template:default} {{name=@{name} Attack"}} {{attack=[[1d20+@{stat}+@{bonus}]] }} {{Damage=[[@{damage}+@{str_mod}]]}}"></button>Code language: HTML, XML (xml)

One feature has is that Roll20 will check if a used name matches anything else in the repeating section row, and if it does, will automatically add the repeating_NAME and the row id parts.

See the @{stat}, @{bonus} and @{damage} attributes – they are in the repeating section, so get used from there. @{str_mod} is not, so it treated as a global stat – the rest of the sheet is checked to see if the attribute exists there.

You have t be careful to make sure you don’t reuse namesin the section that exist elswhere. For example, if you have a @{damage} attribute set globally, that will never be used. Since there is a stat named damage inside the repeating section, roll20 will always use that first.

This is a gotcha that can sometimes catch you out.

Global Attributes

Another gotcha. Let’s say your worker depended on a global attribute, so you have something like this:

on('change:repeating_attacks:weapon change:damage', () => {Code language: JavaScript (javascript)

If you have an exteranl attribute, and it changes, you want every row in the section to be updated. But when you do this, you need to know the row id – because you need to change each row, not just a single row. This worker will return an error, because it doesn’t use getSectionIDs to grab and use the row ids. When using this syntax:

'repeating_attacks_weapon'Code language: HTML, XML (xml)

you are relying on Roll20 to suopply the roll id, and it can only do that when you are modfying just a single row.

Separating Damage Rolls

The worker as fine as it is, but it makes the attack roll and damage roll at the same time. It would be handy to be able to supply a button to roll damage, so the player can press it only when the attack hits. We’ll see how to that in the upcoming post about Action Buttons in Repeating Sections.

Using The Full Section

The sheet worker above changes one entry’s row at a time. We could use getSectionIDs to affect the whole section – so when one type attribute changes, all the rows are recalulated. There are situations where it is beneficial to do it that way. See the last post for who to do that.


We have seen how to monitor and respond to a change in a single row. Now we’ll look at sheet workers that combine global attributes and repeating sections, and then multiple sheet workers at a time.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.