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

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: try upload again 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.nodes.addAll(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 }
(...skipping 397 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 5850 matching lines...) Expand 10 before | Expand all | Expand 10 after
25733 * Key value used when an implementation is unable to identify another key 26123 * Key value used when an implementation is unable to identify another key
25734 * value, due to either hardware, platform, or software constraints 26124 * value, due to either hardware, platform, or software constraints
25735 */ 26125 */
25736 static const String UNIDENTIFIED = "Unidentified"; 26126 static const String UNIDENTIFIED = "Unidentified";
25737 } 26127 }
25738 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 26128 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
25739 // for details. All rights reserved. Use of this source code is governed by a 26129 // for details. All rights reserved. Use of this source code is governed by a
25740 // BSD-style license that can be found in the LICENSE file. 26130 // BSD-style license that can be found in the LICENSE file.
25741 26131
25742 26132
25743 class _ModelTreeObserver { 26133 // This code is inspired by ChangeSummary:
25744 static bool _initialized = false; 26134 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
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 /**
26158 * A data-bound path starting from a view-model or model object, for example
26159 * `foo.bar.baz`.
26160 *
26161 * When the [values] stream is being listened to, this will observe changes to
26162 * the object and any intermediate object along the path, and send [values]
26163 * accordingly. When all listeners are unregistered it will stop observing
26164 * the objects.
26165 *
26166 * This class is used to implement [Node.bind] and similar functionality.
26167 */
26168 // TODO(jmesserly): find a better home for this type.
26169 @Experimental
26170 class PathObserver {
26171 /** The object being observed. */
26172 final object;
26173
26174 /** The path string. */
26175 final String path;
26176
26177 /** True if the path is valid, otherwise false. */
26178 final bool _isValid;
26179
26180 // TODO(jmesserly): same issue here as ObservableMixin: is there an easier
26181 // way to get a broadcast stream?
26182 StreamController _values;
26183 Stream _valueStream;
26184
26185 _PropertyObserver _observer, _lastObserver;
26186
26187 Object _lastValue;
26188 bool _scheduled = false;
25745 26189
25746 /** 26190 /**
25747 * Start an observer watching the document for tree changes to automatically 26191 * Observes [path] on [object] for changes. This returns an object that can be
25748 * propagate model changes. 26192 * used to get the changes and get/set the value at this path.
25749 * 26193 * See [PathObserver.values] and [PathObserver.value].
25750 * Currently this does not support propagation through Shadow DOMs.
25751 */ 26194 */
25752 static void initialize() { 26195 PathObserver(this.object, String path)
25753 if (!_initialized) { 26196 : path = path, _isValid = _isPathValid(path) {
25754 _initialized = true; 26197
25755 26198 // TODO(jmesserly): if the path is empty, or the object is! Observable, we
25756 if (MutationObserver.supported) { 26199 // can optimize the PathObserver to be more lightweight.
25757 var observer = new MutationObserver(_processTreeChange); 26200
25758 observer.observe(document, childList: true, subtree: true); 26201 _values = new StreamController(onListen: _observe, onCancel: _unobserve);
25759 } else { 26202
25760 document.on['DOMNodeInserted'].listen(_handleNodeInserted); 26203 if (_isValid) {
25761 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved); 26204 var segments = [];
26205 for (var segment in path.trim().split('.')) {
26206 if (segment == '') continue;
26207 var index = int.parse(segment, onError: (_) {});
26208 segments.add(index != null ? index : new Symbol(segment));
25762 } 26209 }
25763 } 26210
25764 } 26211 // Create the property observer linked list.
25765 26212 // Note that the structure of a path can't change after it is initially
25766 static void _processTreeChange(List<MutationRecord> mutations, 26213 // constructed, even though the objects along the path can change.
25767 MutationObserver observer) { 26214 for (int i = segments.length - 1; i >= 0; i--) {
25768 for (var record in mutations) { 26215 _observer = new _PropertyObserver(this, segments[i], _observer);
25769 for (var node in record.addedNodes) { 26216 if (_lastObserver == null) _lastObserver = _observer;
25770 // When nodes enter the document we need to make sure that all of the
25771 // models are properly propagated through the entire sub-tree.
25772 propagateModel(node, _calculatedModel(node), true);
25773 } 26217 }
25774 for (var node in record.removedNodes) { 26218 }
25775 propagateModel(node, _calculatedModel(node), false); 26219 }
26220
26221 // TODO(jmesserly): we could try adding the first value to the stream, but
26222 // that delivers the first record async.
26223 /**
26224 * Listens to the stream, and invokes the [callback] immediately with the
26225 * current [value]. This is useful for bindings, which want to be up-to-date
26226 * immediately.
26227 */
26228 StreamSubscription bindSync(void callback(value)) {
26229 var result = values.listen(callback);
26230 callback(value);
26231 return result;
26232 }
26233
26234 // TODO(jmesserly): should this be a change record with the old value?
26235 // TODO(jmesserly): should this be a broadcast stream? We only need
26236 // single-subscription in the bindings system, so single sub saves overhead.
26237 /**
26238 * Gets the stream of values that were observed at this path.
26239 * This returns a single-subscription stream.
26240 */
26241 Stream get values => _values.stream;
26242
26243 /** Force synchronous delivery of [values]. */
26244 void _deliverValues() {
26245 _scheduled = false;
26246
26247 var newValue = value;
26248 if (!identical(_lastValue, newValue)) {
26249 _values.add(newValue);
26250 _lastValue = newValue;
26251 }
26252 }
26253
26254 void _observe() {
26255 if (_observer != null) {
26256 _lastValue = value;
26257 _observer.observe();
26258 }
26259 }
26260
26261 void _unobserve() {
26262 if (_observer != null) _observer.unobserve();
26263 }
26264
26265 void _notifyChange() {
26266 if (_scheduled) return;
26267 _scheduled = true;
26268
26269 // TODO(jmesserly): should we have a guarenteed order with respect to other
26270 // paths? If so, we could implement this fairly easily by sorting instances
26271 // of this class by birth order before delivery.
26272 queueChangeRecords(_deliverValues);
26273 }
26274
26275 /** Gets the last reported value at this path. */
26276 get value {
26277 if (!_isValid) return null;
26278 if (_observer == null) return object;
26279 _observer.ensureValue(object);
26280 return _lastObserver.value;
26281 }
26282
26283 /** Sets the value at this path. */
26284 void set value(Object value) {
26285 // TODO(jmesserly): throw if property cannot be set?
26286 // MDV seems tolerant of these error.
26287 if (_observer == null || !_isValid) return;
26288 _observer.ensureValue(object);
26289 var last = _lastObserver;
26290 if (_setObjectProperty(last._object, last._property, value)) {
26291 // Technically, this would get updated asynchronously via a change record.
26292 // However, it is nice if calling the getter will yield the same value
26293 // that was just set. So we use this opportunity to update our cache.
26294 last.value = value;
26295 }
26296 }
26297 }
26298
26299 // TODO(jmesserly): these should go away in favor of mirrors!
26300 _getObjectProperty(object, property) {
26301 if (object is List && property is int) {
26302 if (property >= 0 && property < object.length) {
26303 return object[property];
26304 } else {
26305 return null;
26306 }
26307 }
26308
26309 // TODO(jmesserly): what about length?
26310 if (object is Map) return object[property];
26311
26312 if (object is Observable) return object.getValueWorkaround(property);
26313
26314 return null;
26315 }
26316
26317 bool _setObjectProperty(object, property, value) {
26318 if (object is List && property is int) {
26319 object[property] = value;
26320 } else if (object is Map) {
26321 object[property] = value;
26322 } else if (object is Observable) {
26323 (object as Observable).setValueWorkaround(property, value);
26324 } else {
26325 return false;
26326 }
26327 return true;
26328 }
26329
26330
26331 class _PropertyObserver {
26332 final PathObserver _path;
26333 final _property;
26334 final _PropertyObserver _next;
26335
26336 // TODO(jmesserly): would be nice not to store both of these.
26337 Object _object;
26338 Object _value;
26339 StreamSubscription _sub;
26340
26341 _PropertyObserver(this._path, this._property, this._next);
26342
26343 get value => _value;
26344
26345 void set value(Object newValue) {
26346 _value = newValue;
26347 if (_next != null) {
26348 if (_sub != null) _next.unobserve();
26349 _next.ensureValue(_value);
26350 if (_sub != null) _next.observe();
26351 }
26352 }
26353
26354 void ensureValue(object) {
26355 // If we're observing, values should be up to date already.
26356 if (_sub != null) return;
26357
26358 _object = object;
26359 value = _getObjectProperty(object, _property);
26360 }
26361
26362 void observe() {
26363 if (_object is Observable) {
26364 assert(_sub == null);
26365 _sub = (_object as Observable).changes.listen(_onChange);
26366 }
26367 if (_next != null) _next.observe();
26368 }
26369
26370 void unobserve() {
26371 if (_sub == null) return;
26372
26373 _sub.cancel();
26374 _sub = null;
26375 if (_next != null) _next.unobserve();
26376 }
26377
26378 void _onChange(List<ChangeRecord> changes) {
26379 for (var change in changes) {
26380 // TODO(jmesserly): what to do about "new Symbol" here?
26381 // Ideally this would only preserve names if the user has opted in to
26382 // them being preserved.
26383 // TODO(jmesserly): should we drop observable maps with String keys?
26384 // If so then we only need one check here.
26385 if (change.changes(_property)) {
26386 value = _getObjectProperty(_object, _property);
26387 _path._notifyChange();
26388 return;
25776 } 26389 }
25777 } 26390 }
25778 } 26391 }
25779 26392 }
25780 static void _handleNodeInserted(MutationEvent e) { 26393
25781 var node = e.target; 26394 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
25782 window.setImmediate(() { 26395
25783 propagateModel(node, _calculatedModel(node), true); 26396 const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*';
25784 }); 26397 final _pathRegExp = new RegExp('^'
25785 } 26398 '(?:#?' + _pathIndentPart + ')?'
25786 26399 '(?:'
25787 static void _handleNodeRemoved(MutationEvent e) { 26400 '(?:\\.' + _pathIndentPart + ')'
25788 var node = e.target; 26401 ')*'
25789 window.setImmediate(() { 26402 r'$', caseSensitive: false);
25790 propagateModel(node, _calculatedModel(node), false); 26403
25791 }); 26404 final _spacesRegExp = new RegExp(r'\s');
25792 } 26405
25793 26406 bool _isPathValid(String s) {
25794 /** 26407 s = s.replaceAll(_spacesRegExp, '');
25795 * Figures out what the model should be for a node, avoiding any cached 26408
25796 * model values. 26409 if (s == '') return true;
25797 */ 26410 if (s[0] == '.') return false;
25798 static _calculatedModel(node) { 26411 return _pathRegExp.hasMatch(s);
25799 if (node._hasLocalModel == true) {
25800 return node._model;
25801 } else if (node.parentNode != null) {
25802 return node.parentNode._model;
25803 }
25804 return null;
25805 }
25806
25807 /**
25808 * Pushes model changes down through the tree.
25809 *
25810 * Set fullTree to true if the state of the tree is unknown and model changes
25811 * should be propagated through the entire tree.
25812 */
25813 static void propagateModel(Node node, model, bool fullTree) {
25814 // Calling into user code with the != call could generate exceptions.
25815 // Catch and report them a global exceptions.
25816 try {
25817 if (node._hasLocalModel != true && node._model != model &&
25818 node._modelChangedStreams != null &&
25819 !node._modelChangedStreams.isEmpty) {
25820 node._model = model;
25821 node._modelChangedStreams.toList()
25822 .forEach((controller) => controller.add(node));
25823 }
25824 } catch (e, s) {
25825 new Future.error(e, s);
25826 }
25827 for (var child = node.$dom_firstChild; child != null;
25828 child = child.nextNode) {
25829 if (child._hasLocalModel != true) {
25830 propagateModel(child, model, fullTree);
25831 } else if (fullTree) {
25832 propagateModel(child, child._model, true);
25833 }
25834 }
25835 }
25836 } 26412 }
25837 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 26413 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
25838 // for details. All rights reserved. Use of this source code is governed by a 26414 // for details. All rights reserved. Use of this source code is governed by a
25839 // BSD-style license that can be found in the LICENSE file. 26415 // BSD-style license that can be found in the LICENSE file.
25840 26416
25841 26417
25842 /** 26418 /**
25843 * A utility class for representing two-dimensional positions. 26419 * A utility class for representing two-dimensional positions.
25844 */ 26420 */
25845 class Point { 26421 class Point {
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after
26054 26630
26055 Point get topLeft => new Point(this.left, this.top); 26631 Point get topLeft => new Point(this.left, this.top);
26056 Point get bottomRight => new Point(this.left + this.width, 26632 Point get bottomRight => new Point(this.left + this.width,
26057 this.top + this.height); 26633 this.top + this.height);
26058 } 26634 }
26059 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 26635 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
26060 // for details. All rights reserved. Use of this source code is governed by a 26636 // for details. All rights reserved. Use of this source code is governed by a
26061 // BSD-style license that can be found in the LICENSE file. 26637 // BSD-style license that can be found in the LICENSE file.
26062 26638
26063 26639
26640 // This code is a port of Model-Driven-Views:
26641 // https://github.com/toolkitchen/mdv
26642 // The code mostly comes from src/template_element.js
26643
26644 typedef void _ChangeHandler(value);
26645
26646 /**
26647 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
26648 * but (by design) don't attempt to implement a wide array of specialized
26649 * behaviors.
26650 *
26651 * Enabling these features in MDV is a matter of implementing and registering an
26652 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more
26653 * delegation functions which implement specialized behavior. This object is
26654 * registered with MDV via [TemplateElement.syntax]:
26655 *
26656 *
26657 * HTML:
26658 * <template bind syntax="MySyntax">
26659 * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
26660 * </template>
26661 *
26662 * Dart:
26663 * class MySyntax extends CustomBindingSyntax {
26664 * getBinding(model, path, name, node) {
26665 * // The magic happens here!
26666 * }
26667 * }
26668 *
26669 * ...
26670 *
26671 * TemplateElement.syntax['MySyntax'] = new MySyntax();
26672 *
26673 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more
26674 * information about Custom Syntax.
26675 */
26676 // TODO(jmesserly): if this is just one method, a function type would make it
26677 // more Dart-friendly.
26678 @Experimental
26679 abstract class CustomBindingSyntax {
26680 // TODO(jmesserly): I had to remove type annotations from "name" and "node"
26681 // Normally they are String and Node respectively. But sometimes it will pass
26682 // (int name, CompoundBinding node). That seems very confusing; we may want
26683 // to change this API.
26684 getBinding(model, String path, name, node);
26685 }
26686
26687 /** The callback used in the [CompoundBinding.combinator] field. */
26688 @Experimental
26689 typedef Object CompoundBindingCombinator(Map objects);
26690
26691 /** Information about the instantiated template. */
26692 @Experimental
26693 class TemplateInstance {
26694 // TODO(rafaelw): firstNode & lastNode should be read-synchronous
26695 // in cases where script has modified the template instance boundary.
26696
26697 /** The first node of this template instantiation. */
26698 final Node firstNode;
26699
26700 /**
26701 * The last node of this template instantiation.
26702 * This could be identical to [firstNode] if the template only expanded to a
26703 * single node.
26704 */
26705 final Node lastNode;
26706
26707 /** The model used to instantiate the template. */
26708 final model;
26709
26710 TemplateInstance(this.firstNode, this.lastNode, this.model);
26711 }
26712
26713 /**
26714 * Model-Driven Views contains a helper object which is useful for the
26715 * implementation of a Custom Syntax.
26716 *
26717 * var binding = new CompoundBinding((values) {
26718 * var combinedValue;
26719 * // compute combinedValue based on the current values which are provided
26720 * return combinedValue;
26721 * });
26722 * binding.bind('name1', obj1, path1);
26723 * binding.bind('name2', obj2, path2);
26724 * //...
26725 * binding.bind('nameN', objN, pathN);
26726 *
26727 * CompoundBinding is an object which knows how to listen to multiple path
26728 * values (registered via [bind]) and invoke its [combinator] when one or more
26729 * of the values have changed and set its [value] property to the return value
26730 * of the function. When any value has changed, all current values are provided
26731 * to the [combinator] in the single `values` argument.
26732 *
26733 * See [CustomBindingSyntax] for more information.
26734 */
26735 // TODO(jmesserly): what is the public API surface here? I just guessed;
26736 // most of it seemed non-public.
26737 @Experimental
26738 class CompoundBinding extends ObservableBase {
26739 CompoundBindingCombinator _combinator;
26740
26741 // TODO(jmesserly): ideally these would be String keys, but sometimes we
26742 // use integers.
26743 Map<dynamic, StreamSubscription> _bindings = new Map();
26744 Map _values = new Map();
26745 bool _scheduled = false;
26746 bool _disposed = false;
26747 Object _value;
26748
26749 CompoundBinding([CompoundBindingCombinator combinator]) {
26750 // TODO(jmesserly): this is a tweak to the original code, it seemed to me
26751 // that passing the combinator to the constructor should be equivalent to
26752 // setting it via the property.
26753 // I also added a null check to the combinator setter.
26754 this.combinator = combinator;
26755 }
26756
26757 CompoundBindingCombinator get combinator => _combinator;
26758
26759 set combinator(CompoundBindingCombinator combinator) {
26760 _combinator = combinator;
26761 if (combinator != null) _scheduleResolve();
26762 }
26763
26764 static const _VALUE = const Symbol('value');
26765
26766 get value => _value;
26767
26768 void set value(newValue) {
26769 _value = notifyPropertyChange(_VALUE, _value, newValue);
26770 }
26771
26772 // TODO(jmesserly): remove these workarounds when dart2js supports mirrors!
26773 getValueWorkaround(key) {
26774 if (key == _VALUE) return value;
26775 return null;
26776 }
26777 setValueWorkaround(key, val) {
26778 if (key == _VALUE) value = val;
26779 }
26780
26781 void bind(name, model, String path) {
26782 unbind(name);
26783
26784 _bindings[name] = new PathObserver(model, path).bindSync((value) {
26785 _values[name] = value;
26786 _scheduleResolve();
26787 });
26788 }
26789
26790 void unbind(name, {bool suppressResolve: false}) {
26791 var binding = _bindings.remove(name);
26792 if (binding == null) return;
26793
26794 binding.cancel();
26795 _values.remove(name);
26796 if (!suppressResolve) _scheduleResolve();
26797 }
26798
26799 // TODO(rafaelw): Is this the right processing model?
26800 // TODO(rafaelw): Consider having a seperate ChangeSummary for
26801 // CompoundBindings so to excess dirtyChecks.
26802 void _scheduleResolve() {
26803 if (_scheduled) return;
26804 _scheduled = true;
26805 queueChangeRecords(resolve);
26806 }
26807
26808 void resolve() {
26809 if (_disposed) return;
26810 _scheduled = false;
26811
26812 if (_combinator == null) {
26813 throw new StateError(
26814 'CompoundBinding attempted to resolve without a combinator');
26815 }
26816
26817 value = _combinator(_values);
26818 }
26819
26820 void dispose() {
26821 for (var binding in _bindings.values) {
26822 binding.cancel();
26823 }
26824 _bindings.clear();
26825 _values.clear();
26826
26827 _disposed = true;
26828 value = null;
26829 }
26830 }
26831
26832 Stream<Event> _getStreamForInputType(InputElement element) {
26833 switch (element.type) {
26834 case 'checkbox':
26835 return element.onClick;
26836 case 'radio':
26837 case 'select-multiple':
26838 case 'select-one':
26839 return element.onChange;
26840 default:
26841 return element.onInput;
26842 }
26843 }
26844
26845 abstract class _InputBinding {
26846 final InputElement element;
26847 PathObserver binding;
26848 StreamSubscription _pathSub;
26849 StreamSubscription _eventSub;
26850
26851 _InputBinding(this.element, model, String path) {
26852 binding = new PathObserver(model, path);
26853 _pathSub = binding.bindSync(valueChanged);
26854 _eventSub = _getStreamForInputType(element).listen(updateBinding);
26855 }
26856
26857 void valueChanged(newValue);
26858
26859 void updateBinding(e);
26860
26861 void unbind() {
26862 binding = null;
26863 _pathSub.cancel();
26864 _eventSub.cancel();
26865 }
26866 }
26867
26868 class _ValueBinding extends _InputBinding {
26869 _ValueBinding(element, model, path) : super(element, model, path);
26870
26871 void valueChanged(value) {
26872 element.value = value == null ? '' : '$value';
26873 }
26874
26875 void updateBinding(e) {
26876 binding.value = element.value;
26877 }
26878 }
26879
26880 // TODO(jmesserly): not sure what kind of boolean conversion rules to
26881 // apply for template data-binding. HTML attributes are true if they're present.
26882 // However Dart only treats "true" as true. Since this is HTML we'll use
26883 // something closer to the HTML rules: null (missing) and false are false,
26884 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59
26885 bool _templateBooleanConversion(value) => null != value && false != value;
26886
26887 class _CheckedBinding extends _InputBinding {
26888 _CheckedBinding(element, model, path) : super(element, model, path);
26889
26890 void valueChanged(value) {
26891 element.checked = _templateBooleanConversion(value);
26892 }
26893
26894 void updateBinding(e) {
26895 binding.value = element.checked;
26896
26897 // Only the radio button that is getting checked gets an event. We
26898 // therefore find all the associated radio buttons and update their
26899 // CheckedBinding manually.
26900 if (element is InputElement && element.type == 'radio') {
26901 for (var r in _getAssociatedRadioButtons(element)) {
26902 var checkedBinding = r._checkedBinding;
26903 if (checkedBinding != null) {
26904 // Set the value directly to avoid an infinite call stack.
26905 checkedBinding.binding.value = false;
26906 }
26907 }
26908 }
26909 }
26910 }
26911
26912 // TODO(jmesserly): polyfill document.contains API instead of doing it here
26913 bool _isNodeInDocument(Node node) {
26914 // On non-IE this works:
26915 // return node.document.contains(node);
26916 var document = node.document;
26917 if (node == document || node.parentNode == document) return true;
26918 return document.documentElement.contains(node);
26919 }
26920
26921 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
26922 // Returns an array containing all radio buttons other than |element| that
26923 // have the same |name|, either in the form that |element| belongs to or,
26924 // if no form, in the document tree to which |element| belongs.
26925 //
26926 // This implementation is based upon the HTML spec definition of a
26927 // "radio button group":
26928 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht ml#radio-button-group
26929 //
26930 Iterable _getAssociatedRadioButtons(element) {
26931 if (!_isNodeInDocument(element)) return [];
26932 if (element.form != null) {
26933 return element.form.nodes.where((el) {
26934 return el != element &&
26935 el is InputElement &&
26936 el.type == 'radio' &&
26937 el.name == element.name;
26938 });
26939 } else {
26940 var radios = element.document.queryAll(
26941 'input[type="radio"][name="${element.name}"]');
26942 return radios.where((el) => el != element && el.form == null);
26943 }
26944 }
26945
26946 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
26947 var clone = node.clone(false); // Shallow clone.
26948 if (clone is Element && clone.isTemplate) {
26949 TemplateElement.decorate(clone, node);
26950 if (syntax != null) {
26951 clone.attributes.putIfAbsent('syntax', () => syntax);
26952 }
26953 }
26954
26955 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
26956 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax));
26957 }
26958 return clone;
26959 }
26960
26961 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df n-template-contents-owner
26962 Document _getTemplateContentsOwner(Document doc) {
26963 if (doc.window == null) {
26964 return doc;
26965 }
26966 var d = doc._templateContentsOwner;
26967 if (d == null) {
26968 // TODO(arv): This should either be a Document or HTMLDocument depending
26969 // on doc.
26970 d = doc.implementation.createHtmlDocument('');
26971 while (d.$dom_lastChild != null) {
26972 d.$dom_lastChild.remove();
26973 }
26974 doc._templateContentsOwner = d;
26975 }
26976 return d;
26977 }
26978
26979 Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
26980 var clone = templateElement.clone(false);
26981 var attributes = templateElement.attributes;
26982 for (var name in attributes.keys.toList()) {
26983 switch (name) {
26984 case 'template':
26985 case 'repeat':
26986 case 'bind':
26987 case 'ref':
26988 clone.attributes.remove(name);
26989 break;
26990 default:
26991 attributes.remove(name);
26992 break;
26993 }
26994 }
26995
26996 return clone;
26997 }
26998
26999 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) {
27000 var content = templateElement.content;
27001
27002 if (!templateElement._isAttributeTemplate) {
27003 var child;
27004 while ((child = templateElement.$dom_firstChild) != null) {
27005 content.append(child);
27006 }
27007 return;
27008 }
27009
27010 // For attribute templates we copy the whole thing into the content and
27011 // we move the non template attributes into the content.
27012 //
27013 // <tr foo template>
27014 //
27015 // becomes
27016 //
27017 // <tr template>
27018 // + #document-fragment
27019 // + <tr foo>
27020 //
27021 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
27022 var child;
27023 while ((child = templateElement.$dom_firstChild) != null) {
27024 newRoot.append(child);
27025 }
27026 content.append(newRoot);
27027 }
27028
27029 void _bootstrapTemplatesRecursivelyFrom(Node node) {
27030 void bootstrap(template) {
27031 if (!TemplateElement.decorate(template)) {
27032 _bootstrapTemplatesRecursivelyFrom(template.content);
27033 }
27034 }
27035
27036 // Need to do this first as the contents may get lifted if |node| is
27037 // template.
27038 // TODO(jmesserly): node is DocumentFragment or Element
27039 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors);
27040 if (node is Element && node.isTemplate) bootstrap(node);
27041
27042 templateDescendents.forEach(bootstrap);
27043 }
27044
27045 final String _allTemplatesSelectors = 'template, option[template], ' +
27046 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
27047
27048 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) {
27049 if (node is Element) {
27050 _addAttributeBindings(node, model, syntax);
27051 } else if (node is Text) {
27052 _parseAndBind(node, node.text, 'text', model, syntax);
27053 }
27054
27055 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
27056 _addBindings(c, model, syntax);
27057 }
27058 }
27059
27060
27061 void _addAttributeBindings(Element element, model, syntax) {
27062 element.attributes.forEach((name, value) {
27063 if (value == '' && (name == 'bind' || name == 'repeat')) {
27064 value = '{{}}';
27065 }
27066 _parseAndBind(element, value, name, model, syntax);
27067 });
27068 }
27069
27070 void _parseAndBind(Node node, String text, String name, model,
27071 CustomBindingSyntax syntax) {
27072
27073 var tokens = _parseMustacheTokens(text);
27074 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
27075 return;
27076 }
27077
27078 if (tokens.length == 1 && tokens[0].isBinding) {
27079 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
27080 return;
27081 }
27082
27083 var replacementBinding = new CompoundBinding();
27084 for (var i = 0; i < tokens.length; i++) {
27085 var token = tokens[i];
27086 if (token.isBinding) {
27087 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
27088 }
27089 }
27090
27091 replacementBinding.combinator = (values) {
27092 var newValue = new StringBuffer();
27093
27094 for (var i = 0; i < tokens.length; i++) {
27095 var token = tokens[i];
27096 if (token.isText) {
27097 newValue.write(token.value);
27098 } else {
27099 var value = values[i];
27100 if (value != null) {
27101 newValue.write(value);
27102 }
27103 }
27104 }
27105
27106 return newValue.toString();
27107 };
27108
27109 node.bind(name, replacementBinding, 'value');
27110 }
27111
27112 void _bindOrDelegate(node, name, model, String path,
27113 CustomBindingSyntax syntax) {
27114
27115 if (syntax != null) {
27116 var delegateBinding = syntax.getBinding(model, path, name, node);
27117 if (delegateBinding != null) {
27118 model = delegateBinding;
27119 path = 'value';
27120 }
27121 }
27122
27123 node.bind(name, model, path);
27124 }
27125
27126 class _BindingToken {
27127 final String value;
27128 final bool isBinding;
27129
27130 _BindingToken(this.value, {this.isBinding: false});
27131
27132 bool get isText => !isBinding;
27133 }
27134
27135 List<_BindingToken> _parseMustacheTokens(String s) {
27136 var result = [];
27137 var length = s.length;
27138 var index = 0, lastIndex = 0;
27139 while (lastIndex < length) {
27140 index = s.indexOf('{{', lastIndex);
27141 if (index < 0) {
27142 result.add(new _BindingToken(s.substring(lastIndex)));
27143 break;
27144 } else {
27145 // There is a non-empty text run before the next path token.
27146 if (index > 0 && lastIndex < index) {
27147 result.add(new _BindingToken(s.substring(lastIndex, index)));
27148 }
27149 lastIndex = index + 2;
27150 index = s.indexOf('}}', lastIndex);
27151 if (index < 0) {
27152 var text = s.substring(lastIndex - 2);
27153 if (result.length > 0 && result.last.isText) {
27154 result.last.value += text;
27155 } else {
27156 result.add(new _BindingToken(text));
27157 }
27158 break;
27159 }
27160
27161 var value = s.substring(lastIndex, index).trim();
27162 result.add(new _BindingToken(value, isBinding: true));
27163 lastIndex = index + 2;
27164 }
27165 }
27166 return result;
27167 }
27168
27169 void _addTemplateInstanceRecord(fragment, model) {
27170 if (fragment.$dom_firstChild == null) {
27171 return;
27172 }
27173
27174 var instanceRecord = new TemplateInstance(
27175 fragment.$dom_firstChild, fragment.$dom_lastChild, model);
27176
27177 var node = instanceRecord.firstNode;
27178 while (node != null) {
27179 node._templateInstance = instanceRecord;
27180 node = node.nextNode;
27181 }
27182 }
27183
27184 void _removeAllBindingsRecursively(Node node) {
27185 node.unbindAll();
27186 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
27187 _removeAllBindingsRecursively(c);
27188 }
27189 }
27190
27191 void _removeTemplateChild(Node parent, Node child) {
27192 child._templateInstance = null;
27193 if (child is Element && child.isTemplate) {
27194 // Make sure we stop observing when we remove an element.
27195 var templateIterator = child._templateIterator;
27196 if (templateIterator != null) {
27197 templateIterator.abandon();
27198 child._templateIterator = null;
27199 }
27200 }
27201 child.remove();
27202 _removeAllBindingsRecursively(child);
27203 }
27204
27205 class _InstanceCursor {
27206 final Element _template;
27207 Node _terminator;
27208 Node _previousTerminator;
27209 int _previousIndex = -1;
27210 int _index = 0;
27211
27212 _InstanceCursor(this._template, [index]) {
27213 _terminator = _template;
27214 if (index != null) {
27215 while (index-- > 0) {
27216 next();
27217 }
27218 }
27219 }
27220
27221 void next() {
27222 _previousTerminator = _terminator;
27223 _previousIndex = _index;
27224 _index++;
27225
27226 while (_index > _terminator._instanceTerminatorCount) {
27227 _index -= _terminator._instanceTerminatorCount;
27228 _terminator = _terminator.nextNode;
27229 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
27230 _index += _instanceCount(_terminator);
27231 }
27232 }
27233 }
27234
27235 void abandon() {
27236 assert(_instanceCount(_template) > 0);
27237 assert(_terminator._instanceTerminatorCount > 0);
27238 assert(_index > 0);
27239
27240 _terminator._instanceTerminatorCount--;
27241 _index--;
27242 }
27243
27244 void insert(fragment) {
27245 assert(_template.parentNode != null);
27246
27247 _previousTerminator = _terminator;
27248 _previousIndex = _index;
27249 _index++;
27250
27251 _terminator = fragment.$dom_lastChild;
27252 if (_terminator == null) _terminator = _previousTerminator;
27253 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
27254
27255 _terminator._instanceTerminatorCount++;
27256 if (_terminator != _previousTerminator) {
27257 while (_previousTerminator._instanceTerminatorCount >
27258 _previousIndex) {
27259 _previousTerminator._instanceTerminatorCount--;
27260 _terminator._instanceTerminatorCount++;
27261 }
27262 }
27263 }
27264
27265 void remove() {
27266 assert(_previousIndex != -1);
27267 assert(_previousTerminator != null &&
27268 (_previousIndex > 0 || _previousTerminator == _template));
27269 assert(_terminator != null && _index > 0);
27270 assert(_template.parentNode != null);
27271 assert(_instanceCount(_template) > 0);
27272
27273 if (_previousTerminator == _terminator) {
27274 assert(_index == _previousIndex + 1);
27275 _terminator._instanceTerminatorCount--;
27276 _terminator = _template;
27277 _previousTerminator = null;
27278 _previousIndex = -1;
27279 return;
27280 }
27281
27282 _terminator._instanceTerminatorCount--;
27283
27284 var parent = _template.parentNode;
27285 while (_previousTerminator.nextNode != _terminator) {
27286 _removeTemplateChild(parent, _previousTerminator.nextNode);
27287 }
27288 _removeTemplateChild(parent, _terminator);
27289
27290 _terminator = _previousTerminator;
27291 _index = _previousIndex;
27292 _previousTerminator = null;
27293 _previousIndex = -1; // 0?
27294 }
27295 }
27296
27297
27298 class _TemplateIterator {
27299 final Element _templateElement;
27300 int instanceCount = 0;
27301 List iteratedValue;
27302 bool observing = false;
27303 final CompoundBinding inputs;
27304
27305 StreamSubscription _sub;
27306 StreamSubscription _valueBinding;
27307
27308 _TemplateIterator(this._templateElement)
27309 : inputs = new CompoundBinding(resolveInputs) {
27310
27311 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged);
27312 }
27313
27314 static Object resolveInputs(Map values) {
27315 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) {
27316 return null;
27317 }
27318
27319 if (values.containsKey('repeat')) {
27320 return values['repeat'];
27321 }
27322
27323 if (values.containsKey('bind')) {
27324 return [values['bind']];
27325 }
27326
27327 return null;
27328 }
27329
27330 void valueChanged(value) {
27331 clear();
27332 if (value is! List) return;
27333
27334 iteratedValue = value;
27335
27336 if (value is Observable) {
27337 _sub = value.changes.listen(_handleChanges);
27338 }
27339
27340 int len = iteratedValue.length;
27341 if (len > 0) {
27342 _handleChanges([new ListChangeRecord(0, addedCount: len)]);
27343 }
27344 }
27345
27346 // TODO(jmesserly): port MDV v3.
27347 getInstanceModel(model, syntax) => model;
27348 getInstanceFragment(syntax) => _templateElement.createInstance();
27349
27350 void _handleChanges(List<ListChangeRecord> splices) {
27351 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
27352
27353 for (var splice in splices) {
27354 if (splice is! ListChangeRecord) continue;
27355
27356 for (int i = 0; i < splice.removedCount; i++) {
27357 var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
27358 cursor.remove();
27359 instanceCount--;
27360 }
27361
27362 for (var addIndex = splice.index;
27363 addIndex < splice.index + splice.addedCount;
27364 addIndex++) {
27365
27366 var model = getInstanceModel(iteratedValue[addIndex], syntax);
27367 var fragment = getInstanceFragment(syntax);
27368
27369 _addBindings(fragment, model, syntax);
27370 _addTemplateInstanceRecord(fragment, model);
27371
27372 var cursor = new _InstanceCursor(_templateElement, addIndex);
27373 cursor.insert(fragment);
27374 instanceCount++;
27375 }
27376 }
27377 }
27378
27379 void unobserve() {
27380 if (_sub == null) return;
27381 _sub.cancel();
27382 _sub = null;
27383 }
27384
27385 void clear() {
27386 unobserve();
27387
27388 iteratedValue = null;
27389 if (instanceCount == 0) return;
27390
27391 for (var i = 0; i < instanceCount; i++) {
27392 var cursor = new _InstanceCursor(_templateElement, 1);
27393 cursor.remove();
27394 }
27395
27396 instanceCount = 0;
27397 }
27398
27399 void abandon() {
27400 unobserve();
27401 _valueBinding.cancel();
27402 inputs.dispose();
27403 }
27404 }
27405
27406 int _instanceCount(Element element) {
27407 var templateIterator = element._templateIterator;
27408 return templateIterator != null ? templateIterator.instanceCount : 0;
27409 }
27410 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
27411 // for details. All rights reserved. Use of this source code is governed by a
27412 // BSD-style license that can be found in the LICENSE file.
27413
27414
26064 /** 27415 /**
26065 * Helper class to implement custom events which wrap DOM events. 27416 * Helper class to implement custom events which wrap DOM events.
26066 */ 27417 */
26067 class _WrappedEvent implements Event { 27418 class _WrappedEvent implements Event {
26068 final Event wrapped; 27419 final Event wrapped;
26069 _WrappedEvent(this.wrapped); 27420 _WrappedEvent(this.wrapped);
26070 27421
26071 bool get bubbles => wrapped.bubbles; 27422 bool get bubbles => wrapped.bubbles;
26072 27423
26073 bool get cancelBubble => wrapped.bubbles; 27424 bool get cancelBubble => wrapped.bubbles;
(...skipping 1225 matching lines...) Expand 10 before | Expand all | Expand 10 after
27299 } 28650 }
27300 28651
27301 _send(msg) { 28652 _send(msg) {
27302 _sendToHelperIsolate(msg, _sendPort); 28653 _sendToHelperIsolate(msg, _sendPort);
27303 } 28654 }
27304 } 28655 }
27305 28656
27306 get _pureIsolateTimerFactoryClosure => 28657 get _pureIsolateTimerFactoryClosure =>
27307 ((int milliSeconds, void callback(Timer time), bool repeating) => 28658 ((int milliSeconds, void callback(Timer time), bool repeating) =>
27308 new _PureIsolateTimer(milliSeconds, callback, repeating)); 28659 new _PureIsolateTimer(milliSeconds, callback, repeating));
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698