Commit 955ee4ba0914a6c2c9a1158bdfa7ea7a186ca900
html-element: handle teardown of observables when elements removed from dom
Matt McKegg committed on 7/22/2016, 12:27:11 PMParent: 1b3ee2905a0cb00a98265dfd85dc66f2e87d675b
Files changed
html-element.js | changed |
lib/apply-properties.js | changed |
html-element.js | |||
---|---|---|---|
@@ -11,14 +11,10 @@ | |||
11 | 11 … | return Element.bind(this, document, namespace) | |
12 | 12 … | } | |
13 | 13 … | ||
14 | 14 … | module.exports.destroy = function (node) { | |
15 | - var data = caches.get(node) | ||
16 | - if (data) { | ||
17 | - Array.from(data.releases.values()).forEach(tryInvoke) | ||
18 | - caches.delete(node) | ||
19 | - } | ||
20 | - applyProperties.destroy(node) | ||
15 … | + unbind(node) | ||
16 … | + caches.delete(node) | ||
21 | 17 … | } | |
22 | 18 … | ||
23 | 19 … | function Element (document, namespace, tagName, properties, children) { | |
24 | 20 … | if (!children && (Array.isArray(properties) || isText(properties))) { | |
@@ -42,17 +38,23 @@ | |||
42 | 38 … | } | |
43 | 39 … | ||
44 | 40 … | var data = { | |
45 | 41 … | targets: new Map(), | |
46 | - releases: new Map() | ||
42 … | + bindings: [] | ||
47 | 43 … | } | |
48 | 44 … | ||
49 | 45 … | caches.set(node, data) | |
50 | - applyProperties(node, properties) | ||
46 … | + var hooks = applyProperties(node, properties, data) | ||
51 | 47 … | if (children != null) { | |
52 | 48 … | appendChild(document, node, data, children) | |
53 | 49 … | } | |
54 | 50 … | ||
51 … | + if (Array.isArray(hooks)) { | ||
52 … | + hooks.forEach(function (v) { | ||
53 … | + data.bindings.push(new HookBinding(v, node)) | ||
54 … | + }) | ||
55 … | + } | ||
56 … | + | ||
55 | 57 … | return node | |
56 | 58 … | } | |
57 | 59 … | ||
58 | 60 … | function appendChild (document, target, data, node) { | |
@@ -63,9 +65,9 @@ | |||
63 | 65 … | } else if (isObservable(node)) { | |
64 | 66 … | var nodes = getNodes(document, resolve(node)) | |
65 | 67 … | nodes.forEach(append, target) | |
66 | 68 … | data.targets.set(node, nodes) | |
67 | - data.releases.set(node, bind(document, node, data)) | ||
69 … | + data.bindings.push(new Binding(bind, document, node, data)) | ||
68 | 70 … | } else { | |
69 | 71 … | target.appendChild(getNode(document, node)) | |
70 | 72 … | } | |
71 | 73 … | } | |
@@ -92,16 +94,18 @@ | |||
92 | 94 … | oldNodes.filter(function (node) { | |
93 | 95 … | return !~newNodes.indexOf(node) | |
94 | 96 … | }).forEach(function (node) { | |
95 | 97 … | parent.removeChild(node) | |
98 … | + unbind(node) | ||
96 | 99 … | }) | |
97 | 100 … | if (marker) { | |
98 | 101 … | newNodes.forEach(function (node) { | |
99 | 102 … | parent.insertBefore(node, marker) | |
100 | 103 … | }) | |
101 | 104 … | } else { | |
102 | 105 … | newNodes.forEach(function (node) { | |
103 | 106 … | parent.appendChild(node) | |
107 … | + rebind(node) | ||
104 | 108 … | }) | |
105 | 109 … | } | |
106 | 110 … | } | |
107 | 111 … | ||
@@ -138,8 +142,40 @@ | |||
138 | 142 … | return [getNode(document, nodeOrNodes)] | |
139 | 143 … | } | |
140 | 144 … | } | |
141 | 145 … | ||
146 … | +function 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 … | + | ||
158 … | +function 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 … | + | ||
170 … | +function invokeBind (binding) { | ||
171 … | + binding.bind() | ||
172 … | +} | ||
173 … | + | ||
174 … | +function invokeUnbind (binding) { | ||
175 … | + binding.unbind() | ||
176 … | +} | ||
177 … | + | ||
142 | 178 … | function push (item) { | |
143 | 179 … | this.push(item) | |
144 | 180 … | } | |
145 | 181 … | ||
@@ -151,4 +187,50 @@ | |||
151 | 187 … | if (typeof func === 'function') { | |
152 | 188 … | func() | |
153 | 189 … | } | |
154 | 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 … | +function 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 … | + | ||
222 … | +Binding.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 … | +} |
lib/apply-properties.js | ||
---|---|---|
@@ -1,23 +1,18 @@ | ||
1 | 1 … | var isObservable = require('../is-observable') |
2 | 2 … | var Set = require('../set') |
3 | 3 … | var watch = require('../watch') |
4 | -var caches = new global.WeakMap() | |
5 | 4 … | |
6 | 5 … | module.exports = applyProperties |
7 | 6 … | |
8 | -function applyProperties (target, properties) { | |
9 | - var data = caches.get(target) | |
10 | - if (!data) { | |
11 | - data = { releases: [] } | |
12 | - caches.set(target, data) | |
13 | - } | |
14 | - | |
7 … | +function applyProperties (target, properties, data) { | |
15 | 8 … | var classList = Set() |
16 | 9 … | if (target.className) { |
17 | 10 … | classList.add(target.className) |
18 | 11 … | } |
19 | 12 … | |
13 … | + var hooks = null | |
14 … | + | |
20 | 15 … | for (var key in properties) { |
21 | 16 … | var valueOrObs = properties[key] |
22 | 17 … | var value = resolve(valueOrObs) |
23 | 18 … | |
@@ -28,26 +23,21 @@ | ||
28 | 23 … | var styleObs = isObservable(value[k]) ? value[k] : null |
29 | 24 … | target.style.setProperty(k, styleValue) |
30 | 25 … | |
31 | 26 … | if (styleObs) { |
32 | - data.releases.push(bindStyle(target, styleObs, k)) | |
27 … | + data.bindings.push(new Binding(bindStyle, target, styleObs, k)) | |
33 | 28 … | } |
34 | 29 … | } |
35 | 30 … | } else if (key === 'hooks') { |
36 | - value.forEach(function (v) { | |
37 | - var release = v(target) | |
38 | - if (typeof release === 'function') { | |
39 | - data.releases.push(release) | |
40 | - } | |
41 | - }) | |
31 … | + hooks = value | |
42 | 32 … | } else if (key === 'attributes') { |
43 | 33 … | for (var k in value) { |
44 | 34 … | var attrValue = resolve(value[k]) |
45 | 35 … | var attrObs = isObservable(value[k]) ? value[k] : null |
46 | 36 … | target.setAttribute(k, attrValue) |
47 | 37 … | |
48 | 38 … | if (attrObs) { |
49 | - data.releases.push(bindAttr(target, attrObs, k)) | |
39 … | + data.bindings.push(new Binding(bindAttr, target, attrObs, k)) | |
50 | 40 … | } |
51 | 41 … | } |
52 | 42 … | } else if (key === 'events') { |
53 | 43 … | for (var name in value) { |
@@ -66,9 +56,9 @@ | ||
66 | 56 … | } else { |
67 | 57 … | target[key] = value |
68 | 58 … | var obs = isObservable(valueOrObs) ? valueOrObs : null |
69 | 59 … | if (obs) { |
70 | - data.releases.push(bind(target, obs, key)) | |
60 … | + data.bindings.push(new Binding(bind, target, obs, key)) | |
71 | 61 … | } |
72 | 62 … | } |
73 | 63 … | } |
74 | 64 … | |
@@ -77,18 +67,10 @@ | ||
77 | 67 … | if (value || target.className) { |
78 | 68 … | target.className = value |
79 | 69 … | } |
80 | 70 … | }) |
81 | -} | |
82 | 71 … | |
83 | -applyProperties.destroy = function (target) { | |
84 | - var data = caches.get(target) | |
85 | - if (data) { | |
86 | - while (data.releases.length) { | |
87 | - data.releases.pop()() | |
88 | - } | |
89 | - caches.delete(target) | |
90 | - } | |
72 … | + return hooks | |
91 | 73 … | } |
92 | 74 … | |
93 | 75 … | function bindStyle (target, styleObs, key) { |
94 | 76 … | return styleObs(function (value) { |
@@ -121,4 +103,28 @@ | ||
121 | 103 … | |
122 | 104 … | function resolve (source) { |
123 | 105 … | return typeof source === 'function' ? source() : source |
124 | 106 … | } |
107 … | + | |
108 … | +function Binding (fn, element, source, key) { | |
109 … | + this.element = element | |
110 … | + this.source = source | |
111 … | + this.key = key | |
112 … | + this.fn = fn | |
113 … | + this.bind() | |
114 … | +} | |
115 … | + | |
116 … | +Binding.prototype = { | |
117 … | + bind: function () { | |
118 … | + if (!this.bound) { | |
119 … | + this._release = this.fn(this.element, this.source, this.key) | |
120 … | + this.bound = true | |
121 … | + } | |
122 … | + }, | |
123 … | + unbind: function () { | |
124 … | + if (this.bound) { | |
125 … | + this._release() | |
126 … | + this._release = null | |
127 … | + this.bound = false | |
128 … | + } | |
129 … | + } | |
130 … | +} |
Built with git-ssb-web