From 6389a3889a2771069a2fd93c5f208407ceb29af9 Mon Sep 17 00:00:00 2001 From: Dan Holmsand Date: Tue, 25 Mar 2014 07:58:22 +0100 Subject: [PATCH] Add better javascript interop macros --- project.clj | 5 +++-- src/reagent/interop.clj | 45 ++++++++++++++++++++++++++++++++++++- test/testinterop.cljs | 49 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/project.clj b/project.clj index 6c41151..38a46e3 100644 --- a/project.clj +++ b/project.clj @@ -6,8 +6,9 @@ :dependencies [[org.clojure/clojure "1.5.1"] [org.clojure/clojurescript "0.0-2173"]] :plugins [[lein-cljsbuild "1.0.2"] - [com.cemerick/clojurescript.test "0.2.2"]] - :profiles {:prod {:cljsbuild + [com.cemerick/clojurescript.test "0.3.0"]] + :profiles {:dev {:source-paths ["src" "demo"]} + :prod {:cljsbuild {:builds {:client {:compiler {:optimizations :advanced diff --git a/src/reagent/interop.clj b/src/reagent/interop.clj index 8af1793..fddb61e 100644 --- a/src/reagent/interop.clj +++ b/src/reagent/interop.clj @@ -1,11 +1,54 @@ (ns reagent.interop - (:require [clojure.string :refer [join]])) + (:require [clojure.string :as string :refer [join]])) (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 " 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])) + +(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 '.'." + [object member & args] + (let [[field names] (dot-args object member)] + (if field + (do + (assert (empty? args) + (str "Passing args to field doesn't make sense: " member)) + `(aget ~object ~@names)) + (js-call (list* 'aget object names) args)))) + +(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 '.'." + [object field value] + (let [[field names] (dot-args object field)] + (assert field (str "Field name must start with - in " field)) + `(aset ~object ~@names ~value))) + + (defn- kwd [k] (if (keyword? k) (name k) k)) diff --git a/test/testinterop.cljs b/test/testinterop.cljs index 882fcdb..31b19a2 100644 --- a/test/testinterop.cljs +++ b/test/testinterop.cljs @@ -1,7 +1,7 @@ (ns testinterop (:require [cemerick.cljs.test :as t :refer-macros [is deftest]] [reagent.debug :refer-macros [dbg]] - [reagent.interop :refer-macros [oget oset odo]])) + [reagent.interop :refer-macros [.' .! oget oset odo]])) (deftest interop-basic (let [o #js{:foo "foo" @@ -31,7 +31,7 @@ (is (= "abar1" (-> o2 (oget :o :foo) (odo [] "a")))) - + (is (= "bar1" (odo o :foo))) (is (= "bar1" (odo o [:foo]))) (is (= "bar1" (odo o2 [:o :foo]))) @@ -43,3 +43,48 @@ :call o 1))) (is (= "yy" (odo identity [] "yy"))))) + +(deftest iterop-quote + (let [o #js{:foo "foo" + :foobar #js{:bar "bar"} + :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 (= "barfoo" (.' o -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)))))