- Anatomy of a Sheet Worker
- Events, and watching Attributes
- Variables – How to Name Things
- Arithmetic in Sheet Workers
- What If? in Sheet Workers
- JavaScript Objects
- Getting Loopy With JavaScript
- Logging in the Browser Console
- Strings, Arrays, and Loops
- Asynchronicity and Things to Avoid With Loops
- Changes and the eventInfo Object
- Action Buttons
- setAttrs and Saving Attributes
- Castle Falkenstein Design – Sheet Workers
- The Perils of Sheet Worker Functions
- The Script Block and Identifying Characters
- Arrays and Dropdowns
- Undefined and Other Error Values
- The Ternary Operator – The One-Line If
- Template Literals
- Functions and the Fat Arrow
- Strings in Sheet Workers
- A Sheet Worker Reprise
There are two kinds of buttons on Roll20 character sheets, and each is for a different purpose. Roll buttons run macros and print to chat, while action buttons run sheet workers and so can update attributes on a character sheet.
For a long time, this was true: the button types are completely separate. Roll buttons can only run macros, and action buttons can only run sheet workers, and you can’t combine them. The introduction of Custom Roll Parsing allows action buttons to run macros, but the code for them is complicated and we’ll cover them later. For now, pretend that feature does not exist.
So, action buttons can run sheet workers. This post describes how to use them and why.
Coding Your Buttons
Roll buttons are for when you want to print something to chat and look like this:
<button type="roll" name="roll_example" value="/roll 1d20+5">Example</button>
Code language: HTML, XML (xml)
They have already been covered in the section on html, and are used whenever you want to make a roll macro. They have a type=”roll”, a name that starts “roll_” and a value which is the roll macro. (They can also have a class, if you want to change the button’s appearance, but that’s optional.)
An action button is when you want to trigger a sheet worker with a button, and looks similar but different:
<button type="action" name="act_example">Example</button>
Code language: HTML, XML (xml)
They have a type=”action”, a name that starts with “act_” and no value.
Detecting When a Button Was Clicked
Action buttons work exactly like any other sheet worker, with the only difference being how they are triggered. Instead of using a change: event, use a clicked: event.
on('clicked:example', function() {
Code language: JavaScript (javascript)
You can combine button clicks and attribute changes in the same sheet worker.
on('clicked:example change:example', function() {
Code language: JavaScript (javascript)
Attributes and buttons can have the same name, but refer to different elements, because of the roll_, act_, or attr_ prefix. These are all different elements:
<input type="text" name="attr_example" value="10">
<button type="roll" name="act_roll" value="I was clicked!">Example Roll</button>
<button type="action" name="act_example">Example Action</button>
Code language: HTML, XML (xml)
While you can use the same name for different elements, that doesn’t mean it’s a good idea! Keeping things differentiated is often a good idea. Sometimes you need to tell things apart.
Using Action Buttons
Since action buttons can do anything a sheet worker can do, the only real limit is your imagination. But there are some common uses:
Resource Use, Increment a Value
Let’s say your character has a number of arrows, and you want to check off one each time you fire.
on('clicked:ammo', function() {
getAttrs(['ammo'], function(values) {
const ammo = +values.ammo || 0;
const new_ammo = Math.max(0,ammo );
setAttrs({
ammo: new_ammo
});
});
});
Code language: JavaScript (javascript)
This sheet worker works with two html elements:
<input type="number" name="attr_ammo" value="10">
<button type="action" name="act_ammo">Use Ammo!</button>
Code language: HTML, XML (xml)
When you click the ammo button, the number of arrows drops by one, and never goes below 0.
You might want another way to check if the player is trying to fire an arrow when they have no ammo left – but that is beyond the scope of this example.
Toggles
One very handy way to track if something is available is use a toggle. On a value of 1, it’s available, and on a value of 0, it’s not. Then you can simply use 1-value to flip between states. If it started at 1, it becomes 0; and if it started at 0 it becomes 1. Here’;’s an example:
on('clicked:toggle_button', function() {
getAttrs(['toggle_value'], function(values) {
const original_value = +values.toggle_value || 0;
const toggled_value = 1 - original_value;
setAttrs({
toggle_value: toggled_value
});
});
});
Code language: JavaScript (javascript)
This is a great way to use checkboxes that have their value set to 1. you can flip them on and off with a button click.
<input type="checkbox" name="attr_toggle_value" value="1">
<button type="action" name="act_toggle_button">Flip Toggle!</button>
Code language: HTML, XML (xml)
You can use this method to flip whether an ability or a buff is active or deactivated.
Clear Several Values
In the previous post, an example was given of creating a series of checkboxes that could be activated by the players but could not be deactivated the same way. Here is a button that resets five attributes to a value of 0. So, at the end of a session, you could click this button and clear all checkboxes in one fell swoop.
on('clicked:clear_all', function() {
setAttrs({
attribute1: 0,
attribute2: 0,
attribute3: 0,
attribute4: 0,
attribute5: 0
});
});
Code language: JavaScript (javascript)
Tabs and Action Buttons
A common technique is to use a set of checkboxes to hide or show tabs of a sheet. You can replace these with action buttons to do it more easily, one for each tab. This uses html, css, and sheet workers.
This is the easiest way to handle tabs, or any group of visible or hidden sections, and can all be handled with one sheet worker and one css declaration no matter how many areas you want to show and hide.
First create the action buttons, to look like your tabs or whatever.
<button type="action" name="act_character" class="tab">Character</button>
<button type="action" name="act_spells" class="tab">Spells</button>
<button type="action" name="act_gear" class="tab">Gear</button>
Code language: HTML, XML (xml)
The tab class is used to apply the same styling to each button. This is for a sheet with three tabs, and you can see their names. Pay attention to the action button names.
Then each section of the sheet, the tab, needs its own hidden attribute and div.
<input type="hidden" name="attr_character" class="toggle" value="1">
<div class="character toggled">
<!-- the character stuff will show here -->
Character
</div>
<input type="hidden" name="attr_spells" class="toggle" value="0">
<div class="spells toggled">
<!-- the Spell stuff will show here -->
Spells
</div>
<input type="hidden" name="attr_gear" class="toggle" value="0">
<div class="gear toggled">
<!-- the Gear stuff will show here -->
Gear
</div>
Code language: HTML, XML (xml)
Each div has two classes. The first is for whatever styling you want to apply to that div, the second is just for the visibility toggle. The hidden attribute must be directly before its linked tab – there must be no other elements between them.
Then you need some CSS to hide or show them. One CSS declaration will handle all three.
.charsheet input.toggle[value="0"] + div.toggled{
display: none;
}
Code language: CSS (css)
This css declaration hides all divs with a class of show, if they also have a preceding input with class toggle and value = 0. Note the defaults above set Character as visible and the rest hidden by default. That is the first view.
Finally, to make it all work you need a sheet worker which uses an array of tab names. This may look complicated, but the only part you have to change is the first line, the array of tab names.
const tabs = ['character', 'spells', 'gear'];
on(tabs.map(tab => `clicked:${tab}`).join(' '), function(event) {
const which = event.triggerName.replace('clicked:', '');
const output = {};
tabs.forEach(tab => output[tab] = tab === which ? 1 : 0);
setAttrs(output);
});
Code language: JavaScript (javascript)
This worker uses a couple of techniques that will be described soon, but for now, remember you only have to change the first line.
The sheet worker relies on the tab names being the same as the hidden attribute names. It wil consider the entire array as one set, and toggle one item on and the rest off.
There is another good way to handle tabs using sheet workers, described here. I believe it’s also on the Roll20 Wiki.
Caveats
The Macro Bar
You can drag a roll button to your macro bar, but there’s no way I’m aware of to do that with an action button. You can address action buttons in macros, using %{character-name|button-name}. If the character was named Bob, you can call the example action button with %{Bob|example}.
When using an action button, always supply the character name (or the selected or target keywords), even on the abilities frame of a character. Never try to do this: %{example}.
Show, Hide, Hidden Classes
Don’t use class names of show, hide, or hidden, unless you really want something visible or hidden. These are classes built into Roll20, and will override any other classes you place on that element.