(require '[wagoe.shared.ui.core.components :as ui]
'[wagoe.shared.ui.core.layout :as layout])
(layout/page-layout
"Products"
[:div
(ui/text-input :name "" {:placeholder "Product name"})
(ui/submit-button "Save")])
com.wagoe/wagoe-shared-ui holds the Hiccup building blocks that more than one
library renders with. It was extracted so admin and user could stop
depending on each other’s UI code — that mutual dependency was a genuine cycle
until this library broke it.
A library of pure functions returning Hiccup. No I/O, no state, no ports.
That last part is deliberate and worth stating, because it makes shared-ui the
one exception to Wagoe’s usual public-API rule. Elsewhere core/ is an
implementation detail and you depend on ports.clj; here there is no port,
because there is nothing to abstract over — a function that turns arguments into
a vector has no adapter to swap. The functions under wagoe.shared.ui.core.
*are the public surface, and the stability policy
covers them as such.
| Namespace | Contains |
|---|---|
| Form controls and inline feedback: |
| Page scaffolding: |
| The admin shell (sidebar, topbar) and the signed-in user’s menu |
|
|
|
|
| Typeahead input that asks the server for options |
| Form attributes, decoding and errors from a Malli map schema |
|
|
| Turning Malli explain output into per-field messages: |
| Inline SVG icon set |
| Alpine.js attribute helpers for HTMX-driven interactions |
shared-ui produces markup; ui-style ships the CSS and
JS that markup expects. The class names the components emit are the contract
between them, which is why the two are versioned in lockstep like everything
else.
(require '[wagoe.shared.ui.core.components :as ui]
'[wagoe.shared.ui.core.layout :as layout])
(layout/page-layout
"Products"
[:div
(ui/text-input :name "" {:placeholder "Product name"})
(ui/submit-button "Save")])
Because every function is pure, they are straightforward to test — call one and assert on the Hiccup vector it returns, with no rendering step and no system to start.
The table’s controls request the list’s own URL and push it, so sort, filters and page live in the address bar. The handler answers an htmx request with the table and anything else with the whole page.
(require '[wagoe.shared.ui.core.table :as table])
(defn orders-table [orders tq filters total]
[:div#orders
(table/data-table
{:url "/orders"
:hx-target "#orders"
:table-query tq
:filters filters
:columns [{:field :number :label "Number"}
{:field :status :label "Status" :filter {:options [["paid" "Paid"]]}}]
:rows (for [o orders] [:tr [:td (:number o)] [:td (:status o)]])})
(table/pagination {:table-query tq :total-count total
:base-url "/orders" :hx-target "#orders" :extra-params filters})])
;; In the handler
(let [{:keys [filters] :as tq} (table/parse-table-params (:query-params request)
{:sortable #{:number :status}
:max-page-size 100})]
...)
With :infinite? true (and :total-count and :rows-url) the last row loads the next page
once it scrolls into view; the handler at :rows-url answers with just the
<tr> rows. Send Vary: HX-Request, HX-Target from a handler that answers one URL with
both the page and the fragment.
Can you improve this documentation? These fine people already did:
thijscreemers & Thijs CreemersEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |