Views, SubViews, and Children
Why This Matters
Hosanna uses a declarative view tree. You describe state and return view structs; the framework builds real views, wires parent/child relationships, and mounts native renderers.
Key Concepts
- View: A virtual object with state that renders through a platform renderer (Roku SceneGraph, web DOM shim). Backed by
BaseView<T>
. - SubViews: Views created by a composite view’s
getViews()
result. They are declared children and tracked insubViews
(flat list) andsubViewsById
. - Children: The on-screen mounted hierarchy. Managed via
addChild/insertChild/removeChild
and tracked inchildren
andchildrenById
.
SubViews are the declared structure from getViews()
. Children are the mounted, on-screen structure. Most composite views have both.
How Views Are Built
- You update state and return structs from
getViews()
. - Hosanna inflates structs into views via
buildView()
. - Each view
onMount()
creates a renderer and becomes a child of its parent. - IDs are resolved and stored for fast lookup.
Sequence
IDs and Auto-Generation
- You can set
id
on any struct. If omitted, Hosanna auto-generates hierarchical IDs based on position. - IDs are stable within a render pass and used for reconciliation and lookups.
Assign explicit IDs to views you need to access later (focus, bounds, animations). Let others auto-generate.
How IDs Are Applied
- During
applyStateChanges
, Hosanna callsapplyIdsToViewStructs()
to ensure each struct has a unique ID. - Auto IDs follow a parent-index pattern like
__auto-0-2
when not specified.
Working with SubViews and Children
getSubView<T>(id, deep?)
looks up declared subViews (optionally recursive).getChildView<T>(id)
returns mounted children by ID.getSubViewRenderer<T>(id)
returns a subView’s renderer if mounted.
Use getSubView
for declaration-time relationships and getChildView
when you need the mounted instance.
Generics and Safety
APIs accept generics so you get type-safe results:
const label = this.getSubView<Label>('title');
const grid = this.getChildView<GridGroup>('results');
const renderer = this.getSubViewRenderer<ISGNGroup>('thumbnail');
Lookups can return undefined. Guard before use, especially during initial mount.
Class Relationships
Getting Views and Renderers
- Views expose
renderer?: R
. It is present when mounted. - Use
addSubView/insertSubView/removeSubView
to manage declared subViews. - Use
addChild/insertChild/removeChild
to manage mounted hierarchy. viewOwner
links a subView back to its creator.
onMount()
calls createRenderer()
and attaches it to the parent’s renderer. On web, renderers are fake SG nodes; on Roku they are SceneGraph nodes.
Focus and Layout Tips
- Composite views act as focus groups and can set an initial focus via
isInitialFocus
on a child struct. layoutChildren()
positions mounted children using theirtranslation
,width
, andheight
.- Use
getBoundsInParent(view)
orgetBoundsInApp()
when you need math-friendly rectangles.
Use setFocusedSubview(id)
to hint where focus should land when children change.
Troubleshooting
- If a lookup fails, verify the ID is actually present on the struct in the latest
getViews()
output. - Ensure the view is mounted before touching its
renderer
. - Prefer explicit IDs on interactive views to avoid auto-ID shifts when order changes.
⚙️ Key APIs
getSubView<T>(id, deep?)
: Find a declared subView by ID; deep search optional.getChildView<T>(id)
: Find a mounted child by ID.getSubViewRenderer<T>(id)
: Get a subView’s renderer.addChild/insertChild/removeChild
: Manage mounted hierarchy.addSubView/insertSubView/removeSubView
: Manage declared subViews.
Don’t change state inside getViews()
. Compute structure only; set state before/after.