-
Notifications
You must be signed in to change notification settings - Fork 52
/
dom.cljs
190 lines (157 loc) · 5.46 KB
/
dom.cljs
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
(ns c2.dom
(:refer-clojure :exclude [val])
(:use-macros [c2.util :only [p pp timeout bind!]]
[clojure.core.match.js :only [match]])
(:require [clojure.string :as string]
[singult.core :as singult]
[goog.dom :as gdom]
[goog.dom.forms :as gforms]
[goog.dom.classes :as gclasses]
[goog.style :as gstyle]))
;;Going down a terrible, terrible road here...
(js* "Element.prototype.matchesSelector = Element.prototype.webkitMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector")
;;Seq over native JavaScript node collections
(when (js* "typeof NodeList != \"undefined\"")
(extend-type js/NodeList
ISeqable
(-seq [array] (array-seq array 0))))
(extend-type js/HTMLCollection
ISeqable
(-seq [array] (array-seq array 0)))
(declare select)
(defprotocol IDom
(->dom [x] "Converts x to a live DOM node"))
(extend-protocol IDom
string
(->dom [selector] (select selector))
PersistentVector
(->dom [v] (singult/render v)))
(when (js* "typeof Node != \"undefined\"")
(extend-type js/Node
IDom
(->dom [node] node)))
(defn select
"Select a single DOM node via CSS selector, optionally scoped by second arg."
([selector] (.querySelector js/document selector))
([selector container] (.querySelector (->dom container) selector)))
(defn select-all
"Like select, but returns a collection of nodes."
([selector] (.querySelectorAll js/document selector))
([selector container] (.querySelectorAll (->dom container) selector)))
(defn matches-selector?
"Does live `node` match CSS `selector`?"
[node selector]
(.matchesSelector node selector))
(defn children
"Return the children of a live DOM element."
[node]
(.-children (->dom node)))
(defn parent
"Return parent of a live DOM node."
[node]
(.-parentNode (->dom node)))
(defn append!
"Make element last child of container.
Returns live child."
[container el]
(let [el (->dom el)]
(gdom/appendChild (->dom container) el)
el))
(defn prepend!
"Make element first child of container.
Returns live DOM child."
[container el]
(let [el (->dom el)]
(gdom/insertChildAt (->dom container) el 0)
el))
(defn remove!
"Remove element from DOM and return it.
> *el* CSS selector or live DOM node"
[el]
(gdom/removeNode (->dom el)))
(defn replace!
"Replace live DOM node with a new one, returning the latter.
> *old* CSS selector or live DOM node
> *new* CSS selector, live DOM node, or hiccup vector"
[old new]
(let [new (->dom new)]
(gdom/replaceNode new (->dom old))
new))
(defn style
"Get or set inline element style.
`(style el)` map of inline element styles
`(style el :keyword)` value of style :keyword
`(style el {:keyword val})` sets inline style according to map, returns element
`(style el :keyword val)` sets single style, returns element"
([el] (throw (js/Error. "TODO: return map of element styles")))
([el x]
(let [el (->dom el)]
(match [x]
[(k :guard keyword?)] (gstyle/getComputedStyle el (name k))
[(m :guard map?)]
(do
(doseq [[k v] m] (style el k v))
el))))
([el k v]
(gstyle/setStyle (->dom el) (name k)
(match [v]
[s :guard string?] s
[n :guard number?]
(if (#{:height :width :top :left :bottom :right} (keyword k))
(str n "px")
n)))
el))
(defn attr
"Get or set element attributes.
`(attr el)` map of element attributes
`(attr el :keyword)` value of attr :keyword
`(attr el {:keyword val})` sets element attributes according to map, returns element
`(attr el :keyword val)` sets single attr, returns element"
([el] (let [attrs (.-attributes (->dom el))]
(into {} (for [i (range (.-length attrs))]
[(keyword (.-name (aget attrs i)))
(.-value (aget attrs i))]))))
([el x]
(let [el (->dom el)]
(match [x]
[(k :guard keyword?)] (.getAttribute el (name k))
[(m :guard map?)]
(do (doseq [[k v] m] (attr el k v))
el))))
([el k v]
(let [el (->dom el)]
(if (nil? v)
(.removeAttribute el (name k))
(if (= :style k)
(style el v)
(.setAttribute el (name k) v)))
el)))
(defn text
"Get or set element text, returning element"
([el]
(gdom/getTextContent (->dom el)))
([el v]
(let [el (->dom el)]
(gdom/setTextContent el v)
el)))
(defn val
"Get or set element value."
([el]
(gforms/getValue (->dom el)))
([el v]
(let [el (->dom el)]
(gforms/setValue el v)
el)))
(defn classed!
"Add or remove `class` to element based on boolean `classed?`, returning element."
[el class classed?]
(gclasses/enable (->dom el) (name class) classed?)
el)
;;TODO: make these kind of shortcuts macros for better performance.
(defn add-class! [el class] (classed! el class true))
(defn remove-class! [el class] (classed! el class false))
;;Call this fn with a fn that should be executed on the next browser animation frame.
(def request-animation-frame
(or (.-requestAnimationFrame js/window)
(.-webkitRequestAnimationFrame js/window)
#(timeout 10 (%))))