docs
Loading...
Searching...
No Matches
ultralight::dom::data

Overview

Data-binding API that connects native C++ data to HTML and CSS markup.

#include <Ultralight/dom/data/Context.h>

Note
This API is a preview and may still change after 2.0.

Data bindings connect native C++ objects directly to page markup. Your HTML and CSS declare where values appear and which elements dispatch actions, while your data and application logic remain in native code.

You update your native objects during your normal update loop and call dom::data::Context::Sync() once per frame. The library reads every bound instance through its schema and sends only the values that changed in a single batch.

A data context binds native instances and synchronizes their fields with an attached View:

struct Player {
int health = 100;
int64_t gold = 0;
};
Player player;
dd::Context ctx = dd::Context::Create();
dd::Binding binding = ctx.Bind("player", player); // "player" in markup
if (ctx.AttachTo(view.get()))
view->LoadURL("file:///hud.html");
// Once per frame:
player.health -= 10;
ctx.Sync();

The page markup displays those values using placeholder syntax:

<p>Health: {{player.health}}</p>
<p>Gold: {{player.gold}}</p>

Common Tasks

Select a type based on the task you need to perform:

Task Type
Bind instances and sync pages dom::data::Context
Describe a type for markup dom::data::TypeTraits
Handle actions and edits dom::data::Binding
Skip unchanged data during sync dom::data::Model
Support custom value types dom::data::ValueTraits

Header and Namespace

The data-binding types live in <Ultralight/dom/data/Context.h> and aren't included by <Ultralight/DOM.h>. Including this header provides the full data-binding API.

The library provides dd as a namespace alias for ultralight::dom::data. Reference documentation and code examples use this alias for brevity (dd::Context, dd::Binding).

Core Rules

  • Most operations belong to the Context's home thread. You bind instances, register handlers, modify bound objects, and call dom::data::Context::Sync() on the thread that created the Context. Members safe to call from any thread say so in their documentation.
  • The library reads bound objects only during dom::data::Context::Sync() and never writes them. You can modify your instances between sync calls.
  • Page input arrives as requests that your handlers apply. User edits and button clicks don't modify your objects directly. Your dom::data::Binding::OnChange() and dom::data::Binding::OnAction() callbacks decide whether to store or reject each change.
  • The API reports errors through return values rather than C++ exceptions. Functions return false or empty handles when an operation can't be completed. If a handler, formatter, validator, or posted task throws, the library catches it, drops that call, and logs a warning.
See also
dom::data::Context, dom::data::Binding, dom::data::TypeTraits, dom::data::Schema()

Classes

class  ActionInfo
 Details of a delivered action (its name and the list row it fired from). More...
struct  ActionToken
 An action entry (the value Action() returns). More...
struct  AttachOptions
 Options for Context::AttachTo(). More...
class  Binding
 A handle that keeps an instance bound to pages and registers its input handlers. More...
class  Context
 Data-binding context for attached Views. More...
struct  EditableTag
 Marks a field the page may request changes to. More...
struct  EnumRange
 The range of values searched for an enum's enumerator names. More...
struct  FieldToken
 A field entry (the value Field() returns). More...
struct  InternalTag
 Marks a field or list that pages can't reach. More...
struct  ListToken
 A list entry (the value List() returns). More...
class  Model
 Handle passed to a type's static Sync function to control which fields are read during synchronization. More...
struct  OnlyLatestTag
 Marks an action whose pending emissions collapse into the newest one. More...
class  Snapshot
 Read-only access to the synchronized values of a described type. More...
struct  TypeTraits
 Traits template for describing a type to data bindings. More...
class  Value
 A generic value passed to data-binding formatters and handlers. More...
struct  ValueTraits
 Conversion rules that expose a C++ type as a primitive value in data bindings. More...
struct  VarToken
 A CSS custom property entry (the value Var() returns). More...

Concepts

concept  Bindable
 Whether or not T binds as a leaf value (ignoring const, volatile, and references), ie.
concept  BindableHolder
 Whether or not Context::Bind() can bind an instance through H.
concept  Described
 Whether or not T has a schema (ignoring const, volatile, and references), ie.
concept  ScalarPayload
 Whether or not P can be an action's payload without a payload struct (ignoring const, volatile, and references).

Enumerations

enum class  DumpAt { NextSync , Now }
 When Context::DumpSchema() writes its file. More...
enum class  AttachFlags : unsigned { None = 0 }
 The flags for Context::AttachTo() (the typed form of ULDOMDataContextAttachFlags). More...
