/
dom.clj
136 lines (119 loc) · 5.59 KB
/
dom.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
(ns fulcro.client.dom
"MACROS for generating CLJS code. See dom.cljs"
(:refer-clojure :exclude [map meta time mask select use])
(:require
[clojure.spec.alpha :as s]
[fulcro.util :as util]
[clojure.future :refer :all]
[fulcro.client.dom-common :as cdom]
[clojure.string :as str])
(:import
(cljs.tagged_literals JSValue)
(clojure.lang ExceptionInfo)))
(defn- map-of-literals? [v]
(and (map? v) (not-any? symbol? (tree-seq #(or (map? %) (vector? %) (seq? %)) seq v))))
(s/def ::map-of-literals map-of-literals?)
(defn- map-with-expr? [v]
(and (map? v) (some #(or (symbol? %) (list? %)) (tree-seq #(or (map? %) (vector? %) (seq? %)) seq v))))
(s/def ::map-with-expr map-with-expr?)
(s/def ::dom-macro-args
(s/cat
:css (s/? keyword?)
:attrs (s/? (s/or :nil nil?
:map ::map-of-literals
:runtime-map ::map-with-expr
:js-object #(instance? JSValue %)
:expression list?
:symbol symbol?))
:children (s/* (s/or :string string?
:number number?
:symbol symbol?
:nil nil?
:list sequential?))))
(defn clj-map->js-object
"Recursively convert a map to a JS object. For use in macro expansion."
[m]
{:pre [(map? m)]}
(JSValue. (into {}
(clojure.core/map (fn [[k v]]
(cond
(map? v) [k (clj-map->js-object v)]
(vector? v) [k (mapv #(if (map? %) (clj-map->js-object %) %) v)]
(symbol? v) [k `(cljs.core/clj->js ~v)]
:else [k v])))
m)))
(defn- emit-tag
"Helper function for generating CLJS DOM macros"
[str-tag-name args]
(let [conformed-args (util/conform! ::dom-macro-args args)
{attrs :attrs
children :children
css :css} conformed-args
css-props (cdom/add-kwprops-to-props {} css)
children (mapv (fn [[_ c]]
(if (or (nil? c) (string? c))
c
`(fulcro.util/force-children ~c))) children)
attrs-type (or (first attrs) :nil) ; attrs omitted == nil
attrs-value (or (second attrs) {})
create-element (case str-tag-name
"input" 'fulcro.client.dom/macro-create-wrapped-form-element
"textarea" 'fulcro.client.dom/macro-create-wrapped-form-element
"select" 'fulcro.client.dom/macro-create-wrapped-form-element
"option" 'fulcro.client.dom/macro-create-wrapped-form-element
'fulcro.client.dom/macro-create-element*)
classes-expression? (and (= attrs-type :map) (contains? attrs-value :classes))
attrs-type (if classes-expression? :runtime-map attrs-type)]
(case attrs-type
:js-object ; kw combos not supported
(if css
(let [attr-expr `(cdom/add-kwprops-to-props ~attrs-value ~css)]
`(~create-element ~(JSValue. (into [str-tag-name attr-expr] children))))
`(~create-element ~(JSValue. (into [str-tag-name attrs-value] children))))
:map
`(~create-element ~(JSValue. (into [str-tag-name (-> attrs-value
(cdom/add-kwprops-to-props css)
(clj-map->js-object))]
children)))
:runtime-map
`(fulcro.client.dom/macro-create-element ~str-tag-name ~(into [attrs-value] children) ~css)
(:symbol :expression)
`(fulcro.client.dom/macro-create-element
~str-tag-name ~(into [attrs-value] children) ~css)
:nil
`(~create-element
~(JSValue. (into [str-tag-name (JSValue. css-props)] children)))
;; pure children
`(fulcro.client.dom/macro-create-element
~str-tag-name ~(JSValue. (into [attrs-value] children)) ~css))))
(defn syntax-error
"Format a DOM syntax error"
[and-form ex]
(let [location (clojure.core/meta and-form)
file (some-> (:file location) (str/replace #".*[/]" ""))
line (:line location)
unexpected-input (::s/value (ex-data ex))]
(str "Syntax error at " file ":" line ". Unexpected input " unexpected-input)))
(defn gen-dom-macro [emitter name]
`(defmacro ~name ~(cdom/gen-docstring name true)
[& ~'args]
(let [tag# ~(str name)]
(try
(~emitter tag# ~'args)
(catch ExceptionInfo e#
(throw (ex-info (syntax-error ~'&form e#) (ex-data e#))))))))
(defmacro gen-dom-macros [emitter]
`(do ~@(clojure.core/map (partial gen-dom-macro emitter) cdom/tags)))
(defn- gen-client-dom-fn [create-element-symbol tag]
`(defn ~tag ~(cdom/gen-docstring tag true)
[& ~'args]
(let [conformed-args# (util/conform! :fulcro.client.dom/dom-element-args ~'args) ; see CLJS file for spec
{attrs# :attrs
children# :children
css# :css} conformed-args#
children# (mapv second children#)
attrs-value# (or (second attrs#) {})]
(~create-element-symbol ~(name tag) (into [attrs-value#] children#) css#))))
(defmacro gen-client-dom-fns [create-element-sym]
`(do ~@(clojure.core/map (partial gen-client-dom-fn create-element-sym) cdom/tags)))
(gen-dom-macros fulcro.client.dom/emit-tag)