Stat Sheet Tutorial: Difference between revisions
m (Fixed "#if" should be "/if" in descriptive text) |
m (Added reference to respecting the user's Preferences settings) |
||
Line 44: | Line 44: | ||
In many cases, the embedded instructions are simply to replace the block with the value of a variable (such as {{code|{{portraitWidth}}}}). | In many cases, the embedded instructions are simply to replace the block with the value of a variable (such as {{code|{{portraitWidth}}}}). | ||
<syntaxhighlight lang="handlebars" line | Also note that the MapTool ''Preferences'' allow the user to set their preferred size for the portrait image. Be polite when you create your own stat sheet and respect the user's preferences! (Note the use of {{code|{{portraitWidth}}}} in the {{code|img}} element in the example, below, circa line 21.) | ||
<syntaxhighlight lang="handlebars" line> | |||
<!DOCTYPE html> | <!DOCTYPE html> | ||
<html> | <html> | ||
Line 137: | Line 139: | ||
As you may guess, the {{code|{{#if}}}} is the beginning of an {{code|if}} block and it ends at the corresponding {{code|{{/if}}}}. Else blocks can also be used, as shown. | As you may guess, the {{code|{{#if}}}} is the beginning of an {{code|if}} block and it ends at the corresponding {{code|{{/if}}}}. Else blocks can also be used, as shown. | ||
<syntaxhighlight lang="handlebars" line | <syntaxhighlight lang="handlebars" line> | ||
{{#each properties}} | {{#each properties}} | ||
<div class="property-container {{#if this.gmOnly}}gm-only{{/if}}”> | <div class="property-container {{#if this.gmOnly}}gm-only{{/if}}”> | ||
Line 154: | Line 156: | ||
The example above will render into HTML as shown here for GM only properties: | The example above will render into HTML as shown here for GM only properties: | ||
<syntaxhighlight lang="handlebars" line | <syntaxhighlight lang="handlebars" line> | ||
<div class="property-container gm-only”> | <div class="property-container gm-only”> | ||
<div class="property-label"> | <div class="property-label"> | ||
Line 171: | Line 173: | ||
In general, extra whitespace in HTML is ignored. However, such whitespace within the {{code|href}} attribute of an anchor element effectively changes the URL! So there ''must'' be a way to trim such whitespace from the output, and... there is. Note the use of the {{code|~}} character in the following snippet. When you see a tilde at the beginning of a handlebars helper, it means whitespace in front of the helper's output should be removed. Similarly, a tilde at the tail of a helper means to trim whitespace from the end of the helper's output. | In general, extra whitespace in HTML is ignored. However, such whitespace within the {{code|href}} attribute of an anchor element effectively changes the URL! So there ''must'' be a way to trim such whitespace from the output, and... there is. Note the use of the {{code|~}} character in the following snippet. When you see a tilde at the beginning of a handlebars helper, it means whitespace in front of the helper's output should be removed. Similarly, a tilde at the tail of a helper means to trim whitespace from the end of the helper's output. | ||
<syntaxhighlight lang="handlebars" line | <syntaxhighlight lang="handlebars" line> | ||
{{#each properties~}} | {{#each properties~}} | ||
<div class="property-container {{#if this.gmOnly}}gm-only{{/if}}”> | <div class="property-container {{#if this.gmOnly}}gm-only{{/if}}”> | ||
Line 188: | Line 190: | ||
The above would generate slightly different HTML. In the output, below, note how the first nested {{code|div}} has its closing tag on a separate line? That's because the preceding {{code|{{/if}}}} block didn't have a trailing tilde. | The above would generate slightly different HTML. In the output, below, note how the first nested {{code|div}} has its closing tag on a separate line? That's because the preceding {{code|{{/if}}}} block didn't have a trailing tilde. | ||
<syntaxhighlight lang="handlebars" line | <syntaxhighlight lang="handlebars" line> | ||
<div class="property-container gm-only”> | <div class="property-container gm-only”> | ||
<div class="property-label"><!-- this.shortName or this.displayName as a fallback --> | <div class="property-label"><!-- this.shortName or this.displayName as a fallback --> |
Revision as of 21:33, 28 December 2023
Languages: English
INTERMEDIATE
THIS IS AN INTERMEDIATE ARTICLE
Tutorial for Building a Custom Stat Sheet
MapTool 1.14.3 has added the ability to define your own pop-up stat sheets using Handerbars, HTML, and CSS (and JavaScript!).
The new stat sheets are part of the add-on functionalty which is replacing lib:
tokens. This is essentially my CliffNotes companion to the technical information at Stat Sheet.
Configuration
To be recognized, the Add-on needs to have the file stat_sheets.json
in its root directory. This file is a JSON object; the key statSheets
contains an array of objects defining each stat sheet. For example:
{
"statSheets": [
{
"name": "Rev-Smoked-Glass",
"description": "Glass",
"propertyTypes": [“Basic”, “monster”],
"entry": "sheets/rev.glass.hbs"
}
]
}
The above contents of stat_sheets.json
only defines a single stat sheet (named Rev-Smoked-Glass
) because there is only a single JSON object in the array.
- description - appears in the dropdown list that is part of the MapTool user interface on the Config tab of the Token Editor dialog.
- propertyTypes - specifies the list of token types (see Introduction to Properties#Changes_in_1.14.0+) that this stat sheet can be used with. Leave it empty to mean "all" token types.
- entry - the location of the Handlebars template file that defines this stat sheet. It is relative to the
/library/public
directory inside the Add-on.
Handlebars Templates
Handlebars is a templating language commonly used within HTML pages. Its goal is to allow repetitive content to be created dynamically and/or conditionally without the author having to repeat themselves within the document.
See the Handlebars web site for full details.
Within a MapTool Add-on, the Handlebars template has a filename extension of .hbs
. This template is read by MapTool and the result of the template replacement process is the HTML5 that is displayed to the user.
Here's an example of such a stat sheet. Look for blocks of text that begin with a double open-brace and end with a double close-brace. Those are replaceable with the result of the embedded instructions.
In many cases, the embedded instructions are simply to replace the block with the value of a variable (such as {{portraitWidth}}
).
Also note that the MapTool Preferences allow the user to set their preferred size for the portrait image. Be polite when you create your own stat sheet and respect the user's preferences! (Note the use of {{portraitWidth}}
in the img
element in the example, below, circa line 21.)
If you look through the example, above, you'll find handlebars blocks that contain a single string -- a variable name. MapTool provides multiple predefined variable names when the template is invoked.
Comments
Comments can be added in two ways. First, you can use normal HTML comments of the form . They will be passed through handlebars and embedded inside the resulting HTML. There's not much need for these kinds of comments since you won't be able to see them within MapTool, but if you like the idea of using your desktop web browser to develop the stat sheet and move it into an Add-on only when you're done, you might like the HTML comments as your browser's debug panel should show them to you when you view the HTML.
Second, you can use handlebars comments. They start with {{!
and end with }}
. These are interpreted as comments by handlebars and, as comments, they are stripped from the resulting HTML.
Helpers
Handlebars provides "helpers" as well. Conditional logic for if
statements, loops, and much more. For a full list of helpers built into the MapTool implementation, see this GitHub issue.
For example, this block from the example shows how an if
statement can be embedded inside the template. If the expression is true
, the HTML div
element will be present in the output; if the expression is false
, the div
will be missing from the element.
In the example, the {{gmName}}
is a reference to a handlebars variable, one that is predefined by MapTool.
Conditionals
Here's an example of creating a loop. The variable properties
is predefined by MapTool and represents all properties that are visible to the current MapTool user (meaning that players will not be able to see properties that are GM only). (More info at Stat Sheet.)
Inside the loop, one property at a time will be available. The variable this
refers to that single property.
As you may guess, the {{#if}}
is the beginning of an if
block and it ends at the corresponding {{/if}}
. Else blocks can also be used, as shown.
The example above will render into HTML as shown here for GM only properties:
The only difference when the property is NOT GM only is that the class won't appear on the leading div
(likely causing a slightly different visual look for this row of property information).
In general, extra whitespace in HTML is ignored. However, such whitespace within the href
attribute of an anchor element effectively changes the URL! So there must be a way to trim such whitespace from the output, and... there is. Note the use of the ~
character in the following snippet. When you see a tilde at the beginning of a handlebars helper, it means whitespace in front of the helper's output should be removed. Similarly, a tilde at the tail of a helper means to trim whitespace from the end of the helper's output.
The above would generate slightly different HTML. In the output, below, note how the first nested div
has its closing tag on a separate line? That's because the preceding {{/if}}
block didn't have a trailing tilde.
Modifications
Handlebars provides many helpers that can modify the content of dsata before passing it through as HTML. For example, suppose you have a variable msg
that contains the string my answer is no
. There might be a situation where you want that string converted to a format that would work as a valid CSS class name. To do that, the spaces would need to be eliminated. Deleting them entirely can make the HTML difficult to read later, though, so handlebars provides the slugify
helper that replaces the spaces with hyphens.
would produce the following as output:
Note that the tildes on either end of the slugify
helper aren't really needed in this case -- extra spaces in the class
attribute will be ignored in the HTML.
Other Requirements
The handlebars template will need to include the following line in the <head>
block:
<link rel="stylesheet" href="lib://net.rptools.maptool/css/mt-stat-sheet.css" />
(There's no need for the usual ?cachelib=false
nonsense here, since the text being included will not change (except perhaps between versions of MapTool). This means there's no need to workaround the built in caching that the HTML engine employs.)
The container of your stat sheet, ie. the top-level HTML element that will enclose all of your visible content (likely a div
), must have id="statSheet"
so that MapTool can locate said element. It must also include statSheetLocation
amongst the list of elements for the class
attribute. (See Stat Sheet for more information on how one can use statSheetLocation
.)
Gotchas
- HTML
id
s are case-sensitive, as is pretty much anything inside quotes in HTML (such as class names or other attribute values). Make sure you use"statSheet"
and not"statsheet"
! - CSS has specificity rules. You may find that your rules are not overriding the built in rules. (This is one reason why I like building my HTML output in a regular file and viewing it with a web browser -- browsers have developer tools specifically designed to help with this kind of thing.) You can add
!important
to your rules to cause the rendering engine to always use them, but once you start down that path, it can be a slippery slope as you start to need it everywhere! Generally, using anid
to narrow down the scope of your CSS initially, then using class names for additional categories is sufficient. - The
&
CSS selector didn't work when I created the examples, but may be available by the time you read this. It doesn't hurt to try it and see. - Keep accessibility and theming in mind. What looks good on one platform may not look good on others. This is often a function of font selection and the typeface support within the rendering engine.
- Similar to the above, MapTool provides a set of predefined CSS variables to represent colors from the different themes. Check out Stat Sheet for a complete list.
- Be careful with sizing when using images. An image can look great at a particular size and/or scale, but the user gets to specify how big they want the portrait rendered in their Preferences so keep that in mind. (A user on a smaller resolution display may choose a very tiny portrait to save screen space for other things, for example.)
- Relative URLs start at the location of the handlebars template. Keep that mind when using
../
in a URL. Remember that absolute URLs, those start with/
, always begin their search starting at/library/public
. - Output from handlebars is normally HTML-escaped. So a variable containing
Bold
will literally display those characters! The word Bold will not be output! To prevent that mechanism, use a "triple-stash", ie.{{{this.displayName}}}
. - Animated images aren't on macOS (at the time of this writing). This is likely a limitation of the JavaFX rendering engine and should be expected to change in the future, as new releases of JavaFX come out.
- In fact, that's a good warning in general -- expect things to change! JavaFX is still a moving target and bugs are being found and squashed all the time. Doing something in as simple a way as possible is likely more future proof than taking advantage of the latest browser feature.
- When developing, use a
lib:
token to help fine tune things. Linking to the tokenlib:
macro allows you to tweak CSS and JS on-the-fly. For my Add-on developing, I used a tokenlib:aodev
containing macrosstatsheet.css
andstatsheet.js
, then included these two lines in the<head>
section of the handlebars template:
<link rel="stylesheet" href="lib://aodev/macro/statsheet.css?cachelib=false" />
<script delay src="lib://aodev/macro/statsheet.js?cachelib=false"></script>
- Test your stat sheet in each of the eight possible locations on a token. The goal is to use strange and "will never happen" values for everything, including really long and really short values that will screw up your layout. It's okay to use HTML tables when they are appropriate -- things that should be displayed in a table (like token properties, for example) should use tables. Tables can be helpful for folks with visual handicaps who are using screen readers. They can also be harmful when used to position elements on the screen when the data itself isn't tabular (such as when columns in a row are not related to each other and are only there for the visual layout). Use of CSS Grid and Flex can typically obviate the need for tables for layout purposes.