enum  EntryFlags : uint8_t {
  kEntryFlags_Editable = 1 << 0 , kEntryFlags_Internal = 1 << 1 , kEntryFlags_OnlyLatest = 1 << 2 , kEntryFlags_Keyed = 1 << 3 ,
  kEntryFlags_Var = 1 << 4 , kEntryFlags_Nullable = 1 << 5
}
 Flags on a schema entry. More...
enum class  ValueKind : uint8_t {
  Bool = 0 , Int64 , Double , String ,
  StyleValue , Color , Object , List ,
  Action
}
 The kinds of value a schema entry holds. More...

Functions

template<typename Fn>
requires std::is_class_v<Fn>
consteval ValidateWith< Fn > Validate (Fn fn)
 Attach a validator to a field (a Field() tag).
template<typename Fn>
requires std::is_class_v<Fn>
consteval ValidateEntry< Fn > Validate (const char *name, Fn fn)
 Attach a validator to an already-declared field (a Schema() entry).
template<detail::IsAccessor A, typename... Tags>
consteval auto Field (const char *name, A accessor, Tags... tags)
 Declare a field.
template<typename Value, typename... Tags>
requires (!detail::IsAccessor<Value>)
consteval auto Field (const char *name, Tags... tags)
 Declare a field without an accessor.
template<detail::IsAccessor A, typename KeyPtr, typename... Tags>
requires std::is_member_object_pointer_v<KeyPtr>
consteval auto List (const char *name, A accessor, KeyPtr key, Tags... tags)
 Declare a keyed list.
template<detail::IsAccessor A, typename... Tags>
requires (!std::is_member_object_pointer_v<Tags> && ...)
consteval auto List (const char *name, A accessor, Tags... tags)
 Declare a list whose rows are matched by position.
template<typename Payload = void, typename... Tags>
consteval ActionToken< Payload > Action (const char *name, Tags... tags)
 Declare an action (a request the page sends to your code).
template<detail::IsAccessor A>
consteval auto Var (const char *name, A accessor)
 Declare a CSS custom property that follows your data.
template<typename... Entries>
consteval auto Schema (Entries... entries)
 Declare a type's schema.
template<typename T>
requires std::is_default_constructible_v<T>
T OrEmpty (Result< T > result)
 Get a Result's value or a default-constructed T if it holds an error (see dom::OrEmpty()).

Variables

constexpr EditableTag Editable {}
constexpr InternalTag Internal {}
constexpr OnlyLatestTag OnlyLatest {}

Enumeration Type Documentation

◆ AttachFlags

enum class AttachFlags : unsigned
strong

The flags for Context::AttachTo() (the typed form of ULDOMDataContextAttachFlags).

None are defined yet.

Enumerator
None 

No options.

◆ DumpAt

enum class DumpAt
strong

When Context::DumpSchema() writes its file.

Enumerator
NextSync 

At the end of the next Context::Sync().

Now 

Right away.

◆ EntryFlags

enum EntryFlags : uint8_t

Flags on a schema entry.

You set Editable on fields, Internal on fields and lists, and OnlyLatest on actions. The library sets Keyed, Var, and Nullable from the entry's declaration. Tools read the flags from the schema JSON (see Context::schema()).

Note
These values stay the same across releases. New flags are added at the end.
Enumerator
kEntryFlags_Editable 

The page may request changes to this field.

kEntryFlags_Internal 

Pages can't reach this entry (see dd::Internal).

kEntryFlags_OnlyLatest 

Only the newest pending action is delivered.

kEntryFlags_Keyed 

A list whose rows have a key member.

kEntryFlags_Var 

A CSS custom property (see Var()).

kEntryFlags_Nullable 

A nested object that may be absent.

◆ ValueKind

enum class ValueKind : uint8_t
strong

The kinds of value a schema entry holds.

A leaf field (one that holds a value, not a nested object or a list) has one of the first six kinds (see ValueTraits). Object, List, and Action entries hold no value of their own.

Enumerator
Bool 

A boolean.

Int64 

A signed 64-bit integer (from any integer type).

Double 

A double-precision float (from any floating-point type).

String 

UTF-8 text (from strings and enums).

StyleValue 

A CSS number with a unit (see dom::StyleValue).

Color 

A color (see ultralight::Color).

Object 

A nested object (no value of its own).

List 

A list of rows (no value of its own).

Action 

An action (no value of its own).

Function Documentation

◆ Action()

template<typename Payload = void, typename... Tags>
ActionToken< Payload > Action ( const char * name,
Tags... tags )
consteval

Declare an action (a request the page sends to your code).

Pages fire an action from markup (eg, ul-on:click="hud.respawn"), and your code can send one with Binding::PostAction(). Handlers registered with Binding::OnAction() receive it inside Context::Sync().

