Skip to content

Latest commit

 

History

114 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

re-alm

An Elm Architecture experiment in ClojureScript, powered by Reagent

Clojars Project Dependencies Status

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.

About

An Elm Architecture experiment in ClojureScript

Topics

Resources

Stars

23 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages