hello.html
Loading…A micro JS framework — add app and bind to your markup, register a small object, and go. One file, no build step.
This guide starts simple and builds up — basics first, then core concepts, then advanced binding, ajax, and routing.
Load from unpkg (no install), via npm, or use dist/ from this repo.
<!-- CDN (latest) -->
<script src="https://unpkg.com/pax-js-framework/dist/pax.js"></script>
<!-- minified: https://unpkg.com/pax-js-framework/dist/pax.min.js -->
<!-- npm install pax-js-framework -->
<script src="node_modules/pax-js-framework/dist/pax.min.js"></script>
<!-- local clone / this site -->
<script src="dist/pax.js"></script>
Then mark a root element with app="name", register with pax.app('name', { … }), and PAX boots on page load.
An app is a named chunk of HTML + data. Put template text inside the root element, or pass a template string. Use {{this.field}} to print escaped data.
<div app="hello">{{this.message}}</div>
<script src="https://unpkg.com/pax-js-framework/dist/pax.js"></script>
<script>
pax.app('hello', {
data: { message: 'Hello World' }
});
</script>
bind="field" connects an element to data.field. Text binds update when you call this.set('field', value).
<div app="greeter">
<h1 bind="title"></h1>
<button onclick="pax.greeter.greet()">Greet</button>
</div>
pax.app('greeter', {
data: { title: 'Welcome' },
greet: function() {
this.set('title', 'Hello, PAX!');
}
});
A <ul bind="todos"> holds an array of strings. Call this.push('todos', value) to add a row — PAX renders each item as an <li> for you.
<div app="todo">
<h1 bind="title"></h1>
<ul bind="todos"></ul>
<input bind="inp" type="text" />
<button onclick="pax.todo.add()">Add</button>
</div>
pax.app('todo', {
data: {
title: 'My List',
todos: ['Learn PAX'],
inp: ''
},
add: function() {
if (!this.data.inp) return;
this.push('todos', this.data.inp);
this.set('inp', '');
}
});
Inputs sync back to data automatically. Use a change handler to react — here we reverse the typed text into another bind.
<div app="mirror">
<input bind="inp" type="text" placeholder="Type here" />
<h2 bind="message">Reversed text appears here</h2>
</div>
pax.app('mirror', {
change: {
inp: function(v) {
this.set('message', v.split('').reverse().join(''));
}
}
});
Write normal JS in onclick="…". PAX rewires handlers at render (CSP-friendly). Call app methods via pax.appname.method().
<button onclick="pax.counter.up()">+1</button>
<span bind="count">0</span>
pax.app('counter', {
data: { count: 0 },
up: function() {
this.set('count', this.data.count + 1);
}
});
Each bind="field" connects to data.field. PAX picks the wiring from the HTML tag — you don't declare types in JS.
| Element | data shape | How it updates |
|---|---|---|
h1, p, span, div, … | string | this.set('field', val) |
input, textarea | string | Two-way — typing updates data |
ul / ol | array of strings | push(), pop() |
For a list, put an array in data and call push — PAX renders one <li> per item using the default template {{value}}.
<ul bind="todos"></ul>
data: { todos: ['Learn PAX', 'Build something'] }
this.push('todos', 'New item');
this.pop('todos'); // remove last
this.pop('todos', 1); // remove at index
More element types (select, checkbox, tables, custom row templates) are in Lists, forms & tables.
Use {{…}} in app template strings or inside bind markup. Output is escaped by default.
{{this.name}} // text from data
{{this.user.email}} // nested path (reactive in templates)
{{this.name || 'Guest'}} // fallback when empty
{{html this.body}} // trusted HTML (use carefully)
{{myApp.data.field}} // read another app
{{this.field}} and nested paths like {{this.a.b}} or {{this.a.b.c}} in an app template are compiled to bind at boot — they stay live when you set() the path or replace a parent object. In attributes (e.g. src="{{this.image}}"), PAX compiles to attr:src="this.image" instead. Fallbacks ({{this.x || 'Guest'}}) and {{html …}} stay one-shot; use an explicit bind when you need those to update.
templates iteration
If data for a bind is an array, PAX renders the template once per item. Objects and strings render a single pass — no separate type flag needed. Nested paths like {{this.meta.tag}} work in row templates; use set() or push/pop to refresh. See nested.html.
Toggle visibility and attributes from data — no manual style.display or helper strings in templates.
<p show="!items.length">Nothing here yet.</p>
<p hide="items.length">List is empty.</p>
<img attr:src="this.image" />
Expressions use data paths (todos.length), ! for negation, or app methods (isAdmin()).
<div app="showdemo">
<ul bind="items"></ul>
<p show="!items.length">No items yet.</p>
<button onclick="pax.showdemo.add()">Add</button>
</div>
pax.app('showdemo', {
data: { items: [] },
add: function() {
this.push('items', 'Item ' + (this.data.items.length + 1));
}
});
No items yet.
Each app boots in order. Pick the hook that matches when you need to run code:
| Hook | When | Use for |
|---|---|---|
init | Before ajax / render | Early setup that does not need fetched data or DOM |
loaded | After ajax finishes, before first render | Map or transform load data, read from dependency apps |
ready | After first render | Focus, logging, third-party plugins, reading pax.url |
destroy | When leaving a routed app | Clear timers, listeners, or other teardown |
Boot order with ajax: init → load requests → loaded → render → ready. destroy runs only for routed apps when navigating away. See also depends in Part 3.
Full bind reference — for when you need more than strings in a list.
| Element | data shape | How it updates |
|---|---|---|
ul / ol | array (strings or objects) | push(), pop(), render() |
input type="checkbox" | — | Checked state in values as {checked, text} |
select | array of {id, text} | Options from data; selection in values |
select multiple | array of {id, text} | Selected items in values |
wrapper bind-type="radio" | array of {id, text} | Selected option in values |
wrapper bind-type="checkboxes" | array of {id, text} | Selected options in values (array) |
contenteditable | string (HTML) | Two-way — edits update data on blur |
tbody / table | array of objects | push(), pop(), render() |
String arrays use the default row: <li data-index="{{index}}">{{value}}</li>. For objects or buttons per row, set templates:
data: { todos: [{ text: 'Learn PAX', done: 0 }] },
templates: {
todos: `<li data-index="{{index}}">{{this.text}}
<button onclick='pax.home.pop("todos",{{index}})'>x</button></li>`
}
In list rows use {{this.field}}, {{index}}, and {{value}} for the current item. Put {{…}} inside the <ul> in HTML to use that as the row template instead.
See todoAdvanced.html for a full example. push and pop patch individual rows when possible (append/remove) so other rows keep focus; call render() yourself after sort or bulk edits.
data: {
colors: [{ id: 'red', text: 'Red' }, { id: 'green', text: 'Green' }],
multiTags: [{ id: 'a', text: 'Alpha' }, { id: 'b', text: 'Beta' }],
size: [{ id: 's', text: 'Small' }, { id: 'm', text: 'Medium' }],
tagBoxes: [{ id: 'a', text: 'Alpha' }, { id: 'b', text: 'Beta' }]
},
values: {
agree: { checked: false, text: 'I agree' }
},
<label><input type="checkbox" bind="agree" /> I agree</label>
<select bind="colors"></select>
<select multiple bind="multiTags"></select>
<div bind="size" bind-type="radio"></div>
<div bind="tagBoxes" bind-type="checkboxes"></div>
this.set('colors', false, { id: 'green' }); // selected option → values
this.set('tagBoxes', false, [{ id: 'a' }]); // checked boxes → values
Options live in data (arrays of {id, text}); selections live in values. Use a unique bind id per element — the docs reuse tags in two snippets only to show both APIs, not on one page. See checkboxes.html.
input type="radio" alone is not a bind target — use a wrapper with bind-type="radio". For multiple checkboxes, use bind-type="checkboxes" the same way (options in data, selections in values).
<div bind="body" contenteditable="true"></div>
data: { body: '<p>Edit me</p>' } // two-way HTML on blur
On init, if a bind key is missing from data, PAX reads the DOM once (existing <li> items, table rows, select options). If the key exists — even [] or '' — data wins.
Use filters to transform values before render: filters: { price: function(v) { return '$' + v; } }. Override tag detection with bind-type when needed.
Declare load: { key: { url: '…' } }. The JSON response is copied to data.key by default (setData is true). Use loaded only when you need extra mapping, and error on a load entry for failed requests.
pax.app('home', {
load: {
question: {
url: 'https://yesno.wtf/api',
error: function(err) {
this.data.question = { answer: 'unavailable', image: '' };
}
}
},
template: '<h1>{{this.question.answer}}</h1><img src="{{this.question.image}}" alt="" />'
});
loaded vs ready
Ajax responses land on data automatically. Use app loaded for extra mapping before render. Use ready for DOM work after the template is on screen. Set setData: false on a load entry to keep the response only on load.key.data.
depends: ['otherApp'] waits until the other app finishes load + render. Apps without an [app] element still boot when something depends on them.
pax.app('session', {
data: { user: 'Jane Doe', role: 'Admin' },
template: '<small>Session: {{this.user}}</small>'
});
pax.app('dash', {
depends: ['session'],
loaded: function() {
this.data.msg = 'Welcome, ' + pax.session.data.user;
},
template: '<strong>{{this.msg}}</strong>'
});
Set pax.routeHash = 1, put route templates in #routes, and navigate with pax.link('#/page').
Each routed app needs a url — set it on the app def, or let PAX derive it from <template app="…">:
<div id="routes">
<template app="home">…</template>
<template app="page1">…</template>
<template app="settings_profile">…</template>
</div>
// auto: home → /home, page1 → /page1, settings_profile → /settings/profile
// app="index" → /
// override anytime: pax.app('home', { url: '/' })
Underscores in the app name become path segments; dashes become underscores in the app key (my-page → app my_page, url /my-page). An explicit url on pax.app() always wins.
While a route app loads, PAX shows pax.loadTemplate in the route outlet. The core build keeps this empty to stay small — add a loader only if you want one.
<script src="pax.min.js"></script>
<script src="pax-loader.js"></script> <!-- optional SVG spinner -->
// or inline / custom:
pax.loadTemplate = '<svg …>…</svg>';
// or fetch from CDN (call before first navigation):
pax.setLoadTemplate('https://unpkg.com/pax-js-framework@1.0.11/dist/pax-loader.svg');
dist/pax-loader.svg is a compact animated spinner (~400 bytes). pax-loader.js sets loadTemplate for you — include it right after pax.min.js.
pax.urlOn every navigation, PAX splits the path into pax.url — an array of segments between slashes. Routing picks the longest registered match; anything after that stays in the array for your app to read.
// #/page1/edit/42 → pax.url = ['page1', 'edit', '42']
// route /page1 matches → page1 app boots
// pax.urlTail = ['edit', '42'] (segments after the route)
pax.app('page1', {
url: '/page1',
ready: function() {
this.data.action = pax.url[1]; // 'edit'
this.data.id = pax.url[2]; // '42'
// or use pax.urlTail / this.urlTail
}
});
pax.path is the full path string (no #). Query strings are in pax.getVars (e.g. ?tab=settings → pax.getVars.tab).
There is no built-in :id param syntax yet — read pax.url or pax.urlTail in ready / loaded and validate length yourself. That keeps the router small; named params can come later if needed.
Use destroy on routed apps to clear timers or listeners when the user navigates away.
See routing (manual urls) and routingAutomated (auto urls) below.
pax.config.debug = true; // before pax.app() calls
Logs warnings for typos, missing apps, handler errors, and bad depends.
| Key | Description |
|---|---|
| State | |
data | Reactive app state. Each key lines up with a bind="key" field. Merged (not replaced) when you call pax.app() again. Holds list rows, text, checkbox state, and ajax payloads unless setData is disabled. |
values | Current form selections, separate from option lists. For select, select multiple, and radio/checkbox groups, data holds the options ([{id, text}]) while values holds what the user picked. pax.getVals() prefers values over data for a bind id. |
| Templates | |
template | HTML string for the whole app root — one block with {{this.field}} expressions. Simple {{this.field}} entries compile to binds and stay live on set(). If omitted, PAX uses the innerHTML of the [app] element or a route <template app> tag. |
templates | Per-bind HTML keyed by bind id — a child template for each bind instead of a parent template for the entire app. Controls how that bound element renders (lists, inputs, or any bind). When data for that bind is an array, PAX iterates the template once per item. Merged on re-register. |
| Ajax & dependencies | |
load | Ajax entries — each needs url. Optional error, loaded (per entry), and setData (default true — copies the JSON response onto data[key]). |
depends | Array of app names this app waits for before booting. Useful when one app reads another's data or ajax results. |
| Callbacks | |
change | Per-field callbacks — change: { field: function(val, id, key) { … } }. Fires after set(), user input, push(), or pop() updates that field. |
| Routing & instances | |
url | Route path for SPA routing. Auto-set from <template app> if omitted (underscores become path segments). |
shared | true (default) — one global app instance. false — a separate instance per [app] element (see pax.get(name, ref)). |
| Lifecycle | |
init | Runs once at boot, before ajax load requests. Use for early setup that does not need fetched data or rendered DOM. |
loaded | Runs after all load requests finish, before the first render. Use to map or transform ajax data, or read from dependency apps. |
ready | Runs after the first render. DOM is on screen — use for focus, logging, third-party plugins, or reading pax.url / pax.urlTail. |
destroy | Runs when a routed app is left for another route. Use to clear timers, listeners, or other teardown. Not called for static page apps. |
| Method | Description |
|---|---|
this.set(id, val) | Update data and re-render binds |
this.push(id, val) | Append to array bind |
this.pop(id) | Remove from array bind |
this.render() | Re-run bind wiring |
| Element | Data | How it updates |
|---|---|---|
| Text elements | string | set() writes data → DOM (one-way) |
input, textarea | string | Two-way — typing updates data; set() updates the field from code |
input type="checkbox" | — | Checked state in values as {checked, text} — set('agree', false, {checked:true}) from code |
select | [{id, text}] | Options in data; selected option in values. set() refreshes options; user change updates values |
select multiple | [{id, text}] | Options in data; selected items in values (array) |
bind-type="radio" | [{id, text}] | Options in data; selected option in values |
bind-type="checkboxes" | [{id, text}] | Options in data; selected options in values (array) |
contenteditable | string (HTML) | Two-way — editing updates data on blur; set() updates HTML from code |
ul, ol, tbody | array | push(), pop(), render() |
Two-way means user input flows back into data (PAX listens for keyup / change). One-way text binds only update when you call set(). In both cases, set() is how you change a field from JavaScript and refresh the DOM.
| Attribute | Description |
|---|---|
show="expr" | Visible when expression is truthy |
hide="expr" | Hidden when expression is truthy |
attr:name="expr" | Bind attribute to data (attr:src, attr:checked, …) |
pax)| API | Description |
|---|---|
pax.app(name, def) | Register app (register alias) |
pax.get(name, ref?) | Get app or instance (ref = index for shared: false) |
pax.getVals(key, ids) | Read bind values from an app |
pax.version | Framework version string |
pax.link(path) | Navigate (hash or history) |
pax.loadTemplate | HTML shown in the route outlet while a page loads (empty by default) |
pax.setLoadTemplate(src, done?) | Set loader HTML inline or fetch an SVG/file; optional callback when fetch finishes |
pax.url | Path segments array — ['page1', 'edit', '42'] |
pax.urlTail | Segments after the matched route |
pax.path | Full path string (no hash) |
pax.getVars | Query string key/value pairs |
pax.routeHash | 1 for #/path routing |
pax.config.debug | Enable dev warnings |
pax.el.routes | Route outlet selector (default #routes) |
Each example shows the source code next to a live preview. Open the full page for more room.
Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…Loading…