| 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 |
| 11 // TODO(jmesserly): not sure what kind of boolean conversion rules to | 11 // TODO(jmesserly): not sure what kind of boolean conversion rules to |
| 12 // apply for template data-binding. HTML attributes are true if they're | 12 // apply for template data-binding. HTML attributes are true if they're |
| 13 // present. However Dart only treats "true" as true. Since this is HTML we'll | 13 // present. However Dart only treats "true" as true. Since this is HTML we'll |
| 14 // use something closer to the HTML rules: null (missing) and false are false, | 14 // use something closer to the HTML rules: null (missing) and false are false, |
| 15 // everything else is true. See: https://github.com/polymer-project/mdv/issues/5
9 | 15 // everything else is true. See: https://github.com/polymer-project/mdv/issues/5
9 |
| 16 bool _toBoolean(value) => null != value && false != value; | 16 bool _toBoolean(value) => null != value && false != value; |
| 17 | 17 |
| 18 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { | 18 Node _createDeepCloneAndDecorateTemplates(Node node, BindingDelegate delegate) { |
| 19 var clone = node.clone(false); // Shallow clone. | 19 var clone = node.clone(false); // Shallow clone. |
| 20 if (clone is Element && clone.isTemplate) { | 20 if (clone is Element && clone.isTemplate) { |
| 21 TemplateElement.decorate(clone, node); | 21 TemplateElement.decorate(clone, node); |
| 22 if (syntax != null) { | 22 if (delegate != null) { |
| 23 clone.attributes.putIfAbsent('syntax', () => syntax); | 23 _mdv(clone)._bindingDelegate = delegate; |
| 24 } | 24 } |
| 25 } | 25 } |
| 26 | 26 |
| 27 for (var c = node.firstChild; c != null; c = c.nextNode) { | 27 for (var c = node.firstChild; c != null; c = c.nextNode) { |
| 28 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax)); | 28 clone.append(_createDeepCloneAndDecorateTemplates(c, delegate)); |
| 29 } | 29 } |
| 30 return clone; | 30 return clone; |
| 31 } | 31 } |
| 32 | 32 |
| 33 void _addBindings(Node node, model, [CustomBindingSyntax delegate]) { | 33 void _addBindings(Node node, model, [BindingDelegate delegate]) { |
| 34 if (node is Element) { | 34 if (node is Element) { |
| 35 _addAttributeBindings(node, model, delegate); | 35 _addAttributeBindings(node, model, delegate); |
| 36 } else if (node is Text) { | 36 } else if (node is Text) { |
| 37 _parseAndBind(node, 'text', node.text, model, delegate); | 37 _parseAndBind(node, 'text', node.text, model, delegate); |
| 38 } | 38 } |
| 39 | 39 |
| 40 for (var c = node.firstChild; c != null; c = c.nextNode) { | 40 for (var c = node.firstChild; c != null; c = c.nextNode) { |
| 41 _addBindings(c, model, delegate); | 41 _addBindings(c, model, delegate); |
| 42 } | 42 } |
| 43 } | 43 } |
| 44 | 44 |
| 45 void _addAttributeBindings(Element element, model, delegate) { | 45 void _addAttributeBindings(Element element, model, delegate) { |
| 46 element.attributes.forEach((name, value) { | 46 element.attributes.forEach((name, value) { |
| 47 if (value == '' && (name == 'bind' || name == 'repeat')) { | 47 if (value == '' && (name == 'bind' || name == 'repeat')) { |
| 48 value = '{{}}'; | 48 value = '{{}}'; |
| 49 } | 49 } |
| 50 _parseAndBind(element, name, value, model, delegate); | 50 _parseAndBind(element, name, value, model, delegate); |
| 51 }); | 51 }); |
| 52 } | 52 } |
| 53 | 53 |
| 54 void _parseAndBind(Node node, String name, String text, model, | 54 void _parseAndBind(Node node, String name, String text, model, |
| 55 CustomBindingSyntax delegate) { | 55 BindingDelegate delegate) { |
| 56 | 56 |
| 57 var tokens = _parseMustacheTokens(text); | 57 var tokens = _parseMustacheTokens(text); |
| 58 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { | 58 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { |
| 59 return; | 59 return; |
| 60 } | 60 } |
| 61 | 61 |
| 62 // If this is a custom element, give the .xtag a change to bind. | 62 // If this is a custom element, give the .xtag a change to bind. |
| 63 node = _nodeOrCustom(node); | 63 node = _nodeOrCustom(node); |
| 64 | 64 |
| 65 if (tokens.length == 1 && tokens[0].isBinding) { | 65 if (tokens.length == 1 && tokens[0].isBinding) { |
| (...skipping 24 matching lines...) Expand all Loading... |
| 90 } | 90 } |
| 91 } | 91 } |
| 92 | 92 |
| 93 return newValue.toString(); | 93 return newValue.toString(); |
| 94 }; | 94 }; |
| 95 | 95 |
| 96 node.bind(name, replacementBinding, 'value'); | 96 node.bind(name, replacementBinding, 'value'); |
| 97 } | 97 } |
| 98 | 98 |
| 99 void _bindOrDelegate(node, name, model, String path, | 99 void _bindOrDelegate(node, name, model, String path, |
| 100 CustomBindingSyntax delegate) { | 100 BindingDelegate delegate) { |
| 101 | 101 |
| 102 if (delegate != null) { | 102 if (delegate != null) { |
| 103 var delegateBinding = delegate.getBinding(model, path, name, node); | 103 var delegateBinding = delegate.getBinding(model, path, name, node); |
| 104 if (delegateBinding != null) { | 104 if (delegateBinding != null) { |
| 105 model = delegateBinding; | 105 model = delegateBinding; |
| 106 path = 'value'; | 106 path = 'value'; |
| 107 } | 107 } |
| 108 } | 108 } |
| 109 | 109 |
| 110 node.bind(name, model, path); | 110 node.bind(name, model, path); |
| (...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 263 var parent = _templateElement.parentNode; | 263 var parent = _templateElement.parentNode; |
| 264 while (terminator != previousTerminator) { | 264 while (terminator != previousTerminator) { |
| 265 var node = previousTerminator.nextNode; | 265 var node = previousTerminator.nextNode; |
| 266 if (node == terminator) terminator = previousTerminator; | 266 if (node == terminator) terminator = previousTerminator; |
| 267 node.remove(); | 267 node.remove(); |
| 268 instanceNodes.add(node); | 268 instanceNodes.add(node); |
| 269 } | 269 } |
| 270 return instanceNodes; | 270 return instanceNodes; |
| 271 } | 271 } |
| 272 | 272 |
| 273 getInstanceModel(model, String syntax) { | 273 getInstanceModel(model, BindingDelegate delegate) { |
| 274 var delegate = TemplateElement.syntax[syntax]; | |
| 275 if (delegate != null) { | 274 if (delegate != null) { |
| 276 return delegate.getInstanceModel(_templateElement, model); | 275 return delegate.getInstanceModel(_templateElement, model); |
| 277 } | 276 } |
| 278 return model; | 277 return model; |
| 279 } | 278 } |
| 280 | 279 |
| 281 List<Node> getInstanceNodes(model, String syntax, IdentityMap instanceCache) { | 280 List<Node> getInstanceNodes(model, BindingDelegate delegate, |
| 281 IdentityMap instanceCache) { |
| 282 |
| 282 var instanceNodes = instanceCache.remove(model); | 283 var instanceNodes = instanceCache.remove(model); |
| 283 if (instanceNodes != null) return instanceNodes; | 284 if (instanceNodes != null) return instanceNodes; |
| 284 | 285 |
| 285 var fragment = _templateElement.createInstance(model, syntax); | 286 var fragment = _templateElement.createInstance(model, delegate); |
| 286 instanceNodes = fragment.nodes.toList(); | 287 instanceNodes = fragment.nodes.toList(); |
| 287 fragment.nodes.clear(); | 288 fragment.nodes.clear(); |
| 288 return instanceNodes; | 289 return instanceNodes; |
| 289 } | 290 } |
| 290 | 291 |
| 291 void _handleChanges(Iterable<ChangeRecord> splices) { | 292 void _handleChanges(Iterable<ChangeRecord> splices) { |
| 292 if (closed) return; | 293 if (closed) return; |
| 293 | 294 |
| 294 splices = splices.where((s) => s is ListChangeRecord); | 295 splices = splices.where((s) => s is ListChangeRecord); |
| 295 | 296 |
| 296 var template = _templateElement; | 297 var template = _templateElement; |
| 297 var syntax = template.attributes['syntax']; | 298 var delegate = template.bindingDelegate; |
| 298 | 299 |
| 299 if (template.parentNode == null || template.document.window == null) { | 300 if (template.parentNode == null || template.document.window == null) { |
| 300 close(); | 301 close(); |
| 301 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here, | 302 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here, |
| 302 // but I think that's a no-op because only nodes are used as keys. | 303 // but I think that's a no-op because only nodes are used as keys. |
| 303 // See https://github.com/Polymer/mdv/pull/114. | 304 // See https://github.com/Polymer/mdv/pull/114. |
| 304 return; | 305 return; |
| 305 } | 306 } |
| 306 | 307 |
| 307 // TODO(jmesserly): IdentityMap matches JS semantics, but it's O(N) right | 308 // TODO(jmesserly): IdentityMap matches JS semantics, but it's O(N) right |
| 308 // now. See http://dartbug.com/4161. | 309 // now. See http://dartbug.com/4161. |
| 309 var instanceCache = new IdentityMap(); | 310 var instanceCache = new IdentityMap(); |
| 310 var removeDelta = 0; | 311 var removeDelta = 0; |
| 311 for (var splice in splices) { | 312 for (var splice in splices) { |
| 312 for (int i = 0; i < splice.removedCount; i++) { | 313 for (int i = 0; i < splice.removedCount; i++) { |
| 313 var instanceNodes = extractInstanceAt(splice.index + removeDelta); | 314 var instanceNodes = extractInstanceAt(splice.index + removeDelta); |
| 314 if (instanceNodes.length == 0) continue; | 315 if (instanceNodes.length == 0) continue; |
| 315 var model = _mdv(instanceNodes.first)._templateInstance.model; | 316 var model = _mdv(instanceNodes.first)._templateInstance.model; |
| 316 instanceCache[model] = instanceNodes; | 317 instanceCache[model] = instanceNodes; |
| 317 } | 318 } |
| 318 | 319 |
| 319 removeDelta -= splice.addedCount; | 320 removeDelta -= splice.addedCount; |
| 320 } | 321 } |
| 321 | 322 |
| 322 for (var splice in splices) { | 323 for (var splice in splices) { |
| 323 for (var addIndex = splice.index; | 324 for (var addIndex = splice.index; |
| 324 addIndex < splice.index + splice.addedCount; | 325 addIndex < splice.index + splice.addedCount; |
| 325 addIndex++) { | 326 addIndex++) { |
| 326 | 327 |
| 327 var model = getInstanceModel(iteratedValue[addIndex], syntax); | 328 var model = getInstanceModel(iteratedValue[addIndex], delegate); |
| 328 | 329 |
| 329 var instanceNodes = getInstanceNodes(model, syntax, instanceCache); | 330 var instanceNodes = getInstanceNodes(model, delegate, instanceCache); |
| 330 insertInstanceAt(addIndex, instanceNodes); | 331 insertInstanceAt(addIndex, instanceNodes); |
| 331 } | 332 } |
| 332 } | 333 } |
| 333 | 334 |
| 334 for (var instanceNodes in instanceCache.values) { | 335 for (var instanceNodes in instanceCache.values) { |
| 335 instanceNodes.forEach(_unbindAllRecursively); | 336 instanceNodes.forEach(_unbindAllRecursively); |
| 336 } | 337 } |
| 337 } | 338 } |
| 338 | 339 |
| 339 void unobserve() { | 340 void unobserve() { |
| (...skipping 22 matching lines...) Expand all Loading... |
| 362 nodeExt._templateIterator = null; | 363 nodeExt._templateIterator = null; |
| 363 } | 364 } |
| 364 } | 365 } |
| 365 | 366 |
| 366 _nodeOrCustom(node).unbindAll(); | 367 _nodeOrCustom(node).unbindAll(); |
| 367 for (var c = node.firstChild; c != null; c = c.nextNode) { | 368 for (var c = node.firstChild; c != null; c = c.nextNode) { |
| 368 _unbindAllRecursively(c); | 369 _unbindAllRecursively(c); |
| 369 } | 370 } |
| 370 } | 371 } |
| 371 } | 372 } |
| OLD | NEW |