Skip to main content

Fragment Data Bindings

Fragment bindings copy values from a DynamicCell item or FragmentView.itemContent into fields on the fragment's SceneGraph children. Use a direct binding when one data value maps to one field. Use a registered function binding when one field needs a small calculation that cannot be expressed as a computed value.

{
"cells": {
"poster": {
"$supportsDataMap": true,
"views": {
"base": [
{
"id": "art",
"subType": "Poster",
"uri": "${data.images[0].url}",
"width": 384,
"height": 216,
"opacity": 0.85,
"scale": [1, 1]
},
{
"id": "title",
"subType": "Label",
"text": "${data.title}",
"width": 384,
"translation": [0, 228]
}
],
"normal": {
"art": { "opacity": 0.85, "scale": [1, 1] }
},
"focused": {
"art": { "opacity": 1, "scale": [1.05, 1.05] }
}
}
}
}
}

The corresponding content is an ordinary object:

{
id: 'film-42',
title: 'North by Northwest',
images: [{ url: 'pkg:/assets/posters/north-by-northwest.jpg' }],
}

Binding Requirements

A working direct or function binding has all of these properties:

  • The fragment sets $supportsDataMap: true.
  • The bound field is declared on a child in views.base.
  • That child has a non-empty, unique id.
  • The entire field value is one binding expression.
  • The host receives new content through CollectionView data updates, FragmentView.itemContent, or FragmentView.updateData(...).

Do not put ${data.*} or ${fn.*} expressions in views.normal, views.focused, or another status map. Status maps update existing child fields when the status changes; they are not compiled into the fragment data map. Keep bindings in views.base and keep status maps for values such as opacity, scale, color, and visibility.

Direct Data Bindings

Use ${data.path} to copy one value from the current content object:

{
"id": "metadata",
"subType": "Label",
"text": "${data.details.rating}"
}

The binding occupies the whole field. String interpolation is not supported:

{
"text": "Rating: ${data.details.rating}"
}

Build combined text in the content model, with a function binding, or with a computed value where its primitive operations are sufficient.

Nested Objects and Arrays

Dot-separated paths traverse nested objects:

{
"text": "${data.details.rating}"
}

One bracketed array index is normalized into a dotted numeric segment:

{
"uri": "${data.images[0].url}"
}

This is stored as images.0.url. The current parser normalizes only one bracket pair in an expression. For multiple array levels, use dotted numeric segments:

{
"text": "${data.matrix.0.1.label}"
}

Resolution-Specific Data

$RES$ inside a direct data path is replaced with the current AppConfig resolution suffix:

{
"uri": "${data.imageSet.$RES$}"
}

For an fhd configuration, the fragment reads imageSet.fhd. Supply the corresponding key in the content object for each supported resolution.

Missing Values and Updates

If a path does not exist, the runtime assigns undefined to the target field. An explicitly null terminal value is also collapsed to undefined; direct bindings do not preserve null as a SceneGraph field value. Binding syntax has no inline default or fallback operator. Normalize nullable or optional content before binding it when a SceneGraph field requires a concrete value.

Bindings are one-way assignments, not observers. Mutating a nested property on the existing content object does not by itself update a fragment. Send the updated item through the CollectionView data source, assign new FragmentView.itemContent, or call FragmentView.updateData(...).

Function Bindings

Use ${fn.name(literals)} when one field needs a registered calculation:

{
"id": "progress",
"subType": "Rectangle",
"height": 8,
"width": "${fn.progressWidth(320, true, 'compact')}"
}

Register the function before any fragment using that style is acquired:

import { AppUtils } from '@hs-src/hosanna-bridge-core/AppUtils';
import {
type ISpecificViewFragmentCallbackHandler,
ViewFragmentCallbackType,
} from '@hs-src/hosanna-ui/views/lib/AppConfig';
import type { IViewFragmentProvider } from '@hs-src/hosanna-ui/views/lib/ViewFragmentProvider';

const provider = AppUtils.resolve<IViewFragmentProvider>('viewFragmentProvider');

provider.registerViewFragmentCallback<ISpecificViewFragmentCallbackHandler>(
ViewFragmentCallbackType.OnSpecificViewDataChange,
'progressWidth',
(data, childView, field, args) => {
const fullWidth = args[0] as number;
return fullWidth * Number(data.progress ?? 0);
},
);

The handler receives (data, childView, field, args). Its return value is assigned to the bound field. Function arguments are literals parsed from AppConfig; supported argument forms are numbers, quoted strings, booleans, null, and undefined. Function arguments are not data-path or computed-value expressions—the handler already receives the complete content object.

Callback names are resolved when a fragment instance is created. If the handler has not been registered at that point, the function binding is skipped and its field is not updated. Register global fragment functions during app setup, before screens can create or prewarm fragments. Registering the missing name later does not retrofit a fragment that is already in use or in the pool.

Supported and Unsupported Forms

FormCurrent behavior
${data.path}Supported one-way assignment from the current content object.
${fn.name(literals)}Supported when name is registered as OnSpecificViewDataChange before fragment creation.
computed.nameSupported by the separate computed-value pipeline. It is an exact whole-field reference without ${...}.
{{constraint.name(...)}}Supported by the separate fragment-constraint pipeline on layout fields.
${view.path}Parsed into internal metadata, but runtime view binding is not implemented. Do not use it.
$(...)Observer/callback binding syntax is not implemented. Do not use it.
{{data.path}}Not a data-binding syntax. Use ${data.path}.
Text containing ${data.path}Interpolation is not supported. Bind the entire field instead.

See Fragment Computed Values for computed.name references and the Fragment Constraint Catalog for {{constraint.*}}.

Do Not Hand-Author Compiled Binding Maps

During AppConfig resolution, ${data.*}, ${fn.*}, and exact computed.name fields are removed from the static child style and recorded under the fragment's internal _dataMap.

Treat _dataMap.view, _dataMap.data, and _dataMap.fn as compiler output. Do not hand-author or patch those sections. Author the binding on the identified child in views.base and let AppConfig generate the maps. Explicit _dataMap.computed declarations and _dataMap.constraints rules are separate documented authoring surfaces.

Current $extends Limitation

Do not rely on a derived fragment inheriting ${data.*}, ${fn.*}, or exact computed.name field bindings from a $supportsDataMap base fragment. The current $extends resolution path can rebuild the derived data map without preserving the base fragment's compiled data, fn, and view sections.

Until that framework defect is fixed, put bound fields in each concrete leaf fragment. If a bound fragment must use $extends, redeclare the complete views.base array, including every binding, in the derived style and verify it on the target platforms. Do not work around the issue by copying generated _dataMap.view, _dataMap.data, or _dataMap.fn sections.

Troubleshooting

SymptomCheck
The literal ${data.title} appears on screenThe binding is in views.base, the child has an id, and $supportsDataMap is true.
A field becomes empty or invalidThe data path exists and resolves to a value accepted by that SceneGraph field. Missing paths and terminal null produce undefined.
A nested array value is missingUse a single bracket index or dotted numeric path segments for deeper arrays.
A function-bound field retains an old valueRegister the function before fragment creation and make sure the handler always returns the intended field value.
A derived fragment stops updatingRedeclare bindings in the concrete $extends leaf; inherited compiled binding maps are not currently reliable.
A status change replaces bound contentKeep data and function bindings in views.base; status maps should contain only status-specific overrides.

For fragment lifecycle callbacks such as onMount, onUnmount, onApplyViewStatus, and onDataChange, see Fragment Callbacks and Lifecycle.

Talk to us