145 lines
3.5 KiB
Clojure
Executable File
145 lines
3.5 KiB
Clojure
Executable File
(ns reagent.impl.util
|
|
(:require [reagent.debug :refer-macros [dbg log warn]]
|
|
[reagent.interop :refer-macros [.' .!]]
|
|
[clojure.string :as string]))
|
|
|
|
(def is-client (and (exists? js/window)
|
|
(-> js/window (.' :document) nil? not)))
|
|
|
|
;;; Props accessors
|
|
|
|
(defn extract-props [v]
|
|
(let [p (nth v 1 nil)]
|
|
(if (map? p) p)))
|
|
|
|
(defn extract-children [v]
|
|
(let [p (nth v 1 nil)
|
|
first-child (if (or (nil? p) (map? p)) 2 1)]
|
|
(if (> (count v) first-child)
|
|
(subvec v first-child))))
|
|
|
|
(defn get-argv [c]
|
|
(.' c :props.argv))
|
|
|
|
(defn get-props [c]
|
|
(-> (.' c :props.argv) extract-props))
|
|
|
|
(defn get-children [c]
|
|
(-> (.' c :props.argv) extract-children))
|
|
|
|
(defn reagent-component? [c]
|
|
(-> (.' c :props.argv) nil? not))
|
|
|
|
(defn cached-react-class [c]
|
|
(.' c :cljsReactClass))
|
|
|
|
(defn cache-react-class [c constructor]
|
|
(.! c :cljsReactClass constructor))
|
|
|
|
;; Misc utilities
|
|
|
|
(defn memoize-1 [f]
|
|
(let [mem (atom {})]
|
|
(fn [arg]
|
|
(let [v (get @mem arg)]
|
|
(if-not (nil? v)
|
|
v
|
|
(let [ret (f arg)]
|
|
(swap! mem assoc arg ret)
|
|
ret))))))
|
|
|
|
(def dont-camel-case #{"aria" "data"})
|
|
|
|
(defn capitalize [s]
|
|
(if (< (count s) 2)
|
|
(string/upper-case s)
|
|
(str (string/upper-case (subs s 0 1)) (subs s 1))))
|
|
|
|
(defn dash-to-camel [dashed]
|
|
(if (string? dashed)
|
|
dashed
|
|
(let [name-str (name dashed)
|
|
[start & parts] (string/split name-str #"-")]
|
|
(if (dont-camel-case start)
|
|
name-str
|
|
(apply str start (map capitalize parts))))))
|
|
|
|
|
|
(deftype partial-ifn [f args ^:mutable p]
|
|
IFn
|
|
(-invoke [_ & a]
|
|
(or p (set! p (apply clojure.core/partial f args)))
|
|
(apply p a))
|
|
IEquiv
|
|
(-equiv [_ other]
|
|
(and (= f (.-f other)) (= args (.-args other))))
|
|
IHash
|
|
(-hash [_] (hash [f args])))
|
|
|
|
(defn- merge-class [p1 p2]
|
|
(let [class (when-let [c1 (:class p1)]
|
|
(when-let [c2 (:class p2)]
|
|
(str c1 " " c2)))]
|
|
(if (nil? class)
|
|
p2
|
|
(assoc p2 :class class))))
|
|
|
|
(defn- merge-style [p1 p2]
|
|
(let [style (when-let [s1 (:style p1)]
|
|
(when-let [s2 (:style p2)]
|
|
(merge s1 s2)))]
|
|
(if (nil? style)
|
|
p2
|
|
(assoc p2 :style style))))
|
|
|
|
(defn merge-props [p1 p2]
|
|
(if (nil? p1)
|
|
p2
|
|
(do
|
|
(assert (map? p1))
|
|
(merge-style p1 (merge-class p1 (merge p1 p2))))))
|
|
|
|
|
|
(def ^:dynamic *always-update* false)
|
|
|
|
(defonce roots (atom {}))
|
|
|
|
(defn clear-container [node]
|
|
;; If render throws, React may get confused, and throw on
|
|
;; unmount as well, so try to force React to start over.
|
|
(some-> node
|
|
(.! :innerHTML "")))
|
|
|
|
(defn render-component [comp container callback]
|
|
(let [rendered (volatile! nil)]
|
|
(try
|
|
(binding [*always-update* true]
|
|
(->> (.' js/React render (comp) container
|
|
(fn []
|
|
(binding [*always-update* false]
|
|
(swap! roots assoc container [comp container])
|
|
(if (some? callback)
|
|
(callback)))))
|
|
(vreset! rendered)))
|
|
(finally
|
|
(when-not @rendered
|
|
(clear-container container))))))
|
|
|
|
(defn re-render-component [comp container]
|
|
(render-component comp container nil))
|
|
|
|
(defn unmount-component-at-node [container]
|
|
(swap! roots dissoc container)
|
|
(.' js/React unmountComponentAtNode container))
|
|
|
|
(defn force-update-all []
|
|
(doseq [v (vals @roots)]
|
|
(apply re-render-component v))
|
|
"Updated")
|
|
|
|
(defn force-update [comp deep]
|
|
(if deep
|
|
(binding [*always-update* true]
|
|
(.' comp forceUpdate))
|
|
(.' comp forceUpdate)))
|