To make things on your character sheet rollable, you'll need to add a class that you can listen for in your sheet's activateListeners() method. In the Boilerplate System, ability rolls have an example of this with a rollable class. Let's look at the ability modifier line from actor-sheet.html:
<span class="ability-mod rollable" data-roll="d20+@abilities.{{key}}.mod" data-label="{{ability.label}}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
We've added a rollable class along with a few different attributes for data-roll and data-label that our listener can then use to create the rolls. These data attributes aren't strictly necessary - there's many ways to set things up with a mix of dataset properties like here as well as deriving in the javascript event handler.
The HTML Dataset property
The MDN article on the dataset property is helpful for understanding how data is passed between the HTML and your javascript.
The formula in this example includes @abilities.{{key}}.mod, which would compute as @abilities.str.mod, for example. This should follow the same format if you were to write out inline rolls in chat or in macros, because in the roll method we're about to make, we're passing it this.actor.data.data just like core does for those scenarios.
Finally, the rollable class also includes some lightweight CSS for it to turn the mouse into a pointer and add a red text shadow on hover or focus.
Next, let's take a look at actor-sheet.js where we've overridden the ActorSheet class. We need to add a new click listener inside our ActorSheet's activateListeners() method:
// Rollable abilities.
html.on('click', '.rollable', this._onRoll.bind(this));
This will call a custom _onRoll() method on click, and the call to bind ensures that our context of this is preserved (otherwise the callback will change this to the clicked element). Because that method doesn't exist, we need to go down towards the end of our class and add a new method after the _onItemCreate() method:
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
_onRoll(event) {
event.preventDefault();
const element = event.currentTarget;
const dataset = element.dataset;
// Handle item rolls.
if (dataset.rollType) {
if (dataset.rollType == 'item') {
const itemId = element.closest('.item').dataset.itemId;
const item = this.actor.items.get(itemId);
if (item) return item.roll();
}
}
// Handle rolls that supply the formula directly.
if (dataset.roll) {
let label = dataset.label ? `[ability] ${dataset.label}` : '';
let roll = new Roll(dataset.roll, this.actor.getRollData());
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
});
return roll;
}
}
First, we're preventing other click behaviors (such as if this were an <a> tag), and then we're grabbing any data attributes that were included on that element.
Our first scenario is when there's a rollType attribute, which right now is only used for item rolls. We navigate the DOM to find the item, fetch it, and then call the item's roll method.
Alternatively, if there is a dataset.roll attribute (which we only use if we're also going to provide the formula), we'll use that to build the roll. First thing is getting the label attribute if there is one.
To create a roll, we use new Roll('formula'), and we've already written out the formula in our Handlebars template. The second argument of Roll() is the object to use for its data, so in this case we want to pass along the actor's data attributes.
And with that we have everything we need to send it to chat. Creating a new roll doesn't actually roll it, but calling the toMessage() method will roll before sending that result to to chat. We want this to reference the actor token as the speaker for the chat message, so we set the speaker and flavor properties to match what we need.
And with that, we've successfully made rollable attributes!