| 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 13 matching lines...) Expand all Loading... |
| 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, delegate)); | 28 clone.append(_createDeepCloneAndDecorateTemplates(c, delegate)); |
| 29 } | 29 } |
| 30 return clone; | 30 return clone; |
| 31 } | 31 } |
| 32 | 32 |
| 33 void _addBindings(Node node, model, [BindingDelegate delegate]) { | 33 void _addBindings(Node node, model, [BindingDelegate delegate]) { |
| 34 List bindings = null; |
| 34 if (node is Element) { | 35 if (node is Element) { |
| 35 _addAttributeBindings(node, model, delegate); | 36 bindings = _parseAttributeBindings(node); |
| 36 } else if (node is Text) { | 37 } else if (node is Text) { |
| 37 _parseAndBind(node, 'text', node.text, model, delegate); | 38 var tokens = _parseMustacheTokens(node.text); |
| 39 if (tokens != null) bindings = ['text', tokens]; |
| 40 } |
| 41 |
| 42 if (bindings != null) { |
| 43 _processBindings(bindings, node, model, delegate); |
| 38 } | 44 } |
| 39 | 45 |
| 40 for (var c = node.firstChild; c != null; c = c.nextNode) { | 46 for (var c = node.firstChild; c != null; c = c.nextNode) { |
| 41 _addBindings(c, model, delegate); | 47 _addBindings(c, model, delegate); |
| 42 } | 48 } |
| 43 } | 49 } |
| 44 | 50 |
| 45 void _addAttributeBindings(Element element, model, delegate) { | 51 List _parseAttributeBindings(Element element) { |
| 46 var attrs = new LinkedHashMap.from(element.attributes); | 52 var bindings = null; |
| 47 if (element.isTemplate) { | 53 var ifFound = false; |
| 48 // Accept 'naked' bind & repeat. | 54 var bindFound = false; |
| 49 if (attrs['bind'] == '') attrs['bind'] = '{{}}'; | 55 var isTemplateNode = element.isTemplate; |
| 50 if (attrs['repeat'] == '') attrs['repeat'] = '{{}}'; | |
| 51 | 56 |
| 52 // Treat <template if="{{expr}}"> as <template bind if="{{expr}}"> | 57 element.attributes.forEach((name, value) { |
| 53 if (attrs.containsKey('if') && | 58 if (isTemplateNode) { |
| 54 !attrs.containsKey('bind') && | 59 if (name == 'if') { |
| 55 !attrs.containsKey('repeat')) { | 60 ifFound = true; |
| 56 attrs['bind'] = '{{}}'; | 61 } else if (name == 'bind' || name == 'repeat') { |
| 62 bindFound = true; |
| 63 if (value == '') value = '{{}}'; |
| 64 } |
| 57 } | 65 } |
| 66 |
| 67 var tokens = _parseMustacheTokens(value); |
| 68 if (tokens != null) { |
| 69 if (bindings == null) bindings = []; |
| 70 bindings..add(name)..add(tokens); |
| 71 } |
| 72 }); |
| 73 |
| 74 // Treat <template if> as <template bind if> |
| 75 if (ifFound && !bindFound) { |
| 76 if (bindings == null) bindings = []; |
| 77 bindings..add('bind')..add(_parseMustacheTokens('{{}}')); |
| 58 } | 78 } |
| 59 | 79 |
| 60 attrs.forEach((name, value) { | 80 return bindings; |
| 61 _parseAndBind(element, name, value, model, delegate); | |
| 62 }); | |
| 63 } | 81 } |
| 64 | 82 |
| 65 void _parseAndBind(Node node, String name, String text, model, | 83 void _processBindings(List bindings, Node node, model, |
| 66 BindingDelegate delegate) { | 84 BindingDelegate delegate) { |
| 67 | 85 |
| 68 var tokens = _parseMustacheTokens(text); | 86 for (var i = 0; i < bindings.length; i += 2) { |
| 69 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { | 87 _setupBinding(node, bindings[i], bindings[i + 1], model, delegate); |
| 70 return; | |
| 71 } | 88 } |
| 89 } |
| 90 |
| 91 void _setupBinding(Node node, String name, List tokens, model, |
| 92 BindingDelegate delegate) { |
| 72 | 93 |
| 73 // If this is a custom element, give the .xtag a change to bind. | 94 // If this is a custom element, give the .xtag a change to bind. |
| 74 node = _nodeOrCustom(node); | 95 node = _nodeOrCustom(node); |
| 75 | 96 |
| 76 if (tokens.length == 1 && tokens[0].isBinding) { | 97 if (_isSimpleBinding(tokens)) { |
| 77 _bindOrDelegate(node, name, model, tokens[0].value, delegate); | 98 _bindOrDelegate(node, name, model, tokens[1], delegate); |
| 78 return; | 99 return; |
| 79 } | 100 } |
| 80 | 101 |
| 81 var replacementBinding = new CompoundBinding(); | 102 var replacementBinding = new CompoundBinding(); |
| 82 for (var i = 0; i < tokens.length; i++) { | 103 for (var i = 1; i < tokens.length; i += 2) { |
| 83 var token = tokens[i]; | 104 // TODO(jmesserly): not sure if this index is correct. See my comment here: |
| 84 if (token.isBinding) { | 105 // https://github.com/Polymer/mdv/commit/f1af6fe683fd06eed2a7a7849f01c227db1
2cda3#L0L1035 |
| 85 _bindOrDelegate(replacementBinding, i, model, token.value, delegate); | 106 _bindOrDelegate(replacementBinding, i, model, tokens[i], delegate); |
| 86 } | |
| 87 } | 107 } |
| 88 | 108 |
| 89 replacementBinding.combinator = (values) { | 109 replacementBinding.combinator = (values) { |
| 90 var newValue = new StringBuffer(); | 110 var newValue = new StringBuffer(); |
| 91 | 111 |
| 92 for (var i = 0; i < tokens.length; i++) { | 112 for (var i = 0, text = true; i < tokens.length; i++, text = !text) { |
| 93 var token = tokens[i]; | 113 if (text) { |
| 94 if (token.isText) { | 114 newValue.write(tokens[i]); |
| 95 newValue.write(token.value); | |
| 96 } else { | 115 } else { |
| 97 var value = values[i]; | 116 var value = values[i]; |
| 98 if (value != null) { | 117 if (value != null) { |
| 99 newValue.write(value); | 118 newValue.write(value); |
| 100 } | 119 } |
| 101 } | 120 } |
| 102 } | 121 } |
| 103 | 122 |
| 104 return newValue.toString(); | 123 return newValue.toString(); |
| 105 }; | 124 }; |
| (...skipping 16 matching lines...) Expand all Loading... |
| 122 } | 141 } |
| 123 | 142 |
| 124 /** | 143 /** |
| 125 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns | 144 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns |
| 126 * the node. This is used so nodes can override [Node.bind], [Node.unbind], | 145 * the node. This is used so nodes can override [Node.bind], [Node.unbind], |
| 127 * and [Node.unbindAll] like InputElement does. | 146 * and [Node.unbindAll] like InputElement does. |
| 128 */ | 147 */ |
| 129 // TODO(jmesserly): remove this when we can extend Element for real. | 148 // TODO(jmesserly): remove this when we can extend Element for real. |
| 130 _nodeOrCustom(node) => node is Element ? node.xtag : node; | 149 _nodeOrCustom(node) => node is Element ? node.xtag : node; |
| 131 | 150 |
| 132 List<_BindingToken> _parseMustacheTokens(String s) { | 151 /** True if and only if [tokens] is of the form `['', path, '']`. */ |
| 133 var result = []; | 152 bool _isSimpleBinding(List<String> tokens) => |
| 153 tokens.length == 3 && tokens[0].isEmpty && tokens[2].isEmpty; |
| 154 |
| 155 /** |
| 156 * Parses {{ mustache }} bindings. |
| 157 * |
| 158 * Returns null if there are no matches. Otherwise returns |
| 159 * [TEXT, (PATH, TEXT)+] if there is at least one mustache. |
| 160 */ |
| 161 List<String> _parseMustacheTokens(String s) { |
| 162 if (s.isEmpty) return; |
| 163 |
| 164 var tokens = null; |
| 134 var length = s.length; | 165 var length = s.length; |
| 135 var index = 0, lastIndex = 0; | 166 var startIndex = 0, lastIndex = 0, endIndex = 0; |
| 136 while (lastIndex < length) { | 167 while (lastIndex < length) { |
| 137 index = s.indexOf('{{', lastIndex); | 168 startIndex = s.indexOf('{{', lastIndex); |
| 138 if (index < 0) { | 169 endIndex = startIndex < 0 ? -1 : s.indexOf('}}', startIndex + 2); |
| 139 result.add(new _BindingToken(s.substring(lastIndex))); | 170 |
| 171 if (endIndex < 0) { |
| 172 if (tokens == null) return null; |
| 173 |
| 174 tokens.add(s.substring(lastIndex)); |
| 140 break; | 175 break; |
| 141 } else { | 176 } |
| 142 // There is a non-empty text run before the next path token. | |
| 143 if (index > 0 && lastIndex < index) { | |
| 144 result.add(new _BindingToken(s.substring(lastIndex, index))); | |
| 145 } | |
| 146 lastIndex = index + 2; | |
| 147 index = s.indexOf('}}', lastIndex); | |
| 148 if (index < 0) { | |
| 149 var text = s.substring(lastIndex - 2); | |
| 150 if (result.length > 0 && result.last.isText) { | |
| 151 result.last.value += text; | |
| 152 } else { | |
| 153 result.add(new _BindingToken(text)); | |
| 154 } | |
| 155 break; | |
| 156 } | |
| 157 | 177 |
| 158 var value = s.substring(lastIndex, index).trim(); | 178 if (tokens == null) tokens = <String>[]; |
| 159 result.add(new _BindingToken(value, isBinding: true)); | 179 tokens.add(s.substring(lastIndex, startIndex)); // TEXT |
| 160 lastIndex = index + 2; | 180 tokens.add(s.substring(startIndex + 2, endIndex).trim()); // PATH |
| 161 } | 181 lastIndex = endIndex + 2; |
| 162 } | 182 } |
| 163 return result; | 183 |
| 184 if (lastIndex == length) tokens.add(''); |
| 185 return tokens; |
| 164 } | 186 } |
| 165 | 187 |
| 166 void _addTemplateInstanceRecord(fragment, model) { | 188 void _addTemplateInstanceRecord(fragment, model) { |
| 167 if (fragment.firstChild == null) { | 189 if (fragment.firstChild == null) { |
| 168 return; | 190 return; |
| 169 } | 191 } |
| 170 | 192 |
| 171 var instanceRecord = new TemplateInstance( | 193 var instanceRecord = new TemplateInstance( |
| 172 fragment.firstChild, fragment.lastChild, model); | 194 fragment.firstChild, fragment.lastChild, model); |
| 173 | 195 |
| 174 var node = instanceRecord.firstNode; | 196 var node = instanceRecord.firstNode; |
| 175 while (node != null) { | 197 while (node != null) { |
| 176 _mdv(node)._templateInstance = instanceRecord; | 198 _mdv(node)._templateInstance = instanceRecord; |
| 177 node = node.nextNode; | 199 node = node.nextNode; |
| 178 } | 200 } |
| 179 } | 201 } |
| 180 | 202 |
| 181 class _BindingToken { | |
| 182 final String value; | |
| 183 final bool isBinding; | |
| 184 | |
| 185 _BindingToken(this.value, {this.isBinding: false}); | |
| 186 | |
| 187 bool get isText => !isBinding; | |
| 188 } | |
| 189 | 203 |
| 190 class _TemplateIterator { | 204 class _TemplateIterator { |
| 191 final Element _templateElement; | 205 final Element _templateElement; |
| 192 final List<Node> terminators = []; | 206 final List<Node> terminators = []; |
| 193 final CompoundBinding inputs; | 207 final CompoundBinding inputs; |
| 194 List iteratedValue; | 208 List iteratedValue; |
| 195 Object _lastValue; | 209 Object _lastValue; |
| 196 | 210 |
| 197 StreamSubscription _sub; | 211 StreamSubscription _sub; |
| 198 StreamSubscription _valueBinding; | 212 StreamSubscription _valueBinding; |
| (...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 386 nodeExt._templateIterator = null; | 400 nodeExt._templateIterator = null; |
| 387 } | 401 } |
| 388 } | 402 } |
| 389 | 403 |
| 390 _nodeOrCustom(node).unbindAll(); | 404 _nodeOrCustom(node).unbindAll(); |
| 391 for (var c = node.firstChild; c != null; c = c.nextNode) { | 405 for (var c = node.firstChild; c != null; c = c.nextNode) { |
| 392 _unbindAllRecursively(c); | 406 _unbindAllRecursively(c); |
| 393 } | 407 } |
| 394 } | 408 } |
| 395 } | 409 } |
| OLD | NEW |