6#include <Ultralight/CAPI/CAPI_DOMData.h>
10#include <Ultralight/dom/data/detail/Delivery.h>
11#include <Ultralight/dom/data/detail/Emission.h>
12#include <Ultralight/dom/data/detail/Receivers.h>
31template <
typename Fn,
typename =
void>
32struct HandlerSignature {
33 static constexpr bool kKnown =
false;
36struct HandlerSignature<Fn, std::void_t<decltype(&Fn::operator())>> {
37 static constexpr bool kKnown =
true;
38 using args =
typename MemberCallableSig<
decltype(&Fn::operator())>::args;
45template <
typename Fn,
typename... Forms>
46consteval bool HandlerParamIsOneOf() {
47 using Plain = std::remove_cvref_t<Fn>;
48 if constexpr (!HandlerSignature<Plain>::kKnown) {
51 using Args =
typename HandlerSignature<Plain>::args;
52 if constexpr (std::tuple_size_v<Args> != 1)
55 return (std::is_same_v<std::decay_t<std::tuple_element_t<0, Args>>, Forms> || ...);
59template <
typename V, ValueKind kKind,
typename Fn>
60consteval void CheckChangeHandlerForm() {
61 if constexpr (kKind == ValueKind::Color) {
62 static_assert(HandlerParamIsOneOf<Fn, Color>(),
63 "dom::data: an OnChange handler for a color field takes an "
65 }
else if constexpr (kKind == ValueKind::String && std::is_enum_v<V>) {
66 static_assert(HandlerParamIsOneOf<Fn, V, Value>(),
67 "dom::data: an OnChange handler for an enum field takes the enum value "
69 }
else if constexpr (kKind == ValueKind::String) {
70 static_assert(HandlerParamIsOneOf<Fn, V, ultralight::String, std::string_view, Value>(),
71 "dom::data: an OnChange handler for a string field takes the field's "
72 "value type, a const String&, or a dd::Value");
73 }
else if constexpr (kKind == ValueKind::Bool) {
74 static_assert(HandlerParamIsOneOf<Fn, V, bool, Value>(),
75 "dom::data: an OnChange handler takes the field's value type (or its "
76 "slot type, or a dd::Value)");
78 static_assert(HandlerParamIsOneOf<Fn, V, int64_t, double, Value>(),
79 "dom::data: an OnChange handler takes the field's value type (or its "
80 "slot type, or a dd::Value)");
84template <
typename P,
typename Fn>
85consteval void CheckRootActionHandlerForm() {
86 constexpr bool kOk = [] {
87 if constexpr (std::is_void_v<P>)
88 return HandlerParamIsOneOf<Fn, Value>();
90 return HandlerParamIsOneOf<Fn, std::decay_t<P>, Value>();
93 "dom::data: an OnAction handler takes nothing, the action's declared "
94 "payload (its struct by const reference, or the scalar by value), a "
95 "dd::Value, or (dd::Value, dd::ActionInfo)");
98template <
typename R, ValueKind kKeyKind,
typename Fn>
99consteval void CheckRowActionHandlerForm() {
100 using Key = std::conditional_t<kKeyKind == ValueKind::String, std::string_view, int64_t>;
101 static_assert(HandlerParamIsOneOf<Fn, R, Key, Value>(),
102 "dom::data: a row OnAction handler takes nothing, the row type by const "
103 "reference, the row key (int64_t, or std::string_view for string-keyed "
104 "lists), a dd::Value, or (dd::Value, dd::ActionInfo)");
250 : handle_(other.handle_), handlers_(other.handlers_),
251 access_(std::move(other.access_)) {
252 other.handle_ =
nullptr;
253 other.handlers_ =
nullptr;
254 other.access_ =
nullptr;
265 if (
this != &other) {
266 ulDestroyDOMDataBinding(handle_);
267 handle_ = other.handle_;
268 handlers_ = other.handlers_;
269 access_ = std::move(other.access_);
270 other.handle_ =
nullptr;
271 other.handlers_ =
nullptr;
272 other.access_ =
nullptr;
289 explicit operator bool()
const {
return handle_ !=
nullptr; }
298 bool IsEmpty()
const {
return handle_ ==
nullptr; }
308 bool IsAlive()
const {
return ulDOMDataBindingIsAlive(handle_); }
348 template <ultralight::detail::FixedString Field,
typename Fn>
349 requires(!std::is_member_pointer_v<std::decay_t<Fn>>)
351 constexpr std::string_view path =
Field.view();
352 static_assert(path.find(
'.') == std::string_view::npos,
353 "dom::data: nested and row change delivery is not yet available; "
354 "OnChange registers root-level Editable fields");
355 if constexpr (path.find(
'.') == std::string_view::npos) {
356 constexpr size_t I = detail::SchemaIndexOf<T>(path);
357 static_assert(I != detail::kNpos,
358 "dom::data: the bound type's schema has no entry with this name");
359 if constexpr (I != detail::kNpos) {
361 using Tok = std::tuple_element_t<I, SchemaType>;
362 static_assert(detail::IsFieldToken<Tok>::value
363 && detail::EntryInfo<Tok>::kClass == detail::EntryClass::kLeaf,
364 "dom::data: OnChange registers leaf Field entries");
365 if constexpr (detail::IsFieldToken<Tok>::value
366 && detail::EntryInfo<Tok>::kClass == detail::EntryClass::kLeaf) {
369 "dom::data: OnChange registers Editable fields (changes are never staged "
370 "for a field the UI cannot edit)");
371 detail::CheckChangeHandlerForm<std::remove_cvref_t<typename Tok::value_type>,
372 detail::EntryInfo<Tok>::kKind, Fn>();
373 detail::WithBindingOwnerTurn(handle_, [&] {
374 if (
auto* table = EnsureHandlers()) {
375 table->changes[
static_cast<uint32_t
>(I)]
376 = detail::MakeChangeHandler<T, I>(std::forward<Fn>(fn));
397 template <ultralight::detail::FixedString Field,
typename C,
typename M>
417 template <ultralight::detail::FixedString Field,
typename H,
typename M>
420 return OnChange<Field>(detail::WrapHolderMember(std::move(holder), method));
434 template <ultralight::detail::FixedString Field,
typename M>
435 requires std::is_member_function_pointer_v<M>
437 return OnChange<Field>(detail::WrapImplicitMember<T>(access_, method));
451 template <ultralight::detail::FixedString
Field,
typename... A>
454 return std::move(*
this);
504 template <ultralight::detail::FixedString Name,
typename Fn>
505 requires(!std::is_member_pointer_v<std::decay_t<Fn>>)
507 constexpr std::string_view path = Name.view();
508 constexpr size_t dot = path.find(
'.');
509 if constexpr (dot == std::string_view::npos) {
510 constexpr size_t I = detail::SchemaIndexOf<T>(path);
511 static_assert(I != detail::kNpos,
512 "dom::data: the bound type's schema has no entry with this name");
513 if constexpr (I != detail::kNpos) {
515 using Tok = std::tuple_element_t<I, SchemaType>;
516 static_assert(detail::IsActionToken<Tok>::value,
517 "dom::data: OnAction registers Action entries (the named entry is "
519 if constexpr (detail::IsActionToken<Tok>::value) {
520 detail::CheckRootActionHandlerForm<typename Tok::payload_type, Fn>();
521 detail::WithBindingOwnerTurn(handle_, [&] {
522 if (
auto* table = EnsureHandlers()) {
523 table->actions[std::make_pair(detail::kRootActionSlot,
524 static_cast<uint32_t
>(I))]
525 = detail::MakeRootActionHandler<T, I>(std::forward<Fn>(fn));
531 static_assert(path.find(
'.', dot + 1) == std::string_view::npos,
532 "dom::data: one dotted level is supported (list.action); deeper "
533 "paths are not yet deliverable");
534 constexpr size_t L = detail::SchemaIndexOf<T>(path.substr(0, dot));
535 static_assert(L != detail::kNpos,
536 "dom::data: the bound type's schema has no entry named by the first "
538 if constexpr (L != detail::kNpos && path.find(
'.', dot + 1) == std::string_view::npos) {
540 using LTok = std::tuple_element_t<L, SchemaType>;
541 static_assert(detail::EntryInfo<LTok>::kClass != detail::EntryClass::kObject
542 && detail::EntryInfo<LTok>::kClass
543 != detail::EntryClass::kNullableObject,
544 "dom::data: actions behind object fields are not yet deliverable");
545 static_assert(detail::EntryInfo<LTok>::kClass == detail::EntryClass::kList,
546 "dom::data: OnAction's dotted form addresses an action of a list's "
548 if constexpr (detail::EntryInfo<LTok>::kClass == detail::EntryClass::kList) {
549 using R =
typename detail::EntryInfo<LTok>::child_type;
550 constexpr size_t A = detail::SchemaIndexOf<R>(path.substr(dot + 1));
551 static_assert(A != detail::kNpos,
552 "dom::data: the row type's schema has no entry named by the "
553 "second path segment");
554 if constexpr (A != detail::kNpos) {
555 using RTok = std::tuple_element_t<
557 static_assert(detail::IsActionToken<RTok>::value,
558 "dom::data: OnAction registers Action entries (the named row "
559 "entry is not an Action)");
560 if constexpr (detail::IsActionToken<RTok>::value) {
562 if constexpr (detail::IsListToken<LTok>::value)
563 return detail::EntryInfo<LTok>::KeyKind();
567 detail::CheckRowActionHandlerForm<R, kKeyKind, Fn>();
568 detail::WithBindingOwnerTurn(handle_, [&] {
569 if (
auto* table = EnsureHandlers()) {
570 table->actions[std::make_pair(
static_cast<uint32_t
>(L),
571 static_cast<uint32_t
>(A))]
572 = detail::MakeRowActionHandler<T, L, A>(std::forward<Fn>(fn));
595 template <ultralight::detail::FixedString Name,
typename C,
typename M>
598 return OnAction<Name>(detail::WrapBorrowedMember(receiver, method));
614 template <ultralight::detail::FixedString Name,
typename H,
typename M>
617 return OnAction<Name>(detail::WrapHolderMember(std::move(holder), method));
631 template <ultralight::detail::FixedString Name,
typename M>
632 requires std::is_member_function_pointer_v<M>
634 return OnAction<Name>(detail::WrapImplicitMember<T>(access_, method));
648 template <ultralight::detail::FixedString Name,
typename... A>
651 return std::move(*
this);
661 template <ultralight::detail::FixedString Field>
663 constexpr std::string_view path =
Field.view();
664 static_assert(path.find(
'.') == std::string_view::npos,
665 "dom::data: nested and row change delivery is not yet available; "
666 "change handlers register root-level Editable fields");
667 if constexpr (path.find(
'.') == std::string_view::npos) {
668 constexpr size_t I = detail::SchemaIndexOf<T>(path);
669 static_assert(I != detail::kNpos,
670 "dom::data: the bound type's schema has no entry with this name");
671 if constexpr (I != detail::kNpos) {
673 using Tok = std::tuple_element_t<I, SchemaType>;
674 static_assert(detail::IsFieldToken<Tok>::value
675 && detail::EntryInfo<Tok>::kClass == detail::EntryClass::kLeaf,
676 "dom::data: change handlers address leaf Field entries");
677 if constexpr (detail::IsFieldToken<Tok>::value
678 && detail::EntryInfo<Tok>::kClass == detail::EntryClass::kLeaf) {
681 "dom::data: change handlers address Editable fields (changes are never "
682 "staged for a field the UI cannot edit)");
683 detail::WithBindingOwnerTurn(handle_, [&] {
684 if (
auto* table = FindHandlers())
685 table->changes.erase(
static_cast<uint32_t
>(I));
698 template <ultralight::detail::FixedString Field>
701 return std::move(*
this);
712 template <ultralight::detail::FixedString Name>
714 constexpr std::string_view path = Name.view();
715 constexpr size_t dot = path.find(
'.');
716 if constexpr (dot == std::string_view::npos) {
717 constexpr size_t I = detail::SchemaIndexOf<T>(path);
718 static_assert(I != detail::kNpos,
719 "dom::data: the bound type's schema has no entry with this name");
720 if constexpr (I != detail::kNpos) {
722 using Tok = std::tuple_element_t<I, SchemaType>;
723 static_assert(detail::IsActionToken<Tok>::value,
724 "dom::data: action handlers address Action entries (the named "
725 "entry is not an Action)");
726 if constexpr (detail::IsActionToken<Tok>::value) {
727 detail::WithBindingOwnerTurn(handle_, [&] {
728 if (
auto* table = FindHandlers()) {
729 table->actions.erase(
730 std::make_pair(detail::kRootActionSlot,
static_cast<uint32_t
>(I)));
736 static_assert(path.find(
'.', dot + 1) == std::string_view::npos,
737 "dom::data: one dotted level is supported (list.action); deeper "
738 "paths are not yet deliverable");
739 constexpr size_t L = detail::SchemaIndexOf<T>(path.substr(0, dot));
740 static_assert(L != detail::kNpos,
741 "dom::data: the bound type's schema has no entry named by the first "
743 if constexpr (L != detail::kNpos && path.find(
'.', dot + 1) == std::string_view::npos) {
745 using LTok = std::tuple_element_t<L, SchemaType>;
746 static_assert(detail::EntryInfo<LTok>::kClass == detail::EntryClass::kList,
747 "dom::data: the dotted form addresses an action of a list's row "
749 if constexpr (detail::EntryInfo<LTok>::kClass == detail::EntryClass::kList) {
750 using R =
typename detail::EntryInfo<LTok>::child_type;
751 constexpr size_t A = detail::SchemaIndexOf<R>(path.substr(dot + 1));
752 static_assert(A != detail::kNpos,
753 "dom::data: the row type's schema has no entry named by the "
754 "second path segment");
755 if constexpr (A != detail::kNpos) {
756 using RTok = std::tuple_element_t<
758 static_assert(detail::IsActionToken<RTok>::value,
759 "dom::data: action handlers address Action entries (the named "
760 "row entry is not an Action)");
761 if constexpr (detail::IsActionToken<RTok>::value) {
762 detail::WithBindingOwnerTurn(handle_, [&] {
763 if (
auto* table = FindHandlers()) {
764 table->actions.erase(
765 std::make_pair(
static_cast<uint32_t
>(L),
static_cast<uint32_t
>(A)));
781 template <ultralight::detail::FixedString Name>
784 return std::move(*
this);
805 template <ultralight::detail::FixedString Name>
807 constexpr std::string_view path = Name.view();
808 static_assert(path.find(
'.') == std::string_view::npos,
809 "dom::data: PostAction addresses root actions (row-scoped actions fire "
810 "from bound markup with their row's identity)");
811 if constexpr (path.find(
'.') == std::string_view::npos) {
812 constexpr size_t I = detail::SchemaIndexOf<T>(path);
813 static_assert(I != detail::kNpos,
814 "dom::data: the bound type's schema has no entry with this name");
815 if constexpr (I != detail::kNpos) {
817 using Tok = std::tuple_element_t<I, SchemaType>;
818 static_assert(detail::IsActionToken<Tok>::value,
819 "dom::data: PostAction emits Action entries (the named entry is "
821 if constexpr (detail::IsActionToken<Tok>::value) {
822 static_assert(std::is_void_v<typename Tok::payload_type>,
823 "dom::data: this action declares a payload; pass it as "
824 "PostAction's argument");
825 if constexpr (std::is_void_v<typename Tok::payload_type>) {
826 return detail::PostBindingAction(handle_,
static_cast<unsigned int>(I));
851 template <ultralight::detail::FixedString Name>
852 bool PostAction(
const detail::ActionPayloadArg<T, Name>& payload) {
853 constexpr std::string_view path = Name.view();
854 static_assert(path.find(
'.') == std::string_view::npos,
855 "dom::data: PostAction addresses root actions (row-scoped actions fire "
856 "from bound markup with their row's identity)");
857 if constexpr (path.find(
'.') == std::string_view::npos) {
858 constexpr size_t I = detail::SchemaIndexOf<T>(path);
859 static_assert(I != detail::kNpos,
860 "dom::data: the bound type's schema has no entry with this name");
861 if constexpr (I != detail::kNpos) {
863 using Tok = std::tuple_element_t<I, SchemaType>;
864 static_assert(detail::IsActionToken<Tok>::value,
865 "dom::data: PostAction emits Action entries (the named entry is "
867 if constexpr (detail::IsActionToken<Tok>::value) {
868 static_assert(!std::is_void_v<typename Tok::payload_type>,
869 "dom::data: this action declares no payload; call PostAction "
871 if constexpr (!std::is_void_v<typename Tok::payload_type>) {
874 detail::PayloadMarshalable<typename Tok::payload_type>(),
875 "dom::data: a payload type carries plain data fields only (readable "
876 "leaf members of boolean, integral, floating-point, string, or "
877 "reflected enum type)");
878 std::vector<std::string> strings;
879 std::vector<ULDOMDataPayloadEntry> entries;
880 detail::MarshalPayload(payload, strings, entries);
881 return detail::PostBindingAction(handle_,
static_cast<unsigned int>(I),
882 entries.empty() ?
nullptr : entries.data(),
886 ULDOMDataPayloadEntry entry {};
887 detail::MarshalScalarPayload(payload, backing, entry);
888 return detail::PostBindingAction(handle_,
static_cast<unsigned int>(I), &entry,
908 return std::string();
909 ULString text = ulDOMDataBindingGetName(handle_);
911 return std::string();
912 std::string result(ulStringGetData(text), ulStringGetLength(text));
913 ulDestroyString(text);
937 return Binding(handle ? ulCreateDOMDataBindingRef(handle) :
nullptr);
946 ULDOMDataBinding
raw()
const {
return handle_; }
954 ULDOMDataBinding handle = handle_;
963 explicit Binding(ULDOMDataBinding handle) : handle_(handle) {}
973 detail::HandlerTable* EnsureHandlers() {
974 if (!handle_ || !ulDOMDataBindingGetStagingTable(handle_))
976 ULDOMDataChangeCallback current =
nullptr;
977 void* current_data =
nullptr;
978 ulDOMDataBindingGetChangeCallback(handle_, ¤t, ¤t_data);
979 if (current == &detail::HandlerTableChangeThunk) {
980 handlers_ =
static_cast<detail::HandlerTable*
>(current_data);
983 auto* table =
new detail::HandlerTable();
985 table->context = detail::BorrowBindingContext(handle_);
986 ulDOMDataBindingSetChangeCallback(handle_, &detail::HandlerTableChangeThunk, table,
987 &detail::ReleaseHandlerTable);
988 ulDOMDataBindingSetActionCallback(handle_, &detail::HandlerTableActionThunk, table,
989 &detail::ReleaseHandlerTable);
990 const bool registered = table->refs == 3;
991 detail::ReleaseHandlerTable(table);
1000 detail::HandlerTable* FindHandlers() {
1003 ULDOMDataChangeCallback current =
nullptr;
1004 void* current_data =
nullptr;
1005 ulDOMDataBindingGetChangeCallback(handle_, ¤t, ¤t_data);
1006 if (current == &detail::HandlerTableChangeThunk) {
1007 handlers_ =
static_cast<detail::HandlerTable*
>(current_data);
1013 ULDOMDataBinding handle_ =
nullptr;
1014 detail::HandlerTable* handlers_ =
nullptr;
1017 detail::InstanceAccess<T> access_;
ULDOMDataBinding raw() const
Get the C handle, for passing to the <Ultralight/CAPI/CAPI_DOMData.h> functions.
Definition Binding.h:946
Binding & OnAction(Fn &&fn) &
Register the handler for an action (replacing any earlier one).
Definition Binding.h:506
Binding & operator=(const Binding &)=delete
std::string name() const
Get the binding name.
Definition Binding.h:906
bool PostAction(const detail::ActionPayloadArg< T, Name > &payload)
Send one of this binding's actions with its payload, as if the page fired it.
Definition Binding.h:852
Binding()=default
Create an empty Binding.
Binding OnChange(A &&... args) &&
Register the handler for edits to an Editable field on a Binding you just created.
Definition Binding.h:452
bool PostAction()
Send one of this binding's actions that has no payload, as if the page fired it.
Definition Binding.h:806
Binding & OnChange(M method) &
Register a member function of the bound type as the handler for edits to an Editable field,...
Definition Binding.h:436
Binding & OnAction(H holder, M method) &
Register a member function as the handler for an action, called through a smart pointer.
Definition Binding.h:616
static Binding FromBorrowed(ULDOMDataBinding handle)
Wrap a C handle someone else owns (eg, another Binding's raw()), adding a reference.
Definition Binding.h:936
Binding RemoveActionHandler() &&
Remove the handler for an action on a Binding you just created.
Definition Binding.h:782
T described_type
The bound type.
Definition Binding.h:234
Binding & OnChange(H holder, M method) &
Register a member function as the handler for edits to an Editable field, called through a smart poin...
Definition Binding.h:419
Binding & RemoveActionHandler() &
Remove the handler for an action (if there is one).
Definition Binding.h:713
static Binding Adopt(ULDOMDataBinding handle)
Wrap a C handle you own, taking ownership of it.
Definition Binding.h:927
ULDOMDataBinding LeakRef()
Give up ownership of the C handle and return it.
Definition Binding.h:953
bool IsEmpty() const
Whether or not this Binding holds nothing (eg, after a move or a failed Context::Bind()).
Definition Binding.h:298
bool IsAlive() const
Whether or not this Binding still works.
Definition Binding.h:308
Binding & OnAction(M method) &
Register a member function of the bound type as the handler for an action, called on the bound instan...
Definition Binding.h:633
Binding(const Binding &)=delete
Binding & operator=(Binding &&other) noexcept
Move assignment (releases the binding this one held, then takes over other's).
Definition Binding.h:264
Binding & OnChange(Fn &&fn) &
Register the handler for edits to an Editable field (replacing any earlier one).
Definition Binding.h:350
Binding & OnChange(C &receiver, M method) &
Register a member function of receiver as the handler for edits to an Editable field.
Definition Binding.h:399
Binding(Binding &&other) noexcept
Move constructor (other becomes empty).
Definition Binding.h:249
Binding OnAction(A &&... args) &&
Register the handler for an action on a Binding you just created.
Definition Binding.h:649
friend class Context
Definition Binding.h:962
Binding & OnAction(C &receiver, M method) &
Register a member function of receiver as the handler for an action.
Definition Binding.h:597
Binding RemoveChangeHandler() &&
Remove the handler for edits to an Editable field on a Binding you just created.
Definition Binding.h:699
~Binding()
Destroy this Binding (see Binding Lifetime in the class description).
Definition Binding.h:280
Binding & RemoveChangeHandler() &
Remove the handler for edits to an Editable field (if there is one).
Definition Binding.h:662
Whether or not the DOM API can hold an object through H (ignoring const and references),...
Definition Holders.h:41
Whether or not T has a schema (ignoring const, volatile, and references), ie.
Definition TypeTraits.h:163
Data-binding API that connects native C++ data to HTML and CSS markup.
Definition ActionInfo.h:13
ValueKind
The kinds of value a schema entry holds.
Definition ValueTraits.h:27
@ Int64
A signed 64-bit integer (from any integer type).
Definition ValueTraits.h:29
consteval auto Field(const char *name, A accessor, Tags... tags)
Declare a field.
Definition Schema.h:551
@ kEntryFlags_Editable
The page may request changes to this field.
Definition Schema.h:100
Direct C++ access to modify page elements and handle events.
Root namespace for every public Ultralight type, function, and enumeration.
Traits template for describing a type to data bindings.
Definition TypeTraits.h:156