Skip to content

Recipe: a form ​

Lay the form out in XML and bind each input to a ref. bind:value, bind:selected and bind:checked on a plain ref name are two way, so typed values land in your refs with no code. See Bindings and events.

ts
import { Component, Controller, DEFAULT_STAGE, Ref, Screen, TemplateRegistry } from "willowui";

const TEMPLATE: string = `
<Panel layout="vbox" spacing="10" padding="20">
    <Label text="New character" fontSize="20" />
    <TwoColumnLayout leftWidth="110" gap="12" rowSpacing="8">
        <Label text="Name" />
        <TextInput bind:value="name" width="220" placeholder="Hero" />
        <Label text="Class" />
        <Select options="Warrior,Mage,Rogue" size="220,32" bind:selected="classIndex" />
        <Label text="Hardcore" />
        <Checkbox text="One life only" bind:checked="hardcore" />
    </TwoColumnLayout>
    <Button text="Create" on:click="create" bind:enabled="name != ''" />
    <Label bind:text="status" />
</Panel>`;

const CLASSES: string[] = ["Warrior", "Mage", "Rogue"];

class NewCharacter implements Controller {
    name: Ref<string> = new Ref<string>("");
    classIndex: Ref<int> = new Ref<int>(0);
    hardcore: Ref<boolean> = new Ref<boolean>(false);
    status: Ref<string> = new Ref<string>("");

    onCreate(screen: Screen): void {
        const reg: TemplateRegistry = new TemplateRegistry();
        reg.registerXML("form", TEMPLATE);
        const root: Component = reg.instantiate("form", this, screen);
        screen.add(root);
    }
    onUpdate(dt: float): void {}
    onDispose(): void {}

    lookupRef(path: string): any {
        if (path == "name") { return this.name; }
        if (path == "classIndex") { return this.classIndex; }
        if (path == "hardcore") { return this.hardcore; }
        if (path == "status") { return this.status; }
        return null;
    }
    callMethod(method: string): boolean {
        if (method == "create") {
            const mode: string = this.hardcore.peek() ? " (hardcore)" : "";
            this.status.set("Created " + this.name.peek() + " the " + CLASSES[this.classIndex.peek()] + mode);
            return true;
        }
        return false;
    }
}

DEFAULT_STAGE.setScene(engine.root2d);
DEFAULT_STAGE.add(new Screen().withController(new NewCharacter()));
App.onFrame((dt: float): void => { DEFAULT_STAGE.update(dt); });

Notes:

  • bind:enabled="name != ''" disables the button while the name is empty. Two strings compare as text (see Expressions).
  • Validation messages fit an InputField (setValidationState, setValidationMessage) instead of a bare TextInput.