reagent/demo/demo.cljs

354 lines
12 KiB
Plaintext
Raw Normal View History

2014-01-02 13:04:30 +00:00
(ns demo
2014-01-17 10:12:11 +00:00
(:require [reagent.core :as reagent :refer [atom]]
2014-01-06 11:46:58 +00:00
[clojure.string :as string]
[demoutil :as demoutil :refer-macros [get-source]]
2014-01-17 10:12:11 +00:00
[reagent.debug :refer-macros [dbg println]]))
2014-01-02 13:04:30 +00:00
(defn src-parts [src]
2014-01-06 11:46:58 +00:00
(string/split src #"\n(?=[(])"))
2014-01-02 13:04:30 +00:00
(defn src-defs [parts]
2014-01-06 11:46:58 +00:00
(let [ws #"\s+"]
(into {} (for [x parts]
[(-> x (string/split ws) second keyword) x]))))
2014-01-02 13:04:30 +00:00
(def srcmap
2014-01-06 11:46:58 +00:00
(-> "demo.cljs" get-source src-parts src-defs))
2014-01-02 13:04:30 +00:00
(def nssrc
"(ns example
2014-01-17 10:12:11 +00:00
(:require [reagent.core :as reagent :refer [atom]]))
2014-01-02 13:04:30 +00:00
")
(defn src-for-names [names]
2014-01-06 11:46:58 +00:00
(string/join "\n" (-> srcmap
(assoc :ns nssrc)
(select-keys names)
vals)))
2014-01-02 16:45:31 +00:00
2014-01-05 12:29:22 +00:00
(defn src-for [defs]
2014-01-06 11:46:58 +00:00
[:pre (-> defs src-for-names demoutil/syntaxify)])
2014-01-02 13:04:30 +00:00
(defn demo-component [{:keys [comp defs src]}]
2014-01-07 11:45:08 +00:00
(let [colored (if src
(demoutil/syntaxify src)
(src-for defs))
showing (atom true)]
(fn []
[:div
(when comp
[:div.demo-example
[:a.demo-example-hide {:on-click (fn [e]
(.preventDefault e)
(swap! showing not))}
(if @showing "hide" "show")]
[:h3.demo-heading "Example "]
(when @showing
(if defs
[:div.simple-demo [comp]]
[comp]))])
(when @showing
[:div.demo-source
[:h3.demo-heading "Source"]
colored])])))
2014-01-02 15:18:21 +00:00
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)]
2014-01-03 15:52:05 +00:00
[:li {:key item} "Item " item])])
2014-01-03 09:56:15 +00:00
(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
2014-01-03 14:52:18 +00:00
"The atom " [:code "click-count"] " has value: "
@click-count ". "
2014-01-04 14:03:26 +00:00
[:input {:type "button" :value "Click me!"
2014-01-03 12:22:41 +00:00
:on-click #(swap! click-count inc)}]])
2014-01-04 14:03:26 +00:00
(defn atom-input [{:keys [value]}]
[:input {:type "text"
:value @value
:on-change #(reset! value (-> % .-target .-value))}])
(defn shared-state []
2014-01-03 12:22:41 +00:00
(let [val (atom "foo")]
(fn []
[:div
2014-01-04 09:56:21 +00:00
[:p "The value is now: " @val]
2014-01-04 14:03:26 +00:00
[:p "Change it here: "
[atom-input {:value val}]]])))
2014-01-03 14:52:18 +00:00
2014-01-04 09:56:21 +00:00
(defn timer-component []
(let [seconds-elapsed (atom 0)]
(fn []
(js/setTimeout #(swap! seconds-elapsed inc) 1000)
[:div
"Seconds Elapsed: " @seconds-elapsed])))
2014-01-03 14:52:18 +00:00
(defn render-simple []
2014-01-17 10:12:11 +00:00
(reagent/render-component [simple-component]
(.-body js/document)))
2014-01-03 12:22:41 +00:00
2014-01-07 11:45:08 +00:00
(defn calc-bmi [params to-calc]
(let [{:keys [height weight bmi]} params
h (/ height 100)]
(case to-calc
:bmi (assoc params :bmi (/ weight (* h h)))
:weight (assoc params :weight (* bmi h h)))))
2014-01-02 15:18:21 +00:00
2014-01-07 11:45:08 +00:00
(def bmi-data (atom (calc-bmi {:height 180 :weight 80} :bmi)))
2014-01-02 15:18:21 +00:00
2014-01-07 11:45:08 +00:00
(defn set-bmi [key val]
(swap! bmi-data #(calc-bmi (assoc % key val)
(case key :bmi :weight :bmi))))
2014-01-02 15:18:21 +00:00
2014-01-07 11:45:08 +00:00
(defn slider [{:keys [value min max param]}]
2014-01-07 16:01:28 +00:00
[:input {:type "range" :value value :min min :max max
:style {:width "100%"}
:on-change #(set-bmi param (-> % .-target .-value))}])
2014-01-02 15:18:21 +00:00
(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
2014-01-03 14:52:18 +00:00
[:h3 "BMI calculator"]
2014-01-02 15:18:21 +00:00
[:div
"Height: " (int height) "cm"
2014-01-03 14:52:18 +00:00
[slider {:value height :min 100 :max 220 :param :height}]]
2014-01-02 15:18:21 +00:00
[:div
"Weight: " (int weight) "kg"
2014-01-03 14:52:18 +00:00
[slider {:value weight :min 30 :max 150 :param :weight}]]
2014-01-02 15:18:21 +00:00
[:div
"BMI: " (int bmi) " "
[:span {:style {:color color}} diagnose]
2014-01-07 11:45:08 +00:00
[slider {:value bmi :min 10 :max 50 :param :bmi}]]]))
2014-01-02 15:18:21 +00:00
2014-01-03 09:56:15 +00:00
(defn intro []
2014-01-17 10:12:11 +00:00
(let [github {:href "https://github.com/holmsand/reagent"}
2014-01-09 13:37:11 +00:00
clojurescript {:href "https://github.com/clojure/clojurescript"}
react {:href "http://facebook.github.io/react/"}
hiccup {:href "https://github.com/weavejester/hiccup"}]
[:div.demo-text
2014-01-17 10:12:11 +00:00
[:h2 "Introduction to Reagent"]
2014-01-09 13:37:11 +00:00
2014-01-17 10:12:11 +00:00
[:p [:a github "Reagent"] " provides a minimalistic interface
2014-01-09 13:37:11 +00:00
between " [:a clojurescript "ClojureScript"] " and " [:a
react "React"] ". It allows you to define efficient React
components using nothing but plain ClojureScript functions and
data, that describe your UI using a " [:a hiccup "Hiccup"] "-like
syntax."]
2014-01-17 10:12:11 +00:00
[:p "The goal of Reagent is to make it possible to define
2014-01-09 13:37:11 +00:00
arbitrarily complex UIs using just a couple of basic concepts,
and to be fast enough by default that you rarely have to care
about performance."]
2014-01-17 10:12:11 +00:00
[:p "A very basic Reagent component may look something like this: "]
2014-01-09 13:37:11 +00:00
[demo-component {:comp simple-component
:defs [:simple-component]}]
[:p "You can build new components using other components as
building blocks. Like this:"]
[demo-component {:comp simple-parent
:defs [:simple-parent]}]
[:p "Data is passed to child components using plain old Clojure
maps. For example, here is a component that shows items in a "
[:code "seq"] ":" ]
[demo-component {:comp lister-user
:defs [:lister :lister-user]}]
[:p [:strong "Note: "]
"The " [:code "{:key item}"] " part of the " [:code ":li"] "
isnt really necessary in this simple example, but passing a
unique key for every item in a dynamically generated list of
components is good practice, and helps React to improve
performance for large lists."]]))
2014-01-03 12:22:41 +00:00
(defn managing-state []
2014-01-07 11:45:08 +00:00
[:div.demo-text
2014-01-17 10:12:11 +00:00
[:h2 "Managing state in Reagent"]
2014-01-03 12:22:41 +00:00
2014-01-17 10:12:11 +00:00
[:p "The easiest way to manage state in Reagent is to use Reagents
2014-01-03 12:22:41 +00:00
own version of " [:code "atom"] ". It works exactly like the one in
clojure.core, except that it keeps track of every time it is
2014-01-09 13:37:11 +00:00
derefed. Any component that uses an " [:code "atom"]" is automagically
2014-01-03 14:52:18 +00:00
re-rendered when its value changes."]
2014-01-03 12:22:41 +00:00
2014-01-09 13:37:11 +00:00
[:p "Lets demonstrate that with a simple example:"]
2014-01-03 12:22:41 +00:00
[demo-component {:comp counting-component
:defs [:ns :click-count :counting-component]}]
[:p "Sometimes you may want to maintain state locally in a
2014-01-03 14:52:18 +00:00
component. That is easy to do with an " [:code "atom"] " as well."]
2014-01-03 12:22:41 +00:00
2014-01-07 16:01:28 +00:00
[:p "Here is an example of that, where we call "
2014-01-09 13:37:11 +00:00
[:code "setTimeout"] " every time the component is rendered to
2014-01-07 16:01:28 +00:00
update a counter:"]
2014-01-04 14:03:26 +00:00
[demo-component {:comp timer-component
:defs [:timer-component]}]
2014-01-17 10:12:11 +00:00
[:p "The previous example also uses another feature of Reagent: a component
2014-01-03 12:22:41 +00:00
function can return another function, that is used to do the actual
2014-01-09 13:37:11 +00:00
rendering. This allows you to perform some setup of newly
created components, without resorting to Reacts lifecycle
2014-01-04 09:56:21 +00:00
events."]
2014-01-04 14:03:26 +00:00
[:p "By simply passing atoms around you can share state management
between components, like this:"]
[demo-component {:comp shared-state
2014-01-09 13:37:11 +00:00
:defs [:ns :atom-input :shared-state]}]
[:p [:strong "Note: "] "Component functions (including the ones
returned by other component functions) are called with three
arguments: "]
[:ul
[:li [:code "props"] ": a map passed from a parent" ]
[:li [:code "children"] ": a vector of the children passed to the component"]
[:li [:code "this"] ": the actual React component"]]])
2014-01-03 09:56:15 +00:00
2014-01-03 14:52:18 +00:00
(defn essential-api []
2014-01-07 11:45:08 +00:00
[:div.demo-text
2014-01-03 14:52:18 +00:00
[:h2 "Essential API"]
2014-01-17 10:12:11 +00:00
[:p "Reagent supports most of Reacts API, but there is really only
one entry-point that is necessary for most applications: "
2014-01-17 10:12:11 +00:00
[:code "reagent.core/render-component"] "."]
2014-01-03 14:52:18 +00:00
2014-01-11 17:24:46 +00:00
[:p "It takes two arguments: a component, and a DOM node. For
2014-01-03 14:52:18 +00:00
example, splashing the very first example all over the page would
look like this:"]
[demo-component {:defs [:ns :simple-component :render-simple]}]])
2014-01-03 09:56:15 +00:00
2014-01-07 16:01:28 +00:00
(defn performance []
[:div.demo-text
[:h2 "Performance"]
2014-01-17 10:12:11 +00:00
[:p "React itself is very fast, and so is Reagent. In fact, Reagent
2014-01-09 13:37:11 +00:00
will be even faster than plain React a lot of the time, thanks to
optimizations made possible by ClojureScript."]
[:p "Mounted components are only re-rendered when their parameters
have changed. The change could come from a derefed "
[:code "atom"] ", the arguments passed to the component (i.e the
”props” map and children) or component state."]
[:p "All of these are checked for changes with a simple "
[:code "identical?"] " which is basically only a pointer
comparison, so the overhead is very low (even if the components of
the props map are compared separately, and "
[:code ":style"] " attributes are handled specially). Even the
built-in React components are handled the same way."]
[:p "All this means that you (hopefully) simply wont have to care
about performance most of the time. Just define your UI however you like
it will be fast enough."]
[:p "There are a couple of situations that you might have to care
2014-01-17 10:12:11 +00:00
about, though. If you give Reagent big " [:code "seq"] "s of
2014-01-09 13:37:11 +00:00
components to render, you might have to supply all of them with a
unique " [:code ":key"] " attribute to speed up rendering. Also note
that anonymous functions are not, in general, equal to each other
even if they represent the same code and closure."]
[:p "But again, in general you should just trust that React and
2014-01-17 10:12:11 +00:00
Reagent will be fast enough. This very page is composed of a single
Reagent component with thousands of child components (every single
2014-01-09 13:37:11 +00:00
parenthesis etc in the code examples is a separate component), and
yet the page can be updated many times every second without taxing
the browser the slightest."]
[:p "Incidentally, this page also uses another React trick: the
entire page is pre-rendered using Node, and "
2014-01-17 10:12:11 +00:00
[:code "reagent/render-component-to-string"] ". When it is loaded
2014-01-09 13:37:11 +00:00
into the browser, React automatically attaches event-handlers to
the already present DOM tree."]])
2014-01-07 16:01:28 +00:00
2014-01-02 15:18:21 +00:00
(defn bmi-demo []
2014-01-07 11:45:08 +00:00
[:div.demo-text
2014-01-03 14:52:18 +00:00
[:h2 "Putting it all together"]
[:p "Here is a slightly less contrived example: a simple BMI
calculator."]
2014-01-17 10:12:11 +00:00
[:p "Data is kept in a single " [:code "reagent.core/atom"] ": a map
2014-01-03 14:52:18 +00:00
with height, weight and BMI as keys."]
2014-01-02 15:18:21 +00:00
[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 complete-simple-demo []
2014-01-07 11:45:08 +00:00
[:div.demo-text
2014-01-07 16:01:28 +00:00
[:h2 "Complete demo"]
2014-01-17 10:12:11 +00:00
[:p "Reagent comes with a couple of complete examples, with
2014-01-09 13:37:11 +00:00
Leiningen project files and everything. Heres one of them in
2014-01-07 16:01:28 +00:00
action:"]
[demo-component {:comp simpleexample/simple-example
:src (get-source "simpleexample.cljs")}]])
(defn todomvc-demo []
2014-01-07 11:45:08 +00:00
[:div.demo-text
[:h2 "Todomvc"]
2014-01-07 16:01:28 +00:00
[:p "The obligatory todo list looks roughly like this in
2014-01-17 10:12:11 +00:00
Reagent (cheating a little bit by skipping routing and
2014-01-07 16:01:28 +00:00
persistence):"]
[demo-component {:comp todomvc/todo-app
:src (get-source "todomvc.cljs")}]])
2014-01-07 16:01:28 +00:00
(defn github-badge []
[:a.github-badge
2014-01-17 10:12:11 +00:00
{:href "https://github.com/holmsand/reagent"}
2014-01-07 16:01:28 +00:00
[:img {:style {:position "absolute" :top 0 :left 0 :border 0}
:src "https://s3.amazonaws.com/github/ribbons/forkme_left_orange_ff7600.png"
:alt "Fork me on GitHub"}]])
2014-01-02 13:04:30 +00:00
(defn demo []
[:div
2014-01-17 10:12:11 +00:00
[:div.reagent-demo
[:h1 "Reagent: Minimalistic React for ClojureScript"]
[intro]
[managing-state]
[essential-api]
[bmi-demo]
2014-01-09 13:37:11 +00:00
[performance]
[complete-simple-demo]
2014-01-09 13:37:11 +00:00
[todomvc-demo]]
[github-badge]])
2014-01-05 10:16:01 +00:00
(defn ^:export mountdemo []
2014-01-17 10:12:11 +00:00
(reagent/render-component [demo] (.-body js/document)))
2014-01-05 10:16:01 +00:00
(defn ^:export genpage []
2014-01-17 10:12:11 +00:00
(reagent/render-component-to-string [demo]))