| 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:nativewrappers'; | 12 import 'dart:nativewrappers'; |
| 13 import 'dart:mdv_observe_impl'; | |
| 14 import 'dart:typed_data'; | 13 import 'dart:typed_data'; |
| 15 import 'dart:web_gl' as gl; | 14 import 'dart:web_gl' as gl; |
| 16 import 'dart:web_sql'; | 15 import 'dart:web_sql'; |
| 17 import 'dart:svg' as svg; | 16 import 'dart:svg' as svg; |
| 18 import 'dart:web_audio' as web_audio; | 17 import 'dart:web_audio' as web_audio; |
| 19 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 18 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 20 // for details. All rights reserved. Use of this source code is governed by a | 19 // for details. All rights reserved. Use of this source code is governed by a |
| 21 // BSD-style license that can be found in the LICENSE file. | 20 // BSD-style license that can be found in the LICENSE file. |
| 22 | 21 |
| 23 // DO NOT EDIT | 22 // DO NOT EDIT |
| (...skipping 7013 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 7037 | 7036 |
| 7038 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or | 7037 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or |
| 7039 // SVG strings? | 7038 // SVG strings? |
| 7040 void set innerHtml(String value) { | 7039 void set innerHtml(String value) { |
| 7041 this.nodes.clear(); | 7040 this.nodes.clear(); |
| 7042 | 7041 |
| 7043 final e = new Element.tag("div"); | 7042 final e = new Element.tag("div"); |
| 7044 e.innerHtml = value; | 7043 e.innerHtml = value; |
| 7045 | 7044 |
| 7046 // Copy list first since we don't want liveness during iteration. | 7045 // Copy list first since we don't want liveness during iteration. |
| 7047 List nodes = new List.from(e.nodes, growable: false); | 7046 List nodes = new List.from(e.nodes); |
| 7048 this.append(nodes); | 7047 this.nodes.addAll(nodes); |
| 7049 } | 7048 } |
| 7050 | 7049 |
| 7051 /** | 7050 /** |
| 7052 * Adds the specified text as a text node after the last child of this | 7051 * Adds the specified text as a text node after the last child of this |
| 7053 * document fragment. | 7052 * document fragment. |
| 7054 */ | 7053 */ |
| 7055 void appendText(String text) { | 7054 void appendText(String text) { |
| 7056 this.append(new Text(text)); | 7055 this.append(new Text(text)); |
| 7057 } | 7056 } |
| 7058 | 7057 |
| (...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 7455 if (result == null) throw new StateError("No elements"); | 7454 if (result == null) throw new StateError("No elements"); |
| 7456 return result; | 7455 return result; |
| 7457 } | 7456 } |
| 7458 | 7457 |
| 7459 Element get single { | 7458 Element get single { |
| 7460 if (length > 1) throw new StateError("More than one element"); | 7459 if (length > 1) throw new StateError("More than one element"); |
| 7461 return first; | 7460 return first; |
| 7462 } | 7461 } |
| 7463 } | 7462 } |
| 7464 | 7463 |
| 7465 /** | 7464 /** |
| 7466 * An immutable list containing HTML elements. This list contains some | 7465 * An immutable list containing HTML elements. This list contains some |
| 7467 * additional methods for ease of CSS manipulation on a group of elements. | 7466 * additional methods for ease of CSS manipulation on a group of elements. |
| 7468 */ | 7467 */ |
| 7469 abstract class ElementList<T extends Element> extends ListBase<T> { | 7468 abstract class ElementList<T extends Element> extends ListBase<T> { |
| 7470 /** | 7469 /** |
| 7471 * The union of all CSS classes applied to the elements in this list. | 7470 * The union of all CSS classes applied to the elements in this list. |
| 7472 * | 7471 * |
| 7473 * This set makes it easy to add, remove or toggle (add if not present, remove | 7472 * This set makes it easy to add, remove or toggle (add if not present, remove |
| 7474 * if present) the classes applied to a collection of elements. | 7473 * if present) the classes applied to a collection of elements. |
| 7475 * | 7474 * |
| (...skipping 314 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 7790 this.$dom_scrollIntoViewIfNeeded(true); | 7789 this.$dom_scrollIntoViewIfNeeded(true); |
| 7791 } else { | 7790 } else { |
| 7792 this.$dom_scrollIntoViewIfNeeded(); | 7791 this.$dom_scrollIntoViewIfNeeded(); |
| 7793 } | 7792 } |
| 7794 } else { | 7793 } else { |
| 7795 this.$dom_scrollIntoView(); | 7794 this.$dom_scrollIntoView(); |
| 7796 } | 7795 } |
| 7797 } | 7796 } |
| 7798 | 7797 |
| 7799 | 7798 |
| 7800 @Creates('Null') | |
| 7801 Map<String, StreamSubscription> _attributeBindings; | |
| 7802 | |
| 7803 // TODO(jmesserly): I'm concerned about adding these to every element. | |
| 7804 // Conceptually all of these belong on TemplateElement. They are here to | |
| 7805 // support browsers that don't have <template> yet. | |
| 7806 // However even in the polyfill they're restricted to certain tags | |
| 7807 // (see [isTemplate]). So we can probably convert it to a (public) mixin, and | |
| 7808 // only mix it in to the elements that need it. | |
| 7809 var _model; | |
| 7810 | |
| 7811 _TemplateIterator _templateIterator; | |
| 7812 | |
| 7813 Element _templateInstanceRef; | |
| 7814 | |
| 7815 // Note: only used if `this is! TemplateElement` | |
| 7816 DocumentFragment _templateContent; | |
| 7817 | |
| 7818 bool _templateIsDecorated; | |
| 7819 | |
| 7820 // TODO(jmesserly): should path be optional, and default to empty path? | |
| 7821 // It is used that way in at least one path in JS TemplateElement tests | |
| 7822 // (see "BindImperative" test in original JS code). | |
| 7823 @Experimental | |
| 7824 void bind(String name, model, String path) { | |
| 7825 _bindElement(this, name, model, path); | |
| 7826 } | |
| 7827 | |
| 7828 // TODO(jmesserly): this is static to work around http://dartbug.com/10166 | |
| 7829 // Similar issue for unbind/unbindAll below. | |
| 7830 static void _bindElement(Element self, String name, model, String path) { | |
| 7831 if (self._bindTemplate(name, model, path)) return; | |
| 7832 | |
| 7833 if (self._attributeBindings == null) { | |
| 7834 self._attributeBindings = new Map<String, StreamSubscription>(); | |
| 7835 } | |
| 7836 | |
| 7837 self.attributes.remove(name); | |
| 7838 | |
| 7839 var changed; | |
| 7840 if (name.endsWith('?')) { | |
| 7841 name = name.substring(0, name.length - 1); | |
| 7842 | |
| 7843 changed = (value) { | |
| 7844 if (_templateBooleanConversion(value)) { | |
| 7845 self.attributes[name] = ''; | |
| 7846 } else { | |
| 7847 self.attributes.remove(name); | |
| 7848 } | |
| 7849 }; | |
| 7850 } else { | |
| 7851 changed = (value) { | |
| 7852 // TODO(jmesserly): escape value if needed to protect against XSS. | |
| 7853 // See https://github.com/toolkitchen/mdv/issues/58 | |
| 7854 self.attributes[name] = value == null ? '' : '$value'; | |
| 7855 }; | |
| 7856 } | |
| 7857 | |
| 7858 self.unbind(name); | |
| 7859 | |
| 7860 self._attributeBindings[name] = | |
| 7861 new PathObserver(model, path).bindSync(changed); | |
| 7862 } | |
| 7863 | |
| 7864 @Experimental | |
| 7865 void unbind(String name) { | |
| 7866 _unbindElement(this, name); | |
| 7867 } | |
| 7868 | |
| 7869 static _unbindElement(Element self, String name) { | |
| 7870 if (self._unbindTemplate(name)) return; | |
| 7871 if (self._attributeBindings != null) { | |
| 7872 var binding = self._attributeBindings.remove(name); | |
| 7873 if (binding != null) binding.cancel(); | |
| 7874 } | |
| 7875 } | |
| 7876 | |
| 7877 @Experimental | |
| 7878 void unbindAll() { | |
| 7879 _unbindAllElement(this); | |
| 7880 } | |
| 7881 | |
| 7882 static void _unbindAllElement(Element self) { | |
| 7883 self._unbindAllTemplate(); | |
| 7884 | |
| 7885 if (self._attributeBindings != null) { | |
| 7886 for (var binding in self._attributeBindings.values) { | |
| 7887 binding.cancel(); | |
| 7888 } | |
| 7889 self._attributeBindings = null; | |
| 7890 } | |
| 7891 } | |
| 7892 | |
| 7893 // TODO(jmesserly): unlike the JS polyfill, we can't mixin | |
| 7894 // HTMLTemplateElement at runtime into things that are semantically template | |
| 7895 // elements. So instead we implement it here with a runtime check. | |
| 7896 // If the bind succeeds, we return true, otherwise we return false and let | |
| 7897 // the normal Element.bind logic kick in. | |
| 7898 bool _bindTemplate(String name, model, String path) { | |
| 7899 if (isTemplate) { | |
| 7900 switch (name) { | |
| 7901 case 'bind': | |
| 7902 case 'repeat': | |
| 7903 case 'if': | |
| 7904 _ensureTemplate(); | |
| 7905 if (_templateIterator == null) { | |
| 7906 _templateIterator = new _TemplateIterator(this); | |
| 7907 } | |
| 7908 _templateIterator.inputs.bind(name, model, path); | |
| 7909 return true; | |
| 7910 } | |
| 7911 } | |
| 7912 return false; | |
| 7913 } | |
| 7914 | |
| 7915 bool _unbindTemplate(String name) { | |
| 7916 if (isTemplate) { | |
| 7917 switch (name) { | |
| 7918 case 'bind': | |
| 7919 case 'repeat': | |
| 7920 case 'if': | |
| 7921 _ensureTemplate(); | |
| 7922 if (_templateIterator != null) { | |
| 7923 _templateIterator.inputs.unbind(name); | |
| 7924 } | |
| 7925 return true; | |
| 7926 } | |
| 7927 } | |
| 7928 return false; | |
| 7929 } | |
| 7930 | |
| 7931 void _unbindAllTemplate() { | |
| 7932 if (isTemplate) { | |
| 7933 unbind('bind'); | |
| 7934 unbind('repeat'); | |
| 7935 unbind('if'); | |
| 7936 } | |
| 7937 } | |
| 7938 | |
| 7939 /** | |
| 7940 * Gets the template this node refers to. | |
| 7941 * This is only supported if [isTemplate] is true. | |
| 7942 */ | |
| 7943 @Experimental | |
| 7944 Element get ref { | |
| 7945 _ensureTemplate(); | |
| 7946 | |
| 7947 Element ref = null; | |
| 7948 var refId = attributes['ref']; | |
| 7949 if (refId != null) { | |
| 7950 ref = document.getElementById(refId); | |
| 7951 } | |
| 7952 | |
| 7953 return ref != null ? ref : _templateInstanceRef; | |
| 7954 } | |
| 7955 | |
| 7956 /** | |
| 7957 * Gets the content of this template. | |
| 7958 * This is only supported if [isTemplate] is true. | |
| 7959 */ | |
| 7960 @Experimental | |
| 7961 DocumentFragment get content { | |
| 7962 _ensureTemplate(); | |
| 7963 return _templateContent; | |
| 7964 } | |
| 7965 | |
| 7966 /** | |
| 7967 * Creates an instance of the template. | |
| 7968 * This is only supported if [isTemplate] is true. | |
| 7969 */ | |
| 7970 @Experimental | |
| 7971 DocumentFragment createInstance() { | |
| 7972 _ensureTemplate(); | |
| 7973 | |
| 7974 var template = ref; | |
| 7975 if (template == null) template = this; | |
| 7976 | |
| 7977 var instance = _createDeepCloneAndDecorateTemplates(template.content, | |
| 7978 attributes['syntax']); | |
| 7979 | |
| 7980 if (TemplateElement._instanceCreated != null) { | |
| 7981 TemplateElement._instanceCreated.add(instance); | |
| 7982 } | |
| 7983 return instance; | |
| 7984 } | |
| 7985 | |
| 7986 /** | |
| 7987 * The data model which is inherited through the tree. | |
| 7988 * This is only supported if [isTemplate] is true. | |
| 7989 * | |
| 7990 * Setting this will destructive propagate the value to all descendant nodes, | |
| 7991 * and reinstantiate all of the nodes expanded by this template. | |
| 7992 * | |
| 7993 * Currently this does not support propagation through Shadow DOMs. | |
| 7994 */ | |
| 7995 @Experimental | |
| 7996 get model => _model; | |
| 7997 | |
| 7998 @Experimental | |
| 7999 void set model(value) { | |
| 8000 _ensureTemplate(); | |
| 8001 | |
| 8002 _model = value; | |
| 8003 _addBindings(this, model); | |
| 8004 } | |
| 8005 | |
| 8006 // TODO(jmesserly): const set would be better | |
| 8007 static const _TABLE_TAGS = const { | |
| 8008 'caption': null, | |
| 8009 'col': null, | |
| 8010 'colgroup': null, | |
| 8011 'tbody': null, | |
| 8012 'td': null, | |
| 8013 'tfoot': null, | |
| 8014 'th': null, | |
| 8015 'thead': null, | |
| 8016 'tr': null, | |
| 8017 }; | |
| 8018 | |
| 8019 bool get _isAttributeTemplate => attributes.containsKey('template') && | |
| 8020 (localName == 'option' || _TABLE_TAGS.containsKey(localName)); | |
| 8021 | |
| 8022 /** | |
| 8023 * Returns true if this node is a template. | |
| 8024 * | |
| 8025 * A node is a template if [tagName] is TEMPLATE, or the node has the | |
| 8026 * 'template' attribute and this tag supports attribute form for backwards | |
| 8027 * compatibility with existing HTML parsers. The nodes that can use attribute | |
| 8028 * form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP | |
| 8029 * and COL) and OPTION. | |
| 8030 */ | |
| 8031 // TODO(jmesserly): this is not a public MDV API, but it seems like a useful | |
| 8032 // place to document which tags our polyfill considers to be templates. | |
| 8033 // Otherwise I'd be repeating it in several other places. | |
| 8034 // See if we can replace this with a TemplateMixin. | |
| 8035 @Experimental | |
| 8036 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate; | |
| 8037 | |
| 8038 void _ensureTemplate() { | |
| 8039 if (!isTemplate) { | |
| 8040 throw new UnsupportedError('$this is not a template.'); | |
| 8041 } | |
| 8042 TemplateElement.decorate(this); | |
| 8043 } | |
| 8044 | |
| 8045 Element.internal() : super.internal(); | 7799 Element.internal() : super.internal(); |
| 8046 | 7800 |
| 8047 @DomName('Element.abortEvent') | 7801 @DomName('Element.abortEvent') |
| 8048 @DocsEditable | 7802 @DocsEditable |
| 8049 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider
<Event>('abort'); | 7803 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider
<Event>('abort'); |
| 8050 | 7804 |
| 8051 @DomName('Element.beforecopyEvent') | 7805 @DomName('Element.beforecopyEvent') |
| 8052 @DocsEditable | 7806 @DocsEditable |
| 8053 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro
vider<Event>('beforecopy'); | 7807 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro
vider<Event>('beforecopy'); |
| 8054 | 7808 |
| (...skipping 714 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 8769 @DomName('Element.onwebkitfullscreenchange') | 8523 @DomName('Element.onwebkitfullscreenchange') |
| 8770 @DocsEditable | 8524 @DocsEditable |
| 8771 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); | 8525 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); |
| 8772 | 8526 |
| 8773 @DomName('Element.onwebkitfullscreenerror') | 8527 @DomName('Element.onwebkitfullscreenerror') |
| 8774 @DocsEditable | 8528 @DocsEditable |
| 8775 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); | 8529 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); |
| 8776 | 8530 |
| 8777 } | 8531 } |
| 8778 | 8532 |
| 8779 | |
| 8780 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); | 8533 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); |
| 8781 class _ElementFactoryProvider { | 8534 class _ElementFactoryProvider { |
| 8782 static const _CUSTOM_PARENT_TAG_MAP = const { | 8535 static const _CUSTOM_PARENT_TAG_MAP = const { |
| 8783 'body' : 'html', | 8536 'body' : 'html', |
| 8784 'head' : 'html', | 8537 'head' : 'html', |
| 8785 'caption' : 'table', | 8538 'caption' : 'table', |
| 8786 'td': 'tr', | 8539 'td': 'tr', |
| 8787 'th': 'tr', | 8540 'th': 'tr', |
| 8788 'colgroup': 'table', | 8541 'colgroup': 'table', |
| 8789 'col' : 'colgroup', | 8542 'col' : 'colgroup', |
| 8790 'tr' : 'tbody', | 8543 'tr' : 'tbody', |
| 8791 'tbody' : 'table', | 8544 'tbody' : 'table', |
| 8792 'tfoot' : 'table', | 8545 'tfoot' : 'table', |
| 8793 'thead' : 'table', | 8546 'thead' : 'table', |
| 8794 'track' : 'audio', | 8547 'track' : 'audio', |
| 8795 }; | 8548 }; |
| 8796 | 8549 |
| 8550 // TODO(jmesserly): const set would be better |
| 8551 static const _TABLE_TAGS = const { |
| 8552 'caption': null, |
| 8553 'col': null, |
| 8554 'colgroup': null, |
| 8555 'tbody': null, |
| 8556 'td': null, |
| 8557 'tfoot': null, |
| 8558 'th': null, |
| 8559 'thead': null, |
| 8560 'tr': null, |
| 8561 }; |
| 8562 |
| 8797 @DomName('Document.createElement') | 8563 @DomName('Document.createElement') |
| 8798 static Element createElement_html(String html) { | 8564 static Element createElement_html(String html) { |
| 8799 // TODO(jacobr): this method can be made more robust and performant. | 8565 // TODO(jacobr): this method can be made more robust and performant. |
| 8800 // 1) Cache the dummy parent elements required to use innerHTML rather than | 8566 // 1) Cache the dummy parent elements required to use innerHTML rather than |
| 8801 // creating them every call. | 8567 // creating them every call. |
| 8802 // 2) Verify that the html does not contain leading or trailing text nodes. | 8568 // 2) Verify that the html does not contain leading or trailing text nodes. |
| 8803 // 3) Verify that the html does not contain both <head> and <body> tags. | 8569 // 3) Verify that the html does not contain both <head> and <body> tags. |
| 8804 // 4) Detatch the created element from its dummy parent. | 8570 // 4) Detatch the created element from its dummy parent. |
| 8805 String parentTag = 'div'; | 8571 String parentTag = 'div'; |
| 8806 String tag; | 8572 String tag; |
| 8807 final match = _START_TAG_REGEXP.firstMatch(html); | 8573 final match = _START_TAG_REGEXP.firstMatch(html); |
| 8808 if (match != null) { | 8574 if (match != null) { |
| 8809 tag = match.group(1).toLowerCase(); | 8575 tag = match.group(1).toLowerCase(); |
| 8810 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { | 8576 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { |
| 8811 return _createTableForIE(html, tag); | 8577 return _createTableForIE(html, tag); |
| 8812 } | 8578 } |
| 8813 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; | 8579 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; |
| 8814 if (parentTag == null) parentTag = 'div'; | 8580 if (parentTag == null) parentTag = 'div'; |
| 8815 } | 8581 } |
| 8816 | 8582 |
| 8817 final temp = new Element.tag(parentTag); | 8583 final temp = new Element.tag(parentTag); |
| 8818 temp.innerHtml = html; | 8584 temp.innerHtml = html; |
| 8819 | 8585 |
| 8820 Element element; | 8586 Element element; |
| (...skipping 2015 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 10836 @SupportedBrowser(SupportedBrowser.SAFARI) | 10602 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 10837 @Experimental | 10603 @Experimental |
| 10838 Element get pointerLockElement => | 10604 Element get pointerLockElement => |
| 10839 $dom_webkitPointerLockElement; | 10605 $dom_webkitPointerLockElement; |
| 10840 | 10606 |
| 10841 @DomName('Document.webkitVisibilityState') | 10607 @DomName('Document.webkitVisibilityState') |
| 10842 @SupportedBrowser(SupportedBrowser.CHROME) | 10608 @SupportedBrowser(SupportedBrowser.CHROME) |
| 10843 @SupportedBrowser(SupportedBrowser.SAFARI) | 10609 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 10844 @Experimental | 10610 @Experimental |
| 10845 String get visibilityState => $dom_webkitVisibilityState; | 10611 String get visibilityState => $dom_webkitVisibilityState; |
| 10846 | |
| 10847 | |
| 10848 // Note: used to polyfill <template> | |
| 10849 Document _templateContentsOwner; | |
| 10850 } | 10612 } |
| 10851 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 10613 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 10852 // for details. All rights reserved. Use of this source code is governed by a | 10614 // for details. All rights reserved. Use of this source code is governed by a |
| 10853 // BSD-style license that can be found in the LICENSE file. | 10615 // BSD-style license that can be found in the LICENSE file. |
| 10854 | 10616 |
| 10855 // WARNING: Do not edit - generated code. | 10617 // WARNING: Do not edit - generated code. |
| 10856 | 10618 |
| 10857 | 10619 |
| 10858 @DocsEditable | 10620 @DocsEditable |
| 10859 @DomName('HTMLHtmlElement') | 10621 @DomName('HTMLHtmlElement') |
| (...skipping 920 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11780 factory InputElement({String type}) { | 11542 factory InputElement({String type}) { |
| 11781 var e = document.$dom_createElement("input"); | 11543 var e = document.$dom_createElement("input"); |
| 11782 if (type != null) { | 11544 if (type != null) { |
| 11783 try { | 11545 try { |
| 11784 // IE throws an exception for unknown types. | 11546 // IE throws an exception for unknown types. |
| 11785 e.type = type; | 11547 e.type = type; |
| 11786 } catch(_) {} | 11548 } catch(_) {} |
| 11787 } | 11549 } |
| 11788 return e; | 11550 return e; |
| 11789 } | 11551 } |
| 11790 | |
| 11791 _ValueBinding _valueBinding; | |
| 11792 | |
| 11793 _CheckedBinding _checkedBinding; | |
| 11794 | |
| 11795 @Experimental | |
| 11796 void bind(String name, model, String path) { | |
| 11797 switch (name) { | |
| 11798 case 'value': | |
| 11799 unbind('value'); | |
| 11800 attributes.remove('value'); | |
| 11801 _valueBinding = new _ValueBinding(this, model, path); | |
| 11802 break; | |
| 11803 case 'checked': | |
| 11804 unbind('checked'); | |
| 11805 attributes.remove('checked'); | |
| 11806 _checkedBinding = new _CheckedBinding(this, model, path); | |
| 11807 break; | |
| 11808 default: | |
| 11809 // TODO(jmesserly): this should be "super" (http://dartbug.com/10166). | |
| 11810 // Similar issue for unbind/unbindAll below. | |
| 11811 Element._bindElement(this, name, model, path); | |
| 11812 break; | |
| 11813 } | |
| 11814 } | |
| 11815 | |
| 11816 @Experimental | |
| 11817 void unbind(String name) { | |
| 11818 switch (name) { | |
| 11819 case 'value': | |
| 11820 if (_valueBinding != null) { | |
| 11821 _valueBinding.unbind(); | |
| 11822 _valueBinding = null; | |
| 11823 } | |
| 11824 break; | |
| 11825 case 'checked': | |
| 11826 if (_checkedBinding != null) { | |
| 11827 _checkedBinding.unbind(); | |
| 11828 _checkedBinding = null; | |
| 11829 } | |
| 11830 break; | |
| 11831 default: | |
| 11832 Element._unbindElement(this, name); | |
| 11833 break; | |
| 11834 } | |
| 11835 } | |
| 11836 | |
| 11837 @Experimental | |
| 11838 void unbindAll() { | |
| 11839 unbind('value'); | |
| 11840 unbind('checked'); | |
| 11841 Element._unbindAllElement(this); | |
| 11842 } | |
| 11843 | |
| 11844 InputElement.internal() : super.internal(); | 11552 InputElement.internal() : super.internal(); |
| 11845 | 11553 |
| 11846 @DomName('HTMLInputElement.webkitSpeechChangeEvent') | 11554 @DomName('HTMLInputElement.webkitSpeechChangeEvent') |
| 11847 @DocsEditable | 11555 @DocsEditable |
| 11848 @SupportedBrowser(SupportedBrowser.CHROME) | 11556 @SupportedBrowser(SupportedBrowser.CHROME) |
| 11849 @SupportedBrowser(SupportedBrowser.SAFARI) | 11557 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 11850 @Experimental | 11558 @Experimental |
| 11851 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); | 11559 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); |
| 11852 | 11560 |
| 11853 @DomName('HTMLInputElement.accept') | 11561 @DomName('HTMLInputElement.accept') |
| (...skipping 3710 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 15564 _this.$dom_removeChild(node); | 15272 _this.$dom_removeChild(node); |
| 15565 return true; | 15273 return true; |
| 15566 } | 15274 } |
| 15567 | 15275 |
| 15568 void _filter(bool test(Node node), bool removeMatching) { | 15276 void _filter(bool test(Node node), bool removeMatching) { |
| 15569 // This implementation of removeWhere/retainWhere is more efficient | 15277 // This implementation of removeWhere/retainWhere is more efficient |
| 15570 // than the default in ListBase. Child nodes can be removed in constant | 15278 // than the default in ListBase. Child nodes can be removed in constant |
| 15571 // time. | 15279 // time. |
| 15572 Node child = _this.$dom_firstChild; | 15280 Node child = _this.$dom_firstChild; |
| 15573 while (child != null) { | 15281 while (child != null) { |
| 15574 Node nextChild = child.nextNode; | 15282 Node nextChild = child.nextSibling; |
| 15575 if (test(child) == removeMatching) { | 15283 if (test(child) == removeMatching) { |
| 15576 _this.$dom_removeChild(child); | 15284 _this.$dom_removeChild(child); |
| 15577 } | 15285 } |
| 15578 child = nextChild; | 15286 child = nextChild; |
| 15579 } | 15287 } |
| 15580 } | 15288 } |
| 15581 | 15289 |
| 15582 void removeWhere(bool test(Node node)) { | 15290 void removeWhere(bool test(Node node)) { |
| 15583 _filter(test, true); | 15291 _filter(test, true); |
| 15584 } | 15292 } |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 15689 // Should use $dom_firstChild, Bug 8886. | 15397 // Should use $dom_firstChild, Bug 8886. |
| 15690 this.insertBefore(newNodes[0], refChild); | 15398 this.insertBefore(newNodes[0], refChild); |
| 15691 } | 15399 } |
| 15692 } else { | 15400 } else { |
| 15693 for (var node in newNodes) { | 15401 for (var node in newNodes) { |
| 15694 this.insertBefore(node, refChild); | 15402 this.insertBefore(node, refChild); |
| 15695 } | 15403 } |
| 15696 } | 15404 } |
| 15697 } | 15405 } |
| 15698 | 15406 |
| 15407 // Note that this may either be the locally set model or a cached value |
| 15408 // of the inherited model. This is cached to minimize model change |
| 15409 // notifications. |
| 15410 var _model; |
| 15411 bool _hasLocalModel; |
| 15412 Set<StreamController<Node>> _modelChangedStreams; |
| 15413 |
| 15414 /** |
| 15415 * The data model which is inherited through the tree. |
| 15416 * |
| 15417 * Setting this will propagate the value to all descendant nodes. If the |
| 15418 * model is not set on this node then it will be inherited from ancestor |
| 15419 * nodes. |
| 15420 * |
| 15421 * Currently this does not support propagation through Shadow DOMs. |
| 15422 * |
| 15423 * [clearModel] must be used to remove the model property from this node |
| 15424 * and have the model inherit from ancestor nodes. |
| 15425 */ |
| 15426 @Experimental |
| 15427 get model { |
| 15428 // If we have a change handler then we've cached the model locally. |
| 15429 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) { |
| 15430 return _model; |
| 15431 } |
| 15432 // Otherwise start looking up the tree. |
| 15433 for (var node = this; node != null; node = node.parentNode) { |
| 15434 if (node._hasLocalModel == true) { |
| 15435 return node._model; |
| 15436 } |
| 15437 } |
| 15438 return null; |
| 15439 } |
| 15440 |
| 15441 @Experimental |
| 15442 void set model(value) { |
| 15443 var changed = model != value; |
| 15444 _model = value; |
| 15445 _hasLocalModel = true; |
| 15446 _ModelTreeObserver.initialize(); |
| 15447 |
| 15448 if (changed) { |
| 15449 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) { |
| 15450 _modelChangedStreams.toList().forEach((stream) => stream.add(this)); |
| 15451 } |
| 15452 // Propagate new model to all descendants. |
| 15453 _ModelTreeObserver.propagateModel(this, value, false); |
| 15454 } |
| 15455 } |
| 15456 |
| 15457 /** |
| 15458 * Clears the locally set model and makes this model be inherited from parent |
| 15459 * nodes. |
| 15460 */ |
| 15461 @Experimental |
| 15462 void clearModel() { |
| 15463 if (_hasLocalModel == true) { |
| 15464 _hasLocalModel = false; |
| 15465 |
| 15466 // Propagate new model to all descendants. |
| 15467 if (parentNode != null) { |
| 15468 _ModelTreeObserver.propagateModel(this, parentNode.model, false); |
| 15469 } else { |
| 15470 _ModelTreeObserver.propagateModel(this, null, false); |
| 15471 } |
| 15472 } |
| 15473 } |
| 15474 |
| 15475 /** |
| 15476 * Get a stream of models, whenever the model changes. |
| 15477 */ |
| 15478 Stream<Node> get onModelChanged { |
| 15479 if (_modelChangedStreams == null) { |
| 15480 _modelChangedStreams = new Set<StreamController<Node>>(); |
| 15481 } |
| 15482 var controller; |
| 15483 controller = new StreamController( |
| 15484 onListen: () { _modelChangedStreams.add(controller); }, |
| 15485 onCancel: () { _modelChangedStreams.remove(controller); }); |
| 15486 return controller.stream; |
| 15487 } |
| 15488 |
| 15699 /** | 15489 /** |
| 15700 * Print out a String representation of this Node. | 15490 * Print out a String representation of this Node. |
| 15701 */ | 15491 */ |
| 15702 String toString() => localName == null ? | 15492 String toString() => localName == null ? |
| 15703 (nodeValue == null ? super.toString() : nodeValue) : localName; | 15493 (nodeValue == null ? super.toString() : nodeValue) : localName; |
| 15704 | 15494 |
| 15705 /** | |
| 15706 * Binds the attribute [name] to the [path] of the [model]. | |
| 15707 * Path is a String of accessors such as `foo.bar.baz`. | |
| 15708 */ | |
| 15709 @Experimental | |
| 15710 void bind(String name, model, String path) { | |
| 15711 // TODO(jmesserly): should we throw instead? | |
| 15712 window.console.error('Unhandled binding to Node: ' | |
| 15713 '$this $name $model $path'); | |
| 15714 } | |
| 15715 | |
| 15716 /** Unbinds the attribute [name]. */ | |
| 15717 @Experimental | |
| 15718 void unbind(String name) {} | |
| 15719 | |
| 15720 /** Unbinds all bound attributes. */ | |
| 15721 @Experimental | |
| 15722 void unbindAll() {} | |
| 15723 | |
| 15724 TemplateInstance _templateInstance; | |
| 15725 | |
| 15726 // TODO(arv): Consider storing all "NodeRareData" on a single object? | |
| 15727 int __instanceTerminatorCount; | |
| 15728 int get _instanceTerminatorCount { | |
| 15729 if (__instanceTerminatorCount == null) return 0; | |
| 15730 return __instanceTerminatorCount; | |
| 15731 } | |
| 15732 set _instanceTerminatorCount(int value) { | |
| 15733 if (value == 0) value = null; | |
| 15734 __instanceTerminatorCount = value; | |
| 15735 } | |
| 15736 | |
| 15737 /** Gets the template instance that instantiated this node, if any. */ | |
| 15738 @Experimental | |
| 15739 TemplateInstance get templateInstance => | |
| 15740 _templateInstance != null ? _templateInstance : | |
| 15741 (parent != null ? parent.templateInstance : null); | |
| 15742 | |
| 15743 Node.internal() : super.internal(); | 15495 Node.internal() : super.internal(); |
| 15744 | 15496 |
| 15745 static const int ATTRIBUTE_NODE = 2; | 15497 static const int ATTRIBUTE_NODE = 2; |
| 15746 | 15498 |
| 15747 static const int CDATA_SECTION_NODE = 4; | 15499 static const int CDATA_SECTION_NODE = 4; |
| 15748 | 15500 |
| 15749 static const int COMMENT_NODE = 8; | 15501 static const int COMMENT_NODE = 8; |
| 15750 | 15502 |
| 15751 static const int DOCUMENT_FRAGMENT_NODE = 11; | 15503 static const int DOCUMENT_FRAGMENT_NODE = 11; |
| 15752 | 15504 |
| (...skipping 4308 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 20061 @DocsEditable | 19813 @DocsEditable |
| 20062 Element $dom_insertRow(int index) native "HTMLTableSectionElement_insertRow_Ca
llback"; | 19814 Element $dom_insertRow(int index) native "HTMLTableSectionElement_insertRow_Ca
llback"; |
| 20063 } | 19815 } |
| 20064 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 19816 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 20065 // for details. All rights reserved. Use of this source code is governed by a | 19817 // for details. All rights reserved. Use of this source code is governed by a |
| 20066 // BSD-style license that can be found in the LICENSE file. | 19818 // BSD-style license that can be found in the LICENSE file. |
| 20067 | 19819 |
| 20068 // WARNING: Do not edit - generated code. | 19820 // WARNING: Do not edit - generated code. |
| 20069 | 19821 |
| 20070 | 19822 |
| 20071 @Experimental | 19823 @DocsEditable |
| 20072 @DomName('HTMLTemplateElement') | 19824 @DomName('HTMLTemplateElement') |
| 20073 @SupportedBrowser(SupportedBrowser.CHROME) | 19825 @SupportedBrowser(SupportedBrowser.CHROME) |
| 20074 @Experimental | 19826 @Experimental |
| 20075 class TemplateElement extends _Element_Merged { | 19827 class TemplateElement extends _Element_Merged { |
| 20076 TemplateElement.internal() : super.internal(); | 19828 TemplateElement.internal() : super.internal(); |
| 20077 | 19829 |
| 20078 @DomName('HTMLTemplateElement.HTMLTemplateElement') | 19830 @DomName('HTMLTemplateElement.HTMLTemplateElement') |
| 20079 @DocsEditable | 19831 @DocsEditable |
| 20080 factory TemplateElement() => document.$dom_createElement("template"); | 19832 factory TemplateElement() => document.$dom_createElement("template"); |
| 20081 | 19833 |
| 20082 /// Checks if this type is supported on the current platform. | 19834 /// Checks if this type is supported on the current platform. |
| 20083 static bool get supported => true; | 19835 static bool get supported => true; |
| 20084 | 19836 |
| 20085 @DomName('HTMLTemplateElement.content') | 19837 @DomName('HTMLTemplateElement.content') |
| 20086 @DocsEditable | 19838 @DocsEditable |
| 20087 DocumentFragment get $dom_content native "HTMLTemplateElement_content_Getter"; | 19839 DocumentFragment get content native "HTMLTemplateElement_content_Getter"; |
| 20088 | 19840 |
| 20089 | |
| 20090 // For real TemplateElement use the actual DOM .content field instead of | |
| 20091 // our polyfilled expando. | |
| 20092 @Experimental | |
| 20093 DocumentFragment get content => $dom_content; | |
| 20094 | |
| 20095 static StreamController<DocumentFragment> _instanceCreated; | |
| 20096 | |
| 20097 /** | |
| 20098 * *Warning*: This is an implementation helper for Model-Driven Views and | |
| 20099 * should not be used in your code. | |
| 20100 * | |
| 20101 * This event is fired whenever a template is instantiated via | |
| 20102 * [createInstance]. | |
| 20103 */ | |
| 20104 // TODO(rafaelw): This is a hack, and is neccesary for the polyfill | |
| 20105 // because custom elements are not upgraded during clone() | |
| 20106 @Experimental | |
| 20107 static Stream<DocumentFragment> get instanceCreated { | |
| 20108 if (_instanceCreated == null) { | |
| 20109 _instanceCreated = new StreamController<DocumentFragment>(); | |
| 20110 } | |
| 20111 return _instanceCreated.stream; | |
| 20112 } | |
| 20113 | |
| 20114 /** | |
| 20115 * Ensures proper API and content model for template elements. | |
| 20116 * | |
| 20117 * [instanceRef] can be used to set the [Element.ref] property of [template], | |
| 20118 * and use the ref's content will be used as source when createInstance() is | |
| 20119 * invoked. | |
| 20120 * | |
| 20121 * Returns true if this template was just decorated, or false if it was | |
| 20122 * already decorated. | |
| 20123 */ | |
| 20124 @Experimental | |
| 20125 static bool decorate(Element template, [Element instanceRef]) { | |
| 20126 // == true check because it starts as a null field. | |
| 20127 if (template._templateIsDecorated == true) return false; | |
| 20128 | |
| 20129 template._templateIsDecorated = true; | |
| 20130 | |
| 20131 _injectStylesheet(); | |
| 20132 | |
| 20133 // Create content | |
| 20134 if (template is! TemplateElement) { | |
| 20135 var doc = _getTemplateContentsOwner(template.document); | |
| 20136 template._templateContent = doc.createDocumentFragment(); | |
| 20137 } | |
| 20138 | |
| 20139 if (instanceRef != null) { | |
| 20140 template._templateInstanceRef = instanceRef; | |
| 20141 return true; // content is empty. | |
| 20142 } | |
| 20143 | |
| 20144 if (template is TemplateElement) { | |
| 20145 _bootstrapTemplatesRecursivelyFrom(template.content); | |
| 20146 } else { | |
| 20147 _liftNonNativeTemplateChildrenIntoContent(template); | |
| 20148 } | |
| 20149 | |
| 20150 return true; | |
| 20151 } | |
| 20152 | |
| 20153 /** | |
| 20154 * This used to decorate recursively all templates from a given node. | |
| 20155 * | |
| 20156 * By default [decorate] will be called on templates lazily when certain | |
| 20157 * properties such as [model] are accessed, but it can be run eagerly to | |
| 20158 * decorate an entire tree recursively. | |
| 20159 */ | |
| 20160 // TODO(rafaelw): Review whether this is the right public API. | |
| 20161 @Experimental | |
| 20162 static void bootstrap(Node content) { | |
| 20163 _bootstrapTemplatesRecursivelyFrom(content); | |
| 20164 } | |
| 20165 | |
| 20166 static bool _initStyles; | |
| 20167 | |
| 20168 static void _injectStylesheet() { | |
| 20169 if (_initStyles == true) return; | |
| 20170 _initStyles = true; | |
| 20171 | |
| 20172 var style = new StyleElement(); | |
| 20173 style.text = r''' | |
| 20174 template, | |
| 20175 thead[template], | |
| 20176 tbody[template], | |
| 20177 tfoot[template], | |
| 20178 th[template], | |
| 20179 tr[template], | |
| 20180 td[template], | |
| 20181 caption[template], | |
| 20182 colgroup[template], | |
| 20183 col[template], | |
| 20184 option[template] { | |
| 20185 display: none; | |
| 20186 }'''; | |
| 20187 document.head.append(style); | |
| 20188 } | |
| 20189 | |
| 20190 /** | |
| 20191 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for | |
| 20192 * more information. | |
| 20193 */ | |
| 20194 @Experimental | |
| 20195 static Map<String, CustomBindingSyntax> syntax = {}; | |
| 20196 } | 19841 } |
| 20197 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 19842 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 20198 // for details. All rights reserved. Use of this source code is governed by a | 19843 // for details. All rights reserved. Use of this source code is governed by a |
| 20199 // BSD-style license that can be found in the LICENSE file. | 19844 // BSD-style license that can be found in the LICENSE file. |
| 20200 | 19845 |
| 20201 // WARNING: Do not edit - generated code. | 19846 // WARNING: Do not edit - generated code. |
| 20202 | 19847 |
| 20203 | 19848 |
| 20204 @DomName('Text') | 19849 @DomName('Text') |
| 20205 class Text extends CharacterData { | 19850 class Text extends CharacterData { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 20218 String get wholeText native "Text_wholeText_Getter"; | 19863 String get wholeText native "Text_wholeText_Getter"; |
| 20219 | 19864 |
| 20220 @DomName('Text.replaceWholeText') | 19865 @DomName('Text.replaceWholeText') |
| 20221 @DocsEditable | 19866 @DocsEditable |
| 20222 Text replaceWholeText(String content) native "Text_replaceWholeText_Callback"; | 19867 Text replaceWholeText(String content) native "Text_replaceWholeText_Callback"; |
| 20223 | 19868 |
| 20224 @DomName('Text.splitText') | 19869 @DomName('Text.splitText') |
| 20225 @DocsEditable | 19870 @DocsEditable |
| 20226 Text splitText(int offset) native "Text_splitText_Callback"; | 19871 Text splitText(int offset) native "Text_splitText_Callback"; |
| 20227 | 19872 |
| 20228 | |
| 20229 StreamSubscription _textBinding; | |
| 20230 | |
| 20231 @Experimental | |
| 20232 void bind(String name, model, String path) { | |
| 20233 if (name != 'text') { | |
| 20234 super.bind(name, model, path); | |
| 20235 return; | |
| 20236 } | |
| 20237 | |
| 20238 unbind('text'); | |
| 20239 | |
| 20240 _textBinding = new PathObserver(model, path).bindSync((value) { | |
| 20241 text = value == null ? '' : '$value'; | |
| 20242 }); | |
| 20243 } | |
| 20244 | |
| 20245 @Experimental | |
| 20246 void unbind(String name) { | |
| 20247 if (name != 'text') { | |
| 20248 super.unbind(name); | |
| 20249 return; | |
| 20250 } | |
| 20251 | |
| 20252 if (_textBinding == null) return; | |
| 20253 | |
| 20254 _textBinding.cancel(); | |
| 20255 _textBinding = null; | |
| 20256 } | |
| 20257 | |
| 20258 @Experimental | |
| 20259 void unbindAll() { | |
| 20260 unbind('text'); | |
| 20261 super.unbindAll(); | |
| 20262 } | |
| 20263 } | 19873 } |
| 20264 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 19874 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 20265 // for details. All rights reserved. Use of this source code is governed by a | 19875 // for details. All rights reserved. Use of this source code is governed by a |
| 20266 // BSD-style license that can be found in the LICENSE file. | 19876 // BSD-style license that can be found in the LICENSE file. |
| 20267 | 19877 |
| 20268 // WARNING: Do not edit - generated code. | 19878 // WARNING: Do not edit - generated code. |
| 20269 | 19879 |
| 20270 | 19880 |
| 20271 @DocsEditable | 19881 @DocsEditable |
| 20272 @DomName('HTMLTextAreaElement') | 19882 @DomName('HTMLTextAreaElement') |
| (...skipping 5850 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 26123 * Key value used when an implementation is unable to identify another key | 25733 * Key value used when an implementation is unable to identify another key |
| 26124 * value, due to either hardware, platform, or software constraints | 25734 * value, due to either hardware, platform, or software constraints |
| 26125 */ | 25735 */ |
| 26126 static const String UNIDENTIFIED = "Unidentified"; | 25736 static const String UNIDENTIFIED = "Unidentified"; |
| 26127 } | 25737 } |
| 26128 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 25738 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 26129 // for details. All rights reserved. Use of this source code is governed by a | 25739 // for details. All rights reserved. Use of this source code is governed by a |
| 26130 // BSD-style license that can be found in the LICENSE file. | 25740 // BSD-style license that can be found in the LICENSE file. |
| 26131 | 25741 |
| 26132 | 25742 |
| 26133 // This code is inspired by ChangeSummary: | 25743 class _ModelTreeObserver { |
| 26134 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js | 25744 static bool _initialized = false; |
| 26135 // ...which underlies MDV. Since we don't need the functionality of | |
| 26136 // ChangeSummary, we just implement what we need for data bindings. | |
| 26137 // This allows our implementation to be much simpler. | |
| 26138 | |
| 26139 // TODO(jmesserly): should we make these types stronger, and require | |
| 26140 // Observable objects? Currently, it is fine to say something like: | |
| 26141 // var path = new PathObserver(123, ''); | |
| 26142 // print(path.value); // "123" | |
| 26143 // | |
| 26144 // Furthermore this degenerate case is allowed: | |
| 26145 // var path = new PathObserver(123, 'foo.bar.baz.qux'); | |
| 26146 // print(path.value); // "null" | |
| 26147 // | |
| 26148 // Here we see that any invalid (i.e. not Observable) value will break the | |
| 26149 // path chain without producing an error or exception. | |
| 26150 // | |
| 26151 // Now the real question: should we do this? For the former case, the behavior | |
| 26152 // is correct but we could chose to handle it in the dart:html bindings layer. | |
| 26153 // For the latter case, it might be better to throw an error so users can find | |
| 26154 // the problem. | |
| 26155 | |
| 26156 | |
| 26157 // TODO(jmesserly): the primary reason to have this object exposed is because | |
| 26158 // we have get/set for value. Ideally "observePath" could just return the | |
| 26159 // stream. | |
| 26160 /** | |
| 26161 * A data-bound path starting from a view-model or model object, for example | |
| 26162 * `foo.bar.baz`. | |
| 26163 * | |
| 26164 * When the [values] stream is being listened to, this will observe changes to | |
| 26165 * the object and any intermediate object along the path, and send [values] | |
| 26166 * accordingly. When all listeners are unregistered it will stop observing | |
| 26167 * the objects. | |
| 26168 * | |
| 26169 * This class is used to implement [Node.bind] and similar functionality. | |
| 26170 */ | |
| 26171 @Experimental | |
| 26172 class PathObserver { | |
| 26173 /** The object being observed. */ | |
| 26174 final object; | |
| 26175 | |
| 26176 /** The path string. */ | |
| 26177 final String path; | |
| 26178 | |
| 26179 /** True if the path is valid, otherwise false. */ | |
| 26180 final bool _isValid; | |
| 26181 | |
| 26182 // TODO(jmesserly): same issue here as ObservableMixin: is there an easier | |
| 26183 // way to get a broadcast stream? | |
| 26184 StreamController _values; | |
| 26185 Stream _valueStream; | |
| 26186 | |
| 26187 _PropertyObserver _observer, _lastObserver; | |
| 26188 | |
| 26189 Object _lastValue; | |
| 26190 bool _scheduled = false; | |
| 26191 | 25745 |
| 26192 /** | 25746 /** |
| 26193 * Observes [path] on [object] for changes. This returns an object that can be | 25747 * Start an observer watching the document for tree changes to automatically |
| 26194 * used to get the changes and get/set the value at this path. | 25748 * propagate model changes. |
| 26195 * See [PathObserver.values] and [PathObserver.value]. | 25749 * |
| 25750 * Currently this does not support propagation through Shadow DOMs. |
| 26196 */ | 25751 */ |
| 26197 PathObserver(this.object, String path) | 25752 static void initialize() { |
| 26198 : path = path, | 25753 if (!_initialized) { |
| 26199 _isValid = _isPathValid(path) { | 25754 _initialized = true; |
| 26200 | 25755 |
| 26201 // TODO(jmesserly): if the path is empty, or the object is! Observable, we | 25756 if (MutationObserver.supported) { |
| 26202 // can optimize the PathObserver to be more lightweight. | 25757 var observer = new MutationObserver(_processTreeChange); |
| 26203 | 25758 observer.observe(document, childList: true, subtree: true); |
| 26204 _values = new StreamController(onListen: _observe, onCancel: _unobserve); | 25759 } else { |
| 26205 | 25760 document.on['DOMNodeInserted'].listen(_handleNodeInserted); |
| 26206 if (_isValid) { | 25761 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved); |
| 26207 var segments = []; | |
| 26208 for (var segment in path.trim().split('.')) { | |
| 26209 if (segment == '') continue; | |
| 26210 var index = int.parse(segment, onError: (_) {}); | |
| 26211 segments.add(index != null ? index : new Symbol(segment)); | |
| 26212 } | |
| 26213 | |
| 26214 // Create the property observer linked list. | |
| 26215 // Note that the structure of a path can't change after it is initially | |
| 26216 // constructed, even though the objects along the path can change. | |
| 26217 for (int i = segments.length - 1; i >= 0; i--) { | |
| 26218 _observer = new _PropertyObserver(this, segments[i], _observer); | |
| 26219 if (_lastObserver == null) _lastObserver = _observer; | |
| 26220 } | 25762 } |
| 26221 } | 25763 } |
| 26222 } | 25764 } |
| 26223 | 25765 |
| 26224 // TODO(jmesserly): we could try adding the first value to the stream, but | 25766 static void _processTreeChange(List<MutationRecord> mutations, |
| 26225 // that delivers the first record async. | 25767 MutationObserver observer) { |
| 26226 /** | 25768 for (var record in mutations) { |
| 26227 * Listens to the stream, and invokes the [callback] immediately with the | 25769 for (var node in record.addedNodes) { |
| 26228 * current [value]. This is useful for bindings, which want to be up-to-date | 25770 // When nodes enter the document we need to make sure that all of the |
| 26229 * immediately. | 25771 // models are properly propagated through the entire sub-tree. |
| 26230 */ | 25772 propagateModel(node, _calculatedModel(node), true); |
| 26231 StreamSubscription bindSync(void callback(value)) { | 25773 } |
| 26232 var result = values.listen(callback); | 25774 for (var node in record.removedNodes) { |
| 26233 callback(value); | 25775 propagateModel(node, _calculatedModel(node), false); |
| 26234 return result; | 25776 } |
| 26235 } | |
| 26236 | |
| 26237 // TODO(jmesserly): should this be a change record with the old value? | |
| 26238 // TODO(jmesserly): should this be a broadcast stream? We only need | |
| 26239 // single-subscription in the bindings system, so single sub saves overhead. | |
| 26240 /** | |
| 26241 * Gets the stream of values that were observed at this path. | |
| 26242 * This returns a single-subscription stream. | |
| 26243 */ | |
| 26244 Stream get values => _values.stream; | |
| 26245 | |
| 26246 /** Force synchronous delivery of [values]. */ | |
| 26247 void _deliverValues() { | |
| 26248 _scheduled = false; | |
| 26249 | |
| 26250 var newValue = value; | |
| 26251 if (!identical(_lastValue, newValue)) { | |
| 26252 _values.add(newValue); | |
| 26253 _lastValue = newValue; | |
| 26254 } | 25777 } |
| 26255 } | 25778 } |
| 26256 | 25779 |
| 26257 void _observe() { | 25780 static void _handleNodeInserted(MutationEvent e) { |
| 26258 if (_observer != null) { | 25781 var node = e.target; |
| 26259 _lastValue = value; | 25782 window.setImmediate(() { |
| 26260 _observer.observe(); | 25783 propagateModel(node, _calculatedModel(node), true); |
| 26261 } | 25784 }); |
| 26262 } | 25785 } |
| 26263 | 25786 |
| 26264 void _unobserve() { | 25787 static void _handleNodeRemoved(MutationEvent e) { |
| 26265 if (_observer != null) _observer.unobserve(); | 25788 var node = e.target; |
| 25789 window.setImmediate(() { |
| 25790 propagateModel(node, _calculatedModel(node), false); |
| 25791 }); |
| 26266 } | 25792 } |
| 26267 | 25793 |
| 26268 void _notifyChange() { | 25794 /** |
| 26269 if (_scheduled) return; | 25795 * Figures out what the model should be for a node, avoiding any cached |
| 26270 _scheduled = true; | 25796 * model values. |
| 26271 | 25797 */ |
| 26272 // TODO(jmesserly): should we have a guarenteed order with respect to other | 25798 static _calculatedModel(node) { |
| 26273 // paths? If so, we could implement this fairly easily by sorting instances | 25799 if (node._hasLocalModel == true) { |
| 26274 // of this class by birth order before delivery. | 25800 return node._model; |
| 26275 queueChangeRecords(_deliverValues); | 25801 } else if (node.parentNode != null) { |
| 25802 return node.parentNode._model; |
| 25803 } |
| 25804 return null; |
| 26276 } | 25805 } |
| 26277 | 25806 |
| 26278 /** Gets the last reported value at this path. */ | 25807 /** |
| 26279 get value { | 25808 * Pushes model changes down through the tree. |
| 26280 if (!_isValid) return null; | 25809 * |
| 26281 if (_observer == null) return object; | 25810 * Set fullTree to true if the state of the tree is unknown and model changes |
| 26282 _observer.ensureValue(object); | 25811 * should be propagated through the entire tree. |
| 26283 return _lastObserver.value; | 25812 */ |
| 26284 } | 25813 static void propagateModel(Node node, model, bool fullTree) { |
| 26285 | 25814 // Calling into user code with the != call could generate exceptions. |
| 26286 /** Sets the value at this path. */ | 25815 // Catch and report them a global exceptions. |
| 26287 void set value(Object value) { | 25816 try { |
| 26288 // TODO(jmesserly): throw if property cannot be set? | 25817 if (node._hasLocalModel != true && node._model != model && |
| 26289 // MDV seems tolerant of these error. | 25818 node._modelChangedStreams != null && |
| 26290 if (_observer == null || !_isValid) return; | 25819 !node._modelChangedStreams.isEmpty) { |
| 26291 _observer.ensureValue(object); | 25820 node._model = model; |
| 26292 var last = _lastObserver; | 25821 node._modelChangedStreams.toList() |
| 26293 if (_setObjectProperty(last._object, last._property, value)) { | 25822 .forEach((controller) => controller.add(node)); |
| 26294 // Technically, this would get updated asynchronously via a change record. | 25823 } |
| 26295 // However, it is nice if calling the getter will yield the same value | 25824 } catch (e, s) { |
| 26296 // that was just set. So we use this opportunity to update our cache. | 25825 new Future.error(e, s); |
| 26297 last.value = value; | |
| 26298 } | 25826 } |
| 26299 } | 25827 for (var child = node.$dom_firstChild; child != null; |
| 26300 } | 25828 child = child.nextNode) { |
| 26301 | 25829 if (child._hasLocalModel != true) { |
| 26302 // TODO(jmesserly): these should go away in favor of mirrors! | 25830 propagateModel(child, model, fullTree); |
| 26303 _getObjectProperty(object, property) { | 25831 } else if (fullTree) { |
| 26304 if (object is List && property is int) { | 25832 propagateModel(child, child._model, true); |
| 26305 if (property >= 0 && property < object.length) { | |
| 26306 return object[property]; | |
| 26307 } else { | |
| 26308 return null; | |
| 26309 } | |
| 26310 } | |
| 26311 | |
| 26312 // TODO(jmesserly): what about length? | |
| 26313 if (object is Map) return object[property]; | |
| 26314 | |
| 26315 if (object is Observable) return object.getValueWorkaround(property); | |
| 26316 | |
| 26317 return null; | |
| 26318 } | |
| 26319 | |
| 26320 bool _setObjectProperty(object, property, value) { | |
| 26321 if (object is List && property is int) { | |
| 26322 object[property] = value; | |
| 26323 } else if (object is Map) { | |
| 26324 object[property] = value; | |
| 26325 } else if (object is Observable) { | |
| 26326 (object as Observable).setValueWorkaround(property, value); | |
| 26327 } else { | |
| 26328 return false; | |
| 26329 } | |
| 26330 return true; | |
| 26331 } | |
| 26332 | |
| 26333 | |
| 26334 class _PropertyObserver { | |
| 26335 final PathObserver _path; | |
| 26336 final _property; | |
| 26337 final Symbol _symbol; | |
| 26338 final _PropertyObserver _next; | |
| 26339 | |
| 26340 // TODO(jmesserly): would be nice not to store both of these. | |
| 26341 Object _object; | |
| 26342 Object _value; | |
| 26343 StreamSubscription _sub; | |
| 26344 | |
| 26345 _PropertyObserver(this._path, this._property, this._next); | |
| 26346 | |
| 26347 get value => _value; | |
| 26348 | |
| 26349 void set value(Object newValue) { | |
| 26350 _value = newValue; | |
| 26351 if (_next != null) { | |
| 26352 if (_sub != null) _next.unobserve(); | |
| 26353 _next.ensureValue(_value); | |
| 26354 if (_sub != null) _next.observe(); | |
| 26355 } | |
| 26356 } | |
| 26357 | |
| 26358 void ensureValue(object) { | |
| 26359 // If we're observing, values should be up to date already. | |
| 26360 if (_sub != null) return; | |
| 26361 | |
| 26362 _object = object; | |
| 26363 value = _getObjectProperty(object, _property); | |
| 26364 } | |
| 26365 | |
| 26366 void observe() { | |
| 26367 if (_object is Observable) { | |
| 26368 assert(_sub == null); | |
| 26369 _sub = (_object as Observable).changes.listen(_onChange); | |
| 26370 } | |
| 26371 if (_next != null) _next.observe(); | |
| 26372 } | |
| 26373 | |
| 26374 void unobserve() { | |
| 26375 if (_sub == null) return; | |
| 26376 | |
| 26377 _sub.cancel(); | |
| 26378 _sub = null; | |
| 26379 if (_next != null) _next.unobserve(); | |
| 26380 } | |
| 26381 | |
| 26382 void _onChange(List<ChangeRecord> changes) { | |
| 26383 for (var change in changes) { | |
| 26384 // TODO(jmesserly): what to do about "new Symbol" here? | |
| 26385 // Ideally this would only preserve names if the user has opted in to | |
| 26386 // them being preserved. | |
| 26387 // TODO(jmesserly): should we drop observable maps with String keys? | |
| 26388 // If so then we only need one check here. | |
| 26389 if (change.changes(_property)) { | |
| 26390 value = _getObjectProperty(_object, _property); | |
| 26391 _path._notifyChange(); | |
| 26392 return; | |
| 26393 } | 25833 } |
| 26394 } | 25834 } |
| 26395 } | 25835 } |
| 26396 } | 25836 } |
| 26397 | |
| 26398 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js | |
| 26399 | |
| 26400 const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*'; | |
| 26401 final _pathRegExp = new RegExp('^' | |
| 26402 '(?:#?' + _pathIndentPart + ')?' | |
| 26403 '(?:' | |
| 26404 '(?:\\.' + _pathIndentPart + ')' | |
| 26405 ')*' | |
| 26406 r'$', caseSensitive: false); | |
| 26407 | |
| 26408 final _spacesRegExp = new RegExp(r'\s'); | |
| 26409 | |
| 26410 bool _isPathValid(String s) { | |
| 26411 s = s.replaceAll(_spacesRegExp, ''); | |
| 26412 | |
| 26413 if (s == '') return true; | |
| 26414 if (s[0] == '.') return false; | |
| 26415 return _pathRegExp.hasMatch(s); | |
| 26416 } | |
| 26417 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 25837 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 26418 // for details. All rights reserved. Use of this source code is governed by a | 25838 // for details. All rights reserved. Use of this source code is governed by a |
| 26419 // BSD-style license that can be found in the LICENSE file. | 25839 // BSD-style license that can be found in the LICENSE file. |
| 26420 | 25840 |
| 26421 | 25841 |
| 26422 /** | 25842 /** |
| 26423 * A utility class for representing two-dimensional positions. | 25843 * A utility class for representing two-dimensional positions. |
| 26424 */ | 25844 */ |
| 26425 class Point { | 25845 class Point { |
| 26426 final num x; | 25846 final num x; |
| (...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 26634 | 26054 |
| 26635 Point get topLeft => new Point(this.left, this.top); | 26055 Point get topLeft => new Point(this.left, this.top); |
| 26636 Point get bottomRight => new Point(this.left + this.width, | 26056 Point get bottomRight => new Point(this.left + this.width, |
| 26637 this.top + this.height); | 26057 this.top + this.height); |
| 26638 } | 26058 } |
| 26639 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 26059 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 26640 // for details. All rights reserved. Use of this source code is governed by a | 26060 // for details. All rights reserved. Use of this source code is governed by a |
| 26641 // BSD-style license that can be found in the LICENSE file. | 26061 // BSD-style license that can be found in the LICENSE file. |
| 26642 | 26062 |
| 26643 | 26063 |
| 26644 // This code is a port of Model-Driven-Views: | |
| 26645 // https://github.com/toolkitchen/mdv | |
| 26646 // The code mostly comes from src/template_element.js | |
| 26647 | |
| 26648 typedef void _ChangeHandler(value); | |
| 26649 | |
| 26650 /** | |
| 26651 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases, | |
| 26652 * but (by design) don't attempt to implement a wide array of specialized | |
| 26653 * behaviors. | |
| 26654 * | |
| 26655 * Enabling these features in MDV is a matter of implementing and registering an | |
| 26656 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more | |
| 26657 * delegation functions which implement specialized behavior. This object is | |
| 26658 * registered with MDV via [TemplateElement.syntax]: | |
| 26659 * | |
| 26660 * | |
| 26661 * HTML: | |
| 26662 * <template bind syntax="MySyntax"> | |
| 26663 * {{ What!Ever('crazy')->thing^^^I+Want(data) }} | |
| 26664 * </template> | |
| 26665 * | |
| 26666 * Dart: | |
| 26667 * class MySyntax extends CustomBindingSyntax { | |
| 26668 * getBinding(model, path, name, node) { | |
| 26669 * // The magic happens here! | |
| 26670 * } | |
| 26671 * } | |
| 26672 * | |
| 26673 * ... | |
| 26674 * | |
| 26675 * TemplateElement.syntax['MySyntax'] = new MySyntax(); | |
| 26676 * | |
| 26677 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more | |
| 26678 * information about Custom Syntax. | |
| 26679 */ | |
| 26680 // TODO(jmesserly): if this is just one method, a function type would make it | |
| 26681 // more Dart-friendly. | |
| 26682 @Experimental | |
| 26683 abstract class CustomBindingSyntax { | |
| 26684 // TODO(jmesserly): I had to remove type annotations from "name" and "node" | |
| 26685 // Normally they are String and Node respectively. But sometimes it will pass | |
| 26686 // (int name, CompoundBinding node). That seems very confusing; we may want | |
| 26687 // to change this API. | |
| 26688 getBinding(model, String path, name, node); | |
| 26689 } | |
| 26690 | |
| 26691 /** The callback used in the [CompoundBinding.combinator] field. */ | |
| 26692 @Experimental | |
| 26693 typedef Object CompoundBindingCombinator(Map objects); | |
| 26694 | |
| 26695 /** Information about the instantiated template. */ | |
| 26696 @Experimental | |
| 26697 class TemplateInstance { | |
| 26698 // TODO(rafaelw): firstNode & lastNode should be read-synchronous | |
| 26699 // in cases where script has modified the template instance boundary. | |
| 26700 | |
| 26701 /** The first node of this template instantiation. */ | |
| 26702 final Node firstNode; | |
| 26703 | |
| 26704 /** | |
| 26705 * The last node of this template instantiation. | |
| 26706 * This could be identical to [firstNode] if the template only expanded to a | |
| 26707 * single node. | |
| 26708 */ | |
| 26709 final Node lastNode; | |
| 26710 | |
| 26711 /** The model used to instantiate the template. */ | |
| 26712 final model; | |
| 26713 | |
| 26714 TemplateInstance(this.firstNode, this.lastNode, this.model); | |
| 26715 } | |
| 26716 | |
| 26717 /** | |
| 26718 * Model-Driven Views contains a helper object which is useful for the | |
| 26719 * implementation of a Custom Syntax. | |
| 26720 * | |
| 26721 * var binding = new CompoundBinding((values) { | |
| 26722 * var combinedValue; | |
| 26723 * // compute combinedValue based on the current values which are provided | |
| 26724 * return combinedValue; | |
| 26725 * }); | |
| 26726 * binding.bind('name1', obj1, path1); | |
| 26727 * binding.bind('name2', obj2, path2); | |
| 26728 * //... | |
| 26729 * binding.bind('nameN', objN, pathN); | |
| 26730 * | |
| 26731 * CompoundBinding is an object which knows how to listen to multiple path | |
| 26732 * values (registered via [bind]) and invoke its [combinator] when one or more | |
| 26733 * of the values have changed and set its [value] property to the return value | |
| 26734 * of the function. When any value has changed, all current values are provided | |
| 26735 * to the [combinator] in the single `values` argument. | |
| 26736 * | |
| 26737 * See [CustomBindingSyntax] for more information. | |
| 26738 */ | |
| 26739 // TODO(jmesserly): what is the public API surface here? I just guessed; | |
| 26740 // most of it seemed non-public. | |
| 26741 @Experimental | |
| 26742 class CompoundBinding extends ObservableBase { | |
| 26743 CompoundBindingCombinator _combinator; | |
| 26744 | |
| 26745 // TODO(jmesserly): ideally these would be String keys, but sometimes we | |
| 26746 // use integers. | |
| 26747 Map<dynamic, StreamSubscription> _bindings = new Map(); | |
| 26748 Map _values = new Map(); | |
| 26749 bool _scheduled = false; | |
| 26750 bool _disposed = false; | |
| 26751 Object _value; | |
| 26752 | |
| 26753 CompoundBinding([CompoundBindingCombinator combinator]) { | |
| 26754 // TODO(jmesserly): this is a tweak to the original code, it seemed to me | |
| 26755 // that passing the combinator to the constructor should be equivalent to | |
| 26756 // setting it via the property. | |
| 26757 // I also added a null check to the combinator setter. | |
| 26758 this.combinator = combinator; | |
| 26759 } | |
| 26760 | |
| 26761 CompoundBindingCombinator get combinator => _combinator; | |
| 26762 | |
| 26763 set combinator(CompoundBindingCombinator combinator) { | |
| 26764 _combinator = combinator; | |
| 26765 if (combinator != null) _scheduleResolve(); | |
| 26766 } | |
| 26767 | |
| 26768 static const _VALUE = const Symbol('value'); | |
| 26769 | |
| 26770 get value => _value; | |
| 26771 | |
| 26772 void set value(newValue) { | |
| 26773 _value = notifyPropertyChange(_VALUE, _value, newValue); | |
| 26774 } | |
| 26775 | |
| 26776 // TODO(jmesserly): remove these workarounds when dart2js supports mirrors! | |
| 26777 getValueWorkaround(key) { | |
| 26778 if (key == _VALUE) return value; | |
| 26779 return null; | |
| 26780 } | |
| 26781 setValueWorkaround(key, val) { | |
| 26782 if (key == _VALUE) value = val; | |
| 26783 } | |
| 26784 | |
| 26785 void bind(name, model, String path) { | |
| 26786 unbind(name); | |
| 26787 | |
| 26788 _bindings[name] = new PathObserver(model, path).bindSync((value) { | |
| 26789 _values[name] = value; | |
| 26790 _scheduleResolve(); | |
| 26791 }); | |
| 26792 } | |
| 26793 | |
| 26794 void unbind(name, {bool suppressResolve: false}) { | |
| 26795 var binding = _bindings.remove(name); | |
| 26796 if (binding == null) return; | |
| 26797 | |
| 26798 binding.cancel(); | |
| 26799 _values.remove(name); | |
| 26800 if (!suppressResolve) _scheduleResolve(); | |
| 26801 } | |
| 26802 | |
| 26803 // TODO(rafaelw): Is this the right processing model? | |
| 26804 // TODO(rafaelw): Consider having a seperate ChangeSummary for | |
| 26805 // CompoundBindings so to excess dirtyChecks. | |
| 26806 void _scheduleResolve() { | |
| 26807 if (_scheduled) return; | |
| 26808 _scheduled = true; | |
| 26809 queueChangeRecords(resolve); | |
| 26810 } | |
| 26811 | |
| 26812 void resolve() { | |
| 26813 if (_disposed) return; | |
| 26814 _scheduled = false; | |
| 26815 | |
| 26816 if (_combinator == null) { | |
| 26817 throw new StateError( | |
| 26818 'CompoundBinding attempted to resolve without a combinator'); | |
| 26819 } | |
| 26820 | |
| 26821 value = _combinator(_values); | |
| 26822 } | |
| 26823 | |
| 26824 void dispose() { | |
| 26825 for (var binding in _bindings.values) { | |
| 26826 binding.cancel(); | |
| 26827 } | |
| 26828 _bindings.clear(); | |
| 26829 _values.clear(); | |
| 26830 | |
| 26831 _disposed = true; | |
| 26832 value = null; | |
| 26833 } | |
| 26834 } | |
| 26835 | |
| 26836 Stream<Event> _getStreamForInputType(InputElement element) { | |
| 26837 switch (element.type) { | |
| 26838 case 'checkbox': | |
| 26839 return element.onClick; | |
| 26840 case 'radio': | |
| 26841 case 'select-multiple': | |
| 26842 case 'select-one': | |
| 26843 return element.onChange; | |
| 26844 default: | |
| 26845 return element.onInput; | |
| 26846 } | |
| 26847 } | |
| 26848 | |
| 26849 abstract class _InputBinding { | |
| 26850 final InputElement element; | |
| 26851 PathObserver binding; | |
| 26852 StreamSubscription _pathSub; | |
| 26853 StreamSubscription _eventSub; | |
| 26854 | |
| 26855 _InputBinding(this.element, model, String path) { | |
| 26856 binding = new PathObserver(model, path); | |
| 26857 _pathSub = binding.bindSync(valueChanged); | |
| 26858 _eventSub = _getStreamForInputType(element).listen(updateBinding); | |
| 26859 } | |
| 26860 | |
| 26861 void valueChanged(newValue); | |
| 26862 | |
| 26863 void updateBinding(e); | |
| 26864 | |
| 26865 void unbind() { | |
| 26866 binding = null; | |
| 26867 _pathSub.cancel(); | |
| 26868 _eventSub.cancel(); | |
| 26869 } | |
| 26870 } | |
| 26871 | |
| 26872 class _ValueBinding extends _InputBinding { | |
| 26873 _ValueBinding(element, model, path) : super(element, model, path); | |
| 26874 | |
| 26875 void valueChanged(value) { | |
| 26876 element.value = value == null ? '' : '$value'; | |
| 26877 } | |
| 26878 | |
| 26879 void updateBinding(e) { | |
| 26880 binding.value = element.value; | |
| 26881 } | |
| 26882 } | |
| 26883 | |
| 26884 // TODO(jmesserly): not sure what kind of boolean conversion rules to | |
| 26885 // apply for template data-binding. HTML attributes are true if they're present. | |
| 26886 // However Dart only treats "true" as true. Since this is HTML we'll use | |
| 26887 // something closer to the HTML rules: null (missing) and false are false, | |
| 26888 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59 | |
| 26889 bool _templateBooleanConversion(value) => null != value && false != value; | |
| 26890 | |
| 26891 class _CheckedBinding extends _InputBinding { | |
| 26892 _CheckedBinding(element, model, path) : super(element, model, path); | |
| 26893 | |
| 26894 void valueChanged(value) { | |
| 26895 element.checked = _templateBooleanConversion(value); | |
| 26896 } | |
| 26897 | |
| 26898 void updateBinding(e) { | |
| 26899 binding.value = element.checked; | |
| 26900 | |
| 26901 // Only the radio button that is getting checked gets an event. We | |
| 26902 // therefore find all the associated radio buttons and update their | |
| 26903 // CheckedBinding manually. | |
| 26904 if (element is InputElement && element.type == 'radio') { | |
| 26905 for (var r in _getAssociatedRadioButtons(element)) { | |
| 26906 var checkedBinding = r._checkedBinding; | |
| 26907 if (checkedBinding != null) { | |
| 26908 // Set the value directly to avoid an infinite call stack. | |
| 26909 checkedBinding.binding.value = false; | |
| 26910 } | |
| 26911 } | |
| 26912 } | |
| 26913 } | |
| 26914 } | |
| 26915 | |
| 26916 // TODO(jmesserly): polyfill document.contains API instead of doing it here | |
| 26917 bool _isNodeInDocument(Node node) { | |
| 26918 // On non-IE this works: | |
| 26919 // return node.document.contains(node); | |
| 26920 var document = node.document; | |
| 26921 if (node == document || node.parentNode == document) return true; | |
| 26922 return document.documentElement.contains(node); | |
| 26923 } | |
| 26924 | |
| 26925 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. | |
| 26926 // Returns an array containing all radio buttons other than |element| that | |
| 26927 // have the same |name|, either in the form that |element| belongs to or, | |
| 26928 // if no form, in the document tree to which |element| belongs. | |
| 26929 // | |
| 26930 // This implementation is based upon the HTML spec definition of a | |
| 26931 // "radio button group": | |
| 26932 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht
ml#radio-button-group | |
| 26933 // | |
| 26934 Iterable _getAssociatedRadioButtons(element) { | |
| 26935 if (!_isNodeInDocument(element)) return []; | |
| 26936 if (element.form != null) { | |
| 26937 return element.form.nodes.where((el) { | |
| 26938 return el != element && | |
| 26939 el is InputElement && | |
| 26940 el.type == 'radio' && | |
| 26941 el.name == element.name; | |
| 26942 }); | |
| 26943 } else { | |
| 26944 var radios = element.document.queryAll( | |
| 26945 'input[type="radio"][name="${element.name}"]'); | |
| 26946 return radios.where((el) => el != element && el.form == null); | |
| 26947 } | |
| 26948 } | |
| 26949 | |
| 26950 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { | |
| 26951 var clone = node.clone(false); // Shallow clone. | |
| 26952 if (clone is Element && clone.isTemplate) { | |
| 26953 TemplateElement.decorate(clone, node); | |
| 26954 if (syntax != null) { | |
| 26955 clone.attributes.putIfAbsent('syntax', () => syntax); | |
| 26956 } | |
| 26957 } | |
| 26958 | |
| 26959 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { | |
| 26960 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax)); | |
| 26961 } | |
| 26962 return clone; | |
| 26963 } | |
| 26964 | |
| 26965 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df
n-template-contents-owner | |
| 26966 Document _getTemplateContentsOwner(Document doc) { | |
| 26967 if (doc.window == null) { | |
| 26968 return doc; | |
| 26969 } | |
| 26970 var d = doc._templateContentsOwner; | |
| 26971 if (d == null) { | |
| 26972 // TODO(arv): This should either be a Document or HTMLDocument depending | |
| 26973 // on doc. | |
| 26974 d = doc.implementation.createHtmlDocument(''); | |
| 26975 while (d.$dom_lastChild != null) { | |
| 26976 d.$dom_lastChild.remove(); | |
| 26977 } | |
| 26978 doc._templateContentsOwner = d; | |
| 26979 } | |
| 26980 return d; | |
| 26981 } | |
| 26982 | |
| 26983 Element _cloneAndSeperateAttributeTemplate(Element templateElement) { | |
| 26984 var clone = templateElement.clone(false); | |
| 26985 var attributes = templateElement.attributes; | |
| 26986 for (var name in attributes.keys.toList()) { | |
| 26987 switch (name) { | |
| 26988 case 'template': | |
| 26989 case 'repeat': | |
| 26990 case 'bind': | |
| 26991 case 'ref': | |
| 26992 clone.attributes.remove(name); | |
| 26993 break; | |
| 26994 default: | |
| 26995 attributes.remove(name); | |
| 26996 break; | |
| 26997 } | |
| 26998 } | |
| 26999 | |
| 27000 return clone; | |
| 27001 } | |
| 27002 | |
| 27003 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) { | |
| 27004 var content = templateElement.content; | |
| 27005 | |
| 27006 if (!templateElement._isAttributeTemplate) { | |
| 27007 var child; | |
| 27008 while ((child = templateElement.$dom_firstChild) != null) { | |
| 27009 content.append(child); | |
| 27010 } | |
| 27011 return; | |
| 27012 } | |
| 27013 | |
| 27014 // For attribute templates we copy the whole thing into the content and | |
| 27015 // we move the non template attributes into the content. | |
| 27016 // | |
| 27017 // <tr foo template> | |
| 27018 // | |
| 27019 // becomes | |
| 27020 // | |
| 27021 // <tr template> | |
| 27022 // + #document-fragment | |
| 27023 // + <tr foo> | |
| 27024 // | |
| 27025 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement); | |
| 27026 var child; | |
| 27027 while ((child = templateElement.$dom_firstChild) != null) { | |
| 27028 newRoot.append(child); | |
| 27029 } | |
| 27030 content.append(newRoot); | |
| 27031 } | |
| 27032 | |
| 27033 void _bootstrapTemplatesRecursivelyFrom(Node node) { | |
| 27034 void bootstrap(template) { | |
| 27035 if (!TemplateElement.decorate(template)) { | |
| 27036 _bootstrapTemplatesRecursivelyFrom(template.content); | |
| 27037 } | |
| 27038 } | |
| 27039 | |
| 27040 // Need to do this first as the contents may get lifted if |node| is | |
| 27041 // template. | |
| 27042 // TODO(jmesserly): node is DocumentFragment or Element | |
| 27043 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors); | |
| 27044 if (node is Element && node.isTemplate) bootstrap(node); | |
| 27045 | |
| 27046 templateDescendents.forEach(bootstrap); | |
| 27047 } | |
| 27048 | |
| 27049 final String _allTemplatesSelectors = 'template, option[template], ' + | |
| 27050 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", "); | |
| 27051 | |
| 27052 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) { | |
| 27053 if (node is Element) { | |
| 27054 _addAttributeBindings(node, model, syntax); | |
| 27055 } else if (node is Text) { | |
| 27056 _parseAndBind(node, node.text, 'text', model, syntax); | |
| 27057 } | |
| 27058 | |
| 27059 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { | |
| 27060 _addBindings(c, model, syntax); | |
| 27061 } | |
| 27062 } | |
| 27063 | |
| 27064 | |
| 27065 void _addAttributeBindings(Element element, model, syntax) { | |
| 27066 element.attributes.forEach((name, value) { | |
| 27067 if (value == '' && (name == 'bind' || name == 'repeat')) { | |
| 27068 value = '{{}}'; | |
| 27069 } | |
| 27070 _parseAndBind(element, value, name, model, syntax); | |
| 27071 }); | |
| 27072 } | |
| 27073 | |
| 27074 void _parseAndBind(Node node, String text, String name, model, | |
| 27075 CustomBindingSyntax syntax) { | |
| 27076 | |
| 27077 var tokens = _parseMustacheTokens(text); | |
| 27078 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { | |
| 27079 return; | |
| 27080 } | |
| 27081 | |
| 27082 if (tokens.length == 1 && tokens[0].isBinding) { | |
| 27083 _bindOrDelegate(node, name, model, tokens[0].value, syntax); | |
| 27084 return; | |
| 27085 } | |
| 27086 | |
| 27087 var replacementBinding = new CompoundBinding(); | |
| 27088 for (var i = 0; i < tokens.length; i++) { | |
| 27089 var token = tokens[i]; | |
| 27090 if (token.isBinding) { | |
| 27091 _bindOrDelegate(replacementBinding, i, model, token.value, syntax); | |
| 27092 } | |
| 27093 } | |
| 27094 | |
| 27095 replacementBinding.combinator = (values) { | |
| 27096 var newValue = new StringBuffer(); | |
| 27097 | |
| 27098 for (var i = 0; i < tokens.length; i++) { | |
| 27099 var token = tokens[i]; | |
| 27100 if (token.isText) { | |
| 27101 newValue.write(token.value); | |
| 27102 } else { | |
| 27103 var value = values[i]; | |
| 27104 if (value != null) { | |
| 27105 newValue.write(value); | |
| 27106 } | |
| 27107 } | |
| 27108 } | |
| 27109 | |
| 27110 return newValue.toString(); | |
| 27111 }; | |
| 27112 | |
| 27113 node.bind(name, replacementBinding, 'value'); | |
| 27114 } | |
| 27115 | |
| 27116 void _bindOrDelegate(node, name, model, String path, | |
| 27117 CustomBindingSyntax syntax) { | |
| 27118 | |
| 27119 if (syntax != null) { | |
| 27120 var delegateBinding = syntax.getBinding(model, path, name, node); | |
| 27121 if (delegateBinding != null) { | |
| 27122 model = delegateBinding; | |
| 27123 path = 'value'; | |
| 27124 } | |
| 27125 } | |
| 27126 | |
| 27127 node.bind(name, model, path); | |
| 27128 } | |
| 27129 | |
| 27130 class _BindingToken { | |
| 27131 final String value; | |
| 27132 final bool isBinding; | |
| 27133 | |
| 27134 _BindingToken(this.value, {this.isBinding: false}); | |
| 27135 | |
| 27136 bool get isText => !isBinding; | |
| 27137 } | |
| 27138 | |
| 27139 List<_BindingToken> _parseMustacheTokens(String s) { | |
| 27140 var result = []; | |
| 27141 var length = s.length; | |
| 27142 var index = 0, lastIndex = 0; | |
| 27143 while (lastIndex < length) { | |
| 27144 index = s.indexOf('{{', lastIndex); | |
| 27145 if (index < 0) { | |
| 27146 result.add(new _BindingToken(s.substring(lastIndex))); | |
| 27147 break; | |
| 27148 } else { | |
| 27149 // There is a non-empty text run before the next path token. | |
| 27150 if (index > 0 && lastIndex < index) { | |
| 27151 result.add(new _BindingToken(s.substring(lastIndex, index))); | |
| 27152 } | |
| 27153 lastIndex = index + 2; | |
| 27154 index = s.indexOf('}}', lastIndex); | |
| 27155 if (index < 0) { | |
| 27156 var text = s.substring(lastIndex - 2); | |
| 27157 if (result.length > 0 && result.last.isText) { | |
| 27158 result.last.value += text; | |
| 27159 } else { | |
| 27160 result.add(new _BindingToken(text)); | |
| 27161 } | |
| 27162 break; | |
| 27163 } | |
| 27164 | |
| 27165 var value = s.substring(lastIndex, index).trim(); | |
| 27166 result.add(new _BindingToken(value, isBinding: true)); | |
| 27167 lastIndex = index + 2; | |
| 27168 } | |
| 27169 } | |
| 27170 return result; | |
| 27171 } | |
| 27172 | |
| 27173 void _addTemplateInstanceRecord(fragment, model) { | |
| 27174 if (fragment.$dom_firstChild == null) { | |
| 27175 return; | |
| 27176 } | |
| 27177 | |
| 27178 var instanceRecord = new TemplateInstance( | |
| 27179 fragment.$dom_firstChild, fragment.$dom_lastChild, model); | |
| 27180 | |
| 27181 var node = instanceRecord.firstNode; | |
| 27182 while (node != null) { | |
| 27183 node._templateInstance = instanceRecord; | |
| 27184 node = node.nextNode; | |
| 27185 } | |
| 27186 } | |
| 27187 | |
| 27188 void _removeAllBindingsRecursively(Node node) { | |
| 27189 node.unbindAll(); | |
| 27190 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { | |
| 27191 _removeAllBindingsRecursively(c); | |
| 27192 } | |
| 27193 } | |
| 27194 | |
| 27195 void _removeTemplateChild(Node parent, Node child) { | |
| 27196 child._templateInstance = null; | |
| 27197 if (child is Element && child.isTemplate) { | |
| 27198 // Make sure we stop observing when we remove an element. | |
| 27199 var templateIterator = child._templateIterator; | |
| 27200 if (templateIterator != null) { | |
| 27201 templateIterator.abandon(); | |
| 27202 child._templateIterator = null; | |
| 27203 } | |
| 27204 } | |
| 27205 child.remove(); | |
| 27206 _removeAllBindingsRecursively(child); | |
| 27207 } | |
| 27208 | |
| 27209 class _InstanceCursor { | |
| 27210 final Element _template; | |
| 27211 Node _terminator; | |
| 27212 Node _previousTerminator; | |
| 27213 int _previousIndex = -1; | |
| 27214 int _index = 0; | |
| 27215 | |
| 27216 _InstanceCursor(this._template, [index]) { | |
| 27217 _terminator = _template; | |
| 27218 if (index != null) { | |
| 27219 while (index-- > 0) { | |
| 27220 next(); | |
| 27221 } | |
| 27222 } | |
| 27223 } | |
| 27224 | |
| 27225 void next() { | |
| 27226 _previousTerminator = _terminator; | |
| 27227 _previousIndex = _index; | |
| 27228 _index++; | |
| 27229 | |
| 27230 while (_index > _terminator._instanceTerminatorCount) { | |
| 27231 _index -= _terminator._instanceTerminatorCount; | |
| 27232 _terminator = _terminator.nextNode; | |
| 27233 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') { | |
| 27234 _index += _instanceCount(_terminator); | |
| 27235 } | |
| 27236 } | |
| 27237 } | |
| 27238 | |
| 27239 void abandon() { | |
| 27240 assert(_instanceCount(_template) > 0); | |
| 27241 assert(_terminator._instanceTerminatorCount > 0); | |
| 27242 assert(_index > 0); | |
| 27243 | |
| 27244 _terminator._instanceTerminatorCount--; | |
| 27245 _index--; | |
| 27246 } | |
| 27247 | |
| 27248 void insert(fragment) { | |
| 27249 assert(_template.parentNode != null); | |
| 27250 | |
| 27251 _previousTerminator = _terminator; | |
| 27252 _previousIndex = _index; | |
| 27253 _index++; | |
| 27254 | |
| 27255 _terminator = fragment.$dom_lastChild; | |
| 27256 if (_terminator == null) _terminator = _previousTerminator; | |
| 27257 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode); | |
| 27258 | |
| 27259 _terminator._instanceTerminatorCount++; | |
| 27260 if (_terminator != _previousTerminator) { | |
| 27261 while (_previousTerminator._instanceTerminatorCount > | |
| 27262 _previousIndex) { | |
| 27263 _previousTerminator._instanceTerminatorCount--; | |
| 27264 _terminator._instanceTerminatorCount++; | |
| 27265 } | |
| 27266 } | |
| 27267 } | |
| 27268 | |
| 27269 void remove() { | |
| 27270 assert(_previousIndex != -1); | |
| 27271 assert(_previousTerminator != null && | |
| 27272 (_previousIndex > 0 || _previousTerminator == _template)); | |
| 27273 assert(_terminator != null && _index > 0); | |
| 27274 assert(_template.parentNode != null); | |
| 27275 assert(_instanceCount(_template) > 0); | |
| 27276 | |
| 27277 if (_previousTerminator == _terminator) { | |
| 27278 assert(_index == _previousIndex + 1); | |
| 27279 _terminator._instanceTerminatorCount--; | |
| 27280 _terminator = _template; | |
| 27281 _previousTerminator = null; | |
| 27282 _previousIndex = -1; | |
| 27283 return; | |
| 27284 } | |
| 27285 | |
| 27286 _terminator._instanceTerminatorCount--; | |
| 27287 | |
| 27288 var parent = _template.parentNode; | |
| 27289 while (_previousTerminator.nextNode != _terminator) { | |
| 27290 _removeTemplateChild(parent, _previousTerminator.nextNode); | |
| 27291 } | |
| 27292 _removeTemplateChild(parent, _terminator); | |
| 27293 | |
| 27294 _terminator = _previousTerminator; | |
| 27295 _index = _previousIndex; | |
| 27296 _previousTerminator = null; | |
| 27297 _previousIndex = -1; // 0? | |
| 27298 } | |
| 27299 } | |
| 27300 | |
| 27301 | |
| 27302 class _TemplateIterator { | |
| 27303 final Element _templateElement; | |
| 27304 int instanceCount = 0; | |
| 27305 List iteratedValue; | |
| 27306 bool observing = false; | |
| 27307 final CompoundBinding inputs; | |
| 27308 | |
| 27309 StreamSubscription _sub; | |
| 27310 StreamSubscription _valueBinding; | |
| 27311 | |
| 27312 _TemplateIterator(this._templateElement) | |
| 27313 : inputs = new CompoundBinding(resolveInputs) { | |
| 27314 | |
| 27315 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); | |
| 27316 } | |
| 27317 | |
| 27318 static Object resolveInputs(Map values) { | |
| 27319 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) { | |
| 27320 return null; | |
| 27321 } | |
| 27322 | |
| 27323 if (values.containsKey('repeat')) { | |
| 27324 return values['repeat']; | |
| 27325 } | |
| 27326 | |
| 27327 if (values.containsKey('bind')) { | |
| 27328 return [values['bind']]; | |
| 27329 } | |
| 27330 | |
| 27331 return null; | |
| 27332 } | |
| 27333 | |
| 27334 void valueChanged(value) { | |
| 27335 clear(); | |
| 27336 if (value is! List) return; | |
| 27337 | |
| 27338 iteratedValue = value; | |
| 27339 | |
| 27340 if (value is Observable) { | |
| 27341 _sub = value.changes.listen(_handleChanges); | |
| 27342 } | |
| 27343 | |
| 27344 int len = iteratedValue.length; | |
| 27345 if (len > 0) { | |
| 27346 _handleChanges([new ListChangeRecord(0, addedCount: len)]); | |
| 27347 } | |
| 27348 } | |
| 27349 | |
| 27350 // TODO(jmesserly): port MDV v3. | |
| 27351 getInstanceModel(model, syntax) => model; | |
| 27352 getInstanceFragment(syntax) => _templateElement.createInstance(); | |
| 27353 | |
| 27354 void _handleChanges(List<ListChangeRecord> splices) { | |
| 27355 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']]; | |
| 27356 | |
| 27357 for (var splice in splices) { | |
| 27358 if (splice is! ListChangeRecord) continue; | |
| 27359 | |
| 27360 for (int i = 0; i < splice.removedCount; i++) { | |
| 27361 var cursor = new _InstanceCursor(_templateElement, splice.index + 1); | |
| 27362 cursor.remove(); | |
| 27363 instanceCount--; | |
| 27364 } | |
| 27365 | |
| 27366 for (var addIndex = splice.index; | |
| 27367 addIndex < splice.index + splice.addedCount; | |
| 27368 addIndex++) { | |
| 27369 | |
| 27370 var model = getInstanceModel(iteratedValue[addIndex], syntax); | |
| 27371 var fragment = getInstanceFragment(syntax); | |
| 27372 | |
| 27373 _addBindings(fragment, model, syntax); | |
| 27374 _addTemplateInstanceRecord(fragment, model); | |
| 27375 | |
| 27376 var cursor = new _InstanceCursor(_templateElement, addIndex); | |
| 27377 cursor.insert(fragment); | |
| 27378 instanceCount++; | |
| 27379 } | |
| 27380 } | |
| 27381 } | |
| 27382 | |
| 27383 void unobserve() { | |
| 27384 if (_sub == null) return; | |
| 27385 _sub.cancel(); | |
| 27386 _sub = null; | |
| 27387 } | |
| 27388 | |
| 27389 void clear() { | |
| 27390 unobserve(); | |
| 27391 | |
| 27392 iteratedValue = null; | |
| 27393 if (instanceCount == 0) return; | |
| 27394 | |
| 27395 for (var i = 0; i < instanceCount; i++) { | |
| 27396 var cursor = new _InstanceCursor(_templateElement, 1); | |
| 27397 cursor.remove(); | |
| 27398 } | |
| 27399 | |
| 27400 instanceCount = 0; | |
| 27401 } | |
| 27402 | |
| 27403 void abandon() { | |
| 27404 unobserve(); | |
| 27405 _valueBinding.cancel(); | |
| 27406 inputs.dispose(); | |
| 27407 } | |
| 27408 } | |
| 27409 | |
| 27410 int _instanceCount(Element element) { | |
| 27411 var templateIterator = element._templateIterator; | |
| 27412 return templateIterator != null ? templateIterator.instanceCount : 0; | |
| 27413 } | |
| 27414 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 27415 // for details. All rights reserved. Use of this source code is governed by a | |
| 27416 // BSD-style license that can be found in the LICENSE file. | |
| 27417 | |
| 27418 | |
| 27419 /** | 26064 /** |
| 27420 * Helper class to implement custom events which wrap DOM events. | 26065 * Helper class to implement custom events which wrap DOM events. |
| 27421 */ | 26066 */ |
| 27422 class _WrappedEvent implements Event { | 26067 class _WrappedEvent implements Event { |
| 27423 final Event wrapped; | 26068 final Event wrapped; |
| 27424 _WrappedEvent(this.wrapped); | 26069 _WrappedEvent(this.wrapped); |
| 27425 | 26070 |
| 27426 bool get bubbles => wrapped.bubbles; | 26071 bool get bubbles => wrapped.bubbles; |
| 27427 | 26072 |
| 27428 bool get cancelBubble => wrapped.bubbles; | 26073 bool get cancelBubble => wrapped.bubbles; |
| (...skipping 1225 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 28654 } | 27299 } |
| 28655 | 27300 |
| 28656 _send(msg) { | 27301 _send(msg) { |
| 28657 _sendToHelperIsolate(msg, _sendPort); | 27302 _sendToHelperIsolate(msg, _sendPort); |
| 28658 } | 27303 } |
| 28659 } | 27304 } |
| 28660 | 27305 |
| 28661 get _pureIsolateTimerFactoryClosure => | 27306 get _pureIsolateTimerFactoryClosure => |
| 28662 ((int milliSeconds, void callback(Timer time), bool repeating) => | 27307 ((int milliSeconds, void callback(Timer time), bool repeating) => |
| 28663 new _PureIsolateTimer(milliSeconds, callback, repeating)); | 27308 new _PureIsolateTimer(milliSeconds, callback, repeating)); |
| OLD | NEW |