-
Notifications
You must be signed in to change notification settings - Fork 10
Quick Start
Would like to explore by yourself? Check out this demo: https://github.com/Respo/respo-spa-example
Respo is a simple DOM library like React but totally built with ClojureScript to embrace functional programming.
Since you have reached this page, I suppose you have some experiences on Web developing. There are quite some stuffs you need to know:
- Clojure
- ClojureScript https://github.com/clojure/clojurescript/wiki/Quick-Start
- Boot http://boot-clj.com/
Yeah, I will suggest Boot for its simplicity. Boot is like Gulp. You may also try Figwheel.
A component is created by calling create-comp
function with a name and a render method, for example:
(ns respo.component.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}))
(defn render [w h] (fn [state mutate!] (div {:style (style-space w h)})))
(def comp-space (create-comp :space render))
comp-space
here is a function that accepts props.
DOM properties are divided into style
event
and attrs
. Specify them with HashMaps or nothing:
(input
{:style style-input,
:event {:input on-text-state},
:attrs {:value state}})
Component can also be created with state methods, like:
(defn init-state [props] {:draft ""})
(defn update-state [old-state changes]
(merge old-state changes))
(create-comp :demo init-state update-state render)
Both init-state
and update
are pure functions.
In order to render, you need to feed the component with store
and states
.
Better to define them with Atom
since they are the data sources:
(defonce store-ref (atom schema/store))
(defonce states-ref (atom {}))
(defn dispatch! [op op-data]
(let [op-id (.valueOf (js/Date.))
new-store (updater @store-ref op op-data op-id)]
(reset! store-ref new-store)))
(defn render-app! []
(let [mount-target (.querySelector js/document "#app")]
(render! (comp-container @store-ref) mount-target dispatch! states-ref)))
Better to render on package load and on changes of data sources:
(defn -main []
(enable-console-print!)
(render-app!)
(add-watch global-store :rerender render-app!)
(add-watch global-states :rerender render-app!))
(set! (.-onload js/window) -main)
Also by using boot-reload
you get hot swapping for free 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 a state update, you need to pass a function to :input
field in :event
.
This function will be called with event
(wrapped) and dispatch
function we defined in rendering.
And at the end of the function body, call mutate with changes:
(defn on-text-change [mutate!]
(fn [e dispatch!]
(mutate! (:value e)))))
(input
{:style style-input,
:event {:input (on-text-change mutate!)},
: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 like just functions that returns 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
Then open target/index.html
to see if it works.