-
Notifications
You must be signed in to change notification settings - Fork 10
Quick Start
This guide is prepared for
respo@0.3.30
. The latest version of Respo is0.4.x
.
Would like to explore by yourself? Check out the demos https://github.com/Respo/respo-spa-example
Respo is a front-end MVC library like React.js but built with ClojureScript to embrace functional programming.
Since you have reached this page, I suppose you have experiences on Web apps. There are still stuffs you 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}))
(defn render [w h] (fn [state mutate!] (div {:style (style-space w h)})))
(def comp-space (create-comp :space render))
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 mutate!)}, ; a function, will explain later
:attrs {:placeholder "A name"}})
A component can also be created with states, 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-state
should be pure functions. And you will use mutate!
to trigger state updates.
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-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)))
states
are used by Respo internally, make sure it's {}
in an Atom.
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 :gc (fn [] (gc-states! global-states)))
(add-watch global-store :rerender render-app!)
(add-watch global-states :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 [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 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.