Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(370)

Side by Side Diff: sdk/lib/html/dart2js/html_dart2js.dart

Issue 14732003: Implement Model-Driven-Views spec for Dart (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: trying upload again Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 /// The Dart HTML library. 1 /// The Dart HTML library.
2 library dart.dom.html; 2 library dart.dom.html;
3 3
4 import 'dart:async'; 4 import 'dart:async';
5 import 'dart:collection'; 5 import 'dart:collection';
6 import 'dart:_collection-dev'; 6 import 'dart:_collection-dev';
7 import 'dart:html_common'; 7 import 'dart:html_common';
8 import 'dart:indexed_db'; 8 import 'dart:indexed_db';
9 import 'dart:isolate'; 9 import 'dart:isolate';
10 import 'dart:json' as json; 10 import 'dart:json' as json;
11 import 'dart:math'; 11 import 'dart:math';
12 import 'dart:observe';
12 import 'dart:typed_data'; 13 import 'dart:typed_data';
13 import 'dart:svg' as svg; 14 import 'dart:svg' as svg;
14 import 'dart:web_audio' as web_audio; 15 import 'dart:web_audio' as web_audio;
15 import 'dart:web_gl' as gl; 16 import 'dart:web_gl' as gl;
16 import 'dart:web_sql'; 17 import 'dart:web_sql';
17 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin gBehavior, JSName, Null, Returns; 18 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin gBehavior, JSName, Null, Returns;
18 import 'dart:_isolate_helper' show IsolateNatives; 19 import 'dart:_isolate_helper' show IsolateNatives;
19 import 'dart:_foreign_helper' show JS; 20 import 'dart:_foreign_helper' show JS;
20 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 21 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
21 // for details. All rights reserved. Use of this source code is governed by a 22 // for details. All rights reserved. Use of this source code is governed by a
(...skipping 5965 matching lines...) Expand 10 before | Expand all | Expand 10 after
5987 @DomName('Document.cookie') 5988 @DomName('Document.cookie')
5988 @DocsEditable 5989 @DocsEditable
5989 String cookie; 5990 String cookie;
5990 5991
5991 WindowBase get window => _convertNativeToDart_Window(this._get_window); 5992 WindowBase get window => _convertNativeToDart_Window(this._get_window);
5992 @JSName('defaultView') 5993 @JSName('defaultView')
5993 @DomName('Document.window') 5994 @DomName('Document.window')
5994 @DocsEditable 5995 @DocsEditable
5995 @Creates('Window|=Object') 5996 @Creates('Window|=Object')
5996 @Returns('Window|=Object') 5997 @Returns('Window|=Object')
5998 @Creates('Window|=Object|Null')
5999 @Returns('Window|=Object|Null')
5997 final dynamic _get_window; 6000 final dynamic _get_window;
5998 6001
5999 @DomName('Document.documentElement') 6002 @DomName('Document.documentElement')
6000 @DocsEditable 6003 @DocsEditable
6001 final Element documentElement; 6004 final Element documentElement;
6002 6005
6003 @DomName('Document.domain') 6006 @DomName('Document.domain')
6004 @DocsEditable 6007 @DocsEditable
6005 final String domain; 6008 final String domain;
6006 6009
(...skipping 520 matching lines...) Expand 10 before | Expand all | Expand 10 after
6527 * 6530 *
6528 * [selectors] should be a string using CSS selector syntax. 6531 * [selectors] should be a string using CSS selector syntax.
6529 * var items = document.queryAll('.itemClassName'); 6532 * var items = document.queryAll('.itemClassName');
6530 * 6533 *
6531 * For details about CSS selector syntax, see the 6534 * For details about CSS selector syntax, see the
6532 * [CSS selector specification](http://www.w3.org/TR/css3-selectors/). 6535 * [CSS selector specification](http://www.w3.org/TR/css3-selectors/).
6533 */ 6536 */
6534 List<Element> queryAll(String selectors) { 6537 List<Element> queryAll(String selectors) {
6535 return new _FrozenElementList._wrap($dom_querySelectorAll(selectors)); 6538 return new _FrozenElementList._wrap($dom_querySelectorAll(selectors));
6536 } 6539 }
6540
6541 // Note: used to polyfill <template>
6542 Document _templateContentsOwner;
6537 } 6543 }
6538 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file 6544 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
6539 // for details. All rights reserved. Use of this source code is governed by a 6545 // for details. All rights reserved. Use of this source code is governed by a
6540 // BSD-style license that can be found in the LICENSE file. 6546 // BSD-style license that can be found in the LICENSE file.
6541 6547
6542 6548
6543 @DomName('DocumentFragment') 6549 @DomName('DocumentFragment')
6544 class DocumentFragment extends Node native "DocumentFragment" { 6550 class DocumentFragment extends Node native "DocumentFragment" {
6545 factory DocumentFragment() => _DocumentFragmentFactoryProvider.createDocumentF ragment(); 6551 factory DocumentFragment() => _DocumentFragmentFactoryProvider.createDocumentF ragment();
6546 6552
(...skipping 1850 matching lines...) Expand 10 before | Expand all | Expand 10 after
8397 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { 8403 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) {
8398 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); 8404 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors);
8399 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { 8405 } else if (JS('bool', '!!#.mozMatchesSelector', this)) {
8400 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); 8406 return JS('bool', '#.mozMatchesSelector(#)', this, selectors);
8401 } else if (JS('bool', '!!#.msMatchesSelector', this)) { 8407 } else if (JS('bool', '!!#.msMatchesSelector', this)) {
8402 return JS('bool', '#.msMatchesSelector(#)', this, selectors); 8408 return JS('bool', '#.msMatchesSelector(#)', this, selectors);
8403 } 8409 }
8404 throw new UnsupportedError("Not supported on this platform"); 8410 throw new UnsupportedError("Not supported on this platform");
8405 } 8411 }
8406 8412
8413 Map<String, _Binding> _attributeBindings;
8414
8415 // TODO(jmesserly): I'm concerned about adding these to every element.
8416 // Conceptually all of these belong on TemplateElement. They are here to
8417 // support browsers that don't have <template> yet.
8418 // However even in the polyfill they're restricted to certain tags
8419 // (see [isTemplate]). So we can probably convert it to a (public) mixin, and
8420 // only mix it in to the elements that need it.
8421 @Creates('Null')
8422 var _model;
8423
8424 _TemplateIterator _templateIterator;
8425
8426 Element _templateInstanceRef;
8427
8428 // Note: only used if `this is! TemplateElement`
8429 DocumentFragment _templateContent;
8430
8431 bool _templateIsDecorated;
8432
8433 // TODO(jmesserly): should path be optional, and default to empty path?
8434 // It is used that way in at least one path in JS TemplateElement tests
8435 // (see "BindImperative" test in original JS code).
8436 @Experimental
8437 void bind(String name, model, String path) {
8438 if (_bindTemplate(name, model, path)) return;
8439
8440 if (_attributeBindings == null) {
8441 _attributeBindings = new Map<String, _Binding>();
8442 }
8443
8444 attributes.remove(name);
8445
8446 _ChangeHandler changed;
8447 if (name.endsWith('?')) {
8448 name = name.substring(0, name.length - 1);
8449
8450 changed = (value) {
8451 if (_templateBooleanConversion(value)) {
8452 attributes[name] = '';
8453 } else {
8454 attributes.remove(name);
8455 }
8456 };
8457 } else {
8458 changed = (value) {
8459 // TODO(jmesserly): escape value if needed to protect against XSS.
8460 attributes[name] = value == null ? '' : '$value';
8461 };
8462 }
8463
8464 unbind(name);
8465
8466 _attributeBindings[name] = new _Binding(model, path, changed);
8467 }
8468
8469 @Experimental
8470 void unbind(String name) {
8471 if (_unbindTemplate(name)) return;
8472 if (_attributeBindings != null) {
8473 var binding = _attributeBindings.remove(name);
8474 if (binding != null) binding.dispose();
8475 }
8476 }
8477
8478 @Experimental
8479 void unbindAll() {
8480 _unbindAllTemplate();
8481
8482 if (_attributeBindings != null) {
8483 for (var binding in _attributeBindings.values) {
8484 binding.dispose();
8485 }
8486 _attributeBindings = null;
8487 super.unbindAll();
8488 }
8489 }
8490
8491 // TODO(jmesserly): unlike the JS polyfill, we can't mixin
8492 // HTMLTemplateElement at runtime into things that are semantically template
8493 // elements. So instead we implement it here with a runtime check.
8494 // If the bind succeeds, we return true, otherwise we return false and let
8495 // the normal Element.bind logic kick in.
8496 bool _bindTemplate(String name, model, String path) {
8497 if (isTemplate) {
8498 switch (name) {
8499 case 'bind':
8500 case 'repeat':
8501 case 'if':
8502 _ensureTemplate();
8503 if (_templateIterator == null) {
8504 _templateIterator = new _TemplateIterator(this);
8505 }
8506 _templateIterator.inputs.bind(name, model, path);
8507 return true;
8508 }
8509 }
8510 return false;
8511 }
8512
8513 bool _unbindTemplate(String name) {
8514 if (isTemplate) {
8515 switch (name) {
8516 case 'bind':
8517 case 'repeat':
8518 case 'if':
8519 _ensureTemplate();
8520 if (_templateIterator != null) {
8521 _templateIterator.inputs.unbind(name);
8522 }
8523 return true;
8524 }
8525 }
8526 return false;
8527 }
8528
8529 void _unbindAllTemplate() {
8530 if (isTemplate) {
8531 unbind('bind');
8532 unbind('repeat');
8533 unbind('if');
8534 }
8535 }
8536
8537 /**
8538 * Gets the template this node refers to.
8539 * This is only supported if [isTemplate] is true.
8540 */
8541 @Experimental
8542 Element get ref {
8543 _ensureTemplate();
8544
8545 Element ref = null;
8546 var refId = attributes['ref'];
8547 if (refId != null) {
8548 ref = document.getElementById(refId);
8549 }
8550
8551 return ref != null ? ref : _templateInstanceRef;
8552 }
8553
8554 /**
8555 * Gets the content of this template.
8556 * This is only supported if [isTemplate] is true.
8557 */
8558 @Experimental
8559 DocumentFragment get content {
8560 _ensureTemplate();
8561 return _templateContent;
8562 }
8563
8564 /**
8565 * Creates an instance of the template.
8566 * This is only supported if [isTemplate] is true.
8567 */
8568 @Experimental
8569 DocumentFragment createInstance() {
8570 _ensureTemplate();
8571
8572 var template = ref;
8573 if (template == null) template = this;
8574
8575 var instance = _createDeepCloneAndDecorateTemplates(template.content,
8576 attributes['syntax']);
8577
8578 if (TemplateElement._instanceCreated != null) {
8579 TemplateElement._instanceCreated.add(instance);
8580 }
8581 return instance;
8582 }
8583
8584 /**
8585 * The data model which is inherited through the tree.
8586 * This is only supported if [isTemplate] is true.
8587 *
8588 * Setting this will destructive propagate the value to all descendant nodes,
8589 * and reinstantiate all of the nodes expanded by this template.
8590 *
8591 * Currently this does not support propagation through Shadow DOMs.
8592 */
8593 @Experimental
8594 get model => _model;
8595
8596 @Experimental
8597 void set model(value) {
8598 _ensureTemplate();
8599
8600 _model = value;
8601 _addBindings(this, model);
8602 }
8603
8604 // TODO(jmesserly): const set would be better
8605 static const _TABLE_TAGS = const {
8606 'caption': null,
8607 'col': null,
8608 'colgroup': null,
8609 'tbody': null,
8610 'td': null,
8611 'tfoot': null,
8612 'th': null,
8613 'thead': null,
8614 'tr': null,
8615 };
8616
8617 bool get _isAttributeTemplate => attributes.containsKey('template') &&
8618 (localName == 'option' || _TABLE_TAGS.containsKey(localName));
8619
8620 /**
8621 * Returns true if this node is a template.
8622 *
8623 * A node is a template if [tagName] is TEMPLATE, or the node has the
8624 * 'template' attribute and this tag supports attribute form for backwards
8625 * compatibility with existing HTML parsers. The nodes that can use attribute
8626 * form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP
8627 * and COL) and OPTION.
8628 */
8629 // TODO(jmesserly): this is not a public MDV API, but it seems like a useful
8630 // place to document which tags our polyfill considers to be templates.
8631 // Otherwise I'd be repeating it in several other places.
8632 // See if we can replace this with a TemplateMixin.
8633 @Experimental
8634 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate;
8635
8636 void _ensureTemplate() {
8637 if (!isTemplate) {
8638 throw new UnsupportedError('$this is not a template.');
8639 }
8640 TemplateElement.decorate(this);
8641 }
8642
8407 8643
8408 @DomName('Element.abortEvent') 8644 @DomName('Element.abortEvent')
8409 @DocsEditable 8645 @DocsEditable
8410 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort'); 8646 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort');
8411 8647
8412 @DomName('Element.beforecopyEvent') 8648 @DomName('Element.beforecopyEvent')
8413 @DocsEditable 8649 @DocsEditable
8414 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy'); 8650 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy');
8415 8651
8416 @DomName('Element.beforecutEvent') 8652 @DomName('Element.beforecutEvent')
(...skipping 724 matching lines...) Expand 10 before | Expand all | Expand 10 after
9141 @DomName('Element.onwebkitfullscreenchange') 9377 @DomName('Element.onwebkitfullscreenchange')
9142 @DocsEditable 9378 @DocsEditable
9143 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); 9379 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this);
9144 9380
9145 @DomName('Element.onwebkitfullscreenerror') 9381 @DomName('Element.onwebkitfullscreenerror')
9146 @DocsEditable 9382 @DocsEditable
9147 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); 9383 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this);
9148 9384
9149 } 9385 }
9150 9386
9387
9151 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); 9388 final _START_TAG_REGEXP = new RegExp('<(\\w+)');
9152 class _ElementFactoryProvider { 9389 class _ElementFactoryProvider {
9153 static const _CUSTOM_PARENT_TAG_MAP = const { 9390 static const _CUSTOM_PARENT_TAG_MAP = const {
9154 'body' : 'html', 9391 'body' : 'html',
9155 'head' : 'html', 9392 'head' : 'html',
9156 'caption' : 'table', 9393 'caption' : 'table',
9157 'td': 'tr', 9394 'td': 'tr',
9158 'th': 'tr', 9395 'th': 'tr',
9159 'colgroup': 'table', 9396 'colgroup': 'table',
9160 'col' : 'colgroup', 9397 'col' : 'colgroup',
9161 'tr' : 'tbody', 9398 'tr' : 'tbody',
9162 'tbody' : 'table', 9399 'tbody' : 'table',
9163 'tfoot' : 'table', 9400 'tfoot' : 'table',
9164 'thead' : 'table', 9401 'thead' : 'table',
9165 'track' : 'audio', 9402 'track' : 'audio',
9166 }; 9403 };
9167 9404
9168 // TODO(jmesserly): const set would be better
9169 static const _TABLE_TAGS = const {
9170 'caption': null,
9171 'col': null,
9172 'colgroup': null,
9173 'tbody': null,
9174 'td': null,
9175 'tfoot': null,
9176 'th': null,
9177 'thead': null,
9178 'tr': null,
9179 };
9180
9181 @DomName('Document.createElement') 9405 @DomName('Document.createElement')
9182 static Element createElement_html(String html) { 9406 static Element createElement_html(String html) {
9183 // TODO(jacobr): this method can be made more robust and performant. 9407 // TODO(jacobr): this method can be made more robust and performant.
9184 // 1) Cache the dummy parent elements required to use innerHTML rather than 9408 // 1) Cache the dummy parent elements required to use innerHTML rather than
9185 // creating them every call. 9409 // creating them every call.
9186 // 2) Verify that the html does not contain leading or trailing text nodes. 9410 // 2) Verify that the html does not contain leading or trailing text nodes.
9187 // 3) Verify that the html does not contain both <head> and <body> tags. 9411 // 3) Verify that the html does not contain both <head> and <body> tags.
9188 // 4) Detatch the created element from its dummy parent. 9412 // 4) Detatch the created element from its dummy parent.
9189 String parentTag = 'div'; 9413 String parentTag = 'div';
9190 String tag; 9414 String tag;
9191 final match = _START_TAG_REGEXP.firstMatch(html); 9415 final match = _START_TAG_REGEXP.firstMatch(html);
9192 if (match != null) { 9416 if (match != null) {
9193 tag = match.group(1).toLowerCase(); 9417 tag = match.group(1).toLowerCase();
9194 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { 9418 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) {
9195 return _createTableForIE(html, tag); 9419 return _createTableForIE(html, tag);
9196 } 9420 }
9197 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; 9421 parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
9198 if (parentTag == null) parentTag = 'div'; 9422 if (parentTag == null) parentTag = 'div';
9199 } 9423 }
9200 9424
9201 final temp = new Element.tag(parentTag); 9425 final temp = new Element.tag(parentTag);
9202 temp.innerHtml = html; 9426 temp.innerHtml = html;
9203 9427
9204 Element element; 9428 Element element;
(...skipping 3297 matching lines...) Expand 10 before | Expand all | Expand 10 after
12502 var e = document.$dom_createElement("input"); 12726 var e = document.$dom_createElement("input");
12503 if (type != null) { 12727 if (type != null) {
12504 try { 12728 try {
12505 // IE throws an exception for unknown types. 12729 // IE throws an exception for unknown types.
12506 e.type = type; 12730 e.type = type;
12507 } catch(_) {} 12731 } catch(_) {}
12508 } 12732 }
12509 return e; 12733 return e;
12510 } 12734 }
12511 12735
12736 _ValueBinding _valueBinding;
12737 _CheckedBinding _checkedBinding;
12738
12739 @Experimental
12740 void bind(String name, model, String path) {
12741 switch (name) {
12742 case 'value':
12743 unbind('value');
12744 attributes.remove('value');
12745 _valueBinding = new _ValueBinding(this, model, path);
12746 break;
12747 case 'checked':
12748 unbind('checked');
12749 attributes.remove('checked');
12750 _checkedBinding = new _CheckedBinding(this, model, path);
12751 break;
12752 default:
12753 super.bind(name, model, path);
12754 break;
12755 }
12756 }
12757
12758 @Experimental
12759 void unbind(String name) {
12760 switch (name) {
12761 case 'value':
12762 if (_valueBinding != null) {
12763 _valueBinding.unbind();
12764 _valueBinding = null;
12765 }
12766 break;
12767 case 'checked':
12768 if (_checkedBinding != null) {
12769 _checkedBinding.unbind();
12770 _checkedBinding = null;
12771 }
12772 break;
12773 default:
12774 super.unbind(name);
12775 break;
12776 }
12777 }
12778
12779 @Experimental
12780 void unbindAll() {
12781 unbind('value');
12782 unbind('checked');
12783 super.unbindAll();
12784 }
12785
12786
12512 @DomName('HTMLInputElement.webkitSpeechChangeEvent') 12787 @DomName('HTMLInputElement.webkitSpeechChangeEvent')
12513 @DocsEditable 12788 @DocsEditable
12514 @SupportedBrowser(SupportedBrowser.CHROME) 12789 @SupportedBrowser(SupportedBrowser.CHROME)
12515 @SupportedBrowser(SupportedBrowser.SAFARI) 12790 @SupportedBrowser(SupportedBrowser.SAFARI)
12516 @Experimental 12791 @Experimental
12517 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange'); 12792 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange');
12518 12793
12519 @DomName('HTMLInputElement.accept') 12794 @DomName('HTMLInputElement.accept')
12520 @DocsEditable 12795 @DocsEditable
12521 String accept; 12796 String accept;
(...skipping 3162 matching lines...) Expand 10 before | Expand all | Expand 10 after
15684 _this.$dom_removeChild(node); 15959 _this.$dom_removeChild(node);
15685 return true; 15960 return true;
15686 } 15961 }
15687 15962
15688 void _filter(bool test(Node node), bool removeMatching) { 15963 void _filter(bool test(Node node), bool removeMatching) {
15689 // This implementation of removeWhere/retainWhere is more efficient 15964 // This implementation of removeWhere/retainWhere is more efficient
15690 // than the default in ListBase. Child nodes can be removed in constant 15965 // than the default in ListBase. Child nodes can be removed in constant
15691 // time. 15966 // time.
15692 Node child = _this.$dom_firstChild; 15967 Node child = _this.$dom_firstChild;
15693 while (child != null) { 15968 while (child != null) {
15694 Node nextChild = child.nextSibling; 15969 Node nextChild = child.nextNode;
15695 if (test(child) == removeMatching) { 15970 if (test(child) == removeMatching) {
15696 _this.$dom_removeChild(child); 15971 _this.$dom_removeChild(child);
15697 } 15972 }
15698 child = nextChild; 15973 child = nextChild;
15699 } 15974 }
15700 } 15975 }
15701 15976
15702 void removeWhere(bool test(Node node)) { 15977 void removeWhere(bool test(Node node)) {
15703 _filter(test, true); 15978 _filter(test, true);
15704 } 15979 }
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
15809 // Should use $dom_firstChild, Bug 8886. 16084 // Should use $dom_firstChild, Bug 8886.
15810 this.insertBefore(newNodes[0], refChild); 16085 this.insertBefore(newNodes[0], refChild);
15811 } 16086 }
15812 } else { 16087 } else {
15813 for (var node in newNodes) { 16088 for (var node in newNodes) {
15814 this.insertBefore(node, refChild); 16089 this.insertBefore(node, refChild);
15815 } 16090 }
15816 } 16091 }
15817 } 16092 }
15818 16093
15819 // Note that this may either be the locally set model or a cached value
15820 // of the inherited model. This is cached to minimize model change
15821 // notifications.
15822 @Creates('Null')
15823 var _model;
15824 bool _hasLocalModel;
15825 Set<StreamController<Node>> _modelChangedStreams;
15826
15827 /**
15828 * The data model which is inherited through the tree.
15829 *
15830 * Setting this will propagate the value to all descendant nodes. If the
15831 * model is not set on this node then it will be inherited from ancestor
15832 * nodes.
15833 *
15834 * Currently this does not support propagation through Shadow DOMs.
15835 *
15836 * [clearModel] must be used to remove the model property from this node
15837 * and have the model inherit from ancestor nodes.
15838 */
15839 @Experimental
15840 get model {
15841 // If we have a change handler then we've cached the model locally.
15842 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) {
15843 return _model;
15844 }
15845 // Otherwise start looking up the tree.
15846 for (var node = this; node != null; node = node.parentNode) {
15847 if (node._hasLocalModel == true) {
15848 return node._model;
15849 }
15850 }
15851 return null;
15852 }
15853
15854 @Experimental
15855 void set model(value) {
15856 var changed = model != value;
15857 _model = value;
15858 _hasLocalModel = true;
15859 _ModelTreeObserver.initialize();
15860
15861 if (changed) {
15862 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) {
15863 _modelChangedStreams.toList().forEach((stream) => stream.add(this));
15864 }
15865 // Propagate new model to all descendants.
15866 _ModelTreeObserver.propagateModel(this, value, false);
15867 }
15868 }
15869
15870 /**
15871 * Clears the locally set model and makes this model be inherited from parent
15872 * nodes.
15873 */
15874 @Experimental
15875 void clearModel() {
15876 if (_hasLocalModel == true) {
15877 _hasLocalModel = false;
15878
15879 // Propagate new model to all descendants.
15880 if (parentNode != null) {
15881 _ModelTreeObserver.propagateModel(this, parentNode.model, false);
15882 } else {
15883 _ModelTreeObserver.propagateModel(this, null, false);
15884 }
15885 }
15886 }
15887
15888 /**
15889 * Get a stream of models, whenever the model changes.
15890 */
15891 Stream<Node> get onModelChanged {
15892 if (_modelChangedStreams == null) {
15893 _modelChangedStreams = new Set<StreamController<Node>>();
15894 }
15895 var controller;
15896 controller = new StreamController(
15897 onListen: () { _modelChangedStreams.add(controller); },
15898 onCancel: () { _modelChangedStreams.remove(controller); });
15899 return controller.stream;
15900 }
15901
15902 /** 16094 /**
15903 * Print out a String representation of this Node. 16095 * Print out a String representation of this Node.
15904 */ 16096 */
15905 String toString() => localName == null ? 16097 String toString() => localName == null ?
15906 (nodeValue == null ? super.toString() : nodeValue) : localName; 16098 (nodeValue == null ? super.toString() : nodeValue) : localName;
15907 16099
16100 /**
16101 * Binds the attribute [name] to the [path] of the [model].
16102 * Path is a String of accessors such as `foo.bar.baz`.
16103 */
16104 @Experimental
16105 void bind(String name, model, String path) {
16106 // TODO(jmesserly): should we throw instead?
16107 window.console.error('Unhandled binding to Node: '
16108 '$this $name $model $path');
16109 }
16110
16111 /** Unbinds the attribute [name]. */
16112 @Experimental
16113 void unbind(String name) {}
16114
16115 /** Unbinds all bound attributes. */
16116 @Experimental
16117 void unbindAll() {}
16118
16119 TemplateInstance _templateInstance;
16120
16121 // TODO(arv): Consider storing all "NodeRareData" on a single object?
16122 int __instanceTerminatorCount;
16123 int get _instanceTerminatorCount {
16124 if (__instanceTerminatorCount == null) return 0;
16125 return __instanceTerminatorCount;
16126 }
16127 set _instanceTerminatorCount(int value) {
16128 if (value == 0) value = null;
16129 __instanceTerminatorCount = value;
16130 }
16131
16132 /** Gets the template instance that instantiated this node, if any. */
16133 @Experimental
16134 TemplateInstance get templateInstance =>
16135 _templateInstance != null ? _templateInstance :
16136 (parent != null ? parent.templateInstance : null);
16137
15908 16138
15909 static const int ATTRIBUTE_NODE = 2; 16139 static const int ATTRIBUTE_NODE = 2;
15910 16140
15911 static const int CDATA_SECTION_NODE = 4; 16141 static const int CDATA_SECTION_NODE = 4;
15912 16142
15913 static const int COMMENT_NODE = 8; 16143 static const int COMMENT_NODE = 8;
15914 16144
15915 static const int DOCUMENT_FRAGMENT_NODE = 11; 16145 static const int DOCUMENT_FRAGMENT_NODE = 11;
15916 16146
15917 static const int DOCUMENT_NODE = 9; 16147 static const int DOCUMENT_NODE = 9;
(...skipping 4132 matching lines...) Expand 10 before | Expand all | Expand 10 after
20050 20280
20051 @JSName('insertRow') 20281 @JSName('insertRow')
20052 @DomName('HTMLTableSectionElement.insertRow') 20282 @DomName('HTMLTableSectionElement.insertRow')
20053 @DocsEditable 20283 @DocsEditable
20054 Element $dom_insertRow(int index) native; 20284 Element $dom_insertRow(int index) native;
20055 } 20285 }
20056 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20286 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
20057 // for details. All rights reserved. Use of this source code is governed by a 20287 // for details. All rights reserved. Use of this source code is governed by a
20058 // BSD-style license that can be found in the LICENSE file. 20288 // BSD-style license that can be found in the LICENSE file.
20059 20289
20290 // WARNING: Do not edit - generated code.
20060 20291
20061 @DocsEditable 20292
20293 @Experimental
20062 @DomName('HTMLTemplateElement') 20294 @DomName('HTMLTemplateElement')
20063 class TemplateElement extends Element native "HTMLTemplateElement" { 20295 class TemplateElement extends Element native "HTMLTemplateElement" {
20064 20296
20065 @DomName('HTMLTemplateElement.content') 20297 @DomName('HTMLTemplateElement.content')
20066 @DocsEditable 20298 @DocsEditable
20067 final DocumentFragment content; 20299 final DocumentFragment content;
20300
20301
20302 /**
20303 * True if `<template>` element is natively supported.
20304 *
20305 * Note: this primarily affects the HTML parser. Most other features of
20306 * `<template>` work regardless of whether the parser supports template.
20307 * You can workaround the lack of parser support by using the "template"
20308 * attribute on certain nodes, see [isTemplate] for more information.
20309 *
20310 * See also: [content], [decorate], [bind], [unbind], [unbindAll].
20311 */
20312 static bool get supported => new Element.tag('template') is! UnknownElement;
20313
20314 static StreamController<DocumentFragment> _instanceCreated;
20315
20316 /**
20317 * *Warning*: This is an implementation helper for Model-Driven Views and
20318 * should not be used in your code.
20319 *
20320 * This event is fired whenever a template is instantiated via
20321 * [createInstance].
20322 */
20323 // TODO(rafaelw): This is a hack, and is neccesary for the polyfill
20324 // because custom elements are not upgraded during clone()
20325 @Experimental
20326 static Stream<DocumentFragment> get instanceCreated {
20327 if (_instanceCreated == null) {
20328 _instanceCreated = new StreamController<DocumentFragment>();
20329 }
20330 return _instanceCreated.stream;
20331 }
20332
20333 /**
20334 * Ensures proper API and content model for template elements.
20335 *
20336 * [instanceRef] can be used to set the [Element.ref] property of [template],
20337 * and use the ref's content will be used as source when createInstance() is
20338 * invoked.
20339 *
20340 * Returns true if this template was just decorated, or false if it was
20341 * already decorated.
20342 */
20343 @Experimental
20344 static bool decorate(Element template, [Element instanceRef]) {
20345 // == true check because it starts as a null field.
20346 if (template._templateIsDecorated == true) return false;
20347
20348 template._templateIsDecorated = true;
20349
20350 _injectStylesheet();
20351
20352 // Create content
20353 if (template is! TemplateElement) {
20354 var doc = _getTemplateContentsOwner(template.document);
20355 template._templateContent = doc.createDocumentFragment();
20356 }
20357
20358 if (instanceRef != null) {
20359 template._templateInstanceRef = instanceRef;
20360 return true; // content is empty.
20361 }
20362
20363 if (template is TemplateElement) {
20364 _bootstrapTemplatesRecursivelyFrom(template.content);
20365 } else {
20366 _liftNonNativeTemplateChildrenIntoContent(template);
20367 }
20368
20369 return true;
20370 }
20371
20372 /**
20373 * This used to decorate recursively all templates from a given node.
20374 *
20375 * By default [decorate] will be called on templates lazily when certain
20376 * properties such as [model] are accessed, but it can be run eagerly to
20377 * decorate an entire tree recursively.
20378 */
20379 // TODO(rafaelw): Review whether this is the right public API.
20380 @Experimental
20381 static void bootstrap(Node content) {
20382 _bootstrapTemplatesRecursivelyFrom(content);
20383 }
20384
20385 static bool _initStyles;
20386
20387 static void _injectStylesheet() {
20388 if (_initStyles == true) return;
20389 _initStyles = true;
20390
20391 var style = new StyleElement();
20392 style.text = r'''
20393 template,
20394 thead[template],
20395 tbody[template],
20396 tfoot[template],
20397 th[template],
20398 tr[template],
20399 td[template],
20400 caption[template],
20401 colgroup[template],
20402 col[template],
20403 option[template] {
20404 display: none;
20405 }''';
20406 document.head.nodes.add(style);
20407 }
20408
20409 /**
20410 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for
20411 * more information.
20412 */
20413 @Experimental
20414 static Map<String, CustomBindingSyntax> syntax = {};
20068 } 20415 }
20069 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20416 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
20070 // for details. All rights reserved. Use of this source code is governed by a 20417 // for details. All rights reserved. Use of this source code is governed by a
20071 // BSD-style license that can be found in the LICENSE file. 20418 // BSD-style license that can be found in the LICENSE file.
20072 20419
20073 // WARNING: Do not edit - generated code. 20420 // WARNING: Do not edit - generated code.
20074 20421
20075 20422
20076 @DomName('Text') 20423 @DomName('Text')
20077 class Text extends CharacterData native "Text" { 20424 class Text extends CharacterData native "Text" {
(...skipping 12 matching lines...) Expand all
20090 final String wholeText; 20437 final String wholeText;
20091 20438
20092 @DomName('Text.replaceWholeText') 20439 @DomName('Text.replaceWholeText')
20093 @DocsEditable 20440 @DocsEditable
20094 Text replaceWholeText(String content) native; 20441 Text replaceWholeText(String content) native;
20095 20442
20096 @DomName('Text.splitText') 20443 @DomName('Text.splitText')
20097 @DocsEditable 20444 @DocsEditable
20098 Text splitText(int offset) native; 20445 Text splitText(int offset) native;
20099 20446
20447
20448 _Binding _textBinding;
20449
20450 @Experimental
20451 void bind(String name, model, String path) {
20452 if (name != 'text') {
20453 super.bind(name, model, path);
20454 return;
20455 }
20456
20457 unbind('text');
20458 _textBinding = new _Binding(model, path, (value) { text = '$value'; });
20459 }
20460
20461 @Experimental
20462 void unbind(String name) {
20463 if (name != 'text') {
20464 super.unbind(name);
20465 return;
20466 }
20467
20468 if (_textBinding == null) return;
20469
20470 _textBinding.dispose();
20471 _textBinding = null;
20472 }
20473
20474 @Experimental
20475 void unbindAll() {
20476 unbind('text');
20477 super.unbindAll();
20478 }
20100 } 20479 }
20101 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20480 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
20102 // for details. All rights reserved. Use of this source code is governed by a 20481 // for details. All rights reserved. Use of this source code is governed by a
20103 // BSD-style license that can be found in the LICENSE file. 20482 // BSD-style license that can be found in the LICENSE file.
20104 20483
20105 20484
20106 @DocsEditable 20485 @DocsEditable
20107 @DomName('HTMLTextAreaElement') 20486 @DomName('HTMLTextAreaElement')
20108 class TextAreaElement extends Element native "HTMLTextAreaElement" { 20487 class TextAreaElement extends Element native "HTMLTextAreaElement" {
20109 20488
(...skipping 7956 matching lines...) Expand 10 before | Expand all | Expand 10 after
28066 * Key value used when an implementation is unable to identify another key 28445 * Key value used when an implementation is unable to identify another key
28067 * value, due to either hardware, platform, or software constraints 28446 * value, due to either hardware, platform, or software constraints
28068 */ 28447 */
28069 static const String UNIDENTIFIED = "Unidentified"; 28448 static const String UNIDENTIFIED = "Unidentified";
28070 } 28449 }
28071 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 28450 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
28072 // for details. All rights reserved. Use of this source code is governed by a 28451 // for details. All rights reserved. Use of this source code is governed by a
28073 // BSD-style license that can be found in the LICENSE file. 28452 // BSD-style license that can be found in the LICENSE file.
28074 28453
28075 28454
28076 class _ModelTreeObserver {
28077 static bool _initialized = false;
28078
28079 /**
28080 * Start an observer watching the document for tree changes to automatically
28081 * propagate model changes.
28082 *
28083 * Currently this does not support propagation through Shadow DOMs.
28084 */
28085 static void initialize() {
28086 if (!_initialized) {
28087 _initialized = true;
28088
28089 if (MutationObserver.supported) {
28090 var observer = new MutationObserver(_processTreeChange);
28091 observer.observe(document, childList: true, subtree: true);
28092 } else {
28093 document.on['DOMNodeInserted'].listen(_handleNodeInserted);
28094 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved);
28095 }
28096 }
28097 }
28098
28099 static void _processTreeChange(List<MutationRecord> mutations,
28100 MutationObserver observer) {
28101 for (var record in mutations) {
28102 for (var node in record.addedNodes) {
28103 // When nodes enter the document we need to make sure that all of the
28104 // models are properly propagated through the entire sub-tree.
28105 propagateModel(node, _calculatedModel(node), true);
28106 }
28107 for (var node in record.removedNodes) {
28108 propagateModel(node, _calculatedModel(node), false);
28109 }
28110 }
28111 }
28112
28113 static void _handleNodeInserted(MutationEvent e) {
28114 var node = e.target;
28115 window.setImmediate(() {
28116 propagateModel(node, _calculatedModel(node), true);
28117 });
28118 }
28119
28120 static void _handleNodeRemoved(MutationEvent e) {
28121 var node = e.target;
28122 window.setImmediate(() {
28123 propagateModel(node, _calculatedModel(node), false);
28124 });
28125 }
28126
28127 /**
28128 * Figures out what the model should be for a node, avoiding any cached
28129 * model values.
28130 */
28131 static _calculatedModel(node) {
28132 if (node._hasLocalModel == true) {
28133 return node._model;
28134 } else if (node.parentNode != null) {
28135 return node.parentNode._model;
28136 }
28137 return null;
28138 }
28139
28140 /**
28141 * Pushes model changes down through the tree.
28142 *
28143 * Set fullTree to true if the state of the tree is unknown and model changes
28144 * should be propagated through the entire tree.
28145 */
28146 static void propagateModel(Node node, model, bool fullTree) {
28147 // Calling into user code with the != call could generate exceptions.
28148 // Catch and report them a global exceptions.
28149 try {
28150 if (node._hasLocalModel != true && node._model != model &&
28151 node._modelChangedStreams != null &&
28152 !node._modelChangedStreams.isEmpty) {
28153 node._model = model;
28154 node._modelChangedStreams.toList()
28155 .forEach((controller) => controller.add(node));
28156 }
28157 } catch (e, s) {
28158 new Future.error(e, s);
28159 }
28160 for (var child = node.$dom_firstChild; child != null;
28161 child = child.nextNode) {
28162 if (child._hasLocalModel != true) {
28163 propagateModel(child, model, fullTree);
28164 } else if (fullTree) {
28165 propagateModel(child, child._model, true);
28166 }
28167 }
28168 }
28169 }
28170 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
28171 // for details. All rights reserved. Use of this source code is governed by a
28172 // BSD-style license that can be found in the LICENSE file.
28173
28174
28175 /** 28455 /**
28176 * A utility class for representing two-dimensional positions. 28456 * A utility class for representing two-dimensional positions.
28177 */ 28457 */
28178 class Point { 28458 class Point {
28179 final num x; 28459 final num x;
28180 final num y; 28460 final num y;
28181 28461
28182 const Point([num x = 0, num y = 0]): x = x, y = y; 28462 const Point([num x = 0, num y = 0]): x = x, y = y;
28183 28463
28184 String toString() => '($x, $y)'; 28464 String toString() => '($x, $y)';
(...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after
28382 * Truncates coordinates to integers and returns the result as a new 28662 * Truncates coordinates to integers and returns the result as a new
28383 * rectangle. 28663 * rectangle.
28384 */ 28664 */
28385 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), 28665 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
28386 height.toInt()); 28666 height.toInt());
28387 28667
28388 Point get topLeft => new Point(this.left, this.top); 28668 Point get topLeft => new Point(this.left, this.top);
28389 Point get bottomRight => new Point(this.left + this.width, 28669 Point get bottomRight => new Point(this.left + this.width,
28390 this.top + this.height); 28670 this.top + this.height);
28391 } 28671 }
28672 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
28673 // for details. All rights reserved. Use of this source code is governed by a
28674 // BSD-style license that can be found in the LICENSE file.
28675
28676
28677 // This code is a port of Model-Driven-Views:
28678 // https://github.com/toolkitchen/mdv
28679 // The code mostly comes from src/template_element.js
28680
28681 typedef void _ChangeHandler(value);
28682
28683 /**
28684 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
28685 * but (by design) don't attempt to implement a wide array of specialized
28686 * behaviors.
28687 *
28688 * Enabling these features in MDV is a matter of implementing and registering an
28689 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more
28690 * delegation functions which implement specialized behavior. This object is
28691 * registered with MDV via [TemplateElement.syntax]:
28692 *
28693 *
28694 * HTML:
28695 * <template bind syntax="MySyntax">
28696 * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
28697 * </template>
28698 *
28699 * Dart:
28700 * class MySyntax extends CustomBindingSyntax {
28701 * getBinding(model, path, name, node) {
28702 * // The magic happens here!
28703 * }
28704 * }
28705 *
28706 * ...
28707 *
28708 * TemplateElement.syntax['MySyntax'] = new MySyntax();
28709 *
28710 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more
28711 * information about Custom Syntax.
28712 */
28713 // TODO(jmesserly): if this is just one method, a function type would make it
28714 // more Dart-friendly.
28715 @Experimental
28716 abstract class CustomBindingSyntax {
28717 // TODO(jmesserly): I had to remove type annotations from "name" and "node"
28718 // Normally they are String and Node respectively. But sometimes it will pass
28719 // (int name, CompoundBinding node). That seems very confusing; we may want
28720 // to change this API.
28721 getBinding(model, String path, name, node);
28722 }
28723
28724 /** The callback used in the [CompoundBinding.combinator] field. */
28725 @Experimental
28726 typedef Object CompoundBindingCombinator(Map objects);
28727
28728 /** Information about the instantiated template. */
28729 @Experimental
28730 class TemplateInstance {
28731 // TODO(rafaelw): firstNode & lastNode should be read-synchronous
28732 // in cases where script has modified the template instance boundary.
28733
28734 /** The first node of this template instantiation. */
28735 final Node firstNode;
28736
28737 /**
28738 * The last node of this template instantiation.
28739 * This could be identical to [firstNode] if the template only expanded to a
28740 * single node.
28741 */
28742 final Node lastNode;
28743
28744 /** The model used to instantiate the template. */
28745 final model;
28746
28747 TemplateInstance(this.firstNode, this.lastNode, this.model);
28748 }
28749
28750 /**
28751 * Model-Driven Views contains a helper object which is useful for the
28752 * implementation of a Custom Syntax.
28753 *
28754 * var binding = new CompoundBinding((values) {
28755 * var combinedValue;
28756 * // compute combinedValue based on the current values which are provided
28757 * return combinedValue;
28758 * });
28759 * binding.bind('name1', obj1, path1);
28760 * binding.bind('name2', obj2, path2);
28761 * //...
28762 * binding.bind('nameN', objN, pathN);
28763 *
28764 * CompoundBinding is an object which knows how to listen to multiple path
28765 * values (registered via [bind]) and invoke its [combinator] when one or more
28766 * of the values have changed and set its [value] property to the return value
28767 * of the function. When any value has changed, all current values are provided
28768 * to the [combinator] in the single `values` argument.
28769 *
28770 * See [CustomBindingSyntax] for more information.
28771 */
28772 // TODO(jmesserly): what is the public API surface here? I just guessed;
28773 // most of it seemed non-public.
28774 // TODO(jmesserly): should this move into dart:observe?
28775 @Experimental
28776 class CompoundBinding extends ObservableMixin {
28777 CompoundBindingCombinator _combinator;
28778
28779 // TODO(jmesserly): ideally these would be String keys, but sometimes we
28780 // use integers.
28781 Map<dynamic, _Binding> _bindings = {};
28782 Map _values = {};
28783 bool _scheduled = false;
28784 bool _disposed = false;
28785 Object _value;
28786
28787 CompoundBinding([CompoundBindingCombinator combinator]) {
28788 // TODO(jmesserly): this is a tweak to the original code, it seemed to me
28789 // that passing the combinator to the constructor should be equivalent to
28790 // setting it via the property.
28791 // I also added a null check to the combinator setter.
28792 this.combinator = combinator;
28793 }
28794
28795 CompoundBindingCombinator get combinator => _combinator;
28796
28797 set combinator(CompoundBindingCombinator combinator) {
28798 _combinator = combinator;
28799 if (combinator != null) _scheduleResolve();
28800 }
28801
28802 static const _VALUE = 'value';
28803
28804 get value => _value;
28805
28806 void set value(newValue) {
28807 _value = notifyChange('value', _value, newValue);
28808 }
28809
28810 // TODO(jmesserly): remove getValue/setValue when dart2js supports mirrors!
28811 getValue(key) {
28812 if (key == _VALUE) return value;
28813 return null;
28814 }
28815 setValue(key, val) {
28816 if (key == _VALUE) value = val;
28817 }
28818
28819 void bind(name, model, String path) {
28820 unbind(name);
28821
28822 _bindings[name] = new _Binding(model, path, (value) {
28823 _values[name] = value;
28824 _scheduleResolve();
28825 });
28826 }
28827
28828 void unbind(name, {bool suppressResolve: false}) {
28829 var binding = _bindings.remove(name);
28830 if (binding == null) return;
28831
28832 binding.dispose();
28833 _values.remove(name);
28834 if (!suppressResolve) _scheduleResolve();
28835 }
28836
28837 // TODO(rafaelw): Is this the right processing model?
28838 // TODO(rafaelw): Consider having a seperate ChangeSummary for
28839 // CompoundBindings so to excess dirtyChecks.
28840 void _scheduleResolve() {
28841 if (_scheduled) return;
28842 _scheduled = true;
28843 queueChangeRecords(resolve);
28844 }
28845
28846 void resolve() {
28847 if (_disposed) return;
28848 _scheduled = false;
28849
28850 if (_combinator == null) {
28851 throw new StateError(
28852 'CompoundBinding attempted to resolve without a combinator');
28853 }
28854
28855 value = _combinator(_values);
28856 }
28857
28858 void dispose() {
28859 for (var binding in _bindings.values) {
28860 binding.dispose();
28861 }
28862 _bindings.clear();
28863 _values.clear();
28864
28865 _disposed = true;
28866 value = null;
28867 }
28868 }
28869
28870 // TODO(jmesserly): we can probably inline this object as it doesn't do much.
28871 class _Binding {
28872 final _ChangeHandler changed;
28873 final PathObserver _path;
28874 StreamSubscription _sub;
28875
28876 _Binding(model, String path, _ChangeHandler changed)
28877 : _path = observePath(model, path) {
28878 if (_path == null) {
28879 // TODO(jmesserly): Should we display an error message about invalid path?
28880 changed(null);
28881 return;
28882 }
28883 _sub = _path.values.listen(changed);
28884 changed(_path.value);
28885 }
28886
28887 void dispose() {
28888 if (_sub != null) _sub.cancel();
28889 }
28890
28891 void set value(newValue) {
28892 if (_path != null) _path.value = newValue;
28893 }
28894 }
28895
28896 Stream<Event> _getStreamForInputType(InputElement element) {
28897 switch (element.type) {
28898 case 'checkbox':
28899 return element.onClick;
28900 case 'radio':
28901 case 'select-multiple':
28902 case 'select-one':
28903 return element.onChange;
28904 default:
28905 return element.onInput;
28906 }
28907 }
28908
28909 abstract class _InputBinding {
28910 final InputElement element;
28911 _Binding binding;
28912 StreamSubscription _sub;
28913
28914 _InputBinding(this.element, model, String path) {
28915 binding = new _Binding(model, path, valueChanged);
28916 _sub = _getStreamForInputType(element).listen(updateBinding);
28917 }
28918
28919 void valueChanged(newValue);
28920
28921 void updateBinding(e);
28922
28923 void unbind() {
28924 binding.dispose();
28925 _sub.cancel();
28926 }
28927 }
28928
28929 class _ValueBinding extends _InputBinding {
28930 _ValueBinding(element, model, path) : super(element, model, path);
28931
28932 void valueChanged(value) {
28933 element.value = value == null ? '' : '$value';
28934 }
28935
28936 void updateBinding(e) {
28937 binding.value = element.value;
28938 }
28939 }
28940
28941 // TODO(jmesserly): not sure what kind of boolean conversion rules to
28942 // apply for template data-binding. HTML attributes are true if they're present.
28943 // However Dart only treats "true" as true. Since this is HTML we'll use
28944 // something closer to the HTML rules: null (missing) and false are false,
28945 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59
28946 bool _templateBooleanConversion(value) => null != value && false != value;
28947
28948 class _CheckedBinding extends _InputBinding {
28949 _CheckedBinding(element, model, path) : super(element, model, path);
28950
28951 void valueChanged(value) {
28952 element.checked = _templateBooleanConversion(value);
28953 }
28954
28955 void updateBinding(e) {
28956 binding.value = element.checked;
28957
28958 // Only the radio button that is getting checked gets an event. We
28959 // therefore find all the associated radio buttons and update their
28960 // CheckedBinding manually.
28961 if (element is InputElement && element.type == 'radio') {
28962 for (var r in _getAssociatedRadioButtons(element)) {
28963 var checkedBinding = r._checkedBinding;
28964 if (checkedBinding != null) {
28965 // Set the value directly to avoid an infinite call stack.
28966 checkedBinding.binding.value = false;
28967 }
28968 }
28969 }
28970 }
28971 }
28972
28973 // TODO(jmesserly): do we need to polyfill document.contains for IE?
28974 bool _isNodeInDocument(Node node) => node.document.contains(node);
28975
28976 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
28977 // Returns an array containing all radio buttons other than |element| that
28978 // have the same |name|, either in the form that |element| belongs to or,
28979 // if no form, in the document tree to which |element| belongs.
28980 //
28981 // This implementation is based upon the HTML spec definition of a
28982 // "radio button group":
28983 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht ml#radio-button-group
28984 //
28985 Iterable<Element> _getAssociatedRadioButtons(element) {
28986 if (!_isNodeInDocument(element)) return [];
28987 if (element.form != null) {
28988 return element.form.nodes.where((el) {
28989 return el != element &&
28990 el is InputElement &&
28991 el.type == 'radio' &&
28992 el.name == element.name;
28993 });
28994 } else {
28995 var radios = element.document.queryAll(
28996 'input[type="radio"][name="${element.name}"]');
28997 return radios.where((el) => el != element && el.form == null);
28998 }
28999 }
29000
29001 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
29002 var clone = node.clone(false); // Shallow clone.
29003 if (clone is Element && clone.isTemplate) {
29004 TemplateElement.decorate(clone, node);
29005 if (syntax != null) {
29006 clone.attributes.putIfAbsent('syntax', () => syntax);
29007 }
29008 }
29009
29010 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
29011 clone.nodes.add(_createDeepCloneAndDecorateTemplates(c, syntax));
29012 }
29013 return clone;
29014 }
29015
29016 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df n-template-contents-owner
29017 Document _getTemplateContentsOwner(Document doc) {
29018 if (doc.window == null) {
29019 return doc;
29020 }
29021 var d = doc._templateContentsOwner;
29022 if (d == null) {
29023 // TODO(arv): This should either be a Document or HTMLDocument depending
29024 // on doc.
29025 d = doc.implementation.createHtmlDocument('');
29026 while (d.$dom_lastChild != null) {
29027 d.$dom_lastChild.remove();
29028 }
29029 doc._templateContentsOwner = d;
29030 }
29031 return d;
29032 }
29033
29034 Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
29035 var clone = templateElement.clone(false);
29036 var attributes = templateElement.attributes;
29037 for (var name in attributes.keys.toList()) {
29038 switch (name) {
29039 case 'template':
29040 case 'repeat':
29041 case 'bind':
29042 case 'ref':
29043 clone.attributes.remove(name);
29044 break;
29045 default:
29046 attributes.remove(name);
29047 break;
29048 }
29049 }
29050
29051 return clone;
29052 }
29053
29054 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) {
29055 var content = templateElement.content;
29056
29057 if (!templateElement._isAttributeTemplate) {
29058 var child;
29059 while ((child = templateElement.$dom_firstChild) != null) {
29060 content.nodes.add(child);
29061 }
29062 return;
29063 }
29064
29065 // For attribute templates we copy the whole thing into the content and
29066 // we move the non template attributes into the content.
29067 //
29068 // <tr foo template>
29069 //
29070 // becomes
29071 //
29072 // <tr template>
29073 // + #document-fragment
29074 // + <tr foo>
29075 //
29076 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
29077 var child;
29078 while ((child = templateElement.$dom_firstChild) != null) {
29079 newRoot.nodes.add(child);
29080 }
29081 content.nodes.add(newRoot);
29082 }
29083
29084 void _bootstrapTemplatesRecursivelyFrom(Node node) {
29085 void bootstrap(template) {
29086 if (!TemplateElement.decorate(template)) {
29087 _bootstrapTemplatesRecursivelyFrom(template.content);
29088 }
29089 }
29090
29091 // Need to do this first as the contents may get lifted if |node| is
29092 // template.
29093 // TODO(jmesserly): node is DocumentFragment or Element
29094 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors);
29095 if (node is Element && node.isTemplate) bootstrap(node);
29096
29097 templateDescendents.forEach(bootstrap);
29098 }
29099
29100 final String _allTemplatesSelectors = 'template, option[template], ' +
29101 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
29102
29103 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) {
29104 if (node is Element) {
29105 _addAttributeBindings(node, model, syntax);
29106 } else if (node is Text) {
29107 _parseAndBind(node, node.text, 'text', model, syntax);
29108 }
29109
29110 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
29111 _addBindings(c, model, syntax);
29112 }
29113 }
29114
29115
29116 void _addAttributeBindings(Element element, model, syntax) {
29117 element.attributes.forEach((name, value) {
29118 if (value == '' && (name == 'bind' || name == 'repeat')) {
29119 value = '{{}}';
29120 }
29121 _parseAndBind(element, value, name, model, syntax);
29122 });
29123 }
29124
29125 void _parseAndBind(Node node, String text, String name, model,
29126 CustomBindingSyntax syntax) {
29127
29128 var tokens = _parseMustacheTokens(text);
29129 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
29130 return;
29131 }
29132
29133 if (tokens.length == 1 && tokens[0].isBinding) {
29134 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
29135 return;
29136 }
29137
29138 var replacementBinding = new CompoundBinding();
29139 for (var i = 0; i < tokens.length; i++) {
29140 var token = tokens[i];
29141 if (token.isBinding) {
29142 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
29143 }
29144 }
29145
29146 replacementBinding.combinator = (values) {
29147 var newValue = new StringBuffer();
29148
29149 for (var i = 0; i < tokens.length; i++) {
29150 var token = tokens[i];
29151 if (token.isText) {
29152 newValue.write(token.value);
29153 } else {
29154 var value = values[i];
29155 if (value != null) {
29156 newValue.write(value);
29157 }
29158 }
29159 }
29160
29161 return newValue.toString();
29162 };
29163
29164 node.bind(name, replacementBinding, 'value');
29165 }
29166
29167 void _bindOrDelegate(node, name, model, String path,
29168 CustomBindingSyntax syntax) {
29169
29170 if (syntax != null) {
29171 var delegateBinding = syntax.getBinding(model, path, name, node);
29172 if (delegateBinding != null) {
29173 model = delegateBinding;
29174 path = 'value';
29175 }
29176 }
29177
29178 node.bind(name, model, path);
29179 }
29180
29181 class _BindingToken {
29182 final String value;
29183 final bool isBinding;
29184
29185 _BindingToken(this.value, {this.isBinding: false});
29186
29187 bool get isText => !isBinding;
29188 }
29189
29190 List<_BindingToken> _parseMustacheTokens(String s) {
29191 var result = [];
29192 var length = s.length;
29193 var index = 0, lastIndex = 0;
29194 while (lastIndex < length) {
29195 index = s.indexOf('{{', lastIndex);
29196 if (index < 0) {
29197 result.add(new _BindingToken(s.substring(lastIndex)));
29198 break;
29199 } else {
29200 // There is a non-empty text run before the next path token.
29201 if (index > 0 && lastIndex < index) {
29202 result.add(new _BindingToken(s.substring(lastIndex, index)));
29203 }
29204 lastIndex = index + 2;
29205 index = s.indexOf('}}', lastIndex);
29206 if (index < 0) {
29207 var text = s.substring(lastIndex - 2);
29208 if (result.length > 0 && result.last.isText) {
29209 result.last.value += text;
29210 } else {
29211 result.add(new _BindingToken(text));
29212 }
29213 break;
29214 }
29215
29216 var value = s.substring(lastIndex, index).trim();
29217 result.add(new _BindingToken(value, isBinding: true));
29218 lastIndex = index + 2;
29219 }
29220 }
29221 return result;
29222 }
29223
29224 void _addTemplateInstanceRecord(fragment, model) {
29225 if (fragment.$dom_firstChild == null) {
29226 return;
29227 }
29228
29229 var instanceRecord = new TemplateInstance(
29230 fragment.$dom_firstChild, fragment.$dom_lastChild, model);
29231
29232 var node = instanceRecord.firstNode;
29233 while (node != null) {
29234 node._templateInstance = instanceRecord;
29235 node = node.nextNode;
29236 }
29237 }
29238
29239 void _removeAllBindingsRecursively(Node node) {
29240 node.unbindAll();
29241 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
29242 _removeAllBindingsRecursively(c);
29243 }
29244 }
29245
29246 void _removeTemplateChild(Node parent, Node child) {
29247 child._templateInstance = null;
29248 if (child is Element && child.isTemplate) {
29249 // Make sure we stop observing when we remove an element.
29250 var templateIterator = child._templateIterator;
29251 if (templateIterator != null) {
29252 templateIterator.abandon();
29253 child._templateIterator = null;
29254 }
29255 }
29256 child.remove();
29257 _removeAllBindingsRecursively(child);
29258 }
29259
29260 class _InstanceCursor {
29261 final Element _template;
29262 Node _terminator;
29263 Node _previousTerminator;
29264 int _previousIndex = -1;
29265 int _index = 0;
29266
29267 _InstanceCursor(this._template, [index]) {
29268 _terminator = _template;
29269 if (index != null) {
29270 while (index-- > 0) {
29271 next();
29272 }
29273 }
29274 }
29275
29276 void next() {
29277 _previousTerminator = _terminator;
29278 _previousIndex = _index;
29279 _index++;
29280
29281 while (_index > _terminator._instanceTerminatorCount) {
29282 _index -= _terminator._instanceTerminatorCount;
29283 _terminator = _terminator.nextNode;
29284 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
29285 _index += _instanceCount(_terminator);
29286 }
29287 }
29288 }
29289
29290 void abandon() {
29291 assert(_instanceCount(_template) > 0);
29292 assert(_terminator._instanceTerminatorCount > 0);
29293 assert(_index > 0);
29294
29295 _terminator._instanceTerminatorCount--;
29296 _index--;
29297 }
29298
29299 void insert(fragment) {
29300 assert(_template.parentNode != null);
29301
29302 _previousTerminator = _terminator;
29303 _previousIndex = _index;
29304 _index++;
29305
29306 _terminator = fragment.$dom_lastChild;
29307 if (_terminator == null) _terminator = _previousTerminator;
29308 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
29309
29310 _terminator._instanceTerminatorCount++;
29311 if (_terminator != _previousTerminator) {
29312 while (_previousTerminator._instanceTerminatorCount >
29313 _previousIndex) {
29314 _previousTerminator._instanceTerminatorCount--;
29315 _terminator._instanceTerminatorCount++;
29316 }
29317 }
29318 }
29319
29320 void remove() {
29321 assert(_previousIndex != -1);
29322 assert(_previousTerminator != null &&
29323 (_previousIndex > 0 || _previousTerminator == _template));
29324 assert(_terminator != null && _index > 0);
29325 assert(_template.parentNode != null);
29326 assert(_instanceCount(_template) > 0);
29327
29328 if (_previousTerminator == _terminator) {
29329 assert(_index == _previousIndex + 1);
29330 _terminator._instanceTerminatorCount--;
29331 _terminator = _template;
29332 _previousTerminator = null;
29333 _previousIndex = -1;
29334 return;
29335 }
29336
29337 _terminator._instanceTerminatorCount--;
29338
29339 var parent = _template.parentNode;
29340 while (_previousTerminator.nextNode != _terminator) {
29341 _removeTemplateChild(parent, _previousTerminator.nextNode);
29342 }
29343 _removeTemplateChild(parent, _terminator);
29344
29345 _terminator = _previousTerminator;
29346 _index = _previousIndex;
29347 _previousTerminator = null;
29348 _previousIndex = -1; // 0?
29349 }
29350 }
29351
29352
29353 class _TemplateIterator {
29354 final Element _templateElement;
29355 int instanceCount = 0;
29356 List iteratedValue;
29357 bool observing = false;
29358 CompoundBinding inputs;
29359 _Binding valueBinding;
29360 StreamSubscription _sub;
29361
29362 _TemplateIterator(this._templateElement) {
29363 inputs = new CompoundBinding(resolveInputs);
29364 valueBinding = new _Binding(inputs, 'value', valueChanged);
29365 }
29366
29367 Object resolveInputs(Map values) {
29368 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) {
29369 return null;
29370 }
29371
29372 if (values.containsKey('repeat')) {
29373 return values['repeat'];
29374 }
29375
29376 if (values.containsKey('bind')) {
29377 return [values['bind']];
29378 }
29379
29380 return null;
29381 }
29382
29383 void valueChanged(value) {
29384 clear();
29385 if (value is! List) return;
29386
29387 iteratedValue = value;
29388
29389 if (value is Observable) {
29390 _sub = value.changes.listen(handleChange);
29391 }
29392
29393 handleSplices([new ListChangeDelta(0, addedCount: iteratedValue.length)]);
29394 }
29395
29396 // TODO(jmesserly): these properties appear not be finished. I think it's
29397 // part of custom syntax like repeat="i in items".
29398 getInstanceModel(model, syntax) => model;
29399 getInstanceFragment(syntax) => _templateElement.createInstance();
29400
29401 void handleChange(List<ChangeRecord> records) {
29402 print('!!! got records $records');
29403
29404 handleSplices(summarizeListChanges(iteratedValue, records));
29405 }
29406
29407 void handleSplices(List<ListChangeDelta> splices) {
29408 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
29409
29410 print('!!! got splices $splices');
29411
29412 for (var splice in splices) {
29413 for (var removed in splice.removed) {
29414 var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
29415 cursor.remove();
29416 instanceCount--;
29417 }
29418
29419 for (var addIndex = splice.index;
29420 addIndex < splice.index + splice.addedCount;
29421 addIndex++) {
29422
29423 var model = getInstanceModel(iteratedValue[addIndex], syntax);
29424 var fragment = getInstanceFragment(syntax);
29425
29426 _addBindings(fragment, model, syntax);
29427 _addTemplateInstanceRecord(fragment, model);
29428
29429 var cursor = new _InstanceCursor(_templateElement, addIndex);
29430 cursor.insert(fragment);
29431 instanceCount++;
29432 }
29433 }
29434 }
29435
29436 void unobserve() {
29437 if (_sub == null) return;
29438 _sub.cancel();
29439 _sub = null;
29440 }
29441
29442 void clear() {
29443 unobserve();
29444
29445 iteratedValue = null;
29446 if (instanceCount == 0) return;
29447
29448 for (var i = 0; i < instanceCount; i++) {
29449 var cursor = new _InstanceCursor(_templateElement, 1);
29450 cursor.remove();
29451 }
29452
29453 instanceCount = 0;
29454 }
29455
29456 void abandon() {
29457 unobserve();
29458 valueBinding.dispose();
29459 inputs.dispose();
29460 }
29461 }
29462
29463 int _instanceCount(Element element) {
29464 var templateIterator = element._templateIterator;
29465 return templateIterator != null ? templateIterator.instanceCount : 0;
29466 }
28392 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 29467 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28393 // for details. All rights reserved. Use of this source code is governed by a 29468 // for details. All rights reserved. Use of this source code is governed by a
28394 // BSD-style license that can be found in the LICENSE file. 29469 // BSD-style license that can be found in the LICENSE file.
28395 29470
28396 29471
28397 class _HttpRequestUtils { 29472 class _HttpRequestUtils {
28398 29473
28399 // Helper for factory HttpRequest.get 29474 // Helper for factory HttpRequest.get
28400 static HttpRequest get(String url, 29475 static HttpRequest get(String url,
28401 onComplete(HttpRequest request), 29476 onComplete(HttpRequest request),
(...skipping 763 matching lines...) Expand 10 before | Expand all | Expand 10 after
29165 DateTime _convertNativeToDart_DateTime(date) { 30240 DateTime _convertNativeToDart_DateTime(date) {
29166 var millisSinceEpoch = JS('int', '#.getTime()', date); 30241 var millisSinceEpoch = JS('int', '#.getTime()', date);
29167 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); 30242 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true);
29168 } 30243 }
29169 30244
29170 _convertDartToNative_DateTime(DateTime date) { 30245 _convertDartToNative_DateTime(DateTime date) {
29171 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); 30246 return JS('', 'new Date(#)', date.millisecondsSinceEpoch);
29172 } 30247 }
29173 30248
29174 WindowBase _convertNativeToDart_Window(win) { 30249 WindowBase _convertNativeToDart_Window(win) {
30250 if (win == null) return null;
29175 return _DOMWindowCrossFrame._createSafe(win); 30251 return _DOMWindowCrossFrame._createSafe(win);
29176 } 30252 }
29177 30253
29178 EventTarget _convertNativeToDart_EventTarget(e) { 30254 EventTarget _convertNativeToDart_EventTarget(e) {
29179 if (e == null) { 30255 if (e == null) {
29180 return null; 30256 return null;
29181 } 30257 }
29182 // Assume it's a Window if it contains the setInterval property. It may be 30258 // Assume it's a Window if it contains the setInterval property. It may be
29183 // from a different frame - without a patched prototype - so we cannot 30259 // from a different frame - without a patched prototype - so we cannot
29184 // rely on Dart type checking. 30260 // rely on Dart type checking.
(...skipping 407 matching lines...) Expand 10 before | Expand all | Expand 10 after
29592 _position = nextPosition; 30668 _position = nextPosition;
29593 return true; 30669 return true;
29594 } 30670 }
29595 _current = null; 30671 _current = null;
29596 _position = _array.length; 30672 _position = _array.length;
29597 return false; 30673 return false;
29598 } 30674 }
29599 30675
29600 T get current => _current; 30676 T get current => _current;
29601 } 30677 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698