Hypermedia

HTM-style patterns, built right in. Load HTML fragments, fetch vDOM/Object DOM, clone templates - all with the src and href attributes. Requires lightview-x.js

If you look at the source for the navigation to the left, you will see that this page is built using a hypermedia approach.

Fetching Content

HTML

Point src at an HTML file and Lightview loads it as children:

const { tags } = Lightview;
const { div, header, main } = tags;

// Load HTML partials
const app = div(
    header({ src: '/partials/nav.html' }),
    main({ src: '/partials/content.html' })
);

// The HTML is fetched, parsed, and made reactive automatically!

By default, Lightview enforces a Same-Domain security policy for all remote fetches. Content can only be fetched from the same domain or its subdomains. External domains are blocked to prevent Cross-Site Scripting (XSS) and unauthorized data ingestion. Different ports on the same host (e.g., localhost:3000 and localhost:4000) are allowed by default.

Relative Paths: Any URL that does not specify a protocol (e.g., ./data.html, /api/user) is automatically considered valid. This ensures reliability in sandboxed environments (like iframes) where the global origin might be reported as null.

Controlling Access with validateUrl

You can customize or disable this security policy using the validateUrl hook. This hook is checked before any src fetch or non-standard href navigation.

// Allow specific external CDNs
Lightview.hooks.validateUrl = (url) => {
    const target = new URL(url, location.origin);
    const allowed = ['trusted-api.com', 'cdn.content.org', 'localhost'];
    return allowed.includes(target.hostname) || target.hostname.endsWith('.mysite.com');
};

// Disable security (Not recommended for production)
Lightview.hooks.validateUrl = () => true;

Dangerous Protocol Blocking

Lightview automatically blocks dangerous URI protocols in src and href attributes, including javascript:, vbscript:, and certain data: types. If a dangerous protocol is detected, the action is cancelled and a warning is logged to the console.

vDOM and Object DOM

Files with .vdom or .odom extensions are parsed as JSON and converted to elements. Any Template Literals within the JSON values are automatically resolved.

// /api/cards.vdom
[
    { "tag": "div", "attributes": { "class": "card" }, "children": ["Card 1"] },
    { "tag": "div", "attributes": { "class": "card" }, "children": ["Card 2"] }
]

// Load vDOM
div({ src: '/api/cards.vdom' })

Cloning DOM Elements

Use CSS selectors to clone existing elements:

// Clone a template
div({ src: '#my-template' })

// Clone multiple elements
div({ src: '.card-template' })
<!-- Hidden template in HTML -->
<template id="my-template">
    <div class="modal">
        <h2>Modal Title</h2>
        <p>Modal content here</p>
    </div>
</template>

HREF Navigation

Add href to any element to make it interactive. The behavior depends on the target attribute:

Note: The same src Fetch Security controls (same-domain policy and protocol blocking) apply to all interactive href navigations.

1. Self-Loading (Default)

If no target is specified, clicking sets the element's own src to the href value:

// Loads content into itself on click
button({ href: '/partials/data.html' }, 'Load Data')

2. Browser Navigation

Use standard underscore targets for window navigation:

// Opens new tab
button({ href: 'https://example.com', target: '_blank' }, 'Open External')

// Navigates current page
div({ href: '/home', target: '_self' }, 'Go Home')

3. Targeting Other Elements

Use a CSS selector as the target to load content into other elements:

// Loads content into element with id="main"
button({ href: '/pages/about.html', target: '#main' }, 'Load About Page')

div({ id: 'main' }) // Content appears here

4. Positioning Content

Control where content is inserted using the location attribute or a target suffix.

Supported locations: innerhtml (default), outerhtml, beforebegin, afterbegin, beforeend, afterend, shadow.

// Option A: Suffix syntax for href (Target Selector:Location)
button({ 
    href: '/partials/item.html', 
    target: '#list:beforeend' // Append to list
}, 'Add Item')

// Option B: Explicit attribute on target for src
div({ 
    src: '/partials/banner.html', 
    location: 'afterbegin' 
})

Smart Replacement: Lightview tracks inserted content. Fetching the same content to the same location is a no-op. Fetching different content replaces the previous content at that specific location.

5. Hash Scrolling

If an href or src includes a hash (e.g., /page.html#section-1), Lightview will automatically attempt to scroll to the element with that ID after the content is loaded and injected. This works for both standard document targets and Shadow DOM targets (where it searches within the specific shadowRoot).

// Scrolls to #details within the loaded content
button({ href: '/api/items.html#details', target: '#content' }, 'View Details')

Declarative Event Gating (lv-before)

The lv-before attribute provides a declarative way to intercept, filter, and modify events before they reach your standard handlers (like onclick) or trigger native browser behavior.

Important: Custom gating functions must be defined in the globalThis scope (e.g., window.myGate = ...) to be accessible to the declarative parser.

1. Selection & Exclusions

You must specify which events to gate by name, or use * to gate all common (sensible) UI and form events. You can also exclude specific events:

<!-- Gate only clicks -->
<button lv-before="click myGate()">Click Me</button>

<!-- Gate all common events EXCEPT keyboard events -->
<div lv-before="* !keydown !keyup logInteraction()">...</div>

2. Rate Limiting (Throttle & Debounce)

Lightview-X provides built-in "Gate Modifiers" for common timing patterns. These return true only when the timing condition is met, effectively filtering the event stream.

<!-- Prevent button spam (only 1 click per second) -->
<button lv-before="click throttle(1000)" onclick="saveData()">Save</button>

<!-- Wait for 500ms of silence before processing input -->
<input lv-before="input debounce(500)" oninput="search(this.value)">

3. Sequence & Async Pipelines

You can chain multiple gates in a space-separated list. They execute sequentially and can be asynchronous. If any function returns false, null, or undefined, the entire event is aborted.

Parsing Note: Expressions are fundamentally space-separated. However, spaces within single quotes ('), double quotes ("), or parentheses (()) are preserved. This allows you to pass strings with spaces to your gate functions, such as confirmation messages.

<!-- Chained: Throttled first, then a confirmation -->
<button lv-before="click throttle(2000) confirm('Really delete?')" 
        onclick="doDelete()">Delete</button>

4. Context & Arguments

Inside an lv-before expression, this refers to the current element. You can pass the native event object, as well as state and signal registries, to your custom gate functions.

<!-- Pass event to a custom logic function -->
<button lv-before="click validateClick(event)" onclick="proceed()">...</button>

Template Literals

External HTML, .vdom and .odom files, can reference named signals or state with template syntax:

// main.js - Register named signals and state
const count = signal(0, 'count');
const userName = signal('Guest', 'userName');
const userPrefs = state({ theme: 'dark', lang: 'en' }, 'userPrefs');

// Load template that uses them
div({ src: '/partials/dashboard.html' })
div({ src: '/partials/dashboard.vdom' })
div({ src: '/partials/dashboard.odom' })
<!-- /partials/dashboard.html -->
<div class="dashboard">
    <h1>Welcome, ${signal.get('userName').value}!</h1>
    <p>You have ${signal.get('count').value} notifications.</p>
    <p>Theme: ${state.get('userPrefs').theme}</p>
</div>
// /partials/dashboard.vdom (JSON Array)
[
    { 
        "tag": "div", 
        "attributes": { "class": "dashboard" }, 
        "children": [
             { "tag": "h1", "children": ["Welcome, ${signal.get('userName').value}!"] },
             { "tag": "p", "children": ["Theme: ${state.get('userPrefs').theme}"] }
        ]
    }
]
// /partials/dashboard.odom (Object DOM)
{
    "div": {
        "class": "dashboard",
        "children": [
             { "h1": { "children": ["Welcome, ${signal.get('userName').value}!"] } },
             { "p": { "children": ["Theme: ${state.get('userPrefs').theme}"] } }
        ]
    }
}

Shadow DOM

Load content into shadow DOM for style isolation using location="shadow" or the :shadow suffix:

// Option A: location attribute
div({ src: '/components/widget.html', location: 'shadow' })

// Option B: target suffix
button({ href: '/components/widget.html', target: '#container:shadow' }, 'Load Widget')

HTMX-style Apps

Combine src and href for hypermedia-driven UIs. You can use lv-before for confirmation or validation before navigation.

Using Tag Functions

const { tags } = Lightview;
const { div, nav, button, main } = tags;

const app = div({ class: 'app' },
    nav({ class: 'sidebar' },
        button({ 
            href: '/pages/dashboard.html', 
            target: '#content' 
        }, '📊 Dashboard'),
        button({ 
            href: '/pages/settings.html', 
            target: '#content',
            'lv-before': "click confirm('Go to settings?')" // Event gate
        }, '⚙️ Settings')
    ),
    main({ 
        id: 'content',
        src: '/pages/dashboard.html'  // Initial content
    })
);

Using Plain HTML (Lightview Syntax)

<div class="app">
    <nav class="sidebar">
        <button href="/pages/dashboard.html" target="#content">📊 Dashboard</button>
        <button href="/pages/settings.html" target="#content" 
                lv-before="click confirm('Go to settings?')">⚙️ Settings</button>
    </nav>
    <main id="content" src="/pages/dashboard.html"></main>
</div>

Using HTMX

<div class="app">
    <nav class="sidebar">
        <button hx-get="/pages/dashboard.html" hx-target="#content">📊 Dashboard</button>
        <button hx-get="/pages/settings.html" hx-target="#content" 
                hx-confirm="Go to settings?">⚙️ Settings</button>
    </nav>
    <main id="content" hx-get="/pages/dashboard.html" hx-trigger="load"></main>
</div>