About the DOM API
Manipulate the DOM and handle events directly from native code.
On this page
The DOM API allows you to manipulate DOM elements and handle events directly in C++, offering higher performance and lower memory-usage than doing so via JavaScript.
Its syntax closely matches JavaScript for quick porting of existing code and general ease-of-use.
🚧 Preview API
The DOM API ships as a preview in 2.0. Names and behavior may still change after 2.0.
📘 Works Without JavaScript
The DOM API works even with JavaScript disabled (
ViewConfig::enable_javascript = false). That makes it well suited for games and embedded devices (see Embedded and Device UI).
Same Syntax as JavaScript
Updating an element in JavaScript looks like this:
const status = document.querySelector("#status");
status.textContent = "Connected";
status.classList.add("online");
status.style.backgroundColor = "purple";
The same four lines in C++ look nearly identical:
auto status = document.querySelector("#status");
status.textContent = "Connected";
status.classList.add("online");
status.style.backgroundColor = "purple";
Quick Start
Consider an HTML page with a status line and a button:
<p id="status">Offline</p>
<button id="save">Save</button>
You can wire up actions to be performed on the DOM via dom::Triggers callbacks:
#include <Ultralight/DOM.h>
// Define our DOM triggers:
dom::Triggers page;
page.OnDOMReady([](dom::Document document) {
auto status = document.getElementById("status");
status.textContent = "Connected";
status.classList.add("online");
});
page.On("#save", "click", [](dom::Element button) {
button.textContent = "Saved";
button.disabled = true;
});
// Attach to a View (persists across navigations)
if (page.AttachTo(view.get()))
view->LoadURL("file:///app.html");
Rules to Know
- Call the API on the Renderer's thread. Copying, moving, and destroying a handle is safe from any thread.
- A handle never keeps its page alive. Once the page navigates away, the handle's page is gone and anything you do with it fails safely (no null checks needed).
- The API never throws C++ exceptions. A call that fails returns an empty value (pass
dom::Checkedto get adom::Resultholding the reason).
Choosing Between the APIs
Native code can interact with a page through data bindings, the DOM API, or the JavaScript API. Each fits a different task.
| Task | What to Use |
|---|---|
| Show state on the page and keep it updated | Data bindings (ul-* attributes and dd::Context::Sync()). See About Data Bindings. |
| Make one-off DOM changes, like focusing an element, measuring layout, or creating nodes | The DOM API |
| Respond to clicks in native code with no page script | A ul-on action in data bindings, or a dom::Triggers listener when native code needs the element or the event |
| Call native code from page script and return a result | A js::API function. See About JavaScript Interop. |
| Send an event to page script | js::API::Emit() |
| Work with the DOM from another thread | Call Renderer::PostTask() to run a task on the Renderer's thread, then use the DOM API inside that task |
Both data bindings and the DOM API work with JavaScript disabled (ViewConfig::enable_javascript = false)— the JavaScript API requires it enabled.
Using Multiple APIs on One View
You can attach js::API, dom::Triggers, and dd::Context to the same View using AttachTo()— each stays attached until you call DetachFrom() or destroy the owning object.
| Behavior | js::API | dom::Triggers | dd::Context |
|---|---|---|---|
| Thread to attach from | Renderer's thread | Renderer's thread | Any thread |
| When current page gets it | Right away | Right away (DOM-ready hooks run inside AttachTo()) |
During a later Renderer::Update() |
| Subframes | Supported with js::AllFrames |
Supported with dom::AllFrames |
Never (main frame only) |
After DetachFrom() |
Events stop right away, and the page keeps bindings until it navigates | Listeners stop right away | Page drops bindings during a later Renderer::Update() |
🚧 Match Origin Rules Across APIs
All three APIs accept origin rules and default to local content (
file://pages andView::LoadHTML()). If you use custom origin rules, pass the same rules to every API attached to the View so pages do not receive one set of bindings while missing another.
For details on attaching and configuring each system, see Extending JavaScript with Native API, DOM Triggers and Navigation, and Binding Threads and Lifetime.
Where to Go Next
- DOM Handles and Errors — Check handle validity, handle errors, and enable runtime diagnostics.
- Finding and Modifying Elements — Query elements with CSS selectors, update content and attributes, and build nodes.
- Walking the Node Tree — Traverse node hierarchies, inspect text and comment nodes, and navigate frames.
- Reading and Writing Styles — Read computed CSS styles and set inline style properties.
- Element Geometry and Scrolling — Measure element bounds, control scrolling, inspect the viewport, and hit-test coordinates.
- Forms and Inputs — Read and set form control values, work with select elements, and handle form submission.
- Handling DOM Events — Register event listeners, read event objects, and dispatch custom events.
- DOM Triggers and Navigation — Keep event listeners active across navigations and manage back-forward cache restores.
- Ranges and Selection — Create DOM ranges, inspect user selections, and measure text dimensions.
For C applications, see DOM Access in C.