|
Ultralight C++ API 2.0.0
|
#include <Ultralight/dom/data/Context.h>
Data-binding context for attached Views.
A Context registers your C++ instances under names that HTML markup can reference, exposing their fields and actions to the pages loaded by each attached View. Your markup declares where values appear and which interactions trigger native actions, keeping page presentation separate from your application's data.
Calling Sync() once per frame delivers input from the page to your native handlers and broadcasts changed values in a single batch to keep those pages current.
This example binds a player instance to a context and attaches it to a View:
The loaded page references bound fields and click actions directly in markup:
Calling Bind() registers an instance under a binding name that forms the root of every markup path on the page.
Always store the returned dom::data::Binding handle alongside your data, because destroying it unbinds the instance immediately (see dom::data::Binding).
A context can bind an instance by reference, by value, or through a smart pointer:
Each binding form defines who keeps the bound instance alive:
Call AttachTo() to connect a Context to a View. You only attach once– every page that View loads receives the bindings automatically, including pages restored from the back-forward cache.
A single Context can attach to multiple Views, and a View can hold bindings from multiple Contexts. One call to Sync() synchronizes all attached Views at once.
Data bindings work only in a View's main frame.
By default, only local file:// pages and pages loaded with View::LoadHTML() receive bindings.
Passing custom origin rules in AttachOptions replaces the default policy completely. To allow local files alongside remote origins, include file://* in the rule list (see OriginRules for pattern syntax).
Pages loaded with View::LoadHTML() without a URL have an opaque origin that no custom rule matches, so they receive bindings only under the default policy. When loaded with an explicit URL, they match against that URL's origin.
The thread that calls Create() becomes the home thread for that Context. In a single-threaded application, this is the Renderer's thread. You bind instances, register handlers, and call Sync() on this thread. When you modify bound data, make those changes on the home thread between calls to Sync().
When multiple worker threads need to modify bound data, give each thread its own Context. If the thread that calls Sync() changes over time (such as across worker threads in a job system), call CreateThreadSafe() instead so any thread can bind data, register handlers, and call Sync().
A mistake in markup drops only the broken binding, allowing the rest of the page to bind normally.
When developer mode is on (Config::diagnostics), the library outputs warnings for markup mistakes to the native Logger and the page's console.
Static Public Member Functions | |
| static Context | Create () |
| Create a Context whose home thread is the calling thread. | |
| static Context | CreateThreadSafe () |
| Create a Context that any thread can use. | |
| static Context | Adopt (ULDOMDataContext handle) |
| Wrap a C handle you own, taking ownership of it. | |
| static Context | FromBorrowed (ULDOMDataContext handle) |
| Wrap a C handle someone else owns, without owning the context. | |
Public Member Functions | |
| Context (const Context &)=delete | |
| Context & | operator= (const Context &)=delete |
| Context (Context &&other) noexcept | |
| Move constructor (other becomes empty). | |
| Context & | operator= (Context &&other) noexcept |
| Move assignment (other becomes empty). | |
| ~Context () | |
| Destroy this handle. | |
| operator bool () const | |
| Whether or not this Context is valid (false after a move or LeakRef()). | |
| template<typename T> requires (Described<T> && !std::is_const_v<T>) | |
| Binding< T > | Bind (std::string_view name, T &instance) |
| Bind an instance of a described type under a binding name. | |
| template<typename T> requires Described<T> | |
| Binding< T > | Bind (std::string_view name, const T &instance) |
| Bind a const instance under a binding name. | |
| template<typename T> requires (Described<std::remove_cvref_t<T>> && !std::is_lvalue_reference_v<T>) | |
| Binding< std::remove_cvref_t< T > > | Bind (std::string_view name, T &&instance) |
| Bind an instance by value under a binding name. | |
| template<typename H> requires BindableHolder<H> | |
| Binding< std::remove_cv_t< typename HolderTraits< std::remove_cvref_t< H > >::element_type > > | Bind (std::string_view name, H &&holder) |
| Bind an instance through a smart pointer under a binding name. | |
| bool | AttachTo (View *view, const AttachOptions &options={}) |
| Attach this Context to a View. | |
| void | DetachFrom (View *view) |
| Detach this Context from a View. | |
| void | Sync () |
| Deliver input from the page to your handlers and send your changes to the pages. | |
| bool | PostAction (std::string_view name) |
| Send an action to a bound instance's handler as if the page fired it. | |
| template<typename P> requires Described<std::remove_cvref_t<P>> | |
| bool | PostAction (std::string_view name, const P &payload) |
| Send an action with a payload struct (see the payloadless overload). | |
| template<typename P> requires (!Described<std::decay_t<P>> && ScalarPayload<std::decay_t<P>>) | |
| bool | PostAction (std::string_view name, const P &payload) |
| Send an action with a single-value payload (see the payloadless overload). | |
| template<typename F> requires (std::is_invocable_v<std::decay_t<F>&> || std::is_invocable_v<std::decay_t<F>&, Document>) | |
| void | PostTask (F &&callback) |
| Run a callable on the Renderer's thread once the pages show your latest data. | |
| template<typename Fn> | |
| Context & | DefineFormat (std::string_view name, Fn &&fn) |
| Define a formatter for {{path|name}} text. | |
| Context & | UndefineFormat (std::string_view name) |
| Remove a formatter. | |
| uint64_t | generation () const |
| Get the generation of the last update Sync() sent to the pages. | |
| void | set_action_queue_capacity (uint32_t capacity) |
| Set how many actions can wait for the next Sync(). | |
| uint32_t | action_queue_capacity () const |
| Get how many actions can wait for the next Sync(). | |
| std::string | schema () const |
| Get the schema of every bound instance as JSON, with their current values. | |
| bool | DumpSchema (const char *utf8_path, DumpAt when=DumpAt::NextSync) |
| Write the JSON from schema() to a file. | |
| ULDOMDataContext | raw () const |
| Get the C handle, for passing to the <Ultralight/CAPI/CAPI_DOMData.h> functions. | |
| ULDOMDataContext | LeakRef () |
| Give up ownership of the C handle and return it. | |
|
delete |
|
inlinenoexcept |
Move constructor (other becomes empty).
| other | The Context to move from. |
|
inline |
Destroy this handle.
Destroying the Context detaches it from every View (the pages drop its bindings the same way DetachFrom() does) and destroys its bindings, handlers, and formatters (and anything they capture) right here, on the calling thread. So destroy it on its home thread. A Binding that outlives it stays safe to use, but its instance is no longer read.
|
inline |
|
inlinestatic |
|
inlinenodiscard |
Attach this Context to a View.
Every page the View loads then gets this Context's bindings, starting with the current page during a later Renderer::Update(). The View keeps the Context attached until you detach it or destroy its last owning Context.
A page gets the bindings only if the origin rules allow it. With no rules, only your application's own content does (local pages and pages loaded with View::LoadHTML()). Rules have the scheme://host[:port] form (see OriginRules for the full syntax), and they replace that default, so add file://* if you still want local pages:
A page whose origin isn't allowed shows its authored markup, and its controls send no input. A page loaded with View::LoadHTML() without a URL has an opaque origin– no rule matches it and no filter allows one, so it gets bindings only with the default rules. Attaching an already attached Context replaces its flags and rules.
| view | The View to attach to. |
| options | The attach flags and the origin rules (see AttachOptions). |
|
inlinenodiscard |
Bind a const instance under a binding name.
This works like the non-const overload, except that handlers which are member functions of the bound type (eg, OnAction<"respawn">(&HUD::Respawn)) do nothing, since they would change the instance. Register a lambda or another receiver instead.
| name | The binding name pages use. |
| instance | The instance to bind. Ownership remains with the caller. |
|
inlinenodiscard |
Bind an instance through a smart pointer under a binding name.
holder can be a std::shared_ptr, RefPtr, or moved std::unique_ptr. It can also be a std::weak_ptr, WeakPtr, or your own smart pointer type with an ultralight::HolderTraits specialization. The Binding keeps the holder (a copy or the moved std::unique_ptr)– an owning pointer keeps the instance alive while it's bound. Sync() locks the holder while it reads the instance.
Once a weak holder expires, Sync() skips the binding and pages keep its last values. Handlers that are member functions of the bound type skip it too, and do nothing at all when the holder points to a const type.
| name | The binding name pages use. |
| holder | The smart pointer to the instance. |
|
inlinenodiscard |
Bind an instance by value under a binding name.
The Binding takes ownership of the moved-in instance and destroys it when it unbinds. Use this for data you build once and don't change afterwards.
Handlers that are member functions of the bound type (eg, OnAction<"respawn">(&HUD::Respawn)) run on the owned instance. They're the only way to change it after binding.
| name | The binding name pages use. |
| instance | The instance to move into the Binding. |
|
inlinenodiscard |
Bind an instance of a described type under a binding name.
Pages reach the instance's fields through the binding name (eg, hud.health), and each Sync() sends your changes to them. The Binding borrows instance, so keep it alive while it's bound.
Binding a name again replaces the earlier binding (see Binding).
| name | The binding name pages use. It starts with a letter or an underscore, followed by letters, digits, and underscores (eg, hud or main_menu). |
| instance | The instance to bind. Ownership remains with the caller. |
|
inlinestatic |
|
inlinestatic |
Create a Context that any thread can use.
Use this when the thread that calls Sync() changes over time (eg, a job system that syncs on different worker threads). Any thread can then bind, sync, and register handlers. The calls take turns (each one waits until the running one finishes), and handlers run on the thread that called Sync().
Only the Context's own calls are thread-safe, not your bound instances. Sync() reads them without any lock you can see, so don't change an instance while another thread may be syncing. To hand an instance to another thread, use your own synchronization.
A handler that waits on another thread that needs this Context deadlocks. Calling back into the Context from the handler itself is fine.
|
inline |
Define a formatter for {{path|name}} text.
A formatter turns a bool, number, or string value into the text the page shows:
Only {{path}} text uses formatters (ul-text doesn't take one), and only for the bindings of this Context. Defining a name again replaces its formatter. A new formatter shows the next time the text changes.
| name | The formatter name, used after the pipe. It starts with a letter or an underscore, followed by letters, digits, and underscores. A name that doesn't defines nothing (the library logs a warning). |
| fn | The formatter. It takes the value as a dd::Value and returns the text (a String, a std::string, or a C string). |
|
inline |
Detach this Context from a View.
During a later Renderer::Update(), the View's page rebuilds its bindings from the Contexts still attached. When none are left, the page gets its authored markup back ({{path}} text, ul-if templates, and no rows). Values already written by ul-text, classes, attributes, and styles stay.
| view | The View to detach from (nullptr does nothing). |
|
inline |
Write the JSON from schema() to a file.
With DumpAt::NextSync (the default), the library writes the file at the end of the next Sync() on the home thread, so you can call this from any thread (eg, from a debug hotkey). A write that fails then logs a warning with the path.
With DumpAt::Now, the library writes the file before this returns. Call it on the home thread.
| utf8_path | The file's path as a UTF-8 string (on Windows too). An existing file is replaced. |
| when | When to write the file. |
|
inlinestatic |
Wrap a C handle someone else owns, without owning the context.
The result works like any Context, but destroying it never detaches the context or destroys its bindings, and it doesn't keep the context attached once its owners are gone.
| handle | The borrowed handle (NULL gives an empty Context). |
|
inline |
|
inline |
Give up ownership of the C handle and return it.
This Context becomes empty.
|
inlineexplicit |
|
inline |
Send an action to a bound instance's handler as if the page fired it.
Use this where you have no Binding at hand, eg, in a DOM listener that handles an interaction the markup can't express. Where you have the Binding, use Binding::PostAction() instead (it checks the action name at compile time).
The action reaches its handler at the next Sync(), in order with the actions from the page. An action with an unknown binding or action name is dropped there, with a warning when DOM diagnostics are on (see "Finding Mistakes" in the class overview).
| name | The binding name and the action name joined by a dot (eg, "inv.sort"). Only actions of the bound type itself can be sent (not a row's actions). |
|
inline |
Send an action with a single-value payload (see the payloadless overload).
Use this for an action declared with one bool, number, string, or reflected enum value (eg, dd::Action<double>("seek")):
| name | The binding name and the action name joined by a dot. |
| payload | The value to send. |
|
inline |
Send an action with a payload struct (see the payloadless overload).
The payload is a described struct whose fields are all bool, integer, floating-point, string, or reflected enum values (checked at compile time). The action's handler receives it as its declared payload type (a handler whose payload type doesn't match doesn't receive it).
| name | The binding name and the action name joined by a dot. |
| payload | The payload to send. |
|
inline |
Run a callable on the Renderer's thread once the pages show your latest data.
The callable runs during a later Renderer::Update(), after the pages have applied the data from:
How often it runs depends on its parameters:
A View whose page is still loading holds it up until that page shows the data. A page that doesn't use this Context (eg, its origin isn't allowed) doesn't hold it up.
| callback | The callable to run. Capture by value, since it runs later (and on another thread unless your home thread is the Renderer's thread). |
|
inline |
|
inline |
Get the schema of every bound instance as JSON, with their current values.
The mock data tools read this JSON, so you can build and test pages in a browser with your app's real model shapes (see DumpSchema() to write it to a file). It has:
A reader should ignore keys and value types it doesn't know. The version changes only for a change that would break an existing reader.
Since the values are your app's real data, you should only write them out from development builds.
|
inline |
Set how many actions can wait for the next Sync().
(Default: 1024)
When the queue is full, new actions are dropped (the library logs a warning). For an action the page fires continuously (eg, a scroll position), declare it dd::OnlyLatest so each new one replaces the waiting one instead of raising the limit.
| capacity | The number of actions (values below 1 become 1). |
|
inline |
Deliver input from the page to your handlers and send your changes to the pages.
Call this once per tick on the home thread. Each call does three things in order:
Pages show the update during a later Renderer::Update(), and they never show part of one. A Sync with nothing to send is cheap.
A type can control how its instances are read with a static Sync function, which reads fields with Model::Sync() (see "Three Kinds of Sync" in the Model class description).
|
inline |
Remove a formatter.
Text that uses it shows the plain value (and logs a warning) from the next time it changes.
| name | The formatter name. |