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

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

Issue 14732003: Implement Model-Driven-Views spec for Dart (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: merged Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 /// The Dart HTML library. 1 /// The Dart HTML library.
2 library dart.dom.html; 2 library dart.dom.html;
3 3
4 import 'dart:async'; 4 import 'dart:async';
5 import 'dart:collection'; 5 import 'dart:collection';
6 import 'dart:_collection-dev'; 6 import 'dart:_collection-dev' hide Symbol;
7 import 'dart:html_common'; 7 import 'dart:html_common';
8 import 'dart:indexed_db'; 8 import 'dart:indexed_db';
9 import 'dart:isolate'; 9 import 'dart:isolate';
10 import 'dart:json' as json; 10 import 'dart:json' as json;
11 import 'dart:math'; 11 import 'dart:math';
12 import 'dart:nativewrappers'; 12 import 'dart:nativewrappers';
13 import 'dart:mdv_observe_impl';
13 import 'dart:typed_data'; 14 import 'dart:typed_data';
14 import 'dart:web_gl' as gl; 15 import 'dart:web_gl' as gl;
15 import 'dart:web_sql'; 16 import 'dart:web_sql';
16 import 'dart:svg' as svg; 17 import 'dart:svg' as svg;
17 import 'dart:web_audio' as web_audio; 18 import 'dart:web_audio' as web_audio;
18 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 19 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
19 // for details. All rights reserved. Use of this source code is governed by a 20 // for details. All rights reserved. Use of this source code is governed by a
20 // BSD-style license that can be found in the LICENSE file. 21 // BSD-style license that can be found in the LICENSE file.
21 22
22 // DO NOT EDIT 23 // DO NOT EDIT
(...skipping 7013 matching lines...) Expand 10 before | Expand all | Expand 10 after
7036 7037
7037 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or 7038 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or
7038 // SVG strings? 7039 // SVG strings?
7039 void set innerHtml(String value) { 7040 void set innerHtml(String value) {
7040 this.nodes.clear(); 7041 this.nodes.clear();
7041 7042
7042 final e = new Element.tag("div"); 7043 final e = new Element.tag("div");
7043 e.innerHtml = value; 7044 e.innerHtml = value;
7044 7045
7045 // Copy list first since we don't want liveness during iteration. 7046 // Copy list first since we don't want liveness during iteration.
7046 List nodes = new List.from(e.nodes); 7047 List nodes = new List.from(e.nodes, growable: false);
7047 this.nodes.addAll(nodes); 7048 this.append(nodes);
7048 } 7049 }
7049 7050
7050 /** 7051 /**
7051 * Adds the specified text as a text node after the last child of this 7052 * Adds the specified text as a text node after the last child of this
7052 * document fragment. 7053 * document fragment.
7053 */ 7054 */
7054 void appendText(String text) { 7055 void appendText(String text) {
7055 this.append(new Text(text)); 7056 this.append(new Text(text));
7056 } 7057 }
7057 7058
(...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after
7454 if (result == null) throw new StateError("No elements"); 7455 if (result == null) throw new StateError("No elements");
7455 return result; 7456 return result;
7456 } 7457 }
7457 7458
7458 Element get single { 7459 Element get single {
7459 if (length > 1) throw new StateError("More than one element"); 7460 if (length > 1) throw new StateError("More than one element");
7460 return first; 7461 return first;
7461 } 7462 }
7462 } 7463 }
7463 7464
7464 /** 7465 /**
7465 * An immutable list containing HTML elements. This list contains some 7466 * An immutable list containing HTML elements. This list contains some
7466 * additional methods for ease of CSS manipulation on a group of elements. 7467 * additional methods for ease of CSS manipulation on a group of elements.
7467 */ 7468 */
7468 abstract class ElementList<T extends Element> extends ListBase<T> { 7469 abstract class ElementList<T extends Element> extends ListBase<T> {
7469 /** 7470 /**
7470 * The union of all CSS classes applied to the elements in this list. 7471 * The union of all CSS classes applied to the elements in this list.
7471 * 7472 *
7472 * This set makes it easy to add, remove or toggle (add if not present, remove 7473 * This set makes it easy to add, remove or toggle (add if not present, remove
7473 * if present) the classes applied to a collection of elements. 7474 * if present) the classes applied to a collection of elements.
7474 * 7475 *
(...skipping 314 matching lines...) Expand 10 before | Expand all | Expand 10 after
7789 this.$dom_scrollIntoViewIfNeeded(true); 7790 this.$dom_scrollIntoViewIfNeeded(true);
7790 } else { 7791 } else {
7791 this.$dom_scrollIntoViewIfNeeded(); 7792 this.$dom_scrollIntoViewIfNeeded();
7792 } 7793 }
7793 } else { 7794 } else {
7794 this.$dom_scrollIntoView(); 7795 this.$dom_scrollIntoView();
7795 } 7796 }
7796 } 7797 }
7797 7798
7798 7799
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
7799 Element.internal() : super.internal(); 8045 Element.internal() : super.internal();
7800 8046
7801 @DomName('Element.abortEvent') 8047 @DomName('Element.abortEvent')
7802 @DocsEditable 8048 @DocsEditable
7803 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort'); 8049 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort');
7804 8050
7805 @DomName('Element.beforecopyEvent') 8051 @DomName('Element.beforecopyEvent')
7806 @DocsEditable 8052 @DocsEditable
7807 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy'); 8053 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy');
7808 8054
(...skipping 714 matching lines...) Expand 10 before | Expand all | Expand 10 after
8523 @DomName('Element.onwebkitfullscreenchange') 8769 @DomName('Element.onwebkitfullscreenchange')
8524 @DocsEditable 8770 @DocsEditable
8525 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); 8771 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this);
8526 8772
8527 @DomName('Element.onwebkitfullscreenerror') 8773 @DomName('Element.onwebkitfullscreenerror')
8528 @DocsEditable 8774 @DocsEditable
8529 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); 8775 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this);
8530 8776
8531 } 8777 }
8532 8778
8779
8533 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); 8780 final _START_TAG_REGEXP = new RegExp('<(\\w+)');
8534 class _ElementFactoryProvider { 8781 class _ElementFactoryProvider {
8535 static const _CUSTOM_PARENT_TAG_MAP = const { 8782 static const _CUSTOM_PARENT_TAG_MAP = const {
8536 'body' : 'html', 8783 'body' : 'html',
8537 'head' : 'html', 8784 'head' : 'html',
8538 'caption' : 'table', 8785 'caption' : 'table',
8539 'td': 'tr', 8786 'td': 'tr',
8540 'th': 'tr', 8787 'th': 'tr',
8541 'colgroup': 'table', 8788 'colgroup': 'table',
8542 'col' : 'colgroup', 8789 'col' : 'colgroup',
8543 'tr' : 'tbody', 8790 'tr' : 'tbody',
8544 'tbody' : 'table', 8791 'tbody' : 'table',
8545 'tfoot' : 'table', 8792 'tfoot' : 'table',
8546 'thead' : 'table', 8793 'thead' : 'table',
8547 'track' : 'audio', 8794 'track' : 'audio',
8548 }; 8795 };
8549 8796
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
8563 @DomName('Document.createElement') 8797 @DomName('Document.createElement')
8564 static Element createElement_html(String html) { 8798 static Element createElement_html(String html) {
8565 // TODO(jacobr): this method can be made more robust and performant. 8799 // TODO(jacobr): this method can be made more robust and performant.
8566 // 1) Cache the dummy parent elements required to use innerHTML rather than 8800 // 1) Cache the dummy parent elements required to use innerHTML rather than
8567 // creating them every call. 8801 // creating them every call.
8568 // 2) Verify that the html does not contain leading or trailing text nodes. 8802 // 2) Verify that the html does not contain leading or trailing text nodes.
8569 // 3) Verify that the html does not contain both <head> and <body> tags. 8803 // 3) Verify that the html does not contain both <head> and <body> tags.
8570 // 4) Detatch the created element from its dummy parent. 8804 // 4) Detatch the created element from its dummy parent.
8571 String parentTag = 'div'; 8805 String parentTag = 'div';
8572 String tag; 8806 String tag;
8573 final match = _START_TAG_REGEXP.firstMatch(html); 8807 final match = _START_TAG_REGEXP.firstMatch(html);
8574 if (match != null) { 8808 if (match != null) {
8575 tag = match.group(1).toLowerCase(); 8809 tag = match.group(1).toLowerCase();
8576 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { 8810 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) {
8577 return _createTableForIE(html, tag); 8811 return _createTableForIE(html, tag);
8578 } 8812 }
8579 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; 8813 parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
8580 if (parentTag == null) parentTag = 'div'; 8814 if (parentTag == null) parentTag = 'div';
8581 } 8815 }
8582 8816
8583 final temp = new Element.tag(parentTag); 8817 final temp = new Element.tag(parentTag);
8584 temp.innerHtml = html; 8818 temp.innerHtml = html;
8585 8819
8586 Element element; 8820 Element element;
(...skipping 2015 matching lines...) Expand 10 before | Expand all | Expand 10 after
10602 @SupportedBrowser(SupportedBrowser.SAFARI) 10836 @SupportedBrowser(SupportedBrowser.SAFARI)
10603 @Experimental 10837 @Experimental
10604 Element get pointerLockElement => 10838 Element get pointerLockElement =>
10605 $dom_webkitPointerLockElement; 10839 $dom_webkitPointerLockElement;
10606 10840
10607 @DomName('Document.webkitVisibilityState') 10841 @DomName('Document.webkitVisibilityState')
10608 @SupportedBrowser(SupportedBrowser.CHROME) 10842 @SupportedBrowser(SupportedBrowser.CHROME)
10609 @SupportedBrowser(SupportedBrowser.SAFARI) 10843 @SupportedBrowser(SupportedBrowser.SAFARI)
10610 @Experimental 10844 @Experimental
10611 String get visibilityState => $dom_webkitVisibilityState; 10845 String get visibilityState => $dom_webkitVisibilityState;
10846
10847
10848 // Note: used to polyfill <template>
10849 Document _templateContentsOwner;
10612 } 10850 }
10613 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 10851 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
10614 // for details. All rights reserved. Use of this source code is governed by a 10852 // for details. All rights reserved. Use of this source code is governed by a
10615 // BSD-style license that can be found in the LICENSE file. 10853 // BSD-style license that can be found in the LICENSE file.
10616 10854
10617 // WARNING: Do not edit - generated code. 10855 // WARNING: Do not edit - generated code.
10618 10856
10619 10857
10620 @DocsEditable 10858 @DocsEditable
10621 @DomName('HTMLHtmlElement') 10859 @DomName('HTMLHtmlElement')
(...skipping 920 matching lines...) Expand 10 before | Expand all | Expand 10 after
11542 factory InputElement({String type}) { 11780 factory InputElement({String type}) {
11543 var e = document.$dom_createElement("input"); 11781 var e = document.$dom_createElement("input");
11544 if (type != null) { 11782 if (type != null) {
11545 try { 11783 try {
11546 // IE throws an exception for unknown types. 11784 // IE throws an exception for unknown types.
11547 e.type = type; 11785 e.type = type;
11548 } catch(_) {} 11786 } catch(_) {}
11549 } 11787 }
11550 return e; 11788 return e;
11551 } 11789 }
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
11552 InputElement.internal() : super.internal(); 11844 InputElement.internal() : super.internal();
11553 11845
11554 @DomName('HTMLInputElement.webkitSpeechChangeEvent') 11846 @DomName('HTMLInputElement.webkitSpeechChangeEvent')
11555 @DocsEditable 11847 @DocsEditable
11556 @SupportedBrowser(SupportedBrowser.CHROME) 11848 @SupportedBrowser(SupportedBrowser.CHROME)
11557 @SupportedBrowser(SupportedBrowser.SAFARI) 11849 @SupportedBrowser(SupportedBrowser.SAFARI)
11558 @Experimental 11850 @Experimental
11559 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange'); 11851 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange');
11560 11852
11561 @DomName('HTMLInputElement.accept') 11853 @DomName('HTMLInputElement.accept')
(...skipping 3710 matching lines...) Expand 10 before | Expand all | Expand 10 after
15272 _this.$dom_removeChild(node); 15564 _this.$dom_removeChild(node);
15273 return true; 15565 return true;
15274 } 15566 }
15275 15567
15276 void _filter(bool test(Node node), bool removeMatching) { 15568 void _filter(bool test(Node node), bool removeMatching) {
15277 // This implementation of removeWhere/retainWhere is more efficient 15569 // This implementation of removeWhere/retainWhere is more efficient
15278 // than the default in ListBase. Child nodes can be removed in constant 15570 // than the default in ListBase. Child nodes can be removed in constant
15279 // time. 15571 // time.
15280 Node child = _this.$dom_firstChild; 15572 Node child = _this.$dom_firstChild;
15281 while (child != null) { 15573 while (child != null) {
15282 Node nextChild = child.nextSibling; 15574 Node nextChild = child.nextNode;
15283 if (test(child) == removeMatching) { 15575 if (test(child) == removeMatching) {
15284 _this.$dom_removeChild(child); 15576 _this.$dom_removeChild(child);
15285 } 15577 }
15286 child = nextChild; 15578 child = nextChild;
15287 } 15579 }
15288 } 15580 }
15289 15581
15290 void removeWhere(bool test(Node node)) { 15582 void removeWhere(bool test(Node node)) {
15291 _filter(test, true); 15583 _filter(test, true);
15292 } 15584 }
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
15397 // Should use $dom_firstChild, Bug 8886. 15689 // Should use $dom_firstChild, Bug 8886.
15398 this.insertBefore(newNodes[0], refChild); 15690 this.insertBefore(newNodes[0], refChild);
15399 } 15691 }
15400 } else { 15692 } else {
15401 for (var node in newNodes) { 15693 for (var node in newNodes) {
15402 this.insertBefore(node, refChild); 15694 this.insertBefore(node, refChild);
15403 } 15695 }
15404 } 15696 }
15405 } 15697 }
15406 15698
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
15489 /** 15699 /**
15490 * Print out a String representation of this Node. 15700 * Print out a String representation of this Node.
15491 */ 15701 */
15492 String toString() => localName == null ? 15702 String toString() => localName == null ?
15493 (nodeValue == null ? super.toString() : nodeValue) : localName; 15703 (nodeValue == null ? super.toString() : nodeValue) : localName;
15494 15704
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
15495 Node.internal() : super.internal(); 15743 Node.internal() : super.internal();
15496 15744
15497 static const int ATTRIBUTE_NODE = 2; 15745 static const int ATTRIBUTE_NODE = 2;
15498 15746
15499 static const int CDATA_SECTION_NODE = 4; 15747 static const int CDATA_SECTION_NODE = 4;
15500 15748
15501 static const int COMMENT_NODE = 8; 15749 static const int COMMENT_NODE = 8;
15502 15750
15503 static const int DOCUMENT_FRAGMENT_NODE = 11; 15751 static const int DOCUMENT_FRAGMENT_NODE = 11;
15504 15752
(...skipping 4308 matching lines...) Expand 10 before | Expand all | Expand 10 after
19813 @DocsEditable 20061 @DocsEditable
19814 Element $dom_insertRow(int index) native "HTMLTableSectionElement_insertRow_Ca llback"; 20062 Element $dom_insertRow(int index) native "HTMLTableSectionElement_insertRow_Ca llback";
19815 } 20063 }
19816 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20064 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
19817 // for details. All rights reserved. Use of this source code is governed by a 20065 // for details. All rights reserved. Use of this source code is governed by a
19818 // BSD-style license that can be found in the LICENSE file. 20066 // BSD-style license that can be found in the LICENSE file.
19819 20067
19820 // WARNING: Do not edit - generated code. 20068 // WARNING: Do not edit - generated code.
19821 20069
19822 20070
19823 @DocsEditable 20071 @Experimental
19824 @DomName('HTMLTemplateElement') 20072 @DomName('HTMLTemplateElement')
19825 @SupportedBrowser(SupportedBrowser.CHROME) 20073 @SupportedBrowser(SupportedBrowser.CHROME)
19826 @Experimental 20074 @Experimental
19827 class TemplateElement extends _Element_Merged { 20075 class TemplateElement extends _Element_Merged {
19828 TemplateElement.internal() : super.internal(); 20076 TemplateElement.internal() : super.internal();
19829 20077
19830 @DomName('HTMLTemplateElement.HTMLTemplateElement') 20078 @DomName('HTMLTemplateElement.HTMLTemplateElement')
19831 @DocsEditable 20079 @DocsEditable
19832 factory TemplateElement() => document.$dom_createElement("template"); 20080 factory TemplateElement() => document.$dom_createElement("template");
19833 20081
19834 /// Checks if this type is supported on the current platform. 20082 /// Checks if this type is supported on the current platform.
19835 static bool get supported => true; 20083 static bool get supported => true;
19836 20084
19837 @DomName('HTMLTemplateElement.content') 20085 @DomName('HTMLTemplateElement.content')
19838 @DocsEditable 20086 @DocsEditable
19839 DocumentFragment get content native "HTMLTemplateElement_content_Getter"; 20087 DocumentFragment get $dom_content native "HTMLTemplateElement_content_Getter";
19840 20088
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 = {};
19841 } 20196 }
19842 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20197 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
19843 // for details. All rights reserved. Use of this source code is governed by a 20198 // for details. All rights reserved. Use of this source code is governed by a
19844 // BSD-style license that can be found in the LICENSE file. 20199 // BSD-style license that can be found in the LICENSE file.
19845 20200
19846 // WARNING: Do not edit - generated code. 20201 // WARNING: Do not edit - generated code.
19847 20202
19848 20203
19849 @DomName('Text') 20204 @DomName('Text')
19850 class Text extends CharacterData { 20205 class Text extends CharacterData {
(...skipping 12 matching lines...) Expand all
19863 String get wholeText native "Text_wholeText_Getter"; 20218 String get wholeText native "Text_wholeText_Getter";
19864 20219
19865 @DomName('Text.replaceWholeText') 20220 @DomName('Text.replaceWholeText')
19866 @DocsEditable 20221 @DocsEditable
19867 Text replaceWholeText(String content) native "Text_replaceWholeText_Callback"; 20222 Text replaceWholeText(String content) native "Text_replaceWholeText_Callback";
19868 20223
19869 @DomName('Text.splitText') 20224 @DomName('Text.splitText')
19870 @DocsEditable 20225 @DocsEditable
19871 Text splitText(int offset) native "Text_splitText_Callback"; 20226 Text splitText(int offset) native "Text_splitText_Callback";
19872 20227
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 }
19873 } 20263 }
19874 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20264 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
19875 // for details. All rights reserved. Use of this source code is governed by a 20265 // for details. All rights reserved. Use of this source code is governed by a
19876 // BSD-style license that can be found in the LICENSE file. 20266 // BSD-style license that can be found in the LICENSE file.
19877 20267
19878 // WARNING: Do not edit - generated code. 20268 // WARNING: Do not edit - generated code.
19879 20269
19880 20270
19881 @DocsEditable 20271 @DocsEditable
19882 @DomName('HTMLTextAreaElement') 20272 @DomName('HTMLTextAreaElement')
(...skipping 5853 matching lines...) Expand 10 before | Expand all | Expand 10 after
25736 * Key value used when an implementation is unable to identify another key 26126 * Key value used when an implementation is unable to identify another key
25737 * value, due to either hardware, platform, or software constraints 26127 * value, due to either hardware, platform, or software constraints
25738 */ 26128 */
25739 static const String UNIDENTIFIED = "Unidentified"; 26129 static const String UNIDENTIFIED = "Unidentified";
25740 } 26130 }
25741 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 26131 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
25742 // for details. All rights reserved. Use of this source code is governed by a 26132 // for details. All rights reserved. Use of this source code is governed by a
25743 // BSD-style license that can be found in the LICENSE file. 26133 // BSD-style license that can be found in the LICENSE file.
25744 26134
25745 26135
25746 class _ModelTreeObserver { 26136 // This code is inspired by ChangeSummary:
25747 static bool _initialized = false; 26137 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
26138 // ...which underlies MDV. Since we don't need the functionality of
26139 // ChangeSummary, we just implement what we need for data bindings.
26140 // This allows our implementation to be much simpler.
26141
26142 // TODO(jmesserly): should we make these types stronger, and require
26143 // Observable objects? Currently, it is fine to say something like:
26144 // var path = new PathObserver(123, '');
26145 // print(path.value); // "123"
26146 //
26147 // Furthermore this degenerate case is allowed:
26148 // var path = new PathObserver(123, 'foo.bar.baz.qux');
26149 // print(path.value); // "null"
26150 //
26151 // Here we see that any invalid (i.e. not Observable) value will break the
26152 // path chain without producing an error or exception.
26153 //
26154 // Now the real question: should we do this? For the former case, the behavior
26155 // is correct but we could chose to handle it in the dart:html bindings layer.
26156 // For the latter case, it might be better to throw an error so users can find
26157 // the problem.
26158
26159
26160 // TODO(jmesserly): the primary reason to have this object exposed is because
26161 // we have get/set for value. Ideally "observePath" could just return the
26162 // stream.
26163 /**
26164 * A data-bound path starting from a view-model or model object, for example
26165 * `foo.bar.baz`.
26166 *
26167 * When the [values] stream is being listened to, this will observe changes to
26168 * the object and any intermediate object along the path, and send [values]
26169 * accordingly. When all listeners are unregistered it will stop observing
26170 * the objects.
26171 *
26172 * This class is used to implement [Node.bind] and similar functionality.
26173 */
26174 @Experimental
26175 class PathObserver {
26176 /** The object being observed. */
26177 final object;
26178
26179 /** The path string. */
26180 final String path;
26181
26182 /** True if the path is valid, otherwise false. */
26183 final bool _isValid;
26184
26185 // TODO(jmesserly): same issue here as ObservableMixin: is there an easier
26186 // way to get a broadcast stream?
26187 StreamController _values;
26188 Stream _valueStream;
26189
26190 _PropertyObserver _observer, _lastObserver;
26191
26192 Object _lastValue;
26193 bool _scheduled = false;
25748 26194
25749 /** 26195 /**
25750 * Start an observer watching the document for tree changes to automatically 26196 * Observes [path] on [object] for changes. This returns an object that can be
25751 * propagate model changes. 26197 * used to get the changes and get/set the value at this path.
25752 * 26198 * See [PathObserver.values] and [PathObserver.value].
25753 * Currently this does not support propagation through Shadow DOMs.
25754 */ 26199 */
25755 static void initialize() { 26200 PathObserver(this.object, String path)
25756 if (!_initialized) { 26201 : path = path,
25757 _initialized = true; 26202 _isValid = _isPathValid(path) {
25758 26203
25759 if (MutationObserver.supported) { 26204 // TODO(jmesserly): if the path is empty, or the object is! Observable, we
25760 var observer = new MutationObserver(_processTreeChange); 26205 // can optimize the PathObserver to be more lightweight.
25761 observer.observe(document, childList: true, subtree: true); 26206
25762 } else { 26207 _values = new StreamController(onListen: _observe, onCancel: _unobserve);
25763 document.on['DOMNodeInserted'].listen(_handleNodeInserted); 26208
25764 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved); 26209 if (_isValid) {
26210 var segments = [];
26211 for (var segment in path.trim().split('.')) {
26212 if (segment == '') continue;
26213 var index = int.parse(segment, onError: (_) {});
26214 segments.add(index != null ? index : new Symbol(segment));
25765 } 26215 }
25766 } 26216
25767 } 26217 // Create the property observer linked list.
25768 26218 // Note that the structure of a path can't change after it is initially
25769 static void _processTreeChange(List<MutationRecord> mutations, 26219 // constructed, even though the objects along the path can change.
25770 MutationObserver observer) { 26220 for (int i = segments.length - 1; i >= 0; i--) {
25771 for (var record in mutations) { 26221 _observer = new _PropertyObserver(this, segments[i], _observer);
25772 for (var node in record.addedNodes) { 26222 if (_lastObserver == null) _lastObserver = _observer;
25773 // When nodes enter the document we need to make sure that all of the
25774 // models are properly propagated through the entire sub-tree.
25775 propagateModel(node, _calculatedModel(node), true);
25776 } 26223 }
25777 for (var node in record.removedNodes) { 26224 }
25778 propagateModel(node, _calculatedModel(node), false); 26225 }
26226
26227 // TODO(jmesserly): we could try adding the first value to the stream, but
26228 // that delivers the first record async.
26229 /**
26230 * Listens to the stream, and invokes the [callback] immediately with the
26231 * current [value]. This is useful for bindings, which want to be up-to-date
26232 * immediately.
26233 */
26234 StreamSubscription bindSync(void callback(value)) {
26235 var result = values.listen(callback);
26236 callback(value);
26237 return result;
26238 }
26239
26240 // TODO(jmesserly): should this be a change record with the old value?
26241 // TODO(jmesserly): should this be a broadcast stream? We only need
26242 // single-subscription in the bindings system, so single sub saves overhead.
26243 /**
26244 * Gets the stream of values that were observed at this path.
26245 * This returns a single-subscription stream.
26246 */
26247 Stream get values => _values.stream;
26248
26249 /** Force synchronous delivery of [values]. */
26250 void _deliverValues() {
26251 _scheduled = false;
26252
26253 var newValue = value;
26254 if (!identical(_lastValue, newValue)) {
26255 _values.add(newValue);
26256 _lastValue = newValue;
26257 }
26258 }
26259
26260 void _observe() {
26261 if (_observer != null) {
26262 _lastValue = value;
26263 _observer.observe();
26264 }
26265 }
26266
26267 void _unobserve() {
26268 if (_observer != null) _observer.unobserve();
26269 }
26270
26271 void _notifyChange() {
26272 if (_scheduled) return;
26273 _scheduled = true;
26274
26275 // TODO(jmesserly): should we have a guarenteed order with respect to other
26276 // paths? If so, we could implement this fairly easily by sorting instances
26277 // of this class by birth order before delivery.
26278 queueChangeRecords(_deliverValues);
26279 }
26280
26281 /** Gets the last reported value at this path. */
26282 get value {
26283 if (!_isValid) return null;
26284 if (_observer == null) return object;
26285 _observer.ensureValue(object);
26286 return _lastObserver.value;
26287 }
26288
26289 /** Sets the value at this path. */
26290 void set value(Object value) {
26291 // TODO(jmesserly): throw if property cannot be set?
26292 // MDV seems tolerant of these error.
26293 if (_observer == null || !_isValid) return;
26294 _observer.ensureValue(object);
26295 var last = _lastObserver;
26296 if (_setObjectProperty(last._object, last._property, value)) {
26297 // Technically, this would get updated asynchronously via a change record.
26298 // However, it is nice if calling the getter will yield the same value
26299 // that was just set. So we use this opportunity to update our cache.
26300 last.value = value;
26301 }
26302 }
26303 }
26304
26305 // TODO(jmesserly): these should go away in favor of mirrors!
26306 _getObjectProperty(object, property) {
26307 if (object is List && property is int) {
26308 if (property >= 0 && property < object.length) {
26309 return object[property];
26310 } else {
26311 return null;
26312 }
26313 }
26314
26315 // TODO(jmesserly): what about length?
26316 if (object is Map) return object[property];
26317
26318 if (object is Observable) return object.getValueWorkaround(property);
26319
26320 return null;
26321 }
26322
26323 bool _setObjectProperty(object, property, value) {
26324 if (object is List && property is int) {
26325 object[property] = value;
26326 } else if (object is Map) {
26327 object[property] = value;
26328 } else if (object is Observable) {
26329 (object as Observable).setValueWorkaround(property, value);
26330 } else {
26331 return false;
26332 }
26333 return true;
26334 }
26335
26336
26337 class _PropertyObserver {
26338 final PathObserver _path;
26339 final _property;
26340 final Symbol _symbol;
26341 final _PropertyObserver _next;
26342
26343 // TODO(jmesserly): would be nice not to store both of these.
26344 Object _object;
26345 Object _value;
26346 StreamSubscription _sub;
26347
26348 _PropertyObserver(this._path, this._property, this._next);
26349
26350 get value => _value;
26351
26352 void set value(Object newValue) {
26353 _value = newValue;
26354 if (_next != null) {
26355 if (_sub != null) _next.unobserve();
26356 _next.ensureValue(_value);
26357 if (_sub != null) _next.observe();
26358 }
26359 }
26360
26361 void ensureValue(object) {
26362 // If we're observing, values should be up to date already.
26363 if (_sub != null) return;
26364
26365 _object = object;
26366 value = _getObjectProperty(object, _property);
26367 }
26368
26369 void observe() {
26370 if (_object is Observable) {
26371 assert(_sub == null);
26372 _sub = (_object as Observable).changes.listen(_onChange);
26373 }
26374 if (_next != null) _next.observe();
26375 }
26376
26377 void unobserve() {
26378 if (_sub == null) return;
26379
26380 _sub.cancel();
26381 _sub = null;
26382 if (_next != null) _next.unobserve();
26383 }
26384
26385 void _onChange(List<ChangeRecord> changes) {
26386 for (var change in changes) {
26387 // TODO(jmesserly): what to do about "new Symbol" here?
26388 // Ideally this would only preserve names if the user has opted in to
26389 // them being preserved.
26390 // TODO(jmesserly): should we drop observable maps with String keys?
26391 // If so then we only need one check here.
26392 if (change.changes(_property)) {
26393 value = _getObjectProperty(_object, _property);
26394 _path._notifyChange();
26395 return;
25779 } 26396 }
25780 } 26397 }
25781 } 26398 }
25782 26399 }
25783 static void _handleNodeInserted(MutationEvent e) { 26400
25784 var node = e.target; 26401 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
25785 window.setImmediate(() { 26402
25786 propagateModel(node, _calculatedModel(node), true); 26403 const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*';
25787 }); 26404 final _pathRegExp = new RegExp('^'
25788 } 26405 '(?:#?' + _pathIndentPart + ')?'
25789 26406 '(?:'
25790 static void _handleNodeRemoved(MutationEvent e) { 26407 '(?:\\.' + _pathIndentPart + ')'
25791 var node = e.target; 26408 ')*'
25792 window.setImmediate(() { 26409 r'$', caseSensitive: false);
25793 propagateModel(node, _calculatedModel(node), false); 26410
25794 }); 26411 final _spacesRegExp = new RegExp(r'\s');
25795 } 26412
25796 26413 bool _isPathValid(String s) {
25797 /** 26414 s = s.replaceAll(_spacesRegExp, '');
25798 * Figures out what the model should be for a node, avoiding any cached 26415
25799 * model values. 26416 if (s == '') return true;
25800 */ 26417 if (s[0] == '.') return false;
25801 static _calculatedModel(node) { 26418 return _pathRegExp.hasMatch(s);
25802 if (node._hasLocalModel == true) {
25803 return node._model;
25804 } else if (node.parentNode != null) {
25805 return node.parentNode._model;
25806 }
25807 return null;
25808 }
25809
25810 /**
25811 * Pushes model changes down through the tree.
25812 *
25813 * Set fullTree to true if the state of the tree is unknown and model changes
25814 * should be propagated through the entire tree.
25815 */
25816 static void propagateModel(Node node, model, bool fullTree) {
25817 // Calling into user code with the != call could generate exceptions.
25818 // Catch and report them a global exceptions.
25819 try {
25820 if (node._hasLocalModel != true && node._model != model &&
25821 node._modelChangedStreams != null &&
25822 !node._modelChangedStreams.isEmpty) {
25823 node._model = model;
25824 node._modelChangedStreams.toList()
25825 .forEach((controller) => controller.add(node));
25826 }
25827 } catch (e, s) {
25828 new Future.error(e, s);
25829 }
25830 for (var child = node.$dom_firstChild; child != null;
25831 child = child.nextNode) {
25832 if (child._hasLocalModel != true) {
25833 propagateModel(child, model, fullTree);
25834 } else if (fullTree) {
25835 propagateModel(child, child._model, true);
25836 }
25837 }
25838 }
25839 } 26419 }
25840 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 26420 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
25841 // for details. All rights reserved. Use of this source code is governed by a 26421 // for details. All rights reserved. Use of this source code is governed by a
25842 // BSD-style license that can be found in the LICENSE file. 26422 // BSD-style license that can be found in the LICENSE file.
25843 26423
25844 26424
25845 /** 26425 /**
25846 * A utility class for representing two-dimensional positions. 26426 * A utility class for representing two-dimensional positions.
25847 */ 26427 */
25848 class Point { 26428 class Point {
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after
26057 26637
26058 Point get topLeft => new Point(this.left, this.top); 26638 Point get topLeft => new Point(this.left, this.top);
26059 Point get bottomRight => new Point(this.left + this.width, 26639 Point get bottomRight => new Point(this.left + this.width,
26060 this.top + this.height); 26640 this.top + this.height);
26061 } 26641 }
26062 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 26642 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
26063 // for details. All rights reserved. Use of this source code is governed by a 26643 // for details. All rights reserved. Use of this source code is governed by a
26064 // BSD-style license that can be found in the LICENSE file. 26644 // BSD-style license that can be found in the LICENSE file.
26065 26645
26066 26646
26647 // This code is a port of Model-Driven-Views:
26648 // https://github.com/toolkitchen/mdv
26649 // The code mostly comes from src/template_element.js
26650
26651 typedef void _ChangeHandler(value);
26652
26653 /**
26654 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
26655 * but (by design) don't attempt to implement a wide array of specialized
26656 * behaviors.
26657 *
26658 * Enabling these features in MDV is a matter of implementing and registering an
26659 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more
26660 * delegation functions which implement specialized behavior. This object is
26661 * registered with MDV via [TemplateElement.syntax]:
26662 *
26663 *
26664 * HTML:
26665 * <template bind syntax="MySyntax">
26666 * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
26667 * </template>
26668 *
26669 * Dart:
26670 * class MySyntax extends CustomBindingSyntax {
26671 * getBinding(model, path, name, node) {
26672 * // The magic happens here!
26673 * }
26674 * }
26675 *
26676 * ...
26677 *
26678 * TemplateElement.syntax['MySyntax'] = new MySyntax();
26679 *
26680 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more
26681 * information about Custom Syntax.
26682 */
26683 // TODO(jmesserly): if this is just one method, a function type would make it
26684 // more Dart-friendly.
26685 @Experimental
26686 abstract class CustomBindingSyntax {
26687 // TODO(jmesserly): I had to remove type annotations from "name" and "node"
26688 // Normally they are String and Node respectively. But sometimes it will pass
26689 // (int name, CompoundBinding node). That seems very confusing; we may want
26690 // to change this API.
26691 getBinding(model, String path, name, node);
26692 }
26693
26694 /** The callback used in the [CompoundBinding.combinator] field. */
26695 @Experimental
26696 typedef Object CompoundBindingCombinator(Map objects);
26697
26698 /** Information about the instantiated template. */
26699 @Experimental
26700 class TemplateInstance {
26701 // TODO(rafaelw): firstNode & lastNode should be read-synchronous
26702 // in cases where script has modified the template instance boundary.
26703
26704 /** The first node of this template instantiation. */
26705 final Node firstNode;
26706
26707 /**
26708 * The last node of this template instantiation.
26709 * This could be identical to [firstNode] if the template only expanded to a
26710 * single node.
26711 */
26712 final Node lastNode;
26713
26714 /** The model used to instantiate the template. */
26715 final model;
26716
26717 TemplateInstance(this.firstNode, this.lastNode, this.model);
26718 }
26719
26720 /**
26721 * Model-Driven Views contains a helper object which is useful for the
26722 * implementation of a Custom Syntax.
26723 *
26724 * var binding = new CompoundBinding((values) {
26725 * var combinedValue;
26726 * // compute combinedValue based on the current values which are provided
26727 * return combinedValue;
26728 * });
26729 * binding.bind('name1', obj1, path1);
26730 * binding.bind('name2', obj2, path2);
26731 * //...
26732 * binding.bind('nameN', objN, pathN);
26733 *
26734 * CompoundBinding is an object which knows how to listen to multiple path
26735 * values (registered via [bind]) and invoke its [combinator] when one or more
26736 * of the values have changed and set its [value] property to the return value
26737 * of the function. When any value has changed, all current values are provided
26738 * to the [combinator] in the single `values` argument.
26739 *
26740 * See [CustomBindingSyntax] for more information.
26741 */
26742 // TODO(jmesserly): what is the public API surface here? I just guessed;
26743 // most of it seemed non-public.
26744 @Experimental
26745 class CompoundBinding extends ObservableBase {
26746 CompoundBindingCombinator _combinator;
26747
26748 // TODO(jmesserly): ideally these would be String keys, but sometimes we
26749 // use integers.
26750 Map<dynamic, StreamSubscription> _bindings = new Map();
26751 Map _values = new Map();
26752 bool _scheduled = false;
26753 bool _disposed = false;
26754 Object _value;
26755
26756 CompoundBinding([CompoundBindingCombinator combinator]) {
26757 // TODO(jmesserly): this is a tweak to the original code, it seemed to me
26758 // that passing the combinator to the constructor should be equivalent to
26759 // setting it via the property.
26760 // I also added a null check to the combinator setter.
26761 this.combinator = combinator;
26762 }
26763
26764 CompoundBindingCombinator get combinator => _combinator;
26765
26766 set combinator(CompoundBindingCombinator combinator) {
26767 _combinator = combinator;
26768 if (combinator != null) _scheduleResolve();
26769 }
26770
26771 static const _VALUE = const Symbol('value');
26772
26773 get value => _value;
26774
26775 void set value(newValue) {
26776 _value = notifyPropertyChange(_VALUE, _value, newValue);
26777 }
26778
26779 // TODO(jmesserly): remove these workarounds when dart2js supports mirrors!
26780 getValueWorkaround(key) {
26781 if (key == _VALUE) return value;
26782 return null;
26783 }
26784 setValueWorkaround(key, val) {
26785 if (key == _VALUE) value = val;
26786 }
26787
26788 void bind(name, model, String path) {
26789 unbind(name);
26790
26791 _bindings[name] = new PathObserver(model, path).bindSync((value) {
26792 _values[name] = value;
26793 _scheduleResolve();
26794 });
26795 }
26796
26797 void unbind(name, {bool suppressResolve: false}) {
26798 var binding = _bindings.remove(name);
26799 if (binding == null) return;
26800
26801 binding.cancel();
26802 _values.remove(name);
26803 if (!suppressResolve) _scheduleResolve();
26804 }
26805
26806 // TODO(rafaelw): Is this the right processing model?
26807 // TODO(rafaelw): Consider having a seperate ChangeSummary for
26808 // CompoundBindings so to excess dirtyChecks.
26809 void _scheduleResolve() {
26810 if (_scheduled) return;
26811 _scheduled = true;
26812 queueChangeRecords(resolve);
26813 }
26814
26815 void resolve() {
26816 if (_disposed) return;
26817 _scheduled = false;
26818
26819 if (_combinator == null) {
26820 throw new StateError(
26821 'CompoundBinding attempted to resolve without a combinator');
26822 }
26823
26824 value = _combinator(_values);
26825 }
26826
26827 void dispose() {
26828 for (var binding in _bindings.values) {
26829 binding.cancel();
26830 }
26831 _bindings.clear();
26832 _values.clear();
26833
26834 _disposed = true;
26835 value = null;
26836 }
26837 }
26838
26839 Stream<Event> _getStreamForInputType(InputElement element) {
26840 switch (element.type) {
26841 case 'checkbox':
26842 return element.onClick;
26843 case 'radio':
26844 case 'select-multiple':
26845 case 'select-one':
26846 return element.onChange;
26847 default:
26848 return element.onInput;
26849 }
26850 }
26851
26852 abstract class _InputBinding {
26853 final InputElement element;
26854 PathObserver binding;
26855 StreamSubscription _pathSub;
26856 StreamSubscription _eventSub;
26857
26858 _InputBinding(this.element, model, String path) {
26859 binding = new PathObserver(model, path);
26860 _pathSub = binding.bindSync(valueChanged);
26861 _eventSub = _getStreamForInputType(element).listen(updateBinding);
26862 }
26863
26864 void valueChanged(newValue);
26865
26866 void updateBinding(e);
26867
26868 void unbind() {
26869 binding = null;
26870 _pathSub.cancel();
26871 _eventSub.cancel();
26872 }
26873 }
26874
26875 class _ValueBinding extends _InputBinding {
26876 _ValueBinding(element, model, path) : super(element, model, path);
26877
26878 void valueChanged(value) {
26879 element.value = value == null ? '' : '$value';
26880 }
26881
26882 void updateBinding(e) {
26883 binding.value = element.value;
26884 }
26885 }
26886
26887 // TODO(jmesserly): not sure what kind of boolean conversion rules to
26888 // apply for template data-binding. HTML attributes are true if they're present.
26889 // However Dart only treats "true" as true. Since this is HTML we'll use
26890 // something closer to the HTML rules: null (missing) and false are false,
26891 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59
26892 bool _templateBooleanConversion(value) => null != value && false != value;
26893
26894 class _CheckedBinding extends _InputBinding {
26895 _CheckedBinding(element, model, path) : super(element, model, path);
26896
26897 void valueChanged(value) {
26898 element.checked = _templateBooleanConversion(value);
26899 }
26900
26901 void updateBinding(e) {
26902 binding.value = element.checked;
26903
26904 // Only the radio button that is getting checked gets an event. We
26905 // therefore find all the associated radio buttons and update their
26906 // CheckedBinding manually.
26907 if (element is InputElement && element.type == 'radio') {
26908 for (var r in _getAssociatedRadioButtons(element)) {
26909 var checkedBinding = r._checkedBinding;
26910 if (checkedBinding != null) {
26911 // Set the value directly to avoid an infinite call stack.
26912 checkedBinding.binding.value = false;
26913 }
26914 }
26915 }
26916 }
26917 }
26918
26919 // TODO(jmesserly): polyfill document.contains API instead of doing it here
26920 bool _isNodeInDocument(Node node) {
26921 // On non-IE this works:
26922 // return node.document.contains(node);
26923 var document = node.document;
26924 if (node == document || node.parentNode == document) return true;
26925 return document.documentElement.contains(node);
26926 }
26927
26928 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
26929 // Returns an array containing all radio buttons other than |element| that
26930 // have the same |name|, either in the form that |element| belongs to or,
26931 // if no form, in the document tree to which |element| belongs.
26932 //
26933 // This implementation is based upon the HTML spec definition of a
26934 // "radio button group":
26935 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht ml#radio-button-group
26936 //
26937 Iterable _getAssociatedRadioButtons(element) {
26938 if (!_isNodeInDocument(element)) return [];
26939 if (element.form != null) {
26940 return element.form.nodes.where((el) {
26941 return el != element &&
26942 el is InputElement &&
26943 el.type == 'radio' &&
26944 el.name == element.name;
26945 });
26946 } else {
26947 var radios = element.document.queryAll(
26948 'input[type="radio"][name="${element.name}"]');
26949 return radios.where((el) => el != element && el.form == null);
26950 }
26951 }
26952
26953 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
26954 var clone = node.clone(false); // Shallow clone.
26955 if (clone is Element && clone.isTemplate) {
26956 TemplateElement.decorate(clone, node);
26957 if (syntax != null) {
26958 clone.attributes.putIfAbsent('syntax', () => syntax);
26959 }
26960 }
26961
26962 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
26963 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax));
26964 }
26965 return clone;
26966 }
26967
26968 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df n-template-contents-owner
26969 Document _getTemplateContentsOwner(Document doc) {
26970 if (doc.window == null) {
26971 return doc;
26972 }
26973 var d = doc._templateContentsOwner;
26974 if (d == null) {
26975 // TODO(arv): This should either be a Document or HTMLDocument depending
26976 // on doc.
26977 d = doc.implementation.createHtmlDocument('');
26978 while (d.$dom_lastChild != null) {
26979 d.$dom_lastChild.remove();
26980 }
26981 doc._templateContentsOwner = d;
26982 }
26983 return d;
26984 }
26985
26986 Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
26987 var clone = templateElement.clone(false);
26988 var attributes = templateElement.attributes;
26989 for (var name in attributes.keys.toList()) {
26990 switch (name) {
26991 case 'template':
26992 case 'repeat':
26993 case 'bind':
26994 case 'ref':
26995 clone.attributes.remove(name);
26996 break;
26997 default:
26998 attributes.remove(name);
26999 break;
27000 }
27001 }
27002
27003 return clone;
27004 }
27005
27006 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) {
27007 var content = templateElement.content;
27008
27009 if (!templateElement._isAttributeTemplate) {
27010 var child;
27011 while ((child = templateElement.$dom_firstChild) != null) {
27012 content.append(child);
27013 }
27014 return;
27015 }
27016
27017 // For attribute templates we copy the whole thing into the content and
27018 // we move the non template attributes into the content.
27019 //
27020 // <tr foo template>
27021 //
27022 // becomes
27023 //
27024 // <tr template>
27025 // + #document-fragment
27026 // + <tr foo>
27027 //
27028 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
27029 var child;
27030 while ((child = templateElement.$dom_firstChild) != null) {
27031 newRoot.append(child);
27032 }
27033 content.append(newRoot);
27034 }
27035
27036 void _bootstrapTemplatesRecursivelyFrom(Node node) {
27037 void bootstrap(template) {
27038 if (!TemplateElement.decorate(template)) {
27039 _bootstrapTemplatesRecursivelyFrom(template.content);
27040 }
27041 }
27042
27043 // Need to do this first as the contents may get lifted if |node| is
27044 // template.
27045 // TODO(jmesserly): node is DocumentFragment or Element
27046 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors);
27047 if (node is Element && node.isTemplate) bootstrap(node);
27048
27049 templateDescendents.forEach(bootstrap);
27050 }
27051
27052 final String _allTemplatesSelectors = 'template, option[template], ' +
27053 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
27054
27055 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) {
27056 if (node is Element) {
27057 _addAttributeBindings(node, model, syntax);
27058 } else if (node is Text) {
27059 _parseAndBind(node, node.text, 'text', model, syntax);
27060 }
27061
27062 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
27063 _addBindings(c, model, syntax);
27064 }
27065 }
27066
27067
27068 void _addAttributeBindings(Element element, model, syntax) {
27069 element.attributes.forEach((name, value) {
27070 if (value == '' && (name == 'bind' || name == 'repeat')) {
27071 value = '{{}}';
27072 }
27073 _parseAndBind(element, value, name, model, syntax);
27074 });
27075 }
27076
27077 void _parseAndBind(Node node, String text, String name, model,
27078 CustomBindingSyntax syntax) {
27079
27080 var tokens = _parseMustacheTokens(text);
27081 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
27082 return;
27083 }
27084
27085 if (tokens.length == 1 && tokens[0].isBinding) {
27086 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
27087 return;
27088 }
27089
27090 var replacementBinding = new CompoundBinding();
27091 for (var i = 0; i < tokens.length; i++) {
27092 var token = tokens[i];
27093 if (token.isBinding) {
27094 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
27095 }
27096 }
27097
27098 replacementBinding.combinator = (values) {
27099 var newValue = new StringBuffer();
27100
27101 for (var i = 0; i < tokens.length; i++) {
27102 var token = tokens[i];
27103 if (token.isText) {
27104 newValue.write(token.value);
27105 } else {
27106 var value = values[i];
27107 if (value != null) {
27108 newValue.write(value);
27109 }
27110 }
27111 }
27112
27113 return newValue.toString();
27114 };
27115
27116 node.bind(name, replacementBinding, 'value');
27117 }
27118
27119 void _bindOrDelegate(node, name, model, String path,
27120 CustomBindingSyntax syntax) {
27121
27122 if (syntax != null) {
27123 var delegateBinding = syntax.getBinding(model, path, name, node);
27124 if (delegateBinding != null) {
27125 model = delegateBinding;
27126 path = 'value';
27127 }
27128 }
27129
27130 node.bind(name, model, path);
27131 }
27132
27133 class _BindingToken {
27134 final String value;
27135 final bool isBinding;
27136
27137 _BindingToken(this.value, {this.isBinding: false});
27138
27139 bool get isText => !isBinding;
27140 }
27141
27142 List<_BindingToken> _parseMustacheTokens(String s) {
27143 var result = [];
27144 var length = s.length;
27145 var index = 0, lastIndex = 0;
27146 while (lastIndex < length) {
27147 index = s.indexOf('{{', lastIndex);
27148 if (index < 0) {
27149 result.add(new _BindingToken(s.substring(lastIndex)));
27150 break;
27151 } else {
27152 // There is a non-empty text run before the next path token.
27153 if (index > 0 && lastIndex < index) {
27154 result.add(new _BindingToken(s.substring(lastIndex, index)));
27155 }
27156 lastIndex = index + 2;
27157 index = s.indexOf('}}', lastIndex);
27158 if (index < 0) {
27159 var text = s.substring(lastIndex - 2);
27160 if (result.length > 0 && result.last.isText) {
27161 result.last.value += text;
27162 } else {
27163 result.add(new _BindingToken(text));
27164 }
27165 break;
27166 }
27167
27168 var value = s.substring(lastIndex, index).trim();
27169 result.add(new _BindingToken(value, isBinding: true));
27170 lastIndex = index + 2;
27171 }
27172 }
27173 return result;
27174 }
27175
27176 void _addTemplateInstanceRecord(fragment, model) {
27177 if (fragment.$dom_firstChild == null) {
27178 return;
27179 }
27180
27181 var instanceRecord = new TemplateInstance(
27182 fragment.$dom_firstChild, fragment.$dom_lastChild, model);
27183
27184 var node = instanceRecord.firstNode;
27185 while (node != null) {
27186 node._templateInstance = instanceRecord;
27187 node = node.nextNode;
27188 }
27189 }
27190
27191 void _removeAllBindingsRecursively(Node node) {
27192 node.unbindAll();
27193 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
27194 _removeAllBindingsRecursively(c);
27195 }
27196 }
27197
27198 void _removeTemplateChild(Node parent, Node child) {
27199 child._templateInstance = null;
27200 if (child is Element && child.isTemplate) {
27201 // Make sure we stop observing when we remove an element.
27202 var templateIterator = child._templateIterator;
27203 if (templateIterator != null) {
27204 templateIterator.abandon();
27205 child._templateIterator = null;
27206 }
27207 }
27208 child.remove();
27209 _removeAllBindingsRecursively(child);
27210 }
27211
27212 class _InstanceCursor {
27213 final Element _template;
27214 Node _terminator;
27215 Node _previousTerminator;
27216 int _previousIndex = -1;
27217 int _index = 0;
27218
27219 _InstanceCursor(this._template, [index]) {
27220 _terminator = _template;
27221 if (index != null) {
27222 while (index-- > 0) {
27223 next();
27224 }
27225 }
27226 }
27227
27228 void next() {
27229 _previousTerminator = _terminator;
27230 _previousIndex = _index;
27231 _index++;
27232
27233 while (_index > _terminator._instanceTerminatorCount) {
27234 _index -= _terminator._instanceTerminatorCount;
27235 _terminator = _terminator.nextNode;
27236 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
27237 _index += _instanceCount(_terminator);
27238 }
27239 }
27240 }
27241
27242 void abandon() {
27243 assert(_instanceCount(_template) > 0);
27244 assert(_terminator._instanceTerminatorCount > 0);
27245 assert(_index > 0);
27246
27247 _terminator._instanceTerminatorCount--;
27248 _index--;
27249 }
27250
27251 void insert(fragment) {
27252 assert(_template.parentNode != null);
27253
27254 _previousTerminator = _terminator;
27255 _previousIndex = _index;
27256 _index++;
27257
27258 _terminator = fragment.$dom_lastChild;
27259 if (_terminator == null) _terminator = _previousTerminator;
27260 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
27261
27262 _terminator._instanceTerminatorCount++;
27263 if (_terminator != _previousTerminator) {
27264 while (_previousTerminator._instanceTerminatorCount >
27265 _previousIndex) {
27266 _previousTerminator._instanceTerminatorCount--;
27267 _terminator._instanceTerminatorCount++;
27268 }
27269 }
27270 }
27271
27272 void remove() {
27273 assert(_previousIndex != -1);
27274 assert(_previousTerminator != null &&
27275 (_previousIndex > 0 || _previousTerminator == _template));
27276 assert(_terminator != null && _index > 0);
27277 assert(_template.parentNode != null);
27278 assert(_instanceCount(_template) > 0);
27279
27280 if (_previousTerminator == _terminator) {
27281 assert(_index == _previousIndex + 1);
27282 _terminator._instanceTerminatorCount--;
27283 _terminator = _template;
27284 _previousTerminator = null;
27285 _previousIndex = -1;
27286 return;
27287 }
27288
27289 _terminator._instanceTerminatorCount--;
27290
27291 var parent = _template.parentNode;
27292 while (_previousTerminator.nextNode != _terminator) {
27293 _removeTemplateChild(parent, _previousTerminator.nextNode);
27294 }
27295 _removeTemplateChild(parent, _terminator);
27296
27297 _terminator = _previousTerminator;
27298 _index = _previousIndex;
27299 _previousTerminator = null;
27300 _previousIndex = -1; // 0?
27301 }
27302 }
27303
27304
27305 class _TemplateIterator {
27306 final Element _templateElement;
27307 int instanceCount = 0;
27308 List iteratedValue;
27309 bool observing = false;
27310 final CompoundBinding inputs;
27311
27312 StreamSubscription _sub;
27313 StreamSubscription _valueBinding;
27314
27315 _TemplateIterator(this._templateElement)
27316 : inputs = new CompoundBinding(resolveInputs) {
27317
27318 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged);
27319 }
27320
27321 static Object resolveInputs(Map values) {
27322 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) {
27323 return null;
27324 }
27325
27326 if (values.containsKey('repeat')) {
27327 return values['repeat'];
27328 }
27329
27330 if (values.containsKey('bind')) {
27331 return [values['bind']];
27332 }
27333
27334 return null;
27335 }
27336
27337 void valueChanged(value) {
27338 clear();
27339 if (value is! List) return;
27340
27341 iteratedValue = value;
27342
27343 if (value is Observable) {
27344 _sub = value.changes.listen(_handleChanges);
27345 }
27346
27347 int len = iteratedValue.length;
27348 if (len > 0) {
27349 _handleChanges([new ListChangeRecord(0, addedCount: len)]);
27350 }
27351 }
27352
27353 // TODO(jmesserly): port MDV v3.
27354 getInstanceModel(model, syntax) => model;
27355 getInstanceFragment(syntax) => _templateElement.createInstance();
27356
27357 void _handleChanges(List<ListChangeRecord> splices) {
27358 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
27359
27360 for (var splice in splices) {
27361 if (splice is! ListChangeRecord) continue;
27362
27363 for (int i = 0; i < splice.removedCount; i++) {
27364 var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
27365 cursor.remove();
27366 instanceCount--;
27367 }
27368
27369 for (var addIndex = splice.index;
27370 addIndex < splice.index + splice.addedCount;
27371 addIndex++) {
27372
27373 var model = getInstanceModel(iteratedValue[addIndex], syntax);
27374 var fragment = getInstanceFragment(syntax);
27375
27376 _addBindings(fragment, model, syntax);
27377 _addTemplateInstanceRecord(fragment, model);
27378
27379 var cursor = new _InstanceCursor(_templateElement, addIndex);
27380 cursor.insert(fragment);
27381 instanceCount++;
27382 }
27383 }
27384 }
27385
27386 void unobserve() {
27387 if (_sub == null) return;
27388 _sub.cancel();
27389 _sub = null;
27390 }
27391
27392 void clear() {
27393 unobserve();
27394
27395 iteratedValue = null;
27396 if (instanceCount == 0) return;
27397
27398 for (var i = 0; i < instanceCount; i++) {
27399 var cursor = new _InstanceCursor(_templateElement, 1);
27400 cursor.remove();
27401 }
27402
27403 instanceCount = 0;
27404 }
27405
27406 void abandon() {
27407 unobserve();
27408 _valueBinding.cancel();
27409 inputs.dispose();
27410 }
27411 }
27412
27413 int _instanceCount(Element element) {
27414 var templateIterator = element._templateIterator;
27415 return templateIterator != null ? templateIterator.instanceCount : 0;
27416 }
27417 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
27418 // for details. All rights reserved. Use of this source code is governed by a
27419 // BSD-style license that can be found in the LICENSE file.
27420
27421
26067 /** 27422 /**
26068 * Helper class to implement custom events which wrap DOM events. 27423 * Helper class to implement custom events which wrap DOM events.
26069 */ 27424 */
26070 class _WrappedEvent implements Event { 27425 class _WrappedEvent implements Event {
26071 final Event wrapped; 27426 final Event wrapped;
26072 _WrappedEvent(this.wrapped); 27427 _WrappedEvent(this.wrapped);
26073 27428
26074 bool get bubbles => wrapped.bubbles; 27429 bool get bubbles => wrapped.bubbles;
26075 27430
26076 bool get cancelBubble => wrapped.bubbles; 27431 bool get cancelBubble => wrapped.bubbles;
(...skipping 1225 matching lines...) Expand 10 before | Expand all | Expand 10 after
27302 } 28657 }
27303 28658
27304 _send(msg) { 28659 _send(msg) {
27305 _sendToHelperIsolate(msg, _sendPort); 28660 _sendToHelperIsolate(msg, _sendPort);
27306 } 28661 }
27307 } 28662 }
27308 28663
27309 get _pureIsolateTimerFactoryClosure => 28664 get _pureIsolateTimerFactoryClosure =>
27310 ((int milliSeconds, void callback(Timer time), bool repeating) => 28665 ((int milliSeconds, void callback(Timer time), bool repeating) =>
27311 new _PureIsolateTimer(milliSeconds, callback, repeating)); 28666 new _PureIsolateTimer(milliSeconds, callback, repeating));
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698