| 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' hide Symbol; | 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:mdv_observe_impl'; | |
| 13 import 'dart:typed_data'; | 12 import 'dart:typed_data'; |
| 14 import 'dart:svg' as svg; | 13 import 'dart:svg' as svg; |
| 15 import 'dart:web_audio' as web_audio; | 14 import 'dart:web_audio' as web_audio; |
| 16 import 'dart:web_gl' as gl; | 15 import 'dart:web_gl' as gl; |
| 17 import 'dart:web_sql'; | 16 import 'dart:web_sql'; |
| 18 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin
gBehavior, JSName, Null, Returns; | 17 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin
gBehavior, JSName, Null, Returns; |
| 19 import 'dart:_isolate_helper' show IsolateNatives; | 18 import 'dart:_isolate_helper' show IsolateNatives; |
| 20 import 'dart:_foreign_helper' show JS; | 19 import 'dart:_foreign_helper' show JS; |
| 21 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 20 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 22 // for details. All rights reserved. Use of this source code is governed by a | 21 // 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... |
| 6031 @DomName('Document.cookie') | 6030 @DomName('Document.cookie') |
| 6032 @DocsEditable | 6031 @DocsEditable |
| 6033 String cookie; | 6032 String cookie; |
| 6034 | 6033 |
| 6035 WindowBase get window => _convertNativeToDart_Window(this._get_window); | 6034 WindowBase get window => _convertNativeToDart_Window(this._get_window); |
| 6036 @JSName('defaultView') | 6035 @JSName('defaultView') |
| 6037 @DomName('Document.window') | 6036 @DomName('Document.window') |
| 6038 @DocsEditable | 6037 @DocsEditable |
| 6039 @Creates('Window|=Object') | 6038 @Creates('Window|=Object') |
| 6040 @Returns('Window|=Object') | 6039 @Returns('Window|=Object') |
| 6041 @Creates('Window|=Object|Null') | |
| 6042 @Returns('Window|=Object|Null') | |
| 6043 final dynamic _get_window; | 6040 final dynamic _get_window; |
| 6044 | 6041 |
| 6045 @DomName('Document.documentElement') | 6042 @DomName('Document.documentElement') |
| 6046 @DocsEditable | 6043 @DocsEditable |
| 6047 final Element documentElement; | 6044 final Element documentElement; |
| 6048 | 6045 |
| 6049 @DomName('Document.domain') | 6046 @DomName('Document.domain') |
| 6050 @DocsEditable | 6047 @DocsEditable |
| 6051 final String domain; | 6048 final String domain; |
| 6052 | 6049 |
| (...skipping 586 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 6639 | 6636 |
| 6640 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or | 6637 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or |
| 6641 // SVG strings? | 6638 // SVG strings? |
| 6642 void set innerHtml(String value) { | 6639 void set innerHtml(String value) { |
| 6643 this.nodes.clear(); | 6640 this.nodes.clear(); |
| 6644 | 6641 |
| 6645 final e = new Element.tag("div"); | 6642 final e = new Element.tag("div"); |
| 6646 e.innerHtml = value; | 6643 e.innerHtml = value; |
| 6647 | 6644 |
| 6648 // Copy list first since we don't want liveness during iteration. | 6645 // Copy list first since we don't want liveness during iteration. |
| 6649 List nodes = new List.from(e.nodes, growable: false); | 6646 List nodes = new List.from(e.nodes); |
| 6650 this.append(nodes); | 6647 this.nodes.addAll(nodes); |
| 6651 } | 6648 } |
| 6652 | 6649 |
| 6653 /** | 6650 /** |
| 6654 * Adds the specified text as a text node after the last child of this | 6651 * Adds the specified text as a text node after the last child of this |
| 6655 * document fragment. | 6652 * document fragment. |
| 6656 */ | 6653 */ |
| 6657 void appendText(String text) { | 6654 void appendText(String text) { |
| 6658 this.append(new Text(text)); | 6655 this.append(new Text(text)); |
| 6659 } | 6656 } |
| 6660 | 6657 |
| (...skipping 360 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 7021 if (result == null) throw new StateError("No elements"); | 7018 if (result == null) throw new StateError("No elements"); |
| 7022 return result; | 7019 return result; |
| 7023 } | 7020 } |
| 7024 | 7021 |
| 7025 Element get single { | 7022 Element get single { |
| 7026 if (length > 1) throw new StateError("More than one element"); | 7023 if (length > 1) throw new StateError("More than one element"); |
| 7027 return first; | 7024 return first; |
| 7028 } | 7025 } |
| 7029 } | 7026 } |
| 7030 | 7027 |
| 7031 /** | 7028 /** |
| 7032 * An immutable list containing HTML elements. This list contains some | 7029 * An immutable list containing HTML elements. This list contains some |
| 7033 * additional methods for ease of CSS manipulation on a group of elements. | 7030 * additional methods for ease of CSS manipulation on a group of elements. |
| 7034 */ | 7031 */ |
| 7035 abstract class ElementList<T extends Element> extends ListBase<T> { | 7032 abstract class ElementList<T extends Element> extends ListBase<T> { |
| 7036 /** | 7033 /** |
| 7037 * The union of all CSS classes applied to the elements in this list. | 7034 * The union of all CSS classes applied to the elements in this list. |
| 7038 * | 7035 * |
| 7039 * This set makes it easy to add, remove or toggle (add if not present, remove | 7036 * This set makes it easy to add, remove or toggle (add if not present, remove |
| 7040 * if present) the classes applied to a collection of elements. | 7037 * if present) the classes applied to a collection of elements. |
| 7041 * | 7038 * |
| (...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 7503 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { | 7500 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { |
| 7504 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); | 7501 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); |
| 7505 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { | 7502 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { |
| 7506 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); | 7503 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); |
| 7507 } else if (JS('bool', '!!#.msMatchesSelector', this)) { | 7504 } else if (JS('bool', '!!#.msMatchesSelector', this)) { |
| 7508 return JS('bool', '#.msMatchesSelector(#)', this, selectors); | 7505 return JS('bool', '#.msMatchesSelector(#)', this, selectors); |
| 7509 } | 7506 } |
| 7510 throw new UnsupportedError("Not supported on this platform"); | 7507 throw new UnsupportedError("Not supported on this platform"); |
| 7511 } | 7508 } |
| 7512 | 7509 |
| 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 | |
| 7762 | 7510 |
| 7763 @DomName('Element.abortEvent') | 7511 @DomName('Element.abortEvent') |
| 7764 @DocsEditable | 7512 @DocsEditable |
| 7765 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider
<Event>('abort'); | 7513 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider
<Event>('abort'); |
| 7766 | 7514 |
| 7767 @DomName('Element.beforecopyEvent') | 7515 @DomName('Element.beforecopyEvent') |
| 7768 @DocsEditable | 7516 @DocsEditable |
| 7769 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro
vider<Event>('beforecopy'); | 7517 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro
vider<Event>('beforecopy'); |
| 7770 | 7518 |
| 7771 @DomName('Element.beforecutEvent') | 7519 @DomName('Element.beforecutEvent') |
| (...skipping 724 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 8496 @DomName('Element.onwebkitfullscreenchange') | 8244 @DomName('Element.onwebkitfullscreenchange') |
| 8497 @DocsEditable | 8245 @DocsEditable |
| 8498 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); | 8246 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); |
| 8499 | 8247 |
| 8500 @DomName('Element.onwebkitfullscreenerror') | 8248 @DomName('Element.onwebkitfullscreenerror') |
| 8501 @DocsEditable | 8249 @DocsEditable |
| 8502 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); | 8250 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); |
| 8503 | 8251 |
| 8504 } | 8252 } |
| 8505 | 8253 |
| 8506 | |
| 8507 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); | 8254 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); |
| 8508 class _ElementFactoryProvider { | 8255 class _ElementFactoryProvider { |
| 8509 static const _CUSTOM_PARENT_TAG_MAP = const { | 8256 static const _CUSTOM_PARENT_TAG_MAP = const { |
| 8510 'body' : 'html', | 8257 'body' : 'html', |
| 8511 'head' : 'html', | 8258 'head' : 'html', |
| 8512 'caption' : 'table', | 8259 'caption' : 'table', |
| 8513 'td': 'tr', | 8260 'td': 'tr', |
| 8514 'th': 'tr', | 8261 'th': 'tr', |
| 8515 'colgroup': 'table', | 8262 'colgroup': 'table', |
| 8516 'col' : 'colgroup', | 8263 'col' : 'colgroup', |
| 8517 'tr' : 'tbody', | 8264 'tr' : 'tbody', |
| 8518 'tbody' : 'table', | 8265 'tbody' : 'table', |
| 8519 'tfoot' : 'table', | 8266 'tfoot' : 'table', |
| 8520 'thead' : 'table', | 8267 'thead' : 'table', |
| 8521 'track' : 'audio', | 8268 'track' : 'audio', |
| 8522 }; | 8269 }; |
| 8523 | 8270 |
| 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 |
| 8524 @DomName('Document.createElement') | 8284 @DomName('Document.createElement') |
| 8525 static Element createElement_html(String html) { | 8285 static Element createElement_html(String html) { |
| 8526 // TODO(jacobr): this method can be made more robust and performant. | 8286 // TODO(jacobr): this method can be made more robust and performant. |
| 8527 // 1) Cache the dummy parent elements required to use innerHTML rather than | 8287 // 1) Cache the dummy parent elements required to use innerHTML rather than |
| 8528 // creating them every call. | 8288 // creating them every call. |
| 8529 // 2) Verify that the html does not contain leading or trailing text nodes. | 8289 // 2) Verify that the html does not contain leading or trailing text nodes. |
| 8530 // 3) Verify that the html does not contain both <head> and <body> tags. | 8290 // 3) Verify that the html does not contain both <head> and <body> tags. |
| 8531 // 4) Detatch the created element from its dummy parent. | 8291 // 4) Detatch the created element from its dummy parent. |
| 8532 String parentTag = 'div'; | 8292 String parentTag = 'div'; |
| 8533 String tag; | 8293 String tag; |
| 8534 final match = _START_TAG_REGEXP.firstMatch(html); | 8294 final match = _START_TAG_REGEXP.firstMatch(html); |
| 8535 if (match != null) { | 8295 if (match != null) { |
| 8536 tag = match.group(1).toLowerCase(); | 8296 tag = match.group(1).toLowerCase(); |
| 8537 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { | 8297 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { |
| 8538 return _createTableForIE(html, tag); | 8298 return _createTableForIE(html, tag); |
| 8539 } | 8299 } |
| 8540 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; | 8300 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; |
| 8541 if (parentTag == null) parentTag = 'div'; | 8301 if (parentTag == null) parentTag = 'div'; |
| 8542 } | 8302 } |
| 8543 | 8303 |
| 8544 final temp = new Element.tag(parentTag); | 8304 final temp = new Element.tag(parentTag); |
| 8545 temp.innerHtml = html; | 8305 temp.innerHtml = html; |
| 8546 | 8306 |
| 8547 Element element; | 8307 Element element; |
| (...skipping 1897 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 10445 @SupportedBrowser(SupportedBrowser.SAFARI) | 10205 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 10446 @Experimental | 10206 @Experimental |
| 10447 Element get pointerLockElement => | 10207 Element get pointerLockElement => |
| 10448 $dom_webkitPointerLockElement; | 10208 $dom_webkitPointerLockElement; |
| 10449 | 10209 |
| 10450 @DomName('Document.webkitVisibilityState') | 10210 @DomName('Document.webkitVisibilityState') |
| 10451 @SupportedBrowser(SupportedBrowser.CHROME) | 10211 @SupportedBrowser(SupportedBrowser.CHROME) |
| 10452 @SupportedBrowser(SupportedBrowser.SAFARI) | 10212 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 10453 @Experimental | 10213 @Experimental |
| 10454 String get visibilityState => $dom_webkitVisibilityState; | 10214 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; | |
| 10460 } | 10215 } |
| 10461 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 10216 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 10462 // for details. All rights reserved. Use of this source code is governed by a | 10217 // for details. All rights reserved. Use of this source code is governed by a |
| 10463 // BSD-style license that can be found in the LICENSE file. | 10218 // BSD-style license that can be found in the LICENSE file. |
| 10464 | 10219 |
| 10465 | 10220 |
| 10466 @DocsEditable | 10221 @DocsEditable |
| 10467 @DomName('HTMLHtmlElement') | 10222 @DomName('HTMLHtmlElement') |
| 10468 class HtmlElement extends Element native "HTMLHtmlElement" { | 10223 class HtmlElement extends Element native "HTMLHtmlElement" { |
| 10469 | 10224 |
| (...skipping 816 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11286 var e = document.$dom_createElement("input"); | 11041 var e = document.$dom_createElement("input"); |
| 11287 if (type != null) { | 11042 if (type != null) { |
| 11288 try { | 11043 try { |
| 11289 // IE throws an exception for unknown types. | 11044 // IE throws an exception for unknown types. |
| 11290 e.type = type; | 11045 e.type = type; |
| 11291 } catch(_) {} | 11046 } catch(_) {} |
| 11292 } | 11047 } |
| 11293 return e; | 11048 return e; |
| 11294 } | 11049 } |
| 11295 | 11050 |
| 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 | |
| 11352 @DomName('HTMLInputElement.webkitSpeechChangeEvent') | 11051 @DomName('HTMLInputElement.webkitSpeechChangeEvent') |
| 11353 @DocsEditable | 11052 @DocsEditable |
| 11354 @SupportedBrowser(SupportedBrowser.CHROME) | 11053 @SupportedBrowser(SupportedBrowser.CHROME) |
| 11355 @SupportedBrowser(SupportedBrowser.SAFARI) | 11054 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 11356 @Experimental | 11055 @Experimental |
| 11357 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); | 11056 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); |
| 11358 | 11057 |
| 11359 @DomName('HTMLInputElement.accept') | 11058 @DomName('HTMLInputElement.accept') |
| 11360 @DocsEditable | 11059 @DocsEditable |
| 11361 String accept; | 11060 String accept; |
| (...skipping 3251 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 14613 _this.$dom_removeChild(node); | 14312 _this.$dom_removeChild(node); |
| 14614 return true; | 14313 return true; |
| 14615 } | 14314 } |
| 14616 | 14315 |
| 14617 void _filter(bool test(Node node), bool removeMatching) { | 14316 void _filter(bool test(Node node), bool removeMatching) { |
| 14618 // This implementation of removeWhere/retainWhere is more efficient | 14317 // This implementation of removeWhere/retainWhere is more efficient |
| 14619 // than the default in ListBase. Child nodes can be removed in constant | 14318 // than the default in ListBase. Child nodes can be removed in constant |
| 14620 // time. | 14319 // time. |
| 14621 Node child = _this.$dom_firstChild; | 14320 Node child = _this.$dom_firstChild; |
| 14622 while (child != null) { | 14321 while (child != null) { |
| 14623 Node nextChild = child.nextNode; | 14322 Node nextChild = child.nextSibling; |
| 14624 if (test(child) == removeMatching) { | 14323 if (test(child) == removeMatching) { |
| 14625 _this.$dom_removeChild(child); | 14324 _this.$dom_removeChild(child); |
| 14626 } | 14325 } |
| 14627 child = nextChild; | 14326 child = nextChild; |
| 14628 } | 14327 } |
| 14629 } | 14328 } |
| 14630 | 14329 |
| 14631 void removeWhere(bool test(Node node)) { | 14330 void removeWhere(bool test(Node node)) { |
| 14632 _filter(test, true); | 14331 _filter(test, true); |
| 14633 } | 14332 } |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 14738 // Should use $dom_firstChild, Bug 8886. | 14437 // Should use $dom_firstChild, Bug 8886. |
| 14739 this.insertBefore(newNodes[0], refChild); | 14438 this.insertBefore(newNodes[0], refChild); |
| 14740 } | 14439 } |
| 14741 } else { | 14440 } else { |
| 14742 for (var node in newNodes) { | 14441 for (var node in newNodes) { |
| 14743 this.insertBefore(node, refChild); | 14442 this.insertBefore(node, refChild); |
| 14744 } | 14443 } |
| 14745 } | 14444 } |
| 14746 } | 14445 } |
| 14747 | 14446 |
| 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 |
| 14748 /** | 14530 /** |
| 14749 * Print out a String representation of this Node. | 14531 * Print out a String representation of this Node. |
| 14750 */ | 14532 */ |
| 14751 String toString() => localName == null ? | 14533 String toString() => localName == null ? |
| 14752 (nodeValue == null ? super.toString() : nodeValue) : localName; | 14534 (nodeValue == null ? super.toString() : nodeValue) : localName; |
| 14753 | 14535 |
| 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 | |
| 14792 | 14536 |
| 14793 static const int ATTRIBUTE_NODE = 2; | 14537 static const int ATTRIBUTE_NODE = 2; |
| 14794 | 14538 |
| 14795 static const int CDATA_SECTION_NODE = 4; | 14539 static const int CDATA_SECTION_NODE = 4; |
| 14796 | 14540 |
| 14797 static const int COMMENT_NODE = 8; | 14541 static const int COMMENT_NODE = 8; |
| 14798 | 14542 |
| 14799 static const int DOCUMENT_FRAGMENT_NODE = 11; | 14543 static const int DOCUMENT_FRAGMENT_NODE = 11; |
| 14800 | 14544 |
| 14801 static const int DOCUMENT_NODE = 9; | 14545 static const int DOCUMENT_NODE = 9; |
| (...skipping 3857 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 18659 | 18403 |
| 18660 @JSName('insertRow') | 18404 @JSName('insertRow') |
| 18661 @DomName('HTMLTableSectionElement.insertRow') | 18405 @DomName('HTMLTableSectionElement.insertRow') |
| 18662 @DocsEditable | 18406 @DocsEditable |
| 18663 Element $dom_insertRow(int index) native; | 18407 Element $dom_insertRow(int index) native; |
| 18664 } | 18408 } |
| 18665 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 18409 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 18666 // for details. All rights reserved. Use of this source code is governed by a | 18410 // for details. All rights reserved. Use of this source code is governed by a |
| 18667 // BSD-style license that can be found in the LICENSE file. | 18411 // BSD-style license that can be found in the LICENSE file. |
| 18668 | 18412 |
| 18669 // WARNING: Do not edit - generated code. | |
| 18670 | 18413 |
| 18671 | 18414 @DocsEditable |
| 18672 @Experimental | |
| 18673 @DomName('HTMLTemplateElement') | 18415 @DomName('HTMLTemplateElement') |
| 18674 @SupportedBrowser(SupportedBrowser.CHROME) | 18416 @SupportedBrowser(SupportedBrowser.CHROME) |
| 18675 @Experimental | 18417 @Experimental |
| 18676 class TemplateElement extends Element native "HTMLTemplateElement" { | 18418 class TemplateElement extends Element native "HTMLTemplateElement" { |
| 18677 | 18419 |
| 18678 @DomName('HTMLTemplateElement.HTMLTemplateElement') | 18420 @DomName('HTMLTemplateElement.HTMLTemplateElement') |
| 18679 @DocsEditable | 18421 @DocsEditable |
| 18680 factory TemplateElement() => document.$dom_createElement("template"); | 18422 factory TemplateElement() => document.$dom_createElement("template"); |
| 18681 | 18423 |
| 18682 /// Checks if this type is supported on the current platform. | 18424 /// Checks if this type is supported on the current platform. |
| 18683 static bool get supported => Element.isTagSupported('template'); | 18425 static bool get supported => Element.isTagSupported('template'); |
| 18684 | 18426 |
| 18685 @JSName('content') | |
| 18686 @DomName('HTMLTemplateElement.content') | 18427 @DomName('HTMLTemplateElement.content') |
| 18687 @DocsEditable | 18428 @DocsEditable |
| 18688 final DocumentFragment $dom_content; | 18429 final DocumentFragment 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 = {}; | |
| 18797 } | 18430 } |
| 18798 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 18431 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 18799 // for details. All rights reserved. Use of this source code is governed by a | 18432 // for details. All rights reserved. Use of this source code is governed by a |
| 18800 // BSD-style license that can be found in the LICENSE file. | 18433 // BSD-style license that can be found in the LICENSE file. |
| 18801 | 18434 |
| 18802 // WARNING: Do not edit - generated code. | 18435 // WARNING: Do not edit - generated code. |
| 18803 | 18436 |
| 18804 | 18437 |
| 18805 @DomName('Text') | 18438 @DomName('Text') |
| 18806 class Text extends CharacterData native "Text" { | 18439 class Text extends CharacterData native "Text" { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 18819 final String wholeText; | 18452 final String wholeText; |
| 18820 | 18453 |
| 18821 @DomName('Text.replaceWholeText') | 18454 @DomName('Text.replaceWholeText') |
| 18822 @DocsEditable | 18455 @DocsEditable |
| 18823 Text replaceWholeText(String content) native; | 18456 Text replaceWholeText(String content) native; |
| 18824 | 18457 |
| 18825 @DomName('Text.splitText') | 18458 @DomName('Text.splitText') |
| 18826 @DocsEditable | 18459 @DocsEditable |
| 18827 Text splitText(int offset) native; | 18460 Text splitText(int offset) native; |
| 18828 | 18461 |
| 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 } | |
| 18865 } | 18462 } |
| 18866 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 18463 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 18867 // for details. All rights reserved. Use of this source code is governed by a | 18464 // for details. All rights reserved. Use of this source code is governed by a |
| 18868 // BSD-style license that can be found in the LICENSE file. | 18465 // BSD-style license that can be found in the LICENSE file. |
| 18869 | 18466 |
| 18870 | 18467 |
| 18871 @DocsEditable | 18468 @DocsEditable |
| 18872 @DomName('HTMLTextAreaElement') | 18469 @DomName('HTMLTextAreaElement') |
| 18873 class TextAreaElement extends Element native "HTMLTextAreaElement" { | 18470 class TextAreaElement extends Element native "HTMLTextAreaElement" { |
| 18874 | 18471 |
| (...skipping 5555 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 24430 * Key value used when an implementation is unable to identify another key | 24027 * Key value used when an implementation is unable to identify another key |
| 24431 * value, due to either hardware, platform, or software constraints | 24028 * value, due to either hardware, platform, or software constraints |
| 24432 */ | 24029 */ |
| 24433 static const String UNIDENTIFIED = "Unidentified"; | 24030 static const String UNIDENTIFIED = "Unidentified"; |
| 24434 } | 24031 } |
| 24435 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 24032 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 24436 // for details. All rights reserved. Use of this source code is governed by a | 24033 // for details. All rights reserved. Use of this source code is governed by a |
| 24437 // BSD-style license that can be found in the LICENSE file. | 24034 // BSD-style license that can be found in the LICENSE file. |
| 24438 | 24035 |
| 24439 | 24036 |
| 24440 // This code is inspired by ChangeSummary: | 24037 class _ModelTreeObserver { |
| 24441 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js | 24038 static bool _initialized = false; |
| 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 // TODO(jmesserly): the primary reason to have this object exposed is because | |
| 24465 // we have get/set for value. Ideally "observePath" could just return the | |
| 24466 // stream. | |
| 24467 /** | |
| 24468 * A data-bound path starting from a view-model or model object, for example | |
| 24469 * `foo.bar.baz`. | |
| 24470 * | |
| 24471 * When the [values] stream is being listened to, this will observe changes to | |
| 24472 * the object and any intermediate object along the path, and send [values] | |
| 24473 * accordingly. When all listeners are unregistered it will stop observing | |
| 24474 * the objects. | |
| 24475 * | |
| 24476 * This class is used to implement [Node.bind] and similar functionality. | |
| 24477 */ | |
| 24478 @Experimental | |
| 24479 class PathObserver { | |
| 24480 /** The object being observed. */ | |
| 24481 final object; | |
| 24482 | |
| 24483 /** The path string. */ | |
| 24484 final String path; | |
| 24485 | |
| 24486 /** True if the path is valid, otherwise false. */ | |
| 24487 final bool _isValid; | |
| 24488 | |
| 24489 // TODO(jmesserly): same issue here as ObservableMixin: is there an easier | |
| 24490 // way to get a broadcast stream? | |
| 24491 StreamController _values; | |
| 24492 Stream _valueStream; | |
| 24493 | |
| 24494 _PropertyObserver _observer, _lastObserver; | |
| 24495 | |
| 24496 Object _lastValue; | |
| 24497 bool _scheduled = false; | |
| 24498 | 24039 |
| 24499 /** | 24040 /** |
| 24500 * Observes [path] on [object] for changes. This returns an object that can be | 24041 * Start an observer watching the document for tree changes to automatically |
| 24501 * used to get the changes and get/set the value at this path. | 24042 * propagate model changes. |
| 24502 * See [PathObserver.values] and [PathObserver.value]. | 24043 * |
| 24044 * Currently this does not support propagation through Shadow DOMs. |
| 24503 */ | 24045 */ |
| 24504 PathObserver(this.object, String path) | 24046 static void initialize() { |
| 24505 : path = path, | 24047 if (!_initialized) { |
| 24506 _isValid = _isPathValid(path) { | 24048 _initialized = true; |
| 24507 | 24049 |
| 24508 // TODO(jmesserly): if the path is empty, or the object is! Observable, we | 24050 if (MutationObserver.supported) { |
| 24509 // can optimize the PathObserver to be more lightweight. | 24051 var observer = new MutationObserver(_processTreeChange); |
| 24510 | 24052 observer.observe(document, childList: true, subtree: true); |
| 24511 _values = new StreamController(onListen: _observe, onCancel: _unobserve); | 24053 } else { |
| 24512 | 24054 document.on['DOMNodeInserted'].listen(_handleNodeInserted); |
| 24513 if (_isValid) { | 24055 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved); |
| 24514 var segments = []; | |
| 24515 for (var segment in path.trim().split('.')) { | |
| 24516 if (segment == '') continue; | |
| 24517 var index = int.parse(segment, onError: (_) {}); | |
| 24518 segments.add(index != null ? index : new Symbol(segment)); | |
| 24519 } | |
| 24520 | |
| 24521 // Create the property observer linked list. | |
| 24522 // Note that the structure of a path can't change after it is initially | |
| 24523 // constructed, even though the objects along the path can change. | |
| 24524 for (int i = segments.length - 1; i >= 0; i--) { | |
| 24525 _observer = new _PropertyObserver(this, segments[i], _observer); | |
| 24526 if (_lastObserver == null) _lastObserver = _observer; | |
| 24527 } | 24056 } |
| 24528 } | 24057 } |
| 24529 } | 24058 } |
| 24530 | 24059 |
| 24531 // TODO(jmesserly): we could try adding the first value to the stream, but | 24060 static void _processTreeChange(List<MutationRecord> mutations, |
| 24532 // that delivers the first record async. | 24061 MutationObserver observer) { |
| 24533 /** | 24062 for (var record in mutations) { |
| 24534 * Listens to the stream, and invokes the [callback] immediately with the | 24063 for (var node in record.addedNodes) { |
| 24535 * current [value]. This is useful for bindings, which want to be up-to-date | 24064 // When nodes enter the document we need to make sure that all of the |
| 24536 * immediately. | 24065 // models are properly propagated through the entire sub-tree. |
| 24537 */ | 24066 propagateModel(node, _calculatedModel(node), true); |
| 24538 StreamSubscription bindSync(void callback(value)) { | 24067 } |
| 24539 var result = values.listen(callback); | 24068 for (var node in record.removedNodes) { |
| 24540 callback(value); | 24069 propagateModel(node, _calculatedModel(node), false); |
| 24541 return result; | 24070 } |
| 24542 } | |
| 24543 | |
| 24544 // TODO(jmesserly): should this be a change record with the old value? | |
| 24545 // TODO(jmesserly): should this be a broadcast stream? We only need | |
| 24546 // single-subscription in the bindings system, so single sub saves overhead. | |
| 24547 /** | |
| 24548 * Gets the stream of values that were observed at this path. | |
| 24549 * This returns a single-subscription stream. | |
| 24550 */ | |
| 24551 Stream get values => _values.stream; | |
| 24552 | |
| 24553 /** Force synchronous delivery of [values]. */ | |
| 24554 void _deliverValues() { | |
| 24555 _scheduled = false; | |
| 24556 | |
| 24557 var newValue = value; | |
| 24558 if (!identical(_lastValue, newValue)) { | |
| 24559 _values.add(newValue); | |
| 24560 _lastValue = newValue; | |
| 24561 } | 24071 } |
| 24562 } | 24072 } |
| 24563 | 24073 |
| 24564 void _observe() { | 24074 static void _handleNodeInserted(MutationEvent e) { |
| 24565 if (_observer != null) { | 24075 var node = e.target; |
| 24566 _lastValue = value; | 24076 window.setImmediate(() { |
| 24567 _observer.observe(); | 24077 propagateModel(node, _calculatedModel(node), true); |
| 24568 } | 24078 }); |
| 24569 } | 24079 } |
| 24570 | 24080 |
| 24571 void _unobserve() { | 24081 static void _handleNodeRemoved(MutationEvent e) { |
| 24572 if (_observer != null) _observer.unobserve(); | 24082 var node = e.target; |
| 24083 window.setImmediate(() { |
| 24084 propagateModel(node, _calculatedModel(node), false); |
| 24085 }); |
| 24573 } | 24086 } |
| 24574 | 24087 |
| 24575 void _notifyChange() { | 24088 /** |
| 24576 if (_scheduled) return; | 24089 * Figures out what the model should be for a node, avoiding any cached |
| 24577 _scheduled = true; | 24090 * model values. |
| 24578 | 24091 */ |
| 24579 // TODO(jmesserly): should we have a guarenteed order with respect to other | 24092 static _calculatedModel(node) { |
| 24580 // paths? If so, we could implement this fairly easily by sorting instances | 24093 if (node._hasLocalModel == true) { |
| 24581 // of this class by birth order before delivery. | 24094 return node._model; |
| 24582 queueChangeRecords(_deliverValues); | 24095 } else if (node.parentNode != null) { |
| 24096 return node.parentNode._model; |
| 24097 } |
| 24098 return null; |
| 24583 } | 24099 } |
| 24584 | 24100 |
| 24585 /** Gets the last reported value at this path. */ | 24101 /** |
| 24586 get value { | 24102 * Pushes model changes down through the tree. |
| 24587 if (!_isValid) return null; | 24103 * |
| 24588 if (_observer == null) return object; | 24104 * Set fullTree to true if the state of the tree is unknown and model changes |
| 24589 _observer.ensureValue(object); | 24105 * should be propagated through the entire tree. |
| 24590 return _lastObserver.value; | 24106 */ |
| 24591 } | 24107 static void propagateModel(Node node, model, bool fullTree) { |
| 24592 | 24108 // Calling into user code with the != call could generate exceptions. |
| 24593 /** Sets the value at this path. */ | 24109 // Catch and report them a global exceptions. |
| 24594 void set value(Object value) { | 24110 try { |
| 24595 // TODO(jmesserly): throw if property cannot be set? | 24111 if (node._hasLocalModel != true && node._model != model && |
| 24596 // MDV seems tolerant of these error. | 24112 node._modelChangedStreams != null && |
| 24597 if (_observer == null || !_isValid) return; | 24113 !node._modelChangedStreams.isEmpty) { |
| 24598 _observer.ensureValue(object); | 24114 node._model = model; |
| 24599 var last = _lastObserver; | 24115 node._modelChangedStreams.toList() |
| 24600 if (_setObjectProperty(last._object, last._property, value)) { | 24116 .forEach((controller) => controller.add(node)); |
| 24601 // Technically, this would get updated asynchronously via a change record. | 24117 } |
| 24602 // However, it is nice if calling the getter will yield the same value | 24118 } catch (e, s) { |
| 24603 // that was just set. So we use this opportunity to update our cache. | 24119 new Future.error(e, s); |
| 24604 last.value = value; | |
| 24605 } | 24120 } |
| 24606 } | 24121 for (var child = node.$dom_firstChild; child != null; |
| 24607 } | 24122 child = child.nextNode) { |
| 24608 | 24123 if (child._hasLocalModel != true) { |
| 24609 // TODO(jmesserly): these should go away in favor of mirrors! | 24124 propagateModel(child, model, fullTree); |
| 24610 _getObjectProperty(object, property) { | 24125 } else if (fullTree) { |
| 24611 if (object is List && property is int) { | 24126 propagateModel(child, child._model, true); |
| 24612 if (property >= 0 && property < object.length) { | |
| 24613 return object[property]; | |
| 24614 } else { | |
| 24615 return null; | |
| 24616 } | |
| 24617 } | |
| 24618 | |
| 24619 // TODO(jmesserly): what about length? | |
| 24620 if (object is Map) return object[property]; | |
| 24621 | |
| 24622 if (object is Observable) return object.getValueWorkaround(property); | |
| 24623 | |
| 24624 return null; | |
| 24625 } | |
| 24626 | |
| 24627 bool _setObjectProperty(object, property, value) { | |
| 24628 if (object is List && property is int) { | |
| 24629 object[property] = value; | |
| 24630 } else if (object is Map) { | |
| 24631 object[property] = value; | |
| 24632 } else if (object is Observable) { | |
| 24633 (object as Observable).setValueWorkaround(property, value); | |
| 24634 } else { | |
| 24635 return false; | |
| 24636 } | |
| 24637 return true; | |
| 24638 } | |
| 24639 | |
| 24640 | |
| 24641 class _PropertyObserver { | |
| 24642 final PathObserver _path; | |
| 24643 final _property; | |
| 24644 final Symbol _symbol; | |
| 24645 final _PropertyObserver _next; | |
| 24646 | |
| 24647 // TODO(jmesserly): would be nice not to store both of these. | |
| 24648 Object _object; | |
| 24649 Object _value; | |
| 24650 StreamSubscription _sub; | |
| 24651 | |
| 24652 _PropertyObserver(this._path, this._property, this._next); | |
| 24653 | |
| 24654 get value => _value; | |
| 24655 | |
| 24656 void set value(Object newValue) { | |
| 24657 _value = newValue; | |
| 24658 if (_next != null) { | |
| 24659 if (_sub != null) _next.unobserve(); | |
| 24660 _next.ensureValue(_value); | |
| 24661 if (_sub != null) _next.observe(); | |
| 24662 } | |
| 24663 } | |
| 24664 | |
| 24665 void ensureValue(object) { | |
| 24666 // If we're observing, values should be up to date already. | |
| 24667 if (_sub != null) return; | |
| 24668 | |
| 24669 _object = object; | |
| 24670 value = _getObjectProperty(object, _property); | |
| 24671 } | |
| 24672 | |
| 24673 void observe() { | |
| 24674 if (_object is Observable) { | |
| 24675 assert(_sub == null); | |
| 24676 _sub = (_object as Observable).changes.listen(_onChange); | |
| 24677 } | |
| 24678 if (_next != null) _next.observe(); | |
| 24679 } | |
| 24680 | |
| 24681 void unobserve() { | |
| 24682 if (_sub == null) return; | |
| 24683 | |
| 24684 _sub.cancel(); | |
| 24685 _sub = null; | |
| 24686 if (_next != null) _next.unobserve(); | |
| 24687 } | |
| 24688 | |
| 24689 void _onChange(List<ChangeRecord> changes) { | |
| 24690 for (var change in changes) { | |
| 24691 // TODO(jmesserly): what to do about "new Symbol" here? | |
| 24692 // Ideally this would only preserve names if the user has opted in to | |
| 24693 // them being preserved. | |
| 24694 // TODO(jmesserly): should we drop observable maps with String keys? | |
| 24695 // If so then we only need one check here. | |
| 24696 if (change.changes(_property)) { | |
| 24697 value = _getObjectProperty(_object, _property); | |
| 24698 _path._notifyChange(); | |
| 24699 return; | |
| 24700 } | 24127 } |
| 24701 } | 24128 } |
| 24702 } | 24129 } |
| 24703 } | 24130 } |
| 24704 | |
| 24705 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js | |
| 24706 | |
| 24707 const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*'; | |
| 24708 final _pathRegExp = new RegExp('^' | |
| 24709 '(?:#?' + _pathIndentPart + ')?' | |
| 24710 '(?:' | |
| 24711 '(?:\\.' + _pathIndentPart + ')' | |
| 24712 ')*' | |
| 24713 r'$', caseSensitive: false); | |
| 24714 | |
| 24715 final _spacesRegExp = new RegExp(r'\s'); | |
| 24716 | |
| 24717 bool _isPathValid(String s) { | |
| 24718 s = s.replaceAll(_spacesRegExp, ''); | |
| 24719 | |
| 24720 if (s == '') return true; | |
| 24721 if (s[0] == '.') return false; | |
| 24722 return _pathRegExp.hasMatch(s); | |
| 24723 } | |
| 24724 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 24131 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 24725 // for details. All rights reserved. Use of this source code is governed by a | 24132 // for details. All rights reserved. Use of this source code is governed by a |
| 24726 // BSD-style license that can be found in the LICENSE file. | 24133 // BSD-style license that can be found in the LICENSE file. |
| 24727 | 24134 |
| 24728 | 24135 |
| 24729 /** | 24136 /** |
| 24730 * A utility class for representing two-dimensional positions. | 24137 * A utility class for representing two-dimensional positions. |
| 24731 */ | 24138 */ |
| 24732 class Point { | 24139 class Point { |
| 24733 final num x; | 24140 final num x; |
| (...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 24936 * Truncates coordinates to integers and returns the result as a new | 24343 * Truncates coordinates to integers and returns the result as a new |
| 24937 * rectangle. | 24344 * rectangle. |
| 24938 */ | 24345 */ |
| 24939 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), | 24346 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), |
| 24940 height.toInt()); | 24347 height.toInt()); |
| 24941 | 24348 |
| 24942 Point get topLeft => new Point(this.left, this.top); | 24349 Point get topLeft => new Point(this.left, this.top); |
| 24943 Point get bottomRight => new Point(this.left + this.width, | 24350 Point get bottomRight => new Point(this.left + this.width, |
| 24944 this.top + this.height); | 24351 this.top + this.height); |
| 24945 } | 24352 } |
| 24946 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 24947 // for details. All rights reserved. Use of this source code is governed by a | |
| 24948 // BSD-style license that can be found in the LICENSE file. | |
| 24949 | |
| 24950 | |
| 24951 // This code is a port of Model-Driven-Views: | |
| 24952 // https://github.com/toolkitchen/mdv | |
| 24953 // The code mostly comes from src/template_element.js | |
| 24954 | |
| 24955 typedef void _ChangeHandler(value); | |
| 24956 | |
| 24957 /** | |
| 24958 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases, | |
| 24959 * but (by design) don't attempt to implement a wide array of specialized | |
| 24960 * behaviors. | |
| 24961 * | |
| 24962 * Enabling these features in MDV is a matter of implementing and registering an | |
| 24963 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more | |
| 24964 * delegation functions which implement specialized behavior. This object is | |
| 24965 * registered with MDV via [TemplateElement.syntax]: | |
| 24966 * | |
| 24967 * | |
| 24968 * HTML: | |
| 24969 * <template bind syntax="MySyntax"> | |
| 24970 * {{ What!Ever('crazy')->thing^^^I+Want(data) }} | |
| 24971 * </template> | |
| 24972 * | |
| 24973 * Dart: | |
| 24974 * class MySyntax extends CustomBindingSyntax { | |
| 24975 * getBinding(model, path, name, node) { | |
| 24976 * // The magic happens here! | |
| 24977 * } | |
| 24978 * } | |
| 24979 * | |
| 24980 * ... | |
| 24981 * | |
| 24982 * TemplateElement.syntax['MySyntax'] = new MySyntax(); | |
| 24983 * | |
| 24984 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more | |
| 24985 * information about Custom Syntax. | |
| 24986 */ | |
| 24987 // TODO(jmesserly): if this is just one method, a function type would make it | |
| 24988 // more Dart-friendly. | |
| 24989 @Experimental | |
| 24990 abstract class CustomBindingSyntax { | |
| 24991 // TODO(jmesserly): I had to remove type annotations from "name" and "node" | |
| 24992 // Normally they are String and Node respectively. But sometimes it will pass | |
| 24993 // (int name, CompoundBinding node). That seems very confusing; we may want | |
| 24994 // to change this API. | |
| 24995 getBinding(model, String path, name, node); | |
| 24996 } | |
| 24997 | |
| 24998 /** The callback used in the [CompoundBinding.combinator] field. */ | |
| 24999 @Experimental | |
| 25000 typedef Object CompoundBindingCombinator(Map objects); | |
| 25001 | |
| 25002 /** Information about the instantiated template. */ | |
| 25003 @Experimental | |
| 25004 class TemplateInstance { | |
| 25005 // TODO(rafaelw): firstNode & lastNode should be read-synchronous | |
| 25006 // in cases where script has modified the template instance boundary. | |
| 25007 | |
| 25008 /** The first node of this template instantiation. */ | |
| 25009 final Node firstNode; | |
| 25010 | |
| 25011 /** | |
| 25012 * The last node of this template instantiation. | |
| 25013 * This could be identical to [firstNode] if the template only expanded to a | |
| 25014 * single node. | |
| 25015 */ | |
| 25016 final Node lastNode; | |
| 25017 | |
| 25018 /** The model used to instantiate the template. */ | |
| 25019 final model; | |
| 25020 | |
| 25021 TemplateInstance(this.firstNode, this.lastNode, this.model); | |
| 25022 } | |
| 25023 | |
| 25024 /** | |
| 25025 * Model-Driven Views contains a helper object which is useful for the | |
| 25026 * implementation of a Custom Syntax. | |
| 25027 * | |
| 25028 * var binding = new CompoundBinding((values) { | |
| 25029 * var combinedValue; | |
| 25030 * // compute combinedValue based on the current values which are provided | |
| 25031 * return combinedValue; | |
| 25032 * }); | |
| 25033 * binding.bind('name1', obj1, path1); | |
| 25034 * binding.bind('name2', obj2, path2); | |
| 25035 * //... | |
| 25036 * binding.bind('nameN', objN, pathN); | |
| 25037 * | |
| 25038 * CompoundBinding is an object which knows how to listen to multiple path | |
| 25039 * values (registered via [bind]) and invoke its [combinator] when one or more | |
| 25040 * of the values have changed and set its [value] property to the return value | |
| 25041 * of the function. When any value has changed, all current values are provided | |
| 25042 * to the [combinator] in the single `values` argument. | |
| 25043 * | |
| 25044 * See [CustomBindingSyntax] for more information. | |
| 25045 */ | |
| 25046 // TODO(jmesserly): what is the public API surface here? I just guessed; | |
| 25047 // most of it seemed non-public. | |
| 25048 @Experimental | |
| 25049 class CompoundBinding extends ObservableBase { | |
| 25050 CompoundBindingCombinator _combinator; | |
| 25051 | |
| 25052 // TODO(jmesserly): ideally these would be String keys, but sometimes we | |
| 25053 // use integers. | |
| 25054 Map<dynamic, StreamSubscription> _bindings = new Map(); | |
| 25055 Map _values = new Map(); | |
| 25056 bool _scheduled = false; | |
| 25057 bool _disposed = false; | |
| 25058 Object _value; | |
| 25059 | |
| 25060 CompoundBinding([CompoundBindingCombinator combinator]) { | |
| 25061 // TODO(jmesserly): this is a tweak to the original code, it seemed to me | |
| 25062 // that passing the combinator to the constructor should be equivalent to | |
| 25063 // setting it via the property. | |
| 25064 // I also added a null check to the combinator setter. | |
| 25065 this.combinator = combinator; | |
| 25066 } | |
| 25067 | |
| 25068 CompoundBindingCombinator get combinator => _combinator; | |
| 25069 | |
| 25070 set combinator(CompoundBindingCombinator combinator) { | |
| 25071 _combinator = combinator; | |
| 25072 if (combinator != null) _scheduleResolve(); | |
| 25073 } | |
| 25074 | |
| 25075 static const _VALUE = const Symbol('value'); | |
| 25076 | |
| 25077 get value => _value; | |
| 25078 | |
| 25079 void set value(newValue) { | |
| 25080 _value = notifyPropertyChange(_VALUE, _value, newValue); | |
| 25081 } | |
| 25082 | |
| 25083 // TODO(jmesserly): remove these workarounds when dart2js supports mirrors! | |
| 25084 getValueWorkaround(key) { | |
| 25085 if (key == _VALUE) return value; | |
| 25086 return null; | |
| 25087 } | |
| 25088 setValueWorkaround(key, val) { | |
| 25089 if (key == _VALUE) value = val; | |
| 25090 } | |
| 25091 | |
| 25092 void bind(name, model, String path) { | |
| 25093 unbind(name); | |
| 25094 | |
| 25095 _bindings[name] = new PathObserver(model, path).bindSync((value) { | |
| 25096 _values[name] = value; | |
| 25097 _scheduleResolve(); | |
| 25098 }); | |
| 25099 } | |
| 25100 | |
| 25101 void unbind(name, {bool suppressResolve: false}) { | |
| 25102 var binding = _bindings.remove(name); | |
| 25103 if (binding == null) return; | |
| 25104 | |
| 25105 binding.cancel(); | |
| 25106 _values.remove(name); | |
| 25107 if (!suppressResolve) _scheduleResolve(); | |
| 25108 } | |
| 25109 | |
| 25110 // TODO(rafaelw): Is this the right processing model? | |
| 25111 // TODO(rafaelw): Consider having a seperate ChangeSummary for | |
| 25112 // CompoundBindings so to excess dirtyChecks. | |
| 25113 void _scheduleResolve() { | |
| 25114 if (_scheduled) return; | |
| 25115 _scheduled = true; | |
| 25116 queueChangeRecords(resolve); | |
| 25117 } | |
| 25118 | |
| 25119 void resolve() { | |
| 25120 if (_disposed) return; | |
| 25121 _scheduled = false; | |
| 25122 | |
| 25123 if (_combinator == null) { | |
| 25124 throw new StateError( | |
| 25125 'CompoundBinding attempted to resolve without a combinator'); | |
| 25126 } | |
| 25127 | |
| 25128 value = _combinator(_values); | |
| 25129 } | |
| 25130 | |
| 25131 void dispose() { | |
| 25132 for (var binding in _bindings.values) { | |
| 25133 binding.cancel(); | |
| 25134 } | |
| 25135 _bindings.clear(); | |
| 25136 _values.clear(); | |
| 25137 | |
| 25138 _disposed = true; | |
| 25139 value = null; | |
| 25140 } | |
| 25141 } | |
| 25142 | |
| 25143 Stream<Event> _getStreamForInputType(InputElement element) { | |
| 25144 switch (element.type) { | |
| 25145 case 'checkbox': | |
| 25146 return element.onClick; | |
| 25147 case 'radio': | |
| 25148 case 'select-multiple': | |
| 25149 case 'select-one': | |
| 25150 return element.onChange; | |
| 25151 default: | |
| 25152 return element.onInput; | |
| 25153 } | |
| 25154 } | |
| 25155 | |
| 25156 abstract class _InputBinding { | |
| 25157 final InputElement element; | |
| 25158 PathObserver binding; | |
| 25159 StreamSubscription _pathSub; | |
| 25160 StreamSubscription _eventSub; | |
| 25161 | |
| 25162 _InputBinding(this.element, model, String path) { | |
| 25163 binding = new PathObserver(model, path); | |
| 25164 _pathSub = binding.bindSync(valueChanged); | |
| 25165 _eventSub = _getStreamForInputType(element).listen(updateBinding); | |
| 25166 } | |
| 25167 | |
| 25168 void valueChanged(newValue); | |
| 25169 | |
| 25170 void updateBinding(e); | |
| 25171 | |
| 25172 void unbind() { | |
| 25173 binding = null; | |
| 25174 _pathSub.cancel(); | |
| 25175 _eventSub.cancel(); | |
| 25176 } | |
| 25177 } | |
| 25178 | |
| 25179 class _ValueBinding extends _InputBinding { | |
| 25180 _ValueBinding(element, model, path) : super(element, model, path); | |
| 25181 | |
| 25182 void valueChanged(value) { | |
| 25183 element.value = value == null ? '' : '$value'; | |
| 25184 } | |
| 25185 | |
| 25186 void updateBinding(e) { | |
| 25187 binding.value = element.value; | |
| 25188 } | |
| 25189 } | |
| 25190 | |
| 25191 // TODO(jmesserly): not sure what kind of boolean conversion rules to | |
| 25192 // apply for template data-binding. HTML attributes are true if they're present. | |
| 25193 // However Dart only treats "true" as true. Since this is HTML we'll use | |
| 25194 // something closer to the HTML rules: null (missing) and false are false, | |
| 25195 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59 | |
| 25196 bool _templateBooleanConversion(value) => null != value && false != value; | |
| 25197 | |
| 25198 class _CheckedBinding extends _InputBinding { | |
| 25199 _CheckedBinding(element, model, path) : super(element, model, path); | |
| 25200 | |
| 25201 void valueChanged(value) { | |
| 25202 element.checked = _templateBooleanConversion(value); | |
| 25203 } | |
| 25204 | |
| 25205 void updateBinding(e) { | |
| 25206 binding.value = element.checked; | |
| 25207 | |
| 25208 // Only the radio button that is getting checked gets an event. We | |
| 25209 // therefore find all the associated radio buttons and update their | |
| 25210 // CheckedBinding manually. | |
| 25211 if (element is InputElement && element.type == 'radio') { | |
| 25212 for (var r in _getAssociatedRadioButtons(element)) { | |
| 25213 var checkedBinding = r._checkedBinding; | |
| 25214 if (checkedBinding != null) { | |
| 25215 // Set the value directly to avoid an infinite call stack. | |
| 25216 checkedBinding.binding.value = false; | |
| 25217 } | |
| 25218 } | |
| 25219 } | |
| 25220 } | |
| 25221 } | |
| 25222 | |
| 25223 // TODO(jmesserly): polyfill document.contains API instead of doing it here | |
| 25224 bool _isNodeInDocument(Node node) { | |
| 25225 // On non-IE this works: | |
| 25226 // return node.document.contains(node); | |
| 25227 var document = node.document; | |
| 25228 if (node == document || node.parentNode == document) return true; | |
| 25229 return document.documentElement.contains(node); | |
| 25230 } | |
| 25231 | |
| 25232 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. | |
| 25233 // Returns an array containing all radio buttons other than |element| that | |
| 25234 // have the same |name|, either in the form that |element| belongs to or, | |
| 25235 // if no form, in the document tree to which |element| belongs. | |
| 25236 // | |
| 25237 // This implementation is based upon the HTML spec definition of a | |
| 25238 // "radio button group": | |
| 25239 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht
ml#radio-button-group | |
| 25240 // | |
| 25241 Iterable _getAssociatedRadioButtons(element) { | |
| 25242 if (!_isNodeInDocument(element)) return []; | |
| 25243 if (element.form != null) { | |
| 25244 return element.form.nodes.where((el) { | |
| 25245 return el != element && | |
| 25246 el is InputElement && | |
| 25247 el.type == 'radio' && | |
| 25248 el.name == element.name; | |
| 25249 }); | |
| 25250 } else { | |
| 25251 var radios = element.document.queryAll( | |
| 25252 'input[type="radio"][name="${element.name}"]'); | |
| 25253 return radios.where((el) => el != element && el.form == null); | |
| 25254 } | |
| 25255 } | |
| 25256 | |
| 25257 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { | |
| 25258 var clone = node.clone(false); // Shallow clone. | |
| 25259 if (clone is Element && clone.isTemplate) { | |
| 25260 TemplateElement.decorate(clone, node); | |
| 25261 if (syntax != null) { | |
| 25262 clone.attributes.putIfAbsent('syntax', () => syntax); | |
| 25263 } | |
| 25264 } | |
| 25265 | |
| 25266 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { | |
| 25267 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax)); | |
| 25268 } | |
| 25269 return clone; | |
| 25270 } | |
| 25271 | |
| 25272 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df
n-template-contents-owner | |
| 25273 Document _getTemplateContentsOwner(Document doc) { | |
| 25274 if (doc.window == null) { | |
| 25275 return doc; | |
| 25276 } | |
| 25277 var d = doc._templateContentsOwner; | |
| 25278 if (d == null) { | |
| 25279 // TODO(arv): This should either be a Document or HTMLDocument depending | |
| 25280 // on doc. | |
| 25281 d = doc.implementation.createHtmlDocument(''); | |
| 25282 while (d.$dom_lastChild != null) { | |
| 25283 d.$dom_lastChild.remove(); | |
| 25284 } | |
| 25285 doc._templateContentsOwner = d; | |
| 25286 } | |
| 25287 return d; | |
| 25288 } | |
| 25289 | |
| 25290 Element _cloneAndSeperateAttributeTemplate(Element templateElement) { | |
| 25291 var clone = templateElement.clone(false); | |
| 25292 var attributes = templateElement.attributes; | |
| 25293 for (var name in attributes.keys.toList()) { | |
| 25294 switch (name) { | |
| 25295 case 'template': | |
| 25296 case 'repeat': | |
| 25297 case 'bind': | |
| 25298 case 'ref': | |
| 25299 clone.attributes.remove(name); | |
| 25300 break; | |
| 25301 default: | |
| 25302 attributes.remove(name); | |
| 25303 break; | |
| 25304 } | |
| 25305 } | |
| 25306 | |
| 25307 return clone; | |
| 25308 } | |
| 25309 | |
| 25310 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) { | |
| 25311 var content = templateElement.content; | |
| 25312 | |
| 25313 if (!templateElement._isAttributeTemplate) { | |
| 25314 var child; | |
| 25315 while ((child = templateElement.$dom_firstChild) != null) { | |
| 25316 content.append(child); | |
| 25317 } | |
| 25318 return; | |
| 25319 } | |
| 25320 | |
| 25321 // For attribute templates we copy the whole thing into the content and | |
| 25322 // we move the non template attributes into the content. | |
| 25323 // | |
| 25324 // <tr foo template> | |
| 25325 // | |
| 25326 // becomes | |
| 25327 // | |
| 25328 // <tr template> | |
| 25329 // + #document-fragment | |
| 25330 // + <tr foo> | |
| 25331 // | |
| 25332 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement); | |
| 25333 var child; | |
| 25334 while ((child = templateElement.$dom_firstChild) != null) { | |
| 25335 newRoot.append(child); | |
| 25336 } | |
| 25337 content.append(newRoot); | |
| 25338 } | |
| 25339 | |
| 25340 void _bootstrapTemplatesRecursivelyFrom(Node node) { | |
| 25341 void bootstrap(template) { | |
| 25342 if (!TemplateElement.decorate(template)) { | |
| 25343 _bootstrapTemplatesRecursivelyFrom(template.content); | |
| 25344 } | |
| 25345 } | |
| 25346 | |
| 25347 // Need to do this first as the contents may get lifted if |node| is | |
| 25348 // template. | |
| 25349 // TODO(jmesserly): node is DocumentFragment or Element | |
| 25350 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors); | |
| 25351 if (node is Element && node.isTemplate) bootstrap(node); | |
| 25352 | |
| 25353 templateDescendents.forEach(bootstrap); | |
| 25354 } | |
| 25355 | |
| 25356 final String _allTemplatesSelectors = 'template, option[template], ' + | |
| 25357 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", "); | |
| 25358 | |
| 25359 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) { | |
| 25360 if (node is Element) { | |
| 25361 _addAttributeBindings(node, model, syntax); | |
| 25362 } else if (node is Text) { | |
| 25363 _parseAndBind(node, node.text, 'text', model, syntax); | |
| 25364 } | |
| 25365 | |
| 25366 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { | |
| 25367 _addBindings(c, model, syntax); | |
| 25368 } | |
| 25369 } | |
| 25370 | |
| 25371 | |
| 25372 void _addAttributeBindings(Element element, model, syntax) { | |
| 25373 element.attributes.forEach((name, value) { | |
| 25374 if (value == '' && (name == 'bind' || name == 'repeat')) { | |
| 25375 value = '{{}}'; | |
| 25376 } | |
| 25377 _parseAndBind(element, value, name, model, syntax); | |
| 25378 }); | |
| 25379 } | |
| 25380 | |
| 25381 void _parseAndBind(Node node, String text, String name, model, | |
| 25382 CustomBindingSyntax syntax) { | |
| 25383 | |
| 25384 var tokens = _parseMustacheTokens(text); | |
| 25385 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { | |
| 25386 return; | |
| 25387 } | |
| 25388 | |
| 25389 if (tokens.length == 1 && tokens[0].isBinding) { | |
| 25390 _bindOrDelegate(node, name, model, tokens[0].value, syntax); | |
| 25391 return; | |
| 25392 } | |
| 25393 | |
| 25394 var replacementBinding = new CompoundBinding(); | |
| 25395 for (var i = 0; i < tokens.length; i++) { | |
| 25396 var token = tokens[i]; | |
| 25397 if (token.isBinding) { | |
| 25398 _bindOrDelegate(replacementBinding, i, model, token.value, syntax); | |
| 25399 } | |
| 25400 } | |
| 25401 | |
| 25402 replacementBinding.combinator = (values) { | |
| 25403 var newValue = new StringBuffer(); | |
| 25404 | |
| 25405 for (var i = 0; i < tokens.length; i++) { | |
| 25406 var token = tokens[i]; | |
| 25407 if (token.isText) { | |
| 25408 newValue.write(token.value); | |
| 25409 } else { | |
| 25410 var value = values[i]; | |
| 25411 if (value != null) { | |
| 25412 newValue.write(value); | |
| 25413 } | |
| 25414 } | |
| 25415 } | |
| 25416 | |
| 25417 return newValue.toString(); | |
| 25418 }; | |
| 25419 | |
| 25420 node.bind(name, replacementBinding, 'value'); | |
| 25421 } | |
| 25422 | |
| 25423 void _bindOrDelegate(node, name, model, String path, | |
| 25424 CustomBindingSyntax syntax) { | |
| 25425 | |
| 25426 if (syntax != null) { | |
| 25427 var delegateBinding = syntax.getBinding(model, path, name, node); | |
| 25428 if (delegateBinding != null) { | |
| 25429 model = delegateBinding; | |
| 25430 path = 'value'; | |
| 25431 } | |
| 25432 } | |
| 25433 | |
| 25434 node.bind(name, model, path); | |
| 25435 } | |
| 25436 | |
| 25437 class _BindingToken { | |
| 25438 final String value; | |
| 25439 final bool isBinding; | |
| 25440 | |
| 25441 _BindingToken(this.value, {this.isBinding: false}); | |
| 25442 | |
| 25443 bool get isText => !isBinding; | |
| 25444 } | |
| 25445 | |
| 25446 List<_BindingToken> _parseMustacheTokens(String s) { | |
| 25447 var result = []; | |
| 25448 var length = s.length; | |
| 25449 var index = 0, lastIndex = 0; | |
| 25450 while (lastIndex < length) { | |
| 25451 index = s.indexOf('{{', lastIndex); | |
| 25452 if (index < 0) { | |
| 25453 result.add(new _BindingToken(s.substring(lastIndex))); | |
| 25454 break; | |
| 25455 } else { | |
| 25456 // There is a non-empty text run before the next path token. | |
| 25457 if (index > 0 && lastIndex < index) { | |
| 25458 result.add(new _BindingToken(s.substring(lastIndex, index))); | |
| 25459 } | |
| 25460 lastIndex = index + 2; | |
| 25461 index = s.indexOf('}}', lastIndex); | |
| 25462 if (index < 0) { | |
| 25463 var text = s.substring(lastIndex - 2); | |
| 25464 if (result.length > 0 && result.last.isText) { | |
| 25465 result.last.value += text; | |
| 25466 } else { | |
| 25467 result.add(new _BindingToken(text)); | |
| 25468 } | |
| 25469 break; | |
| 25470 } | |
| 25471 | |
| 25472 var value = s.substring(lastIndex, index).trim(); | |
| 25473 result.add(new _BindingToken(value, isBinding: true)); | |
| 25474 lastIndex = index + 2; | |
| 25475 } | |
| 25476 } | |
| 25477 return result; | |
| 25478 } | |
| 25479 | |
| 25480 void _addTemplateInstanceRecord(fragment, model) { | |
| 25481 if (fragment.$dom_firstChild == null) { | |
| 25482 return; | |
| 25483 } | |
| 25484 | |
| 25485 var instanceRecord = new TemplateInstance( | |
| 25486 fragment.$dom_firstChild, fragment.$dom_lastChild, model); | |
| 25487 | |
| 25488 var node = instanceRecord.firstNode; | |
| 25489 while (node != null) { | |
| 25490 node._templateInstance = instanceRecord; | |
| 25491 node = node.nextNode; | |
| 25492 } | |
| 25493 } | |
| 25494 | |
| 25495 void _removeAllBindingsRecursively(Node node) { | |
| 25496 node.unbindAll(); | |
| 25497 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { | |
| 25498 _removeAllBindingsRecursively(c); | |
| 25499 } | |
| 25500 } | |
| 25501 | |
| 25502 void _removeTemplateChild(Node parent, Node child) { | |
| 25503 child._templateInstance = null; | |
| 25504 if (child is Element && child.isTemplate) { | |
| 25505 // Make sure we stop observing when we remove an element. | |
| 25506 var templateIterator = child._templateIterator; | |
| 25507 if (templateIterator != null) { | |
| 25508 templateIterator.abandon(); | |
| 25509 child._templateIterator = null; | |
| 25510 } | |
| 25511 } | |
| 25512 child.remove(); | |
| 25513 _removeAllBindingsRecursively(child); | |
| 25514 } | |
| 25515 | |
| 25516 class _InstanceCursor { | |
| 25517 final Element _template; | |
| 25518 Node _terminator; | |
| 25519 Node _previousTerminator; | |
| 25520 int _previousIndex = -1; | |
| 25521 int _index = 0; | |
| 25522 | |
| 25523 _InstanceCursor(this._template, [index]) { | |
| 25524 _terminator = _template; | |
| 25525 if (index != null) { | |
| 25526 while (index-- > 0) { | |
| 25527 next(); | |
| 25528 } | |
| 25529 } | |
| 25530 } | |
| 25531 | |
| 25532 void next() { | |
| 25533 _previousTerminator = _terminator; | |
| 25534 _previousIndex = _index; | |
| 25535 _index++; | |
| 25536 | |
| 25537 while (_index > _terminator._instanceTerminatorCount) { | |
| 25538 _index -= _terminator._instanceTerminatorCount; | |
| 25539 _terminator = _terminator.nextNode; | |
| 25540 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') { | |
| 25541 _index += _instanceCount(_terminator); | |
| 25542 } | |
| 25543 } | |
| 25544 } | |
| 25545 | |
| 25546 void abandon() { | |
| 25547 assert(_instanceCount(_template) > 0); | |
| 25548 assert(_terminator._instanceTerminatorCount > 0); | |
| 25549 assert(_index > 0); | |
| 25550 | |
| 25551 _terminator._instanceTerminatorCount--; | |
| 25552 _index--; | |
| 25553 } | |
| 25554 | |
| 25555 void insert(fragment) { | |
| 25556 assert(_template.parentNode != null); | |
| 25557 | |
| 25558 _previousTerminator = _terminator; | |
| 25559 _previousIndex = _index; | |
| 25560 _index++; | |
| 25561 | |
| 25562 _terminator = fragment.$dom_lastChild; | |
| 25563 if (_terminator == null) _terminator = _previousTerminator; | |
| 25564 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode); | |
| 25565 | |
| 25566 _terminator._instanceTerminatorCount++; | |
| 25567 if (_terminator != _previousTerminator) { | |
| 25568 while (_previousTerminator._instanceTerminatorCount > | |
| 25569 _previousIndex) { | |
| 25570 _previousTerminator._instanceTerminatorCount--; | |
| 25571 _terminator._instanceTerminatorCount++; | |
| 25572 } | |
| 25573 } | |
| 25574 } | |
| 25575 | |
| 25576 void remove() { | |
| 25577 assert(_previousIndex != -1); | |
| 25578 assert(_previousTerminator != null && | |
| 25579 (_previousIndex > 0 || _previousTerminator == _template)); | |
| 25580 assert(_terminator != null && _index > 0); | |
| 25581 assert(_template.parentNode != null); | |
| 25582 assert(_instanceCount(_template) > 0); | |
| 25583 | |
| 25584 if (_previousTerminator == _terminator) { | |
| 25585 assert(_index == _previousIndex + 1); | |
| 25586 _terminator._instanceTerminatorCount--; | |
| 25587 _terminator = _template; | |
| 25588 _previousTerminator = null; | |
| 25589 _previousIndex = -1; | |
| 25590 return; | |
| 25591 } | |
| 25592 | |
| 25593 _terminator._instanceTerminatorCount--; | |
| 25594 | |
| 25595 var parent = _template.parentNode; | |
| 25596 while (_previousTerminator.nextNode != _terminator) { | |
| 25597 _removeTemplateChild(parent, _previousTerminator.nextNode); | |
| 25598 } | |
| 25599 _removeTemplateChild(parent, _terminator); | |
| 25600 | |
| 25601 _terminator = _previousTerminator; | |
| 25602 _index = _previousIndex; | |
| 25603 _previousTerminator = null; | |
| 25604 _previousIndex = -1; // 0? | |
| 25605 } | |
| 25606 } | |
| 25607 | |
| 25608 | |
| 25609 class _TemplateIterator { | |
| 25610 final Element _templateElement; | |
| 25611 int instanceCount = 0; | |
| 25612 List iteratedValue; | |
| 25613 bool observing = false; | |
| 25614 final CompoundBinding inputs; | |
| 25615 | |
| 25616 StreamSubscription _sub; | |
| 25617 StreamSubscription _valueBinding; | |
| 25618 | |
| 25619 _TemplateIterator(this._templateElement) | |
| 25620 : inputs = new CompoundBinding(resolveInputs) { | |
| 25621 | |
| 25622 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); | |
| 25623 } | |
| 25624 | |
| 25625 static Object resolveInputs(Map values) { | |
| 25626 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) { | |
| 25627 return null; | |
| 25628 } | |
| 25629 | |
| 25630 if (values.containsKey('repeat')) { | |
| 25631 return values['repeat']; | |
| 25632 } | |
| 25633 | |
| 25634 if (values.containsKey('bind')) { | |
| 25635 return [values['bind']]; | |
| 25636 } | |
| 25637 | |
| 25638 return null; | |
| 25639 } | |
| 25640 | |
| 25641 void valueChanged(value) { | |
| 25642 clear(); | |
| 25643 if (value is! List) return; | |
| 25644 | |
| 25645 iteratedValue = value; | |
| 25646 | |
| 25647 if (value is Observable) { | |
| 25648 _sub = value.changes.listen(_handleChanges); | |
| 25649 } | |
| 25650 | |
| 25651 int len = iteratedValue.length; | |
| 25652 if (len > 0) { | |
| 25653 _handleChanges([new ListChangeRecord(0, addedCount: len)]); | |
| 25654 } | |
| 25655 } | |
| 25656 | |
| 25657 // TODO(jmesserly): port MDV v3. | |
| 25658 getInstanceModel(model, syntax) => model; | |
| 25659 getInstanceFragment(syntax) => _templateElement.createInstance(); | |
| 25660 | |
| 25661 void _handleChanges(List<ListChangeRecord> splices) { | |
| 25662 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']]; | |
| 25663 | |
| 25664 for (var splice in splices) { | |
| 25665 if (splice is! ListChangeRecord) continue; | |
| 25666 | |
| 25667 for (int i = 0; i < splice.removedCount; i++) { | |
| 25668 var cursor = new _InstanceCursor(_templateElement, splice.index + 1); | |
| 25669 cursor.remove(); | |
| 25670 instanceCount--; | |
| 25671 } | |
| 25672 | |
| 25673 for (var addIndex = splice.index; | |
| 25674 addIndex < splice.index + splice.addedCount; | |
| 25675 addIndex++) { | |
| 25676 | |
| 25677 var model = getInstanceModel(iteratedValue[addIndex], syntax); | |
| 25678 var fragment = getInstanceFragment(syntax); | |
| 25679 | |
| 25680 _addBindings(fragment, model, syntax); | |
| 25681 _addTemplateInstanceRecord(fragment, model); | |
| 25682 | |
| 25683 var cursor = new _InstanceCursor(_templateElement, addIndex); | |
| 25684 cursor.insert(fragment); | |
| 25685 instanceCount++; | |
| 25686 } | |
| 25687 } | |
| 25688 } | |
| 25689 | |
| 25690 void unobserve() { | |
| 25691 if (_sub == null) return; | |
| 25692 _sub.cancel(); | |
| 25693 _sub = null; | |
| 25694 } | |
| 25695 | |
| 25696 void clear() { | |
| 25697 unobserve(); | |
| 25698 | |
| 25699 iteratedValue = null; | |
| 25700 if (instanceCount == 0) return; | |
| 25701 | |
| 25702 for (var i = 0; i < instanceCount; i++) { | |
| 25703 var cursor = new _InstanceCursor(_templateElement, 1); | |
| 25704 cursor.remove(); | |
| 25705 } | |
| 25706 | |
| 25707 instanceCount = 0; | |
| 25708 } | |
| 25709 | |
| 25710 void abandon() { | |
| 25711 unobserve(); | |
| 25712 _valueBinding.cancel(); | |
| 25713 inputs.dispose(); | |
| 25714 } | |
| 25715 } | |
| 25716 | |
| 25717 int _instanceCount(Element element) { | |
| 25718 var templateIterator = element._templateIterator; | |
| 25719 return templateIterator != null ? templateIterator.instanceCount : 0; | |
| 25720 } | |
| 25721 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 24353 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 25722 // for details. All rights reserved. Use of this source code is governed by a | 24354 // for details. All rights reserved. Use of this source code is governed by a |
| 25723 // BSD-style license that can be found in the LICENSE file. | 24355 // BSD-style license that can be found in the LICENSE file. |
| 25724 | 24356 |
| 25725 | 24357 |
| 25726 class _HttpRequestUtils { | 24358 class _HttpRequestUtils { |
| 25727 | 24359 |
| 25728 // Helper for factory HttpRequest.get | 24360 // Helper for factory HttpRequest.get |
| 25729 static HttpRequest get(String url, | 24361 static HttpRequest get(String url, |
| 25730 onComplete(HttpRequest request), | 24362 onComplete(HttpRequest request), |
| (...skipping 763 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 26494 DateTime _convertNativeToDart_DateTime(date) { | 25126 DateTime _convertNativeToDart_DateTime(date) { |
| 26495 var millisSinceEpoch = JS('int', '#.getTime()', date); | 25127 var millisSinceEpoch = JS('int', '#.getTime()', date); |
| 26496 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); | 25128 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); |
| 26497 } | 25129 } |
| 26498 | 25130 |
| 26499 _convertDartToNative_DateTime(DateTime date) { | 25131 _convertDartToNative_DateTime(DateTime date) { |
| 26500 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); | 25132 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); |
| 26501 } | 25133 } |
| 26502 | 25134 |
| 26503 WindowBase _convertNativeToDart_Window(win) { | 25135 WindowBase _convertNativeToDart_Window(win) { |
| 26504 if (win == null) return null; | |
| 26505 return _DOMWindowCrossFrame._createSafe(win); | 25136 return _DOMWindowCrossFrame._createSafe(win); |
| 26506 } | 25137 } |
| 26507 | 25138 |
| 26508 EventTarget _convertNativeToDart_EventTarget(e) { | 25139 EventTarget _convertNativeToDart_EventTarget(e) { |
| 26509 if (e == null) { | 25140 if (e == null) { |
| 26510 return null; | 25141 return null; |
| 26511 } | 25142 } |
| 26512 // Assume it's a Window if it contains the setInterval property. It may be | 25143 // Assume it's a Window if it contains the setInterval property. It may be |
| 26513 // from a different frame - without a patched prototype - so we cannot | 25144 // from a different frame - without a patched prototype - so we cannot |
| 26514 // rely on Dart type checking. | 25145 // rely on Dart type checking. |
| (...skipping 424 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 26939 _position = nextPosition; | 25570 _position = nextPosition; |
| 26940 return true; | 25571 return true; |
| 26941 } | 25572 } |
| 26942 _current = null; | 25573 _current = null; |
| 26943 _position = _array.length; | 25574 _position = _array.length; |
| 26944 return false; | 25575 return false; |
| 26945 } | 25576 } |
| 26946 | 25577 |
| 26947 T get current => _current; | 25578 T get current => _current; |
| 26948 } | 25579 } |
| OLD | NEW |