Function Library

This post lists some useful functions, along with explanations of what they do. If you want to use these, just put them at the start of your script block and you’ll be able to use them in any sheet worker. The functions exclusively use the Fat Arrow syntax, because that is very handy for short functions.

Code Snippets

Copy these lines into the very start of your script block, and you can use these functions whenever you need them. I’ll explain each one below. I might expand this post with more snippets later.

// parsing values
const num = (value, fallback = 0) => +value || fallback;
const int = (value, fallback = 0) => parseInt(value) || fallback;

const places = (score, decimals = 0) => +score.toFixed(decimals);

// sum a set of numbers
const sum = (scores, int = false, fallback = 0) => 
   scores.reduce((a, b) => a + (int ? int(b, fallback) : num(b, fallback)), 0);
const sum_obj = (values, int = false, fallback = 0) =>
   Object.values(values).reduce((a, b) =>
      a + (int ? int(b, fallback) : num(b, fallback)), 0);

// build a changes event string from an array
const build_changes = (stats, sheet_open = false) => 
   stats.reduce((all,stat) => 
   `${all} change:${stat.toLowerCase()}`, sheet_open ? 'sheet:opened' : '');

// tabs - show/hide sections of the sheet
const swaps = {
   /* enter sheet-name : 'tab-attribute'; see text below */
}
Object.keys(swaps).forEach(button => {
   on(`clicked:${button}`, () => {
      setAttrs({
         [swaps[button]]: button
      });
   });
}); 
Code language: JavaScript (javascript)

I’ll explain what each does below, though not necessarily how they do it. They do get more complicated.

Parsing Values

You often need to grab attribute values and turn them into a number, either an integer or floating-point number (a number with decimals). That code might look like:

const strength = parseInt(values.strength) || 0;
const dexterity = parseInt(values.dexterity) || 0;Code language: JavaScript (javascript)

You have to type the same code over and over. With the lines above, you can instead do

const strength = int(values.strength);
const dexterity = int(values.dexterity);Code language: JavaScript (javascript)

You can use int() to get integers (whole numbers) and num() to get numbers where you need decimals.

If the attribute isn’t recognised, it is set to 0. You might want to set it a different default value, say 1, and you can do that just by adding the default value like so:

const strength = int(values.strength, 1);
const dexterity = int(values.dexterity, 1);Code language: JavaScript (javascript)

Just include the default value after a comma.

The Places Function

This converts a number into one with a specific number of decimal places. It’s described in more detail on the page about numbers.

Summing Up Numbers

Sometimes you just want to add up all attributes included in getAttrs or all attributes supplied in an array.

const sum = (scores, int = false, fallback = 0) => 
   scores.reduce((a, b) => a + (int ? int(b, fallback) : num(b, fallback)), 0);

const sum_obj = (values, int = false, fallback = 0) => 
   Object.values(values).reduce((a, b) => 
      a + (int ? int(b, fallback) : num(b, fallback)), 0);Code language: JavaScript (javascript)

In the first of these workers, you supply stats in an array, and it adds them all together. You can create an array on the fly (recall that an array is bounded by [ ] brackets).

const my_sum = sum([str,dex,con,int,wiz,cha]);Code language: JavaScript (javascript)

If those six stat variables exist, they will be added together. Any that don’t exist will be treated as 0. If you specifically want integers (parseInt), you can use:

const my_sum = sum([str,dex,con,int,wiz,cha], true);Code language: JavaScript (javascript)

This function assumes you have the int function also installed.

Sometimes you want to simply add everything in getAttrs. That’s where sum_obj is used.

