Skip to content

Quick Start

ChenYong edited this page May 11, 2017 · 33 revisions

Want to explore by yourself?

What is Respo?

Respo is a virtual DOM library like React, but with ClojureScript to embrace functional programming.

Before start

Besides experiences on Web apps, you also need to know:

I suggest Boot which is like Gulp. You may also try Figwheel and Leiningen.

Component definition

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"}})

States

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 {: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))

Render to the DOM

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.

Rerender on updates

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.

Handling events

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.

Composing component

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.

Compile and run

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.

Find me on Twitter if you run into problems

Clone this wiki locally