-
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 div]])
(:require [respo.alias :refer [create-comp]]))
(def style-space
{:width "1px", :display "inline-block", :height "1px"})
(defn compute [w h]
(if (some? w)
(assoc style-space :width w)
(assoc style-space :height h)))
(defcomp comp-space [w h]
(div {:style (compute w h)}))
; which expands to:
(def comp-space
(create-comp :space
(fn [w h]
(fn [cursor]
(div {:style (compute 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"},
:on {:input (on-text-state cursor)}, ; a function, will explain later
; attributes
{:placeholder "A name"})
<>
is a macro to simplify you simple text:
(<> span text style)
expands to
(span {:inner-text text, :style style})
=<
is an alias for comp-space
, just use it like that:
(=< 8 nil) ; (comp-space 8 nil)
A component can also be created with states.
cursor
is inserted with macro defcomp
so no worries.
(defn on-input [old-state cursor]
(fn [e dispatch! mutate!]
(mutate! (:value e))))
(defcomp comp-demo [states]
(let [state (or (:data states) "")]
(input {:value state
:on {:input (on-input state cursor)}}))
(mutate! s)
updates state of current component.
To update parents' states, use (mutate! cursor s)
with a right cursor
(advanced topic).
Use respo.macros/cursor->
to pick the branch of the state tree:
(cursor-> :demo comp-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 op-id]
(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 id! [] (.valueOf (js/Date.))
(defn dispatch! [op op-data]
(let [op-id (id!))
new-store (updater @*store op op-data op-id)]
(reset! *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! []
(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 :on
.
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 [old-state]
(fn [e dispatch! mutate!]
(mutate! (:value e))))
(input
{:value (:text task)
:style style-input
:on {:input (on-text-change old-state)}})
To handle a global action, call dispatch!
function with an action type and a parameter:
(defn handle-remove [props]
(fn [e dispatch! mutate!]
(dispatch! :remove (:id (:task props)))))
(div
{:style style-button,
:on {:click (handle-remove props)}}
(<> span "Remove" nil))
dispatch!
will cause a change in *store
. Also note previously :on
was :event
.
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
:on {: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.5.0"]]
:builds {:app {:output-dir "target/"
:asset-path "."
:target :browser
:modules {:main {:entries [app.main]}}
:devtools {:after-load app.main/reload!}}}}
then you can compile it:
shadow-cljs watch app
Find the more in https://github.com/Respo/minimal-respo
Create an HTML with <script src="main.js"></script>
in target/
to run it.
http-server target/
I will be on Twitter if you need help.