Character Vault
Any Concept / Any System
Compendium
Your System Come To Life
Roll20 for Android
Streamlined for your Tablet
Roll20 for iPad
Streamlined for your Tablet

Personal tools

Difference between revisions of "Sheetworker examples for Non-programmers"

From Roll20 Wiki

Jump to: navigation, search
m (added "See Also")
m (move to main sw-page)
 
(27 intermediate revisions by 5 users not shown)
Line 1: Line 1:
While the majority of Roll20's character sheets are written using only HTML and CSS, sheetworker scripts(javascript) are often necessary to either simplify complicated HTML or add more advanced functionality to a character sheet. For those of us "non-programmers", the examples given on the [[Sheet_Worker_Scripts|Sheet Worker Scripts]] page may be less than exhaustive.  This page was created to help better explain how to use sheetworker scripts in your character sheets.
+
{{revdate}}{{BCS}}''Main Article:'' '''[[Sheet Worker Scripts]]'''
 +
<br>
 +
While the majority of Roll20's character sheets are written using only HTML and CSS, there is often need for "sheetworker scripts"(AKA just "sheetworkers) to either simplify complicated HTML, or add more advanced functionality to a character sheet. These are written in [https://developer.mozilla.org/en-US/docs/Web/javascript JavaScript], with a few unique features made for Roll20's character sheets.
 +
{{NavSheetDoc}}
 +
For those of us "non-programmers", the examples given on the [[Sheet_Worker_Scripts|Sheet Worker Scripts]] page may be less than exhaustive.  This page was created to help better explain how to use sheetworkers in your character sheets.
  
===Simple Template===
+
This page may or may not contain specific examples of sheetworker code, however the information provided is invaluable to learning how to implement sheetworkers into your character sheet. '''[https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps JavaScript: First Steps]''' by Mozilla is a good starting point to get familiar with JavaScript if you want to understand some of the things better.
 +
 
 +
====Autocalc Fields and Sheet Workers====
 
