git ssb

1+

Matt McKegg / mutant



Commit aacf3bddbfecb924c25697a24bafc3bb0b1b86ad

more work on improving late binding and clean-up

Matt McKegg committed on 7/26/2016, 12:08:04 AM
Parent: aa8acd0d5045ae3a53f460c3c001f88dfdf80904

Files changed

html-element.jschanged
lib/apply-properties.jschanged
lib/walk.jsadded
html-element.jsView
@@ -1,8 +1,11 @@
11 var applyProperties = require('./lib/apply-properties')
22 var isObservable = require('./is-observable')
33 var parseTag = require('./lib/parse-tag')
4 +var walk = require('./lib/walk')
5 +var watch = require('./watch')
46 var caches = new global.WeakMap()
7 +var watcher = null
58
69 module.exports = function (tag, attributes, children) {
710 return Element(global.document, null, tag, attributes, children)
811 }
@@ -10,19 +13,15 @@
1013 module.exports.forDocument = function (document, namespace) {
1114 return Element.bind(this, document, namespace)
1215 }
1316
14-module.exports.destroy = function (node) {
15- unbind(node)
16- caches.delete(node)
17-}
18-
1917 function Element (document, namespace, tagName, properties, children) {
2018 if (!children && (Array.isArray(properties) || isText(properties))) {
2119 children = properties
2220 properties = null
2321 }
2422
23 + checkWatcher(document)
2524 properties = properties || {}
2625
2726 var tag = parseTag(tagName, properties, namespace)
2827 var node = namespace
@@ -42,19 +41,13 @@
4241 bindings: []
4342 }
4443
4544 caches.set(node, data)
46- var hooks = applyProperties(node, properties, data)
45 + applyProperties(node, properties, data)
4746 if (children != null) {
4847 appendChild(document, node, data, children)
4948 }
5049
51- if (Array.isArray(hooks)) {
52- hooks.forEach(function (v) {
53- data.bindings.push(new HookBinding(v, node))
54- })
55- }
56-
5750 return node
5851 }
5952
6053 function appendChild (document, target, data, node) {
@@ -67,18 +60,50 @@
6760 nodes.forEach(append, target)
6861 data.targets.set(node, nodes)
6962 data.bindings.push(new Binding(bind, document, node, data))
7063 } else {
71- target.appendChild(getNode(document, node))
64 + node = getNode(document, node)
65 + target.appendChild(node)
66 + walk(node, rebind)
7267 }
7368 }
7469
7570 function append (child) {
7671 this.appendChild(child)
72 + walk(child, rebind)
7773 }
7874
75 +function checkWatcher (document) {
76 + if (!watcher && global.MutationObserver) {
77 + watcher = new global.MutationObserver(onMutate)
78 + watcher.observe(document, {subtree: true, childList: true})
79 + }
80 +}
81 +
82 +function onMutate (changes) {
83 + changes.forEach(handleChange)
84 +}
85 +
86 +function handleChange (change) {
87 + for (var i = 0; i < change.addedNodes.length; i++) {
88 + // if parent is a mutant element, then safe to assume it has already been bound
89 + var node = change.addedNodes[i]
90 + if (!caches.has(node.parentNode)) {
91 + walk(node, rebind)
92 + }
93 + }
94 + for (var i = 0; i < change.removedNodes.length; i++) {
95 + // if has already been unbound, safe to assume children have also
96 + var node = change.removedNodes[i]
97 + var data = caches.get(node)
98 + if (data && data.bound) {
99 + walk(node, unbind)
100 + }
101 + }
102 +}
103 +
79104 function bind (document, obs, data) {
80- return obs(function (value) {
105 + return watch(obs, function (value) {
81106 var oldNodes = data.targets.get(obs)
82107 var newNodes = getNodes(document, value)
83108 if (oldNodes) {
84109 replace(oldNodes, newNodes)
@@ -94,18 +119,19 @@
94119 oldNodes.filter(function (node) {
95120 return !~newNodes.indexOf(node)
96121 }).forEach(function (node) {
97122 parent.removeChild(node)
98- unbind(node)
123 + walk(node, unbind)
99124 })
100125 if (marker) {
101126 newNodes.forEach(function (node) {
102127 parent.insertBefore(node, marker)
128 + walk(node, rebind)
103129 })
104130 } else {
105131 newNodes.forEach(function (node) {
106132 parent.appendChild(node)
107- rebind(node)
133 + walk(node, rebind)
108134 })
109135 }
110136 }
111137
@@ -148,11 +174,8 @@
148174 var data = caches.get(node)
149175 if (data) {
150176 data.bindings.forEach(invokeBind)
151177 }
152- for (var i = 0; i < node.childNodes.length; i++) {
153- rebind(node.childNodes[i])
154- }
155178 }
156179 }
157180
158181 function unbind (node) {
@@ -160,11 +183,8 @@
160183 var data = caches.get(node)
161184 if (data) {
162185 data.bindings.forEach(invokeUnbind)
163186 }
164- for (var i = 0; i < node.childNodes.length; i++) {
165- unbind(node.childNodes[i])
166- }
167187 }
168188 }
169189
170190 function invokeBind (binding) {
@@ -182,42 +202,14 @@
182202 function resolve (source) {
183203 return typeof source === 'function' ? source() : source
184204 }
185205
186-function tryInvoke (func) {
187- if (typeof func === 'function') {
188- func()
189- }
190-}
191-
192-function HookBinding (fn, element) {
193- this.element = element
194- this.fn = fn
195- this.bind()
196-}
197-
198-HookBinding.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-
214206 function Binding (fn, document, obs, data) {
215207 this.document = document
216208 this.obs = obs
217209 this.data = data
218210 this.fn = fn
219- this.bind()
211 + this.bound = false
220212 }
221213
222214 Binding.prototype = {
223215 bind: function () {
lib/apply-properties.jsView
@@ -9,35 +9,35 @@
99 if (target.className) {
1010 classList.add(target.className)
1111 }
1212
13- var hooks = null
14-
1513 for (var key in properties) {
1614 var valueOrObs = properties[key]
1715 var value = resolve(valueOrObs)
1816
1917 if (key === 'style') {
2018 // TODO: handle observable at root for style objects
2119 for (var k in value) {
22- var styleValue = resolve(value[k])
2320 var styleObs = isObservable(value[k]) ? value[k] : null
24- target.style.setProperty(k, styleValue)
25-
2621 if (styleObs) {
2722 data.bindings.push(new Binding(bindStyle, target, styleObs, k))
23 + } else {
24 + target.style.setProperty(k, value[k])
2825 }
2926 }
3027 } else if (key === 'hooks') {
31- hooks = value
28 + if (Array.isArray(value)) {
29 + value.forEach(function (v) {
30 + data.bindings.push(new HookBinding(v, target))
31 + })
32 + }
3233 } else if (key === 'attributes') {
3334 for (var k in value) {
34- var attrValue = resolve(value[k])
3535 var attrObs = isObservable(value[k]) ? value[k] : null
36- target.setAttribute(k, attrValue)
37-
3836 if (attrObs) {
3937 data.bindings.push(new Binding(bindAttr, target, attrObs, k))
38 + } else {
39 + target.setAttribute(k, value[k])
4040 }
4141 }
4242 } else if (key === 'events') {
4343 for (var name in value) {
@@ -61,26 +61,30 @@
6161 }
6262 }
6363 }
6464
65- watch(classList, function (value) {
65 + if (classList.getLength()) {
66 + data.bindings.push(new Binding(bindClassList, target, classList, 'className'))
67 + }
68 +}
69 +
70 +function bindClassList (target, obs, key) {
71 + return watch(obs, function (value) {
6672 value = [].concat.apply([], value).filter(present).join(' ')
67- if (value || target.className) {
68- target.className = value
73 + if (value || target[key]) {
74 + target[key] = value
6975 }
7076 })
71-
72- return hooks
7377 }
7478
7579 function bindStyle (target, styleObs, key) {
76- return styleObs(function (value) {
80 + return watch(styleObs, function (value) {
7781 target.style.setProperty(key, value)
7882 })
7983 }
8084
8185 function bindAttr (target, attrObs, key) {
82- return attrObs(function (value) {
86 + return watch(attrObs, function (value) {
8387 if (value == null) {
8488 target.removeAttribute(key)
8589 } else {
8690 target.setAttribute(key, value)
@@ -88,9 +92,9 @@
8892 })
8993 }
9094
9195 function bind (target, obs, key) {
92- return obs(function (toValue) {
96 + return watch(obs, function (toValue) {
9397 var fromValue = target.getAttribute(key)
9498 if (fromValue !== toValue) {
9599 target.setAttribute(key, toValue)
96100 }
@@ -109,9 +113,9 @@
109113 this.element = element
110114 this.source = source
111115 this.key = key
112116 this.fn = fn
113- this.bind()
117 + this.bound = false
114118 }
115119
116120 Binding.prototype = {
117121 bind: function () {
@@ -127,4 +131,26 @@
127131 this.bound = false
128132 }
129133 }
130134 }
135 +
136 +function HookBinding (fn, element) {
137 + this.element = element
138 + this.fn = fn
139 + this.bound = false
140 +}
141 +
142 +HookBinding.prototype = {
143 + bind: function () {
144 + if (!this.bound) {
145 + this._release = this.fn(this.element)
146 + this.bound = true
147 + }
148 + },
149 + unbind: function () {
150 + if (this.bound && typeof this._release === 'function') {
151 + this._release()
152 + this._release = null
153 + this.bound = false
154 + }
155 + }
156 +}
lib/walk.jsView
@@ -1,0 +1,16 @@
1 +module.exports = function walk (node, fn) {
2 + var current = node
3 + while (current) {
4 + fn(current)
5 + current = nextNode(current, node)
6 + }
7 +}
8 +
9 +function nextNode (current, root) {
10 + var result = current.firstChild || current.nextSibling
11 + if (!result && current.parentNode && current.parentNode !== root) {
12 + return current.nextSibling
13 + } else {
14 + return result
15 + }
16 +}

Built with git-ssb-web