docs
Loading...
Searching...
No Matches
DocumentFragment.h
Go to the documentation of this file.
1///
2/// Copyright (C) 2026 Ultralight, Inc. All rights reserved.
3/// A license is required for commercial use. https://ultralig.ht
4///
5#pragma once
6#include <Ultralight/CAPI/CAPI_DOMDocument.h>
8
9#include <type_traits>
10#include <utility>
11
12namespace ultralight {
13namespace dom {
14
15///
16/// A container for assembling DOM nodes off the page.
17///
18/// A DocumentFragment is a DOM node that holds child nodes outside the document tree. It serves as
19/// temporary storage for building content in native code before inserting it into the page.
20///
21/// You can build list items from C++ data off the page and insert them in one call:
22///
23/// ```
24/// dom::DocumentFragment items = document.createDocumentFragment();
25/// for (const Score& score : scores) {
26/// dom::Element entry = document.createElement("li");
27/// entry.append(score.player, ": ", std::to_string(score.points));
28/// items.appendChild(entry);
29/// }
30/// document.getElementById("scores").replaceChildren(items);
31/// ```
32///
33/// ## Inserting a Fragment
34///
35/// Because DocumentFragment inherits from dom::Node, you can pass it to any DOM insertion call.
36///
37/// Inserting a fragment moves all its children into the page in order, leaving the fragment empty.
38/// The handle remains valid after insertion, so you can fill it with new nodes and insert it again.
39///
40/// @see dom::Document::createDocumentFragment(), dom::Element::replaceChildren(), dom::Node,
41/// dom::Range::extractContents()
42///
43class DocumentFragment : public Node {
44 public:
45 ///
46 /// Create an empty DocumentFragment.
47 ///
48 /// @see Document::createDocumentFragment()
49 ///
50 DocumentFragment() = default;
51
52 ///
53 /// Copy constructor (both handles refer to the same fragment).
54 ///
55 /// @param other The DocumentFragment to copy.
56 ///
57 DocumentFragment(const DocumentFragment& other) : Node(other) {}
58
59 ///
60 /// Move constructor (`other` becomes empty).
61 ///
62 /// @param other The DocumentFragment to move from.
63 ///
64 DocumentFragment(DocumentFragment&& other) noexcept : Node(std::move(other)) {}
65
66 ///
67 /// Assignment (copies or moves).
68 ///
69 /// @param other The DocumentFragment to assign from.
70 ///
71 /// @return Returns this DocumentFragment.
72 ///
74 std::swap(detail_.handle, other.detail_.handle);
75 return *this;
76 }
77
78 // Validity (operator bool, IsEmpty, IsAlive), IsSame(), destruction, and the node-level
79 // surface (childNodes(), firstChild(), hasChildNodes(), textContent, ...) are inherited from
80 // Node: a DocumentFragment IS a Node, and the handle families share one object underneath.
81
82 ///
83 /// Append an element to the fragment (appendChild).
84 ///
85 /// If the element is already in the page (or in another fragment), it moves here.
86 ///
87 /// @param child The element to append.
88 ///
89 /// @return Returns the appended element (empty if this fragment or `child` is empty or its page
90 /// is gone).
91 ///
92 Element appendChild(const Element& child) const {
93 return ulDOMFragmentAppendElement(raw(), child.raw(), nullptr) ? child : Element();
94 }
95
96 ///
97 /// Same as appendChild(), but returns a Result with the reason for a failure.
98 ///
99 /// @return Returns the appended element (it fails only when this fragment or `child` is empty
100 /// or its page is gone).
101 ///
102 [[nodiscard]] Result<Element> appendChild(const Element& child, Checked_t) const {
103 if (IsEmpty() || child.IsEmpty())
104 return detail::EmptyHandleError();
105 detail::ErrorScope error;
106 if (ulDOMFragmentAppendElement(raw(), child.raw(), error.out()))
107 return child;
108 return Unexpected<Error>(error.TakeError());
109 }
110
111 ///
112 /// Append a node of any kind to the fragment (appendChild).
113 ///
114 /// Use this for text and comment nodes (eg, from Document::createTextNode()). An Element
115 /// argument goes to the overload that returns an Element.
116 ///
117 /// @param child The node to append. If it's already in the page (or in another fragment), it
118 /// moves here.
119 ///
120 /// @return Returns `child` (empty if nothing was appended, eg, because `child` is a doctype, or
121 /// a handle is empty or its page is gone).
122 ///
123 Node appendChild(const Node& child) const {
124 ULDOMNodeOrString item = detail::NodeItem(child);
125 return ulDOMFragmentAppend(raw(), &item, 1, nullptr) ? child : Node();
126 }
127
128 ///
129 /// Same as appendChild() with a Node, but returns a Result with the reason for a failure.
130 ///
131 /// @return Returns `child`. Fails with a HierarchyRequestError if `child` can't go in a
132 /// fragment (eg, a doctype).
133 ///
134 [[nodiscard]] Result<Node> appendChild(const Node& child, Checked_t) const {
135 if (IsEmpty() || child.IsEmpty())
136 return detail::EmptyHandleError();
137 ULDOMNodeOrString item = detail::NodeItem(child);
138 detail::ErrorScope error;
139 if (ulDOMFragmentAppend(raw(), &item, 1, error.out()))
140 return child;
141 return Unexpected<Error>(error.TakeError());
142 }
143
144 ///
145 /// Add nodes and strings to the end of the fragment (append).
146 ///
147 /// Pass any mix of nodes (including elements) and strings. Each string is added as a new text
148 /// node (it's never parsed as markup).
149 ///
150 /// @param nodes The nodes and strings to add, in order. A node that's already in the page (or
151 /// in another fragment) moves here.
152 ///
153 template <typename... T>
154 requires((detail::NodeOrString<T> && ...))
155 void append(T&&... nodes) const {
156 auto items = detail::NodeOrStringItems(std::forward<T>(nodes)...);
157 ulDOMFragmentAppend(raw(), items.data(), items.size(), nullptr);
158 }
159
160 ///
161 /// Same as append(), but returns a Result with the reason for a failure.
162 ///
163 /// dom::Checked comes first here, before the nodes (`batch.append(dom::Checked, row, "x")`).
164 ///
165 /// @return Returns success. Fails with a HierarchyRequestError if one of the nodes can't go in
166 /// a fragment (eg, a doctype).
167 ///
168 template <typename... T>
169 requires((detail::NodeOrString<T> && ...))
170 [[nodiscard]] Result<void> append(Checked_t, T&&... nodes) const {
171 auto items = detail::NodeOrStringItems(std::forward<T>(nodes)...);
172 if (IsEmpty() || detail::HasEmptyItem(items))
173 return detail::EmptyHandleError();
174 detail::ErrorScope error;
175 if (ulDOMFragmentAppend(raw(), items.data(), items.size(), error.out()))
176 return {};
177 return Unexpected<Error>(error.TakeError());
178 }
179
180 ///
181 /// Add nodes and strings to the start of the fragment, before its first child (prepend).
182 ///
183 /// Works like append().
184 ///
185 /// @param nodes The nodes and strings to add, in order. A node that's already in the page (or
186 /// in another fragment) moves here.
187 ///
188 template <typename... T>
189 requires((detail::NodeOrString<T> && ...))
190 void prepend(T&&... nodes) const {
191 auto items = detail::NodeOrStringItems(std::forward<T>(nodes)...);
192 ulDOMFragmentPrepend(raw(), items.data(), items.size(), nullptr);
193 }
194
195 ///
196 /// Same as prepend(), but returns a Result with the reason for a failure.
197 ///
198 /// dom::Checked comes first here, before the nodes (`batch.prepend(dom::Checked, row, "x")`).
199 ///
200 /// @return Returns success. Fails with a HierarchyRequestError if one of the nodes can't go in
201 /// a fragment (eg, a doctype).
202 ///
203 template <typename... T>
204 requires((detail::NodeOrString<T> && ...))
205 [[nodiscard]] Result<void> prepend(Checked_t, T&&... nodes) const {
206 auto items = detail::NodeOrStringItems(std::forward<T>(nodes)...);
207 if (IsEmpty() || detail::HasEmptyItem(items))
208 return detail::EmptyHandleError();
209 detail::ErrorScope error;
210 if (ulDOMFragmentPrepend(raw(), items.data(), items.size(), error.out()))
211 return {};
212 return Unexpected<Error>(error.TakeError());
213 }
214
215 // --- Interop with the C API (most embedders never touch raw handles) -------------------
216 //
217 // These deliberately hide Node's same-named members with fragment-typed versions, as
218 // Element's do: ULDOMNode and ULDOMFragment name one object underneath, so the casts below
219 // are exact by construction.
220
221 ///
222 /// Wrap a C handle you own, taking ownership of it.
223 ///
224 /// @param handle A handle from the C API that you would otherwise destroy with
225 /// ulDestroyDOMFragment() (NULL gives an empty DocumentFragment).
226 ///
227 /// @return Returns a DocumentFragment that destroys `handle` when it's done.
228 ///
229 static DocumentFragment Adopt(ULDOMFragment handle) { return DocumentFragment(handle); }
230
231 ///
232 /// Wrap a C handle the library owns, adding a reference.
233 ///
234 /// @param handle The borrowed handle (NULL gives an empty DocumentFragment).
235 ///
236 /// @return Returns a DocumentFragment with its own reference to `handle`.
237 ///
238 static DocumentFragment FromBorrowed(ULDOMFragment handle) {
239 return DocumentFragment(handle ? ulCreateDOMFragmentRef(handle) : nullptr);
240 }
241
242 ///
243 /// Get the C handle, for passing to the C API (eg, ulDOMElementAppendFragment()).
244 ///
245 /// @return Returns the handle (NULL for an empty DocumentFragment). This DocumentFragment still
246 /// owns it, so don't destroy it.
247 ///
248 ULDOMFragment raw() const { return reinterpret_cast<ULDOMFragment>(detail_.handle); }
249
250 ///
251 /// Give up ownership of the C handle and return it. This DocumentFragment becomes empty.
252 ///
253 /// @return Returns the handle. You must call ulDestroyDOMFragment() when finished.
254 ///
255 ULDOMFragment LeakRef() { return reinterpret_cast<ULDOMFragment>(Node::LeakRef()); }
256
257 protected:
258 explicit DocumentFragment(ULDOMFragment handle) : Node(reinterpret_cast<ULDOMNode>(handle)) {}
259};
260
261// DocumentFragment adds no data to Node: its copy, move, and assignment transfer only the Node
262// handle, and a DocumentFragment passed or sliced to a Node loses nothing.
263static_assert(std::is_standard_layout_v<DocumentFragment>,
264 "DocumentFragment must stay standard-layout");
265static_assert(sizeof(DocumentFragment) == sizeof(Node),
266 "DocumentFragment must not declare data members of its own");
267
268inline void Element::appendChild(const DocumentFragment& fragment) const {
269 ulDOMElementAppendFragment(raw(), fragment.raw(), nullptr);
270}
271
273 if (IsEmpty() || fragment.IsEmpty())
274 return detail::EmptyHandleError();
275 detail::ErrorScope error;
276 if (ulDOMElementAppendFragment(raw(), fragment.raw(), error.out()))
277 return {};
278 return Unexpected<Error>(error.TakeError());
279}
280
282 return DocumentFragment::Adopt(ulDOMNodeAsFragment(detail_.handle));
283}
284
285} // namespace dom
286} // namespace ultralight
A container for assembling DOM nodes off the page.
Definition DocumentFragment.h:43
void prepend(T &&... nodes) const
Add nodes and strings to the start of the fragment, before its first child (prepend).
Definition DocumentFragment.h:190
DocumentFragment()=default
Create an empty DocumentFragment.
ULDOMFragment LeakRef()
Give up ownership of the C handle and return it.
Definition DocumentFragment.h:255
Element appendChild(const Element &child) const
Append an element to the fragment (appendChild).
Definition DocumentFragment.h:92
Result< void > append(Checked_t, T &&... nodes) const
Same as append(), but returns a Result with the reason for a failure.
Definition DocumentFragment.h:170
Result< Node > appendChild(const Node &child, Checked_t) const
Same as appendChild() with a Node, but returns a Result with the reason for a failure.
Definition DocumentFragment.h:134
Result< Element > appendChild(const Element &child, Checked_t) const
Same as appendChild(), but returns a Result with the reason for a failure.
Definition DocumentFragment.h:102
static DocumentFragment Adopt(ULDOMFragment handle)
Wrap a C handle you own, taking ownership of it.
Definition DocumentFragment.h:229
DocumentFragment(ULDOMFragment handle)
Definition DocumentFragment.h:258
Result< void > prepend(Checked_t, T &&... nodes) const
Same as prepend(), but returns a Result with the reason for a failure.
Definition DocumentFragment.h:205
DocumentFragment & operator=(DocumentFragment other) noexcept
Assignment (copies or moves).
Definition DocumentFragment.h:73
Node appendChild(const Node &child) const
Append a node of any kind to the fragment (appendChild).
Definition DocumentFragment.h:123
DocumentFragment(const DocumentFragment &other)
Copy constructor (both handles refer to the same fragment).
Definition DocumentFragment.h:57
DocumentFragment(DocumentFragment &&other) noexcept
Move constructor (other becomes empty).
Definition DocumentFragment.h:64
static DocumentFragment FromBorrowed(ULDOMFragment handle)
Wrap a C handle the library owns, adding a reference.
Definition DocumentFragment.h:238
ULDOMFragment raw() const
Get the C handle, for passing to the C API (eg, ulDOMElementAppendFragment()).
Definition DocumentFragment.h:248
void append(T &&... nodes) const
Add nodes and strings to the end of the fragment (append).
Definition DocumentFragment.h:155
A handle to an element on a page.
Definition Element.h:142
Element appendChild(const Element &child) const
Add an element as the last child of this element (appendChild).
Definition Element.h:676
Element()
Create an empty Element.
Definition Element.h:368
ULDOMElement raw() const
Get the C handle, for passing to the <Ultralight/CAPI/CAPI_DOMElement.h> functions.
Definition Element.h:2006
detail::NodeStorage detail_
Internal storage (not part of the API).
Definition Node.h:166
Node()
Create an empty Node.
Definition Node.h:171
ULDOMNode LeakRef()
Give up ownership of the C handle and return it.
Definition Node.h:546
DocumentFragment AsFragment() const
Get this node as a DocumentFragment.
Definition DocumentFragment.h:281
bool IsEmpty() const
Whether or not this Node is empty (it holds no handle).
Definition Node.h:216
Direct C++ access to modify page elements and handle events.
Expected< T, Error > Result
The result of a DOM operation that can fail (either a T or a dom::Error).
Definition Error.h:277
Root namespace for every public Ultralight type, function, and enumeration.
The type of dom::Checked.
Definition Error.h:282