Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of mdv; | 5 part of mdv; |
| 6 | 6 |
| 7 // This code is a port of Model-Driven-Views: | 7 // This code is a port of Model-Driven-Views: |
| 8 // https://github.com/polymer-project/mdv | 8 // https://github.com/polymer-project/mdv |
| 9 // The code mostly comes from src/template_element.js | 9 // The code mostly comes from src/template_element.js |
| 10 | 10 |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 74 | 74 |
| 75 void updateBinding(e) { | 75 void updateBinding(e) { |
| 76 binding.value = element.value; | 76 binding.value = element.value; |
| 77 } | 77 } |
| 78 } | 78 } |
| 79 | 79 |
| 80 class _CheckedBinding extends _InputBinding { | 80 class _CheckedBinding extends _InputBinding { |
| 81 _CheckedBinding(element, model, path) : super(element, model, path); | 81 _CheckedBinding(element, model, path) : super(element, model, path); |
| 82 | 82 |
| 83 void valueChanged(value) { | 83 void valueChanged(value) { |
| 84 element.checked = _Bindings._toBoolean(value); | 84 element.checked = _toBoolean(value); |
| 85 } | 85 } |
| 86 | 86 |
| 87 void updateBinding(e) { | 87 void updateBinding(e) { |
| 88 binding.value = element.checked; | 88 binding.value = element.checked; |
| 89 | 89 |
| 90 // Only the radio button that is getting checked gets an event. We | 90 // Only the radio button that is getting checked gets an event. We |
| 91 // therefore find all the associated radio buttons and update their | 91 // therefore find all the associated radio buttons and update their |
| 92 // CheckedBinding manually. | 92 // CheckedBinding manually. |
| 93 if (element is InputElement && element.type == 'radio') { | 93 if (element is InputElement && element.type == 'radio') { |
| 94 for (var r in _getAssociatedRadioButtons(element)) { | 94 for (var r in _getAssociatedRadioButtons(element)) { |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 129 // TODO(jmesserly): polyfill document.contains API instead of doing it here | 129 // TODO(jmesserly): polyfill document.contains API instead of doing it here |
| 130 static bool _isNodeInDocument(Node node) { | 130 static bool _isNodeInDocument(Node node) { |
| 131 // On non-IE this works: | 131 // On non-IE this works: |
| 132 // return node.document.contains(node); | 132 // return node.document.contains(node); |
| 133 var document = node.document; | 133 var document = node.document; |
| 134 if (node == document || node.parentNode == document) return true; | 134 if (node == document || node.parentNode == document) return true; |
| 135 return document.documentElement.contains(node); | 135 return document.documentElement.contains(node); |
| 136 } | 136 } |
| 137 } | 137 } |
| 138 | 138 |
| 139 class _Bindings { | 139 // TODO(jmesserly): not sure what kind of boolean conversion rules to |
|
Jennifer Messerly
2013/06/28 01:11:05
this is simply removing the static class, as sugge
| |
| 140 // TODO(jmesserly): not sure what kind of boolean conversion rules to | 140 // apply for template data-binding. HTML attributes are true if they're |
| 141 // apply for template data-binding. HTML attributes are true if they're | 141 // present. However Dart only treats "true" as true. Since this is HTML we'll |
| 142 // present. However Dart only treats "true" as true. Since this is HTML we'll | 142 // use something closer to the HTML rules: null (missing) and false are false, |
| 143 // use something closer to the HTML rules: null (missing) and false are false, | 143 // everything else is true. See: https://github.com/polymer-project/mdv/issues/5 9 |
| 144 // everything else is true. See: https://github.com/polymer-project/mdv/issues /59 | 144 bool _toBoolean(value) => null != value && false != value; |
| 145 static bool _toBoolean(value) => null != value && false != value; | |
| 146 | 145 |
| 147 static Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { | 146 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { |
| 148 var clone = node.clone(false); // Shallow clone. | 147 var clone = node.clone(false); // Shallow clone. |
| 149 if (clone is Element && clone.isTemplate) { | 148 if (clone is Element && clone.isTemplate) { |
| 150 TemplateElement.decorate(clone, node); | 149 TemplateElement.decorate(clone, node); |
| 151 if (syntax != null) { | 150 if (syntax != null) { |
| 152 clone.attributes.putIfAbsent('syntax', () => syntax); | 151 clone.attributes.putIfAbsent('syntax', () => syntax); |
| 152 } | |
| 153 } | |
| 154 | |
| 155 for (var c = node.firstChild; c != null; c = c.nextNode) { | |
| 156 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax)); | |
| 157 } | |
| 158 return clone; | |
| 159 } | |
| 160 | |
| 161 void _addBindings(Node node, model, [CustomBindingSyntax delegate]) { | |
| 162 if (node is Element) { | |
| 163 _addAttributeBindings(node, model, delegate); | |
| 164 } else if (node is Text) { | |
| 165 _parseAndBind(node, 'text', node.text, model, delegate); | |
| 166 } | |
| 167 | |
| 168 for (var c = node.firstChild; c != null; c = c.nextNode) { | |
| 169 _addBindings(c, model, delegate); | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 void _addAttributeBindings(Element element, model, delegate) { | |
| 174 element.attributes.forEach((name, value) { | |
| 175 if (value == '' && (name == 'bind' || name == 'repeat')) { | |
| 176 value = '{{}}'; | |
| 177 } | |
| 178 _parseAndBind(element, name, value, model, delegate); | |
| 179 }); | |
| 180 } | |
| 181 | |
| 182 void _parseAndBind(Node node, String name, String text, model, | |
| 183 CustomBindingSyntax delegate) { | |
| 184 | |
| 185 var tokens = _parseMustacheTokens(text); | |
| 186 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { | |
| 187 return; | |
| 188 } | |
| 189 | |
| 190 // If this is a custom element, give the .xtag a change to bind. | |
| 191 node = _nodeOrCustom(node); | |
| 192 | |
| 193 if (tokens.length == 1 && tokens[0].isBinding) { | |
| 194 _bindOrDelegate(node, name, model, tokens[0].value, delegate); | |
| 195 return; | |
| 196 } | |
| 197 | |
| 198 var replacementBinding = new CompoundBinding(); | |
| 199 for (var i = 0; i < tokens.length; i++) { | |
| 200 var token = tokens[i]; | |
| 201 if (token.isBinding) { | |
| 202 _bindOrDelegate(replacementBinding, i, model, token.value, delegate); | |
| 203 } | |
| 204 } | |
| 205 | |
| 206 replacementBinding.combinator = (values) { | |
| 207 var newValue = new StringBuffer(); | |
| 208 | |
| 209 for (var i = 0; i < tokens.length; i++) { | |
| 210 var token = tokens[i]; | |
| 211 if (token.isText) { | |
| 212 newValue.write(token.value); | |
| 213 } else { | |
| 214 var value = values[i]; | |
| 215 if (value != null) { | |
| 216 newValue.write(value); | |
| 217 } | |
| 153 } | 218 } |
| 154 } | 219 } |
| 155 | 220 |
| 156 for (var c = node.firstChild; c != null; c = c.nextNode) { | 221 return newValue.toString(); |
| 157 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax)); | 222 }; |
| 158 } | |
| 159 return clone; | |
| 160 } | |
| 161 | 223 |
| 162 static void _addBindings(Node node, model, [CustomBindingSyntax syntax]) { | 224 node.bind(name, replacementBinding, 'value'); |
| 163 if (node is Element) { | 225 } |
| 164 _addAttributeBindings(node, model, syntax); | |
| 165 } else if (node is Text) { | |
| 166 _parseAndBind(node, 'text', node.text, model, syntax); | |
| 167 } | |
| 168 | 226 |
| 169 for (var c = node.firstChild; c != null; c = c.nextNode) { | 227 void _bindOrDelegate(node, name, model, String path, |
| 170 _addBindings(c, model, syntax); | 228 CustomBindingSyntax delegate) { |
| 229 | |
| 230 if (delegate != null) { | |
| 231 var delegateBinding = delegate.getBinding(model, path, name, node); | |
| 232 if (delegateBinding != null) { | |
| 233 model = delegateBinding; | |
| 234 path = 'value'; | |
| 171 } | 235 } |
| 172 } | 236 } |
| 173 | 237 |
| 174 static void _addAttributeBindings(Element element, model, syntax) { | 238 node.bind(name, model, path); |
| 175 element.attributes.forEach((name, value) { | 239 } |
| 176 if (value == '' && (name == 'bind' || name == 'repeat')) { | 240 |
| 177 value = '{{}}'; | 241 /** |
| 242 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns | |
| 243 * the node. This is used so nodes can override [Node.bind], [Node.unbind], | |
| 244 * and [Node.unbindAll] like InputElement does. | |
| 245 */ | |
| 246 // TODO(jmesserly): remove this when we can extend Element for real. | |
| 247 _nodeOrCustom(node) => node is Element ? node.xtag : node; | |
| 248 | |
| 249 List<_BindingToken> _parseMustacheTokens(String s) { | |
| 250 var result = []; | |
| 251 var length = s.length; | |
| 252 var index = 0, lastIndex = 0; | |
| 253 while (lastIndex < length) { | |
| 254 index = s.indexOf('{{', lastIndex); | |
| 255 if (index < 0) { | |
| 256 result.add(new _BindingToken(s.substring(lastIndex))); | |
| 257 break; | |
| 258 } else { | |
| 259 // There is a non-empty text run before the next path token. | |
| 260 if (index > 0 && lastIndex < index) { | |
| 261 result.add(new _BindingToken(s.substring(lastIndex, index))); | |
| 178 } | 262 } |
| 179 _parseAndBind(element, name, value, model, syntax); | 263 lastIndex = index + 2; |
| 180 }); | 264 index = s.indexOf('}}', lastIndex); |
| 265 if (index < 0) { | |
| 266 var text = s.substring(lastIndex - 2); | |
| 267 if (result.length > 0 && result.last.isText) { | |
| 268 result.last.value += text; | |
| 269 } else { | |
| 270 result.add(new _BindingToken(text)); | |
| 271 } | |
| 272 break; | |
| 273 } | |
| 274 | |
| 275 var value = s.substring(lastIndex, index).trim(); | |
| 276 result.add(new _BindingToken(value, isBinding: true)); | |
| 277 lastIndex = index + 2; | |
| 278 } | |
| 279 } | |
| 280 return result; | |
| 281 } | |
| 282 | |
| 283 void _addTemplateInstanceRecord(fragment, model) { | |
| 284 if (fragment.firstChild == null) { | |
| 285 return; | |
| 181 } | 286 } |
| 182 | 287 |
| 183 static void _parseAndBind(Node node, String name, String text, model, | 288 var instanceRecord = new TemplateInstance( |
| 184 CustomBindingSyntax syntax) { | 289 fragment.firstChild, fragment.lastChild, model); |
| 185 | 290 |
| 186 var tokens = _parseMustacheTokens(text); | 291 var node = instanceRecord.firstNode; |
| 187 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { | 292 while (node != null) { |
| 188 return; | 293 _mdv(node)._templateInstance = instanceRecord; |
| 189 } | 294 node = node.nextNode; |
| 190 | |
| 191 // If this is a custom element, give the .xtag a change to bind. | |
| 192 node = _nodeOrCustom(node); | |
| 193 | |
| 194 if (tokens.length == 1 && tokens[0].isBinding) { | |
| 195 _bindOrDelegate(node, name, model, tokens[0].value, syntax); | |
| 196 return; | |
| 197 } | |
| 198 | |
| 199 var replacementBinding = new CompoundBinding(); | |
| 200 for (var i = 0; i < tokens.length; i++) { | |
| 201 var token = tokens[i]; | |
| 202 if (token.isBinding) { | |
| 203 _bindOrDelegate(replacementBinding, i, model, token.value, syntax); | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 replacementBinding.combinator = (values) { | |
| 208 var newValue = new StringBuffer(); | |
| 209 | |
| 210 for (var i = 0; i < tokens.length; i++) { | |
| 211 var token = tokens[i]; | |
| 212 if (token.isText) { | |
| 213 newValue.write(token.value); | |
| 214 } else { | |
| 215 var value = values[i]; | |
| 216 if (value != null) { | |
| 217 newValue.write(value); | |
| 218 } | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 return newValue.toString(); | |
| 223 }; | |
| 224 | |
| 225 node.bind(name, replacementBinding, 'value'); | |
| 226 } | |
| 227 | |
| 228 static void _bindOrDelegate(node, name, model, String path, | |
| 229 CustomBindingSyntax syntax) { | |
| 230 | |
| 231 if (syntax != null) { | |
| 232 var delegateBinding = syntax.getBinding(model, path, name, node); | |
| 233 if (delegateBinding != null) { | |
| 234 model = delegateBinding; | |
| 235 path = 'value'; | |
| 236 } | |
| 237 } | |
| 238 | |
| 239 node.bind(name, model, path); | |
| 240 } | |
| 241 | |
| 242 /** | |
| 243 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns | |
| 244 * the node. This is used so nodes can override [Node.bind], [Node.unbind], | |
| 245 * and [Node.unbindAll] like InputElement does. | |
| 246 */ | |
| 247 // TODO(jmesserly): remove this when we can extend Element for real. | |
| 248 static _nodeOrCustom(node) => node is Element ? node.xtag : node; | |
| 249 | |
| 250 static List<_BindingToken> _parseMustacheTokens(String s) { | |
| 251 var result = []; | |
| 252 var length = s.length; | |
| 253 var index = 0, lastIndex = 0; | |
| 254 while (lastIndex < length) { | |
| 255 index = s.indexOf('{{', lastIndex); | |
| 256 if (index < 0) { | |
| 257 result.add(new _BindingToken(s.substring(lastIndex))); | |
| 258 break; | |
| 259 } else { | |
| 260 // There is a non-empty text run before the next path token. | |
| 261 if (index > 0 && lastIndex < index) { | |
| 262 result.add(new _BindingToken(s.substring(lastIndex, index))); | |
| 263 } | |
| 264 lastIndex = index + 2; | |
| 265 index = s.indexOf('}}', lastIndex); | |
| 266 if (index < 0) { | |
| 267 var text = s.substring(lastIndex - 2); | |
| 268 if (result.length > 0 && result.last.isText) { | |
| 269 result.last.value += text; | |
| 270 } else { | |
| 271 result.add(new _BindingToken(text)); | |
| 272 } | |
| 273 break; | |
| 274 } | |
| 275 | |
| 276 var value = s.substring(lastIndex, index).trim(); | |
| 277 result.add(new _BindingToken(value, isBinding: true)); | |
| 278 lastIndex = index + 2; | |
| 279 } | |
| 280 } | |
| 281 return result; | |
| 282 } | |
| 283 | |
| 284 static void _addTemplateInstanceRecord(fragment, model) { | |
| 285 if (fragment.firstChild == null) { | |
| 286 return; | |
| 287 } | |
| 288 | |
| 289 var instanceRecord = new TemplateInstance( | |
| 290 fragment.firstChild, fragment.lastChild, model); | |
| 291 | |
| 292 var node = instanceRecord.firstNode; | |
| 293 while (node != null) { | |
| 294 _mdv(node)._templateInstance = instanceRecord; | |
| 295 node = node.nextNode; | |
| 296 } | |
| 297 } | 295 } |
| 298 } | 296 } |
| 299 | 297 |
| 300 class _BindingToken { | 298 class _BindingToken { |
| 301 final String value; | 299 final String value; |
| 302 final bool isBinding; | 300 final bool isBinding; |
| 303 | 301 |
| 304 _BindingToken(this.value, {this.isBinding: false}); | 302 _BindingToken(this.value, {this.isBinding: false}); |
| 305 | 303 |
| 306 bool get isText => !isBinding; | 304 bool get isText => !isBinding; |
| 307 } | 305 } |
| 308 | 306 |
| 309 class _TemplateIterator { | 307 class _TemplateIterator { |
| 310 final Element _templateElement; | 308 final Element _templateElement; |
| 311 final List<Node> terminators = []; | 309 final List<Node> terminators = []; |
| 312 final CompoundBinding inputs; | 310 final CompoundBinding inputs; |
| 313 List iteratedValue; | 311 List iteratedValue; |
| 314 Object _lastValue; | 312 Object _lastValue; |
| 315 | 313 |
| 316 StreamSubscription _sub; | 314 StreamSubscription _sub; |
| 317 StreamSubscription _valueBinding; | 315 StreamSubscription _valueBinding; |
| 318 | 316 |
| 319 _TemplateIterator(this._templateElement) | 317 _TemplateIterator(this._templateElement) |
| 320 : inputs = new CompoundBinding(resolveInputs) { | 318 : inputs = new CompoundBinding(resolveInputs) { |
| 321 | 319 |
| 322 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); | 320 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); |
| 323 } | 321 } |
| 324 | 322 |
| 325 static Object resolveInputs(Map values) { | 323 static Object resolveInputs(Map values) { |
| 326 if (values.containsKey('if') && !_Bindings._toBoolean(values['if'])) { | 324 if (values.containsKey('if') && !_toBoolean(values['if'])) { |
| 327 return null; | 325 return null; |
| 328 } | 326 } |
| 329 | 327 |
| 330 if (values.containsKey('repeat')) { | 328 if (values.containsKey('repeat')) { |
| 331 return values['repeat']; | 329 return values['repeat']; |
| 332 } | 330 } |
| 333 | 331 |
| 334 if (values.containsKey('bind')) { | 332 if (values.containsKey('bind')) { |
| 335 return [values['bind']]; | 333 return [values['bind']]; |
| 336 } | 334 } |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 398 var parent = _templateElement.parentNode; | 396 var parent = _templateElement.parentNode; |
| 399 while (terminator != previousTerminator) { | 397 while (terminator != previousTerminator) { |
| 400 var node = terminator; | 398 var node = terminator; |
| 401 terminator = node.previousNode; | 399 terminator = node.previousNode; |
| 402 node.remove(); | 400 node.remove(); |
| 403 instanceNodes.add(node); | 401 instanceNodes.add(node); |
| 404 } | 402 } |
| 405 return instanceNodes; | 403 return instanceNodes; |
| 406 } | 404 } |
| 407 | 405 |
| 408 getInstanceModel(model, syntax) { | 406 getInstanceModel(model, String syntax) { |
| 409 if (syntax != null) { | 407 var delegate = TemplateElement.syntax[syntax]; |
| 410 return syntax.getInstanceModel(_templateElement, model); | 408 if (delegate != null) { |
| 409 return delegate.getInstanceModel(_templateElement, model); | |
| 411 } | 410 } |
| 412 return model; | 411 return model; |
| 413 } | 412 } |
| 414 | 413 |
| 415 Node getInstanceFragment(syntax) { | 414 List<Node> getInstanceNodes(model, String syntax, IdentityMap instanceCache) { |
| 416 if (syntax != null) { | 415 var instanceNodes = instanceCache.remove(model); |
| 417 return syntax.getInstanceFragment(_templateElement); | 416 if (instanceNodes != null) return instanceNodes; |
| 418 } | |
| 419 return _templateElement.createInstance(); | |
| 420 } | |
| 421 | 417 |
| 422 List<Node> getInstanceNodes(model, syntax) { | 418 var fragment = _templateElement.createInstance(model, syntax); |
| 423 // TODO(jmesserly): this line of code is jumping ahead of what MDV supports. | 419 instanceNodes = fragment.nodes.toList(); |
| 424 // It doesn't let the custom syntax override createInstance(). | |
| 425 var fragment = getInstanceFragment(syntax); | |
| 426 | |
| 427 _Bindings._addBindings(fragment, model, syntax); | |
| 428 _Bindings._addTemplateInstanceRecord(fragment, model); | |
| 429 | |
| 430 var instanceNodes = fragment.nodes.toList(); | |
| 431 fragment.nodes.clear(); | 420 fragment.nodes.clear(); |
| 432 return instanceNodes; | 421 return instanceNodes; |
| 433 } | 422 } |
| 434 | 423 |
| 435 void _handleChanges(Iterable<ChangeRecord> splices) { | 424 void _handleChanges(Iterable<ChangeRecord> splices) { |
| 436 splices = splices.where((s) => s is ListChangeRecord); | 425 splices = splices.where((s) => s is ListChangeRecord); |
| 437 | 426 |
| 438 var template = _templateElement; | 427 var template = _templateElement; |
| 439 var syntax = TemplateElement.syntax[template.attributes['syntax']]; | 428 var syntax = template.attributes['syntax']; |
| 440 | 429 |
| 441 if (template.parentNode == null || template.document.window == null) { | 430 if (template.parentNode == null || template.document.window == null) { |
| 442 abandon(); | 431 abandon(); |
| 443 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here, | 432 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here, |
| 444 // but I think that's a no-op because only nodes are used as keys. | 433 // but I think that's a no-op because only nodes are used as keys. |
| 445 // See https://github.com/Polymer/mdv/pull/114. | 434 // See https://github.com/Polymer/mdv/pull/114. |
| 446 return; | 435 return; |
| 447 } | 436 } |
| 448 | 437 |
| 449 // TODO(jmesserly): IdentityMap matches JS semantics, but it's O(N) right | 438 // TODO(jmesserly): IdentityMap matches JS semantics, but it's O(N) right |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 460 removeDelta -= splice.addedCount; | 449 removeDelta -= splice.addedCount; |
| 461 } | 450 } |
| 462 | 451 |
| 463 for (var splice in splices) { | 452 for (var splice in splices) { |
| 464 for (var addIndex = splice.index; | 453 for (var addIndex = splice.index; |
| 465 addIndex < splice.index + splice.addedCount; | 454 addIndex < splice.index + splice.addedCount; |
| 466 addIndex++) { | 455 addIndex++) { |
| 467 | 456 |
| 468 var model = getInstanceModel(iteratedValue[addIndex], syntax); | 457 var model = getInstanceModel(iteratedValue[addIndex], syntax); |
| 469 | 458 |
| 470 var instanceNodes = instanceCache.remove(model); | 459 var instanceNodes = getInstanceNodes(model, syntax, instanceCache); |
| 471 if (instanceNodes == null) { | |
| 472 instanceNodes = getInstanceNodes(model, syntax); | |
| 473 } | |
| 474 insertInstanceAt(addIndex, instanceNodes); | 460 insertInstanceAt(addIndex, instanceNodes); |
| 475 } | 461 } |
| 476 } | 462 } |
| 477 | 463 |
| 478 for (var instanceNodes in instanceCache.values) { | 464 for (var instanceNodes in instanceCache.values) { |
| 479 instanceNodes.forEach(_unbindAllRecursively); | 465 instanceNodes.forEach(_unbindAllRecursively); |
| 480 } | 466 } |
| 481 } | 467 } |
| 482 | 468 |
| 483 void unobserve() { | 469 void unobserve() { |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 498 nodeExt._templateInstance = null; | 484 nodeExt._templateInstance = null; |
| 499 if (node is Element && (node as Element).isTemplate) { | 485 if (node is Element && (node as Element).isTemplate) { |
| 500 // Make sure we stop observing when we remove an element. | 486 // Make sure we stop observing when we remove an element. |
| 501 var templateIterator = nodeExt._templateIterator; | 487 var templateIterator = nodeExt._templateIterator; |
| 502 if (templateIterator != null) { | 488 if (templateIterator != null) { |
| 503 templateIterator.abandon(); | 489 templateIterator.abandon(); |
| 504 nodeExt._templateIterator = null; | 490 nodeExt._templateIterator = null; |
| 505 } | 491 } |
| 506 } | 492 } |
| 507 | 493 |
| 508 _Bindings._nodeOrCustom(node).unbindAll(); | 494 _nodeOrCustom(node).unbindAll(); |
| 509 for (var c = node.firstChild; c != null; c = c.nextNode) { | 495 for (var c = node.firstChild; c != null; c = c.nextNode) { |
| 510 _unbindAllRecursively(c); | 496 _unbindAllRecursively(c); |
| 511 } | 497 } |
| 512 } | 498 } |
| 513 } | 499 } |
| OLD | NEW |