SimpleUI is an app development tool for fast product development and even faster page load times. It uses htmx on the frontend.
(defcomponent ^:endpoint hello [req my-name]
[:div#hello "Hello " my-name])
(def ring-handler
(fn [req]
;; page renders initial html
(page
[:form.hello {:hx-patch "hello" :hx-target "#hello"}
[:label "What is your name?"]
[:input.mr {:type "text" :name "my-name"}]
[:input {:type "submit"}]
(hello req "")])))
The core of SimpleUI is the defcomponent
macro which expands to both:
my-name
.defcomponent
enables developers to quickly build rich user interfaces with no javascript. All code is on the server backend and yet it feels the same as frontend code.
Getting started is easy with clojure tools and the excellent kit framework.
clojure -Ttools install com.github.seancorfield/clj-new '{:git/tag "v1.2.381"}' :as new
clojure -Tnew create :template io.github.kit-clj :name yourname/guestbook
cd guestbook
make repl
(kit/sync-modules)
(kit/install-module :kit/simpleui)
Quit the process, make repl
then
(go)
Visit localhost:3000. To reload changes
(reset)
SimpleUI uses Hypermedia as the Engine of Application State (HATEOAS), the web as it was originally supposed to be. Application state is implicitly stored in the html itself, not in a separate javascript layer. By extending the original html model instead of building a javascript layer over top, we get simplicity and much faster page load times.
(defcomponent ^:endpoint form [req ^:path first-name ^:path last-name]
[:form {:id id :hx-post "form"}
[:input {:type "text" :name (path "first-name") :value first-name}] [:br]
[:input {:type "text" :name (path "last-name") :value last-name}] [:br]
(when (= ["Barry" "Crump"] [first-name last-name])
[:div "A good keen man!"])
[:input {:type "submit"}]])
(def ring-handler
(fn [req]
;; page renders initial html
(page
(form req "Barry" ""))))
SimpleUI maintains a call stack of nested components. This makes it easy to label data without name clashes. Try submitting the above form and then inspecting the browser network tab.
(path "first-name")
and (path "last-name")
macroexpand to unique values which are automatically mapped back to the function arguments. We can use the form
component multiple times on the page without worrying about a name clash.
If a component only exists once on a page you can use the above example without path
.
To expand (path "first-name")
and (path "last-name")
consistently we must be careful with components inside arrays. Use simpleui.rt/map-indexed
to map values across an array.
Matthew | Molloy |
Chad | Thomson |
(defcomponent table-row [req i first-name last-name]
[:tr
[:td first-name] [:td last-name]])
(defcomponent table [req]
[:table
(simpleui.rt/map-indexed
table-row
req
[{:first-name "Matthew" :last-name "Molloy"}
{:first-name "Chad" :last-name "Thomson"}])])
(def ring-handler
(fn [req]
;; page renders initial html
(page
(table req))))
(defcomponent ^:endpoint click-div [req ^:long num-clicks]
[:form {:id id :hx-get "click-div" :hx-trigger "click"}
[:input {:type "hidden" :name "num-clicks" :value (inc num-clicks)}]
"You have clicked me " num-clicks " times!"])
(def ring-handler
(fn [req]
;; page renders initial html
(page
(click-div req 0))))
SimpleUI uses native html forms, so data is submitted as strings. We can cast it as necessary. Supported casts include ^:long, ^:boolean and ^:double. See documentation for details.
We may also cast within the body of defcomponent
.
[:div
(if ^:boolean (value "grumpy")
"Cheer up!"
"How are you?")]
Please see the examples or full documentation.