JTX is a tiny library that makes a server‑rendered page feel alive. Keep writing normal HTML on the server; sprinkle a few small tags and attributes in your markup; JTX binds your JSON data to the page so it updates reactively — without a build step or a virtual DOM.
For the full, precise specification, see SPEC.md. This README is a practical "how to".
What JTX is
- A lightweight layer on top of your existing HTML.
- Driven by JSON from HTTP/SSE/WebSocket sources or local state.
- Declarative: add attributes/tags; JTX updates the DOM for you.
- Build‑free: include a script and you’re done.
Why use it
- Keep SSR and progressively enhance with minimal client code.
- Great for lists, text, toggles, and live feeds.
- Stays close to the platform — just HTML + a few helpers.
- NPM:
npm install @angerenage/jtx - CDN:
<script src="https://unpkg.com/@angerenage/jtx"></script>
JTX auto‑initializes on DOMContentLoaded. If you append HTML later, call JTX.init(subtreeElement).
@name.path: read values from a state or source.- Expressions are plain JS (in attribute values).
- Event handlers use
jtx-on="event: code".
Helpers available in expressions and handlers:
emit(name, detail): dispatch a custom event on the current element.refresh('srcName')or@src.refresh(): re‑fetch a source.get/post/put/patch/del(url, [bodyOrHeaders], [headers]): thinfetchhelpers.$event: current event injtx-on.$el: current element.
Declare a state and expose keys as data to descendants.
<jtx-state name="ui" counter="0" theme="'light'" persist="theme" persist-url="counter">
<button jtx-on="click: @ui.counter++">+</button>
<span jtx-text="@ui.counter"></span>
</jtx-state>name(required): unique id.- Any other attribute becomes a key (evaluated once at init).
persist: comma‑separated keys saved tolocalStorage.persist-url: comma‑separated keys synced to query string.
Events you can listen to (they bubble):
init:{ name, value }when created.update:{ name, keys, value }after batched changes.
Two‑way binding with form elements via jtx-model:
<jtx-state name="form" email="''">
<input type="email" jtx-model="@form.email">
<p jtx-text="@form.email"></p>
</jtx-state>Notes:
- States are scoped: a
<jtx-state>inside a template/section shadows outer states of the same name.
Fetch JSON over HTTP or stream JSON via SSE/WebSocket. Use child content to render the value.
<!-- HTTP example: fetch on load and every 30s -->
<jtx-src name="orders" url="/api/orders" fetch="onload, every 30s" select="items">
<jtx-loading>Loading…</jtx-loading>
<jtx-error>Could not load orders.</jtx-error>
<jtx-empty>No orders yet.</jtx-empty>
<ul>
<jtx-insert for="o in @orders">
<jtx-template>
<li>
<span jtx-text="o.id"></span> — <span jtx-text="o.title"></span>
</li>
</jtx-template>
</jtx-insert>
</ul>
</jtx-src><!-- SSE example: stream events; optionally filter with sse-event -->
<jtx-src name="feed" url="sse:/events" sse-event="tick">
<p jtx-text="@feed.$status"></p>
</jtx-src>
<!-- WebSocket example -->
<jtx-src name="chat" url="wss:/ws/chat"></jtx-src>Attributes:
name(required): unique id.url(required): HTTP (/apiorhttps://…), SSE (sse:/path), WS (ws:/…orwss:/…).fetch: when to fetch (HTTP only):onload(default),idle,visible,every 5s, ormanualfor manual control.headers: JSON or expression for request headers, e.g.{ Authorization: 'Bearer ' + @auth.token }.select: dot‑path to pick a nested value from the JSON payload.sse-event: for SSE, only handle a specific event type.
Children you can add inside a jtx-src (optional): jtx-loading, jtx-error, jtx-empty.
Programmatic refresh:
- In JS:
JTX.refresh('orders') - In expressions/handlers:
@orders.refresh()orrefresh('orders')
Source status and error are readable in expressions:
@orders.$statusis one ofidle | loading | ready | error@orders.$errorcontains last error, if any
Events you can listen to on the <jtx-src> element:
init:{ name }when registered.fetch:{ url, headers }before an HTTP request.update:{ name, value }after new data is set.error:{ name, type, status?, message, raw? }on network/parse/connection errors.open:{ name, type: 'sse' | 'ws' }when a stream opens.close:{ name, code?, reason? }when a stream closes.message:{ name, type, data, lastEventId? }for raw SSE/WS messages. For SSE events, JTX also dispatches a DOM event named after it's type on the<jtx-src>in addition tomessage.
Use these attributes anywhere in your HTML:
jtx-if="expr": add/remove the element based on truthiness.jtx-show="expr": togglehiddenattribute.jtx-text="expr": settextContent(falls back to original content ifnull/undefined).jtx-html="expr": setinnerHTML(you are responsible for sanitizing).jtx-attr-FOO="expr": bind any attributeFOO(boolean true => present, false/null/undefined => removed).jtx-model="@state.key": two‑way bind form inputs/selects/textarea to state (supports nested paths like@state.user.name).jtx-on="event: code; other: code": run JS on events. Also supports timers:every 5s: code.
Insert text/HTML or render lists with a template.
Scalar insert:
Hello, <jtx-insert text="@user.name">guest</jtx-insert>!List insert:
<jtx-insert for="item in @orders" key="item.id" strategy="replace">
<jtx-template>
<div>
<span jtx-text="item.id"></span>
<span jtx-text="item.title"></span>
</div>
</jtx-template>
</jtx-insert>Options:
for(required for lists):item in <expr>orvalue,key in <expr>.key: stable key per item (recommended; required to accumulate with merge).strategy:replace(default): remove all current items and insert the new ones from scratch.append: always add the received item(s) to the end.prepend: always add the received item(s) to the beginning.append merge: keyed upsert — update existing items by key; append new keys; respectswindow(trims from start).prepend merge: keyed upsert — update existing items by key; prepend new keys; respectswindow(trims from end).
window: required for merge strategies and optional for append/prepend — caps the number of rendered items.- Optional children:
jtx-loading,jtx-error,jtx-empty(useful when nested under ajtx-src).
- Auto‑init on page load. To initialize a dynamically inserted subtree:
JTX.init(element). - Attributes inside
<jtx-template>are compiled per item when the list renders.
- Keep expressions simple and safe; they run in the browser.
- Prefer
selectonjtx-srcto avoid repeating deep paths in templates. - Unknown
@namereferences warn in the console; check yournameattributes.
- Detailed behavior, edge cases, and full semantics: see SPEC.md.
MIT — see LICENSE.