(credit: [https://app.roll20.net/users/157788 GiGs])
 
(credit: [https://app.roll20.net/users/157788 GiGs])
 +
A common pitfall is to try to combine autocalc fields and sheet workers. Try not to do this.
  
You really don't need to understand how sheet workers work, to be honest.
+
First, autocalc fields need to be set as disabled, and sheet workers cannot edit a disabled input. If you want to use a sheetworker to create a value that is displayed but cant be edited, set the input to readonly. this looks the same as disabled, and cant be edited, but sheetworkers can update it.
As long as you copy the change / get attrs / setattrs sections properly, to get the data you operate on with fairly standard programming techniques.
+
  
<pre data-language="javascript" style="overflow:auto; width:auto;">
+
Second, don't try to grab autocalc fields in a sheet worker. The getAttrs function in a sheet worker will grab the text of an autocalc field, not the number it resolves to. If the attribute is something like "[[@{STR} + @{DEX}]]" then getAttrs will give you a value of "[@{STR} + @{DEX}]]" and not 23 or 30 or whatever number you are expecting.
  
on("change:stat1 change:stat2 sheet:opened", function() {  //sheet:opened is optional, but is useful for some workers - the script runs every time the sheet opens.
+
So, you need to recreate that formula in the sheet worker anyway, by using getAttrs to grab STR and DEX values, and then adding them together within the worker. Alternatively, replace the initial autocalc field with a sheet worker.
  getAttrs(["stat1","stat2"], function(values) {
+
        let stat1 = values.stat1;                // wrap this in a parseInt function if you need it to be a number
+
        let stat2 = parseInt(values.stat2)||0;    // like so
+
// ============================
+
// at this point you have the variables you need to work with. Do whatever you need below, using ifs, switches, for loops, and so on
+
// as you would in other programming languages. You can use very simple stuff, similar to BASIC programming, whatever suits your level of skill.
+
  
 +
Because of these two facts, once you start using sheet workers, you tend to abandon autocalc fields. They dont interact well. You can still use autocalcs for standalone attributes that dont affect anything else, but sheet workers for everything else.
  
// then once you have done whatever stuff you need to do to get your desired output:
 
// the bit below the line lets you wrap up the sheet worker.
 
// ============================
 
        setAttrs({                           
 
            stat1:stat1,
 
            stat2:stat2
 
        });
 
  });
 
});
 
  
</pre>
+
== Sheet Worker Tutorial ==
If you have different, more, or less stats, you just need to add them to the change, getattrs, and setattrs lines, and the variable assignments. You really don't need to understand how they work, as long as you can copy them and update them with correct values.
+
(credit: [https://app.roll20.net/users/157788 GiGs])
  
Then you can do what you need with them between the double lines.
+
All Sheet Workers have a common structure, four steps that must happen in order:
I hope this helps. In the beginning I was just as befuddled about them as you are, and using a template like this got me started.
+
# Event Trigger
 +
# Get Attributes
 +
# Do the Work
 +
# Update the Character Sheet
  
Two tips I'd suggest when starting out:
+
=== Event Trigger ===
 +
Sheet Workers are triggered by something changing on the character sheet. Triggers are covered fully under [[Sheet_Worker_Scripts#Events | Sheetworker - Events]]. On this page we will mainly be looking at the '''change''' event. Imagine we have a game where characters have a Hit Point attribute, that is calculated by adding two stats, SIZ and CON together. let's build a sheet worker to calculate that.
  
* Keep your attribute names lower case.
+
First we need to build the trigger:
* Don't use dashes or other symbols except underscores in attribute names.
+
<pre data-language="javascript" style="overflow:auto; width:auto;">
:So if you have a name like Health-Value, change it to health_value.
+
on("change:siz change:con sheet:opened", function() { 
  
You can break these guidelines when you know what you're doing, but you need to write the sheet workers slightly differently, and it's better to avoid the hassle when starting out.
+
});
  
===Auto-calculating Attributes===
 
'''example 1;'''(credit: [https://app.roll20.net/users/4692 Rabulias])
 
 
Your best bet would be to avoid the autocalc fields entirely if you can. Monitor the two other fields and when they change, have a sheetworker that adds them up to the new value. Then you can refer to the new value in other calculations much easier.
 
<pre data-language="javascript" style="overflow:auto; width:auto;">
 
on("sheet:opened change:stat_a change:stat_b", function() {
 
    getAttrs(["stat_a", "stat_b"], function(values) {
 
        setAttrs({
 
            "foo_modchars": parseInt(values["stat_a"],10) || 0 + parseInt(values["stat_b"],10) || 0
 
        });
 
    });
 
});
 
 
</pre>
 
</pre>
 +
That's pretty straightforward. Notice that the attribute names are lower case. Confusingly, attributes must always be in lower case when on the event trigger line, but their case doesn't matter anywhere else. Roll20 attribute names are case-insensitive, but must always be lower case on the event trigger line.
  
'''example 2;'''(credit: [https://app.roll20.net/users/157788 GiGs])
+
You can have as many change:attribute_name statements as you need for the script.
  
I rememembr seeing a script someone wrote to allow you to use autocalc fields within sheet workers, but it's just simpler to use Rabulias's approach(see example 1 above).
+
Note: '''sheet:opened''' tells the worker to run every time the character sheet is opened. This is optional, but is handy to keep a sheet properly updated.
  
Add the relevant stats to the on(change:) line, and duplicate the calculation within the sheet worker.  
+
=== Get Attributes ===
 +
So an attribute changes, and its sheet worker is triggered. Now, you must collect the value of all needed attributes. The sheet worker does not automatically know anything about the rest of the character sheet: each sheet worker only knows what data you collect for it.
 +
 
 +
This is where the getAttrs function comes in.
 +
<pre data-language="javascript">
 +
getAttrs(["SIZ","CON"], function(values) {
 +
        let siz = values.SIZ;
 +
        let con = values.CON;
  
I generally don't put my working in the setattrs call, but before it so i can more easily check it.
 
Something like
 
<pre data-language="javascript" style="overflow:auto; width:auto;">
 
on("sheet:opened change:stat_a change:stat_b", function() {
 
  getAttrs(["stat_a", "stat_b"], function(values) {
 
      var stat_a = parseInt(values["stat_a"],10)||0;
 
      var stat_b = parseInt(values["stat_b"],10)||0;
 
      var output = stat_a + stat_b;
 
      setAttrs({
 
        "foo_modchars": output
 
      });
 
  });
 
 
});
 
});
 
</pre>
 
</pre>
 +
getAttrs has two steps: the first line, the function line, is where you grab the attributes. They are stored in an data object, whose name is defined after the word function. In this case, values. You can call it whatever you want, most people use values, or just v.
  
===Summing Repeating Sets===
+
This object is a Javascript Object, and you can read up on that elsewhere. But all you really need to know is, it contains the names and values of the attributes you declared - in this case, SIZ and CON.
See [[repeatingSum|The RepeatingSum Function]] for a way to sum items in inventory, and similar repeating sections.
+
  
===Looping Sheet Workers===
+
Since this object was named values, you get SIZ by using values.SIZ, and con by using values.CON. And that's exactly what we do above - we define two variables, one for each attribute, and assign the values of those attributes.
You often need to build many copies of similar sheet workers. For example, when calculating modifiers for attributes, you might build a sheet worker for each attribute, and the code may be identical except for the attribute name.
+
Wouldn't it be nice if you could write just one sheet worker, and use it for all your stats? See [[universalSheetWorkers|Universal Sheet Workers]] for a way to this.
+
  
===Helpful Advice===
+
There is a complication. If you doing math with the stats, you need to convert them into a number. If you don't, you can get an error. There are several ways to do this, a common one is to use parseInt (for whole numbers) or parseFloat (for fractional or decimal numbers). Here's what that looks like:
This section may or may not contain specific examples of sheet worker code, however the information provided is invaluable to learning how to implement sheet workers into your character sheet.
+
<pre data-language="javascript">
 +
getAttrs(["SIZ","CON"], function(values) {
 +
        let siz = parseInt(values.SIZ)||0;
 +
        let con = parseInt(values.CON)||0;
  
====Understanding sheet workers.====
+
});
(credit: [https://app.roll20.net/users/726129 Jakob])
+
</pre>
 +
That looks a lot more complicated, but don't worry, you dont need to understand it. Just copy it. For the record, the end bit ||0 sets a default of 0. So if the SIZ attribute contains nothing, or text, it will be treated as a value of 0 and an error is avoided.
  
Usually, the best way to learn something is to do it! Start solving the problems you want to solve using sheet workers, and learn along the way :).
+
=== Do The Work ===
 +
Each Sheet Worker is designed to ''do something''. Sometimes that will be very complicated, other times like this one it will be very simple.
 +
We are just adding two stats, so that looks like this:
 +
<pre data-language="javascript">
 +
let hp = siz + con;
 +
</pre>
 +
I did say it was simple.
  
Now, that probably wasn't very helpful. I could also encourage you to look at code others have written, which you should do; just realize that many sheet authors are also amateurs (such as me), hence the code may not be the most elegantly-written example.
+
=== Update the Character Sheet ===
 +
Now we have the hit point total, we need to actually save it to the character sheet. For that we need the setAttrs function. Assuming the attribute is called hit_points, that would look like this:
 +
<pre data-language="javascript">
 +
setAttrs({                           
 +
    hitpoints: hp
 +
});
 +
</pre>
 +
Note: some sheet workers will update multiple attributes. You seperate them with commas. That would look like this:
 +
<pre data-language="javascript">
 +
setAttrs({                           
 +
    hitpoints: hp,
 +
    anotherstat: anotherscore
 +
});
 +
</pre>
 +
The final attribute must not have a comma - that breaks the script.
  
That probably also wasn't helpful. Let me try something else: depending on what your course taught you, much of it (everything dealing with manipulation of the DOM) is not going to be very helpful. Here's some important things you need to keep in mind that might make sheet workers different from what you've learned:
+
=== 5. Putting It Together ===
 +
So we now have all the steps needed to put the worker together.
 +
<pre data-language="javascript" style="overflow:auto;white-space:pre-wrap;">
 +
on("change:siz change:con sheet:opened", function() {
 +
  //sheetworker triggers on any changes to the "siz" or "con" attribute, or when sheet is opened.
 +
getAttrs(["SIZ","CON"], function(values) {
 +
            //get the values for the two attributes, so they can used.
 +
let siz = parseInt(values.SIZ)||0;
 +
let con = parseInt(values.CON)||0;
 +
                  //saves the current values of the two attributes on two temporary variables, which then can be used easily in calculations.
 +
let hp = siz + con;
 +
                    // defines new variable, based on the sum of the two attributes
 +
setAttrs({                           
 +
hitpoints: hp
 +
                          // changes the "hitpoints" sheet attribute to be the value of our temporary variable "hp"
 +
});
 +
});
 +
});
 +
</pre>
  
:1. '''Sheet workers are event-driven.''' Everything interesting your workers do will happen in response to a certain event on the sheet; most of the time, the triggering event will be changing an attribute on the sheet (i.e., the value of some input, be it a checkbox, radio, text, number input, or textarea). This is what the on("change:attr", function () { ...}) is there for: it's about registering a function that's supposed to be executed every time attr changes.
+
=== Finishing Thoughts ===
 +
This is a very simple example, but steps 1, 2, and 4, are basically the same in all sheet workers. Once you understand the basic structure, you can easily copy those, and just do the working (section 3).
 +
The [[Sheet_Worker_Snippets|Sheet Worker Snippets]] will contain more examples for you to copy.
  
:2. '''Sheet workers can only really do one thing.''' Sheet workers can change the value of attributes, and that's pretty much it (they can also remove repeating rows, but that's not their main use). If you want your sheet workers to do a thing, ask yourself if it can be accomplished by changing the value of an attribute (this includes being able to (un)check checkboxes), +perhaps some CSS. If yes, you can do it with sheet workers. If not, you cannot.
 
 
:3. '''Sheet workers are asynchronous.''' The main interesting functions you have access to in sheet workers, namely getAttrs, setAttrs, and getSectionIDs, take as their second argument functions that do other stuff. This means that you cannot trust that your code will simply be executed from top to bottom, and have to write in such a way that it makes sense no matter when asynchronous functions are executed. This is often a source of confusion for people.
 
 
 
====Good Practices====
 
(credit: [https://app.roll20.net/users/157788 GiGs])
 
 
* I think it's a good idea (especially when still learning) to get into the habit of putting all the values you are using in variables at the top of the code. It makes writing the code a bit more laborious, but if you reuse values, or have long macros, it can make things clearer, and most importantly, you can then use console.log statements to check what their values are. (they will appear in the browser's dev console, which you can usually find by pressing f12).
 
 
* Another tip is its a good idea to just use a single setAttrs statement, at the end of the sheet worker. For this one which is just modifying a single attribute, it doesn't matter, but you'll eventually get into making workers that set multiple attributes. when that happens, you definitely want to group them into a single statement, to avoid unpredictable errors.
 
 
* Also its a good idea to use variable names that match the value they are calling. In the code below, for example, i renamed index to currenthp, which I think is more descriptive.
 
 
* One final tip: if you are using parseInt on cells that users can enter values into, it's a good idea to add a default value. For instance parseInt('something')|||0 means that if the cell contains text, or not a number, you'll get a number in the code. Your if statements will fail without this, if the cells being called don't actually have numbers in them.
 
 
====Autocalc Fields and Sheet Workers====
 
(credit: [https://app.roll20.net/users/157788 GiGs])
 
A common pitfall is to try to combine autocalc fields and sheet workers. Try not to do this.
 
 
First, autocalc fields need to be set as disabled, and sheet workers cannot edit a disabled input. If you want to use a sheetworker to create a value that is displayed but cant be edited, set the input to readonly. this looks the same as disabled, and cant be edited, but sheetworkers can update it.
 
 
Second, don't try to grab autocalc fields in a sheet worker. The getAttrs function in a sheet worker will grab the text of an autocalc field, not the number it resolves to. If the attribute is something like "[[@{STR} + @{DEX}]]" then getAttrs will give you a value of "[@{STR} + @{DEX}]]" and not 23 or 30 or whatever number you are expecting.
 
 
So, you need to recreate that formula in the sheet worker anyway, by using getAttrs to grab STR and DEX values, and then adding them together within the worker. Alternatively, replace the initial autocalc field with a sheet worker.
 
 
Because of these two facts, once you start using sheet workers, you tend to abandon autocalc fields. They dont interact well. You can still use autocalcs for standalone attributes that dont affect anything else, but sheet workers for everything else.
 
 
   
 
   
 
__FORCETOC__
 
__FORCETOC__
  
 
==See Also==
 
==See Also==
* [[UniversalSheetWorkers]]
+
* [[Sheet_Worker_Snippets|Sheet Worker Snippets]]
* [https://app.roll20.net/forum/post/6963354/build-lookup-table-into-a-character-sheet/?pageforid=6964447#post-6964447 How to integrate table of stats into a sheet]
+
* [[UniversalSheetWorkers|Universal Sheet Workers]] - a function that can handle a bunch of similar sheetworkers
 +
* [[repeatingSum|RepeatingSum]] - sum numbers from a repeating section
 +
* '''{{fpl|8034567/ Sheet Worker Optimization}}''' by [[Scott C.]]
 +
* {{fpl|6964447/ How to integrate table of stats into a sheet}} by [[GiGs]]
 +
* [https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps Introduction to JavaScript] - MDN web docs
  
 
<br>
 
<br>
 
<br>
 
<br>
  
[[Category:Tips]]
 
[[Category:User content]]
 
 
[[Category:Sheetworker]]
 
[[Category:Sheetworker]]
 +
[[Category:Character Sheet Creation]]

Latest revision as of 08:18, 6 April 2022

Main Article: Sheet Worker Scripts


While the majority of Roll20's character sheets are written using only HTML and CSS, there is often need for "sheetworker scripts"(AKA just "sheetworkers) to either simplify complicated HTML, or add more advanced functionality to a character sheet. These are written in JavaScript, with a few unique features made for Roll20's character sheets.

For those of us "non-programmers", the examples given on the Sheet Worker Scripts page may be less than exhaustive. This page was created to help better explain how to use sheetworkers in your character sheets.

This page may or may not contain specific examples of sheetworker code, however the information provided is invaluable to learning how to implement sheetworkers into your character sheet. JavaScript: First Steps by Mozilla is a good starting point to get familiar with JavaScript if you want to understand some of the things better.

Contents

[edit] Autocalc Fields and Sheet Workers

(credit: GiGs) A common pitfall is to try to combine autocalc fields and sheet workers. Try not to do this.

First, autocalc fields need to be set as disabled, and sheet workers cannot edit a disabled input. If you want to use a sheetworker to create a value that is displayed but cant be edited, set the input to readonly. this looks the same as disabled, and cant be edited, but sheetworkers can update it.

Second, don't try to grab autocalc fields in a sheet worker. The getAttrs function in a sheet worker will grab the text of an autocalc field, not the number it resolves to. If the attribute is something like "[[@{STR} + @{DEX}]]" then getAttrs will give you a value of "[@{STR} + @{DEX}]]" and not 23 or 30 or whatever number you are expecting.

So, you need to recreate that formula in the sheet worker anyway, by using getAttrs to grab STR and DEX values, and then adding them together within the worker. Alternatively, replace the initial autocalc field with a sheet worker.

Because of these two facts, once you start using sheet workers, you tend to abandon autocalc fields. They dont interact well. You can still use autocalcs for standalone attributes that dont affect anything else, but sheet workers for everything else.


[edit] Sheet Worker Tutorial

(credit: GiGs)

All Sheet Workers have a common structure, four steps that must happen in order:

  1. Event Trigger
  2. Get Attributes
  3. Do the Work
  4. Update the Character Sheet

[edit] Event Trigger

Sheet Workers are triggered by something changing on the character sheet. Triggers are covered fully under Sheetworker - Events. On this page we will mainly be looking at the change event. Imagine we have a game where characters have a Hit Point attribute, that is calculated by adding two stats, SIZ and CON together. let's build a sheet worker to calculate that.

First we need to build the trigger:

on("change:siz change:con sheet:opened", function() {  

});

That's pretty straightforward. Notice that the attribute names are lower case. Confusingly, attributes must always be in lower case when on the event trigger line, but their case doesn't matter anywhere else. Roll20 attribute names are case-insensitive, but must always be lower case on the event trigger line.

You can have as many change:attribute_name statements as you need for the script.

Note: sheet:opened tells the worker to run every time the character sheet is opened. This is optional, but is handy to keep a sheet properly updated.

[edit] Get Attributes

So an attribute changes, and its sheet worker is triggered. Now, you must collect the value of all needed attributes. The sheet worker does not automatically know anything about the rest of the character sheet: each sheet worker only knows what data you collect for it.

This is where the getAttrs function comes in.

getAttrs(["SIZ","CON"], function(values) {
        let siz = values.SIZ;
        let con = values.CON;

});

getAttrs has two steps: the first line, the function line, is where you grab the attributes. They are stored in an data object, whose name is defined after the word function. In this case, values. You can call it whatever you want, most people use values, or just v.

This object is a Javascript Object, and you can read up on that elsewhere. But all you really need to know is, it contains the names and values of the attributes you declared - in this case, SIZ and CON.

Since this object was named values, you get SIZ by using values.SIZ, and con by using values.CON. And that's exactly what we do above - we define two variables, one for each attribute, and assign the values of those attributes.

There is a complication. If you doing math with the stats, you need to convert them into a number. If you don't, you can get an error. There are several ways to do this, a common one is to use parseInt (for whole numbers) or parseFloat (for fractional or decimal numbers). Here's what that looks like:

getAttrs(["SIZ","CON"], function(values) {
        let siz = parseInt(values.SIZ)||0;
        let con = parseInt(values.CON)||0;

});

That looks a lot more complicated, but don't worry, you dont need to understand it. Just copy it. For the record, the end bit ||0 sets a default of 0. So if the SIZ attribute contains nothing, or text, it will be treated as a value of 0 and an error is avoided.

[edit] Do The Work

Each Sheet Worker is designed to do something. Sometimes that will be very complicated, other times like this one it will be very simple. We are just adding two stats, so that looks like this:

let hp = siz + con;

I did say it was simple.

[edit] Update the Character Sheet

Now we have the hit point total, we need to actually save it to the character sheet. For that we need the setAttrs function. Assuming the attribute is called hit_points, that would look like this:

setAttrs({                            
     hitpoints: hp
});

Note: some sheet workers will update multiple attributes. You seperate them with commas. That would look like this:

setAttrs({                            
     hitpoints: hp,
     anotherstat: anotherscore
});

The final attribute must not have a comma - that breaks the script.

[edit] 5. Putting It Together

So we now have all the steps needed to put the worker together.

on("change:siz change:con sheet:opened", function() {
   //sheetworker triggers on any changes to the "siz" or "con" attribute, or when sheet is opened.
	getAttrs(["SIZ","CON"], function(values) {
            //get the values for the two attributes, so they can used.
		let siz = parseInt(values.SIZ)||0;
		let con = parseInt(values.CON)||0;
                   //saves the current values of the two attributes on two temporary variables, which then can be used easily in calculations.
		let hp = siz + con;
                    // defines new variable, based on the sum of the two attributes
		setAttrs({                            
			hitpoints: hp
                           // changes the "hitpoints" sheet attribute to be the value of our temporary variable "hp"
		});
	});
});

[edit] Finishing Thoughts

This is a very simple example, but steps 1, 2, and 4, are basically the same in all sheet workers. Once you understand the basic structure, you can easily copy those, and just do the working (section 3). The Sheet Worker Snippets will contain more examples for you to copy.



[edit] See Also