2014-01-02 13:04:30 +00:00
|
|
|
(ns demo
|
|
|
|
(:require [cloact.core :as cloact :refer [atom]]
|
|
|
|
[clojure.string :as string])
|
|
|
|
(:require-macros [demoutil :refer [get-source]]
|
|
|
|
[cloact.debug :refer [dbg println]]))
|
|
|
|
|
|
|
|
(def demosrc (get-source "demo.cljs"))
|
|
|
|
|
|
|
|
(defn src-parts [src]
|
|
|
|
(->>
|
|
|
|
(string/split src #"\n\(")
|
|
|
|
rest
|
|
|
|
(map #(str "(" %))))
|
|
|
|
|
|
|
|
(defn src-defs [parts]
|
|
|
|
(into {} (for [x parts]
|
|
|
|
[(keyword (nth (string/split x #"\s+") 1))
|
|
|
|
x])))
|
|
|
|
|
|
|
|
(def srcmap
|
|
|
|
(-> "demo.cljs"
|
|
|
|
get-source
|
|
|
|
src-parts
|
|
|
|
src-defs))
|
|
|
|
|
|
|
|
(def nssrc
|
|
|
|
"(ns example
|
|
|
|
(:require [cloact.core :as cloact :refer [atom]])
|
|
|
|
")
|
|
|
|
|
|
|
|
(defn src-for-names [names]
|
|
|
|
(let [defs (merge srcmap {:ns nssrc})]
|
|
|
|
(string/join "\n"
|
|
|
|
(map #(% defs) names))))
|
|
|
|
|
2014-01-02 16:45:31 +00:00
|
|
|
(def builtins ["def" "defn" "ns" "atom" "let" "if" "when"
|
|
|
|
"cond" "merge"])
|
|
|
|
|
|
|
|
(defn syntaxify [src]
|
|
|
|
(let [str-p "\"[^\"]*\""
|
|
|
|
keyw-p ":[^\\][(){} \\t\\n]+"
|
|
|
|
res-p (string/join "\\b|" builtins)
|
|
|
|
any-p ".|\\n"
|
|
|
|
patt (re-pattern (str "("
|
|
|
|
(string/join ")|(" [str-p keyw-p res-p any-p])
|
|
|
|
")"))]
|
|
|
|
(apply vector :pre
|
|
|
|
(for [[s str keyw res] (re-seq patt src)]
|
|
|
|
(cond
|
|
|
|
str [:span {:style {:color "green"}} str]
|
|
|
|
keyw [:span {:style {:color "blue"}} keyw]
|
|
|
|
res [:b res]
|
|
|
|
:else s)))))
|
|
|
|
|
2014-01-02 13:04:30 +00:00
|
|
|
(defn src-for [{:keys [defs]}]
|
2014-01-02 16:45:31 +00:00
|
|
|
[:pre (syntaxify (src-for-names defs))])
|
2014-01-02 13:04:30 +00:00
|
|
|
|
2014-01-02 15:18:21 +00:00
|
|
|
(defn demo-component [props]
|
|
|
|
[:div.example
|
|
|
|
[:h3 "Example"]
|
|
|
|
[(:comp props)]
|
|
|
|
[:h3 "Source"]
|
|
|
|
[src-for props]])
|
|
|
|
|
2014-01-02 13:04:30 +00:00
|
|
|
(defn simple-component []
|
|
|
|
[:div
|
2014-01-03 09:56:15 +00:00
|
|
|
[:p "I am a component!"]
|
2014-01-02 13:04:30 +00:00
|
|
|
[:p.someclass
|
|
|
|
"I have " [:strong "bold"]
|
2014-01-03 09:56:15 +00:00
|
|
|
[:span {:style {:color "red"}} " and red "] "text."]])
|
2014-01-02 13:04:30 +00:00
|
|
|
|
2014-01-03 09:56:15 +00:00
|
|
|
(defn simple-parent []
|
2014-01-02 13:04:30 +00:00
|
|
|
[:div
|
2014-01-03 09:56:15 +00:00
|
|
|
[:p "I include simple-component."]
|
|
|
|
[simple-component]])
|
|
|
|
|
|
|
|
(defn lister [props]
|
|
|
|
[:ul
|
|
|
|
(for [item (:items props)]
|
|
|
|
[:li "Item " item])])
|
|
|
|
|
|
|
|
(defn lister-user []
|
|
|
|
[:div
|
|
|
|
"Here is a list:"
|
|
|
|
[lister {:items (range 3)}]])
|
2014-01-02 15:18:21 +00:00
|
|
|
|
2014-01-03 12:22:41 +00:00
|
|
|
(def click-count (atom 0))
|
|
|
|
|
|
|
|
(defn counting-component []
|
|
|
|
[:div
|
|
|
|
"The atom " [:code "click-count"] " has value: " @click-count ". "
|
|
|
|
[:input {:type "button"
|
|
|
|
:value "Click me!"
|
|
|
|
:on-click #(swap! click-count inc)}]])
|
|
|
|
|
|
|
|
(defn local-state []
|
|
|
|
(let [val (atom "foo")]
|
|
|
|
(fn []
|
|
|
|
[:div
|
|
|
|
[:p "The value of " [:code "val"] " is now: " @val]
|
|
|
|
[:p "Change it: "
|
|
|
|
[:input {:type "text"
|
|
|
|
:value @val
|
|
|
|
:on-change #(reset! val (-> % .-target .-value))}]]])))
|
|
|
|
|
2014-01-02 15:18:21 +00:00
|
|
|
(defn calc-bmi [{:keys [height weight bmi] :as params}]
|
|
|
|
(let [h (/ height 100)]
|
|
|
|
(if (nil? bmi)
|
|
|
|
(assoc params :bmi (/ weight (* h h)))
|
|
|
|
(assoc params :weight (* bmi h h)))))
|
|
|
|
|
|
|
|
(def bmi-data (atom (calc-bmi {:height 180 :weight 80})))
|
|
|
|
|
|
|
|
(defn set-bmi [key val clear]
|
|
|
|
(swap! bmi-data #(calc-bmi (merge % {key val, clear nil}))))
|
|
|
|
|
|
|
|
(defn slider [{:keys [value key clear min max]}]
|
|
|
|
[:div
|
|
|
|
[:input {:type "range" :min min :max max :value value
|
|
|
|
:style {:width "100%"}
|
|
|
|
:on-change #(set-bmi key (-> % .-target .-value)
|
|
|
|
(or clear :bmi))}]])
|
|
|
|
|
|
|
|
(defn bmi-component []
|
|
|
|
(let [{:keys [weight height bmi]} @bmi-data
|
|
|
|
[color diagnose] (cond
|
|
|
|
(< bmi 18.5) ["orange" "underweight"]
|
|
|
|
(< bmi 25) ["inherit" "normal"]
|
|
|
|
(< bmi 30) ["orange" "overweight"]
|
|
|
|
:else ["red" "obese"])]
|
|
|
|
[:div
|
|
|
|
[:div
|
|
|
|
"Height: " (int height) "cm"
|
|
|
|
[slider {:value height :min 100 :max 220 :key :height}]]
|
|
|
|
[:div
|
|
|
|
"Weight: " (int weight) "kg"
|
|
|
|
[slider {:value weight :min 50 :max 200 :key :weight}]]
|
|
|
|
[:div
|
|
|
|
"BMI: " (int bmi) " "
|
|
|
|
[:span {:style {:color color}} diagnose]
|
|
|
|
[slider {:value bmi :min 10 :max 50 :key :bmi :clear :weight}]]]))
|
|
|
|
|
2014-01-03 09:56:15 +00:00
|
|
|
(defn intro []
|
|
|
|
[:div
|
2014-01-03 12:22:41 +00:00
|
|
|
[:h2 "Introduction to Cloact"]
|
2014-01-03 09:56:15 +00:00
|
|
|
|
|
|
|
[:p "Cloact provides a minimalistic interface between ClojureScript
|
|
|
|
and React. It allows you to define React components using nothing but
|
|
|
|
plain ClojureScript functions, that describe your UI using a
|
|
|
|
Hiccup-like syntax."]
|
|
|
|
|
|
|
|
[:p "A very basic component may look something like this: "]
|
|
|
|
[demo-component {:comp simple-component
|
|
|
|
:defs [:simple-component]}]
|
|
|
|
|
|
|
|
[:p "You can build new components using other components as
|
|
|
|
building blocks. That looks like this:"]
|
|
|
|
[demo-component {:comp simple-parent
|
|
|
|
:defs [:simple-parent]}]
|
|
|
|
|
|
|
|
[:p "Data is passed to child components using a plain old Clojure
|
|
|
|
maps. For example, here is a component that shows items in a "
|
|
|
|
[:code "seq"] "." ]
|
|
|
|
|
|
|
|
[demo-component {:comp lister-user
|
2014-01-03 12:22:41 +00:00
|
|
|
:defs [:lister :lister-user]}]])
|
|
|
|
|
|
|
|
(defn managing-state []
|
|
|
|
[:div
|
|
|
|
[:h2 "Managing state in Cloact"]
|
|
|
|
|
|
|
|
[:p "The easiest way to manage state in Cloact is to use Cloact's
|
|
|
|
own version of " [:code "atom"] ". It works exactly like the one in
|
|
|
|
clojure.core, except that it keeps track of every time it is
|
|
|
|
deref'ed. Any component that uses the atom is automagically
|
|
|
|
re-rendered."]
|
|
|
|
|
|
|
|
[:p "Let's demonstrate that with a simple example:"]
|
|
|
|
[demo-component {:comp counting-component
|
|
|
|
:defs [:ns :click-count :counting-component]}]
|
|
|
|
|
|
|
|
[:p "Sometimes you may want to maintain state locally in a
|
|
|
|
component. That is very easy to do with an " [:code "atom"] " as well."]
|
|
|
|
|
|
|
|
[:p "Here is an example of that:"]
|
|
|
|
[demo-component {:comp local-state
|
|
|
|
:defs [:ns :local-state]}]
|
|
|
|
|
|
|
|
[:p "This example also uses another feature of Cloact: a component
|
|
|
|
function can return another function, that is used to do the actual
|
|
|
|
rendering. It is called with the same arguments as any other
|
|
|
|
component function. This allows you to perform some setup of newly
|
|
|
|
created components, without resorting to React's lifecycle
|
|
|
|
events."]])
|
2014-01-03 09:56:15 +00:00
|
|
|
|
|
|
|
|
2014-01-02 15:18:21 +00:00
|
|
|
(defn bmi-demo []
|
|
|
|
[:div
|
|
|
|
[:h2 "Simple BMI calculator"]
|
|
|
|
[demo-component {:comp bmi-component
|
|
|
|
:defs [:ns :calc-bmi :bmi-data :set-bmi :slider
|
|
|
|
:bmi-component]}]])
|
2014-01-02 13:04:30 +00:00
|
|
|
|
|
|
|
(defn demo []
|
|
|
|
[:div
|
|
|
|
[:h1 "This will become a demo"]
|
2014-01-03 09:56:15 +00:00
|
|
|
[intro]
|
2014-01-03 12:22:41 +00:00
|
|
|
[managing-state]
|
2014-01-02 15:18:21 +00:00
|
|
|
[bmi-demo]
|
2014-01-02 13:04:30 +00:00
|
|
|
[:p "WIP"]])
|