#Custom elements and the DocumentSheet update flow
1 messages · Page 1 of 1 (latest)
Okay, so, I have a sheet, whose template looks like this (with extra bits omitted):
<form>
<ability-score-field
name="system.scores.str.value"
value="{{actor.system.scores.str.value}}"
modifier-value="{{actor.system.scores.str.mod}}"
data-dtype="Number"
>
Field A
</ability-score-field>
<ability-score-field
name="system.scores.int.value"
value="{{actor.system.scores.int.value}}"
modifier-value="{{actor.system.scores.int.mod}}"
data-dtype="Number"
>
Field B
</ability-score-field>
</form>
The sheet class looks like this:
export default class SomeSheetClass extends ActorSheet {
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
template: `path/to/some/template.hbs`,
});
}
_onChangeInput(e: any) {
// ... whatever transformations might need to happen
super._onChangeInput(e);
}
activateListeners(html: JQuery<HTMLElement>) {
super.activateListeners(html);
html.on("change", "ability-score-field", (e) => this._onChangeInput(e));
}
}
My custom element looks like this:
export default class AbilityScoreField extends HTMLElement {
static formAssociated = true;
value: string = "";
name: string = "";
#internals: ElementInternals;
#shadowRoot: ShadowRoot;
constructor() {
super();
this.#internals = this.attachInternals();
this.#shadowRoot = this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.#render();
this.#shadowRoot
.querySelector("input")
?.addEventListener("change", (e) => {
this.onInput(e);
});
}
get #label() {
const label: HTMLLabelElement = document.createElement("label");
label.setAttribute("for", this.id);
const slot: HTMLSlotElement = document.createElement("slot");
label.append(slot);
return label;
}
get #input() {
const input: HTMLInputElement = document.createElement("input");
input.setAttribute("type", "text");
input.setAttribute("name", this.getAttribute("name") || "");
input.setAttribute("id", this.getAttribute("id") || "");
input.setAttribute(
"value",
this.getAttribute("value")?.toString() || ""
);
input.toggleAttribute("readonly", this.hasAttribute("readonly"));
input.toggleAttribute("disabled", this.hasAttribute("disabled"));
return input;
}
#render() {
this.#shadowRoot.append(this.#label as Node, this.#input);
}
onInput(e: Event) {
const oldValue = parseInt(this.getAttribute("value") || "", 10);
let newValue = parseInt((e.target as HTMLInputElement).value || "", 10);
if (newValue < 0) newValue = 0;
if (Number.isNaN(newValue)) newValue = oldValue;
this.setValue(newValue.toString());
}
setValue(newValue: string = "") {
this.value = newValue;
this.setAttribute("value", newValue);
this.#internals?.setFormValue(newValue);
this.dispatchEvent(new Event("change", { bubbles: true }));
}
}
customElements.define("ability-score-field", AbilityScoreField);
Everything here works fine -- editing the field updates the document as expected. But it removes focus from the sheet.
Right, because it completely destroys and recreates the DOM elements
Which I figured, given how the next field in tab order gets focus when you tab out of a changed field; your cursor ends up at the start of the field, rather than selecting the content of the field.
That's the thing I'd like to mimic with my setup -- when I change focus, at least get the cursor in the next/previous field in tab order.
you would have to add some code to record what to focus after rendering again
Yep! I was just wondering how Foundry's doing that, so I can try to stay hooked into how core's doing things.