The ActorSheet class is the class associated with our actor's character sheets. Let's take a look at what Boilerplate System does:
Every sheet needs to define its default options.
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
export class BoilerplateActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["boilerplate", "sheet", "actor"],
template: "systems/boilerplate/templates/actor/actor-sheet.html",
width: 600,
height: 600,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
});
}
First, we're exporting the class (don't forget to rename yours!) and defining the default options for the sheet. In the defaultOptions()
method we need to return a mergedObject of the default options from the main ActorSheet class and our customizations. Those customizations are:
- classes: an array of CSS classes to apply to the character sheet.
- template: the path to the Handlebars HTML template that we'll use for this character sheet. This needs to be relative to Foundry's root, so you need to include
systems/MYSYSTEMNAME
in the start of the path. - width: default window width
- height: default window height
- tabs: Use this to define your tabs on the character sheet. This should match up with what you've created in your HTML template, but you must have a nav selector CSS class, content selector CSS class, and choose a default/initial tab.
Much like the Actor class' prepareData()
method, we can use the getData()
method to derive new data for the character sheet. The main difference is that values created here will only be available within this class and on the character sheet's HTML template. If you were to use your browser's inspector to take a look at an actor's available data, you wouldn't see these values in the list, unlike those created in prepareData().
/* -------------------------------------------- */
/** @override */
getData() {
const data = super.getData();
data.dtypes = ["String", "Number", "Boolean"];
for (let attr of Object.values(data.data.attributes)) {
attr.isCheckbox = attr.dtype === "Boolean";
}
return data;
}
There's not a whole lot happening in this example of getData(), as we're just checking to see what kind of attribute this is and setting the type to Boolean if it's a checkbox. You may not need to use this, so feel free to skip over it if you have everything you need from the prepareData() method instead.
If you want your sheet to be interactive, this is where that needs to happen. The activateListeners()
method is where you can execute jQuery on your sheet to do things like create rollable skills and powers, add new items, or delete items. This method is passed an html
object that behaves much like $('.sheet')
would if you were trying to run jQuery logic on your sheet in your browser's console.
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Add Inventory Item
html.find('.item-create').click(this._onItemCreate.bind(this));
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("itemId"));
item.sheet.render(true);
});
// Delete Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.actor.deleteOwnedItem(li.data("itemId"));
li.slideUp(200, () => this.render(false));
});
}
The Boilerplate System includes three examples of click listeners, one to create new items, one to edit existing items, and one to delete items. We'll revisit this later in the tutorial to add a listener for rollable attributes.
The first click listener we added was to create new items, but notice that it uses this._onItemCreate.bind(this)
rather than calling its code directly like the edit and delete listeners do. You can follow that code pattern to break your listeners into custom methods to make your code more organized as it grows over time. For now, let's take a closer look at the _onItemCreate()
custom method:
/* -------------------------------------------- */
/**
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
* @param {Event} event The originating click event
* @private
*/
_onItemCreate(event) {
event.preventDefault();
const header = event.currentTarget;
// Get the type of item to create.
const type = header.dataset.type;
// Grab any data associated with this control.
const data = duplicate(header.dataset);
// Initialize a default name.
const name = `New ${type.capitalize()}`;
// Prepare the item object.
const itemData = {
name: name,
type: type,
data: data
};
// Remove the type from the dataset since it's in the itemData.type prop.
delete itemData.data["type"];
// Finally, create the item!
return this.actor.createOwnedItem(itemData);
}
We're doing a few different things here. First, we're getting the element (header) that was clicked, and then we're finding out what type of item it was. In this case that type is item
, but it could also be something like feature
or spell
. After that, we're grabbing any custom data attributes on the element that was clicked and using them to create a new itemData
object. Finally, we're passing all of that over to this.actor.createdOwnedItem(itemData)
to create the item on this actor.
And since these examples have all been the individual sections, don't forget your closing bracket for the class itself!
}