mirror of
https://github.com/status-im/reagent.git
synced 2025-01-12 21:05:20 +00:00
Merge branch 'delayed'
Conflicts: src/reagent/impl/template.cljs
This commit is contained in:
commit
aca69347b7
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
|||||||
index.html
|
index.html
|
||||||
assets/
|
assets/
|
||||||
news/
|
/news/
|
||||||
target
|
target
|
||||||
pom.xml
|
pom.xml
|
||||||
.lein-repl-history
|
.lein-repl-history
|
||||||
|
6
Makefile
6
Makefile
@ -26,10 +26,10 @@ runtest:
|
|||||||
$(MAKE) run PROF=test,$(PROF)
|
$(MAKE) run PROF=test,$(PROF)
|
||||||
|
|
||||||
runsite: setup
|
runsite: setup
|
||||||
(sleep 3 && open "http://127.0.0.1:$(PORT)") &
|
(sleep 3 && open "http://127.0.0.1:$(PORT)/$$(basename $$PWD)") &
|
||||||
( trap "kill 0" SIGINT SIGTERM EXIT; \
|
( trap "kill 0" SIGINT SIGTERM EXIT; \
|
||||||
( python -m SimpleHTTPServer $(PORT) & ); \
|
( cd .. && python -m SimpleHTTPServer $(PORT) & ); \
|
||||||
lein -o with-profile $(PROF) cljsbuild auto $(CLJSBUILD) )
|
lein -o with-profile $(PROF),prod cljsbuild auto $(CLJSBUILD) )
|
||||||
|
|
||||||
install: leinbuild
|
install: leinbuild
|
||||||
lein install
|
lein install
|
||||||
|
@ -33,13 +33,14 @@
|
|||||||
[github-badge]])
|
[github-badge]])
|
||||||
|
|
||||||
(defn ^:export mountdemo [p]
|
(defn ^:export mountdemo [p]
|
||||||
(when p (reset! page p))
|
(when p (page/set-start-page p))
|
||||||
(reagent/render-component [demo] (.-body js/document)))
|
(reagent/render-component [demo] (.-body js/document)))
|
||||||
|
|
||||||
(defn gen-page [p timestamp]
|
(defn gen-page [p timestamp]
|
||||||
(reset! page p)
|
(reset! page p)
|
||||||
(let [body (reagent/render-component-to-string [demo])
|
(let [body (reagent/render-component-to-string [demo])
|
||||||
title @page/title-atom]
|
title @page/title-atom
|
||||||
|
load-page (case p "index.html" "" p)]
|
||||||
(str "<!doctype html>
|
(str "<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@ -53,7 +54,7 @@
|
|||||||
<script type='text/javascript'
|
<script type='text/javascript'
|
||||||
src='" (prefix "assets/demo.js") timestamp "'></script>
|
src='" (prefix "assets/demo.js") timestamp "'></script>
|
||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
setTimeout(function() {demo.mountdemo('" p "')}, 200);
|
setTimeout(function() {demo.mountdemo('" load-page "')}, 200);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>")))
|
</html>")))
|
||||||
|
@ -21,9 +21,7 @@
|
|||||||
")
|
")
|
||||||
|
|
||||||
(defn src-for-names [srcmap names]
|
(defn src-for-names [srcmap names]
|
||||||
(string/join "\n" (-> srcmap
|
(string/join "\n" (map srcmap names)))
|
||||||
(select-keys names)
|
|
||||||
vals)))
|
|
||||||
|
|
||||||
(defn fun-map [src]
|
(defn fun-map [src]
|
||||||
(-> src src-parts src-defs (assoc :ns nssrc)))
|
(-> src src-parts src-defs (assoc :ns nssrc)))
|
||||||
@ -36,10 +34,11 @@
|
|||||||
(fn []
|
(fn []
|
||||||
[:div
|
[:div
|
||||||
(when comp
|
(when comp
|
||||||
[:div.demo-example
|
[:div.demo-example.clearfix
|
||||||
[:a.demo-example-hide {:on-click (fn [e]
|
[:a.demo-example-hide {:on-click (fn [e]
|
||||||
(.preventDefault e)
|
(.preventDefault e)
|
||||||
(swap! showing not))}
|
(swap! showing not)
|
||||||
|
false)}
|
||||||
(if @showing "hide" "show")]
|
(if @showing "hide" "show")]
|
||||||
[:h3.demo-heading "Example "]
|
[:h3.demo-heading "Example "]
|
||||||
(when @showing
|
(when @showing
|
||||||
@ -47,6 +46,6 @@
|
|||||||
[:div.simple-demo [comp]]
|
[:div.simple-demo [comp]]
|
||||||
[comp]))])
|
[comp]))])
|
||||||
(when @showing
|
(when @showing
|
||||||
[:div.demo-source
|
[:div.demo-source.clearfix
|
||||||
[:h3.demo-heading "Source"]
|
[:h3.demo-heading "Source"]
|
||||||
src])])))
|
src])])))
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
[reagentdemo.syntax :refer-macros [get-source]]
|
[reagentdemo.syntax :refer-macros [get-source]]
|
||||||
[reagentdemo.page :refer [title link page-map]]
|
[reagentdemo.page :refer [title link page-map]]
|
||||||
[reagentdemo.common :as common :refer [demo-component]]
|
[reagentdemo.common :as common :refer [demo-component]]
|
||||||
|
[reagentdemo.news.async :as async]
|
||||||
[todomvc :as todomvc]))
|
[todomvc :as todomvc]))
|
||||||
|
|
||||||
(def funmap (-> "reagentdemo/news.cljs" get-source common/fun-map))
|
(def funmap (-> "reagentdemo/news.cljs" get-source common/fun-map))
|
||||||
@ -44,7 +45,7 @@
|
|||||||
(reset! undo-list nil)
|
(reset! undo-list nil)
|
||||||
(remove-watch state ::undo-watcher))}))
|
(remove-watch state ::undo-watcher))}))
|
||||||
|
|
||||||
(defn undo-example []
|
(defn undo-example [{:keys [summary]}]
|
||||||
(let [head "Cloact becomes Reagent: Undo is trivial"]
|
(let [head "Cloact becomes Reagent: Undo is trivial"]
|
||||||
[:div.reagent-demo
|
[:div.reagent-demo
|
||||||
[:h1 [link {:href undo-example} head]]
|
[:h1 [link {:href undo-example} head]]
|
||||||
@ -63,25 +64,32 @@
|
|||||||
[:p "The API is otherwise unchanged, so a simple
|
[:p "The API is otherwise unchanged, so a simple
|
||||||
search-and-replace should suffice."]
|
search-and-replace should suffice."]
|
||||||
|
|
||||||
[:h2 "Undo the easy way"]
|
(if summary
|
||||||
|
[link {:href undo-example
|
||||||
|
:class 'news-read-more} "Read more"]
|
||||||
|
[:div.demo-text
|
||||||
|
|
||||||
[:p "To celebrate the undoing of the apparently disgusting name,
|
[:h2 "Undo the easy way"]
|
||||||
here is an example of how easy it is to add undo functionality
|
|
||||||
to Reagent components."]
|
|
||||||
|
|
||||||
[:p "It simply saves the old state whenever it changes, and
|
[:p "To celebrate the undoing of the apparently disgusting
|
||||||
restores it when the button is clicked."]
|
name, here is an example of how easy it is to add undo
|
||||||
|
functionality to Reagent components."]
|
||||||
|
|
||||||
[:p "The really nice thing about ClojureScript is that not only
|
[:p "It simply saves the old state whenever it changes, and
|
||||||
is this easy and safe to do, courtesy of immutable data
|
restores it when the button is clicked."]
|
||||||
structures, it is also efficient. ClojureScript figures out how
|
|
||||||
to represent ”changes” to maps and vectors efficiently, so that
|
[:p "The really nice thing about ClojureScript is that not
|
||||||
you won’t have to."]
|
only is this easy and safe to do, courtesy of immutable data
|
||||||
|
structures, it is also efficient. ClojureScript figures out
|
||||||
[undo-demo-cleanup]]]))
|
how to represent ”changes” to maps and vectors efficiently,
|
||||||
|
so that you won’t have to."]
|
||||||
|
|
||||||
|
[undo-demo-cleanup]])]]))
|
||||||
|
|
||||||
(defn main []
|
(defn main []
|
||||||
[undo-example])
|
[:div
|
||||||
|
[async/main {:summary true}]
|
||||||
|
[undo-example {:summary true}]])
|
||||||
|
|
||||||
(swap! page-map assoc
|
(swap! page-map assoc
|
||||||
"news/cloact-reagent-undo-demo.html" undo-example)
|
"news/cloact-reagent-undo-demo.html" undo-example)
|
||||||
|
197
demo/reagentdemo/news/async.cljs
Normal file
197
demo/reagentdemo/news/async.cljs
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
(ns reagentdemo.news.async
|
||||||
|
(:require [reagent.core :as reagent :refer [atom]]
|
||||||
|
[reagent.debug :refer-macros [dbg println]]
|
||||||
|
[reagentdemo.syntax :refer-macros [get-source]]
|
||||||
|
[reagentdemo.page :refer [title link page-map]]
|
||||||
|
[reagentdemo.common :as common :refer [demo-component]]))
|
||||||
|
|
||||||
|
(def funmap (-> "reagentdemo/news/async.cljs" get-source common/fun-map))
|
||||||
|
(def src-for (partial common/src-for funmap))
|
||||||
|
|
||||||
|
(defn timing-wrapper [{f :component-fn}]
|
||||||
|
(let [start-time (atom nil)
|
||||||
|
render-time (atom nil)
|
||||||
|
now #(.now js/Date)
|
||||||
|
start #(reset! start-time (now))
|
||||||
|
stop #(reset! render-time (- (now) @start-time))
|
||||||
|
timed-f (with-meta f
|
||||||
|
{:component-will-mount start
|
||||||
|
:component-will-update start
|
||||||
|
:component-did-mount stop
|
||||||
|
:component-did-update stop})]
|
||||||
|
(fn [props children]
|
||||||
|
[:div
|
||||||
|
[:p [:em "render time: " @render-time "ms"]]
|
||||||
|
(into [timed-f props] children)])))
|
||||||
|
|
||||||
|
(def base-color (atom {:red 130 :green 160 :blue 120}))
|
||||||
|
(def ncolors (atom 20))
|
||||||
|
(def random-colors (atom nil))
|
||||||
|
|
||||||
|
(defn to-rgb [{:keys [red green blue]}]
|
||||||
|
(let [hex #(str (if (< % 16) "0")
|
||||||
|
(-> % js/Math.round (.toString 16)))]
|
||||||
|
(str "#" (hex red) (hex green) (hex blue))))
|
||||||
|
|
||||||
|
(defn tweak-color [{:keys [red green blue]}]
|
||||||
|
(let [rnd #(-> (js/Math.random) (* 256))
|
||||||
|
tweak #(-> % (+ (rnd)) (/ 2) js/Math.floor)]
|
||||||
|
{:red (tweak red) :green (tweak green) :blue (tweak blue)}))
|
||||||
|
|
||||||
|
(defn reset-random-colors []
|
||||||
|
(reset! random-colors
|
||||||
|
(repeatedly #(-> @base-color tweak-color to-rgb))))
|
||||||
|
|
||||||
|
(defn color-choose [{color-part :color-part}]
|
||||||
|
[:div.color-slider
|
||||||
|
(name color-part) " " (color-part @base-color)
|
||||||
|
[:input {:type "range" :min 0 :max 255
|
||||||
|
:value (color-part @base-color)
|
||||||
|
:on-change (fn [e]
|
||||||
|
(swap! base-color assoc
|
||||||
|
color-part (-> e .-target .-value int))
|
||||||
|
(reset-random-colors))}]])
|
||||||
|
|
||||||
|
(defn ncolors-choose []
|
||||||
|
[:div.color-slider
|
||||||
|
"number of color divs " @ncolors
|
||||||
|
[:input {:type "range" :min 0 :max 500
|
||||||
|
:value @ncolors
|
||||||
|
:on-change #(reset! ncolors (-> % .-target .-value))}]])
|
||||||
|
|
||||||
|
(defn color-plate [{color :color}]
|
||||||
|
[:div.color-plate
|
||||||
|
{:style {:background-color color}}])
|
||||||
|
|
||||||
|
(defn palette []
|
||||||
|
(let [color @base-color
|
||||||
|
n @ncolors]
|
||||||
|
[:div
|
||||||
|
[:div
|
||||||
|
[:p "base color: "]
|
||||||
|
[color-plate {:color (to-rgb color)}]]
|
||||||
|
[:div.color-samples
|
||||||
|
[:p n " random matching colors:"]
|
||||||
|
(map-indexed (fn [k v]
|
||||||
|
[color-plate {:key k :color v}])
|
||||||
|
(take n @random-colors))]]))
|
||||||
|
|
||||||
|
(defn color-demo []
|
||||||
|
(reset-random-colors)
|
||||||
|
(fn []
|
||||||
|
[:div
|
||||||
|
[:h2 "Matching colors"]
|
||||||
|
[color-choose {:color-part :red}]
|
||||||
|
[color-choose {:color-part :green}]
|
||||||
|
[color-choose {:color-part :blue}]
|
||||||
|
[ncolors-choose]
|
||||||
|
[timing-wrapper {:component-fn palette}]]))
|
||||||
|
|
||||||
|
(defn main [{:keys [summary]}]
|
||||||
|
(let [om-article {:href "http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/"}]
|
||||||
|
[:div.reagent-demo
|
||||||
|
[title "Reagent: Faster by waiting"]
|
||||||
|
[:h1 [link {:href main} "Faster by waiting"]]
|
||||||
|
[:div.demo-text
|
||||||
|
[:h2 "Reagent gets async rendering"]
|
||||||
|
|
||||||
|
[:p "Reagent already separates state from components. Now they
|
||||||
|
are also separated in time."]
|
||||||
|
|
||||||
|
[:p "From version 0.3.0, changes in application state (as
|
||||||
|
represented by Reagent’s " [:code "atom"] "s) are no longer
|
||||||
|
rendered immediately to the DOM. Instead, Reagent waits until
|
||||||
|
the browser is ready to repaint the window, and then all the
|
||||||
|
changes are rendered in one single go."]
|
||||||
|
|
||||||
|
(if summary
|
||||||
|
[link {:href main
|
||||||
|
:class 'news-read-more} "Read more"]
|
||||||
|
[:div.demo-text
|
||||||
|
|
||||||
|
[:p "This is good for all sorts of reasons:"]
|
||||||
|
[:ul
|
||||||
|
|
||||||
|
[:li "Reagent doesn't have to spend time doing renderings
|
||||||
|
that no one would ever see (because changes to application
|
||||||
|
state happened faster than the browser could repaint)."]
|
||||||
|
|
||||||
|
[:li "If two or more atoms are changed simultaneously, this
|
||||||
|
now leads to only one re-rendering, and not two."]
|
||||||
|
|
||||||
|
[:li "The new code does proper batching of renderings even
|
||||||
|
when changes to atoms are done outside of event
|
||||||
|
handlers (which is great for e.g core.async users)."]
|
||||||
|
|
||||||
|
[:li "Repaints can be synced by the browser with for example
|
||||||
|
CSS transitions, since Reagent uses requestAnimationFrame
|
||||||
|
to do the batching. That makes for example animations
|
||||||
|
smoother."]]
|
||||||
|
|
||||||
|
[:p "In short, Reagent renders less often, but at the right
|
||||||
|
times. For a much better description of why async rendering
|
||||||
|
is good, see David Nolen’s " [:a om-article "excellent
|
||||||
|
explanation here."]]
|
||||||
|
|
||||||
|
[:h2 "The bad news"]
|
||||||
|
|
||||||
|
[:p "Lunches in general tend to be non-free, and this is no
|
||||||
|
exception… The downside to async rendering is that you can no
|
||||||
|
longer depend on changes to atoms being immediately available
|
||||||
|
in the DOM. (Actually, you couldn’t before either, since
|
||||||
|
React.js itself does batching inside event handlers.)"]
|
||||||
|
|
||||||
|
[:p "This may make testing a bit more verbose: you now have
|
||||||
|
to call " [:code "reagent.core/flush"] " to force Reagent to
|
||||||
|
synchronize state with the DOM."]
|
||||||
|
|
||||||
|
[:h2 "An example"]
|
||||||
|
|
||||||
|
[:p "Here is an example to (hopefully) demonstrate the
|
||||||
|
virtues of async rendering. It consists of a simple color
|
||||||
|
chooser (three sliders to set the red, green and blue
|
||||||
|
components of a base color), and shows the base color + a
|
||||||
|
bunch of divs in random matching colors. As soon as the base
|
||||||
|
color is changed, a new set of random colors is shown."]
|
||||||
|
|
||||||
|
[:p "If you change one of the base color components, the base
|
||||||
|
color should change immediately, and smoothly (on my Macbook
|
||||||
|
Air, rendering takes around 2ms, with 20 colored divs
|
||||||
|
showing)."]
|
||||||
|
|
||||||
|
[:p "But perhaps more interesting is to see what happens when
|
||||||
|
the updates can’t be made smoothly (because the browser
|
||||||
|
simply cannot re-render the colored divs quickly enough). On
|
||||||
|
my machine, this starts to happen if I change the number of
|
||||||
|
divs shown to above 150 or so."]
|
||||||
|
|
||||||
|
[:p "As you increase the number of divs, you’ll notice that
|
||||||
|
the base color no longer changes quite so smoothly when you
|
||||||
|
move the color sliders."]
|
||||||
|
|
||||||
|
[:p "But the crucial point is that the sliders "
|
||||||
|
[:strong "still work"] ". Without async rendering, you could
|
||||||
|
quickly get into a situation where the browser hangs for a
|
||||||
|
while, doing updates corresponding to an old state. "]
|
||||||
|
|
||||||
|
[:p "With async rendering, the only thing that happens is
|
||||||
|
that the frame rate goes down."]
|
||||||
|
|
||||||
|
[:p "Btw, I find it quite impressive that React manages to
|
||||||
|
change 500 divs (12 full screens worth) in slightly more than
|
||||||
|
40ms. And even better: when I change the number of divs
|
||||||
|
shown, it only takes around 6ms to re-render the color
|
||||||
|
palette (because the individual divs don’t have to be
|
||||||
|
re-rendered, divs are just added or removed from the DOM as
|
||||||
|
needed)."]
|
||||||
|
|
||||||
|
[demo-component
|
||||||
|
{:comp color-demo
|
||||||
|
:src (src-for
|
||||||
|
[:ns :timing-wrapper :base-color :ncolors
|
||||||
|
:random-colors :to-rgb :tweak-color
|
||||||
|
:reset-random-colors :color-choose :ncolors-choose
|
||||||
|
:palette :color-demo])}]])]]))
|
||||||
|
|
||||||
|
(swap! page-map assoc
|
||||||
|
"news/reagent-is-async.html" main)
|
@ -8,27 +8,46 @@
|
|||||||
[goog.history EventType]))
|
[goog.history EventType]))
|
||||||
|
|
||||||
(def page (atom ""))
|
(def page (atom ""))
|
||||||
|
(def base-path (atom nil))
|
||||||
|
(def html5-history false)
|
||||||
|
|
||||||
(defn create-history []
|
(defn create-history []
|
||||||
(when reagent/is-client
|
(when reagent/is-client
|
||||||
(let [proto (-> js/window .-location .-protocol)]
|
(let [proto (-> js/window .-location .-protocol)]
|
||||||
(if (and (.isSupported Html5History)
|
(if (and (.isSupported Html5History)
|
||||||
(case proto "http:" true "https:" true false))
|
(case proto "http:" true "https:" true false))
|
||||||
(doto (Html5History.)
|
(do (set! html5-history true)
|
||||||
(.setUseFragment false))
|
(doto (Html5History.)
|
||||||
|
(.setUseFragment false)))
|
||||||
(History.)))))
|
(History.)))))
|
||||||
|
|
||||||
(defn setup-history []
|
(defn setup-history []
|
||||||
(when-let [h (create-history)]
|
(when-let [h (create-history)]
|
||||||
(events/listen h EventType/NAVIGATE
|
(events/listen h EventType/NAVIGATE
|
||||||
(fn [e] (reset! page (.-token e))))
|
(fn [e]
|
||||||
|
(reset! page (subs (.-token e)
|
||||||
|
(count @base-path)))
|
||||||
|
(reagent/flush)))
|
||||||
(add-watch page ::history (fn [_ _ oldp newp]
|
(add-watch page ::history (fn [_ _ oldp newp]
|
||||||
(.setToken h newp)))
|
(.setToken h (str @base-path newp))))
|
||||||
(.setEnabled h true)
|
(.setEnabled h true)
|
||||||
h))
|
h))
|
||||||
|
|
||||||
(def history (setup-history))
|
(def history (setup-history))
|
||||||
|
|
||||||
|
(defn set-start-page [p]
|
||||||
|
(when html5-history
|
||||||
|
;; Find base-path for html5 history
|
||||||
|
(let [loc (-> js/window .-location .-pathname)
|
||||||
|
split #".[^/]*"
|
||||||
|
loc-parts (re-seq split loc)
|
||||||
|
page-parts (re-seq split (case p "" "." p))
|
||||||
|
base (str (apply str
|
||||||
|
(drop-last (count page-parts) loc-parts))
|
||||||
|
"/")]
|
||||||
|
(reset! base-path (string/replace base #"^/" ""))))
|
||||||
|
(reset! page p))
|
||||||
|
|
||||||
(def title-atom (atom ""))
|
(def title-atom (atom ""))
|
||||||
|
|
||||||
(def page-map (atom nil))
|
(def page-map (atom nil))
|
||||||
@ -55,14 +74,22 @@
|
|||||||
:on-click (if history
|
:on-click (if history
|
||||||
(fn [e]
|
(fn [e]
|
||||||
(.preventDefault e)
|
(.preventDefault e)
|
||||||
(reset! page href))
|
(reset! page href)
|
||||||
|
(set! (.-scrollTop (.-body js/document))
|
||||||
|
0))
|
||||||
identity))
|
identity))
|
||||||
children)))
|
children)))
|
||||||
|
|
||||||
|
(add-watch page ::title-watch
|
||||||
|
(fn [_ _ _ p]
|
||||||
|
;; First title on a page wins
|
||||||
|
(reset! title-atom "")))
|
||||||
|
|
||||||
(defn title [props children]
|
(defn title [props children]
|
||||||
(let [name (first children)]
|
(let [name (first children)]
|
||||||
(if reagent/is-client
|
(when (= @title-atom "")
|
||||||
(let [title (aget (.getElementsByTagName js/document "title") 0)]
|
(if reagent/is-client
|
||||||
(set! (.-innerHTML title) name)))
|
(let [title (aget (.getElementsByTagName js/document "title") 0)]
|
||||||
(reset! title-atom name)
|
(set! (.-innerHTML title) name)))
|
||||||
|
(reset! title-atom name))
|
||||||
[:div]))
|
[:div]))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
(defproject reagent "0.2.1"
|
(defproject reagent "0.3.0-SNAPSHOT"
|
||||||
:url "http://github.com/holmsand/reagent"
|
:url "http://github.com/holmsand/reagent"
|
||||||
:license {:name "MIT"}
|
:license {:name "MIT"}
|
||||||
:description "A simple ClojureScript interface to React"
|
:description "A simple ClojureScript interface to React"
|
||||||
|
@ -5,6 +5,16 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.clearfix:before, .clearfix:after {
|
||||||
|
content: " ";
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix:after {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
div.nav {
|
div.nav {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -65,6 +75,8 @@ ul.nav > li.brand > a {
|
|||||||
font-family: 'HelveticaNeue-Light', 'Helvetica Neue', arial;
|
font-family: 'HelveticaNeue-Light', 'Helvetica Neue', arial;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
line-height: 1.25em;
|
line-height: 1.25em;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reagent-demo > h1 > a {
|
.reagent-demo > h1 > a {
|
||||||
@ -112,6 +124,10 @@ ul.nav > li.brand > a {
|
|||||||
color: #444;
|
color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.demo-text > ul > li {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.demo-example {
|
.demo-example {
|
||||||
background-color: #ebebeb;
|
background-color: #ebebeb;
|
||||||
}
|
}
|
||||||
@ -139,4 +155,26 @@ ul.nav > li.brand > a {
|
|||||||
.demo-example-hide {
|
.demo-example-hide {
|
||||||
float: right;
|
float: right;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-read-more {
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Color demo */
|
||||||
|
|
||||||
|
.color-plate {
|
||||||
|
float: left;
|
||||||
|
height: 100px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-slider > input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-samples {
|
||||||
|
clear: both;
|
||||||
|
padding-top: 0.5em;
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
(ns reagent.core
|
(ns reagent.core
|
||||||
(:refer-clojure :exclude [partial atom])
|
(:refer-clojure :exclude [partial atom flush])
|
||||||
(:require-macros [reagent.debug :refer [dbg prn]])
|
(:require-macros [reagent.debug :refer [dbg prn]])
|
||||||
(:require [reagent.impl.template :as tmpl]
|
(:require [reagent.impl.template :as tmpl]
|
||||||
[reagent.impl.component :as comp]
|
[reagent.impl.component :as comp]
|
||||||
@ -13,8 +13,7 @@
|
|||||||
|
|
||||||
(defn render-component
|
(defn render-component
|
||||||
"Render a Reagent component into the DOM. The first argument may be either a
|
"Render a Reagent component into the DOM. The first argument may be either a
|
||||||
vector (using Reagent's Hiccup syntax), or a React component. The second argument
|
vector (using Reagent's Hiccup syntax), or a React component. The second argument should be a DOM node.
|
||||||
should be a DOM node.
|
|
||||||
|
|
||||||
Optionally takes a callback that is called when the component is in place.
|
Optionally takes a callback that is called when the component is in place.
|
||||||
|
|
||||||
@ -44,6 +43,9 @@ looking like this:
|
|||||||
{:get-initial-state (fn [this])
|
{:get-initial-state (fn [this])
|
||||||
:component-will-receive-props (fn [this new-props])
|
:component-will-receive-props (fn [this new-props])
|
||||||
:should-component-update (fn [this old-props new-props old-children new-children])
|
:should-component-update (fn [this old-props new-props old-children new-children])
|
||||||
|
:component-will-mount (fn [this])
|
||||||
|
:component-did-mount (fn [this])
|
||||||
|
:component-will-update (fn [this new-props new-children])
|
||||||
:component-did-update (fn [this old-props old-children])
|
:component-did-update (fn [this old-props old-children])
|
||||||
:component-will-unmount (fn [this])
|
:component-will-unmount (fn [this])
|
||||||
:render (fn [props children this])}
|
:render (fn [props children this])}
|
||||||
@ -105,6 +107,15 @@ specially, like React's transferPropsTo."
|
|||||||
[defaults props]
|
[defaults props]
|
||||||
(util/merge-props defaults props))
|
(util/merge-props defaults props))
|
||||||
|
|
||||||
|
(defn flush
|
||||||
|
"Render dirty components immediately to the DOM.
|
||||||
|
|
||||||
|
Note that this may not work in event handlers, since React.js does
|
||||||
|
batching of updates there."
|
||||||
|
[]
|
||||||
|
(comp/flush))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;; Ratom
|
;; Ratom
|
||||||
|
|
||||||
@ -118,6 +129,11 @@ re-rendered."
|
|||||||
|
|
||||||
;; Utilities
|
;; Utilities
|
||||||
|
|
||||||
|
(defn next-tick
|
||||||
|
"Run f using requestAnimationFrame or equivalent."
|
||||||
|
[f]
|
||||||
|
(comp/next-tick f))
|
||||||
|
|
||||||
(defn partial
|
(defn partial
|
||||||
"Works just like clojure.core/partial, except that it is an IFn, and
|
"Works just like clojure.core/partial, except that it is an IFn, and
|
||||||
the result can be compared with ="
|
the result can be compared with ="
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
(ns reagent.impl.component
|
(ns reagent.impl.component
|
||||||
|
(:refer-clojure :exclude [flush])
|
||||||
(:require [reagent.impl.template :as tmpl
|
(:require [reagent.impl.template :as tmpl
|
||||||
:refer [cljs-props cljs-children React]]
|
:refer [cljs-props cljs-children cljs-level React]]
|
||||||
[reagent.impl.util :as util]
|
[reagent.impl.util :as util]
|
||||||
[reagent.ratom :as ratom]
|
[reagent.ratom :as ratom]
|
||||||
[reagent.debug :refer-macros [dbg prn]]))
|
[reagent.debug :refer-macros [dbg prn]]))
|
||||||
@ -35,7 +36,7 @@
|
|||||||
(-> C js-props props-in-props))
|
(-> C js-props props-in-props))
|
||||||
|
|
||||||
(defn get-children [C]
|
(defn get-children [C]
|
||||||
(->> C js-props (aget cljs-children)))
|
(-> C js-props (aget cljs-children)))
|
||||||
|
|
||||||
(defn replace-props [C newprops]
|
(defn replace-props [C newprops]
|
||||||
(.setProps C (js-obj cljs-props newprops)))
|
(.setProps C (js-obj cljs-props newprops)))
|
||||||
@ -43,17 +44,67 @@
|
|||||||
(defn set-props [C newprops]
|
(defn set-props [C newprops]
|
||||||
(replace-props C (merge (get-props C) newprops)))
|
(replace-props C (merge (get-props C) newprops)))
|
||||||
|
|
||||||
|
;;; Rendering
|
||||||
|
|
||||||
;;; Function wrapping
|
(defn fake-raf [f]
|
||||||
|
(js/setTimeout f 16))
|
||||||
|
|
||||||
|
(def next-tick
|
||||||
|
(if-not tmpl/isClient
|
||||||
|
fake-raf
|
||||||
|
(let [w js/window]
|
||||||
|
(or (.-requestAnimationFrame w)
|
||||||
|
(.-webkitRequestAnimationFrame w)
|
||||||
|
(.-mozRequestAnimationFrame w)
|
||||||
|
(.-msRequestAnimationFrame w)
|
||||||
|
fake-raf))))
|
||||||
|
|
||||||
|
(defn compare-levels [c1 c2]
|
||||||
|
(- (-> c1 js-props (aget cljs-level))
|
||||||
|
(-> c2 js-props (aget cljs-level))))
|
||||||
|
|
||||||
|
(defn run-queue [a]
|
||||||
|
;; sort components by level, to make sure parents
|
||||||
|
;; are rendered before children
|
||||||
|
(.sort a compare-levels)
|
||||||
|
(dotimes [i (alength a)]
|
||||||
|
(let [C (aget a i)]
|
||||||
|
(when (.-cljsIsDirty C)
|
||||||
|
(.forceUpdate C)))))
|
||||||
|
|
||||||
|
(deftype RenderQueue [^:mutable queue ^:mutable scheduled?]
|
||||||
|
Object
|
||||||
|
(queue-render [this C]
|
||||||
|
(.push queue C)
|
||||||
|
(.schedule this))
|
||||||
|
(schedule [this]
|
||||||
|
(when-not scheduled?
|
||||||
|
(set! scheduled? true)
|
||||||
|
(next-tick #(.run-queue this))))
|
||||||
|
(run-queue [_]
|
||||||
|
(let [q queue]
|
||||||
|
(set! queue (array))
|
||||||
|
(set! scheduled? false)
|
||||||
|
(run-queue q))))
|
||||||
|
|
||||||
|
(def render-queue (RenderQueue. (array) false))
|
||||||
|
|
||||||
|
(defn flush []
|
||||||
|
(.run-queue render-queue))
|
||||||
|
|
||||||
|
(defn queue-render [C]
|
||||||
|
(set! (.-cljsIsDirty C) true)
|
||||||
|
(.queue-render render-queue C))
|
||||||
|
|
||||||
(defn do-render [C f]
|
(defn do-render [C f]
|
||||||
|
(set! (.-cljsIsDirty C) false)
|
||||||
(let [p (js-props C)
|
(let [p (js-props C)
|
||||||
props (props-in-props p)
|
props (props-in-props p)
|
||||||
children (aget p cljs-children)
|
children (aget p cljs-children)
|
||||||
;; Call render function with props, children, component
|
;; Call render function with props, children, component
|
||||||
res (f props children C)
|
res (f props children C)
|
||||||
conv (if (vector? res)
|
conv (if (vector? res)
|
||||||
(tmpl/as-component res)
|
(tmpl/as-component res (aget p cljs-level))
|
||||||
(if (fn? res)
|
(if (fn? res)
|
||||||
(do-render C (set! (.-cljsRenderFn C) res))
|
(do-render C (set! (.-cljsRenderFn C) res))
|
||||||
res))]
|
res))]
|
||||||
@ -66,10 +117,13 @@
|
|||||||
(ratom/make-reaction
|
(ratom/make-reaction
|
||||||
#(do-render C (.-cljsRenderFn C))
|
#(do-render C (.-cljsRenderFn C))
|
||||||
:auto-run (if tmpl/isClient
|
:auto-run (if tmpl/isClient
|
||||||
#(.forceUpdate C)
|
#(queue-render C)
|
||||||
identity))))
|
identity))))
|
||||||
(ratom/run (.-cljsRatom C)))
|
(ratom/run (.-cljsRatom C)))
|
||||||
|
|
||||||
|
|
||||||
|
;;; Function wrapping
|
||||||
|
|
||||||
(defn custom-wrapper [key f]
|
(defn custom-wrapper [key f]
|
||||||
(case key
|
(case key
|
||||||
:getDefaultProps
|
:getDefaultProps
|
||||||
@ -98,16 +152,22 @@
|
|||||||
;; call f with oldprops newprops oldchildren newchildren
|
;; call f with oldprops newprops oldchildren newchildren
|
||||||
(f C p1 p2 c1 c2))))
|
(f C p1 p2 c1 c2))))
|
||||||
|
|
||||||
|
:componentWillUpdate
|
||||||
|
(fn [C nextprops]
|
||||||
|
(let [p (aget nextprops cljs-props)
|
||||||
|
c (aget nextprops cljs-children)]
|
||||||
|
(f C p c)))
|
||||||
|
|
||||||
:componentDidUpdate
|
:componentDidUpdate
|
||||||
(fn [C oldprops]
|
(fn [C oldprops]
|
||||||
(let [inprops (js-props C)
|
(let [p (aget oldprops cljs-props)
|
||||||
p (aget inprops cljs-props)
|
c (aget oldprops cljs-children)]
|
||||||
c (aget inprops cljs-children)]
|
|
||||||
(f C p c)))
|
(f C p c)))
|
||||||
|
|
||||||
:componentWillUnmount
|
:componentWillUnmount
|
||||||
(fn [C]
|
(fn [C]
|
||||||
(ratom/dispose! (.-cljsRatom C))
|
(ratom/dispose! (.-cljsRatom C))
|
||||||
|
(set! (.-cljsIsDirty C) false)
|
||||||
(when f (f C)))
|
(when f (f C)))
|
||||||
|
|
||||||
:render
|
:render
|
||||||
|
@ -3,12 +3,13 @@
|
|||||||
(:require [clojure.string :as string]
|
(:require [clojure.string :as string]
|
||||||
[reagent.impl.reactimport :as reactimport]
|
[reagent.impl.reactimport :as reactimport]
|
||||||
[reagent.impl.util :as util]
|
[reagent.impl.util :as util]
|
||||||
[reagent.debug :refer-macros [dbg prn println]]))
|
[reagent.debug :refer-macros [dbg prn println log]]))
|
||||||
|
|
||||||
(def React reactimport/React)
|
(def React reactimport/React)
|
||||||
|
|
||||||
(def cljs-props "cljsProps")
|
(def cljs-props "cljsProps")
|
||||||
(def cljs-children "cljsChildren")
|
(def cljs-children "cljsChildren")
|
||||||
|
(def cljs-level "cljsLevel")
|
||||||
|
|
||||||
(def isClient (not (nil? (try (.-document js/window)
|
(def isClient (not (nil? (try (.-document js/window)
|
||||||
(catch js/Object e nil)))))
|
(catch js/Object e nil)))))
|
||||||
@ -35,13 +36,22 @@
|
|||||||
(def cached-prop-name (memoize undash-prop-name))
|
(def cached-prop-name (memoize undash-prop-name))
|
||||||
(def cached-style-name (memoize dash-to-camel))
|
(def cached-style-name (memoize dash-to-camel))
|
||||||
|
|
||||||
|
(defn to-js-val [v]
|
||||||
|
(if-not (ifn? v)
|
||||||
|
v
|
||||||
|
(cond (keyword? v) (name v)
|
||||||
|
(symbol? v) (str v)
|
||||||
|
(coll? v) (clj->js v)
|
||||||
|
:else (fn [& args] (apply v args)))))
|
||||||
|
|
||||||
(defn convert-prop-value [val]
|
(defn convert-prop-value [val]
|
||||||
(cond (map? val) (let [obj (js-obj)]
|
(if (map? val)
|
||||||
(doseq [[k v] val]
|
(reduce-kv (fn [res k v]
|
||||||
(aset obj (cached-style-name k) (clj->js v)))
|
(doto res
|
||||||
obj)
|
(aset (cached-prop-name k)
|
||||||
(ifn? val) (fn [& args] (apply val args))
|
(to-js-val v))))
|
||||||
:else (clj->js val)))
|
(js-obj) val)
|
||||||
|
(to-js-val val)))
|
||||||
|
|
||||||
(defn set-id-class [props [id class]]
|
(defn set-id-class [props [id class]]
|
||||||
(aset props "id" (or (aget props "id") id))
|
(aset props "id" (or (aget props "id") id))
|
||||||
@ -57,28 +67,67 @@
|
|||||||
(identical? (type props) js/Object) props
|
(identical? (type props) js/Object) props
|
||||||
:else (let [objprops (js-obj)]
|
:else (let [objprops (js-obj)]
|
||||||
(when-not is-empty
|
(when-not is-empty
|
||||||
(doseq [[k v] props]
|
(reduce-kv (fn [o k v]
|
||||||
(aset objprops (cached-prop-name k)
|
(doto o (aset (cached-prop-name k)
|
||||||
(convert-prop-value v))))
|
(convert-prop-value v))))
|
||||||
|
objprops props))
|
||||||
(when-not (nil? id-class)
|
(when-not (nil? id-class)
|
||||||
(set-id-class objprops id-class))
|
(set-id-class objprops id-class))
|
||||||
objprops))))
|
objprops))))
|
||||||
|
|
||||||
(defn map-into-array [f coll]
|
(defn map-into-array [f arg coll]
|
||||||
(let [a (into-array coll)]
|
(reduce (fn [a x]
|
||||||
(dotimes [i (alength a)]
|
(doto a
|
||||||
(aset a i (f (aget a i))))
|
(.push (f x arg))))
|
||||||
a))
|
#js [] coll))
|
||||||
|
|
||||||
(declare as-component)
|
(declare as-component)
|
||||||
|
|
||||||
|
(def DOM (aget React "DOM"))
|
||||||
|
|
||||||
|
(def input-components #{(aget DOM "input")
|
||||||
|
(aget DOM "textarea")})
|
||||||
|
|
||||||
|
(defn get-props [this]
|
||||||
|
(-> this (aget "props") (aget cljs-props)))
|
||||||
|
|
||||||
|
(defn input-initial-state [this]
|
||||||
|
(let [props (get-props this)]
|
||||||
|
#js {:value (:value props)
|
||||||
|
:checked (:checked props)}))
|
||||||
|
|
||||||
|
(defn input-handle-change [this e]
|
||||||
|
(let [props (get-props this)
|
||||||
|
on-change (or (props :on-change) (props "onChange"))]
|
||||||
|
(when-not (nil? on-change)
|
||||||
|
(let [target (.-target e)]
|
||||||
|
(.setState this #js {:value (.-value target)
|
||||||
|
:checked (.-checked target)}))
|
||||||
|
(on-change e))))
|
||||||
|
|
||||||
|
(defn input-will-receive-props [this new-props]
|
||||||
|
(let [props (aget new-props cljs-props)]
|
||||||
|
(.setState this #js {:value (:value props)
|
||||||
|
:checked (:checked props)})))
|
||||||
|
|
||||||
|
(defn input-render-setup [this jsprops]
|
||||||
|
(let [state (aget this "state")]
|
||||||
|
(doto jsprops
|
||||||
|
(aset "value" (.-value state))
|
||||||
|
(aset "checked" (.-checked state))
|
||||||
|
(aset "onChange" (aget this "handleChange")))))
|
||||||
|
|
||||||
(defn wrapped-render [this comp id-class]
|
(defn wrapped-render [this comp id-class]
|
||||||
(let [inprops (aget this "props")
|
(let [inprops (aget this "props")
|
||||||
props (aget inprops cljs-props)
|
props (aget inprops cljs-props)
|
||||||
|
level (aget inprops cljs-level)
|
||||||
hasprops (or (nil? props) (map? props))
|
hasprops (or (nil? props) (map? props))
|
||||||
jsargs (->> (aget inprops cljs-children)
|
jsargs (->> (aget inprops cljs-children)
|
||||||
(map-into-array as-component))]
|
(map-into-array as-component (inc level)))
|
||||||
(.unshift jsargs (convert-props props id-class))
|
jsprops (convert-props props id-class)]
|
||||||
|
(when (input-components comp)
|
||||||
|
(input-render-setup this jsprops))
|
||||||
|
(.unshift jsargs jsprops)
|
||||||
(.apply comp nil jsargs)))
|
(.apply comp nil jsargs)))
|
||||||
|
|
||||||
(defn wrapped-should-update [C nextprops nextstate]
|
(defn wrapped-should-update [C nextprops nextstate]
|
||||||
@ -90,20 +139,25 @@
|
|||||||
(not (util/equal-args p1 c1 p2 c2))))
|
(not (util/equal-args p1 c1 p2 c2))))
|
||||||
|
|
||||||
(defn wrap-component [comp extras name]
|
(defn wrap-component [comp extras name]
|
||||||
(.createClass React (js-obj "render"
|
(let [def #js {:render
|
||||||
#(this-as C (wrapped-render C comp extras))
|
#(this-as C (wrapped-render C comp extras))
|
||||||
"shouldComponentUpdate"
|
:shouldComponentUpdate
|
||||||
#(this-as C (wrapped-should-update C %1 %2))
|
#(this-as C (wrapped-should-update C %1 %2))
|
||||||
"displayName"
|
:displayName (or name "ComponentWrapper")}]
|
||||||
(or name "ComponentWrapper"))))
|
(when (input-components comp)
|
||||||
|
(doto def
|
||||||
|
(aset "shouldComponentUpdate" nil)
|
||||||
|
(aset "getInitialState" #(this-as C (input-initial-state C)))
|
||||||
|
(aset "handleChange" #(this-as C (input-handle-change C %)))
|
||||||
|
(aset "componentWillReceiveProps"
|
||||||
|
#(this-as C (input-will-receive-props C %)))))
|
||||||
|
(.createClass React def)))
|
||||||
|
|
||||||
;; From Weavejester's Hiccup, via pump:
|
;; From Weavejester's Hiccup, via pump:
|
||||||
(def ^{:doc "Regular expression that parses a CSS-style id and class
|
(def ^{:doc "Regular expression that parses a CSS-style id and class
|
||||||
from a tag name."}
|
from a tag name."}
|
||||||
re-tag #"([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?")
|
re-tag #"([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?")
|
||||||
|
|
||||||
(def DOM (aget React "DOM"))
|
|
||||||
|
|
||||||
(defn parse-tag [hiccup-tag]
|
(defn parse-tag [hiccup-tag]
|
||||||
(let [[tag id class] (->> hiccup-tag name (re-matches re-tag) next)
|
(let [[tag id class] (->> hiccup-tag name (re-matches re-tag) next)
|
||||||
comp (aget DOM tag)
|
comp (aget DOM tag)
|
||||||
@ -139,7 +193,7 @@
|
|||||||
(set! (.-cljsReactClass tag) (wrap-component tag nil nil))
|
(set! (.-cljsReactClass tag) (wrap-component tag nil nil))
|
||||||
(fn-to-class tag)))))))
|
(fn-to-class tag)))))))
|
||||||
|
|
||||||
(defn vec-to-comp [v]
|
(defn vec-to-comp [v level]
|
||||||
(assert (pos? (count v)))
|
(assert (pos? (count v)))
|
||||||
(let [[tag props] v
|
(let [[tag props] v
|
||||||
hasmap (map? props)
|
hasmap (map? props)
|
||||||
@ -147,14 +201,17 @@
|
|||||||
c (as-class tag)
|
c (as-class tag)
|
||||||
jsprops (js-obj cljs-props (if hasmap props)
|
jsprops (js-obj cljs-props (if hasmap props)
|
||||||
cljs-children (if (> (count v) first-child)
|
cljs-children (if (> (count v) first-child)
|
||||||
(subvec v first-child)))]
|
(subvec v first-child))
|
||||||
|
cljs-level level)]
|
||||||
(when hasmap
|
(when hasmap
|
||||||
(let [key (:key props)]
|
(let [key (:key props)]
|
||||||
(when-not (nil? key)
|
(when-not (nil? key)
|
||||||
(aset jsprops "key" key))))
|
(aset jsprops "key" key))))
|
||||||
(c jsprops)))
|
(c jsprops)))
|
||||||
|
|
||||||
(defn as-component [x]
|
(defn as-component
|
||||||
(cond (vector? x) (vec-to-comp x)
|
([x] (as-component x 0))
|
||||||
(seq? x) (map-into-array as-component x)
|
([x level]
|
||||||
true x))
|
(cond (vector? x) (vec-to-comp x level)
|
||||||
|
(seq? x) (map-into-array as-component level x)
|
||||||
|
true x)))
|
||||||
|
@ -52,15 +52,19 @@
|
|||||||
(defn shallow-equal-maps [x y]
|
(defn shallow-equal-maps [x y]
|
||||||
;; Compare two maps, using keyword-identical? on all values
|
;; Compare two maps, using keyword-identical? on all values
|
||||||
(or (identical? x y)
|
(or (identical? x y)
|
||||||
(and (== (count x) (count y))
|
(and (map? x)
|
||||||
|
(map? y)
|
||||||
|
(== (count x) (count y))
|
||||||
(reduce-kv (fn [res k v]
|
(reduce-kv (fn [res k v]
|
||||||
(let [yv (get y k -not-found)]
|
(let [yv (get y k -not-found)]
|
||||||
(if (or (keyword-identical? v yv)
|
(if (or (keyword-identical? v yv)
|
||||||
;; hack to allow reagent.core/partial and :style
|
;; Allow :style maps, symbols
|
||||||
;; maps to be compared with =
|
;; and reagent/partial
|
||||||
(and (or
|
;; to be compared properly
|
||||||
(keyword-identical? k :style)
|
(and (keyword-identical? k :style)
|
||||||
(identical? (type v) partial-ifn))
|
(shallow-equal-maps v yv))
|
||||||
|
(and (or (identical? (type v) partial-ifn)
|
||||||
|
(symbol? v))
|
||||||
(= v yv)))
|
(= v yv)))
|
||||||
res
|
res
|
||||||
(reduced false))))
|
(reduced false))))
|
||||||
|
@ -9,11 +9,12 @@
|
|||||||
(defn running [] @-running)
|
(defn running [] @-running)
|
||||||
|
|
||||||
(defn- capture-derefed [f]
|
(defn- capture-derefed [f]
|
||||||
|
;; TODO: Get rid of allocation.
|
||||||
(binding [*ratom-context* (clojure.core/atom #{})]
|
(binding [*ratom-context* (clojure.core/atom #{})]
|
||||||
[(f) @*ratom-context*]))
|
[(f) @*ratom-context*]))
|
||||||
|
|
||||||
(defn- notify-deref-watcher! [derefable]
|
(defn- notify-deref-watcher! [derefable]
|
||||||
(when-not (or (nil? *ratom-context*))
|
(when-not (nil? *ratom-context*)
|
||||||
(swap! *ratom-context* conj derefable)))
|
(swap! *ratom-context* conj derefable)))
|
||||||
|
|
||||||
(deftype RAtom [state meta validator watches]
|
(deftype RAtom [state meta validator watches]
|
||||||
@ -36,8 +37,10 @@
|
|||||||
|
|
||||||
IWatchable
|
IWatchable
|
||||||
(-notify-watches [this oldval newval]
|
(-notify-watches [this oldval newval]
|
||||||
(doseq [[key f] watches]
|
(reduce-kv (fn [_ key f]
|
||||||
(f key this oldval newval)))
|
(f key this oldval newval)
|
||||||
|
nil)
|
||||||
|
nil watches))
|
||||||
(-add-watch [this key f]
|
(-add-watch [this key f]
|
||||||
(set! (.-watches this) (assoc watches key f)))
|
(set! (.-watches this) (assoc watches key f)))
|
||||||
(-remove-watch [this key]
|
(-remove-watch [this key]
|
||||||
@ -63,8 +66,10 @@
|
|||||||
(-handle-change [k sender oldval newval]))
|
(-handle-change [k sender oldval newval]))
|
||||||
|
|
||||||
(defn- call-watches [obs watches oldval newval]
|
(defn- call-watches [obs watches oldval newval]
|
||||||
(doseq [[k wf] watches]
|
(reduce-kv (fn [_ key f]
|
||||||
(wf k obs oldval newval)))
|
(f key obs oldval newval)
|
||||||
|
nil)
|
||||||
|
nil watches))
|
||||||
|
|
||||||
(deftype Reaction [f ^:mutable state ^:mutable dirty? ^:mutable active?
|
(deftype Reaction [f ^:mutable state ^:mutable dirty? ^:mutable active?
|
||||||
^:mutable watching ^:mutable watches
|
^:mutable watching ^:mutable watches
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
(def isClient (not (nil? (try (.-document js/window)
|
(def isClient (not (nil? (try (.-document js/window)
|
||||||
(catch js/Object e nil)))))
|
(catch js/Object e nil)))))
|
||||||
|
|
||||||
|
(def rflush reagent/flush)
|
||||||
|
|
||||||
(defn add-test-div [name]
|
(defn add-test-div [name]
|
||||||
(let [doc js/document
|
(let [doc js/document
|
||||||
body (.-body js/document)
|
body (.-body js/document)
|
||||||
@ -23,7 +25,8 @@
|
|||||||
(when isClient
|
(when isClient
|
||||||
(let [div (add-test-div "_testreagent")]
|
(let [div (add-test-div "_testreagent")]
|
||||||
(let [comp (reagent/render-component comp div #(f comp div))]
|
(let [comp (reagent/render-component comp div #(f comp div))]
|
||||||
(reagent/unmount-component-at-node div)))))
|
(reagent/unmount-component-at-node div)
|
||||||
|
(reagent/flush)))))
|
||||||
|
|
||||||
(defn found-in [re div]
|
(defn found-in [re div]
|
||||||
(let [res (.-innerHTML div)]
|
(let [res (.-innerHTML div)]
|
||||||
@ -95,27 +98,68 @@
|
|||||||
(let [ran (atom 0)
|
(let [ran (atom 0)
|
||||||
runs (running)
|
runs (running)
|
||||||
val (atom 0)
|
val (atom 0)
|
||||||
|
secval (atom 0)
|
||||||
v1 (reaction @val)
|
v1 (reaction @val)
|
||||||
comp (fn []
|
comp (fn []
|
||||||
(swap! ran inc)
|
(swap! ran inc)
|
||||||
[:div (str "val " @v1)])]
|
[:div (str "val " @v1 @val @secval)])]
|
||||||
(with-mounted-component [comp]
|
(with-mounted-component [comp]
|
||||||
(fn [C div]
|
(fn [C div]
|
||||||
(swap! ran inc)
|
(reagent/flush)
|
||||||
(is (not= runs (running)))
|
(is (not= runs (running)))
|
||||||
(is (found-in #"val 0" div))
|
(is (found-in #"val 0" div))
|
||||||
(is (= 2 @ran))
|
(is (= 1 @ran))
|
||||||
|
|
||||||
|
(reset! secval 1)
|
||||||
|
(reset! secval 0)
|
||||||
(reset! val 1)
|
(reset! val 1)
|
||||||
|
(reset! val 2)
|
||||||
|
(reset! val 1)
|
||||||
|
(reagent/flush)
|
||||||
(is (found-in #"val 1" div))
|
(is (found-in #"val 1" div))
|
||||||
(is (= 3 @ran))
|
(is (= 2 @ran))
|
||||||
|
|
||||||
;; should not be rendered
|
;; should not be rendered
|
||||||
(reset! val 1)
|
(reset! val 1)
|
||||||
|
(reagent/flush)
|
||||||
(is (found-in #"val 1" div))
|
(is (found-in #"val 1" div))
|
||||||
(is (= 3 @ran))))
|
(is (= 2 @ran))))
|
||||||
(is (= runs (running)))
|
(is (= runs (running)))
|
||||||
(is (= 3 @ran)))))
|
(is (= 2 @ran)))))
|
||||||
|
|
||||||
|
(deftest batched-update-test []
|
||||||
|
(when isClient
|
||||||
|
(let [ran (atom 0)
|
||||||
|
v1 (atom 0)
|
||||||
|
v2 (atom 0)
|
||||||
|
c2 (fn [{val :val}]
|
||||||
|
(swap! ran inc)
|
||||||
|
(assert (= @v1 val))
|
||||||
|
[:div @v2])
|
||||||
|
c1 (fn []
|
||||||
|
(swap! ran inc)
|
||||||
|
[:div @v1
|
||||||
|
[c2 {:val @v1}]])]
|
||||||
|
(with-mounted-component [c1]
|
||||||
|
(fn [c div]
|
||||||
|
(rflush)
|
||||||
|
(is (= @ran 2))
|
||||||
|
(swap! v2 inc)
|
||||||
|
(is (= @ran 2))
|
||||||
|
(rflush)
|
||||||
|
(is (= @ran 3))
|
||||||
|
(swap! v1 inc)
|
||||||
|
(rflush)
|
||||||
|
(is (= @ran 5))
|
||||||
|
(swap! v2 inc)
|
||||||
|
(swap! v1 inc)
|
||||||
|
(rflush)
|
||||||
|
(is (= @ran 7))
|
||||||
|
(swap! v1 inc)
|
||||||
|
(swap! v1 inc)
|
||||||
|
(swap! v2 inc)
|
||||||
|
(rflush)
|
||||||
|
(is (= @ran 9)))))))
|
||||||
|
|
||||||
(deftest init-state-test
|
(deftest init-state-test
|
||||||
(when isClient
|
(when isClient
|
||||||
@ -132,6 +176,69 @@
|
|||||||
(is (found-in #"this is foobar" div))))
|
(is (found-in #"this is foobar" div))))
|
||||||
(is (= 2 @ran)))))
|
(is (= 2 @ran)))))
|
||||||
|
|
||||||
|
(deftest shoud-update-test
|
||||||
|
(when isClient
|
||||||
|
(let [parent-ran (atom 0)
|
||||||
|
child-ran (atom 0)
|
||||||
|
child-props (atom nil)
|
||||||
|
f (fn [])
|
||||||
|
f1 (fn [])
|
||||||
|
child (fn [p]
|
||||||
|
(swap! child-ran inc)
|
||||||
|
[:div (:val p)])
|
||||||
|
parent(fn []
|
||||||
|
(swap! parent-ran inc)
|
||||||
|
[:div "child-foo" [child @child-props]])]
|
||||||
|
(with-mounted-component [parent nil nil]
|
||||||
|
(fn [c div]
|
||||||
|
(rflush)
|
||||||
|
(is (= @child-ran 1))
|
||||||
|
(is (found-in #"child-foo" div))
|
||||||
|
(do (reset! child-props {:style {:display :none}})
|
||||||
|
(rflush))
|
||||||
|
(is (= @child-ran 2))
|
||||||
|
(do (reset! child-props {:style {:display :none}})
|
||||||
|
(rflush))
|
||||||
|
(is (= @child-ran 2) "keyw is equal")
|
||||||
|
(do (reset! child-props {:class :foo}) (rflush))
|
||||||
|
(is (= @child-ran 3))
|
||||||
|
(do (reset! child-props {:class :foo}) (rflush))
|
||||||
|
(is (= @child-ran 3))
|
||||||
|
(do (reset! child-props {:class 'foo}) (rflush))
|
||||||
|
(is (= @child-ran 4) "symbols are different from keyw")
|
||||||
|
(do (reset! child-props {:class 'foo}) (rflush))
|
||||||
|
(is (= @child-ran 4) "symbols are equal")
|
||||||
|
(do (reset! child-props {:style {:color 'red}}) (rflush))
|
||||||
|
(is (= @child-ran 5))
|
||||||
|
(do (reset! child-props {:on-change (reagent/partial f)})
|
||||||
|
(rflush))
|
||||||
|
(is (= @child-ran 6))
|
||||||
|
(do (reset! child-props {:on-change (reagent/partial f)})
|
||||||
|
(rflush))
|
||||||
|
(is (= @child-ran 6))
|
||||||
|
(do (reset! child-props {:on-change (reagent/partial f1)})
|
||||||
|
(rflush))
|
||||||
|
(is (= @child-ran 7)))))))
|
||||||
|
|
||||||
|
(deftest dirty-test
|
||||||
|
(when isClient
|
||||||
|
(let [ran (atom 0)
|
||||||
|
state (atom 0)
|
||||||
|
really-simple (fn [props children this]
|
||||||
|
(swap! ran inc)
|
||||||
|
(if (= @state 1)
|
||||||
|
(reset! state 3))
|
||||||
|
[:div (str "state=" @state)])]
|
||||||
|
(with-mounted-component [really-simple nil nil]
|
||||||
|
(fn [c div]
|
||||||
|
(is (= 1 @ran))
|
||||||
|
(is (found-in #"state=0" div))
|
||||||
|
(reset! state 1)
|
||||||
|
(rflush)
|
||||||
|
(is (= 2 @ran))
|
||||||
|
(is (found-in #"state=3" div))))
|
||||||
|
(is (= 2 @ran)))))
|
||||||
|
|
||||||
(defn as-string [comp]
|
(defn as-string [comp]
|
||||||
(reagent/render-component-to-string comp))
|
(reagent/render-component-to-string comp))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user