lightview
Version:
A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation
1,153 lines (1,053 loc) • 51.5 kB
HTML
<script src="/lightview-router.js?base=/index.html"></script>
<style>
/* Syntax Tabs */
.syntax-tabs {
display: inline-flex;
gap: 0.25rem;
padding: 0.25rem;
background: #f1f5f9;
border-radius: 8px;
border: 1px solid #e2e8f0;
margin-bottom: 1rem;
}
[data-theme="dark"] .syntax-tabs {
background: #1e293b;
border-color: #334155;
}
.syntax-tab {
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
border: none;
background: transparent;
border-radius: 6px;
cursor: pointer;
color: #64748b;
transition: all 0.2s;
}
.syntax-tab:hover {
background: #ffffff;
color: #1e293b;
}
[data-theme="dark"] .syntax-tab:hover {
background: #334155;
color: #f1f5f9;
}
.syntax-tab-active {
background: #ffffff !important;
color: #6366f1 !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
[data-theme="dark"] .syntax-tab-active {
background: #0f172a !important;
color: #818cf8 !important;
}
</style>
<script>
globalThis.switchCDOMTab = (tabId) => {
const tabs = ['jprx', 'cdomc', 'operators'];
tabs.forEach(t => {
const btn = document.getElementById(`tab-btn-${t}`);
const pane = document.getElementById(`pane-${t}`);
if (t === tabId) {
btn.classList.add('syntax-tab-active');
pane.style.display = 'block';
} else {
btn.classList.remove('syntax-tab-active');
pane.style.display = 'none';
}
});
};
</script>
<div class="docs-layout">
<aside class="docs-sidebar" src="/docs/cdom-nav.html"></aside>
<main class="docs-content">
<h1>cDOM (Computational DOM)</h1>
<p class="text-secondary" style="font-size: 1.125rem;">
A declarative, expression-based way to build reactive UIs.
</p>
<div class="experimental-notice"
style="margin: 1.5rem 0; padding: 1.25rem; border-radius: var(--site-radius); background: var(--site-accent-light); border: 1px solid var(--site-warning); color: var(--site-text);">
<div style="display: flex; gap: 0.75rem; align-items: flex-start;">
<span style="font-size: 1.5rem;">⚠️</span>
<div>
<strong style="display: block; margin-bottom: 0.25rem; font-size: 1.1rem;">Experimental
Feature</strong>
<p style="margin: 0; font-size: 0.95rem; opacity: 0.9;">
cDOM and JPRX are currently in an <strong>experimental</strong> phase. The expression syntax,
helper functions, and integration patterns are subject to change as we continue to evolve the
library. Use with caution in production environments.
</p>
<p
style="margin: 0.75rem 0 0 0; padding-top: 0.75rem; border-top: 1px solid var(--site-border); font-size: 0.95rem; opacity: 0.9;">
<strong>v2.5.0 Syntax Update:</strong> Starting with Lightview v2.5.0 and JPRX v1.4.0,
expressions use a new wrapper syntax:
<code
style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">=(expr)</code>
for JPRX and
<code
style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">#(xpath)</code>
for XPath.
The legacy prefix-only syntax (e.g., <code
style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">=path</code>)
will be <strong>fully deprecated after March 31, 2026</strong>.
</p>
</div>
</div>
</div>
<!-- ===== OVERVIEW ===== -->
<h2 id="overview">Overview</h2>
<p>
The <strong>Computational DOM (cDOM)</strong> is a way to describe user interfaces using reactive
expressions
that feel as natural as spreadsheet formulas. Instead of writing JavaScript logic to update your UI,
you define the <em>relationships</em> between your data and your elements.
</p>
<p>
cDOM was designed so that both developers and LLMs can generate safe user interfaces that humans
can interact with across platforms.
</p>
<p>
cDOM uses <strong>JPRX (JSON Pointer Reactive eXpressions)</strong> as its expression language. JPRX
extends <a href="https://www.rfc-editor.org/rfc/rfc6901" target="_blank">JSON Pointer (RFC 6901)</a>
with reactivity, relative paths, and helper functions. cDOM also supports <strong>standard XPath</strong>
for powerful DOM navigation during element construction. Together with deep integration for
<a href="https://json-schema.org/" target="_blank">JSON Schema</a> (Standard Draft 7+), cDOM provides
industrial-strength data validation and automatic type coercion.
</p>
<!-- ===== SIMPLE EXAMPLE ===== -->
<h2 id="simple-example">Simple Example</h2>
<p>Here's a simple counter in cDOM. Notice how the UI is purely declarative — no JavaScript logic:</p>
<div id="simple-counter-demo">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
type: 'module',
height: '200px',
autoRun: true,
controls: false
});
</script><code>await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;
const cdom = `{
"div": {
"id": "Counter",
"onmount": "=state({ count: 0 }, { name: 'local', schema: 'auto', scope: $this })",
"children": [
{ "h2": "#(../../@id)" }, // XPath: text node -> h2 -> div
{ "p": ["Count: ", "=(/local/count)"] },
{ "button": { "onclick": "=(++/local/count)", "children": ["+"] } },
{ "button": { "onclick": "=(--/local/count)", "children": ["-"] } }
]
}
}`;
$('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
</div>
<p>
This defines a counter with:
</p>
<ul>
<li><code>onmount</code> — A Lightview lifecycle hook where we initialize state.</li>
<li><code>#../../@id</code> — An XPath expression that extracts the <code>id</code> from the grandparent
element.</li>
<li><code>=state(...)</code> — An initializer that creates local reactive state.</li>
<li><code>scope: $this</code> — Explicitly attaches the state to the current element.</li>
<li><code>=/local/count</code> — A path that reactively displays the count value.</li>
</ul>
<p style="font-size: 0.95rem; font-style: italic; color: var(--site-text-secondary);">
<strong>Note:</strong> In cDOM, expressions use a wrapper syntax for clarity: <code>#(xpath)</code> for
<strong>XPath</strong> expressions that
navigate and extract data from the DOM, and <code>=(expr)</code> for <strong>JPRX</strong>
expressions that navigate or apply functions to reactive state. Legacy syntax without parentheses is still
supported.
</p>
<p>
The UI automatically updates whenever <code>count</code> changes — no manual DOM manipulation required. It
also uses XPath to navigate the DOM structure during construction.
</p>
<!-- ===== ADVANTAGES ===== -->
<h2 id="advantages">Advantages</h2>
<div class="feature-grid" style="margin: 2rem 0;">
<div class="feature-card">
<h3 class="feature-title">🛡️ Enhanced Security</h3>
<p>
cDOM strictly avoids <code>eval()</code> and direct HTML injection. By using a custom
high-performance parser and a registry of pre-defined helper functions, it provides a safe sandbox
for dynamic content, making it highly resistant to XSS attacks.
</p>
</div>
<div class="feature-card">
<h3 class="feature-title">🤖 LLM Friendly</h3>
<p>
Large Language Models excel at generating structured data and formulaic expressions. cDOM's
declarative nature and concise syntax make it far easier for AI to generate correct,
bug-free UI components compared to traditional JavaScript-heavy frameworks.
</p>
</div>
</div>
<!-- ===== XPATH NAVIGATION ===== -->
<h2 id="xpath-navigation">Using XPath</h2>
<p>
cDOM allows elements to navigate and reference the DOM structure during construction using standard
<strong>XPath</strong>. This is strictly a <strong>cDOM feature</strong> (not JPRX) used for structural
navigation.
</p>
<p>
XPath is incredibly useful for keeping your definitions <strong>DRY (Don't Repeat Yourself)</strong> by
referencing existing attributes instead of repeating values.
</p>
<div id="xpath-demo">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
type: 'module',
height: '180px',
autoRun: true,
controls: false
});
</script><code>await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;
const cdom = `{
// The cdom below uses a compressed syntax. It does not require quotes
// around properties or JPRX/XPath values and supports comments.
// It is JSON-like, but not JSON.
div: {
id: "profile-container",
class: "card",
data-theme: "dark",
children: [
{ h3: "User Profile" },
{ button: {
id: "7",
// XPath #(../@id) gets the "7" from this button's id
// XPath #(../../@id) gets "profile-container" from the g-parent div
children: ["Button ", #(../@id), " in section ", #(../../@id)]
}}
]
}
}`;
$('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
</div>
<p>
In the example above, the button's text is derived entirely from its own <code>id</code> and its
parent's <code>id</code> using <code>#(../@id)</code> and <code>#(../../@id)</code>.
</p>
<p>
For more details, see the <a href="/docs/cdom-xpath.html">Full XPath Documentation</a>.
</p>
<!-- ===== JPRX INTRODUCTION ===== -->
<h2 id="JPRX">JPRX (JSON Pointer Reactive eXpressions)</h2>
<p>
<strong>JPRX</strong> is the expression language that powers cDOM. It extends
<a href="https://www.rfc-editor.org/rfc/rfc6901" target="_blank">JSON Pointer (RFC 6901)</a>
with reactivity, relative paths, and helper functions.
</p>
<h3 id="JPRX-delimiters">Expression Syntax</h3>
<p>
JPRX expressions use a <strong>wrapper syntax</strong> for unambiguous parsing. The recommended syntax is
<code>=(expression)</code>:
</p>
<table class="api-table">
<thead>
<tr>
<th>Syntax</th>
<th>Purpose</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>=(/path)</code></td>
<td>Access a path in the global registry</td>
<td><code>=(/users/0/name)</code></td>
</tr>
<tr>
<td><code>=(function(...))</code></td>
<td>Call a helper function</td>
<td><code>=(sum(/items...price))</code></td>
</tr>
<tr>
<td><code>#(xpath)</code></td>
<td>XPath expression for DOM navigation</td>
<td><code>#(../../@id)</code></td>
</tr>
</tbody>
</table>
<p>
The wrapper syntax <code>=()</code> and <code>#()</code> makes expressions unambiguous and eliminates
conflicts with literal strings.
Legacy syntax without parentheses (e.g., <code>=/path</code>) is still supported for backward compatibility.
</p>
<h3 id="JPRX-literal-strings">Literal Strings</h3>
<p>
With the new wrapper syntax, you no longer need escape sequences. Any string that doesn't match the wrapper
pattern
is treated as a literal:
</p>
<div class="code-block" style="margin-bottom: 1rem;">
<pre><code>// JPRX expression (wrapped):
{ "p": "=(/user/name)" } // Resolves the path /user/name
// Literal strings (no wrapper):
{ "p": "=E=mc²" } // Renders as "=E=mc²"
{ "p": "=42" } // Renders as "=42"
{ "p": "#ff0000" } // Renders as "#ff0000" (hex color)</code></pre>
</div>
<p>
The wrapper syntax eliminates ambiguity: <code>=(expr)</code> is always an expression, while strings without
the wrapper pattern are always literals.
</p>
<h3 id="JPRX-anatomy">Anatomy of a Path</h3>
<p>
Inside a JPRX expression, paths follow JSON Pointer with these extensions:
</p>
<table class="api-table">
<thead>
<tr>
<th>Path</th>
<th>Description</th>
<th>Origin</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/users/0/name</code></td>
<td>Absolute path (property access)</td>
<td>JSON Pointer</td>
</tr>
<tr>
<td><code>/items[2]</code></td>
<td>Array index (bracket notation)</td>
<td>JPRX extension</td>
</tr>
<tr>
<td><code>./child</code></td>
<td>Relative to current context</td>
<td>JPRX extension</td>
</tr>
<tr>
<td><code>../sibling</code></td>
<td>Parent context access</td>
<td>JPRX extension</td>
</tr>
<tr>
<td><code>/items...price</code></td>
<td>Extract <code>price</code> from all items (explosion)</td>
<td>JPRX extension</td>
</tr>
</tbody>
</table>
<h4>Function Calls</h4>
<p>
Paths can contain function calls to transform data:
</p>
<div class="code-block">
<pre><code>=currency(sum(map(filter(/orders, eq(_/status, 'paid')), _/total)...))</code></pre>
</div>
<ul>
<li><strong>/orders</strong>: Access the <code>orders</code> collection (JSON Pointer)</li>
<li><strong>filter(..., eq(...))</strong>: Keep only "paid" orders</li>
<li><strong>map(..., _/total)</strong>: Extract the <code>total</code> from each order</li>
<li><strong>...</strong>: Explode the array into individual arguments</li>
<li><strong>sum(...)</strong>: Add up all the totals</li>
<li><strong>=currency(...)</strong>: Format as currency string</li>
</ul>
<h3 id="JPRX-placeholders">Placeholders</h3>
<table class="api-table">
<thead>
<tr>
<th>Placeholder</th>
<th>Description</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>_</code></td>
<td>Current item during iteration</td>
<td><code>=map(/items, _/name)</code></td>
</tr>
<tr>
<td><code>$event</code></td>
<td>Event object in handlers</td>
<td><code>=set(=/selected, $event/target/value)</code></td>
</tr>
</tbody>
</table>
<h3 id="JPRX-explosion">Explosion & Mapping (<code>...</code>)</h3>
<p>
The <code>...</code> operator is a powerful JPRX extension used for mapping properties from arrays and
spreading array elements as function arguments.
</p>
<h4>Mapping & Auto-Explosion: <code>path...property</code></h4>
<p>
When used between a path to an array and a property name, it acts as a shorthand for mapping. It extracts
the specified property from every object in the array. <strong>Inside a function call, it automatically
spreads (explodes) the resulting values as individual arguments.</strong>
</p>
<div class="code-block" style="margin-bottom: 1.5rem;">
<pre><code>// Correct: Maps 'price' and automatically spreads results as arguments to sum()
=sum(/cart/items...price)
// Mapping also works for direct display (returns an array)
{ p: ["Prices: ", =/cart/items...price] }</code></pre>
</div>
<h4>Argument Spreading: <code>path...</code></h4>
<p>
When an array path is followed by a trailing <code>...</code> at the end of the path expression (and no
property name follows), the array elements are spread as individual arguments. Use this when you have a
direct reference to an array.
</p>
<div class="code-block">
<pre><code>// Passes each number in the array as a separate argument
=sum(/listOfNumbers...)</code></pre>
</div>
<div class="warning-notice"
style="margin-top: 1.5rem; padding: 1rem; background: rgba(239, 68, 68, 0.1); border-left: 4px solid #ef4444; border-radius: 4px;">
<strong>Warning:</strong> Do not combine infix mapping with trailing dots. <code>/items...price...</code> is
invalid because the trailing dots are interpreted as part of the property name (looking for a property
literally named "price...").
</div>
<!-- ===== COMPARISON TO EXCEL ===== -->
<h2 id="comparison">Comparison to Excel</h2>
<p>
Think of your UI as a spreadsheet. In Excel, if Cell C1 has the formula <code>=A1+B1</code>, C1
updates automatically whenever A1 or B1 changes.
</p>
<p>
cDOM brings this exact paradigm to the web. Every attribute and text node can be a "cell" that
computes its value based on other "cells" (reactive signals).
</p>
<table class="api-table">
<thead>
<tr>
<th>Feature</th>
<th>Excel</th>
<th>cDOM / JPRX</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Reactive Unit</strong></td>
<td>Cell</td>
<td>Signal / State Proxy</td>
</tr>
<tr>
<td><strong>Formulas</strong></td>
<td><code>=SUM(A1:A10)</code></td>
<td><code>=sum(/items...price)</code></td>
</tr>
<tr>
<td><strong>Path Resolution</strong></td>
<td>Cell References (A1, $B$2)</td>
<td>JSON Pointer paths (./name, =/global)</td>
</tr>
<tr>
<td><strong>Recalculation</strong></td>
<td>Automatic on change</td>
<td>Automatic on change</td>
</tr>
</tbody>
</table>
<!-- ===== LIGHTVIEW INTEGRATION ===== -->
<h2 id="integration">Lightview Integration</h2>
<p>
cDOM integrates seamlessly with Lightview's existing DOM formats. You can use JPRX expressions
in any of Lightview's hypermedia formats just by loading <code>lightview-cdom.js</code>:
</p>
<h3>vDOM (Virtual DOM)</h3>
<p>JPRX expressions work directly in vDOM arrays or objects:</p>
<div class="code-block">
<pre><code>// vDOM formal object
{
tag: "div",
attributes: { class: "counter" },
children: [
{ tag: "p", children: ["Count: ", "=/local/count"] },
{ tag: "button", attributes: { onclick: "=++/local/count }, children: ["+"] }
]
}</code></pre>
</div>
<h3>oDOM (Object DOM)</h3>
<p>JPRX expressions work in oDOM objects:</p>
<div class="code-block">
<pre><code>{
div: {
class: "counter",
children: [
{ p: { children: ["Count: ", "=/local/count] } },
{ button: { onclick: "=++/local/count", children: ["+"] } }
]
}
}</code></pre>
</div>
<h3>cDOM Shorthand with JPRX</h3>
<p>cDOM's concise shorthand syntax. Note that cDOM does not require quoting attribute names or JPRX
expressions like vDOM and oDOM do and you can include comments:</p>
<div class="code-block">
<pre><code>{
div: {
class: "counter",
// This is a JPRX comment
children: [
{ p: ["Count: ", =/local/count] },
{ button: { onclick: =++/local/count, children: ["+"] } }
]
}
}</code></pre>
</div>
<!-- ===== DOM PATCHES ===== -->
<h2 id="dom-patches">DOM Patches & Decentralized Layouts</h2>
<p>
cDOM supports <strong>Decentralized Layouts</strong>, allowing components to "move themselves" to their
rightful
home in the DOM upon being created. This is especially powerful for LLM-driven streaming UIs.
</p>
<h3 id="helpers-move">=move(target, location?)</h3>
<p>
The <code>=move</code> helper (typically used in <code>onmount</code>) teleports the host element to a
different part of the document.
</p>
<div class="code-block">
<pre><code>{
div: {
id: "weather-widget",
onmount: =move('#sidebar', 'afterbegin'),
content: "Sunny, 75°F"
}
}</code></pre>
</div>
<h4>Identity & Patching</h4>
<p>
If the moving element has a unique <code>id</code> and an element with that same ID already exists at the
destination,
the existing element is <strong>replaced</strong>. This turns <code>=move</code> into an idempotent
<strong>patch</strong>
command — simply stream the new version of the component with the same ID, and it will update the UI
automatically.
</p>
<h4>Placement Locations</h4>
<table class="api-table">
<thead>
<tr>
<th>Location</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>inner</code> / <code>shadow</code></td>
<td>Replaces all children of the target.</td>
</tr>
<tr>
<td><code>afterbegin</code> / <code>prepend</code></td>
<td>Inserts at the start of the target.</td>
</tr>
<tr>
<td><code>beforeend</code> / <code>append</code></td>
<td>Inserts at the end of the target (default).</td>
</tr>
<tr>
<td><code>outer</code> / <code>replace</code></td>
<td>Replaces the target node itself.</td>
</tr>
<tr>
<td><code>beforebegin</code> / <code>afterend</code></td>
<td>Inserts before or after the target node.</td>
</tr>
</tbody>
</table>
<h2 id="state-binding">State & Binding</h2>
<p>
cDOM does not use attribute directives for state it uses lifecycle events and helpers instead.
</p>
<h3 id="lifecycle-state">Lifecycle State</h3>
<p>
In Lightview, you initialize state within the <code>onmount</code> hook. The <code>=state</code> and
<code>=signal</code> helpers allow you to create reactive data that is tied to the DOM structure.
</p>
<h4 id="scope-resolution">Scope & Name Resolution</h4>
<p>
When JPRX encounters a name (e.g., <code>=/profile/name</code>), it doesn't just look in a global registry.
Instead, it performs an <strong>up-tree search</strong>:
</p>
<ol>
<li>It starts at the element where the expression is defined.</li>
<li>It looks for a registered signal or state with that name.</li>
<li>If not found, it moves to the parent element and repeats the search.</li>
<li>This continues all the way to the <code>document</code> root, finally checking the global registry if
needed.</li>
</ol>
<h4>The <code>$this</code> Placeholder</h4>
<p>
The <code>$this</code> keyword is a special placeholder that represents the <strong>current
element</strong>. When used in the <code>scope</code> option of <code>=state</code> or
<code>=signal</code>, it instructs Lightview to register the name specifically at that element's level in
the DOM.
</p>
<div class="code-block" style="margin-bottom: 1.5rem;">
<pre><code>// Scoping state to the current element creates a local namespace
{ "onmount": "=state({ count: 0 }, { name: 'local', scope: $this })" }</code></pre>
</div>
<p>
This mechanism is what allows you to create <strong>reusable components</strong>. Each instance of a
component can have its own "local" state because the search for <code>local</code> will stop at the first
element it finds that has it registered—typically the root of the component instance.
</p>
<h4>Using a Registered Schema</h4>
<div class="code-block" style="margin-bottom: 1rem;">
<pre><code>// 1. Register centrally in JS
Lightview.registerSchema('User', { name: 'string', age: 'number' });
// 2. Use in cDOM
{ "onmount": "=state({}, { name: 'profile', schema: 'User', scope: $this })" }</code></pre>
</div>
<h4>Standard Schema Behaviors</h4>
<p>
When using the <code>schema</code> option, you can use these standard behaviors:
</p>
<ul style="margin-bottom: 2rem;">
<li><strong>"auto"</strong>: Infers a fixed schema from the initial value. Strict type checking (throws on
mismatch).</li>
<li><strong>"dynamic"</strong>: Like auto, but allows adding new properties to the state object.</li>
<li><strong>"polymorphic"</strong>: The most powerful setting. It includes <strong>"dynamic"</strong>
behavior and automatically <strong>coerces</strong> values to match the inferred type (e.g., "50" ->
50).
</li>
</ul>
<h4>Example: Polymorphic Coercion</h4>
<div class="code-block" style="margin-bottom: 2rem;">
<pre><code>{
div: {
"onmount": "=state({ count: 0 }, {
name: 'local',
schema: 'polymorphic',
scope: $this
})",
children: [
{ p: ["Typing '10' into a bind will save it as the number 10."] }
]
}
}</code></pre>
</div>
<h3 id="bind-helper">Two-Way Binding ($bind)</h3>
<p>
Two-way data binding is achieved via the <code>=bind(path)</code> helper.
</p>
<div class="code-block">
<pre><code>{ input: { type: "text", value: "=bind(/profile/name)", placeholder: "Enter name" } }
{ input: { type: "checkbox", checked: "=bind(/settings/enabled)" } }</code></pre>
</div>
<h4 id="binding-transformations">Handling Transformations</h4>
<p>
Because <code>=bind</code> is strict (it only accepts direct paths), you cannot pass a computation
like <code>=bind(upper(/name))</code>. To transform data during binding, you have two choices:
</p>
<ul>
<li>
<strong>Manual Transformation:</strong> Use an <code>oninput</code> handler:
<code>{ oninput: "=set(/name, upper($event/target/value))" }</code>
</li>
<li>
<strong>Schema Transformation:</strong> Define a <code>transform</code> in your schema:
<div class="code-block" style="margin-top: 0.5rem;">
<pre><code>Lightview.registerSchema('Profile', {
name: { type: 'string', transform: 'upper' }
});</code></pre>
</div>
The state manager will automatically apply the <a
href="/docs/api/state.html#transformations">transformation</a>
during the write-back phase of <code>=bind</code>.
</li>
</ul>
<!-- ===== SHOPPING CART EXAMPLE ===== -->
<h2 id="shopping-cart">Shopping Cart Example</h2>
<p>A more complete example showing reactive expressions with data transformations:</p>
<div id="shopping-cart-example">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
type: 'module',
height: '250px',
autoRun: true
});
</script><code contenteditable="true">// Shopping Cart: Demonstrating $map and $currency helpers
await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;
const cdomString = `{
div: {
"onmount": "=state({
cart: {
items: [
{ name: 'Apple', price: 1.00 },
{ name: 'Orange', price: 2.00 }
]
}
}, 'store')",
children: [
{ h3: "Shopping Cart" },
{ ul: {
children: =(map(/store/cart/items, { li: { children: [_/name, " - ", currency(_/price)] } }))
}},
{ p: {
style: "font-weight: bold; margin-top: 1rem;",
children: ["Total: ", =(currency(sum(/store/cart/items...price)))]
}}
]
}
}`;
const hydrated = hydrate(parseJPRX(cdomString));
$('#example').content(hydrated);
</code></pre></code></pre>
</div>
<!-- ===== INTERACTIVE EXAMPLE ===== -->
<h2 id="interactive-example">Interactive Example</h2>
<p>
Choose a syntax to see how the same reactive counter can be defined using standard JSON, concise cDOMC, or
operator syntax.
</p>
<div role="tablist" class="syntax-tabs">
<button id="tab-btn-jprx" class="syntax-tab syntax-tab-active" onclick="switchCDOMTab('jprx')">JPRX
(Standard)</button>
<button id="tab-btn-cdomc" class="syntax-tab" onclick="switchCDOMTab('cdomc')">cDOMC</button>
<button id="tab-btn-operators" class="syntax-tab" onclick="switchCDOMTab('operators')">Operators</button>
</div>
<!-- JPRX Pane -->
<div id="pane-jprx">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
type: 'module',
height: '250px',
autoRun: true
});
</script><code contenteditable="true">// JPRX: Standard JSON format (strict)
await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;
const cdomString = `{
"div": {
onmount: "=signal(0, 'count')",
"children": [
{ "h3": ["Standard JPRX Counter"] },
{ "p": { "children": ["Count: ", "=(/count)"] }},
{ "div": { "children": [
{ "button": { "onclick": "=(decrement(/count))", "children": ["-"] } },
{ "button": { "onclick": "=(increment(/count))", "children": ["+"] } }
]}}
]
}
}`;
const hydrated = hydrate(parseJPRX(cdomString));
$('#example').content(hydrated);
</code></pre>
</div>
<!-- cDOMC Pane -->
<div id="pane-cdomc" style="display: none;">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
type: 'module',
height: '250px'
});
</script><code contenteditable="true">// cDOMC: Compressed format (unquoted keys, comments)
await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = ightview;
const cdomString = `{
div: {
onmount: =signal(0, 'count'),
children: [
{ h3: ["Compressed Counter"] },
{ p: { children: ["Count: ", =(/count)] }},
{ div: { children: [
{ button: { onclick: =(decrement(/count)), children: ["-"] } },
{ button: { onclick: =(increment(/count)), children: ["+"] } }
]}}
]
}
}`;
const hydrated = hydrate(parseJPRX(cdomString));
$('#example').content(hydrated);
globalThis.LightviewCDOM.activate(hydrated.domEl);
</code></pre>
</div>
<!-- Operators Pane -->
<div id="pane-operators" style="display: none;">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
type: 'module',
height: '250px'
});
</script><code contenteditable="true">// Operators: Using =++/path and =--/path
await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;
const cdomString = `{
div: {
onmount: =signal(0, 'count'),
children: [
{ h3: ["Operator Counter"] },
{ p: { children: ["Count: ", =(/count)] }},
{ div: { children: [
// Prefix operators: =(--/count) and =(++/count)
{ button: { onclick: =(--/count), children: ["-"] } },
{ button: { onclick: =(++/count), children: ["+"] } }
]}}
]
}
}`;
const hydrated = hydrate(parseJPRX(cdomString));
$('#example').content(hydrated);
globalThis.LightviewCDOM.activate(hydrated.domEl);
</code></pre>
</div>
<!-- ===== OPERATOR SYNTAX EXAMPLE ===== -->
<h2 id="operator-syntax">Operator Syntax</h2>
<p>
JPRX supports <strong>prefix</strong> and <strong>postfix</strong> operator syntax as alternatives to
function calls.
This makes expressions more concise and familiar to JavaScript developers.
</p>
<h3>Equivalent Expressions</h3>
<table class="api-table">
<thead>
<tr>
<th>Function Syntax</th>
<th>Operator Syntax</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>=increment(/count)</code></td>
<td><code>=++/count</code> or <code>=/count++</code></td>
<td>Increment by 1</td>
</tr>
<tr>
<td><code>=decrement(/count)</code></td>
<td><code>=--/count</code> or <code>=/count--</code></td>
<td>Decrement by 1</td>
</tr>
<tr>
<td><code>=toggle(/enabled)</code></td>
<td><code>=!!/enabled</code></td>
<td>Toggle boolean (prefix only)</td>
</tr>
</tbody>
</table>
<!-- ===== EVENTS ===== -->
<h2 id="events">Events and Interaction</h2>
<p>
cDOM handles user interactions gracefully, whether in standalone applications or LLM-driven environments.
</p>
<h3 id="events-manual">Manual Implementation</h3>
<p>
Use standard event attributes with JPRX expressions:
</p>
<div class="code-block">
<pre><code>{ button: { onclick: =++/count, children: ["Click Me"] } }
{ input: { oninput: =set(/name, $event/target/value) } }</code></pre>
</div>
<h3 id="events-llm">LLM-Generated Interaction</h3>
<p>
LLMs can generate event handlers to register interest in user actions or trigger UI updates:
</p>
<div class="code-block">
<pre><code>// 1. Notify LLM of an event
{ button: { onclick: =fetch('/api/notify', { method: 'POST', body: $event }), children: ["Notify"] } }
// 2. Request a UI Patch from LLM
{ button: { onclick: =mount('/api/update', { method: 'POST', body: $event }), children: ["Update UI"] } }</code></pre>
</div>
<p>
The <code>$event</code> placeholder gives the LLM full context of the interaction. When using
<code>=mount</code>, the server should respond with cDOM, vDOM, or oDOM content (see the
<a href="#helpers-network">Network section</a> for more details).
</p>
<p>
By default, <code>=mount</code> will <strong>append</strong> the response to the <code>body</code> and
then let it "rip itself out" and <strong>teleport</strong> to its final destination if the response
contains a <code>=move</code> helper. This "Safe Landing" strategy ensures decentralized layouts
work seamlessly without the patcher needing to know the exact destination.
</p>
<h3 id="events-lifecycle">The Interaction Lifecycle</h3>
<div class="feature-grid" style="margin: 2rem 0;">
<div class="feature-card">
<h4 class="feature-title">🤖 LLM-Driven Flow</h4>
<ol style="margin-top: 1rem; font-size: 0.9em; line-height: 1.6;">
<li><strong>LLM</strong> generates UI structure as JSON</li>
<li><strong>Server</strong> serves the JSON to the client</li>
<li><strong>Client</strong> renders cDOM and activates reactivity</li>
<li><strong>User</strong> interacts (e.g., clicks a button)</li>
<li><strong>Client</strong> sends interaction data to server</li>
<li><strong>Server</strong> relays event to LLM</li>
<li><strong>LLM</strong> sends UI patch back</li>
<li><strong>Client</strong> merges update, UI refreshes</li>
</ol>
</div>
<div class="feature-card">
<h4 class="feature-title">🏠 Standalone Flow</h4>
<ol style="margin-top: 1rem; font-size: 0.9em; line-height: 1.6;">
<li><strong>cDOM</strong> defined in source code</li>
<li><strong>Client</strong> activates UI on page load</li>
<li><strong>User</strong> interacts (e.g., toggles a switch)</li>
<li><strong>State</strong> updates immediately</li>
<li><strong>UI</strong> recalculates reactively (0 latency)</li>
<li><strong>Server</strong> only for persistence/APIs</li>
</ol>
</div>
</div>
<!-- ===== JAVASCRIPT API ===== -->
<h2 id="js-api">JavaScript API</h2>
<p>Interact with cDOM programmatically using the <code>LightviewCDOM</code> global object.</p>
<h3 id="api-activate">activate(root)</h3>
<p>
Scans the DOM from <code>root</code> (defaults to <code>document.body</code>) and
initializes all cDOM directives.
</p>
<div class="code-block">
<pre><code>LightviewCDOM.activate();
LightviewCDOM.activate(document.getElementById('myApp'));</code></pre>
</div>
<h3 id="api-hydrate">hydrate(object)</h3>
<p>
Converts <code>=/</code> prefixed strings into reactive computed signals.
</p>
<div class="code-block">
<pre><code>const config = { title: "Dashboard", total: "=/cart/items...price" };
const liveConfig = LightviewCDOM.hydrate(config);</code></pre>
</div>
<h3 id="api-parseJPRX">parseJPRX(string)</h3>
<p>
Parses cDOM's concise syntax (unquoted keys, JPRX expressions) into a JavaScript object.
</p>
<div class="code-block">
<pre><code>const obj = LightviewCDOM.parseJPRX(`{ div: { children: [=/name] } }`);</code></pre>
</div>
<h3 id="api-registerSchema">registerSchema(name, definition)</h3>
<p>
Registers a reusable schema for state validation and initialization. Supported behaviors include
<code>"auto"</code> (strict/fixed), <code>"dynamic"</code> (strict/expandable), and
<code>"polymorphic"</code> (coerce/expandable).
</p>
<div class="code-block">
<pre><code>Lightview.registerSchema('User', { name: 'string', age: 'number' });
// Usable in JPRX: =state({}, { name: 'profile', schema: 'User' })</code></pre>
</div>
<h3 id="api-registerHelper">registerHelper(name, fn, options?)</h3>
<p>
Registers a custom helper function for use in JPRX expressions.
</p>
<div class="code-block">
<pre><code>LightviewCDOM.registerHelper('double', (x) => x * 2);
// Now usable: =double(/count)</code></pre>
</div>
<!-- ===== JPRX HELPERS ===== -->
<h2 id="helpers">JPRX Helper Functions</h2>
<p>
cDOM includes a rich set of built-in helpers for common transformations.
For security, only registered helpers are available — no access to <code>globalThis</code>.
</p>
<h3 id="helpers-math">Math</h3>
<p>Basic arithmetic operations.</p>
<div class="code-block">
<pre><code>+, add, -, sub, *, mul, /, div, round, ceil, floor, abs, mod, pow, sqrt</code></pre>
</div>
<h3 id="helpers-stats">Stats</h3>
<p>Aggregate calculations.</p>
<div class="code-block">
<pre><code>sum, avg, min, max, median, stdev, var</code></pre>
</div>
<h3 id="helpers-string">String</h3>
<p>Text manipulation.</p>
<div class="code-block">
<pre><code>upper, lower, trim, capitalize, titleCase, contains, startsWith, endsWith, replace, split, len, join, concat, default</code></pre>
</div>
<h3 id="helpers-array">Array</h3>
<p>Collection processing.</p>
<div class="code-block">
<pre><code>count, map, filter, find, unique, sort, reverse, first, last, slice, flatten, join, len, length</code></pre>
</div>
<h3 id="helpers-logic">Logic & Comparison</h3>
<p>Boolean logic and comparisons.</p>
<h4 class="text-xs font-bold opacity-60 uppercase mb-2">Named Operators</h4>
<div class="code-block mb-4">
<pre><code>if, and, or, not, eq, neq, gt, lt, gte, lte, between, in</code></pre>
</div>
<h4 class="text-xs font-bold opacity-60 uppercase mb-2">Aliases</h4>
<div class="code-block mb-4">
<pre><code>&&, ||, !, ==, ===, !=, >, <, >=, <=</code></pre>
</div>
<p class="text-sm italic opacity-80" style="margin-top: 1rem;">
Example: <code>=if(gt(=/count, 10), 'Large', 'Small')</code>
</p>
<h3 id="helpers-conditional">Conditional Aggregates</h3>
<p>Statistical functions with predicates.</p>
<div class="code-block">
<pre><code>sumIf, countIf, avgIf</code></pre>
</div>
<h3 id="helpers-formatting">Formatting</h3>
<p>Display formatting.</p>
<div class="code-block">
<pre><code>number, currency, percent, thousands</code></pre>
</div>
<p class="text-xs italic opacity-70" style="margin-top: 0.5rem;">
<strong>Explosion Operator:</strong> In JPRX, <code>...</code> is placed at the end of a path
(e.g., <code>=/items...price</code>) to expand arrays into arguments.
</p>
<h3 id="helpers-datetime">DateTime</h3>
<p>Date operations.</p>
<div class="code-block">
<pre><code>now, today, date, formatDate, year, month, day, weekday, addDays, dateDiff</code></pre>
</div>
<h3 id="helpers-lookup">Lookup</h3>
<p>Data retrieval from indexed structures.</p>
<div class="code-block">
<pre><code>lookup, vlookup, index, match</code></pre>
</div>
<h3 id="helpers-mutation">State & Lifecycle</h3>
<p>Initialize and modify reactive state.</p>
<div class="code-block">
<pre><code>state, signal, bind, set, increment (++), decrement (--), toggle (!!), push, pop, assign, clear</code></pre>
</div>
<h3 id="helpers-network">Network</h3>
<p>HTTP requests.</p>
<div class="code-block" style="margin-bottom: 2rem;">
<pre><code>fetch(url, options?)
<span id="helpers-mount"></span>mount(url, options?)</span></code></pre>
</div>
<h3 id="helpers-dom">DOM & XPath</h3>
<p>Structural navigation and manipulation. For fetching and mounting remote content, see the
<a href="#helpers-network">mount()</a> helper in the Network section.
</p>
<div class="code-block">
<pre><code>move(selector, location?), xpath(expression)</code></pre>
</div>
<p style="margin-top: 1rem;">
While <code>move</code> is for local placement, <code>mount</code> is used for remote Hypermedia updates.
It fetches content (cDOM, vDOM, or oDOM) and injects it into the DOM. If the content contains a
<code>=move</code> helper, it will automatically relocate itself upon mounting.
</p>
<table class="api-table">
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>method</code></td>
<td><code>"GET"</code></td>
<td>HTTP method (GET, POST, etc.)</td>
</tr>
<tr>
<td><code>body</code></td