Skip to content

XML templates ​

An XML template describes a tree of widgets in markup. willowui compiles it once, then builds live widgets from it as often as you like. Templates are a good fit for screens with many controls (forms, settings, inventories, editors), where code would be mostly new, setSize and addChild.

xml
<Panel layout="vbox" spacing="12" padding="20">
    <Label bind:text="'Clicks: ' + clicks" fontSize="20" />
    <Button text="Click me" on:click="add" />
    <Label text="That is a lot of clicks." ui:show="clicks > 4" />
</Panel>

Every element is a widget. Plain attributes configure it. Three prefixes connect it to your code:

PrefixExampleMeaning
bind:bind:text="'Clicks: ' + clicks"Evaluate an expression and keep the attribute in step with it.
on:on:click="add", on:click="select(item)"Call a method on the controller when the widget fires the event, with optional arguments.
ui:ui:show="clicks > 4"A directive: show, hide or repeat the element.

The pieces ​

PieceWhat it is
TemplateRegistryCompiles templates, stores them by name and instantiates them.
The controllerYour object. Its lookupRef(path) answers names used in expressions, and its callMethod(name, args?) runs event handlers.
instantiate(name, controller, screen)Builds the widget tree and returns its root component.

A complete app ​

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

const TEMPLATE: string = `
<Panel layout="vbox" spacing="12" padding="20">
    <Label bind:text="'Clicks: ' + clicks" fontSize="20" />
    <Button text="Click me" on:click="add" />
    <Label text="That is a lot of clicks." ui:show="clicks > 4" />
</Panel>`;

class CounterScreen implements Controller {
    clicks: Ref<int> = new Ref<int>(0);

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

    // Names used in bind: and ui: expressions.
    lookupRef(path: string): any {
        if (path == "clicks") { return this.clicks; }
        return null;
    }

    // Names used in on: attributes. Return true when the name was handled.
    callMethod(name: string): boolean {
        if (name == "add") {
            this.clicks.set(this.clicks.peek() + 1);
            return true;
        }
        return false;
    }
}

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

How it works:

  1. registerXML parses and compiles the text. A mistake (an unknown tag, a bad expression) throws a TemplateError with the line and column.
  2. instantiate builds the widgets. For each bind: it starts a reactive effect, so when clicks changes, the label text and the ui:show visibility update on the next frame.
  3. Clicking the button calls callMethod("add").

Because the screen is passed in, its bindings are stopped when the screen is disposed.

Loading templates from files ​

Keep templates in .xml files and read them with the fs system (add "fs" to systems and permissions: { fs: true } to thicket.json):

ts
import { Files } from "fs";
const templateXML: string = await Files.readText(moduleDir + "/template.xml");

To ship a template without parsing XML at start-up, compile it to the binary form. To edit a template while the app runs, use hot reload.

Registry settings ​

MethodUse
setFonts(fonts, defaultFamily)A map from the names used in font="..." to font families, and the default family. Pass null for no map.
setFontSize(size)The default font size for text widgets (14 when not set). Each element can override it with fontSize.
setTheme(theme)Register a compiled theme's custom variant names, so variant="card" resolves.
setThemeJSON(json)Compile a theme from JSON text, register its variants, and keep the text so templates can patch it with an inline <Theme>.
putAsset(path, bytes)Hand over image bytes for Image src="..." and AnimatedImage gif="...". Setters cannot read files, so you load the bytes first.
registerWidget(tag, factory, setters)Add your own tag. See Custom widgets.
registerBinary(name, bytes)Register a precompiled .xmlui template.
instantiateStatic(name, screen)Instantiate with no controller. Bindings and events then do nothing.
get(name)The compiled tree (IRNode), or null.

newTemplateRegistryWithFont(path, size) makes a registry whose default font is the TTF or OTF file at path.

In this section ​