To send a payload with each action, give its type as the template argument:

dd::Action("respawn") // no payload
dd::Action<MoveItem>("moveItem") // a described struct of value fields
dd::Action<double>("seek") // one bool, number, string, or enum
consteval ActionToken< Payload > Action(const char *name, Tags... tags)
Declare an action (a request the page sends to your code).
Definition Schema.h:688

In the schema JSON, a single-value payload is a struct with one field, value. Handlers receive the value itself.

Parameters
nameThe action's name.
tagsOptional tags (dd::OnlyLatest).
Returns
Returns the entry to pass to Schema().
Note
Actions fired from markup carry no payload, so a handler that takes a payload never receives them.
Note
The action queue holds 1024 actions by default, and a full queue drops new ones (see Context::set_action_queue_capacity()).

◆ Field() [1/2]

template<detail::IsAccessor A, typename... Tags>
auto Field ( const char * name,
A accessor,
Tags... tags )
consteval

Declare a field.

The accessor reads the value from a const instance. It can be a data member pointer, a const member function pointer, or a lambda with no captures that takes a const reference to the instance. The field's type decides what the entry is:

  • A value type (bool, a number, a string, an enum, a StyleValue, a Color, or any type with a ValueTraits specialization) is a value pages can show.
  • A described type (a type with a schema) is a nested object. Pages reach its fields with a longer path (eg, hud.player.name).
  • A pointer, std::optional, std::unique_ptr, or std::shared_ptr to a described type is a nested object that may be absent (null).
  • A container of described rows (eg, std::vector<Row>) is a list whose rows are matched by position. Use List() to give rows a key.

Any other type fails to compile (eg, std::vector<float> or std::optional<int>).

Parameters
nameThe field's name (pages use it in paths like hud.health).
accessorHow to read the value from a const instance.
tagsOptional tags (dd::Editable, dd::Internal, and one Validate()).
Returns
Returns the entry to pass to Schema().
Note
A lambda accessor reads the type's members, so it needs the complete type. Declare a schema that uses one in a TypeTraits specialization (inside the type's own schema member, the type isn't complete yet).

◆ Field() [2/2]

template<typename Value, typename... Tags>
requires (!detail::IsAccessor<Value>)
auto Field ( const char * name,
Tags... tags )
consteval

Declare a field without an accessor.

Give the value type as the template argument, since there's no accessor to take it from (eg, dd::Field<int>("score")). The type's Sync function writes the value with Model::Set().

Parameters
nameThe field's name.
tagsOptional tags, as in the accessor form.
Returns
Returns the entry to pass to Schema().
Note
The library's default sync skips a field without an accessor, so declare a Sync function for the type (see TypeTraits).

◆ List() [1/2]

template<detail::IsAccessor A, typename KeyPtr, typename... Tags>
requires std::is_member_object_pointer_v<KeyPtr>
auto List ( const char * name,
A accessor,
KeyPtr key,
Tags... tags )
consteval

Declare a keyed list.

The accessor returns an iterable container of rows (by reference or by value), and each row is a described type. The key is the row member that identifies a row. When the list reorders, each row keeps its elements in the page:

dd::List("roster", &Match::roster, &PlayerRow::id)
Parameters
nameThe list's name.
accessorHow to read the container from a const instance.
keyA pointer to the row member that identifies each row. The member must be an integer, an enum, or a string.
tagsOptional tags (dd::Internal).
Returns
Returns the entry to pass to Schema().
Note
Keys must be unique among the rows. With duplicates, the library logs a warning and the last row wins.

◆ List() [2/2]

template<detail::IsAccessor A, typename... Tags>
requires (!std::is_member_object_pointer_v<Tags> && ...)
auto List ( const char * name,
A accessor,
Tags... tags )
consteval

Declare a list whose rows are matched by position.

This suits a list that only grows at the end. When rows reorder, each position keeps its elements and shows the new row's values. Use the keyed form when rows move.

Parameters
nameThe list's name.
accessorHow to read the container from a const instance.
tagsOptional tags (dd::Internal).
Returns
Returns the entry to pass to Schema().

◆ OrEmpty()

template<typename T>
requires std::is_default_constructible_v<T>
T OrEmpty ( Result< T > result)
nodiscard

Get a Result's value or a default-constructed T if it holds an error (see dom::OrEmpty()).

◆ Schema()

template<typename... Entries>
auto Schema ( Entries... entries)
consteval

Declare a type's schema.

A schema lists what pages can show and do with a type. Store it in the type as static constexpr auto schema, or in a TypeTraits specialization for a type you can't edit:

