Skip to content

Data Binding

curveo edited this page May 12, 2026 · 5 revisions

Data Binding

TesseraUI supports dynamic content through a TesseraModel object passed at render time. Templates can read model values with {{ }} expressions, loop with v-for, and show/hide elements with v-if and v-show.


TesseraModel

Build a model from a flat key-value map:

TesseraModel model = TesseraModel.of(Map.of(
    "player.name",   player.getName().getString(),
    "player.level",  String.valueOf(playerLevel),
    "player.health", String.valueOf((int) player.getHealth()),
    "items.count",   String.valueOf(items.size())
));

All values are strings. Numeric comparisons in expressions work on the string representation.

Use TesseraModel.EMPTY when your template has no dynamic content.


Interpolation {{ }}

Reference model values directly:

<label>Hello, {{ player.name }}!</label>
<label>Level: {{ player.level }}</label>

Concatenation

<label>{{ player.name }} — Level {{ player.level }}</label>

Ternary expression

<label>{{ items.count == 0 ? "Empty" : items.count + " items" }}</label>

Supported operators: ==, !=, >, <, >=, <=.

Translation in expression

<label>{{ items.count == 0 ? t:ui.mymod.empty : items.count + " " + t:ui.mymod.items }}</label>

Use : (with spaces) as the ternary separator when a t: key is involved.


Loops — v-for

Repeat a block for each item in a list.

Model side

Pass the count and per-item fields:

TesseraModel model = TesseraModel.of(Map.of(
    "items",             String.valueOf(items.size()),   // count
    "item.name.0",       items.get(0).getName(),
    "item.rarity.0",     items.get(0).getRarity(),
    "item.name.1",       items.get(1).getName(),
    "item.rarity.1",     items.get(1).getRarity()
));

Template side

<col v-for="item in items">
  <label>{{ item.name }}</label>
  <badge>{{ item.rarity }}</badge>
</col>

item is the loop variable. Inside the template, {{ item.name }} resolves to item.name.<index> from the model.

Nested access

<col v-for="slot in hotbar">
  <label>{{ slot.name }}</label>
  <label>× {{ slot.count }}</label>
</col>

Conditional rendering — v-if

Completely removes the element and its layout space when the expression is falsy:

<row v-if="player.isAdmin">
  <button onclick="openAdmin">Admin panel</button>
</row>

The element is not rendered and takes no space in the layout when the condition is false.

<!-- Numeric comparison -->
<label v-if="items.count > 0">{{ items.count }} items available</label>

<!-- String equality -->
<badge v-if="player.rank == admin">Admin</badge>

Conditional visibility — v-show

Keeps the element in the layout (space is preserved) but hides it:

<label v-show="player.isOnline">● Online</label>

Use v-show when you want to avoid layout shifts on state changes. Use v-if when the element should never reserve space.


Attribute binding

Bindings work in attributes too:

<img src="{{ player.avatarPath }}"/>
<item-slot item="{{ player.heldItem }}"/>
<input value="{{ config.defaultName }}"/>

Model helpers

Building from a list

Map<String, String> data = new HashMap<>();
data.put("entries", String.valueOf(entries.size()));
for (int i = 0; i < entries.size(); i++) {
    data.put("entry.name."  + i, entries.get(i).getName());
    data.put("entry.count." + i, String.valueOf(entries.get(i).getCount()));
}
TesseraModel model = TesseraModel.of(data);

Lambda model

For dynamic resolution:

TesseraModel model = key -> {
    if (key.equals("time")) return LocalTime.now().toString();
    return null;
};

Clone this wiki locally