Wire sexp.js into page template with auto-init and HTMX integration
- Load sexp.js in ~app-layout before body.js - Auto-process <script type="text/sexp"> tags on DOMContentLoaded - Re-process after htmx:afterSwap for dynamic content - Sexp.mount(target, expr, env) for rendering into DOM elements - Sexp.processScripts() picks up data-components and data-mount tags - client_components_tag() Python helper serializes Component objects back to sexp source for client-side consumption - 37 parity tests all passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1141,6 +1141,54 @@
|
||||
isTruthy: isSexpTruthy,
|
||||
isNil: isNil,
|
||||
|
||||
/**
|
||||
* Mount a sexp expression into a DOM element, replacing its contents.
|
||||
* Sexp.mount(el, '(~card :title "Hi")')
|
||||
* Sexp.mount("#target", '(~card :title "Hi")')
|
||||
* Sexp.mount(el, '(~card :title name)', {name: "Jo"})
|
||||
*/
|
||||
mount: function (target, exprOrText, extraEnv) {
|
||||
var el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
if (!el) return;
|
||||
var node = Sexp.render(exprOrText, extraEnv);
|
||||
el.textContent = "";
|
||||
el.appendChild(node);
|
||||
},
|
||||
|
||||
/**
|
||||
* Process all <script type="text/sexp"> tags in the document.
|
||||
* Tags with data-components load component definitions.
|
||||
* Tags with data-mount="<selector>" render into that element.
|
||||
*/
|
||||
processScripts: function (root) {
|
||||
var scripts = (root || document).querySelectorAll('script[type="text/sexp"]');
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var s = scripts[i];
|
||||
if (s._sexpProcessed) continue;
|
||||
s._sexpProcessed = true;
|
||||
|
||||
var text = s.textContent;
|
||||
if (!text || !text.trim()) continue;
|
||||
|
||||
// data-components: load as component definitions
|
||||
if (s.hasAttribute("data-components")) {
|
||||
Sexp.loadComponents(text);
|
||||
continue;
|
||||
}
|
||||
|
||||
// data-mount="<selector>": render into target
|
||||
var mountSel = s.getAttribute("data-mount");
|
||||
if (mountSel) {
|
||||
var target = document.querySelector(mountSel);
|
||||
if (target) Sexp.mount(target, text);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Default: load as components
|
||||
Sexp.loadComponents(text);
|
||||
}
|
||||
},
|
||||
|
||||
// For testing
|
||||
_types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML },
|
||||
_eval: sexpEval,
|
||||
@@ -1150,4 +1198,23 @@
|
||||
|
||||
global.Sexp = Sexp;
|
||||
|
||||
// =========================================================================
|
||||
// Auto-init in browser
|
||||
// =========================================================================
|
||||
|
||||
if (typeof document !== "undefined") {
|
||||
// Process sexp scripts on DOMContentLoaded
|
||||
function init() { Sexp.processScripts(); }
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
// Re-process after HTMX swaps (new sexp scripts may have arrived)
|
||||
document.addEventListener("htmx:afterSwap", function (e) {
|
||||
Sexp.processScripts(e.detail.target);
|
||||
});
|
||||
}
|
||||
|
||||
})(typeof window !== "undefined" ? window : this);
|
||||
|
||||
Reference in New Issue
Block a user