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.name || 'Guest'}} // fallback when empty
{{html this.body}} // trusted HTML (use carefully)
{{myApp.data.field}} // read another app
{{this.x}} in an app template renders once at boot. bind fields stay live when you set().
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 |
ready | After first render | Focus, logging, DOM plugins |
Ajax hooks (loaded), route teardown (destroy), and depends are covered 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" | { id, text } | id: 1 = checked |
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 |
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.
<label><input type="checkbox" bind="agree" /> I agree</label>
<select bind="colors"></select>
<select multiple bind="tags"></select>
<div bind="size" bind-type="radio"></div>
this.set('colors', false, { id: 'green' }); // set selected option
input type="radio" alone is not a bind target — use a wrapper with bind-type="radio".
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: '…' } }. Map the response in loaded before the template renders. Use error on a load entry for failed requests.
pax.app('home', {
load: {
question: {
url: 'https://yesno.wtf/api',
error: function(err) {
this.data.answer = 'unavailable';
}
}
},
loaded: function() {
this.data.answer = this.load.question.data.answer;
this.data.image = this.load.question.data.image;
},
template: '<h1>{{this.answer}}</h1><img src="{{this.image}}" />'
});
loaded vs ready
Map ajax data in loaded (before render). Use ready for DOM work after the template is on screen.
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.
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 |
|---|---|
data, values | App state (merged on re-register) |
template, templates | HTML templates with {{…}} |
load | Ajax entries — each needs url; optional error |
depends | Array of app names to wait for |
change | Per-field change callbacks |
shared | true (default) or false for instances |
url | Route path; auto-set from <template app> if omitted |
init, loaded, ready, destroy | Lifecycle hooks |
| 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 | Methods |
|---|---|---|
| Text elements | string | set() |
input, textarea | string | two-way + set() |
input type="checkbox" | {id, text} | two-way + set() |
select | [{id, text}] | set() / values |
select multiple | [{id, text}] | values (array) |
bind-type="radio" | [{id, text}] | set() / values |
ul, ol, tbody | array | push(), pop(), render() |
| 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.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…