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

Side by Side Diff: sdk/lib/html/dartium/html_dartium.dart

Issue 14908005: "Reverting 22561" (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/html/dart2js/html_dart2js.dart ('k') | sdk/lib/mdv_observe_impl/mdv_observe_impl.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /// The Dart HTML library. 1 /// The Dart HTML library.
2 library dart.dom.html; 2 library dart.dom.html;
3 3
4 import 'dart:async'; 4 import 'dart:async';
5 import 'dart:collection'; 5 import 'dart:collection';
6 import 'dart:_collection-dev' 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
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
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
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
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
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
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
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
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
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
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
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
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
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));
OLDNEW
« no previous file with comments | « sdk/lib/html/dart2js/html_dart2js.dart ('k') | sdk/lib/mdv_observe_impl/mdv_observe_impl.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698