diff --git a/project.clj b/project.clj index c4d1653..2cd49a8 100644 --- a/project.clj +++ b/project.clj @@ -7,7 +7,9 @@ [org.clojure/clojurescript "0.0-2371"]] :profiles {:dev - {:plugins [[com.cemerick/austin "0.1.5"]]}} + {:plugins [[com.cemerick/austin "0.1.5"]] + :dependencies + [[com.cemerick/clojurescript.test "0.3.1"]]}} :source-paths ["src/clojure"] :javac-options ["-Xlint:unchecked"] :java-source-paths ["src/java"]) diff --git a/src/clojure/freactive/core.cljs b/src/clojure/freactive/core.cljs index 2efd6e3..f372c72 100644 --- a/src/clojure/freactive/core.cljs +++ b/src/clojure/freactive/core.cljs @@ -1,9 +1,24 @@ (ns freactive.core (:refer-clojure :exclude [atom])) -(def ^:dynamic *register-dep* (constantly nil)) +(def ^:dynamic *register-dep* nil) -(deftype ReactiveAtom [state meta validator watches] +(defn register-dep [ref] + (when *register-dep* (*register-dep* ref))) + +(defprotocol IInvalidates + (-notify-invalidation-watches [this]) + (-add-invalidation-watch [this key f]) + (-remove-invalidation-watch [this key])) + + +(defn add-invalidation-watch [this key f] + (-add-invalidation-watch this key f)) + +(defn remove-invalidation-watch [this key] + (-remove-invalidation-watch this key)) + +(deftype ReactiveAtom [state meta validator watches invalidation-watches] Object (equiv [this other] (-equiv this other)) @@ -14,8 +29,8 @@ (-equiv [o other] (identical? o other)) IDeref - (-deref [_] - (*register-dep* thi) + (-deref [this] + (register-dep this) state) IMeta @@ -23,7 +38,7 @@ IPrintWithWriter (-pr-writer [a writer opts] - (-write writer "#")) @@ -38,9 +53,70 @@ (set! (.-watches this) (dissoc watches key))) IHash - (-hash [this] (goog/getUid this))) + (-hash [this] (goog/getUid this)) -(deftype Reactive [state dirty f deps meta] + IInvalidates + (-notify-invalidation-watches [this] + (doseq [[key f] invalidation-watches] + (f key this))) + (-add-invalidation-watch [this key f] + (set! (.-invalidation-watches this) (assoc invalidation-watches key f)) + this) + (-remove-invalidation-watch [this key] + (set! (.-invalidation-watches this) (dissoc invalidation-watches key))) + + IReset + (-reset! [a new-value] + (let [validate (.-validator a)] + (when-not (nil? validate) + (assert (validate new-value) "Validator rejected reference state")) + (let [old-value (.-state a)] + (set! (.-state a) new-value) + (when-not (nil? (.-watches a)) + (-notify-watches a old-value new-value)) + (when-not (nil? (.-invalidation-watches a)) + (-notify-invalidation-watches a)) + new-value))) + + ISwap + (-swap! [a f] + (-reset! a (f (.-state a)))) + (-swap! [a f x] + (-reset! a (f (.-state a) x))) + (-swap! [a f x y] + (-reset! a (f (.-state a) x y))) + (-swap! [a f x y more] + (-reset! a (apply f (.-state a) x y more)))) + +(defn atom + "Creates and returns a ReactiveAtom with an initial value of x and zero or + more options (in any order): + :meta metadata-map + :validator validate-fn + If metadata-map is supplied, it will be come the metadata on the + atom. validate-fn must be nil or a side-effect-free fn of one + argument, which will be passed the intended new state on any state + change. If the new state is unacceptable, the validate-fn should + return false or throw an Error. If either of these error conditions + occur, then the value of the atom will not change." + ([x] (ReactiveAtom. x nil nil nil nil)) + ([x & {:keys [meta validator]}] (ReactiveAtom. x meta validator nil nil))) + +(defn- make-sully-fn [reactive] + (fn sully + ([] + (when-not (.-dirty reactive) + (set! (.-dirty reactive) true) + (-notify-invalidation-watches reactive))) + ([key ref] + (-remove-invalidation-watch ref key) + (sully)) + ([key ref _ _] + (-remove-watch ref key) + (sully)))) + +(deftype ReactiveComputation [^:mutable state ^:mutable dirty f ^:mutable deps meta + ^:mutable watches ^:mutable invalidation-watches sully] Object (equiv [this other] (-equiv this other)) @@ -49,18 +125,30 @@ (-equiv [o other] (identical? o other)) IDeref - (-deref [_] - (*register-dep* thi) + (-deref [this] + (register-dep this) (if-not dirty state - (binding [*register-deps* nil]))) + (binding [*register-dep* + (fn [ref] + (cond + (satisfies? IInvalidates ref) + (-add-invalidation-watch ref this sully) + (satisfies? IWatchable ref) + (-add-watch ref this sully)))] + (set! dirty false) + (let [old-val state + new-val (f)] + (set! state new-val) + (-notify-watches this old-val new-val) + new-val)))) IMeta (-meta [_] meta) IPrintWithWriter (-pr-writer [a writer opts] - (-write writer "#")) @@ -74,5 +162,20 @@ (-remove-watch [this key] (set! (.-watches this) (dissoc watches key))) + IInvalidates + (-notify-invalidation-watches [this] + (doseq [[key f] invalidation-watches] + (f key this))) + (-add-invalidation-watch [this key f] + (set! (.-invalidation-watches this) (assoc invalidation-watches key f)) + this) + (-remove-invalidation-watch [this key] + (set! (.-invalidation-watches this) (dissoc invalidation-watches key))) + IHash (-hash [this] (goog/getUid this))) + +(defn rx* [f] + (let [reactive (ReactiveComputation. nil true f nil nil nil nil nil)] + (set! (.-sully reactive) (make-sully-fn reactive)) + reactive)) diff --git a/src/clojure/freactive/experimental/dom.cljs b/src/clojure/freactive/experimental/dom.cljs index 026e588..1680dae 100644 --- a/src/clojure/freactive/experimental/dom.cljs +++ b/src/clojure/freactive/experimental/dom.cljs @@ -1,32 +1,34 @@ -(ns freactive.experimental.dom) +(ns freactive.experimental.dom + (:require + [freactive.core :as r])) (defn request-animation-frame [f] (.requestAnimationFrame js/window f)) (defn- set-style-prop! [elem prop-name prop-value] (let [prop-name (name prop-name)] - (aset (.-style prop) prop-name (str prop-value)))) + (aset (.-style elem) prop-name (str prop-value)))) (defn- set-attr! [elem attr-name attr-value] (let [attr-name (name attr-name)] (.setAttribute elem attr-name attr-value))) (defn- on-value-ref-invalidated* [set-fn] - (fn on-value-ref-invalidated [ref [element attr-name :as key]] - (r/remove-invalidation-watch attr-value-ref key) + (fn on-value-ref-invalidated [[element attr-name :as key] ref] + (r/remove-invalidation-watch ref key) (request-animation-frame (fn [_] (when (.-parentNode element) - (r/add-invalidation-watch attr-value-ref key on-value-ref-invalidated) - (set-fn elem attr-name attr-value)))))) + (r/add-invalidation-watch ref key on-value-ref-invalidated) + (set-fn element attr-name @ref)))))) -(defn- bind-syle-prop! [element attr-name attr-value] +(defn- bind-style-prop! [element attr-name attr-value] (if (satisfies? cljs.core/IDeref attr-value) - ((on-attr-value-ref-invalidated set-style-prop!) attr-value [element attr-name]) + ((on-value-ref-invalidated* set-style-prop!) attr-value [element attr-name]) (set-style-prop! element attr-name attr-value))) (defn- listen! [element evt-name handler] - ) + (.addEventListener element evt-name handler)) (defn- bind-attr! [element attr-name attr-value] (let [attr-name (name attr-name)] @@ -42,14 +44,11 @@ :default (if (satisfies? cljs.core/IDeref attr-value) - ((on-attr-value-ref-invalidated set-attr!) attr-value [element attr-name]) + ((on-value-ref-invalidated* set-attr!) attr-value [element attr-name]) (set-attr! element attr-name attr-value))))) -(defn- parse-elem-kw [kw] - [kw]) - -(defn- create-elem [tag] - (.createElement js/document (name tag))) +(defn- create-elem [kw] + (.createElement js/document (name kw))) (declare build) @@ -64,7 +63,7 @@ (defn- append-child!* [parent child] (let [child (convert-child child)] (.appendChild parent child) - chuld)) + child)) (defn- replace-child!* [parent new-child old-child] (let [new-child (convert-child new-child)] @@ -73,12 +72,12 @@ (defn on-child-ref-invalidated* [parent] (fn on-child-ref-invalidated - [attr-value-ref cur-elem] - (r/remove-invalidation-watch attr-value-ref wrapper) + [cur-elem child-ref] + (r/remove-invalidation-watch child-ref cur-elem) (request-animation-frame (fn [_] - (r/add-invalidation-watch attr-value-ref cur-elem on-child-ref-invalidated) - (let [new-elem @child + (r/add-invalidation-watch child-ref cur-elem on-child-ref-invalidated) + (let [new-elem @child-ref cur @cur-elem] (reset! cur-elem (if cur @@ -88,7 +87,7 @@ (defn- append-child! [parent child] (if (satisfies? cljs.core/IDeref child) - ((on-child-ref-invalidated* parent) child (atom nil)) + ((on-child-ref-invalidated* parent) (atom nil) child) (append-child!* parent child))) @@ -101,13 +100,10 @@ (append-child! elem ch)))) (defn build [elem-def] - (let [[tag id class-name] (parse-elem-kw (first elem-def)) - elem (create-elem tag) + (let [elem (create-elem (first elem-def)) attrs? (second elem-def) attrs (when (map? attrs?) attrs?) children (if attrs (nnext elem-def) (next elem-def))] - (when id (set-attr! tag "id" id)) - (when id (set-attr! tag "class" class-name)) (doseq [[k v] attrs] (bind-attr! elem k v)) (when children diff --git a/src/java/freactive/ReactiveAtom.java b/src/java/freactive/ReactiveAtom.java index f58ed4a..b2da731 100644 --- a/src/java/freactive/ReactiveAtom.java +++ b/src/java/freactive/ReactiveAtom.java @@ -137,7 +137,7 @@ public void notifyWatches(Object oldVal, Object newVal){ } @Override -public synchronized IInvalidates addInvalidationWatch(Object key, IFn callback) { +public synchronized IInvalidates addInvalidationWatch(final Object key, final IFn callback) { addWatch(key, new AFn() { @Override public Object invoke(Object key, Object ref, Object oldV, Object newV) { diff --git a/test/freactive/core_test.cljs b/test/freactive/core_test.cljs new file mode 100644 index 0000000..207c67b --- /dev/null +++ b/test/freactive/core_test.cljs @@ -0,0 +1,12 @@ +(ns freactive.core-test + (:refer-clojure :exclude [atom]) + (:require + [freactive.core :refer [atom rx*]] + [cemerick.cljs.test :refer-macros [deftest is]])) + +(deftest rx-test1 + (let [r0 (atom 0) + r1 (rx* (fn [] (inc @r0)))] + (is (= 1 @r1)) + (swap! r0 inc) + (is (= 2 @r1))))