Data Binding

RmlUi’s data binding keeps a document in sync with application state without any element look-ups: the markup refers to named variables, and the library re-evaluates the affected parts of the DOM when a variable is marked dirty. The acron.rmlui module backs those variables with Python callables.

A data model is created on the context and must exist before any document that refers to it is loaded:

state = {"name": "Player One", "hp": 80, "items": ["axe", "rope"]}

model = context.create_data_model("hud")
model.bind_func("name", lambda: state["name"])
model.bind_func("hp", lambda: state["hp"],
                lambda value: state.__setitem__("hp", int(value)))
model.bind_list("items", lambda: state["items"])
model.bind_event_callback("use", lambda index: use_item(int(index)))

doc = context.load_document("ui/hud.rml")
doc.show()

The document names the model on an element and uses the variables below it:

<div data-model="hud">
  <p>{{ name }} - {{ hp }} hp</p>
  <input type="range" min="0" max="100" data-value="hp"/>
  <ul>
    <li data-for="item, i : items" data-event-click="use(i)">{{ item }}</li>
  </ul>
  <p data-if="items.size == 0">Inventory empty.</p>
</div>

Kinds of binding

RmlDataModel.bind_func()

A scalar variable (bool, int, float or str) read through a getter, and optionally written through a setter when the document changes it (data-value on a form control, data-checked, …).

RmlDataModel.bind_list()

A list of scalars for data-for. The getter returns the whole list; {{ items.size }} reads its length.

RmlDataModel.bind_dict_list()

A list of records, each a dict of scalars, for data-for with per-field access: data-for="row : rows" then {{ row.name }}, data-if="row.visible" and so on.

RmlDataModel.bind_event_callback()

A callable for data-event-<name> attributes. The arguments written in the expression are converted from RmlUi’s variants and passed positionally, so data-event-click="join(host, port)" calls join(host, port) with the variables’ current values.

Each name can be bound once per model; the bind_* methods return False for a name that is already taken.

Keeping the document current

Binding does not watch your Python objects. After changing state, tell the model what changed with RmlDataModel.dirty_variable(), or dirty_all() after a large update; the DOM is refreshed on the context’s next update, which the region performs every frame.

state["items"].append("torch")
model.dirty_variable("items")

Data expressions have no escape sequences: inside a double-quoted attribute, write string literals with single quotes, data-event-click="go('menu')".

Models are removed with RmlContext.remove_data_model(), and go away with their context. A handle kept past that point is harmless – its methods become no-ops – but RmlDataModel.is_valid() tells.