Declarative UI
The UI base class provides the declarative foundation for building portal dashboards. You define elements as class attributes, and the framework handles instantiation, tag binding, schema serialisation, and interaction routing.
Class Definition
UI subclasses use __init_subclass__ parameters to configure top-level application appearance in the portal.
from pydoover.ui import UI, NumericVariable, Colour
class MyUI(
UI,
display_name="My Dashboard",
icon="thermometer",
colour=Colour.blue,
position=10,
default_open=True,
hidden=False,
):
temperature = NumericVariable("Temperature", value=22.5)
Subclass Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
display_name | str or Tag | "$config.app().APP_DISPLAY_NAME" | The title shown in the portal. Defaults to the config-provided app name. Can be a Tag descriptor for a dynamic, tag-bound title that updates at runtime. |
hidden | bool or str | "$config.app().hidden:boolean:false" | Whether the app section is hidden. Can be a $config reference for dynamic visibility. |
position | int or str | "$config.app().dv_app_position:number:100" | Ordering position among applications on the device page. |
default_open | bool or str | "$config.app().dv_app_default_open:boolean" | Whether the app section starts expanded. |
icon | str or None | None | Icon identifier for the application header. |
colour | str or None | None | Colour for the application header. |
When a str value starts with $, it is treated as a variable reference resolved at schema generation time.
Tag Binding
UI elements display live values by binding to tags through tag_ref() (aliased as bind_tag()). This creates a UITagBinding that serialises to a lookup string the portal evaluates at render time.
Using tag_ref()
from pydoover.ui import tag_ref, NumericVariable
from pydoover.tags import Tags, Number
class MyTags(Tags):
temperature = Number(default=0.0)
class MyUI(UI):
# Bind to a declared Tag definition
temp = NumericVariable(
"Temperature",
value=MyTags.temperature,
)
Tag definitions (Tag, BoundTag) are automatically resolved to UITagBinding during serialisation. You can pass them directly as value without wrapping in tag_ref().
Use tag_ref() when you need to reference a tag by string name (e.g., from a different app or when you don't have a Tags class):
| Input | Behaviour |
|---|---|
str (tag name) | Creates a binding from the name; requires tag_type and optionally default_value |
Tag (class-level declaration) | Extracts tag_name, tag_type, and default from the declaration |
BoundTag (runtime proxy) | Extracts tag_name, tag_type, and default from the bound tag |
UITagBinding | Returns a deep copy of the existing binding |
str | Creates a binding with the given tag name; tag_type and default_value can be passed as additional arguments |
Direct Tag Binding
You can also pass Tag class attributes directly to any UI element property that accepts a tag reference (value, hidden, display_name, etc.). The framework detects Tag and BoundTag instances and converts them to lookup strings automatically — tag_ref() wrapping is optional.
from pydoover.tags import Tag, Tags
from pydoover import ui
class MyTags(Tags):
temperature = Tag("number", default=0.0)
show_advanced = Tag("boolean", default=False)
class MyUI(ui.UI, display_name=MyTags.status_string):
temp = ui.NumericVariable(
"Temperature",
value=MyTags.temperature, # Tag passed directly
hidden=MyTags.show_advanced, # Also works for hidden
)
This form is equivalent to wrapping each reference in tag_ref() and produces the same serialised schema. Use whichever style reads more naturally — direct binding is common when all references point to tags declared on the same Tags class.
UITagBinding Internals
A UITagBinding holds three pieces of information:
- tag_name -- the tag key, optionally prefixed with the app key
- tag_type -- the expected value type (mapped to portal types:
"number","string","boolean","array","object") - default_value -- the fallback value when the tag has no current value
The to_lookup() method serialises the binding into a string the portal understands:
binding = tag_ref("temperature", tag_type="number", default_value=0.0)
print(binding.to_lookup())
# "$tag.app().temperature:number:0.0"
The $tag.app() prefix tells the portal to look up the value from the current app's tag namespace. The type and default are appended after colons.
Config References
In addition to tag bindings, UI properties can reference config values using the $config.app().KEY[:type[:default]] syntax. This is primarily used for the subclass parameters but can appear in any string-valued property.
class MyUI(
UI,
display_name="$config.app().APP_DISPLAY_NAME",
hidden="$config.app().hide_dashboard:boolean:false",
):
pass
When to_schema(resolve_config=True) is called, these references are resolved against the application's config schema, replacing the placeholder strings with actual values.
Lifecycle Methods
setup()
Override setup() to mutate the UI instance before it is bound and installed. This is where you add or remove elements based on configuration.
class MyUI(UI):
base_display = NumericVariable("Base Value", value=0)
async def setup(self):
if self.config.show_advanced.value:
self.add_element(
NumericVariable("Advanced Metric", value=tag_ref("adv_metric"))
)
If you do not override setup(), the framework detects this via the is_static property and may optimise schema generation by skipping the setup phase.
to_schema()
Generates the complete JSON schema for the UI, including all elements and their serialised properties.
schema = my_ui.to_schema() # Returns a dict with displayString, children, hidden, position, etc.
Pass resolve_config=False to keep $config references as-is (useful for exporting templates).
export()
Writes the UI schema to a JSON file, keyed by application name.
from pathlib import Path
my_ui.export(Path("schemas.json"), app_name="my_app")
Dynamic Element Management
Elements can be added or removed at runtime, typically during setup().
add_element()
Adds an element to the UI instance. The element must be an Element subclass. It becomes accessible as an attribute on the UI instance.
from pydoover.ui import Button
async def setup(self):
btn = Button("Emergency Stop", icon="alert")
self.add_element(btn)
# Now accessible as self.emergency_stop
remove_element()
Removes an element by name. Raises KeyError if the element does not exist.
self.remove_element("emergency_stop")
Collecting Interactions
The get_interactions() method recursively collects all Interaction elements from the UI tree, including those nested inside containers. This is used by the framework to register interaction handlers.
interactions = my_ui.get_interactions()
# {"restart_btn": <Button>, "mode_select": <Select>, ...}
bind_tags()
After setup, the framework calls bind_tags(tags) to walk the entire UI element tree and resolve any Tag references into UITagBinding objects. This converts declared tag references into the lookup strings that the portal can evaluate.
my_ui.bind_tags(app.tags)
You rarely need to call this yourself; the framework handles it during application startup.
Complete Example
from pydoover.ui import (
UI, NumericVariable, TextVariable, BooleanVariable,
Button, Select, FloatInput, Submodule,
Colour, Option, tag_ref,
)
from pydoover.tags import Tags, Number, Boolean, String
class MyTags(Tags):
temperature = Number(default=0.0)
pump_running = Boolean(default=False)
mode = String(default="auto")
class DashboardUI(
UI,
display_name="Plant Monitor",
icon="factory",
colour=Colour.blue,
):
# Display elements with tag bindings
temp_display = NumericVariable(
"Temperature",
value=MyTags.temperature,
units="C",
precision=1,
)
pump_status = BooleanVariable(
"Pump Running",
value=MyTags.pump_running,
)
# Interactive elements
restart_btn = Button("Restart System")
mode_select = Select(
"Operating Mode",
options=[Option("Auto"), Option("Manual"), Option("Off")],
)
setpoint = FloatInput("Temperature Setpoint", min_val=0, max_val=100)
# Container grouping
diagnostics = Submodule(
"Diagnostics",
children=[
TextVariable("Firmware", value="v2.1.0"),
TextVariable("Uptime", value=tag_ref("uptime", tag_type="string")),
],
default_open=False,
)
Next Steps
- UI Overview -- introduction to the UI system
- Display Elements -- read-only variables and charts
- Interactive Elements -- buttons, sliders, and input controls
- UI Commands Manager -- interaction handling and the
@handler()decorator