Skip to content

Commit

Permalink
first draft of compile template macros
Browse files Browse the repository at this point in the history
  • Loading branch information
aria42 committed Jan 22, 2013
1 parent fbba2d6 commit 34a236a
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 31 deletions.
2 changes: 1 addition & 1 deletion project.clj
Expand Up @@ -10,7 +10,7 @@
[:email "admin+oss@getprismatic.com"]
[:timezone "-8"]]]
:plugins [[lein-cljsbuild "0.2.10"]]
:dependencies [[crate "0.2.3" :scope "dev"]] ;; for perf test
:dependencies [[org.clojure/clojure "1.4.0"][crate "0.2.3" :scope "dev"]] ;; for perf test
:hooks [leiningen.cljsbuild]
:cljsbuild
{:builds
Expand Down
69 changes: 69 additions & 0 deletions src/dommy/template_compile.clj
@@ -0,0 +1,69 @@
(ns dommy.template-compile
"Clojure macros to generate procedural DOM building when
passed nested structured vectors at compile time. Much faster than runtime templating"
(:require [clojure.string :as str]))

(defmacro compile-add-attr!
"compile-time add attribute"
[d k v]
(assert (keyword? k))
(cond
(identical? k :class) `(set! (.-className ~d) (.trim (str (.-className ~d) " " ~v)))
(identical? k :style) `(.setAttribute ~d ~(name k) (dommy.template/style-str ~v))
(identical? k :classes) `(compile-add-attr! ~d :class ~(str/join " " (map name v)))
:else `(.setAttribute ~d ~(name k) ~v)))

(defn parse-keyword
"return pair [tag class-str id] where tag is dom tag and attrs
are key-value attribute pairs from css-style dom selector"
[node-key]
(let [node-str (name node-key)
node-tag (second (re-find #"^([^.\#]+)[.\#]?" node-str))
classes (map #(.substring ^String % 1) (re-seq #"\.[^.*]*" node-str))
id (first (map #(.substring ^String % 1) (re-seq #"#[^.*]*" node-str)))]
[(if (empty? node-tag) "div" node-tag)
(str/join " " classes)
id]))

(declare node)

(defmacro compile-compound [[node-key & rest]]
(let [literal-attrs (when (map? (first rest)) (first rest))
var-attrs (when (and (not literal-attrs) (-> rest first meta :attrs))
(first rest))
children (drop (if (or literal-attrs var-attrs) 1 0) rest)
[tag class-str id] (parse-keyword node-key)
dom-sym (gensym "dom")]
`(let [~dom-sym (.createElement js/document ~(name tag))]
~@(when-not (empty? class-str)
[`(set! (.-className ~dom-sym) ~class-str)])
~@(when id
[`(.setAttribute ~dom-sym "id" ~id)])
~@(for [[k v] literal-attrs]
(if (keyword? k)
`(compile-add-attr! ~dom-sym ~k ~v)
`(dommy.template/add-attr! ~dom-sym ~k ~v)))
~@(when var-attrs
[`(doseq [[k# v#] ~var-attrs]
(dommy.template/add-attr! ~dom-sym k# v#))])
~@(for [c children]
`(.appendChild ~dom-sym (node ~c)))
~dom-sym)))

(defmacro node [data]
(cond
(vector? data) `(compile-compound ~data)
(keyword? data) `(compile-compound [~data])
(string? data) `(.createTextNode js/document ~data)
:else `(dommy.template/-elem ~data)))

(comment

(compile-compound
[:a {:classes ["class1" "class2"] :href "http://somelink"} "anchor"])

(compile-compound [:a ^:attrs (merge {:class "v"})])

(parse-keyword :div.class1.class2)

)
13 changes: 11 additions & 2 deletions test/dommy/template_perf_test.cljs
@@ -1,6 +1,7 @@
(ns dommy.template-perf-test
(:require [dommy.template :as template]
[crate.core :as crate]))
[crate.core :as crate])
(:require-macros [dommy.template-compile :as template-compile]))

;; Perf Test: dommy vs. crate. vs. jQuery

Expand All @@ -10,6 +11,13 @@
[:div.class1.class2 {:id (str "item" (:key datum))}
[:span.anchor (:name datum)]]]]))


(defn dommy-compiled [datum]
(template-compile/node
[:li [:a {:href (str "#show/" (:key datum))}
[:div.class1.class2 {:id (str "item" (:key datum))}
[:span.anchor (:name datum)]]]]))

(defn crate-template [datum]
(crate/html
[:li [:a {:href (str "#show/" (:key datum))}]
Expand Down Expand Up @@ -45,7 +53,8 @@
(shuffle
[[:jquery jquery-template]
[:crate crate-template]
[:dommy dommy-template]])]
[:dommy dommy-template]
[:dommy-compiled dommy-compiled]])]
(let [ul (-> "<ul>" js/jQuery (.addClass "products"))
secs (run-test ul data li-fn)]
[key secs])))
Expand Down
72 changes: 44 additions & 28 deletions test/dommy/template_test.cljs
@@ -1,33 +1,47 @@
(ns dommy.template-test
(:require [dommy.template :as template]))
(:require [dommy.template :as template])
(:require-macros [dommy.template-compile :as template-compile]))


(defn ^:export simple-test []
(assert (-> :b template/node .-tagName (= "B")))
(assert (-> "some text" template/node .-textContent (= "some text")))
(let [e (template/node [:span "some text"])]
(assert (-> e .-tagName (= "SPAN")))
(assert (-> e .-textContent (= "some text")))
(assert (-> e .-childNodes (aget 0) .-nodeType (= js/document.TEXT_NODE)))
(assert (-> e .-children .-length zero?)))
(let [e (template/node [:a {:classes ["class1" "class2"] :href "http://somelink"} "anchor"])]
(assert (-> e .-tagName (= "A")))
(assert (-> e .-textContent (= "anchor")))
(assert (-> e (.getAttribute "href") (= "http://somelink")))
(assert (-> e .-className (= "class1 class2"))))
(let [e (template/base-element :div#id.class1.class2)]
(assert (-> e .-tagName (= "DIV")))
(assert (-> e (.getAttribute "id") (= "id")))
(assert (-> e .-className (= "class1 class2"))))
(let [e (template/compound-element [:div {:style {:margin-left "15px"}}])]
(assert (-> e .-tagName (= "DIV")))
(assert (-> e (.getAttribute "style") (= "margin-left:15px;"))))
(let [e (template/compound-element [:div.class1 [:span#id1 "span1"] [:span#id2 "span2"]])]
(assert (-> e .-textContent (= "span1span2")))
(assert (-> e .-className (= "class1")))
(assert (-> e .-childNodes .-length (= 2)))
(assert (-> e .-innerHTML (= "<span id=\"id1\">span1</span><span id=\"id2\">span2</span>")))
(assert (-> e .-childNodes (aget 0) .-innerHTML (= "span1")))
(assert (-> e .-childNodes (aget 1) .-innerHTML (= "span2"))))
;; unfortunately to satisfy the macro gods, you need to
;; duplicate the vector literal to test compiled and runtime template
(let [e1 (template/node [:span "some text"])
e2 (template-compile/node [:span "some text"])]
(doseq [e [e1 e2]]
(assert (-> e .-tagName (= "SPAN")))
(assert (-> e .-textContent (= "some text")))
(assert (-> e .-childNodes (aget 0) .-nodeType (= js/document.TEXT_NODE)))
(assert (-> e .-children .-length zero?))))
(let [e1 (template/node [:a {:classes ["class1" "class2"] :href "http://somelink"} "anchor"])
e2 (template-compile/node
[:a {:classes ["class1" "class2"] :href "http://somelink"} "anchor"])]
(doseq [e [e1 e2]] (assert (-> e .-tagName (= "A")))
(assert (-> e .-textContent (= "anchor")))
(assert (-> e (.getAttribute "href") (= "http://somelink")))
(assert (-> e .-className (= "class1 class2")))))
(let [e1 (template/base-element :div#id.class1.class2)
e2 (template-compile/node :div#id.class1.class2)]
(doseq [e [e1 e2]]
(assert (-> e .-tagName (= "DIV")))
(assert (-> e (.getAttribute "id") (= "id")))
(assert (-> e .-className (= "class1 class2")))))
(let [e1 (template/compound-element [:div {:style {:margin-left "15px"}}])
e2 (template-compile/node [:div {:style {:margin-left "15px"}}])]
(doseq [e [e1 e2]]
(assert (-> e .-tagName (= "DIV")))
(assert (-> e (.getAttribute "style") (= "margin-left:15px;")))))
(let [e1 (template/compound-element [:div.class1 [:span#id1 "span1"] [:span#id2 "span2"]])
e2 (template-compile/node [:div.class1 [:span#id1 "span1"] [:span#id2 "span2"]])]
(doseq [e [e1 e2]]
(assert (-> e .-textContent (= "span1span2")))
(assert (-> e .-className (= "class1")))
(assert (-> e .-childNodes .-length (= 2)))
(assert (-> e .-innerHTML (= "<span id=\"id1\">span1</span><span id=\"id2\">span2</span>")))
(assert (-> e .-childNodes (aget 0) .-innerHTML (= "span1")))
(assert (-> e .-childNodes (aget 1) .-innerHTML (= "span2")))))
(assert (= "<span id=\"id1\">span1</span><span id=\"id2\">span2</span>"
(-> [:div (for [x [1 2]] [:span {:id (str "id" x)} (str "span" x)])]
template/node
Expand All @@ -36,9 +50,11 @@
(assert (-> e .-tagName (= "DIV")))
(assert (-> e .-innerHTML (= "<p>some-text</p>")))
(assert (= e (template/node e))))
(let [e (template/base-element :#id1.class1)]
(assert (= (.-outerHTML (template/base-element :div#id1.class1))
(.-outerHTML (template/base-element :#id1.class1)))))
(let [e1 (template/base-element :#id1.class1)
e2 (template-compile/node :#id1.class1)]
(doseq [e [e1 e2]]
(assert (= (.-outerHTML (template/base-element :div#id1.class1))
(.-outerHTML (template/base-element :#id1.class1))))))
(.log js/console "PASS simple-test"))

(simple-test)

0 comments on commit 34a236a

Please sign in to comment.