struct HUD {
int health = 100;
std::vector<Buff> buffs;
static constexpr auto schema = dd::Schema(
dd::Field("health", &HUD::health),
dd::List("buffs", &HUD::buffs, &Buff::id),
dd::Action("respawn"),
dd::Var("--hp", &HUD::health));
};

Entries keep the order you declare them in (Reflect() adds its fields in member order).

Compile-Time Checks

Schema() runs at compile time, and these mistakes fail to compile:

  • Two entries with the same name.
  • A malformed name. A name starts with a letter or an underscore, followed by letters, digits, and underscores (eg, maxHealth or slot_2). A Var() name is two hyphens followed by lowercase letters, digits, and hyphens (eg, --max-hp).
  • An annotation for a missing entry, or for an entry it doesn't apply to (eg, dd::Editable on a list).
  • A second Editable, Internal, or validator on the same field.
  • A validator that doesn't fit its field's type, or one on a field that isn't dd::Editable (validators only check page edits).
  • dd::Editable on a field that isn't a bool, number, string, enum, or color (eg, a nested object or a StyleValue). This check runs where the type is bound.

Look for a function like SchemaErrorDuplicateEntryName in the compiler's error. It states the rule the schema breaks.

Parameters
entriesThe entries in order (Field(), List(), Action(), Var(), and Reflect()), plus any annotations for declared entries (dd::Editable("name"), dd::Internal("name"), and dd::Validate("name", fn)).
Returns
Returns the schema.

◆ Validate() [1/2]

template<typename Fn>
requires std::is_class_v<Fn>
ValidateEntry< Fn > Validate ( const char * name,
Fn fn )
consteval

Attach a validator to an already-declared field (a Schema() entry).

Use this form for a field that Reflect() declares. The validator follows the rules of the Field() tag form.

Parameters
nameThe field's name.
fnThe validator.
Returns
Returns the entry to pass to Schema().

◆ Validate() [2/2]

template<typename Fn>
requires std::is_class_v<Fn>
ValidateWith< Fn > Validate ( Fn fn)
consteval

Attach a validator to a field (a Field() tag).

A validator checks or corrects a value the page requests before the request is queued for your handler. It takes the requested value and returns the value to accept:

dd::Field("volume", &Settings::volume, dd::Editable,
dd::Validate([](float v) { return std::clamp(v, 0.0f, 1.0f); }))

Return a std::optional to reject a request (an empty optional). The control then shows the last accepted value again.

To check the value against other fields, take a second parameter. It gets a Snapshot of the values the page shows. Declare it auto when you write the validator inside the schema, since the type isn't complete there:

dd::Field("bid", &Auction::bid, dd::Editable,
dd::Validate([](int64_t bid, auto s) -> std::optional<int64_t> {
if (bid > s.template Get<"gold">())
return std::nullopt;
return bid;
}))

The value parameter can use the field's own type or the library's form of it (int64_t or double for numbers, const String& for strings). An enum field gets the enum value. A request for a name that isn't one of its enumerators is rejected before your validator runs.

Parameters
fnThe validator (a lambda or function object with no captures). A function pointer doesn't compile.
Returns
Returns the tag to pass to Field().
Note
Validators run on the Renderer's thread, so they must not read or write your objects (use the Snapshot parameter). They run only for page edits of an Editable bool, number, string, enum, or color field of the bound type itself. Values you stage from C (ulDOMDataContextStageChangeBool() and its siblings) skip them.
Warning
A validator must not throw. Reject a value by returning an empty optional.

◆ Var()

template<detail::IsAccessor A>
auto Var ( const char * name,
A accessor )
consteval

Declare a CSS custom property that follows your data.

The page needs no markup for it. On the bound type itself, the property is set on the page's root element, so the whole document can use it. On a row type, it's set on each row's root element:

dd::Var("--hp", &HUD::health) // .bar { width: calc(var(--hp) * 1%); }

The accessor returns a number (a value without a unit), a StyleValue (a value with a unit), or a Color (written as hex, eg #ff8000).

Parameters
nameThe property's name, starting with two hyphens.
accessorHow to read the value from a const instance.
Returns
Returns the entry to pass to Schema().
Note
A Var on a type that's only used as a nested object is never set.
Note
A change to a Var on the bound type makes every style in the page that uses it recompute. For a value that changes every frame, prefer a ul-var-* attribute on the element that uses it. If two bindings set the same property on the root element, the last one written wins and the library logs a warning.

Variable Documentation

◆ Editable

EditableTag Editable {}
inlineconstexpr

◆ Internal

InternalTag Internal {}
inlineconstexpr

◆ OnlyLatest

OnlyLatestTag OnlyLatest {}
inlineconstexpr