getAttrs(['stat1', 'stat2'], values => {
    const my_sum = sum_obj(values);Code language: JavaScript (javascript)

These functions heavily use the reduce function, which is very useful but too complex to describe here. Basically, it’s like a loop – it does something with every item in an array.

Changes and the Event Line

It’s pretty common to have a long list of attributes, and a function that responds to a change in any of them. It can be very tedious to write out the event line for them (all those ‘change:’ entries). But if you have an array of stat names, you can simplify it with the build_changes function.

The build_changes function looks like this:

/ build a changes event string from an array
const build_changes = (stats, sheet_open = false) => 
   stats.reduce((all,stat) => 
      `${all} change:${stat.toLowerCase()}`, sheet_open ? 'sheet:opened' : '');Code language: JavaScript (javascript)

Instead of typing something like this:

on('change:stat1 change:stat2 change:stat3 change:stat4 change:stat5 change:stat6', () => {
   getAttrs(['stat1', 'stat2', 'stat3', 'stat4', 'stat5', 'stat6'], values => {Code language: JavaScript (javascript)

You could do this:

const long_list_of_stats = ['stat1', 'stat2', 'stat3', 'stat4', 'stat5', 'stat6'];

on(build_changes(long_list_of_stats), () => {
   getAttrs(long_list_of_stats, values => {Code language: JavaScript (javascript)

The real advantage is when you use that array again – either in the same worker or later workers. That’s when using functions becomes very useful.

Sheet Tabs

It’s very common to use action buttons to change visible sheet tabs. Imagine you have a sheet with just two tabs, “front” and “back”.

The workflow is: you click the action button, named front or back. The sheet worker is run, changing the sheet_tab attribute. Then a CSS rule is based on the class on that hidden attribute, and decides which div to show.

The Action Buttons

You’d create two action buttons in the HTML like this:

<button type="action" name="act_front">Front Page</button>
<button type="action" name="act_back">Back Page</button>Code language: HTML, XML (xml)

A Hidden Input and the Divs

Then create a hidden input which has a class, and the divs that contain the front and back sheets, like so:

<input type="hidden" name="attr_sheet_tab" class="toggle-sheet" value="front">
<div class="front">
   <!-- whatever code is needed for the front page -->
   Front Page
</div>
<div class="back">
   <!-- whatever code is needed for the back page -->
   Back Page
</div>Code language: HTML, XML (xml)

The Sheet Worker

Now modify the sheet worker above to give the hidden sheet_tab attribute the right values.

const swaps = {
   /* enter sheet-name : tab-attribute */
   front: sheet_tab,
   back: sheet_tab
}
Object.keys(swaps).forEach(button => {
   on(`clicked:${button}`, () => {
      setAttrs({
         [swaps[button]]: button
      });
   });
}); 
Code language: JavaScript (javascript)

When one of the action buttons is clicked, this worker looks for which button was clicked, and then writes its name to the sheet_tab attribute.

The CSS Rule that makes it all work

Then you’d create a CSS rule, to respond to the above sheet_tab class:

input.toggle-sheet:not([value="front"]) ~ div.front,
input.toggle-sheet:not([value="back"]) ~ div.back {
    display: none;
}Code language: CSS (css)

Expanding The Rules

The useful trick here is that this method is easily expanded. Maybe you have a section on the front page that itself has a section with variable contents. Maybe you show different sections if the character is a human, vampire, werewolf, and so on. So you have the user click a button for their race.

const swaps = {
   /* enter sheet-name : tab-attribute */
   front: 'sheet_tab',
   back: 'sheet_tab',
   human: 'race_tab',
   vampire: 'race_tab',
   werewolf: 'race_tab',
}
Object.keys(swaps).forEach(button => {
   on(`clicked:${button}`, () => {
      setAttrs({
         [swaps[button]]: button
      });
   });
}); 
Code language: JavaScript (javascript)

And then you’d just need to create the HTML for the mini-tabs, and the CSS for that.

<input type="hidden" name="attr_race_tab" class="toggle-race" value="front">
<div class="human">
   Human Tab
</div>
<div class="vampire">
   Vampire Page
</div>
<div class="werewolf">
   Werewolf Page
</div>Code language: HTML, XML (xml)

And a CSS rule to make this work

input.toggle-sheet:not([value="front"]) ~ div.front,
input.toggle-sheet:not([value="back"]) ~ div.back,
input.toggle-race:not([value="human"]) ~ div.human,
input.toggle-race:not([value="vampire"]) ~ div.vampire,
input.toggle-race:not([value="werewolf"]) ~ div.werewolf {
    display: none;
}Code language: CSS (css)

You can use this method for all visible/hidden sections on the sheet. Just expand the sheet worker and the CSS (you’ll be creating the HTML anyway).

In that sheet worker, you only need to change that object at the stat – just add the names of the action buttons and the attribute you want to save their values into.

Adding Attribute Changes

An alternate version of the sheet worker can handle attribute changes as well as sheet workers. This is more complicated, but as before only the swaps object at the start needs editing.

const swaps = {
   /* enter sheet-name : tab-attribute */
   front: {attribute: 'sheet_tab', type: 'clicked'},
   back: {attribute: 'sheet_tab', type: 'clicked'},
   human: {attribute: 'race_tab', type: 'change'},
   vampire: {attribute: 'race_tab', type: 'change'},
   werewolf: {attribute: 'race_tab', type: 'change'},
}
Object.keys(swaps).forEach(button => {
   on(`${swaps[button].type}:${swaps[button]}`, () => {
      setAttrs({
         [swaps[button].attribute]: button
      });
   });
});Code language: JavaScript (javascript)

This looks complicated, but if you know how to use objects it is pretty straightforward.

Leave a Reply

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