template<typename T>
class ultralight::dom::data::Binding< T >
A handle that keeps an instance bound to pages and registers its input handlers.
A Binding keeps a native instance bound under the name page markup uses for as long as you keep the handle. It's where you register the handlers for that instance's edits and actions from the page.
Store the handle alongside your data to keep the binding active for the lifetime of your component.
This HUD component stores its binding as a member variable and routes an action to a member function:
class Hud {
public:
explicit Hud(dd::Context& ctx)
: binding_(ctx.Bind("player", player_)
.
OnAction<
"usePotion">(&Player::UsePotion)) {}
private:
Player player_;
dd::Binding<Player> binding_;
};
Binding & OnAction(Fn &&fn) &
Register the handler for an action (replacing any earlier one).
Definition Binding.h:506
Handling Input
Call OnChange() to handle edits to an Editable field from a ul-value form control on the page. The handler receives the new value, but the library never writes directly to your data. Store the value in your instance to accept the edit, or leave the field unchanged to decline it and let the control snap back at the end of the sync.
This handler accepts volume edits by writing the new value back to the settings struct:
dd::Binding settings_binding = ctx.Bind("settings", settings);
settings_binding.OnChange<"volume">([&](double volume) {
settings.volume = volume;
});
Call OnAction() to receive actions triggered by ul-on markup on the page or posted through PostAction(). A dotted name like "items.drop" routes actions from list rows. Handlers run during Context::Sync() on the context's home thread, delivering all value edits before running any actions. Registering a handler for a field or action that already has one replaces the previous handler.
Handler Forms
Both OnChange() and OnAction() route events to any of these targets:
- A callable receives the event directly. Pass a lambda or function object that accepts the field value, the action payload, or no arguments.
- A borrowed receiver calls a member function on an existing object. The library doesn't copy the object, so you must keep it alive while the binding exists.
- A smart pointer manages receiver lifetime. An owning pointer like std::shared_ptr keeps the target alive, while a std::weak_ptr skips deliveries once its object is gone.
- A member function of the bound type runs on the bound instance. It does nothing if you bound a const instance.
Posting Actions from Native Code
Call PostAction() to send an action from native code as if page markup triggered it. The action enters the context's action queue and reaches its handler during the next Context::Sync(). PostAction() is safe to call from any thread, which lets background threads hand work to the thread that owns the data. It returns false if the queue is full or if the binding stopped working.
This queues an action for delivery at the next sync:
binding.PostAction<"usePotion">();
Binding Lifetime
Destroying a Binding unbinds the instance and releases its handlers.
The unbind timing depends on which thread destroys the handle:
- On the home thread, unbinding happens immediately. The context stops reading the instance right away.
- From any other thread, unbinding takes effect at the start of the next Context::Sync(). The binding receives no further input during that synchronization cycle.
Binding the same name again replaces the earlier binding, and destroying the Context ends all its bindings. A replaced binding or one whose Context was destroyed remains safe to hold, but its handlers are dropped and PostAction() returns false.
Inspect the state of a handle using these methods:
- IsAlive() checks whether the binding is active. It returns false when the binding was replaced or its Context was destroyed, even though the bool test stays true.
- IsEmpty() checks whether the handle holds nothing. It returns true after a move or when Context::Bind() fails.
This checks whether a binding remains active after another component bound the same name:
dd::Binding hud = ctx.Bind("player", player);
dd::Binding menu = ctx.Bind("player", player);
if (!hud.IsAlive())
Log("hud's binding was replaced");
- Note
- Actions are requests that can arrive after the state that prompted them changed, so your handler should verify that prerequisites still hold before applying the request.
- Warning
- Calling ulDOMDataBindingSetChangeCallback() or ulDOMDataBindingSetActionCallback() on raw() replaces this Binding's typed handlers, and registering typed handlers replaces any C callbacks.
- See also
- dom::data::Context::Bind(), dom::data::Context::PostAction(), dom::data::ActionInfo, dom::data::Value, dom::data::Editable
|
| | Binding ()=default |
| | Create an empty Binding.
|
| | Binding (const Binding &)=delete |
| Binding & | operator= (const Binding &)=delete |
| | Binding (Binding &&other) noexcept |
| | Move constructor (other becomes empty).
|
| Binding & | operator= (Binding &&other) noexcept |
| | Move assignment (releases the binding this one held, then takes over other's).
|
| | ~Binding () |
| | Destroy this Binding (see Binding Lifetime in the class description).
|
| | operator bool () const |
| | Whether or not this Binding holds a binding (false for an empty Binding, eg, after a move or a failed Context::Bind()).
|
| bool | IsEmpty () const |
| | Whether or not this Binding holds nothing (eg, after a move or a failed Context::Bind()).
|
| bool | IsAlive () const |
| | Whether or not this Binding still works.
|
template<ultralight::detail::FixedString Field, typename Fn>
requires (!std::is_member_pointer_v<std::decay_t<Fn>>) |
| Binding & | OnChange (Fn &&fn) & |
| | Register the handler for edits to an Editable field (replacing any earlier one).
|
template<ultralight::detail::FixedString Field, typename C, typename M>
requires (std::is_member_function_pointer_v<M> && !LockableHolder<C>) |
| Binding & | OnChange (C &receiver, M method) & |
| | Register a member function of receiver as the handler for edits to an Editable field.
|
template<ultralight::detail::FixedString Field, typename H, typename M>
requires (LockableHolder<H> && std::is_member_function_pointer_v<M>) |
| Binding & | OnChange (H holder, M method) & |
| | Register a member function as the handler for edits to an Editable field, called through a smart pointer.
|
template<ultralight::detail::FixedString Field, typename M>
requires std::is_member_function_pointer_v<M> |
| Binding & | OnChange (M method) & |
| | Register a member function of the bound type as the handler for edits to an Editable field, called on the bound instance.
|
| template<ultralight::detail::FixedString Field, typename... A> |
| Binding | OnChange (A &&... args) && |
| | Register the handler for edits to an Editable field on a Binding you just created.
|
template<ultralight::detail::FixedString Name, typename Fn>
requires (!std::is_member_pointer_v<std::decay_t<Fn>>) |
| Binding & | OnAction (Fn &&fn) & |
| | Register the handler for an action (replacing any earlier one).
|
template<ultralight::detail::FixedString Name, typename C, typename M>
requires (std::is_member_function_pointer_v<M> && !LockableHolder<C>) |
| Binding & | OnAction (C &receiver, M method) & |
| | Register a member function of receiver as the handler for an action.
|
template<ultralight::detail::FixedString Name, typename H, typename M>
requires (LockableHolder<H> && std::is_member_function_pointer_v<M>) |
| Binding & | OnAction (H holder, M method) & |
| | Register a member function as the handler for an action, called through a smart pointer.
|
template<ultralight::detail::FixedString Name, typename M>
requires std::is_member_function_pointer_v<M> |
| Binding & | OnAction (M method) & |
| | Register a member function of the bound type as the handler for an action, called on the bound instance.
|
| template<ultralight::detail::FixedString Name, typename... A> |
| Binding | OnAction (A &&... args) && |
| | Register the handler for an action on a Binding you just created.
|
| template<ultralight::detail::FixedString Field> |
| Binding & | RemoveChangeHandler () & |
| | Remove the handler for edits to an Editable field (if there is one).
|
| template<ultralight::detail::FixedString Field> |
| Binding | RemoveChangeHandler () && |
| | Remove the handler for edits to an Editable field on a Binding you just created.
|
| template<ultralight::detail::FixedString Name> |
| Binding & | RemoveActionHandler () & |
| | Remove the handler for an action (if there is one).
|
| template<ultralight::detail::FixedString Name> |
| Binding | RemoveActionHandler () && |
| | Remove the handler for an action on a Binding you just created.
|
| template<ultralight::detail::FixedString Name> |
| bool | PostAction () |
| | Send one of this binding's actions that has no payload, as if the page fired it.
|
| template<ultralight::detail::FixedString Name> |
| bool | PostAction (const detail::ActionPayloadArg< T, Name > &payload) |
| | Send one of this binding's actions with its payload, as if the page fired it.
|
| std::string | name () const |
| | Get the binding name.
|
| ULDOMDataBinding | raw () const |
| | Get the C handle, for passing to the <Ultralight/CAPI/CAPI_DOMData.h> functions.
|
| ULDOMDataBinding | LeakRef () |
| | Give up ownership of the C handle and return it.
|
template<typename T>
template<ultralight::detail::FixedString Name, typename Fn>
requires (!std::is_member_pointer_v<std::decay_t<Fn>>)
Register the handler for an action (replacing any earlier one).
The handler runs during Context::Sync() after all changes, in the order the actions fired. Its parameter list decides what it receives:
b.OnAction<"voteSkip">([&] { match.VoteSkip(); });
b.OnAction<"moveItem">([&](const MoveItem& move) { ... });
b.OnAction<
"roster.focusPlayer">([&](
const ScoreRow&
row) { ... });
b.OnAction<"roster.focusPlayer">([&](int64_t key) { ... });
b.OnAction<"save">([&](dd::Value payload, dd::ActionInfo info) { ... });
ContainerSpec< Children... > row(ContainerOptions options, Children... children)
Describe a row container with child elements.
Definition Builder.h:125
- No parameters works for every action (any payload is ignored).
- The declared payload (a struct by const reference, or a scalar like double by value). An action whose payload doesn't match is dropped. Actions from ul-on:* markup have no payload, so this form never receives them.
- dd::Value holds the payload. You can add a dd::ActionInfo parameter after it (the action name and row key).
- The row type (by const reference, for a row action) holds the row's values as of the last Context::Sync(). The action is dropped if no row has that key anymore (or, in a list without keys, that position).
- The row key (for a row action) is an int64_t (in a list without keys, the row's position when the page fired the action) or a std::string_view for string keys (valid only during the call).
A row action on a list without keys addresses the row by its position when the page fired it, so rows added or removed before the next Sync() make it reach another row (the page warns when it compiles one). Declare a key for a list whose rows can move.
The row type form rebuilds the row, so the row type must be default-constructible and every field in its schema must read a data member (a member pointer or a Reflect() field) of a bool, number, enum (only when ULTRALIGHT_REFLECTION is 1), StyleValue, or Color type, or of a string type constructible from a const char* and a length (eg, std::string). A row with a list, a nested object, a Var(), a getter or lambda accessor, or a std::string_view member doesn't compile in this form, so take the row key instead.
A dotted name (list.action) addresses an action of a list's row type. Only one level is supported, so actions of a nested object or of a list inside a row can't be registered. The name and the handler are checked at compile time, and a parameter must have one of these types exactly (eg, a bool parameter for a double payload doesn't compile).
- Parameters
-
- Returns
- Returns this Binding (for chaining).