| OLD | NEW |
| 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' hide Symbol; |
| 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:mdv_observe_impl'; |
| 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 6008 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 6030 @DomName('Document.cookie') | 6031 @DomName('Document.cookie') |
| 6031 @DocsEditable | 6032 @DocsEditable |
| 6032 String cookie; | 6033 String cookie; |
| 6033 | 6034 |
| 6034 WindowBase get window => _convertNativeToDart_Window(this._get_window); | 6035 WindowBase get window => _convertNativeToDart_Window(this._get_window); |
| 6035 @JSName('defaultView') | 6036 @JSName('defaultView') |
| 6036 @DomName('Document.window') | 6037 @DomName('Document.window') |
| 6037 @DocsEditable | 6038 @DocsEditable |
| 6038 @Creates('Window|=Object') | 6039 @Creates('Window|=Object') |
| 6039 @Returns('Window|=Object') | 6040 @Returns('Window|=Object') |
| 6041 @Creates('Window|=Object|Null') |
| 6042 @Returns('Window|=Object|Null') |
| 6040 final dynamic _get_window; | 6043 final dynamic _get_window; |
| 6041 | 6044 |
| 6042 @DomName('Document.documentElement') | 6045 @DomName('Document.documentElement') |
| 6043 @DocsEditable | 6046 @DocsEditable |
| 6044 final Element documentElement; | 6047 final Element documentElement; |
| 6045 | 6048 |
| 6046 @DomName('Document.domain') | 6049 @DomName('Document.domain') |
| 6047 @DocsEditable | 6050 @DocsEditable |
| 6048 final String domain; | 6051 final String domain; |
| 6049 | 6052 |
| (...skipping 586 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 6636 | 6639 |
| 6637 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or | 6640 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or |
| 6638 // SVG strings? | 6641 // SVG strings? |
| 6639 void set innerHtml(String value) { | 6642 void set innerHtml(String value) { |
| 6640 this.nodes.clear(); | 6643 this.nodes.clear(); |
| 6641 | 6644 |
| 6642 final e = new Element.tag("div"); | 6645 final e = new Element.tag("div"); |
| 6643 e.innerHtml = value; | 6646 e.innerHtml = value; |
| 6644 | 6647 |
| 6645 // Copy list first since we don't want liveness during iteration. | 6648 // Copy list first since we don't want liveness during iteration. |
| 6646 List nodes = new List.from(e.nodes); | 6649 List nodes = new List.from(e.nodes, growable: false); |
| 6647 this.nodes.addAll(nodes); | 6650 this.nodes.addAll(nodes); |
| 6648 } | 6651 } |
| 6649 | 6652 |
| 6650 /** | 6653 /** |
| 6651 * Adds the specified text as a text node after the last child of this | 6654 * Adds the specified text as a text node after the last child of this |
| 6652 * document fragment. | 6655 * document fragment. |
| 6653 */ | 6656 */ |
| 6654 void appendText(String text) { | 6657 void appendText(String text) { |
| 6655 this.append(new Text(text)); | 6658 this.append(new Text(text)); |
| 6656 } | 6659 } |
| (...skipping 361 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 7018 if (result == null) throw new StateError("No elements"); | 7021 if (result == null) throw new StateError("No elements"); |
| 7019 return result; | 7022 return result; |
| 7020 } | 7023 } |
| 7021 | 7024 |
| 7022 Element get single { | 7025 Element get single { |
| 7023 if (length > 1) throw new StateError("More than one element"); | 7026 if (length > 1) throw new StateError("More than one element"); |
| 7024 return first; | 7027 return first; |
| 7025 } | 7028 } |
| 7026 } | 7029 } |
| 7027 | 7030 |
| 7028 /** | 7031 /** |
| 7029 * An immutable list containing HTML elements. This list contains some | 7032 * An immutable list containing HTML elements. This list contains some |
| 7030 * additional methods for ease of CSS manipulation on a group of elements. | 7033 * additional methods for ease of CSS manipulation on a group of elements. |
| 7031 */ | 7034 */ |
| 7032 abstract class ElementList<T extends Element> extends ListBase<T> { | 7035 abstract class ElementList<T extends Element> extends ListBase<T> { |
| 7033 /** | 7036 /** |
| 7034 * The union of all CSS classes applied to the elements in this list. | 7037 * The union of all CSS classes applied to the elements in this list. |
| 7035 * | 7038 * |
| 7036 * This set makes it easy to add, remove or toggle (add if not present, remove | 7039 * This set makes it easy to add, remove or toggle (add if not present, remove |
| 7037 * if present) the classes applied to a collection of elements. | 7040 * if present) the classes applied to a collection of elements. |
| 7038 * | 7041 * |
| (...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 7500 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { | 7503 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { |
| 7501 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); | 7504 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); |
| 7502 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { | 7505 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { |
| 7503 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); | 7506 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); |
| 7504 } else if (JS('bool', '!!#.msMatchesSelector', this)) { | 7507 } else if (JS('bool', '!!#.msMatchesSelector', this)) { |
| 7505 return JS('bool', '#.msMatchesSelector(#)', this, selectors); | 7508 return JS('bool', '#.msMatchesSelector(#)', this, selectors); |
| 7506 } | 7509 } |
| 7507 throw new UnsupportedError("Not supported on this platform"); | 7510 throw new UnsupportedError("Not supported on this platform"); |
| 7508 } | 7511 } |
| 7509 | 7512 |
| 7513 @Creates('Null') |
| 7514 Map<String, StreamSubscription> _attributeBindings; |
| 7515 |
| 7516 // TODO(jmesserly): I'm concerned about adding these to every element. |
| 7517 // Conceptually all of these belong on TemplateElement. They are here to |
| 7518 // support browsers that don't have <template> yet. |
| 7519 // However even in the polyfill they're restricted to certain tags |
| 7520 // (see [isTemplate]). So we can probably convert it to a (public) mixin, and |
| 7521 // only mix it in to the elements that need it. |
| 7522 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 7523 var _model; |
| 7524 |
| 7525 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 7526 _TemplateIterator _templateIterator; |
| 7527 |
| 7528 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 7529 Element _templateInstanceRef; |
| 7530 |
| 7531 // Note: only used if `this is! TemplateElement` |
| 7532 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 7533 DocumentFragment _templateContent; |
| 7534 |
| 7535 bool _templateIsDecorated; |
| 7536 |
| 7537 // TODO(jmesserly): should path be optional, and default to empty path? |
| 7538 // It is used that way in at least one path in JS TemplateElement tests |
| 7539 // (see "BindImperative" test in original JS code). |
| 7540 @Experimental |
| 7541 void bind(String name, model, String path) { |
| 7542 _bindElement(this, name, model, path); |
| 7543 } |
| 7544 |
| 7545 // TODO(jmesserly): this is static to work around http://dartbug.com/10166 |
| 7546 // Similar issue for unbind/unbindAll below. |
| 7547 static void _bindElement(Element self, String name, model, String path) { |
| 7548 if (self._bindTemplate(name, model, path)) return; |
| 7549 |
| 7550 if (self._attributeBindings == null) { |
| 7551 self._attributeBindings = new Map<String, StreamSubscription>(); |
| 7552 } |
| 7553 |
| 7554 self.attributes.remove(name); |
| 7555 |
| 7556 var changed; |
| 7557 if (name.endsWith('?')) { |
| 7558 name = name.substring(0, name.length - 1); |
| 7559 |
| 7560 changed = (value) { |
| 7561 if (_templateBooleanConversion(value)) { |
| 7562 self.attributes[name] = ''; |
| 7563 } else { |
| 7564 self.attributes.remove(name); |
| 7565 } |
| 7566 }; |
| 7567 } else { |
| 7568 changed = (value) { |
| 7569 // TODO(jmesserly): escape value if needed to protect against XSS. |
| 7570 // See https://github.com/toolkitchen/mdv/issues/58 |
| 7571 self.attributes[name] = value == null ? '' : '$value'; |
| 7572 }; |
| 7573 } |
| 7574 |
| 7575 self.unbind(name); |
| 7576 |
| 7577 self._attributeBindings[name] = |
| 7578 new PathObserver(model, path).bindSync(changed); |
| 7579 } |
| 7580 |
| 7581 @Experimental |
| 7582 void unbind(String name) { |
| 7583 _unbindElement(this, name); |
| 7584 } |
| 7585 |
| 7586 static _unbindElement(Element self, String name) { |
| 7587 if (self._unbindTemplate(name)) return; |
| 7588 if (self._attributeBindings != null) { |
| 7589 var binding = self._attributeBindings.remove(name); |
| 7590 if (binding != null) binding.cancel(); |
| 7591 } |
| 7592 } |
| 7593 |
| 7594 @Experimental |
| 7595 void unbindAll() { |
| 7596 _unbindAllElement(this); |
| 7597 } |
| 7598 |
| 7599 static void _unbindAllElement(Element self) { |
| 7600 self._unbindAllTemplate(); |
| 7601 |
| 7602 if (self._attributeBindings != null) { |
| 7603 for (var binding in self._attributeBindings.values) { |
| 7604 binding.cancel(); |
| 7605 } |
| 7606 self._attributeBindings = null; |
| 7607 } |
| 7608 } |
| 7609 |
| 7610 // TODO(jmesserly): unlike the JS polyfill, we can't mixin |
| 7611 // HTMLTemplateElement at runtime into things that are semantically template |
| 7612 // elements. So instead we implement it here with a runtime check. |
| 7613 // If the bind succeeds, we return true, otherwise we return false and let |
| 7614 // the normal Element.bind logic kick in. |
| 7615 bool _bindTemplate(String name, model, String path) { |
| 7616 if (isTemplate) { |
| 7617 switch (name) { |
| 7618 case 'bind': |
| 7619 case 'repeat': |
| 7620 case 'if': |
| 7621 _ensureTemplate(); |
| 7622 if (_templateIterator == null) { |
| 7623 _templateIterator = new _TemplateIterator(this); |
| 7624 } |
| 7625 _templateIterator.inputs.bind(name, model, path); |
| 7626 return true; |
| 7627 } |
| 7628 } |
| 7629 return false; |
| 7630 } |
| 7631 |
| 7632 bool _unbindTemplate(String name) { |
| 7633 if (isTemplate) { |
| 7634 switch (name) { |
| 7635 case 'bind': |
| 7636 case 'repeat': |
| 7637 case 'if': |
| 7638 _ensureTemplate(); |
| 7639 if (_templateIterator != null) { |
| 7640 _templateIterator.inputs.unbind(name); |
| 7641 } |
| 7642 return true; |
| 7643 } |
| 7644 } |
| 7645 return false; |
| 7646 } |
| 7647 |
| 7648 void _unbindAllTemplate() { |
| 7649 if (isTemplate) { |
| 7650 unbind('bind'); |
| 7651 unbind('repeat'); |
| 7652 unbind('if'); |
| 7653 } |
| 7654 } |
| 7655 |
| 7656 /** |
| 7657 * Gets the template this node refers to. |
| 7658 * This is only supported if [isTemplate] is true. |
| 7659 */ |
| 7660 @Experimental |
| 7661 Element get ref { |
| 7662 _ensureTemplate(); |
| 7663 |
| 7664 Element ref = null; |
| 7665 var refId = attributes['ref']; |
| 7666 if (refId != null) { |
| 7667 ref = document.getElementById(refId); |
| 7668 } |
| 7669 |
| 7670 return ref != null ? ref : _templateInstanceRef; |
| 7671 } |
| 7672 |
| 7673 /** |
| 7674 * Gets the content of this template. |
| 7675 * This is only supported if [isTemplate] is true. |
| 7676 */ |
| 7677 @Experimental |
| 7678 DocumentFragment get content { |
| 7679 _ensureTemplate(); |
| 7680 return _templateContent; |
| 7681 } |
| 7682 |
| 7683 /** |
| 7684 * Creates an instance of the template. |
| 7685 * This is only supported if [isTemplate] is true. |
| 7686 */ |
| 7687 @Experimental |
| 7688 DocumentFragment createInstance() { |
| 7689 _ensureTemplate(); |
| 7690 |
| 7691 var template = ref; |
| 7692 if (template == null) template = this; |
| 7693 |
| 7694 var instance = _createDeepCloneAndDecorateTemplates(template.content, |
| 7695 attributes['syntax']); |
| 7696 |
| 7697 if (TemplateElement._instanceCreated != null) { |
| 7698 TemplateElement._instanceCreated.add(instance); |
| 7699 } |
| 7700 return instance; |
| 7701 } |
| 7702 |
| 7703 /** |
| 7704 * The data model which is inherited through the tree. |
| 7705 * This is only supported if [isTemplate] is true. |
| 7706 * |
| 7707 * Setting this will destructive propagate the value to all descendant nodes, |
| 7708 * and reinstantiate all of the nodes expanded by this template. |
| 7709 * |
| 7710 * Currently this does not support propagation through Shadow DOMs. |
| 7711 */ |
| 7712 @Experimental |
| 7713 get model => _model; |
| 7714 |
| 7715 @Experimental |
| 7716 void set model(value) { |
| 7717 _ensureTemplate(); |
| 7718 |
| 7719 _model = value; |
| 7720 _addBindings(this, model); |
| 7721 } |
| 7722 |
| 7723 // TODO(jmesserly): const set would be better |
| 7724 static const _TABLE_TAGS = const { |
| 7725 'caption': null, |
| 7726 'col': null, |
| 7727 'colgroup': null, |
| 7728 'tbody': null, |
| 7729 'td': null, |
| 7730 'tfoot': null, |
| 7731 'th': null, |
| 7732 'thead': null, |
| 7733 'tr': null, |
| 7734 }; |
| 7735 |
| 7736 bool get _isAttributeTemplate => attributes.containsKey('template') && |
| 7737 (localName == 'option' || _TABLE_TAGS.containsKey(localName)); |
| 7738 |
| 7739 /** |
| 7740 * Returns true if this node is a template. |
| 7741 * |
| 7742 * A node is a template if [tagName] is TEMPLATE, or the node has the |
| 7743 * 'template' attribute and this tag supports attribute form for backwards |
| 7744 * compatibility with existing HTML parsers. The nodes that can use attribute |
| 7745 * form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP |
| 7746 * and COL) and OPTION. |
| 7747 */ |
| 7748 // TODO(jmesserly): this is not a public MDV API, but it seems like a useful |
| 7749 // place to document which tags our polyfill considers to be templates. |
| 7750 // Otherwise I'd be repeating it in several other places. |
| 7751 // See if we can replace this with a TemplateMixin. |
| 7752 @Experimental |
| 7753 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate; |
| 7754 |
| 7755 void _ensureTemplate() { |
| 7756 if (!isTemplate) { |
| 7757 throw new UnsupportedError('$this is not a template.'); |
| 7758 } |
| 7759 TemplateElement.decorate(this); |
| 7760 } |
| 7761 |
| 7510 | 7762 |
| 7511 @DomName('Element.abortEvent') | 7763 @DomName('Element.abortEvent') |
| 7512 @DocsEditable | 7764 @DocsEditable |
| 7513 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider
<Event>('abort'); | 7765 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider
<Event>('abort'); |
| 7514 | 7766 |
| 7515 @DomName('Element.beforecopyEvent') | 7767 @DomName('Element.beforecopyEvent') |
| 7516 @DocsEditable | 7768 @DocsEditable |
| 7517 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro
vider<Event>('beforecopy'); | 7769 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro
vider<Event>('beforecopy'); |
| 7518 | 7770 |
| 7519 @DomName('Element.beforecutEvent') | 7771 @DomName('Element.beforecutEvent') |
| (...skipping 724 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 8244 @DomName('Element.onwebkitfullscreenchange') | 8496 @DomName('Element.onwebkitfullscreenchange') |
| 8245 @DocsEditable | 8497 @DocsEditable |
| 8246 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); | 8498 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); |
| 8247 | 8499 |
| 8248 @DomName('Element.onwebkitfullscreenerror') | 8500 @DomName('Element.onwebkitfullscreenerror') |
| 8249 @DocsEditable | 8501 @DocsEditable |
| 8250 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); | 8502 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); |
| 8251 | 8503 |
| 8252 } | 8504 } |
| 8253 | 8505 |
| 8506 |
| 8254 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); | 8507 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); |
| 8255 class _ElementFactoryProvider { | 8508 class _ElementFactoryProvider { |
| 8256 static const _CUSTOM_PARENT_TAG_MAP = const { | 8509 static const _CUSTOM_PARENT_TAG_MAP = const { |
| 8257 'body' : 'html', | 8510 'body' : 'html', |
| 8258 'head' : 'html', | 8511 'head' : 'html', |
| 8259 'caption' : 'table', | 8512 'caption' : 'table', |
| 8260 'td': 'tr', | 8513 'td': 'tr', |
| 8261 'th': 'tr', | 8514 'th': 'tr', |
| 8262 'colgroup': 'table', | 8515 'colgroup': 'table', |
| 8263 'col' : 'colgroup', | 8516 'col' : 'colgroup', |
| 8264 'tr' : 'tbody', | 8517 'tr' : 'tbody', |
| 8265 'tbody' : 'table', | 8518 'tbody' : 'table', |
| 8266 'tfoot' : 'table', | 8519 'tfoot' : 'table', |
| 8267 'thead' : 'table', | 8520 'thead' : 'table', |
| 8268 'track' : 'audio', | 8521 'track' : 'audio', |
| 8269 }; | 8522 }; |
| 8270 | 8523 |
| 8271 // TODO(jmesserly): const set would be better | |
| 8272 static const _TABLE_TAGS = const { | |
| 8273 'caption': null, | |
| 8274 'col': null, | |
| 8275 'colgroup': null, | |
| 8276 'tbody': null, | |
| 8277 'td': null, | |
| 8278 'tfoot': null, | |
| 8279 'th': null, | |
| 8280 'thead': null, | |
| 8281 'tr': null, | |
| 8282 }; | |
| 8283 | |
| 8284 @DomName('Document.createElement') | 8524 @DomName('Document.createElement') |
| 8285 static Element createElement_html(String html) { | 8525 static Element createElement_html(String html) { |
| 8286 // TODO(jacobr): this method can be made more robust and performant. | 8526 // TODO(jacobr): this method can be made more robust and performant. |
| 8287 // 1) Cache the dummy parent elements required to use innerHTML rather than | 8527 // 1) Cache the dummy parent elements required to use innerHTML rather than |
| 8288 // creating them every call. | 8528 // creating them every call. |
| 8289 // 2) Verify that the html does not contain leading or trailing text nodes. | 8529 // 2) Verify that the html does not contain leading or trailing text nodes. |
| 8290 // 3) Verify that the html does not contain both <head> and <body> tags. | 8530 // 3) Verify that the html does not contain both <head> and <body> tags. |
| 8291 // 4) Detatch the created element from its dummy parent. | 8531 // 4) Detatch the created element from its dummy parent. |
| 8292 String parentTag = 'div'; | 8532 String parentTag = 'div'; |
| 8293 String tag; | 8533 String tag; |
| 8294 final match = _START_TAG_REGEXP.firstMatch(html); | 8534 final match = _START_TAG_REGEXP.firstMatch(html); |
| 8295 if (match != null) { | 8535 if (match != null) { |
| 8296 tag = match.group(1).toLowerCase(); | 8536 tag = match.group(1).toLowerCase(); |
| 8297 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { | 8537 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { |
| 8298 return _createTableForIE(html, tag); | 8538 return _createTableForIE(html, tag); |
| 8299 } | 8539 } |
| 8300 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; | 8540 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; |
| 8301 if (parentTag == null) parentTag = 'div'; | 8541 if (parentTag == null) parentTag = 'div'; |
| 8302 } | 8542 } |
| 8303 | 8543 |
| 8304 final temp = new Element.tag(parentTag); | 8544 final temp = new Element.tag(parentTag); |
| 8305 temp.innerHtml = html; | 8545 temp.innerHtml = html; |
| 8306 | 8546 |
| 8307 Element element; | 8547 Element element; |
| (...skipping 1897 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 10205 @SupportedBrowser(SupportedBrowser.SAFARI) | 10445 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 10206 @Experimental | 10446 @Experimental |
| 10207 Element get pointerLockElement => | 10447 Element get pointerLockElement => |
| 10208 $dom_webkitPointerLockElement; | 10448 $dom_webkitPointerLockElement; |
| 10209 | 10449 |
| 10210 @DomName('Document.webkitVisibilityState') | 10450 @DomName('Document.webkitVisibilityState') |
| 10211 @SupportedBrowser(SupportedBrowser.CHROME) | 10451 @SupportedBrowser(SupportedBrowser.CHROME) |
| 10212 @SupportedBrowser(SupportedBrowser.SAFARI) | 10452 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 10213 @Experimental | 10453 @Experimental |
| 10214 String get visibilityState => $dom_webkitVisibilityState; | 10454 String get visibilityState => $dom_webkitVisibilityState; |
| 10455 |
| 10456 |
| 10457 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 10458 // Note: used to polyfill <template> |
| 10459 Document _templateContentsOwner; |
| 10215 } | 10460 } |
| 10216 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 10461 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 10217 // for details. All rights reserved. Use of this source code is governed by a | 10462 // for details. All rights reserved. Use of this source code is governed by a |
| 10218 // BSD-style license that can be found in the LICENSE file. | 10463 // BSD-style license that can be found in the LICENSE file. |
| 10219 | 10464 |
| 10220 | 10465 |
| 10221 @DocsEditable | 10466 @DocsEditable |
| 10222 @DomName('HTMLHtmlElement') | 10467 @DomName('HTMLHtmlElement') |
| 10223 class HtmlElement extends Element native "HTMLHtmlElement" { | 10468 class HtmlElement extends Element native "HTMLHtmlElement" { |
| 10224 | 10469 |
| (...skipping 816 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11041 var e = document.$dom_createElement("input"); | 11286 var e = document.$dom_createElement("input"); |
| 11042 if (type != null) { | 11287 if (type != null) { |
| 11043 try { | 11288 try { |
| 11044 // IE throws an exception for unknown types. | 11289 // IE throws an exception for unknown types. |
| 11045 e.type = type; | 11290 e.type = type; |
| 11046 } catch(_) {} | 11291 } catch(_) {} |
| 11047 } | 11292 } |
| 11048 return e; | 11293 return e; |
| 11049 } | 11294 } |
| 11050 | 11295 |
| 11296 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 11297 _ValueBinding _valueBinding; |
| 11298 |
| 11299 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 11300 _CheckedBinding _checkedBinding; |
| 11301 |
| 11302 @Experimental |
| 11303 void bind(String name, model, String path) { |
| 11304 switch (name) { |
| 11305 case 'value': |
| 11306 unbind('value'); |
| 11307 attributes.remove('value'); |
| 11308 _valueBinding = new _ValueBinding(this, model, path); |
| 11309 break; |
| 11310 case 'checked': |
| 11311 unbind('checked'); |
| 11312 attributes.remove('checked'); |
| 11313 _checkedBinding = new _CheckedBinding(this, model, path); |
| 11314 break; |
| 11315 default: |
| 11316 // TODO(jmesserly): this should be "super" (http://dartbug.com/10166). |
| 11317 // Similar issue for unbind/unbindAll below. |
| 11318 Element._bindElement(this, name, model, path); |
| 11319 break; |
| 11320 } |
| 11321 } |
| 11322 |
| 11323 @Experimental |
| 11324 void unbind(String name) { |
| 11325 switch (name) { |
| 11326 case 'value': |
| 11327 if (_valueBinding != null) { |
| 11328 _valueBinding.unbind(); |
| 11329 _valueBinding = null; |
| 11330 } |
| 11331 break; |
| 11332 case 'checked': |
| 11333 if (_checkedBinding != null) { |
| 11334 _checkedBinding.unbind(); |
| 11335 _checkedBinding = null; |
| 11336 } |
| 11337 break; |
| 11338 default: |
| 11339 Element._unbindElement(this, name); |
| 11340 break; |
| 11341 } |
| 11342 } |
| 11343 |
| 11344 @Experimental |
| 11345 void unbindAll() { |
| 11346 unbind('value'); |
| 11347 unbind('checked'); |
| 11348 Element._unbindAllElement(this); |
| 11349 } |
| 11350 |
| 11351 |
| 11051 @DomName('HTMLInputElement.webkitSpeechChangeEvent') | 11352 @DomName('HTMLInputElement.webkitSpeechChangeEvent') |
| 11052 @DocsEditable | 11353 @DocsEditable |
| 11053 @SupportedBrowser(SupportedBrowser.CHROME) | 11354 @SupportedBrowser(SupportedBrowser.CHROME) |
| 11054 @SupportedBrowser(SupportedBrowser.SAFARI) | 11355 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 11055 @Experimental | 11356 @Experimental |
| 11056 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); | 11357 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); |
| 11057 | 11358 |
| 11058 @DomName('HTMLInputElement.accept') | 11359 @DomName('HTMLInputElement.accept') |
| 11059 @DocsEditable | 11360 @DocsEditable |
| 11060 String accept; | 11361 String accept; |
| (...skipping 3251 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 14312 _this.$dom_removeChild(node); | 14613 _this.$dom_removeChild(node); |
| 14313 return true; | 14614 return true; |
| 14314 } | 14615 } |
| 14315 | 14616 |
| 14316 void _filter(bool test(Node node), bool removeMatching) { | 14617 void _filter(bool test(Node node), bool removeMatching) { |
| 14317 // This implementation of removeWhere/retainWhere is more efficient | 14618 // This implementation of removeWhere/retainWhere is more efficient |
| 14318 // than the default in ListBase. Child nodes can be removed in constant | 14619 // than the default in ListBase. Child nodes can be removed in constant |
| 14319 // time. | 14620 // time. |
| 14320 Node child = _this.$dom_firstChild; | 14621 Node child = _this.$dom_firstChild; |
| 14321 while (child != null) { | 14622 while (child != null) { |
| 14322 Node nextChild = child.nextSibling; | 14623 Node nextChild = child.nextNode; |
| 14323 if (test(child) == removeMatching) { | 14624 if (test(child) == removeMatching) { |
| 14324 _this.$dom_removeChild(child); | 14625 _this.$dom_removeChild(child); |
| 14325 } | 14626 } |
| 14326 child = nextChild; | 14627 child = nextChild; |
| 14327 } | 14628 } |
| 14328 } | 14629 } |
| 14329 | 14630 |
| 14330 void removeWhere(bool test(Node node)) { | 14631 void removeWhere(bool test(Node node)) { |
| 14331 _filter(test, true); | 14632 _filter(test, true); |
| 14332 } | 14633 } |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 14437 // Should use $dom_firstChild, Bug 8886. | 14738 // Should use $dom_firstChild, Bug 8886. |
| 14438 this.insertBefore(newNodes[0], refChild); | 14739 this.insertBefore(newNodes[0], refChild); |
| 14439 } | 14740 } |
| 14440 } else { | 14741 } else { |
| 14441 for (var node in newNodes) { | 14742 for (var node in newNodes) { |
| 14442 this.insertBefore(node, refChild); | 14743 this.insertBefore(node, refChild); |
| 14443 } | 14744 } |
| 14444 } | 14745 } |
| 14445 } | 14746 } |
| 14446 | 14747 |
| 14447 // Note that this may either be the locally set model or a cached value | |
| 14448 // of the inherited model. This is cached to minimize model change | |
| 14449 // notifications. | |
| 14450 @Creates('Null') | |
| 14451 var _model; | |
| 14452 bool _hasLocalModel; | |
| 14453 Set<StreamController<Node>> _modelChangedStreams; | |
| 14454 | |
| 14455 /** | |
| 14456 * The data model which is inherited through the tree. | |
| 14457 * | |
| 14458 * Setting this will propagate the value to all descendant nodes. If the | |
| 14459 * model is not set on this node then it will be inherited from ancestor | |
| 14460 * nodes. | |
| 14461 * | |
| 14462 * Currently this does not support propagation through Shadow DOMs. | |
| 14463 * | |
| 14464 * [clearModel] must be used to remove the model property from this node | |
| 14465 * and have the model inherit from ancestor nodes. | |
| 14466 */ | |
| 14467 @Experimental | |
| 14468 get model { | |
| 14469 // If we have a change handler then we've cached the model locally. | |
| 14470 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) { | |
| 14471 return _model; | |
| 14472 } | |
| 14473 // Otherwise start looking up the tree. | |
| 14474 for (var node = this; node != null; node = node.parentNode) { | |
| 14475 if (node._hasLocalModel == true) { | |
| 14476 return node._model; | |
| 14477 } | |
| 14478 } | |
| 14479 return null; | |
| 14480 } | |
| 14481 | |
| 14482 @Experimental | |
| 14483 void set model(value) { | |
| 14484 var changed = model != value; | |
| 14485 _model = value; | |
| 14486 _hasLocalModel = true; | |
| 14487 _ModelTreeObserver.initialize(); | |
| 14488 | |
| 14489 if (changed) { | |
| 14490 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) { | |
| 14491 _modelChangedStreams.toList().forEach((stream) => stream.add(this)); | |
| 14492 } | |
| 14493 // Propagate new model to all descendants. | |
| 14494 _ModelTreeObserver.propagateModel(this, value, false); | |
| 14495 } | |
| 14496 } | |
| 14497 | |
| 14498 /** | |
| 14499 * Clears the locally set model and makes this model be inherited from parent | |
| 14500 * nodes. | |
| 14501 */ | |
| 14502 @Experimental | |
| 14503 void clearModel() { | |
| 14504 if (_hasLocalModel == true) { | |
| 14505 _hasLocalModel = false; | |
| 14506 | |
| 14507 // Propagate new model to all descendants. | |
| 14508 if (parentNode != null) { | |
| 14509 _ModelTreeObserver.propagateModel(this, parentNode.model, false); | |
| 14510 } else { | |
| 14511 _ModelTreeObserver.propagateModel(this, null, false); | |
| 14512 } | |
| 14513 } | |
| 14514 } | |
| 14515 | |
| 14516 /** | |
| 14517 * Get a stream of models, whenever the model changes. | |
| 14518 */ | |
| 14519 Stream<Node> get onModelChanged { | |
| 14520 if (_modelChangedStreams == null) { | |
| 14521 _modelChangedStreams = new Set<StreamController<Node>>(); | |
| 14522 } | |
| 14523 var controller; | |
| 14524 controller = new StreamController( | |
| 14525 onListen: () { _modelChangedStreams.add(controller); }, | |
| 14526 onCancel: () { _modelChangedStreams.remove(controller); }); | |
| 14527 return controller.stream; | |
| 14528 } | |
| 14529 | |
| 14530 /** | 14748 /** |
| 14531 * Print out a String representation of this Node. | 14749 * Print out a String representation of this Node. |
| 14532 */ | 14750 */ |
| 14533 String toString() => localName == null ? | 14751 String toString() => localName == null ? |
| 14534 (nodeValue == null ? super.toString() : nodeValue) : localName; | 14752 (nodeValue == null ? super.toString() : nodeValue) : localName; |
| 14535 | 14753 |
| 14754 /** |
| 14755 * Binds the attribute [name] to the [path] of the [model]. |
| 14756 * Path is a String of accessors such as `foo.bar.baz`. |
| 14757 */ |
| 14758 @Experimental |
| 14759 void bind(String name, model, String path) { |
| 14760 // TODO(jmesserly): should we throw instead? |
| 14761 window.console.error('Unhandled binding to Node: ' |
| 14762 '$this $name $model $path'); |
| 14763 } |
| 14764 |
| 14765 /** Unbinds the attribute [name]. */ |
| 14766 @Experimental |
| 14767 void unbind(String name) {} |
| 14768 |
| 14769 /** Unbinds all bound attributes. */ |
| 14770 @Experimental |
| 14771 void unbindAll() {} |
| 14772 |
| 14773 TemplateInstance _templateInstance; |
| 14774 |
| 14775 // TODO(arv): Consider storing all "NodeRareData" on a single object? |
| 14776 int __instanceTerminatorCount; |
| 14777 int get _instanceTerminatorCount { |
| 14778 if (__instanceTerminatorCount == null) return 0; |
| 14779 return __instanceTerminatorCount; |
| 14780 } |
| 14781 set _instanceTerminatorCount(int value) { |
| 14782 if (value == 0) value = null; |
| 14783 __instanceTerminatorCount = value; |
| 14784 } |
| 14785 |
| 14786 /** Gets the template instance that instantiated this node, if any. */ |
| 14787 @Experimental |
| 14788 TemplateInstance get templateInstance => |
| 14789 _templateInstance != null ? _templateInstance : |
| 14790 (parent != null ? parent.templateInstance : null); |
| 14791 |
| 14536 | 14792 |
| 14537 static const int ATTRIBUTE_NODE = 2; | 14793 static const int ATTRIBUTE_NODE = 2; |
| 14538 | 14794 |
| 14539 static const int CDATA_SECTION_NODE = 4; | 14795 static const int CDATA_SECTION_NODE = 4; |
| 14540 | 14796 |
| 14541 static const int COMMENT_NODE = 8; | 14797 static const int COMMENT_NODE = 8; |
| 14542 | 14798 |
| 14543 static const int DOCUMENT_FRAGMENT_NODE = 11; | 14799 static const int DOCUMENT_FRAGMENT_NODE = 11; |
| 14544 | 14800 |
| 14545 static const int DOCUMENT_NODE = 9; | 14801 static const int DOCUMENT_NODE = 9; |
| (...skipping 3857 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 18403 | 18659 |
| 18404 @JSName('insertRow') | 18660 @JSName('insertRow') |
| 18405 @DomName('HTMLTableSectionElement.insertRow') | 18661 @DomName('HTMLTableSectionElement.insertRow') |
| 18406 @DocsEditable | 18662 @DocsEditable |
| 18407 Element $dom_insertRow(int index) native; | 18663 Element $dom_insertRow(int index) native; |
| 18408 } | 18664 } |
| 18409 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 18665 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 18410 // for details. All rights reserved. Use of this source code is governed by a | 18666 // for details. All rights reserved. Use of this source code is governed by a |
| 18411 // BSD-style license that can be found in the LICENSE file. | 18667 // BSD-style license that can be found in the LICENSE file. |
| 18412 | 18668 |
| 18669 // WARNING: Do not edit - generated code. |
| 18413 | 18670 |
| 18414 @DocsEditable | 18671 |
| 18672 @Experimental |
| 18415 @DomName('HTMLTemplateElement') | 18673 @DomName('HTMLTemplateElement') |
| 18416 @SupportedBrowser(SupportedBrowser.CHROME) | 18674 @SupportedBrowser(SupportedBrowser.CHROME) |
| 18417 @Experimental | 18675 @Experimental |
| 18418 class TemplateElement extends Element native "HTMLTemplateElement" { | 18676 class TemplateElement extends Element native "HTMLTemplateElement" { |
| 18419 | 18677 |
| 18420 @DomName('HTMLTemplateElement.HTMLTemplateElement') | 18678 @DomName('HTMLTemplateElement.HTMLTemplateElement') |
| 18421 @DocsEditable | 18679 @DocsEditable |
| 18422 factory TemplateElement() => document.$dom_createElement("template"); | 18680 factory TemplateElement() => document.$dom_createElement("template"); |
| 18423 | 18681 |
| 18424 /// Checks if this type is supported on the current platform. | 18682 /// Checks if this type is supported on the current platform. |
| 18425 static bool get supported => Element.isTagSupported('template'); | 18683 static bool get supported => Element.isTagSupported('template'); |
| 18426 | 18684 |
| 18685 @JSName('content') |
| 18427 @DomName('HTMLTemplateElement.content') | 18686 @DomName('HTMLTemplateElement.content') |
| 18428 @DocsEditable | 18687 @DocsEditable |
| 18429 final DocumentFragment content; | 18688 final DocumentFragment $dom_content; |
| 18689 |
| 18690 |
| 18691 // For real TemplateElement use the actual DOM .content field instead of |
| 18692 // our polyfilled expando. |
| 18693 @Experimental |
| 18694 DocumentFragment get content => $dom_content; |
| 18695 |
| 18696 static StreamController<DocumentFragment> _instanceCreated; |
| 18697 |
| 18698 /** |
| 18699 * *Warning*: This is an implementation helper for Model-Driven Views and |
| 18700 * should not be used in your code. |
| 18701 * |
| 18702 * This event is fired whenever a template is instantiated via |
| 18703 * [createInstance]. |
| 18704 */ |
| 18705 // TODO(rafaelw): This is a hack, and is neccesary for the polyfill |
| 18706 // because custom elements are not upgraded during clone() |
| 18707 @Experimental |
| 18708 static Stream<DocumentFragment> get instanceCreated { |
| 18709 if (_instanceCreated == null) { |
| 18710 _instanceCreated = new StreamController<DocumentFragment>(); |
| 18711 } |
| 18712 return _instanceCreated.stream; |
| 18713 } |
| 18714 |
| 18715 /** |
| 18716 * Ensures proper API and content model for template elements. |
| 18717 * |
| 18718 * [instanceRef] can be used to set the [Element.ref] property of [template], |
| 18719 * and use the ref's content will be used as source when createInstance() is |
| 18720 * invoked. |
| 18721 * |
| 18722 * Returns true if this template was just decorated, or false if it was |
| 18723 * already decorated. |
| 18724 */ |
| 18725 @Experimental |
| 18726 static bool decorate(Element template, [Element instanceRef]) { |
| 18727 // == true check because it starts as a null field. |
| 18728 if (template._templateIsDecorated == true) return false; |
| 18729 |
| 18730 template._templateIsDecorated = true; |
| 18731 |
| 18732 _injectStylesheet(); |
| 18733 |
| 18734 // Create content |
| 18735 if (template is! TemplateElement) { |
| 18736 var doc = _getTemplateContentsOwner(template.document); |
| 18737 template._templateContent = doc.createDocumentFragment(); |
| 18738 } |
| 18739 |
| 18740 if (instanceRef != null) { |
| 18741 template._templateInstanceRef = instanceRef; |
| 18742 return true; // content is empty. |
| 18743 } |
| 18744 |
| 18745 if (template is TemplateElement) { |
| 18746 _bootstrapTemplatesRecursivelyFrom(template.content); |
| 18747 } else { |
| 18748 _liftNonNativeTemplateChildrenIntoContent(template); |
| 18749 } |
| 18750 |
| 18751 return true; |
| 18752 } |
| 18753 |
| 18754 /** |
| 18755 * This used to decorate recursively all templates from a given node. |
| 18756 * |
| 18757 * By default [decorate] will be called on templates lazily when certain |
| 18758 * properties such as [model] are accessed, but it can be run eagerly to |
| 18759 * decorate an entire tree recursively. |
| 18760 */ |
| 18761 // TODO(rafaelw): Review whether this is the right public API. |
| 18762 @Experimental |
| 18763 static void bootstrap(Node content) { |
| 18764 _bootstrapTemplatesRecursivelyFrom(content); |
| 18765 } |
| 18766 |
| 18767 static bool _initStyles; |
| 18768 |
| 18769 static void _injectStylesheet() { |
| 18770 if (_initStyles == true) return; |
| 18771 _initStyles = true; |
| 18772 |
| 18773 var style = new StyleElement(); |
| 18774 style.text = r''' |
| 18775 template, |
| 18776 thead[template], |
| 18777 tbody[template], |
| 18778 tfoot[template], |
| 18779 th[template], |
| 18780 tr[template], |
| 18781 td[template], |
| 18782 caption[template], |
| 18783 colgroup[template], |
| 18784 col[template], |
| 18785 option[template] { |
| 18786 display: none; |
| 18787 }'''; |
| 18788 document.head.append(style); |
| 18789 } |
| 18790 |
| 18791 /** |
| 18792 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for |
| 18793 * more information. |
| 18794 */ |
| 18795 @Experimental |
| 18796 static Map<String, CustomBindingSyntax> syntax = {}; |
| 18430 } | 18797 } |
| 18431 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 18798 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 18432 // for details. All rights reserved. Use of this source code is governed by a | 18799 // for details. All rights reserved. Use of this source code is governed by a |
| 18433 // BSD-style license that can be found in the LICENSE file. | 18800 // BSD-style license that can be found in the LICENSE file. |
| 18434 | 18801 |
| 18435 // WARNING: Do not edit - generated code. | 18802 // WARNING: Do not edit - generated code. |
| 18436 | 18803 |
| 18437 | 18804 |
| 18438 @DomName('Text') | 18805 @DomName('Text') |
| 18439 class Text extends CharacterData native "Text" { | 18806 class Text extends CharacterData native "Text" { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 18452 final String wholeText; | 18819 final String wholeText; |
| 18453 | 18820 |
| 18454 @DomName('Text.replaceWholeText') | 18821 @DomName('Text.replaceWholeText') |
| 18455 @DocsEditable | 18822 @DocsEditable |
| 18456 Text replaceWholeText(String content) native; | 18823 Text replaceWholeText(String content) native; |
| 18457 | 18824 |
| 18458 @DomName('Text.splitText') | 18825 @DomName('Text.splitText') |
| 18459 @DocsEditable | 18826 @DocsEditable |
| 18460 Text splitText(int offset) native; | 18827 Text splitText(int offset) native; |
| 18461 | 18828 |
| 18829 |
| 18830 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 18831 StreamSubscription _textBinding; |
| 18832 |
| 18833 @Experimental |
| 18834 void bind(String name, model, String path) { |
| 18835 if (name != 'text') { |
| 18836 super.bind(name, model, path); |
| 18837 return; |
| 18838 } |
| 18839 |
| 18840 unbind('text'); |
| 18841 |
| 18842 _textBinding = new PathObserver(model, path).bindSync((value) { |
| 18843 text = value == null ? '' : '$value'; |
| 18844 }); |
| 18845 } |
| 18846 |
| 18847 @Experimental |
| 18848 void unbind(String name) { |
| 18849 if (name != 'text') { |
| 18850 super.unbind(name); |
| 18851 return; |
| 18852 } |
| 18853 |
| 18854 if (_textBinding == null) return; |
| 18855 |
| 18856 _textBinding.cancel(); |
| 18857 _textBinding = null; |
| 18858 } |
| 18859 |
| 18860 @Experimental |
| 18861 void unbindAll() { |
| 18862 unbind('text'); |
| 18863 super.unbindAll(); |
| 18864 } |
| 18462 } | 18865 } |
| 18463 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 18866 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 18464 // for details. All rights reserved. Use of this source code is governed by a | 18867 // for details. All rights reserved. Use of this source code is governed by a |
| 18465 // BSD-style license that can be found in the LICENSE file. | 18868 // BSD-style license that can be found in the LICENSE file. |
| 18466 | 18869 |
| 18467 | 18870 |
| 18468 @DocsEditable | 18871 @DocsEditable |
| 18469 @DomName('HTMLTextAreaElement') | 18872 @DomName('HTMLTextAreaElement') |
| 18470 class TextAreaElement extends Element native "HTMLTextAreaElement" { | 18873 class TextAreaElement extends Element native "HTMLTextAreaElement" { |
| 18471 | 18874 |
| (...skipping 5555 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 24027 * Key value used when an implementation is unable to identify another key | 24430 * Key value used when an implementation is unable to identify another key |
| 24028 * value, due to either hardware, platform, or software constraints | 24431 * value, due to either hardware, platform, or software constraints |
| 24029 */ | 24432 */ |
| 24030 static const String UNIDENTIFIED = "Unidentified"; | 24433 static const String UNIDENTIFIED = "Unidentified"; |
| 24031 } | 24434 } |
| 24032 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 24435 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 24033 // for details. All rights reserved. Use of this source code is governed by a | 24436 // for details. All rights reserved. Use of this source code is governed by a |
| 24034 // BSD-style license that can be found in the LICENSE file. | 24437 // BSD-style license that can be found in the LICENSE file. |
| 24035 | 24438 |
| 24036 | 24439 |
| 24037 class _ModelTreeObserver { | 24440 // This code is inspired by ChangeSummary: |
| 24038 static bool _initialized = false; | 24441 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js |
| 24442 // ...which underlies MDV. Since we don't need the functionality of |
| 24443 // ChangeSummary, we just implement what we need for data bindings. |
| 24444 // This allows our implementation to be much simpler. |
| 24445 |
| 24446 // TODO(jmesserly): should we make these types stronger, and require |
| 24447 // Observable objects? Currently, it is fine to say something like: |
| 24448 // var path = new PathObserver(123, ''); |
| 24449 // print(path.value); // "123" |
| 24450 // |
| 24451 // Furthermore this degenerate case is allowed: |
| 24452 // var path = new PathObserver(123, 'foo.bar.baz.qux'); |
| 24453 // print(path.value); // "null" |
| 24454 // |
| 24455 // Here we see that any invalid (i.e. not Observable) value will break the |
| 24456 // path chain without producing an error or exception. |
| 24457 // |
| 24458 // Now the real question: should we do this? For the former case, the behavior |
| 24459 // is correct but we could chose to handle it in the dart:html bindings layer. |
| 24460 // For the latter case, it might be better to throw an error so users can find |
| 24461 // the problem. |
| 24462 |
| 24463 |
| 24464 /** |
| 24465 * A data-bound path starting from a view-model or model object, for example |
| 24466 * `foo.bar.baz`. |
| 24467 * |
| 24468 * When the [values] stream is being listened to, this will observe changes to |
| 24469 * the object and any intermediate object along the path, and send [values] |
| 24470 * accordingly. When all listeners are unregistered it will stop observing |
| 24471 * the objects. |
| 24472 * |
| 24473 * This class is used to implement [Node.bind] and similar functionality. |
| 24474 */ |
| 24475 // TODO(jmesserly): find a better home for this type. |
| 24476 @Experimental |
| 24477 class PathObserver { |
| 24478 /** The object being observed. */ |
| 24479 final object; |
| 24480 |
| 24481 /** The path string. */ |
| 24482 final String path; |
| 24483 |
| 24484 /** True if the path is valid, otherwise false. */ |
| 24485 final bool _isValid; |
| 24486 |
| 24487 // TODO(jmesserly): same issue here as ObservableMixin: is there an easier |
| 24488 // way to get a broadcast stream? |
| 24489 StreamController _values; |
| 24490 Stream _valueStream; |
| 24491 |
| 24492 _PropertyObserver _observer, _lastObserver; |
| 24493 |
| 24494 Object _lastValue; |
| 24495 bool _scheduled = false; |
| 24039 | 24496 |
| 24040 /** | 24497 /** |
| 24041 * Start an observer watching the document for tree changes to automatically | 24498 * Observes [path] on [object] for changes. This returns an object that can be |
| 24042 * propagate model changes. | 24499 * used to get the changes and get/set the value at this path. |
| 24043 * | 24500 * See [PathObserver.values] and [PathObserver.value]. |
| 24044 * Currently this does not support propagation through Shadow DOMs. | |
| 24045 */ | 24501 */ |
| 24046 static void initialize() { | 24502 PathObserver(this.object, String path) |
| 24047 if (!_initialized) { | 24503 : path = path, _isValid = _isPathValid(path) { |
| 24048 _initialized = true; | 24504 |
| 24049 | 24505 // TODO(jmesserly): if the path is empty, or the object is! Observable, we |
| 24050 if (MutationObserver.supported) { | 24506 // can optimize the PathObserver to be more lightweight. |
| 24051 var observer = new MutationObserver(_processTreeChange); | 24507 |
| 24052 observer.observe(document, childList: true, subtree: true); | 24508 _values = new StreamController(onListen: _observe, onCancel: _unobserve); |
| 24053 } else { | 24509 |
| 24054 document.on['DOMNodeInserted'].listen(_handleNodeInserted); | 24510 if (_isValid) { |
| 24055 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved); | 24511 var segments = []; |
| 24512 for (var segment in path.trim().split('.')) { |
| 24513 if (segment == '') continue; |
| 24514 var index = int.parse(segment, onError: (_) {}); |
| 24515 segments.add(index != null ? index : new Symbol(segment)); |
| 24056 } | 24516 } |
| 24057 } | 24517 |
| 24058 } | 24518 // Create the property observer linked list. |
| 24059 | 24519 // Note that the structure of a path can't change after it is initially |
| 24060 static void _processTreeChange(List<MutationRecord> mutations, | 24520 // constructed, even though the objects along the path can change. |
| 24061 MutationObserver observer) { | 24521 for (int i = segments.length - 1; i >= 0; i--) { |
| 24062 for (var record in mutations) { | 24522 _observer = new _PropertyObserver(this, segments[i], _observer); |
| 24063 for (var node in record.addedNodes) { | 24523 if (_lastObserver == null) _lastObserver = _observer; |
| 24064 // When nodes enter the document we need to make sure that all of the | |
| 24065 // models are properly propagated through the entire sub-tree. | |
| 24066 propagateModel(node, _calculatedModel(node), true); | |
| 24067 } | 24524 } |
| 24068 for (var node in record.removedNodes) { | 24525 } |
| 24069 propagateModel(node, _calculatedModel(node), false); | 24526 } |
| 24527 |
| 24528 // TODO(jmesserly): we could try adding the first value to the stream, but |
| 24529 // that delivers the first record async. |
| 24530 /** |
| 24531 * Listens to the stream, and invokes the [callback] immediately with the |
| 24532 * current [value]. This is useful for bindings, which want to be up-to-date |
| 24533 * immediately. |
| 24534 */ |
| 24535 StreamSubscription bindSync(void callback(value)) { |
| 24536 var result = values.listen(callback); |
| 24537 callback(value); |
| 24538 return result; |
| 24539 } |
| 24540 |
| 24541 // TODO(jmesserly): should this be a change record with the old value? |
| 24542 // TODO(jmesserly): should this be a broadcast stream? We only need |
| 24543 // single-subscription in the bindings system, so single sub saves overhead. |
| 24544 /** |
| 24545 * Gets the stream of values that were observed at this path. |
| 24546 * This returns a single-subscription stream. |
| 24547 */ |
| 24548 Stream get values => _values.stream; |
| 24549 |
| 24550 /** Force synchronous delivery of [values]. */ |
| 24551 void _deliverValues() { |
| 24552 _scheduled = false; |
| 24553 |
| 24554 var newValue = value; |
| 24555 if (!identical(_lastValue, newValue)) { |
| 24556 _values.add(newValue); |
| 24557 _lastValue = newValue; |
| 24558 } |
| 24559 } |
| 24560 |
| 24561 void _observe() { |
| 24562 if (_observer != null) { |
| 24563 _lastValue = value; |
| 24564 _observer.observe(); |
| 24565 } |
| 24566 } |
| 24567 |
| 24568 void _unobserve() { |
| 24569 if (_observer != null) _observer.unobserve(); |
| 24570 } |
| 24571 |
| 24572 void _notifyChange() { |
| 24573 if (_scheduled) return; |
| 24574 _scheduled = true; |
| 24575 |
| 24576 // TODO(jmesserly): should we have a guarenteed order with respect to other |
| 24577 // paths? If so, we could implement this fairly easily by sorting instances |
| 24578 // of this class by birth order before delivery. |
| 24579 queueChangeRecords(_deliverValues); |
| 24580 } |
| 24581 |
| 24582 /** Gets the last reported value at this path. */ |
| 24583 get value { |
| 24584 if (!_isValid) return null; |
| 24585 if (_observer == null) return object; |
| 24586 _observer.ensureValue(object); |
| 24587 return _lastObserver.value; |
| 24588 } |
| 24589 |
| 24590 /** Sets the value at this path. */ |
| 24591 void set value(Object value) { |
| 24592 // TODO(jmesserly): throw if property cannot be set? |
| 24593 // MDV seems tolerant of these error. |
| 24594 if (_observer == null || !_isValid) return; |
| 24595 _observer.ensureValue(object); |
| 24596 var last = _lastObserver; |
| 24597 if (_setObjectProperty(last._object, last._property, value)) { |
| 24598 // Technically, this would get updated asynchronously via a change record. |
| 24599 // However, it is nice if calling the getter will yield the same value |
| 24600 // that was just set. So we use this opportunity to update our cache. |
| 24601 last.value = value; |
| 24602 } |
| 24603 } |
| 24604 } |
| 24605 |
| 24606 // TODO(jmesserly): these should go away in favor of mirrors! |
| 24607 _getObjectProperty(object, property) { |
| 24608 if (object is List && property is int) { |
| 24609 if (property >= 0 && property < object.length) { |
| 24610 return object[property]; |
| 24611 } else { |
| 24612 return null; |
| 24613 } |
| 24614 } |
| 24615 |
| 24616 // TODO(jmesserly): what about length? |
| 24617 if (object is Map) return object[property]; |
| 24618 |
| 24619 if (object is Observable) return object.getValueWorkaround(property); |
| 24620 |
| 24621 return null; |
| 24622 } |
| 24623 |
| 24624 bool _setObjectProperty(object, property, value) { |
| 24625 if (object is List && property is int) { |
| 24626 object[property] = value; |
| 24627 } else if (object is Map) { |
| 24628 object[property] = value; |
| 24629 } else if (object is Observable) { |
| 24630 (object as Observable).setValueWorkaround(property, value); |
| 24631 } else { |
| 24632 return false; |
| 24633 } |
| 24634 return true; |
| 24635 } |
| 24636 |
| 24637 |
| 24638 class _PropertyObserver { |
| 24639 final PathObserver _path; |
| 24640 final _property; |
| 24641 final _PropertyObserver _next; |
| 24642 |
| 24643 // TODO(jmesserly): would be nice not to store both of these. |
| 24644 Object _object; |
| 24645 Object _value; |
| 24646 StreamSubscription _sub; |
| 24647 |
| 24648 _PropertyObserver(this._path, this._property, this._next); |
| 24649 |
| 24650 get value => _value; |
| 24651 |
| 24652 void set value(Object newValue) { |
| 24653 _value = newValue; |
| 24654 if (_next != null) { |
| 24655 if (_sub != null) _next.unobserve(); |
| 24656 _next.ensureValue(_value); |
| 24657 if (_sub != null) _next.observe(); |
| 24658 } |
| 24659 } |
| 24660 |
| 24661 void ensureValue(object) { |
| 24662 // If we're observing, values should be up to date already. |
| 24663 if (_sub != null) return; |
| 24664 |
| 24665 _object = object; |
| 24666 value = _getObjectProperty(object, _property); |
| 24667 } |
| 24668 |
| 24669 void observe() { |
| 24670 if (_object is Observable) { |
| 24671 assert(_sub == null); |
| 24672 _sub = (_object as Observable).changes.listen(_onChange); |
| 24673 } |
| 24674 if (_next != null) _next.observe(); |
| 24675 } |
| 24676 |
| 24677 void unobserve() { |
| 24678 if (_sub == null) return; |
| 24679 |
| 24680 _sub.cancel(); |
| 24681 _sub = null; |
| 24682 if (_next != null) _next.unobserve(); |
| 24683 } |
| 24684 |
| 24685 void _onChange(List<ChangeRecord> changes) { |
| 24686 for (var change in changes) { |
| 24687 // TODO(jmesserly): what to do about "new Symbol" here? |
| 24688 // Ideally this would only preserve names if the user has opted in to |
| 24689 // them being preserved. |
| 24690 // TODO(jmesserly): should we drop observable maps with String keys? |
| 24691 // If so then we only need one check here. |
| 24692 if (change.changes(_property)) { |
| 24693 value = _getObjectProperty(_object, _property); |
| 24694 _path._notifyChange(); |
| 24695 return; |
| 24070 } | 24696 } |
| 24071 } | 24697 } |
| 24072 } | 24698 } |
| 24073 | 24699 } |
| 24074 static void _handleNodeInserted(MutationEvent e) { | 24700 |
| 24075 var node = e.target; | 24701 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js |
| 24076 window.setImmediate(() { | 24702 |
| 24077 propagateModel(node, _calculatedModel(node), true); | 24703 const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*'; |
| 24078 }); | 24704 final _pathRegExp = new RegExp('^' |
| 24079 } | 24705 '(?:#?' + _pathIndentPart + ')?' |
| 24080 | 24706 '(?:' |
| 24081 static void _handleNodeRemoved(MutationEvent e) { | 24707 '(?:\\.' + _pathIndentPart + ')' |
| 24082 var node = e.target; | 24708 ')*' |
| 24083 window.setImmediate(() { | 24709 r'$', caseSensitive: false); |
| 24084 propagateModel(node, _calculatedModel(node), false); | 24710 |
| 24085 }); | 24711 final _spacesRegExp = new RegExp(r'\s'); |
| 24086 } | 24712 |
| 24087 | 24713 bool _isPathValid(String s) { |
| 24088 /** | 24714 s = s.replaceAll(_spacesRegExp, ''); |
| 24089 * Figures out what the model should be for a node, avoiding any cached | 24715 |
| 24090 * model values. | 24716 if (s == '') return true; |
| 24091 */ | 24717 if (s[0] == '.') return false; |
| 24092 static _calculatedModel(node) { | 24718 return _pathRegExp.hasMatch(s); |
| 24093 if (node._hasLocalModel == true) { | |
| 24094 return node._model; | |
| 24095 } else if (node.parentNode != null) { | |
| 24096 return node.parentNode._model; | |
| 24097 } | |
| 24098 return null; | |
| 24099 } | |
| 24100 | |
| 24101 /** | |
| 24102 * Pushes model changes down through the tree. | |
| 24103 * | |
| 24104 * Set fullTree to true if the state of the tree is unknown and model changes | |
| 24105 * should be propagated through the entire tree. | |
| 24106 */ | |
| 24107 static void propagateModel(Node node, model, bool fullTree) { | |
| 24108 // Calling into user code with the != call could generate exceptions. | |
| 24109 // Catch and report them a global exceptions. | |
| 24110 try { | |
| 24111 if (node._hasLocalModel != true && node._model != model && | |
| 24112 node._modelChangedStreams != null && | |
| 24113 !node._modelChangedStreams.isEmpty) { | |
| 24114 node._model = model; | |
| 24115 node._modelChangedStreams.toList() | |
| 24116 .forEach((controller) => controller.add(node)); | |
| 24117 } | |
| 24118 } catch (e, s) { | |
| 24119 new Future.error(e, s); | |
| 24120 } | |
| 24121 for (var child = node.$dom_firstChild; child != null; | |
| 24122 child = child.nextNode) { | |
| 24123 if (child._hasLocalModel != true) { | |
| 24124 propagateModel(child, model, fullTree); | |
| 24125 } else if (fullTree) { | |
| 24126 propagateModel(child, child._model, true); | |
| 24127 } | |
| 24128 } | |
| 24129 } | |
| 24130 } | 24719 } |
| 24131 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 24720 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 24132 // for details. All rights reserved. Use of this source code is governed by a | 24721 // for details. All rights reserved. Use of this source code is governed by a |
| 24133 // BSD-style license that can be found in the LICENSE file. | 24722 // BSD-style license that can be found in the LICENSE file. |
| 24134 | 24723 |
| 24135 | 24724 |
| 24136 /** | 24725 /** |
| 24137 * A utility class for representing two-dimensional positions. | 24726 * A utility class for representing two-dimensional positions. |
| 24138 */ | 24727 */ |
| 24139 class Point { | 24728 class Point { |
| (...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 24343 * Truncates coordinates to integers and returns the result as a new | 24932 * Truncates coordinates to integers and returns the result as a new |
| 24344 * rectangle. | 24933 * rectangle. |
| 24345 */ | 24934 */ |
| 24346 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), | 24935 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), |
| 24347 height.toInt()); | 24936 height.toInt()); |
| 24348 | 24937 |
| 24349 Point get topLeft => new Point(this.left, this.top); | 24938 Point get topLeft => new Point(this.left, this.top); |
| 24350 Point get bottomRight => new Point(this.left + this.width, | 24939 Point get bottomRight => new Point(this.left + this.width, |
| 24351 this.top + this.height); | 24940 this.top + this.height); |
| 24352 } | 24941 } |
| 24942 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 24943 // for details. All rights reserved. Use of this source code is governed by a |
| 24944 // BSD-style license that can be found in the LICENSE file. |
| 24945 |
| 24946 |
| 24947 // This code is a port of Model-Driven-Views: |
| 24948 // https://github.com/toolkitchen/mdv |
| 24949 // The code mostly comes from src/template_element.js |
| 24950 |
| 24951 typedef void _ChangeHandler(value); |
| 24952 |
| 24953 /** |
| 24954 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases, |
| 24955 * but (by design) don't attempt to implement a wide array of specialized |
| 24956 * behaviors. |
| 24957 * |
| 24958 * Enabling these features in MDV is a matter of implementing and registering an |
| 24959 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more |
| 24960 * delegation functions which implement specialized behavior. This object is |
| 24961 * registered with MDV via [TemplateElement.syntax]: |
| 24962 * |
| 24963 * |
| 24964 * HTML: |
| 24965 * <template bind syntax="MySyntax"> |
| 24966 * {{ What!Ever('crazy')->thing^^^I+Want(data) }} |
| 24967 * </template> |
| 24968 * |
| 24969 * Dart: |
| 24970 * class MySyntax extends CustomBindingSyntax { |
| 24971 * getBinding(model, path, name, node) { |
| 24972 * // The magic happens here! |
| 24973 * } |
| 24974 * } |
| 24975 * |
| 24976 * ... |
| 24977 * |
| 24978 * TemplateElement.syntax['MySyntax'] = new MySyntax(); |
| 24979 * |
| 24980 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more |
| 24981 * information about Custom Syntax. |
| 24982 */ |
| 24983 // TODO(jmesserly): if this is just one method, a function type would make it |
| 24984 // more Dart-friendly. |
| 24985 @Experimental |
| 24986 abstract class CustomBindingSyntax { |
| 24987 // TODO(jmesserly): I had to remove type annotations from "name" and "node" |
| 24988 // Normally they are String and Node respectively. But sometimes it will pass |
| 24989 // (int name, CompoundBinding node). That seems very confusing; we may want |
| 24990 // to change this API. |
| 24991 getBinding(model, String path, name, node); |
| 24992 } |
| 24993 |
| 24994 /** The callback used in the [CompoundBinding.combinator] field. */ |
| 24995 @Experimental |
| 24996 typedef Object CompoundBindingCombinator(Map objects); |
| 24997 |
| 24998 /** Information about the instantiated template. */ |
| 24999 @Experimental |
| 25000 class TemplateInstance { |
| 25001 // TODO(rafaelw): firstNode & lastNode should be read-synchronous |
| 25002 // in cases where script has modified the template instance boundary. |
| 25003 |
| 25004 /** The first node of this template instantiation. */ |
| 25005 final Node firstNode; |
| 25006 |
| 25007 /** |
| 25008 * The last node of this template instantiation. |
| 25009 * This could be identical to [firstNode] if the template only expanded to a |
| 25010 * single node. |
| 25011 */ |
| 25012 final Node lastNode; |
| 25013 |
| 25014 /** The model used to instantiate the template. */ |
| 25015 final model; |
| 25016 |
| 25017 TemplateInstance(this.firstNode, this.lastNode, this.model); |
| 25018 } |
| 25019 |
| 25020 /** |
| 25021 * Model-Driven Views contains a helper object which is useful for the |
| 25022 * implementation of a Custom Syntax. |
| 25023 * |
| 25024 * var binding = new CompoundBinding((values) { |
| 25025 * var combinedValue; |
| 25026 * // compute combinedValue based on the current values which are provided |
| 25027 * return combinedValue; |
| 25028 * }); |
| 25029 * binding.bind('name1', obj1, path1); |
| 25030 * binding.bind('name2', obj2, path2); |
| 25031 * //... |
| 25032 * binding.bind('nameN', objN, pathN); |
| 25033 * |
| 25034 * CompoundBinding is an object which knows how to listen to multiple path |
| 25035 * values (registered via [bind]) and invoke its [combinator] when one or more |
| 25036 * of the values have changed and set its [value] property to the return value |
| 25037 * of the function. When any value has changed, all current values are provided |
| 25038 * to the [combinator] in the single `values` argument. |
| 25039 * |
| 25040 * See [CustomBindingSyntax] for more information. |
| 25041 */ |
| 25042 // TODO(jmesserly): what is the public API surface here? I just guessed; |
| 25043 // most of it seemed non-public. |
| 25044 @Experimental |
| 25045 class CompoundBinding extends ObservableBase { |
| 25046 CompoundBindingCombinator _combinator; |
| 25047 |
| 25048 // TODO(jmesserly): ideally these would be String keys, but sometimes we |
| 25049 // use integers. |
| 25050 Map<dynamic, StreamSubscription> _bindings = new Map(); |
| 25051 Map _values = new Map(); |
| 25052 bool _scheduled = false; |
| 25053 bool _disposed = false; |
| 25054 Object _value; |
| 25055 |
| 25056 CompoundBinding([CompoundBindingCombinator combinator]) { |
| 25057 // TODO(jmesserly): this is a tweak to the original code, it seemed to me |
| 25058 // that passing the combinator to the constructor should be equivalent to |
| 25059 // setting it via the property. |
| 25060 // I also added a null check to the combinator setter. |
| 25061 this.combinator = combinator; |
| 25062 } |
| 25063 |
| 25064 CompoundBindingCombinator get combinator => _combinator; |
| 25065 |
| 25066 set combinator(CompoundBindingCombinator combinator) { |
| 25067 _combinator = combinator; |
| 25068 if (combinator != null) _scheduleResolve(); |
| 25069 } |
| 25070 |
| 25071 static const _VALUE = const Symbol('value'); |
| 25072 |
| 25073 get value => _value; |
| 25074 |
| 25075 void set value(newValue) { |
| 25076 _value = notifyPropertyChange(_VALUE, _value, newValue); |
| 25077 } |
| 25078 |
| 25079 // TODO(jmesserly): remove these workarounds when dart2js supports mirrors! |
| 25080 getValueWorkaround(key) { |
| 25081 if (key == _VALUE) return value; |
| 25082 return null; |
| 25083 } |
| 25084 setValueWorkaround(key, val) { |
| 25085 if (key == _VALUE) value = val; |
| 25086 } |
| 25087 |
| 25088 void bind(name, model, String path) { |
| 25089 unbind(name); |
| 25090 |
| 25091 _bindings[name] = new PathObserver(model, path).bindSync((value) { |
| 25092 _values[name] = value; |
| 25093 _scheduleResolve(); |
| 25094 }); |
| 25095 } |
| 25096 |
| 25097 void unbind(name, {bool suppressResolve: false}) { |
| 25098 var binding = _bindings.remove(name); |
| 25099 if (binding == null) return; |
| 25100 |
| 25101 binding.cancel(); |
| 25102 _values.remove(name); |
| 25103 if (!suppressResolve) _scheduleResolve(); |
| 25104 } |
| 25105 |
| 25106 // TODO(rafaelw): Is this the right processing model? |
| 25107 // TODO(rafaelw): Consider having a seperate ChangeSummary for |
| 25108 // CompoundBindings so to excess dirtyChecks. |
| 25109 void _scheduleResolve() { |
| 25110 if (_scheduled) return; |
| 25111 _scheduled = true; |
| 25112 queueChangeRecords(resolve); |
| 25113 } |
| 25114 |
| 25115 void resolve() { |
| 25116 if (_disposed) return; |
| 25117 _scheduled = false; |
| 25118 |
| 25119 if (_combinator == null) { |
| 25120 throw new StateError( |
| 25121 'CompoundBinding attempted to resolve without a combinator'); |
| 25122 } |
| 25123 |
| 25124 value = _combinator(_values); |
| 25125 } |
| 25126 |
| 25127 void dispose() { |
| 25128 for (var binding in _bindings.values) { |
| 25129 binding.cancel(); |
| 25130 } |
| 25131 _bindings.clear(); |
| 25132 _values.clear(); |
| 25133 |
| 25134 _disposed = true; |
| 25135 value = null; |
| 25136 } |
| 25137 } |
| 25138 |
| 25139 Stream<Event> _getStreamForInputType(InputElement element) { |
| 25140 switch (element.type) { |
| 25141 case 'checkbox': |
| 25142 return element.onClick; |
| 25143 case 'radio': |
| 25144 case 'select-multiple': |
| 25145 case 'select-one': |
| 25146 return element.onChange; |
| 25147 default: |
| 25148 return element.onInput; |
| 25149 } |
| 25150 } |
| 25151 |
| 25152 abstract class _InputBinding { |
| 25153 final InputElement element; |
| 25154 PathObserver binding; |
| 25155 StreamSubscription _pathSub; |
| 25156 StreamSubscription _eventSub; |
| 25157 |
| 25158 _InputBinding(this.element, model, String path) { |
| 25159 binding = new PathObserver(model, path); |
| 25160 _pathSub = binding.bindSync(valueChanged); |
| 25161 _eventSub = _getStreamForInputType(element).listen(updateBinding); |
| 25162 } |
| 25163 |
| 25164 void valueChanged(newValue); |
| 25165 |
| 25166 void updateBinding(e); |
| 25167 |
| 25168 void unbind() { |
| 25169 binding = null; |
| 25170 _pathSub.cancel(); |
| 25171 _eventSub.cancel(); |
| 25172 } |
| 25173 } |
| 25174 |
| 25175 class _ValueBinding extends _InputBinding { |
| 25176 _ValueBinding(element, model, path) : super(element, model, path); |
| 25177 |
| 25178 void valueChanged(value) { |
| 25179 element.value = value == null ? '' : '$value'; |
| 25180 } |
| 25181 |
| 25182 void updateBinding(e) { |
| 25183 binding.value = element.value; |
| 25184 } |
| 25185 } |
| 25186 |
| 25187 // TODO(jmesserly): not sure what kind of boolean conversion rules to |
| 25188 // apply for template data-binding. HTML attributes are true if they're present. |
| 25189 // However Dart only treats "true" as true. Since this is HTML we'll use |
| 25190 // something closer to the HTML rules: null (missing) and false are false, |
| 25191 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59 |
| 25192 bool _templateBooleanConversion(value) => null != value && false != value; |
| 25193 |
| 25194 class _CheckedBinding extends _InputBinding { |
| 25195 _CheckedBinding(element, model, path) : super(element, model, path); |
| 25196 |
| 25197 void valueChanged(value) { |
| 25198 element.checked = _templateBooleanConversion(value); |
| 25199 } |
| 25200 |
| 25201 void updateBinding(e) { |
| 25202 binding.value = element.checked; |
| 25203 |
| 25204 // Only the radio button that is getting checked gets an event. We |
| 25205 // therefore find all the associated radio buttons and update their |
| 25206 // CheckedBinding manually. |
| 25207 if (element is InputElement && element.type == 'radio') { |
| 25208 for (var r in _getAssociatedRadioButtons(element)) { |
| 25209 var checkedBinding = r._checkedBinding; |
| 25210 if (checkedBinding != null) { |
| 25211 // Set the value directly to avoid an infinite call stack. |
| 25212 checkedBinding.binding.value = false; |
| 25213 } |
| 25214 } |
| 25215 } |
| 25216 } |
| 25217 } |
| 25218 |
| 25219 // TODO(jmesserly): polyfill document.contains API instead of doing it here |
| 25220 bool _isNodeInDocument(Node node) { |
| 25221 // On non-IE this works: |
| 25222 // return node.document.contains(node); |
| 25223 var document = node.document; |
| 25224 if (node == document || node.parentNode == document) return true; |
| 25225 return document.documentElement.contains(node); |
| 25226 } |
| 25227 |
| 25228 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. |
| 25229 // Returns an array containing all radio buttons other than |element| that |
| 25230 // have the same |name|, either in the form that |element| belongs to or, |
| 25231 // if no form, in the document tree to which |element| belongs. |
| 25232 // |
| 25233 // This implementation is based upon the HTML spec definition of a |
| 25234 // "radio button group": |
| 25235 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht
ml#radio-button-group |
| 25236 // |
| 25237 Iterable _getAssociatedRadioButtons(element) { |
| 25238 if (!_isNodeInDocument(element)) return []; |
| 25239 if (element.form != null) { |
| 25240 return element.form.nodes.where((el) { |
| 25241 return el != element && |
| 25242 el is InputElement && |
| 25243 el.type == 'radio' && |
| 25244 el.name == element.name; |
| 25245 }); |
| 25246 } else { |
| 25247 var radios = element.document.queryAll( |
| 25248 'input[type="radio"][name="${element.name}"]'); |
| 25249 return radios.where((el) => el != element && el.form == null); |
| 25250 } |
| 25251 } |
| 25252 |
| 25253 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { |
| 25254 var clone = node.clone(false); // Shallow clone. |
| 25255 if (clone is Element && clone.isTemplate) { |
| 25256 TemplateElement.decorate(clone, node); |
| 25257 if (syntax != null) { |
| 25258 clone.attributes.putIfAbsent('syntax', () => syntax); |
| 25259 } |
| 25260 } |
| 25261 |
| 25262 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { |
| 25263 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax)); |
| 25264 } |
| 25265 return clone; |
| 25266 } |
| 25267 |
| 25268 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df
n-template-contents-owner |
| 25269 Document _getTemplateContentsOwner(Document doc) { |
| 25270 if (doc.window == null) { |
| 25271 return doc; |
| 25272 } |
| 25273 var d = doc._templateContentsOwner; |
| 25274 if (d == null) { |
| 25275 // TODO(arv): This should either be a Document or HTMLDocument depending |
| 25276 // on doc. |
| 25277 d = doc.implementation.createHtmlDocument(''); |
| 25278 while (d.$dom_lastChild != null) { |
| 25279 d.$dom_lastChild.remove(); |
| 25280 } |
| 25281 doc._templateContentsOwner = d; |
| 25282 } |
| 25283 return d; |
| 25284 } |
| 25285 |
| 25286 Element _cloneAndSeperateAttributeTemplate(Element templateElement) { |
| 25287 var clone = templateElement.clone(false); |
| 25288 var attributes = templateElement.attributes; |
| 25289 for (var name in attributes.keys.toList()) { |
| 25290 switch (name) { |
| 25291 case 'template': |
| 25292 case 'repeat': |
| 25293 case 'bind': |
| 25294 case 'ref': |
| 25295 clone.attributes.remove(name); |
| 25296 break; |
| 25297 default: |
| 25298 attributes.remove(name); |
| 25299 break; |
| 25300 } |
| 25301 } |
| 25302 |
| 25303 return clone; |
| 25304 } |
| 25305 |
| 25306 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) { |
| 25307 var content = templateElement.content; |
| 25308 |
| 25309 if (!templateElement._isAttributeTemplate) { |
| 25310 var child; |
| 25311 while ((child = templateElement.$dom_firstChild) != null) { |
| 25312 content.append(child); |
| 25313 } |
| 25314 return; |
| 25315 } |
| 25316 |
| 25317 // For attribute templates we copy the whole thing into the content and |
| 25318 // we move the non template attributes into the content. |
| 25319 // |
| 25320 // <tr foo template> |
| 25321 // |
| 25322 // becomes |
| 25323 // |
| 25324 // <tr template> |
| 25325 // + #document-fragment |
| 25326 // + <tr foo> |
| 25327 // |
| 25328 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement); |
| 25329 var child; |
| 25330 while ((child = templateElement.$dom_firstChild) != null) { |
| 25331 newRoot.append(child); |
| 25332 } |
| 25333 content.append(newRoot); |
| 25334 } |
| 25335 |
| 25336 void _bootstrapTemplatesRecursivelyFrom(Node node) { |
| 25337 void bootstrap(template) { |
| 25338 if (!TemplateElement.decorate(template)) { |
| 25339 _bootstrapTemplatesRecursivelyFrom(template.content); |
| 25340 } |
| 25341 } |
| 25342 |
| 25343 // Need to do this first as the contents may get lifted if |node| is |
| 25344 // template. |
| 25345 // TODO(jmesserly): node is DocumentFragment or Element |
| 25346 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors); |
| 25347 if (node is Element && node.isTemplate) bootstrap(node); |
| 25348 |
| 25349 templateDescendents.forEach(bootstrap); |
| 25350 } |
| 25351 |
| 25352 final String _allTemplatesSelectors = 'template, option[template], ' + |
| 25353 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", "); |
| 25354 |
| 25355 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) { |
| 25356 if (node is Element) { |
| 25357 _addAttributeBindings(node, model, syntax); |
| 25358 } else if (node is Text) { |
| 25359 _parseAndBind(node, node.text, 'text', model, syntax); |
| 25360 } |
| 25361 |
| 25362 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { |
| 25363 _addBindings(c, model, syntax); |
| 25364 } |
| 25365 } |
| 25366 |
| 25367 |
| 25368 void _addAttributeBindings(Element element, model, syntax) { |
| 25369 element.attributes.forEach((name, value) { |
| 25370 if (value == '' && (name == 'bind' || name == 'repeat')) { |
| 25371 value = '{{}}'; |
| 25372 } |
| 25373 _parseAndBind(element, value, name, model, syntax); |
| 25374 }); |
| 25375 } |
| 25376 |
| 25377 void _parseAndBind(Node node, String text, String name, model, |
| 25378 CustomBindingSyntax syntax) { |
| 25379 |
| 25380 var tokens = _parseMustacheTokens(text); |
| 25381 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { |
| 25382 return; |
| 25383 } |
| 25384 |
| 25385 if (tokens.length == 1 && tokens[0].isBinding) { |
| 25386 _bindOrDelegate(node, name, model, tokens[0].value, syntax); |
| 25387 return; |
| 25388 } |
| 25389 |
| 25390 var replacementBinding = new CompoundBinding(); |
| 25391 for (var i = 0; i < tokens.length; i++) { |
| 25392 var token = tokens[i]; |
| 25393 if (token.isBinding) { |
| 25394 _bindOrDelegate(replacementBinding, i, model, token.value, syntax); |
| 25395 } |
| 25396 } |
| 25397 |
| 25398 replacementBinding.combinator = (values) { |
| 25399 var newValue = new StringBuffer(); |
| 25400 |
| 25401 for (var i = 0; i < tokens.length; i++) { |
| 25402 var token = tokens[i]; |
| 25403 if (token.isText) { |
| 25404 newValue.write(token.value); |
| 25405 } else { |
| 25406 var value = values[i]; |
| 25407 if (value != null) { |
| 25408 newValue.write(value); |
| 25409 } |
| 25410 } |
| 25411 } |
| 25412 |
| 25413 return newValue.toString(); |
| 25414 }; |
| 25415 |
| 25416 node.bind(name, replacementBinding, 'value'); |
| 25417 } |
| 25418 |
| 25419 void _bindOrDelegate(node, name, model, String path, |
| 25420 CustomBindingSyntax syntax) { |
| 25421 |
| 25422 if (syntax != null) { |
| 25423 var delegateBinding = syntax.getBinding(model, path, name, node); |
| 25424 if (delegateBinding != null) { |
| 25425 model = delegateBinding; |
| 25426 path = 'value'; |
| 25427 } |
| 25428 } |
| 25429 |
| 25430 node.bind(name, model, path); |
| 25431 } |
| 25432 |
| 25433 class _BindingToken { |
| 25434 final String value; |
| 25435 final bool isBinding; |
| 25436 |
| 25437 _BindingToken(this.value, {this.isBinding: false}); |
| 25438 |
| 25439 bool get isText => !isBinding; |
| 25440 } |
| 25441 |
| 25442 List<_BindingToken> _parseMustacheTokens(String s) { |
| 25443 var result = []; |
| 25444 var length = s.length; |
| 25445 var index = 0, lastIndex = 0; |
| 25446 while (lastIndex < length) { |
| 25447 index = s.indexOf('{{', lastIndex); |
| 25448 if (index < 0) { |
| 25449 result.add(new _BindingToken(s.substring(lastIndex))); |
| 25450 break; |
| 25451 } else { |
| 25452 // There is a non-empty text run before the next path token. |
| 25453 if (index > 0 && lastIndex < index) { |
| 25454 result.add(new _BindingToken(s.substring(lastIndex, index))); |
| 25455 } |
| 25456 lastIndex = index + 2; |
| 25457 index = s.indexOf('}}', lastIndex); |
| 25458 if (index < 0) { |
| 25459 var text = s.substring(lastIndex - 2); |
| 25460 if (result.length > 0 && result.last.isText) { |
| 25461 result.last.value += text; |
| 25462 } else { |
| 25463 result.add(new _BindingToken(text)); |
| 25464 } |
| 25465 break; |
| 25466 } |
| 25467 |
| 25468 var value = s.substring(lastIndex, index).trim(); |
| 25469 result.add(new _BindingToken(value, isBinding: true)); |
| 25470 lastIndex = index + 2; |
| 25471 } |
| 25472 } |
| 25473 return result; |
| 25474 } |
| 25475 |
| 25476 void _addTemplateInstanceRecord(fragment, model) { |
| 25477 if (fragment.$dom_firstChild == null) { |
| 25478 return; |
| 25479 } |
| 25480 |
| 25481 var instanceRecord = new TemplateInstance( |
| 25482 fragment.$dom_firstChild, fragment.$dom_lastChild, model); |
| 25483 |
| 25484 var node = instanceRecord.firstNode; |
| 25485 while (node != null) { |
| 25486 node._templateInstance = instanceRecord; |
| 25487 node = node.nextNode; |
| 25488 } |
| 25489 } |
| 25490 |
| 25491 void _removeAllBindingsRecursively(Node node) { |
| 25492 node.unbindAll(); |
| 25493 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { |
| 25494 _removeAllBindingsRecursively(c); |
| 25495 } |
| 25496 } |
| 25497 |
| 25498 void _removeTemplateChild(Node parent, Node child) { |
| 25499 child._templateInstance = null; |
| 25500 if (child is Element && child.isTemplate) { |
| 25501 // Make sure we stop observing when we remove an element. |
| 25502 var templateIterator = child._templateIterator; |
| 25503 if (templateIterator != null) { |
| 25504 templateIterator.abandon(); |
| 25505 child._templateIterator = null; |
| 25506 } |
| 25507 } |
| 25508 child.remove(); |
| 25509 _removeAllBindingsRecursively(child); |
| 25510 } |
| 25511 |
| 25512 class _InstanceCursor { |
| 25513 final Element _template; |
| 25514 Node _terminator; |
| 25515 Node _previousTerminator; |
| 25516 int _previousIndex = -1; |
| 25517 int _index = 0; |
| 25518 |
| 25519 _InstanceCursor(this._template, [index]) { |
| 25520 _terminator = _template; |
| 25521 if (index != null) { |
| 25522 while (index-- > 0) { |
| 25523 next(); |
| 25524 } |
| 25525 } |
| 25526 } |
| 25527 |
| 25528 void next() { |
| 25529 _previousTerminator = _terminator; |
| 25530 _previousIndex = _index; |
| 25531 _index++; |
| 25532 |
| 25533 while (_index > _terminator._instanceTerminatorCount) { |
| 25534 _index -= _terminator._instanceTerminatorCount; |
| 25535 _terminator = _terminator.nextNode; |
| 25536 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') { |
| 25537 _index += _instanceCount(_terminator); |
| 25538 } |
| 25539 } |
| 25540 } |
| 25541 |
| 25542 void abandon() { |
| 25543 assert(_instanceCount(_template) > 0); |
| 25544 assert(_terminator._instanceTerminatorCount > 0); |
| 25545 assert(_index > 0); |
| 25546 |
| 25547 _terminator._instanceTerminatorCount--; |
| 25548 _index--; |
| 25549 } |
| 25550 |
| 25551 void insert(fragment) { |
| 25552 assert(_template.parentNode != null); |
| 25553 |
| 25554 _previousTerminator = _terminator; |
| 25555 _previousIndex = _index; |
| 25556 _index++; |
| 25557 |
| 25558 _terminator = fragment.$dom_lastChild; |
| 25559 if (_terminator == null) _terminator = _previousTerminator; |
| 25560 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode); |
| 25561 |
| 25562 _terminator._instanceTerminatorCount++; |
| 25563 if (_terminator != _previousTerminator) { |
| 25564 while (_previousTerminator._instanceTerminatorCount > |
| 25565 _previousIndex) { |
| 25566 _previousTerminator._instanceTerminatorCount--; |
| 25567 _terminator._instanceTerminatorCount++; |
| 25568 } |
| 25569 } |
| 25570 } |
| 25571 |
| 25572 void remove() { |
| 25573 assert(_previousIndex != -1); |
| 25574 assert(_previousTerminator != null && |
| 25575 (_previousIndex > 0 || _previousTerminator == _template)); |
| 25576 assert(_terminator != null && _index > 0); |
| 25577 assert(_template.parentNode != null); |
| 25578 assert(_instanceCount(_template) > 0); |
| 25579 |
| 25580 if (_previousTerminator == _terminator) { |
| 25581 assert(_index == _previousIndex + 1); |
| 25582 _terminator._instanceTerminatorCount--; |
| 25583 _terminator = _template; |
| 25584 _previousTerminator = null; |
| 25585 _previousIndex = -1; |
| 25586 return; |
| 25587 } |
| 25588 |
| 25589 _terminator._instanceTerminatorCount--; |
| 25590 |
| 25591 var parent = _template.parentNode; |
| 25592 while (_previousTerminator.nextNode != _terminator) { |
| 25593 _removeTemplateChild(parent, _previousTerminator.nextNode); |
| 25594 } |
| 25595 _removeTemplateChild(parent, _terminator); |
| 25596 |
| 25597 _terminator = _previousTerminator; |
| 25598 _index = _previousIndex; |
| 25599 _previousTerminator = null; |
| 25600 _previousIndex = -1; // 0? |
| 25601 } |
| 25602 } |
| 25603 |
| 25604 |
| 25605 class _TemplateIterator { |
| 25606 final Element _templateElement; |
| 25607 int instanceCount = 0; |
| 25608 List iteratedValue; |
| 25609 bool observing = false; |
| 25610 final CompoundBinding inputs; |
| 25611 |
| 25612 StreamSubscription _sub; |
| 25613 StreamSubscription _valueBinding; |
| 25614 |
| 25615 _TemplateIterator(this._templateElement) |
| 25616 : inputs = new CompoundBinding(resolveInputs) { |
| 25617 |
| 25618 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); |
| 25619 } |
| 25620 |
| 25621 static Object resolveInputs(Map values) { |
| 25622 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) { |
| 25623 return null; |
| 25624 } |
| 25625 |
| 25626 if (values.containsKey('repeat')) { |
| 25627 return values['repeat']; |
| 25628 } |
| 25629 |
| 25630 if (values.containsKey('bind')) { |
| 25631 return [values['bind']]; |
| 25632 } |
| 25633 |
| 25634 return null; |
| 25635 } |
| 25636 |
| 25637 void valueChanged(value) { |
| 25638 clear(); |
| 25639 if (value is! List) return; |
| 25640 |
| 25641 iteratedValue = value; |
| 25642 |
| 25643 if (value is Observable) { |
| 25644 _sub = value.changes.listen(_handleChanges); |
| 25645 } |
| 25646 |
| 25647 int len = iteratedValue.length; |
| 25648 if (len > 0) { |
| 25649 _handleChanges([new ListChangeRecord(0, addedCount: len)]); |
| 25650 } |
| 25651 } |
| 25652 |
| 25653 // TODO(jmesserly): port MDV v3. |
| 25654 getInstanceModel(model, syntax) => model; |
| 25655 getInstanceFragment(syntax) => _templateElement.createInstance(); |
| 25656 |
| 25657 void _handleChanges(List<ListChangeRecord> splices) { |
| 25658 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']]; |
| 25659 |
| 25660 for (var splice in splices) { |
| 25661 if (splice is! ListChangeRecord) continue; |
| 25662 |
| 25663 for (int i = 0; i < splice.removedCount; i++) { |
| 25664 var cursor = new _InstanceCursor(_templateElement, splice.index + 1); |
| 25665 cursor.remove(); |
| 25666 instanceCount--; |
| 25667 } |
| 25668 |
| 25669 for (var addIndex = splice.index; |
| 25670 addIndex < splice.index + splice.addedCount; |
| 25671 addIndex++) { |
| 25672 |
| 25673 var model = getInstanceModel(iteratedValue[addIndex], syntax); |
| 25674 var fragment = getInstanceFragment(syntax); |
| 25675 |
| 25676 _addBindings(fragment, model, syntax); |
| 25677 _addTemplateInstanceRecord(fragment, model); |
| 25678 |
| 25679 var cursor = new _InstanceCursor(_templateElement, addIndex); |
| 25680 cursor.insert(fragment); |
| 25681 instanceCount++; |
| 25682 } |
| 25683 } |
| 25684 } |
| 25685 |
| 25686 void unobserve() { |
| 25687 if (_sub == null) return; |
| 25688 _sub.cancel(); |
| 25689 _sub = null; |
| 25690 } |
| 25691 |
| 25692 void clear() { |
| 25693 unobserve(); |
| 25694 |
| 25695 iteratedValue = null; |
| 25696 if (instanceCount == 0) return; |
| 25697 |
| 25698 for (var i = 0; i < instanceCount; i++) { |
| 25699 var cursor = new _InstanceCursor(_templateElement, 1); |
| 25700 cursor.remove(); |
| 25701 } |
| 25702 |
| 25703 instanceCount = 0; |
| 25704 } |
| 25705 |
| 25706 void abandon() { |
| 25707 unobserve(); |
| 25708 _valueBinding.cancel(); |
| 25709 inputs.dispose(); |
| 25710 } |
| 25711 } |
| 25712 |
| 25713 int _instanceCount(Element element) { |
| 25714 var templateIterator = element._templateIterator; |
| 25715 return templateIterator != null ? templateIterator.instanceCount : 0; |
| 25716 } |
| 24353 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 25717 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 24354 // for details. All rights reserved. Use of this source code is governed by a | 25718 // for details. All rights reserved. Use of this source code is governed by a |
| 24355 // BSD-style license that can be found in the LICENSE file. | 25719 // BSD-style license that can be found in the LICENSE file. |
| 24356 | 25720 |
| 24357 | 25721 |
| 24358 class _HttpRequestUtils { | 25722 class _HttpRequestUtils { |
| 24359 | 25723 |
| 24360 // Helper for factory HttpRequest.get | 25724 // Helper for factory HttpRequest.get |
| 24361 static HttpRequest get(String url, | 25725 static HttpRequest get(String url, |
| 24362 onComplete(HttpRequest request), | 25726 onComplete(HttpRequest request), |
| (...skipping 763 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 25126 DateTime _convertNativeToDart_DateTime(date) { | 26490 DateTime _convertNativeToDart_DateTime(date) { |
| 25127 var millisSinceEpoch = JS('int', '#.getTime()', date); | 26491 var millisSinceEpoch = JS('int', '#.getTime()', date); |
| 25128 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); | 26492 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); |
| 25129 } | 26493 } |
| 25130 | 26494 |
| 25131 _convertDartToNative_DateTime(DateTime date) { | 26495 _convertDartToNative_DateTime(DateTime date) { |
| 25132 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); | 26496 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); |
| 25133 } | 26497 } |
| 25134 | 26498 |
| 25135 WindowBase _convertNativeToDart_Window(win) { | 26499 WindowBase _convertNativeToDart_Window(win) { |
| 26500 if (win == null) return null; |
| 25136 return _DOMWindowCrossFrame._createSafe(win); | 26501 return _DOMWindowCrossFrame._createSafe(win); |
| 25137 } | 26502 } |
| 25138 | 26503 |
| 25139 EventTarget _convertNativeToDart_EventTarget(e) { | 26504 EventTarget _convertNativeToDart_EventTarget(e) { |
| 25140 if (e == null) { | 26505 if (e == null) { |
| 25141 return null; | 26506 return null; |
| 25142 } | 26507 } |
| 25143 // Assume it's a Window if it contains the setInterval property. It may be | 26508 // Assume it's a Window if it contains the setInterval property. It may be |
| 25144 // from a different frame - without a patched prototype - so we cannot | 26509 // from a different frame - without a patched prototype - so we cannot |
| 25145 // rely on Dart type checking. | 26510 // rely on Dart type checking. |
| (...skipping 424 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 25570 _position = nextPosition; | 26935 _position = nextPosition; |
| 25571 return true; | 26936 return true; |
| 25572 } | 26937 } |
| 25573 _current = null; | 26938 _current = null; |
| 25574 _position = _array.length; | 26939 _position = _array.length; |
| 25575 return false; | 26940 return false; |
| 25576 } | 26941 } |
| 25577 | 26942 |
| 25578 T get current => _current; | 26943 T get current => _current; |
| 25579 } | 26944 } |
| OLD | NEW |