Radio Buttons in Repeating Sections

Itr’s a very common desire to have a radio button spread across the rows of a repeatiing section. For example, you want to know which of several weapons is selected, or which armour is worn.

This is easy outside of a repeating section. Just create a bunch of radio inputs, with each value representing the item active.

<input type="radio" name="attr_weapon_selected" value="0">
<input type="radio" name="attr_weapon_selected" value="1">
<input type="radio" name="attr_weapon_selected" value="2">
<input type="radio" name="attr_weapon_selected" value="3">Code language: JavaScript (javascript)

Click one button, and it is selected and all the others become unselected. Simple.

Unfortunately, you can’t do this in a repeating section. Notice how the name of all the radio buttons is identical? They must be or the radio button doesn’t work.

But in a repeating section, each field has a different name. Remember how repeating attributes are named. They rae made up of three parts:

  1. The repeating section name (“repeating_weapons”)
  2. A special id for the row (“-vgs3563c9”)
  3. The actual name you created (“weapon_selected”)

This created a complx name that looks like repeating_weapons_-vgs3563c9_weapon_selected. There is no way for ids on different rows to have identical names, becayse of this process.

But worry not – there is a way to simulate this a radio button.

How To Fake a Radio Button

The basic idea is simple. Instead of a radio button in each row of the repeating section, add a checkbox. The tricky part is making sure that when you click a checkbox, that one becomes checked and all the others become unchecked. For this, we need to use eventInfo, to detect which button was clicked.

We start by creating the HTML. Thoe code below shows the weapon’s name, and an checkbox inside a label. This means that if you click anywhere in the label, that checkbox gets clicked.

<fieldset class="repeating_weapons">
    <span>Weapon Name:</span>
    <input type="text" name="attr_weapon" value="" placeholder="name of weapon">
    <label><span>Ready?</span>
        <input type="checkbox" name="attr_selected" value="1">
    </label>
</fieldset>Code language: HTML, XML (xml)

Now we need to write the sheet worker to make sure that the chosen weapon is selected, and only that weapon is selected.

A naive approach would look like this:

on('change:repeating_weapons:selected', () => {
   setAttrs({
      repeating_weapons_selected: 1
   });
});Code language: JavaScript (javascript)

This takes advantage of the Roll20 sheet worker ability to know what the current sheet worker row is. Click a row, and it will be checked.

But this does nothing – clicking a box already checks it, and checkboxes that are already checked remain checkeed. You need a way to check this one, and uncheck the others.

First, we try this:

    on('change:repeating_weapons:selected', () => {
        getSectionIDs('repeating_weapons', ids => {
            const output = {};
            ids.forEach(id => {
                output[section_name('weapons', id, 'selected')] = 0;
            });
            setAttrs(output);
        });
    });Code language: JavaScript (javascript)

This does one thing – it loops through the checkboxes and unchecks them all. But we need to detect the currently clicked one, and make sure it is checked. eventInfo gives us information about the item that just changed.

    on('change:repeating_weapons:selected', (eventInfo) => {
        getSectionIDs('repeating_weapons', ids => {
            const output = {};
            ids.forEach(id => {
                output[section_name('weapons', id, 'selected')] = 0;
            });
            output[eventInfo.triggerName] = 1;
            setAttrs(output);
        });
    });Code language: JavaScript (javascript)

Here we use eventInfo to get the triggerName. That contains the full name of the checked item.

But there is still a problem. Notice the change item? it is monitoring the selected attribute for changes, but our setAttrs changes the selected item. So this creates an infinite loop: each time we change a value, it triggers a change, causing the workwer to run again. We need to stop that.

    on('change:repeating_weapons:selected', (eventInfo) => {
        if(eventInfo.sourceType === 'sheetworker') return;
        getSectionIDs('repeating_weapons', ids => {
            const output = {};
            ids.forEach(id => {
                output[section_name('weapons', id, 'selected')] = 0;
            });
            output[eventInfo.triggerName] = 1;
            setAttrs(output);
        });
    });Code language: JavaScript (javascript)

eventInfo.sourceType can have two values = ‘player’ or ‘sheetworker’. By causing teh sheet worker to stop whenever triggered by a sheet worker, we make sure this worker runs only once, on that initial player click.

There is still one problem. When you click a checkbox to select a weapon, it gets checked properly. But when click to unselect a weapon, so that no weapons are selected, it rechecks it. This because of this line:

output[eventInfo.triggerName] = 1;

The worker correctly updates the row clicked, but always sets it to 1, checking it. Luckily eventinfor contains newvalue, which is the value you are trying to set it to.

    on('change:repeating_weapons:selected', (eventInfo) => {
        if(eventInfo.sourceType === 'sheetworker') return;
        getSectionIDs('repeating_weapons', ids => {
            const output = {};
            ids.forEach(id => {
                output[section_name('weapons', id, 'selected')] = 0;
            });
            output[eventInfo.triggerName] = eventInfo.newValue;
            setAttrs(output);
        });
    });Code language: JavaScript (javascript)

This properly sets the checkbox. We are now finished.

Conclusion

So there we have it. A checkbox in every row of the repeating section that functions like a radio button.

Leave a Reply

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