-
Notifications
You must be signed in to change notification settings - Fork 10
Quick Start
Want to explore by yourself?
- Minimal App https://github.com/Respo/minimal-respo
- Examples https://github.com/Respo/respo-examples
Respo is a virtual DOM library like React, but with ClojureScript to embrace functional programming.
Besides experiences on Web apps, you also need to know:
- Clojure
- ClojureScript http://clojurescript.org
- shadow-cljs https://github.com/thheller/shadow-cljs
Components are defined with a macro called defcomp
:
(ns respo.comp.space
(:require-macros [respo.macros :refer [defcomp]])
(:require [respo.alias :refer [create-comp div]]))
(defn style-space [w h]
(if (some? w)
{:width w, :display "inline-block", :height "1px"}
{:width "1px", :display "inline-block", :height h}))
(defcomp comp-spac [w h]
(div {:style (style-space w h)}))
; ; which expands to:
; (def comp-space
; (create-comp :space
; (fn [w h]
; (fn [cursor]
; (div {:style (style-space w h)})))))
comp-space
is a function that accepts parameters like render
function:
(comp-space nil "16px")
DOM properties are divided into style
event
and attributes. Specify them in HashMaps or nothing:
(input
{:style {:color "grey"},
:event {:input (on-text-state cursor)}, ; a function, will explain later
; attributes
{:placeholder "A name"})
A component can also be created with states, like:
(defn on-input [old-state cursor]
(fn [e dispatch!]
(dispatch! :states [cursor (:value e)])))
(defn comp-demo
(create-comp :demo
(fn [states]
(fn [cursor]
(let [state (or (:data states) "")]
(input {:value state
:event {:input (on-input state cursor)}}))))
Use respo.cursor/with-cursor
to pick the branch of the state tree:
(with-cursor :demo (comp-demo (:demo states)))
You also need respo.cursor/mutate
to update the store since states is held in it:
(defonce *store (atom {:states {}}))
(defn updater [store op op-data]
(case op
:states (update store :states (mutate op-data))
store))
In order to render, you need to define store
and states
.
Use Atoms here since they are the data sources that change over time:
(defonce *store (atom schema/store))
(defn dispatch! [op op-data]
(let [op-id (.valueOf (js/Date.))
new-store (updater @*store op op-data op-id)]
(reset! ref-store new-store)))
(defn render-app! []
(let [mount-target (.querySelector js/document "#app")
app (comp-container @*store)]
(render! app mount-target dispatch!)))
Note that you need to define dispatch!
function by yourself.
Better to render on page load and changes of data sources:
(defn main! []
(enable-console-print!)
(render-app!)
(add-watch global-store :rerender render-app!))
(set! (.-onload js/window) main!)
Also by using boot-reload
plugin you get hot swapping easily with store and states retained:
(defn on-jsload! []
(clear-cache!)
(render-app!))
Notice that clear-cache!
is from respo.core
and it clears component caches after code updated.
To handle state updates, you need to pass a function to :input
field in :event
.
This function will be called with event
(wrapped in :original-event
of e
) and dispatch!
function we defined in rendering. To change component state:
(defn on-text-change [state cursor]
(fn [e dispatch!]
(dispatch! :states [cursor (:value e)]))))
(input
{:value (:text task)
:style style-input
:event {:input (on-text-change state cursor)}})
To handle a global action, call dispatch!
function with an action type and a parameter:
(defn handle-remove [props]
(fn [e dispatch!]
(dispatch! :remove (:id (:task props)))))
(div
{:style style-button,
:event {:click (handle-remove props)}}
(span {:inner-text "Remove"}))
Both dispatch!
will cause a change in ref-store
.
Reusing components is easy. They are wrapped functions that return components:
(div
{:style style-task}
(comp-debug task {:left "160px"})
(button {:style (style-done (:done task))})
(comp-space 8 nil)
(input
{:value (:text task)
:style style-input
:event {:input (on-text-change props state)}})
(comp-space 8 nil)
(div
{:style style-time}
(span
{:inner-text (:time state)
:style style-time})))
You may need a shadow-cljs.edn
to configure the compiler:
{:source-paths ["src"]
:dependencies [[mvc-works/hsl "0.1.2"]
[respo/ui "0.1.9"]
[respo "0.4.4"]]
:builds {:app {:output-dir "target/"
:asset-path "."
:target :browser
:modules {:main {:entries [app.main]}}
:devtools {:after-load app.main/reload!}}}}
Find the whole build.boot
at https://github.com/Respo/respo-spa-example
shadow-cljs -b app --dev
Serve an HTML with js files in target/
to run it.
I will be on Twitter if you need help.