diff --git a/CHANGELOG.md b/CHANGELOG.md index 520b779..c7c5f17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,8 @@ to normalize and combine `:class` values (similar to `classnames` JS library) - Fix comparing Reagent `PartialFn` to `nil` ([#385](https://github.com/reagent-project/reagent/issues/385)) - Reagent no longer abuses `aget` or `aset` for accessing objects, and instead uses correct Object interop forms, allowing use of ClojureScript `:checked-arrays :warn` option. ([#325](https://github.com/reagent-project/reagent/issues/325)) -- **Removed `reagent.interop` namespace** - - These macros where bad practice and don't work properly if - React code is optimized by Closure. Proper object interop forms or `goog.object` functions - should be used instead. +- Deprecated `reagent.interop` namespace + - It is better to use proper object interop forms or `goog.object` functions instead. ## 0.8.1 (2018-05-15) diff --git a/src/reagent/interop.clj b/src/reagent/interop.clj new file mode 100644 index 0000000..defdb08 --- /dev/null +++ b/src/reagent/interop.clj @@ -0,0 +1,91 @@ +(ns reagent.interop + (:require [clojure.string :as string :refer [join]])) + +; taken from cljs.core +; https://github.com/binaryage/cljs-oops/issues/14 +(defmacro unchecked-aget + ([array idx] + (list 'js* "(~{}[~{}])" array idx)) + ([array idx & idxs] + (let [astr (apply str (repeat (count idxs) "[~{}]"))] + `(~'js* ~(str "(~{}[~{}]" astr ")") ~array ~idx ~@idxs)))) + +; taken from cljs.core +; https://github.com/binaryage/cljs-oops/issues/14 +(defmacro unchecked-aset + ([array idx val] + (list 'js* "(~{}[~{}] = ~{})" array idx val)) + ([array idx idx2 & idxv] + (let [n (dec (count idxv)) + astr (apply str (repeat n "[~{}]"))] + `(~'js* ~(str "(~{}[~{}][~{}]" astr " = ~{})") ~array ~idx ~idx2 ~@idxv)))) + +(defn- js-call [f args] + (let [argstr (->> (repeat (count args) "~{}") + (join ","))] + (list* 'js* (str "~{}(" argstr ")") f args))) + +(defn- dot-args [object member] + (assert (or (symbol? member) + (keyword? member)) + (str "Symbol or keyword expected, not " (pr-str member))) + (assert (or (not (symbol? object)) + (not (re-find #"\." (name object)))) + (str "Dot not allowed in " object)) + (let [n (name member) + field? (or (keyword? member) + (= (subs n 0 1) "-")) + names (-> (if (symbol? member) + (string/replace n #"^-" "") + n) + (string/split #"\."))] + [field? names])) + +(defonce $-warning + (delay + (try + (cljs.util/debug-prn "WARNING: reagent.interop/$ has been deprecated. Consider using ClojureScript JS-interop forms or goog.object namespace instead.") + (catch Exception _ nil)))) + +(defmacro $ + "Access member in a javascript object, in a Closure-safe way. + 'member' is assumed to be a field if it is a keyword or if + the name starts with '-', otherwise the named function is + called with the optional args. + 'member' may contain '.', to allow access in nested objects. + If 'object' is a symbol it is not allowed contain '.'. + + ($ o :foo) is equivalent to (.-foo o), except that it gives + the same result under advanced compilation. + ($ o foo arg1 arg2) is the same as (.foo o arg1 arg2)." + {:deprecated true} + [object member & args] + @$-warning + (let [[field names] (dot-args object member)] + (if field + (do + (assert (empty? args) + (str "Passing args to field doesn't make sense: " member)) + `(unchecked-aget ~object ~@names)) + (js-call (list* 'reagent.interop/unchecked-aget object names) args)))) + +(defonce $!-warning + (delay + (try + (cljs.util/debug-prn "WARNING: reagent.interop/$! has been deprecated. Consider using ClojureScript JS-interop forms or goog.object namespace instead.") + (catch Exception _ nil)))) + +(defmacro $! + "Set field in a javascript object, in a Closure-safe way. + 'field' should be a keyword or a symbol starting with '-'. + 'field' may contain '.', to allow access in nested objects. + If 'object' is a symbol it is not allowed contain '.'. + + ($! o :foo 1) is equivalent to (set! (.-foo o) 1), except that it + gives the same result under advanced compilation." + {:deprecated true} + [object field value] + @$!-warning + (let [[field names] (dot-args object field)] + (assert field (str "Field name must start with - in " field)) + `(unchecked-aset ~object ~@names ~value))) diff --git a/src/reagent/interop.cljs b/src/reagent/interop.cljs new file mode 100644 index 0000000..078819c --- /dev/null +++ b/src/reagent/interop.cljs @@ -0,0 +1,2 @@ +(ns reagent.interop + (:require-macros [reagent.interop])) diff --git a/test/reagenttest/runtests.cljs b/test/reagenttest/runtests.cljs index 35faf68..fdd4223 100644 --- a/test/reagenttest/runtests.cljs +++ b/test/reagenttest/runtests.cljs @@ -6,6 +6,7 @@ [reagenttest.testtrack] [reagenttest.testwithlet] [reagenttest.testwrap] + [reagenttest.testinterop] [reagent.impl.template-test] [reagent.impl.util-test] [cljs.test :as test] diff --git a/test/reagenttest/testinterop.cljs b/test/reagenttest/testinterop.cljs new file mode 100644 index 0000000..b5cb92c --- /dev/null +++ b/test/reagenttest/testinterop.cljs @@ -0,0 +1,56 @@ +(ns reagenttest.testinterop + (:require [cljs.test :as t :refer-macros [is deftest]] + [reagent.debug :refer-macros [dbg]] + [reagent.interop :refer-macros [$ $!]])) + +;; (def is-adv (let [o #js{}] +;; (set! (.-somethinglong o) true) +;; (not= (aget (.keys js/Object o) 0) "somethinglong"))) + +(deftest iterop-quote + (let [o #js{:foo "foo" + :foobar #js{:bar "bar" + :bar-foo "bar-foo"} + :bar-foo "barfoo"}] + + (is (= "foo" ($ o :foo))) + (is (= "bar" ($ o :foobar.bar))) + (is (= "barfoo" ($ o :bar-foo))) + + (is (= "foo" ($ o -foo))) + (is (= "bar" ($ o -foobar.bar))) + (is (= "bar-foo" ($ o -foobar.bar-foo))) + (is (= "bar-foo" ($ o :foobar.bar-foo))) + + ($! o :foo "foo1") + (is (= "foo1" ($ o :foo))) + + ($! o -foo "foo2") + (is (= "foo2" ($ o -foo))) + + ($! o :foobar.bar "bar1") + (is (= "bar1" ($ o :foobar.bar))) + + ($! o -foobar.bar "bar1") + (is (= "bar1" ($ o -foobar.bar))))) + +(deftest interop-quote-call + (let [o #js{:bar "bar1" + :foo (fn [x] + (this-as this + (str x ($ this :bar))))} + o2 #js{:o o}] + (is (= "ybar1" ($ o foo "y"))) + (is (= "xxbar1" ($ o2 o.foo "xx"))) + (is (= "abar1" (-> o2 + ($ :o) + ($ foo "a")))) + + (is (= "bar1" ($ o foo))) + (is (= "bar1" ($ o2 o.foo))) + + ($! o :bar "bar2") + (is (= "bar2" ($ o foo))) + + (is (= "1bar2" ($ ($ o :foo) + call o 1)))))