Skip to content

Runtime (handlr-frontend) ​

@phillipsharring/handlr-frontend is a composable HTMX toolkit. Import only what you call — the barrel has no side effects — or opt into the batteries with one import.

The two entry points ​

This contract (ADR 0002) is the toolkit's shape:

js
// Pure named exports, ZERO side effects. Take only what you use; it tree-shakes.
import { openFormModal, HandlrToast, onAfterSwap } from '@phillipsharring/handlr-frontend';

// The batteries. Import once for side effects to wire the default listeners.
import '@phillipsharring/handlr-frontend/init';

./init wires: CSRF (fetch + HTMX header injection), boosted-nav handling, auth-state, form errors, search, sortable, the lifecycle hooks, and the declarative layer (initActions(), initFormResponses(), initAuthFlows()). Everything it turns on is also usable à la carte from the barrel — new capability means a named export or an explicit initX(), never a module that self-registers when imported from ..

A typical app entry:

js
import Handlebars from 'handlebars';
import '@phillipsharring/handlr-frontend/init';
import { registerHandlebarsHelpers, registerToastHelpers } from '@phillipsharring/handlr-frontend';

registerHandlebarsHelpers(Handlebars);
registerToastHelpers(Handlebars);

One global: window.htmx ​

The package sets exactly one global — window.htmx (a single shared instance, so inline scripts and HTMX extensions all use the same one). It does not create window.App. If you want App.ui.openFormModal(...) style access from inline <script> tags, your app builds that namespace and hangs the exports on it:

js
import * as HF from '@phillipsharring/handlr-frontend';
window.App = {
    ui: { openFormModal: HF.openFormModal, toast: HF.HandlrToast },
    hooks: { onAfterSwap: HF.onAfterSwap },
    getRouteParams: HF.getRouteParams,
};

HTMX extensions ​

Two HTMX extensions ship in lib/ and are activated with hx-ext:

  • json-enc — encodes non-GET request bodies as JSON (instead of form-encoding), and sets the JSON Content-Type/Accept. Handlr APIs speak JSON, so this is the default for forms.
  • client-side-templates — renders a JSON response through a Handlebars <template> on the page. This is what turns an API response into DOM. See HTMX Patterns for the full pattern (object vs array templates, the meta envelope, pagination).
html
<body hx-ext="json-enc, client-side-templates">

Handlebars rendering ​

registerHandlebarsHelpers(Handlebars) registers the partials and helpers the templates use — formButtons, copyIdBtn, and helpers like eq, truncate, humanize, timeAgo, formatDateTime. The datetime helpers parse values as UTC and format to the viewer's local zone (backend timestamps are UTC).

Lifecycle hooks ​

Boosted navigation replaces everything inside #app on each swap, so a one-time DOMContentLoaded listener won't re-run for content that arrives later. Use the hooks instead — each fires only when the swap target is #app:

js
import { onPageLoad, onAfterSwap, onAfterSettle, onHistoryRestore } from '@phillipsharring/handlr-frontend';

onPageLoad(doc => { /* DOMContentLoaded — runs immediately if already loaded */ });
onAfterSwap(target => { /* after #app content is swapped in */ });
onAfterSettle(target => { /* after HTMX settles — hx-trigger attrs are wired */ });
onHistoryRestore(() => { /* back/forward navigation */ });

Use onAfterSettle (not onAfterSwap) when you need hx-trigger attributes on the new content to already be active.

Boosted navigation ​

With hx-boost="true" + hx-select="#app" on <body>, links become in-place #app swaps. boosted-nav.js (wired by ./init) handles the sharp edges: it overrides hx-select for non-boosted elements so JSON/template responses aren't filtered, redirects inherited hx-target="this" back to #app, preserves the <title>, and does a full page load when the incoming data-layout differs from the current one.

Runtime modules ​

If you use build-time modules, their browser-side init() runs via initModules() (from @phillipsharring/handlr-build/modules), called once in your app entry with the same module list your site.config.js uses.

See also ​

Released under the MIT License.