-
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
- Boot http://boot-clj.com/
I suggest Boot which is like Gulp. You may also try Figwheel and Leiningen.
A component is defined by calling create-comp
function with a name and a render method:
(ns respo.comp.space
(: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}))
(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 attrs
. Specify them in HashMaps or nothing:
(input
{:style {:color "grey"},
:event {:input (on-text-state cursor)}, ; a function, will explain later
:attrs {: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]
(let [state (or (:data states) "")]
(input {:attrs {: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 ref-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 ref-store (atom schema/store))
(defn dispatch! [op op-data]
(let [op-id (.valueOf (js/Date.))
new-store (updater @ref-store op op-data op-id)]
(reset! store-ref new-store)))
(defn render-app! []
(let [mount-target (.querySelector js/document "#app")]
(render! (comp-container @ref-store) 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. Call mutate!
to change component state:
(defn on-text-change [state cursor]
(fn [e dispatch!]
(dispatch! :states [cursor (:value e)]))))
(input
{:style style-input,
:event {:input (on-text-change state cursor)},
:attrs {:value (:text task)}})
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 {:attrs {:inner-text "Remove"}}))
Both dispatch!
and mutate!
will cause a change in store-ref
or states-ref
.
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
{:style style-input,
:event {:input (on-text-change props state)},
:attrs {:value (:text task)}})
(comp-space 8 nil)
(div
{:style style-time}
(span
{:style style-time,
:attrs {:inner-text (:time state)}})))
We may introduce Clojure Spec in the future to validate component paramaters.
You may need a build.boot
to run ClojureScript, here's the part related:
(deftask dev []
(comp
(watch)
(reload :on-jsload 'spa-example.core/on-jsload!)
(cljs)
(target)))
Find the whole build.boot
at https://github.com/Respo/respo-spa-example
boot dev # and wait a minute
Run target/index.html
to see if it works.