| 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 /** | 5 /** |
| 6 * Part of the template compilation that concerns with extracting information | 6 * Part of the template compilation that concerns with extracting information |
| 7 * from the HTML parse tree. | 7 * from the HTML parse tree. |
| 8 */ | 8 */ |
| 9 library analyzer; | 9 library analyzer; |
| 10 | 10 |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 96 var info = null; | 96 var info = null; |
| 97 if (node.tagName == 'script') { | 97 if (node.tagName == 'script') { |
| 98 // We already extracted script tags in previous phase. | 98 // We already extracted script tags in previous phase. |
| 99 return; | 99 return; |
| 100 } | 100 } |
| 101 | 101 |
| 102 if (node.tagName == 'template' | 102 if (node.tagName == 'template' |
| 103 || node.attributes.containsKey('template') | 103 || node.attributes.containsKey('template') |
| 104 || node.attributes.containsKey('if') | 104 || node.attributes.containsKey('if') |
| 105 || node.attributes.containsKey('instantiate') | 105 || node.attributes.containsKey('instantiate') |
| 106 || node.attributes.containsKey('iterate')) { | 106 || node.attributes.containsKey('iterate') |
| 107 || node.attributes.containsKey('repeat')) { |
| 107 // template tags, conditionals and iteration are handled specially. | 108 // template tags, conditionals and iteration are handled specially. |
| 108 info = _createTemplateInfo(node); | 109 info = _createTemplateInfo(node); |
| 109 } | 110 } |
| 110 | 111 |
| 111 // TODO(jmesserly): it would be nice not to create infos for text or | 112 // TODO(jmesserly): it would be nice not to create infos for text or |
| 112 // elements that don't need data binding. Ideally, we would visit our | 113 // elements that don't need data binding. Ideally, we would visit our |
| 113 // child nodes and get their infos, and if any of them need data binding, | 114 // child nodes and get their infos, and if any of them need data binding, |
| 114 // we create an ElementInfo for ourselves and return it, otherwise we just | 115 // we create an ElementInfo for ourselves and return it, otherwise we just |
| 115 // return null. | 116 // return null. |
| 116 if (info == null) { | 117 if (info == null) { |
| (...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 277 _currentInfo.usedComponents[component] = true; | 278 _currentInfo.usedComponents[component] = true; |
| 278 return component; | 279 return component; |
| 279 } | 280 } |
| 280 return null; | 281 return null; |
| 281 } | 282 } |
| 282 | 283 |
| 283 TemplateInfo _createTemplateInfo(Element node) { | 284 TemplateInfo _createTemplateInfo(Element node) { |
| 284 if (node.tagName != 'template' && | 285 if (node.tagName != 'template' && |
| 285 !node.attributes.containsKey('template')) { | 286 !node.attributes.containsKey('template')) { |
| 286 _messages.warning('template attribute is required when using if, ' | 287 _messages.warning('template attribute is required when using if, ' |
| 287 'instantiate, or iterate attributes.', | 288 'instantiate, repeat, or iterate attributes.', |
| 288 node.sourceSpan); | 289 node.sourceSpan); |
| 289 } | 290 } |
| 290 | 291 |
| 291 var instantiate = node.attributes['instantiate']; | 292 var instantiate = node.attributes['instantiate']; |
| 292 var condition = node.attributes['if']; | 293 var condition = node.attributes['if']; |
| 293 if (instantiate != null) { | 294 if (instantiate != null) { |
| 294 if (instantiate.startsWith('if ')) { | 295 if (instantiate.startsWith('if ')) { |
| 295 if (condition != null) { | 296 if (condition != null) { |
| 296 _messages.warning( | 297 _messages.warning( |
| 297 'another condition was already defined on this element.', | 298 'another condition was already defined on this element.', |
| 298 node.sourceSpan); | 299 node.sourceSpan); |
| 299 } else { | 300 } else { |
| 300 condition = instantiate.substring(3); | 301 condition = instantiate.substring(3); |
| 301 } | 302 } |
| 302 } | 303 } |
| 303 } | 304 } |
| 305 |
| 306 // TODO(jmesserly): deprecate iterate. |
| 304 var iterate = node.attributes['iterate']; | 307 var iterate = node.attributes['iterate']; |
| 308 var repeat = node.attributes['repeat']; |
| 309 if (repeat == null) { |
| 310 repeat = iterate; |
| 311 } else if (iterate != null) { |
| 312 _messages.warning('template cannot have both iterate and repeat. ' |
| 313 'iterate attribute will be ignored.', node.sourceSpan); |
| 314 iterate = null; |
| 315 } |
| 305 | 316 |
| 306 // Note: we issue warnings instead of errors because the spirit of HTML and | 317 // Note: we issue warnings instead of errors because the spirit of HTML and |
| 307 // Dart is to be forgiving. | 318 // Dart is to be forgiving. |
| 308 if (condition != null && iterate != null) { | 319 if (condition != null && repeat != null) { |
| 309 _messages.warning('template cannot have both iteration and conditional ' | 320 _messages.warning('template cannot have both iteration and conditional ' |
| 310 'attributes', node.sourceSpan); | 321 'attributes', node.sourceSpan); |
| 311 return null; | 322 return null; |
| 312 } | 323 } |
| 313 | 324 |
| 314 if (node.parent != null && node.parent.tagName == 'element' && | 325 if (node.parent != null && node.parent.tagName == 'element' && |
| 315 (condition != null || iterate != null)) { | 326 (condition != null || repeat != null)) { |
| 316 | 327 |
| 317 // TODO(jmesserly): would be cool if we could just refactor this, or offer | 328 // TODO(jmesserly): would be cool if we could just refactor this, or offer |
| 318 // a quick fix in the Editor. | 329 // a quick fix in the Editor. |
| 319 var example = new Element.html('<element><template><template>'); | 330 var example = new Element.html('<element><template><template>'); |
| 320 node.parent.attributes.forEach((k, v) { example.attributes[k] = v; }); | 331 node.parent.attributes.forEach((k, v) { example.attributes[k] = v; }); |
| 321 var nestedTemplate = example.nodes.first.nodes.first; | 332 var nestedTemplate = example.nodes.first.nodes.first; |
| 322 node.attributes.forEach((k, v) { nestedTemplate.attributes[k] = v; }); | 333 node.attributes.forEach((k, v) { nestedTemplate.attributes[k] = v; }); |
| 323 | 334 |
| 324 _messages.warning('the <template> of a custom element does not support ' | 335 _messages.warning('the <template> of a custom element does not support ' |
| 325 '"if" or "iterate". However, you can create another template node ' | 336 '"if", "iterate" or "repeat". However, you can create another ' |
| 326 'that is a child node, for example:\n' | 337 'template node that is a child node, for example:\n' |
| 327 '${example.outerHtml}', | 338 '${example.outerHtml}', |
| 328 node.parent.sourceSpan); | 339 node.parent.sourceSpan); |
| 329 return null; | 340 return null; |
| 330 } | 341 } |
| 331 | 342 |
| 332 if (condition != null) { | 343 if (condition != null) { |
| 333 var result = new TemplateInfo(node, _parent, ifCondition: condition); | 344 var result = new TemplateInfo(node, _parent, ifCondition: condition); |
| 334 result.removeAttributes.add('if'); | 345 result.removeAttributes.add('if'); |
| 335 result.removeAttributes.add('instantiate'); | 346 result.removeAttributes.add('instantiate'); |
| 336 if (node.tagName == 'template') { | 347 if (node.tagName == 'template') { |
| 337 return node.nodes.length > 0 ? result : null; | 348 return node.nodes.length > 0 ? result : null; |
| 338 } | 349 } |
| 339 | 350 |
| 340 result.removeAttributes.add('template'); | 351 _createTemplateAttributePlaceholder(node, result); |
| 352 return result; |
| 341 | 353 |
| 342 // TODO(jmesserly): if-conditions in attributes require injecting a | 354 } else if (repeat != null) { |
| 343 // placeholder node, and a real node which is a clone. We should | 355 var match = new RegExp(r"(.*) in (.*)").firstMatch(repeat); |
| 344 // consider a design where we show/hide the node instead (with care | 356 if (match == null) { |
| 345 // taken not to evaluate hidden bindings). That is more along the lines | 357 _messages.warning('template iterate/repeat must be of the form: ' |
| 346 // of AngularJS, and would have a cleaner DOM. See issue #142. | 358 'repeat="variable in list", where "variable" is your variable name ' |
| 347 var contentNode = node.clone(); | 359 'and "list" is the list of items.', |
| 348 // Clear out the original attributes. This is nice to have, but | 360 node.sourceSpan); |
| 349 // necessary for ID because of issue #141. | 361 return null; |
| 350 node.attributes.clear(); | 362 } |
| 351 contentNode.nodes.addAll(node.nodes); | |
| 352 | 363 |
| 353 // Create a new ElementInfo that is a child of "result" -- the | 364 if (node.nodes.length == 0) return null; |
| 354 // placeholder node. This will become result.contentInfo. | 365 var result = new TemplateInfo(node, _parent, loopVariable: match[1], |
| 355 visitElementInfo(_createElementInfo(contentNode, result)); | 366 loopItems: match[2], isRepeat: iterate == null); |
| 356 return result; | 367 result.removeAttributes.add('iterate'); |
| 357 } else if (iterate != null) { | 368 result.removeAttributes.add('repeat'); |
| 358 var match = new RegExp(r"(.*) in (.*)").firstMatch(iterate); | 369 if (node.tagName == 'template') { |
| 359 if (match != null) { | |
| 360 if (node.nodes.length == 0) return null; | |
| 361 var result = new TemplateInfo(node, _parent, loopVariable: match[1], | |
| 362 loopItems: match[2]); | |
| 363 result.removeAttributes.add('iterate'); | |
| 364 if (node.tagName != 'template') result.removeAttributes.add('template'); | |
| 365 return result; | 370 return result; |
| 366 } | 371 } |
| 367 _messages.warning('template iterate must be of the form: ' | 372 |
| 368 'iterate="variable in list", where "variable" is your variable name ' | 373 if (!result.isRepeat) { |
| 369 'and "list" is the list of items.', | 374 result.removeAttributes.add('template'); |
| 370 node.sourceSpan); | 375 // TODO(jmesserly): deprecate this? I think you want "template repeat" |
| 376 // most of the time, but "template iterate" seems useful sometimes. |
| 377 // (Native <template> element parsing would make both obsolete, though.) |
| 378 return result; |
| 379 } |
| 380 |
| 381 _createTemplateAttributePlaceholder(node, result); |
| 382 return result; |
| 371 } | 383 } |
| 384 |
| 372 return null; | 385 return null; |
| 373 } | 386 } |
| 374 | 387 |
| 388 // TODO(jmesserly): if and repeat in attributes require injecting a |
| 389 // placeholder node, and a real node which is a clone. We should |
| 390 // consider a design where we show/hide the node instead (with care |
| 391 // taken not to evaluate hidden bindings). That is more along the lines |
| 392 // of AngularJS, and would have a cleaner DOM. See issue #142. |
| 393 void _createTemplateAttributePlaceholder(Element node, TemplateInfo result) { |
| 394 result.removeAttributes.add('template'); |
| 395 var contentNode = node.clone(); |
| 396 node.attributes.clear(); |
| 397 contentNode.nodes.addAll(node.nodes); |
| 398 |
| 399 // Create a new ElementInfo that is a child of "result" -- the |
| 400 // placeholder node. This will become result.contentInfo. |
| 401 visitElementInfo(_createElementInfo(contentNode, result)); |
| 402 } |
| 403 |
| 375 void visitAttribute(ElementInfo info, String name, String value) { | 404 void visitAttribute(ElementInfo info, String name, String value) { |
| 376 if (name.startsWith('on')) { | 405 if (name.startsWith('on')) { |
| 377 _readEventHandler(info, name, value); | 406 _readEventHandler(info, name, value); |
| 378 return; | 407 return; |
| 379 } else if (name.startsWith('bind-')) { | 408 } else if (name.startsWith('bind-')) { |
| 380 // Strip leading "bind-" and make camel case. | 409 // Strip leading "bind-" and make camel case. |
| 381 var fieldName = toCamelCase(name.substring(5)); | 410 var fieldName = toCamelCase(name.substring(5)); |
| 382 if (_readTwoWayBinding(info, fieldName, value)) { | 411 if (_readTwoWayBinding(info, fieldName, value)) { |
| 383 info.removeAttributes.add(name); | 412 info.removeAttributes.add(name); |
| 384 } | 413 } |
| (...skipping 628 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1013 if (start == null) moveNext(); | 1042 if (start == null) moveNext(); |
| 1014 if (start < length) { | 1043 if (start < length) { |
| 1015 do { | 1044 do { |
| 1016 bindings.add(binding); | 1045 bindings.add(binding); |
| 1017 content.add(textContent); | 1046 content.add(textContent); |
| 1018 } while (moveNext()); | 1047 } while (moveNext()); |
| 1019 } | 1048 } |
| 1020 content.add(textContent); | 1049 content.add(textContent); |
| 1021 } | 1050 } |
| 1022 } | 1051 } |
| OLD | NEW |