- 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
In a normal javascript tutorial, we’d have discussed strings much earlier. But this is focused on Roll20, and numbers come first. But you can do some interesting things with Strings. In this post, we’ll look at how to change the way a name is displayed on the character sheet, and set the foundation for a very important second post.
What is a String?
A String is anything enclosed by quotes. It’s usually a word or a phrase, but can be anything like “10” or even “[1,2,3]”. That last might look like an array, but the quotes make it behave like a string. If you want it to behave like an array, you first have to convert it into an array. Luckily, JS has functions that can be used for that.
Everything in Roll20 is a String
It’s worth mentioning that everything stored in an attribute in Roll20 is a string. (It’s never a good idea to make absolute statements in JS or Roll20. There are some exceptions with numbers, but it’s best to assume everything is a string.) Say you write the following in HTML:
<input type="number" name="attr_stat_value" value="10">
Code language: HTML, XML (xml)
You might expect @{stat_value} to be a number. After all, it says number type right there. But that just means that it is treated as a number in the HTML (which affects things like how the HTML input container is displayed), but the data it contains (“10”) is stored as a string.
Roll20 uses three different languages- HTML, CSS, and JavaScript, and in sheet workers, you are using Javascript. So when you access @{stat_value} in a sheet worker, you need to coerce it into a number before you try to do number-things with it (like arithmetic). That’s where functions like parseInt and Number() come in.
Type Coercion, and parseInt/parseFloat and Number/+
Here are four slightly different ways to force an attribute value to behave like a number:
getAttrs(['stat'], function(values) {
const my_integer = parseInt(values.stat) || 0; // force an integer
const my_float = parseFloat(values.stat) || 0; // force a floating point number.
const my_number = +values.stat || 0; // this is shorthand for the next line.
const my_number_also = Number(values.stat) || 0; // convert into a floating point number in a slightly different way to parseFloat.
Code language: JavaScript (javascript)
parseInt and parseFloat look for a number in the variable and keep only that part. So if you have a string like “10 things”, they will keep the 10 and discard the rest.
On the other hand, Number looks at the whole variable, so “10 things” is not seen as a number – that is a string, and creates an error. But the String “10” works fine because that is a number…
Generally, you can use any method in Roll20, where attributes are usually meant to be numbers anyway. If there is a reason you need integers, use parseInt, but if you need more complicated numbers use either of parseFloat or Number (or +).
(To be absolutely clear here: javascript will dynamically try to change the data type of a variable, but it often gets it wrong, so don’t rely on it. Do your own coercions!)
When You Want a String
Things like parseInt or Number are used for converting the underlying string into a number, and ||0 is used in case the attribute is not detected as a number.
But if you actually want the string, you don’t have to do any of that. You can just do:
getAttrs(['character_name'], function(values) {
const my_name = values.character_name;
Code language: JavaScript (javascript)
Remember: everything in Roll20 is stored as a string. If you want it to behave differently, you need to change the data type. But if you just want the string, things are a lot simpler!
String Concatenation
One thing you often want to do is combine strings, but it also happens accidentally a lot. Let’s say you have two numbers stored as strings and you try to add them together.
const number1 = "10";
const number2 = "50";
const sum = number1 + number2; // result: "1050"
Code language: JavaScript (javascript)
When you try to add numbers together, because you think they are numbers but Roll20 knows they are actually strings, what happens is they are added to each other in a different way: one is added to the end of the other, creating a new String. This is called Concatenation.
You sometimes see this in AutoCalc fields too, where Roll20 is treating quantities you think are numbers as if they were strings, and you have to find a way to get Roll20 to treat them as numbers.
So be careful of Concatenation – it will ruin you day. But there are occasions when you want to do it, so remember you can use the + operator to combine strings easily. You might want to add a space or other character manually. For example:
const first_name = "John";
const surname = "Smith";
const full_name = first_name + surname; // result: "JohnSmith"
const full_name = first_name + " " + surname; // result: "John Smith"
Code language: JavaScript (javascript)
Here ” ” is a space – remember the quotes are not part of the strong, they just show us where it starts and ends.
Multiplication To Make Numbers
While you can use parseInt and Number to coerce strings into numbers, if you know, absolutely positively for sure know, that you have a number inside those string quotes, you can also just multiply by 1.
const look_a_number = "10"*1 + "50"*1; // result: 60
const look_a_number = ("10")*1 + ("50")*1; // result: 60
const look_a_number = ("10"*1) + ("50"*1); // result: 60
const look_a_number = ("10" + "50")*1; // result: 1050
Code language: JavaScript (javascript)
This shows three different ways of doing the same thing. The brackets are completely optional – use them purely to make the code easier to read.
Be careful of the last one, though. You have to coerce each separate value into a number before trying to add them together!
Getting a Name
Okay, with the basics out of the way, we can move on to the main topics of this post. We’ll see some of the things you can do with strings.
Let’s say players store their character’s full name in the character_name attribute, and names can be “John Smith”, “Alexandra”, and so on. You want to just show the first name somewhere on the sheet, so you need to check if the name has multiple names, and get just the first one. We will do that here.
First, let’s build the basic sheet worker skeleton. This will run whenever character_name changes (like the player changes their character’s name).
on('change:character_name', function() {
getAttrs(['character_name'], function(values) {
const full_name = values.character_name;
});
});
Code language: JavaScript (javascript)
Now we want to insert code that gets just the first name.
Getting The First Name
So we have the full name. How to get just the first name? Well we know that if there are more than one name, they will be separated by spaces (” “), so our first step is to find if a space exists.
const space_where = full_name.indexOf(" "); // result: -1 or a positive number starting at 0.
Code language: JavaScript (javascript)
indexOf is a function that tells us where in the string the character is found, and if not found, returns a -1.
In JavaScript most counting sequences start at 0, so the first character is 0, second is 1, third is 2, etc.
Note: if the result is -1, the searched item is not found – there are no spaces. The full name is just one word.
But if the number is positive, the full name is made of multiple parts and the first name is different from the full name. Here we want to trim the word just down to that first word, which is where the substring method comes in.
let first_name = "";
const space_where = full_name.indexOf(" ");
if(space_where === -1) {
first_name = full_name;
} else {
first_name = full_name.substring(0, space_where);
}
Code language: JavaScript (javascript)
Substring is a special function that takes a start and end index (and the end is optional). The problem with the above code is it will include the space. If you have the name “John Smith”, and run that it will give the answer “John “. If we want to get rid of that space, we need to end it one character earlier.
Doing that, and converting it into a function we can call later, we get the code below. This shows the full sheet worker:
function first_name_equals(full_name) {
let first_name = "";
const space_where = full_name.indexOf(" ");
if(space_where === -1) {
first_name = full_name;
} else {
first_name = full_name.substring(0, space_where -1);
}
return first_name;
}
on('change:character_name', function() {
getAttrs(['character_name'], function(values) {
const full_name = values.character_name;
const first_name = first_name_equals(full_name);
setAttrs({
short_name: first_name
});
});
});
Code language: JavaScript (javascript)
Now we have a sheet worker that saves the first name into an attribute called short_name.
Making the Function Shorter Using Split!
We could have used an array function called split to make this function. Here’s what that would look like:
function first_name_equals(full_name) {
let first_name = full_name.split(" ")[0]; // [0] gets the first item in an array.
return first_name;
}
on('change:character_name', function() {
getAttrs(['character_name'], function(values) {
const full_name = values.character_name;
const first_name = first_name_equals(full_name);
setAttrs({
short_name: first_name
});
});
});
Code language: JavaScript (javascript)
The split function converts a string into an array, splitting it on the character you suggest (here using “,”). Then [?] returns an item from the array. If you had an array [“This”, “is”, “an”, “array”], then [0] would return “This” and [2] would return the third item: “an”. Remember, start counting at zero.
Getting The Nickname Or First Name
Some names have a nickname, like Mack “the knife” McKenzie. You might want to present the nickname if it exists, or the first name, or the full name.
You might notice a potential problem (the name might include ” characters, which also mark the beginning and ends of strings). We’ll discuss that in part 2 along with a method to make writing your roll buttons much quicker.