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