SSR modernization: ssr-form-migration skill + Transaction Edit plain-form/Selmer migration #14
@@ -96,6 +96,7 @@
|
||||
[org.clojure/core.async]]
|
||||
|
||||
[hiccup "2.0.0-alpha2"]
|
||||
[selmer "1.12.61"]
|
||||
|
||||
;; needed for java 11
|
||||
[javax.xml.bind/jaxb-api "2.4.0-b180830.0359"]
|
||||
|
||||
7
resources/templates/interop-smoke.html
Normal file
7
resources/templates/interop-smoke.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div id="interop-smoke" class="p-2">
|
||||
<h3>{{ title }}</h3>
|
||||
{# a Hiccup-rendered component, passed in pre-rendered and emitted verbatim #}
|
||||
{{ hiccup_frag|safe }}
|
||||
<input x-ref="input" x-model="value.value"
|
||||
@keydown.down.prevent.stop="tippy?.show()" />
|
||||
</div>
|
||||
43
src/clj/auto_ap/ssr/selmer.clj
Normal file
43
src/clj/auto_ap/ssr/selmer.clj
Normal file
@@ -0,0 +1,43 @@
|
||||
(ns auto-ap.ssr.selmer
|
||||
"Selmer rendering + the Hiccup<->Selmer interop bridge for the SSR form/wizard
|
||||
migration (see .claude/skills/ssr-form-migration). Interactive, attribute-heavy
|
||||
components render from Selmer templates with plain-HTML Alpine/HTMX attributes;
|
||||
the bridge lets a Selmer template embed Hiccup output and lets a Selmer fragment
|
||||
sit inside a Hiccup tree during the strangler transition.
|
||||
|
||||
Templates live under resources/templates/ and are referenced by classpath-relative
|
||||
path, e.g. (render \"templates/components/typeahead.html\" ctx)."
|
||||
(:require
|
||||
[hiccup.util :as hu]
|
||||
[hiccup2.core :as h2]
|
||||
[selmer.parser :as selmer]))
|
||||
|
||||
(defn hiccup->html
|
||||
"Render a Hiccup form to an HTML string so it can be embedded in a Selmer
|
||||
context value and emitted with the |safe filter: {{ frag|safe }}."
|
||||
[hiccup]
|
||||
(str (h2/html {} hiccup)))
|
||||
|
||||
(defn raw
|
||||
"Wrap an already-rendered HTML string (e.g. from `render`) so hiccup2 emits it
|
||||
verbatim instead of escaping it. Use to drop a Selmer fragment into a Hiccup tree:
|
||||
[:div (sel/raw (sel/render \"...\" ctx))]."
|
||||
[^String html]
|
||||
(hu/raw-string html))
|
||||
|
||||
(defn render
|
||||
"Render a Selmer template file (classpath-relative path) with `ctx`, returning an
|
||||
HTML string. Hiccup values in `ctx` should be pre-rendered via `hiccup->html` and
|
||||
referenced with |safe in the template."
|
||||
[template ctx]
|
||||
(selmer/render-file template ctx))
|
||||
|
||||
(defn render-str
|
||||
"Render a Selmer template given as a string (handy for tests/REPL)."
|
||||
[template ctx]
|
||||
(selmer/render template ctx))
|
||||
|
||||
(defn render->hiccup
|
||||
"Render a Selmer template file and wrap the result for safe embedding in Hiccup."
|
||||
[template ctx]
|
||||
(raw (render template ctx)))
|
||||
36
test/clj/auto_ap/ssr/selmer_test.clj
Normal file
36
test/clj/auto_ap/ssr/selmer_test.clj
Normal file
@@ -0,0 +1,36 @@
|
||||
(ns auto-ap.ssr.selmer-test
|
||||
(:require
|
||||
[auto-ap.ssr.selmer :as sut]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :refer [deftest is testing]]
|
||||
[hiccup2.core :as h2]))
|
||||
|
||||
(deftest hiccup->html
|
||||
(testing "renders a Hiccup form to an HTML string"
|
||||
(is (= "<span class=\"label\">A & B</span>"
|
||||
(sut/hiccup->html [:span.label "A & B"])))))
|
||||
|
||||
(deftest selmer-embeds-hiccup
|
||||
(testing "a Hiccup component renders inside a Selmer template via |safe"
|
||||
(let [frag (sut/hiccup->html [:span.badge "from hiccup"])
|
||||
out (sut/render-str "<div>{{frag|safe}}</div>" {:frag frag})]
|
||||
(is (str/includes? out "<span class=\"badge\">from hiccup</span>"))
|
||||
;; without |safe the markup would be escaped; |safe keeps it verbatim
|
||||
(is (not (str/includes? out "<span"))))))
|
||||
|
||||
(deftest selmer-fragment-inside-hiccup
|
||||
(testing "a Selmer fragment renders inside a Hiccup tree without double-escaping"
|
||||
(let [sel (sut/render-str "<a href=\"{{url}}\">{{label}}</a>" {:url "/x" :label "Go"})
|
||||
out (str (h2/html {} [:div (sut/raw sel)]))]
|
||||
(is (= "<div><a href=\"/x\">Go</a></div>" out)))))
|
||||
|
||||
(deftest render-file-from-classpath
|
||||
(testing "render-file resolves a template under resources/templates and keeps plain-HTML Alpine/HTMX attrs"
|
||||
(let [out (sut/render "templates/interop-smoke.html"
|
||||
{:title "Interop OK"
|
||||
:hiccup_frag (sut/hiccup->html [:span.badge "from hiccup"])})]
|
||||
(is (str/includes? out "Interop OK"))
|
||||
(is (str/includes? out "from hiccup"))
|
||||
;; plain-HTML attributes (the whole point of Selmer) survive unambiguously
|
||||
(is (str/includes? out "x-model=\"value.value\""))
|
||||
(is (str/includes? out "tippy?.show()")))))
|
||||
Reference in New Issue
Block a user