git ssb

1+

Matt McKegg / mutant



Tree: 1b503e67aebb1a6a8373f20984ff3e6e745ecaa0

Files: 1b503e67aebb1a6a8373f20984ff3e6e745ecaa0 / html-element.js

5386 bytesRaw
1var applyProperties = require('./lib/apply-properties')
2var isObservable = require('./is-observable')
3var parseTag = require('./lib/parse-tag')
4var caches = new global.WeakMap()
5
6module.exports = function (tag, attributes, children) {
7 return Element(global.document, null, tag, attributes, children)
8}
9
10module.exports.forDocument = function (document, namespace) {
11 return Element.bind(this, document, namespace)
12}
13
14module.exports.destroy = function (node) {
15 unbind(node)
16 caches.delete(node)
17}
18
19function Element (document, namespace, tagName, properties, children) {
20 if (!children && (Array.isArray(properties) || isText(properties))) {
21 children = properties
22 properties = null
23 }
24
25 properties = properties || {}
26
27 var tag = parseTag(tagName, properties, namespace)
28 var node = namespace
29 ? document.createElementNS(namespace, tag.tagName)
30 : document.createElement(tag.tagName)
31
32 if (tag.id) {
33 node.id = tag.id
34 }
35
36 if (tag.classes && tag.classes.length) {
37 node.className = tag.classes.join(' ')
38 }
39
40 var data = {
41 targets: new Map(),
42 bindings: []
43 }
44
45 caches.set(node, data)
46 var hooks = applyProperties(node, properties, data)
47 if (children != null) {
48 appendChild(document, node, data, children)
49 }
50
51 if (Array.isArray(hooks)) {
52 hooks.forEach(function (v) {
53 data.bindings.push(new HookBinding(v, node))
54 })
55 }
56
57 return node
58}
59
60function appendChild (document, target, data, node) {
61 if (Array.isArray(node)) {
62 node.forEach(function (child) {
63 appendChild(document, target, data, child)
64 })
65 } else if (isObservable(node)) {
66 var nodes = getNodes(document, resolve(node))
67 nodes.forEach(append, target)
68 data.targets.set(node, nodes)
69 data.bindings.push(new Binding(bind, document, node, data))
70 } else {
71 target.appendChild(getNode(document, node))
72 }
73}
74
75function append (child) {
76 this.appendChild(child)
77}
78
79function bind (document, obs, data) {
80 return obs(function (value) {
81 var oldNodes = data.targets.get(obs)
82 var newNodes = getNodes(document, value)
83 if (oldNodes) {
84 replace(oldNodes, newNodes)
85 data.targets.set(obs, newNodes)
86 }
87 })
88}
89
90function replace (oldNodes, newNodes) {
91 // TODO: optmize to not reinsert nodes that are already in correct position!
92 var parent = oldNodes[oldNodes.length - 1].parentNode
93 var marker = oldNodes[oldNodes.length - 1].nextSibling
94 oldNodes.filter(function (node) {
95 return !~newNodes.indexOf(node)
96 }).forEach(function (node) {
97 parent.removeChild(node)
98 unbind(node)
99 })
100 if (marker) {
101 newNodes.forEach(function (node) {
102 parent.insertBefore(node, marker)
103 })
104 } else {
105 newNodes.forEach(function (node) {
106 parent.appendChild(node)
107 rebind(node)
108 })
109 }
110}
111
112function isText (value) {
113 return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
114}
115
116function getNode (document, nodeOrText) {
117 if (nodeOrText == null) {
118 return document.createTextNode('')
119 } else if (isText(nodeOrText)) {
120 return document.createTextNode(nodeOrText.toString())
121 } else {
122 return nodeOrText
123 }
124}
125
126function getNodes (document, nodeOrNodes) {
127 if (Array.isArray(nodeOrNodes)) {
128 if (nodeOrNodes.length) {
129 var result = []
130 nodeOrNodes.forEach(function (item) {
131 if (Array.isArray(item)) {
132 getNodes(document, item).forEach(push, result)
133 } else {
134 result.push(getNode(document, item))
135 }
136 })
137 return result.map(getNode.bind(this, document))
138 } else {
139 return [getNode(document, null)]
140 }
141 } else {
142 return [getNode(document, nodeOrNodes)]
143 }
144}
145
146function rebind (node) {
147 if (node.nodeType === 1) {
148 var data = caches.get(node)
149 if (data) {
150 data.bindings.forEach(invokeBind)
151 }
152 for (var i = 0; i < node.childNodes.length; i++) {
153 rebind(node.childNodes[i])
154 }
155 }
156}
157
158function unbind (node) {
159 if (node.nodeType === 1) {
160 var data = caches.get(node)
161 if (data) {
162 data.bindings.forEach(invokeUnbind)
163 }
164 for (var i = 0; i < node.childNodes.length; i++) {
165 unbind(node.childNodes[i])
166 }
167 }
168}
169
170function invokeBind (binding) {
171 binding.bind()
172}
173
174function invokeUnbind (binding) {
175 binding.unbind()
176}
177
178function push (item) {
179 this.push(item)
180}
181
182function resolve (source) {
183 return typeof source === 'function' ? source() : source
184}
185
186function tryInvoke (func) {
187 if (typeof func === 'function') {
188 func()
189 }
190}
191
192function HookBinding (fn, element) {
193 this.element = element
194 this.fn = fn
195 this.bind()
196}
197
198HookBinding.prototype = {
199 bind: function () {
200 if (!this.bound) {
201 this._release = this.fn(this.element)
202 this.bound = true
203 }
204 },
205 unbind: function () {
206 if (this.bound && typeof this._release === 'function') {
207 this._release()
208 this._release = null
209 this.bound = false
210 }
211 }
212}
213
214function Binding (fn, document, obs, data) {
215 this.document = document
216 this.obs = obs
217 this.data = data
218 this.fn = fn
219 this.bind()
220}
221
222Binding.prototype = {
223 bind: function () {
224 if (!this.bound) {
225 this._release = this.fn(this.document, this.obs, this.data)
226 this.bound = true
227 }
228 },
229 unbind: function () {
230 if (this.bound && typeof this._release === 'function') {
231 this._release()
232 this._release = null
233 this.bound = false
234 }
235 }
236}
237

Built with git-ssb-web