Commit aacf3bddbfecb924c25697a24bafc3bb0b1b86ad
more work on improving late binding and clean-up
Matt McKegg committed on 7/26/2016, 12:08:04 AMParent: aa8acd0d5045ae3a53f460c3c001f88dfdf80904
Files changed
html-element.js | changed |
lib/apply-properties.js | changed |
lib/walk.js | added |
html-element.js | ||
---|---|---|
@@ -1,8 +1,11 @@ | ||
1 | 1 … | var applyProperties = require('./lib/apply-properties') |
2 | 2 … | var isObservable = require('./is-observable') |
3 | 3 … | var parseTag = require('./lib/parse-tag') |
4 … | +var walk = require('./lib/walk') | |
5 … | +var watch = require('./watch') | |
4 | 6 … | var caches = new global.WeakMap() |
7 … | +var watcher = null | |
5 | 8 … | |
6 | 9 … | module.exports = function (tag, attributes, children) { |
7 | 10 … | return Element(global.document, null, tag, attributes, children) |
8 | 11 … | } |
@@ -10,19 +13,15 @@ | ||
10 | 13 … | module.exports.forDocument = function (document, namespace) { |
11 | 14 … | return Element.bind(this, document, namespace) |
12 | 15 … | } |
13 | 16 … | |
14 | -module.exports.destroy = function (node) { | |
15 | - unbind(node) | |
16 | - caches.delete(node) | |
17 | -} | |
18 | - | |
19 | 17 … | function Element (document, namespace, tagName, properties, children) { |
20 | 18 … | if (!children && (Array.isArray(properties) || isText(properties))) { |
21 | 19 … | children = properties |
22 | 20 … | properties = null |
23 | 21 … | } |
24 | 22 … | |
23 … | + checkWatcher(document) | |
25 | 24 … | properties = properties || {} |
26 | 25 … | |
27 | 26 … | var tag = parseTag(tagName, properties, namespace) |
28 | 27 … | var node = namespace |
@@ -42,19 +41,13 @@ | ||
42 | 41 … | bindings: [] |
43 | 42 … | } |
44 | 43 … | |
45 | 44 … | caches.set(node, data) |
46 | - var hooks = applyProperties(node, properties, data) | |
45 … | + applyProperties(node, properties, data) | |
47 | 46 … | if (children != null) { |
48 | 47 … | appendChild(document, node, data, children) |
49 | 48 … | } |
50 | 49 … | |
51 | - if (Array.isArray(hooks)) { | |
52 | - hooks.forEach(function (v) { | |
53 | - data.bindings.push(new HookBinding(v, node)) | |
54 | - }) | |
55 | - } | |
56 | - | |
57 | 50 … | return node |
58 | 51 … | } |
59 | 52 … | |
60 | 53 … | function appendChild (document, target, data, node) { |
@@ -67,18 +60,50 @@ | ||
67 | 60 … | nodes.forEach(append, target) |
68 | 61 … | data.targets.set(node, nodes) |
69 | 62 … | data.bindings.push(new Binding(bind, document, node, data)) |
70 | 63 … | } else { |
71 | - target.appendChild(getNode(document, node)) | |
64 … | + node = getNode(document, node) | |
65 … | + target.appendChild(node) | |
66 … | + walk(node, rebind) | |
72 | 67 … | } |
73 | 68 … | } |
74 | 69 … | |
75 | 70 … | function append (child) { |
76 | 71 … | this.appendChild(child) |
72 … | + walk(child, rebind) | |
77 | 73 … | } |
78 | 74 … | |
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 … | + | |
79 | 104 … | function bind (document, obs, data) { |
80 | - return obs(function (value) { | |
105 … | + return watch(obs, function (value) { | |
81 | 106 … | var oldNodes = data.targets.get(obs) |
82 | 107 … | var newNodes = getNodes(document, value) |
83 | 108 … | if (oldNodes) { |
84 | 109 … | replace(oldNodes, newNodes) |
@@ -94,18 +119,19 @@ | ||
94 | 119 … | oldNodes.filter(function (node) { |
95 | 120 … | return !~newNodes.indexOf(node) |
96 | 121 … | }).forEach(function (node) { |
97 | 122 … | parent.removeChild(node) |
98 | - unbind(node) | |
123 … | + walk(node, unbind) | |
99 | 124 … | }) |
100 | 125 … | if (marker) { |
101 | 126 … | newNodes.forEach(function (node) { |
102 | 127 … | parent.insertBefore(node, marker) |
128 … | + walk(node, rebind) | |
103 | 129 … | }) |
104 | 130 … | } else { |
105 | 131 … | newNodes.forEach(function (node) { |
106 | 132 … | parent.appendChild(node) |
107 | - rebind(node) | |
133 … | + walk(node, rebind) | |
108 | 134 … | }) |
109 | 135 … | } |
110 | 136 … | } |
111 | 137 … | |
@@ -148,11 +174,8 @@ | ||
148 | 174 … | var data = caches.get(node) |
149 | 175 … | if (data) { |
150 | 176 … | data.bindings.forEach(invokeBind) |
151 | 177 … | } |
152 | - for (var i = 0; i < node.childNodes.length; i++) { | |
153 | - rebind(node.childNodes[i]) | |
154 | - } | |
155 | 178 … | } |
156 | 179 … | } |
157 | 180 … | |
158 | 181 … | function unbind (node) { |
@@ -160,11 +183,8 @@ | ||
160 | 183 … | var data = caches.get(node) |
161 | 184 … | if (data) { |
162 | 185 … | data.bindings.forEach(invokeUnbind) |
163 | 186 … | } |
164 | - for (var i = 0; i < node.childNodes.length; i++) { | |
165 | - unbind(node.childNodes[i]) | |
166 | - } | |
167 | 187 … | } |
168 | 188 … | } |
169 | 189 … | |
170 | 190 … | function invokeBind (binding) { |
@@ -182,42 +202,14 @@ | ||
182 | 202 … | function resolve (source) { |
183 | 203 … | return typeof source === 'function' ? source() : source |
184 | 204 … | } |
185 | 205 … | |
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 | - | |
214 | 206 … | function Binding (fn, document, obs, data) { |
215 | 207 … | this.document = document |
216 | 208 … | this.obs = obs |
217 | 209 … | this.data = data |
218 | 210 … | this.fn = fn |
219 | - this.bind() | |
211 … | + this.bound = false | |
220 | 212 … | } |
221 | 213 … | |
222 | 214 … | Binding.prototype = { |
223 | 215 … | bind: function () { |
lib/apply-properties.js | ||
---|---|---|
@@ -9,35 +9,35 @@ | ||
9 | 9 … | if (target.className) { |
10 | 10 … | classList.add(target.className) |
11 | 11 … | } |
12 | 12 … | |
13 | - var hooks = null | |
14 | - | |
15 | 13 … | for (var key in properties) { |
16 | 14 … | var valueOrObs = properties[key] |
17 | 15 … | var value = resolve(valueOrObs) |
18 | 16 … | |
19 | 17 … | if (key === 'style') { |
20 | 18 … | // TODO: handle observable at root for style objects |
21 | 19 … | for (var k in value) { |
22 | - var styleValue = resolve(value[k]) | |
23 | 20 … | var styleObs = isObservable(value[k]) ? value[k] : null |
24 | - target.style.setProperty(k, styleValue) | |
25 | - | |
26 | 21 … | if (styleObs) { |
27 | 22 … | data.bindings.push(new Binding(bindStyle, target, styleObs, k)) |
23 … | + } else { | |
24 … | + target.style.setProperty(k, value[k]) | |
28 | 25 … | } |
29 | 26 … | } |
30 | 27 … | } 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 … | + } | |
32 | 33 … | } else if (key === 'attributes') { |
33 | 34 … | for (var k in value) { |
34 | - var attrValue = resolve(value[k]) | |
35 | 35 … | var attrObs = isObservable(value[k]) ? value[k] : null |
36 | - target.setAttribute(k, attrValue) | |
37 | - | |
38 | 36 … | if (attrObs) { |
39 | 37 … | data.bindings.push(new Binding(bindAttr, target, attrObs, k)) |
38 … | + } else { | |
39 … | + target.setAttribute(k, value[k]) | |
40 | 40 … | } |
41 | 41 … | } |
42 | 42 … | } else if (key === 'events') { |
43 | 43 … | for (var name in value) { |
@@ -61,26 +61,30 @@ | ||
61 | 61 … | } |
62 | 62 … | } |
63 | 63 … | } |
64 | 64 … | |
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) { | |
66 | 72 … | 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 | |
69 | 75 … | } |
70 | 76 … | }) |
71 | - | |
72 | - return hooks | |
73 | 77 … | } |
74 | 78 … | |
75 | 79 … | function bindStyle (target, styleObs, key) { |
76 | - return styleObs(function (value) { | |
80 … | + return watch(styleObs, function (value) { | |
77 | 81 … | target.style.setProperty(key, value) |
78 | 82 … | }) |
79 | 83 … | } |
80 | 84 … | |
81 | 85 … | function bindAttr (target, attrObs, key) { |
82 | - return attrObs(function (value) { | |
86 … | + return watch(attrObs, function (value) { | |
83 | 87 … | if (value == null) { |
84 | 88 … | target.removeAttribute(key) |
85 | 89 … | } else { |
86 | 90 … | target.setAttribute(key, value) |
@@ -88,9 +92,9 @@ | ||
88 | 92 … | }) |
89 | 93 … | } |
90 | 94 … | |
91 | 95 … | function bind (target, obs, key) { |
92 | - return obs(function (toValue) { | |
96 … | + return watch(obs, function (toValue) { | |
93 | 97 … | var fromValue = target.getAttribute(key) |
94 | 98 … | if (fromValue !== toValue) { |
95 | 99 … | target.setAttribute(key, toValue) |
96 | 100 … | } |
@@ -109,9 +113,9 @@ | ||
109 | 113 … | this.element = element |
110 | 114 … | this.source = source |
111 | 115 … | this.key = key |
112 | 116 … | this.fn = fn |
113 | - this.bind() | |
117 … | + this.bound = false | |
114 | 118 … | } |
115 | 119 … | |
116 | 120 … | Binding.prototype = { |
117 | 121 … | bind: function () { |
@@ -127,4 +131,26 @@ | ||
127 | 131 … | this.bound = false |
128 | 132 … | } |
129 | 133 … | } |
130 | 134 … | } |
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.js | ||
---|---|---|
@@ -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