lightview
Version:
A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation
283 lines (260 loc) • 12.2 kB
HTML
<!-- SEO-friendly SPA Shim -->
<script src="/lightview-router.js?base=/index.html"></script>
<link rel="stylesheet" href="../components/index.css">
<div class="docs-layout">
<aside class="docs-sidebar" src="./nav.html" data-preserve-scroll="docs-nav"></aside>
<main class="docs-content">
<h1>Elements</h1>
<p>
Lightview is uniquely flexible. It doesn't force you into a single way of describing your DOM. Whether you
prefer concise JavaScript functions, structured JSON, or standard HTML, the same signal-based reactivity
powers it all.
</p>
<h2 id="comparison">Comparison</h2>
<table class="api-table">
<thead>
<tr>
<th>Syntax</th>
<th>Style</th>
<th>Best For</th>
<th>Requirement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Tagged API</strong></td>
<td><code>div(h1('Title'))</code></td>
<td>Application logic, dynamic UIs</td>
<td>Core</td>
</tr>
<tr>
<td><strong>vDOM</strong></td>
<td><code>{ tag: 'div', ... }</code></td>
<td>Serialization, data-driven UI</td>
<td>Core</td>
</tr>
<tr>
<td><strong>Object DOM (oDOM)</strong></td>
<td><code>{ div: { ... } }</code></td>
<td>Concise templates, config files</td>
<td>Lightview X</td>
</tr>
<tr>
<td><strong>Custom Elements</strong></td>
<td><code><lv-button></code></td>
<td>Progressive enhancement, CMS</td>
<td>Lightview X</td>
</tr>
<tr>
<td><strong>cDOM (Experimental)</strong></td>
<td><code>'sum($/cart/items...price)'</code></td>
<td>Declarative logic, LLM generation</td>
<td>lightview-cdom.js</td>
</tr>
</tbody>
</table>
<p style="margin-top: 1rem; font-style: italic;">
Note: An exciting 5th option is coming, the <strong>Computational DOM</strong>, a.k.a. <strong><a
href="/docs/cdom.html">cDOM</a></strong>.
</p>
<h2 id="tagged-api">Tagged API</h2>
<p>
Inspired by Bau.js, this is the most concise way to build UIs in JavaScript. Every HTML tag is available as
a function.
</p>
<div id="syntax-tagged">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
type: 'module',
minHeight: 120,
autoRun: true
});
</script><code contenteditable="true">const { signal, tags, $ } = Lightview;
const { div, h1, p, button } = tags;
const count = signal(0);
const app = div({ class: 'container' },
h1('Hello Lightview'),
p(() => `Count: ${count.value}`),
button({ onclick: () => count.value++ }, 'Click me')
);
$('#example').content(app);</code></pre>
</div>
<p><strong>Pros:</strong> Extremely readable, feels like "HTML in JS" without a compiler, full IDE autocomplete.
</p>
<h2 id="vdom">vDOM Syntax</h2>
<p>
Represent your UI as plain JavaScript objects. This is the underlying format for all non-string elements in
Lightview.
</p>
<div id="syntax-vdom">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
type: 'module',
minHeight: 120,
autoRun: true
});
</script><code contenteditable="true">const { signal, element, $, tags } = Lightview;
const { div } = tags;
const count = signal(0);
// will accept either a function or string as tag, function avoids typos better
const app = { tag:div, attributes: { class: 'container' }, children: [
{ tag: 'h1', attributes: {}, children: ['Hello Lightview'] },
{ tag: 'p', attributes: {}, children: [() => `Count: ${count.value}`] },
{ tag: 'button', attributes: { onclick: () => count.value++ }, children: ['Click me'] }
]};
$('#example').content(app);</code></pre>
</div>
<p><strong>Pros:</strong> Unambiguous, easy to serialize/deserialize as JSON, perfect for programmatic
generation.</p>
<h2 id="object-dom">Object DOM (oDOM)</h2>
<p>
A more compact JSON representation provided by <strong>Lightview X</strong>. In oDOM, an object with a
<strong>single key</strong> and an <strong>object value</strong> represents an element. The key is the
<strong>tag name</strong> (e.g., <code>"div"</code>, <code>"span"</code>) or a <strong>custom element
function name</strong>. The value object contains the element's attributes.
</p>
<h3>Reserved Keys</h3>
<p>
Certain keys on the value object are reserved and have special meaning instead of being treated as
attributes:
</p>
<table class="api-table">
<thead>
<tr>
<th>Key</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>children</code></td>
<td>An array of child elements, strings, or reactive functions.</td>
</tr>
<tr>
<td><code>attributes</code></td>
<td>An explicit attributes object (not needed in oDOM since non-reserved keys are attributes). If
you use this attribute, Lighview MAY assume you are trying to use vDOM not oDOM. Avoid its use
except for vDOM.
</td>
</tr>
<tr>
<td><code>tag</code></td>
<td>Explicitly sets the tag name. If you use this attribute, Lighview MAY assume you are trying to
use vDOM not oDOM. Avoid its use except for vDOM.</td>
</tr>
</tbody>
</table>
<p>
All other keys on the value object are treated as <strong>attribute names</strong>. Keys starting with
<code>on</code> (like <code>onclick</code>, <code>onmouseenter</code>) are bound as event handlers.
</p>
<h3>Array Shorthand</h3>
<p>
If a tag key has an <strong>array</strong> as its value instead of an object, it is shorthand for
<code>{ <key>: { children: <array> } }</code>:
</p>
<pre><code>// These are equivalent:
{ ul: [{ li: ['Item 1'] }, { li: ['Item 2'] }] }
{ ul: { children: [{ li: { children: ['Item 1'] } }, { li: { children: ['Item 2'] } }] } }</code></pre>
<h3>Example</h3>
<div id="syntax-object">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
type: 'module',
minHeight: 120,
autoRun: true
});
</script><code contenteditable="true">const { signal, tags, $ } = Lightview;
const count = signal(0);
const app = { div: { class: 'container', children: [
{ h1: ['Hello Lightview'] },
{ p: { children: [() => `Count: ${count.value}`] } },
{ button: { onclick: () => count.value++, children: ['Click me'] } }
]}};
$('#example').content(app);</code></pre>
</div>
<p><strong>Pros:</strong> Highly readable for templates stored in JSON, significantly less boilerplate than
standard vDOM.</p>
<h2 id="custom-elements">HTML Custom Elements</h2>
<p>
Use standard HTML tags to instantiate Lightview components. Ideal for multi-page apps or content managed by
a CMS.
</p>
<pre><code><!-- Requires registered components & Lightview X -->
<lv-card>
<h3 slot="title">User Profile</h3>
<lv-badge color="primary">Admin</lv-badge>
<p>Active since 2024</p>
<lv-button onclick="alert('Clicked!')">Settings</lv-button>
</lv-card></code></pre>
<p><strong>Pros:</strong> Familiar HTML syntax, framework-agnostic, excellent for progressive enhancement of
server-rendered pages.</p>
<h2 id="pseudo-elements">Pseudo-elements</h2>
<p>
Lightview supports special "pseudo-elements" that perform specific tasks rather than creating a standard
HTML element.
</p>
<h3 id="shadowdom">shadowDOM</h3>
<p>
The <code>shadowDOM</code> tag allows you to attach a Shadow Root to the parent element and render children
inside it. This is useful for building encapsulated components without manual <code>attachShadow</code>
calls.
</p>
<pre><code>const { tags } = Lightview;
const { div, shadowDOM, h2, p } = tags;
const MyComponent = () => div(
shadowDOM({ mode: 'open', styles: ['/my-styles.css'] },
h2('Encapsulated Title'),
p('This content is inside the shadow root.')
)
);</code></pre>
<h3 id="text">text</h3>
<p>
The <code>text</code> tag creates a single <code>Text</code> node containing the concatenated content of all
its children, separated by spaces. It supports reactivity, meaning if any child is a function, the text node
will update automatically.
</p>
<pre><code>const { tags, signal } = Lightview;
const { div, text } = tags;
const firstName = signal('John');
const lastName = signal('Doe');
const greeting = div(
text('Hello,', () => firstName.value, () => lastName.value, '!')
);
// Initial result: <div>Hello, John Doe !</div></code></pre>
<h2 id="attributes-events">Events</h2>
<p>Use standard event handlers prefixed with "on".</p>
<pre><code>button({
onclick: (e) => handleClick(e),
onmouseenter: () => setHovered(true),
onmouseleave: () => setHovered(false)
})</code></pre>
<h2 id="children">Children</h2>
<p>
Children can be strings, numbers, elements, arrays, or functions:
</p>
<pre><code>div(
'Static text', // String
42, // Number (converted to string)
span('Nested element'), // Element
() => `Dynamic: ${value.value}`, // Reactive function
() => items.value.map(i => li(i.name)), // Reactive list
condition && span('Conditional') // Conditional (falsy = not rendered)
)</code></pre>
<h2 id="dom-el">The domEl Property</h2>
<p>
Every Lightview element has a <code>domEl</code> property - the actual DOM node:
</p>
<pre><code>const myDiv = div({ class: 'box' }, 'Hello');
// Access the real DOM element
document.body.appendChild(myDiv.domEl);
// You can also manipulate it directly
myDiv.domEl.classList.add('another-class');</code></pre>
</main>
</div>