re-alm
An Elm Architecture experiment in ClojureScript, powered by Reagent
Why?
You want to build SPAs the simplest way possible, using nothing, but pure, composable functions.
Usage
Start a new project:
lein new re-alm <project-name>
You can also add Webpack support, so You can easily use npm modules
lein new re-alm <project-name> +webpack
Basic usage
The simples component I can came up with:
(defn init-counter []
0)
(defn render-counter [model dispatch]
[:div (str model)
[:button {:on-click #(dispatch :inc)} "increment"]
[:button {:on-click #(dispatch :dec)} "decrement"]])
(defn update-counter [model msg]
(match msg
:inc
(inc model)
:dec
(dec model)
_
model))
In the render function you get the model, and a function to dispatch
your messages with. The messages are automatically routed to your update function.
In the update function you get the actual model, and the message you should handle. I prefer to use core.match here, but it is optional. The result is the new model, or the model and side effects (more on that later)
And finally, you got to bootstrap your app.
(boot
(.getElementById js/document "app")
{:update #'update-counter
:render #'render-counter}
(init-counter))
Advanced usage
The result of the update function can be the new model, or possibly some side effects attached to it.
(defn update [model msg]
(match msg
:go-and-fetch-some-data
(with-fx
(assoc model :loading? true)
(http/get-fx "/url" {:params {:foo :bar}} :fetch-done))
[:fetch-done {:ok data}]
(assoc model
:loading? false
:data data)
_
model))
Every time the model changes, the optionally provided subscriptions
function is called, where you can describe what external events you are interested in.
(defn update-foo [model msg]
(match msg
[:tick _]
(update model :ticks-so-far inc)))
(defn subscriptions [model]
[(time/every 1000 :tick)])
(def foo-component
{:update #'update-foo
:render #'render-foo
:subscriptions #'subscriptions})
Middlewares are following the concept you may already know from Ring. The middleware function takes a component (the root component of your app), and the message being dispatched. It returns a tuple (vector) of the new version of the model, the side effects made during the update, and the subscriptions your model currently interested in.
(defn wrap-log [handler]
(fn [component msg]
(.log js/console msg)
(handler component msg)))
(boot
(.getElementById js/document "app")
component
model
(-> ra/handler
ra/wrap-log))
Parent/children communication example
; TODO
Using websockets
(defn- update-ws [model msg]
(match msg
:send-msg
(ra/with-fx
model
(ws/websocket-fx "/ws" "payload"))
[:message-from-ws m]
(update-in model [:messages] conj m)
_
model))
(defn- subscriptions [model]
[(ws/websocket "/ws" :message-from-ws)])
(def ws-controller
{:render #'render-ws
:update #'update-ws
:subscriptions #'subscriptions})
Roadmap (just a bunch of what-ifs)
- the effets (http, ws, storage) may go into a separate package
- the boot process is a mess, needs some cleanup
License
The license is MIT.