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

Side by Side Diff: sdk/lib/html/dart2js/html_dart2js.dart

Issue 14908005: "Reverting 22561" (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/_internal/libraries.dart ('k') | sdk/lib/html/dartium/html_dartium.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /// The Dart HTML library. 1 /// The Dart HTML library.
2 library dart.dom.html; 2 library dart.dom.html;
3 3
4 import 'dart:async'; 4 import 'dart:async';
5 import 'dart:collection'; 5 import 'dart:collection';
6 import 'dart:_collection-dev' hide Symbol; 6 import 'dart:_collection-dev';
7 import 'dart:html_common'; 7 import 'dart:html_common';
8 import 'dart:indexed_db'; 8 import 'dart:indexed_db';
9 import 'dart:isolate'; 9 import 'dart:isolate';
10 import 'dart:json' as json; 10 import 'dart:json' as json;
11 import 'dart:math'; 11 import 'dart:math';
12 import 'dart:mdv_observe_impl';
13 import 'dart:typed_data'; 12 import 'dart:typed_data';
14 import 'dart:svg' as svg; 13 import 'dart:svg' as svg;
15 import 'dart:web_audio' as web_audio; 14 import 'dart:web_audio' as web_audio;
16 import 'dart:web_gl' as gl; 15 import 'dart:web_gl' as gl;
17 import 'dart:web_sql'; 16 import 'dart:web_sql';
18 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin gBehavior, JSName, Null, Returns; 17 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin gBehavior, JSName, Null, Returns;
19 import 'dart:_isolate_helper' show IsolateNatives; 18 import 'dart:_isolate_helper' show IsolateNatives;
20 import 'dart:_foreign_helper' show JS; 19 import 'dart:_foreign_helper' show JS;
21 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
22 // for details. All rights reserved. Use of this source code is governed by a 21 // for details. All rights reserved. Use of this source code is governed by a
(...skipping 6008 matching lines...) Expand 10 before | Expand all | Expand 10 after
6031 @DomName('Document.cookie') 6030 @DomName('Document.cookie')
6032 @DocsEditable 6031 @DocsEditable
6033 String cookie; 6032 String cookie;
6034 6033
6035 WindowBase get window => _convertNativeToDart_Window(this._get_window); 6034 WindowBase get window => _convertNativeToDart_Window(this._get_window);
6036 @JSName('defaultView') 6035 @JSName('defaultView')
6037 @DomName('Document.window') 6036 @DomName('Document.window')
6038 @DocsEditable 6037 @DocsEditable
6039 @Creates('Window|=Object') 6038 @Creates('Window|=Object')
6040 @Returns('Window|=Object') 6039 @Returns('Window|=Object')
6041 @Creates('Window|=Object|Null')
6042 @Returns('Window|=Object|Null')
6043 final dynamic _get_window; 6040 final dynamic _get_window;
6044 6041
6045 @DomName('Document.documentElement') 6042 @DomName('Document.documentElement')
6046 @DocsEditable 6043 @DocsEditable
6047 final Element documentElement; 6044 final Element documentElement;
6048 6045
6049 @DomName('Document.domain') 6046 @DomName('Document.domain')
6050 @DocsEditable 6047 @DocsEditable
6051 final String domain; 6048 final String domain;
6052 6049
(...skipping 586 matching lines...) Expand 10 before | Expand all | Expand 10 after
6639 6636
6640 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or 6637 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or
6641 // SVG strings? 6638 // SVG strings?
6642 void set innerHtml(String value) { 6639 void set innerHtml(String value) {
6643 this.nodes.clear(); 6640 this.nodes.clear();
6644 6641
6645 final e = new Element.tag("div"); 6642 final e = new Element.tag("div");
6646 e.innerHtml = value; 6643 e.innerHtml = value;
6647 6644
6648 // Copy list first since we don't want liveness during iteration. 6645 // Copy list first since we don't want liveness during iteration.
6649 List nodes = new List.from(e.nodes, growable: false); 6646 List nodes = new List.from(e.nodes);
6650 this.append(nodes); 6647 this.nodes.addAll(nodes);
6651 } 6648 }
6652 6649
6653 /** 6650 /**
6654 * Adds the specified text as a text node after the last child of this 6651 * Adds the specified text as a text node after the last child of this
6655 * document fragment. 6652 * document fragment.
6656 */ 6653 */
6657 void appendText(String text) { 6654 void appendText(String text) {
6658 this.append(new Text(text)); 6655 this.append(new Text(text));
6659 } 6656 }
6660 6657
(...skipping 360 matching lines...) Expand 10 before | Expand all | Expand 10 after
7021 if (result == null) throw new StateError("No elements"); 7018 if (result == null) throw new StateError("No elements");
7022 return result; 7019 return result;
7023 } 7020 }
7024 7021
7025 Element get single { 7022 Element get single {
7026 if (length > 1) throw new StateError("More than one element"); 7023 if (length > 1) throw new StateError("More than one element");
7027 return first; 7024 return first;
7028 } 7025 }
7029 } 7026 }
7030 7027
7031 /** 7028 /**
7032 * An immutable list containing HTML elements. This list contains some 7029 * An immutable list containing HTML elements. This list contains some
7033 * additional methods for ease of CSS manipulation on a group of elements. 7030 * additional methods for ease of CSS manipulation on a group of elements.
7034 */ 7031 */
7035 abstract class ElementList<T extends Element> extends ListBase<T> { 7032 abstract class ElementList<T extends Element> extends ListBase<T> {
7036 /** 7033 /**
7037 * The union of all CSS classes applied to the elements in this list. 7034 * The union of all CSS classes applied to the elements in this list.
7038 * 7035 *
7039 * This set makes it easy to add, remove or toggle (add if not present, remove 7036 * This set makes it easy to add, remove or toggle (add if not present, remove
7040 * if present) the classes applied to a collection of elements. 7037 * if present) the classes applied to a collection of elements.
7041 * 7038 *
(...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after
7503 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { 7500 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) {
7504 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); 7501 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors);
7505 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { 7502 } else if (JS('bool', '!!#.mozMatchesSelector', this)) {
7506 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); 7503 return JS('bool', '#.mozMatchesSelector(#)', this, selectors);
7507 } else if (JS('bool', '!!#.msMatchesSelector', this)) { 7504 } else if (JS('bool', '!!#.msMatchesSelector', this)) {
7508 return JS('bool', '#.msMatchesSelector(#)', this, selectors); 7505 return JS('bool', '#.msMatchesSelector(#)', this, selectors);
7509 } 7506 }
7510 throw new UnsupportedError("Not supported on this platform"); 7507 throw new UnsupportedError("Not supported on this platform");
7511 } 7508 }
7512 7509
7513 @Creates('Null')
7514 Map<String, StreamSubscription> _attributeBindings;
7515
7516 // TODO(jmesserly): I'm concerned about adding these to every element.
7517 // Conceptually all of these belong on TemplateElement. They are here to
7518 // support browsers that don't have <template> yet.
7519 // However even in the polyfill they're restricted to certain tags
7520 // (see [isTemplate]). So we can probably convert it to a (public) mixin, and
7521 // only mix it in to the elements that need it.
7522 @Creates('Null') // Set from Dart code; does not instantiate a native type.
7523 var _model;
7524
7525 @Creates('Null') // Set from Dart code; does not instantiate a native type.
7526 _TemplateIterator _templateIterator;
7527
7528 @Creates('Null') // Set from Dart code; does not instantiate a native type.
7529 Element _templateInstanceRef;
7530
7531 // Note: only used if `this is! TemplateElement`
7532 @Creates('Null') // Set from Dart code; does not instantiate a native type.
7533 DocumentFragment _templateContent;
7534
7535 bool _templateIsDecorated;
7536
7537 // TODO(jmesserly): should path be optional, and default to empty path?
7538 // It is used that way in at least one path in JS TemplateElement tests
7539 // (see "BindImperative" test in original JS code).
7540 @Experimental
7541 void bind(String name, model, String path) {
7542 _bindElement(this, name, model, path);
7543 }
7544
7545 // TODO(jmesserly): this is static to work around http://dartbug.com/10166
7546 // Similar issue for unbind/unbindAll below.
7547 static void _bindElement(Element self, String name, model, String path) {
7548 if (self._bindTemplate(name, model, path)) return;
7549
7550 if (self._attributeBindings == null) {
7551 self._attributeBindings = new Map<String, StreamSubscription>();
7552 }
7553
7554 self.attributes.remove(name);
7555
7556 var changed;
7557 if (name.endsWith('?')) {
7558 name = name.substring(0, name.length - 1);
7559
7560 changed = (value) {
7561 if (_templateBooleanConversion(value)) {
7562 self.attributes[name] = '';
7563 } else {
7564 self.attributes.remove(name);
7565 }
7566 };
7567 } else {
7568 changed = (value) {
7569 // TODO(jmesserly): escape value if needed to protect against XSS.
7570 // See https://github.com/toolkitchen/mdv/issues/58
7571 self.attributes[name] = value == null ? '' : '$value';
7572 };
7573 }
7574
7575 self.unbind(name);
7576
7577 self._attributeBindings[name] =
7578 new PathObserver(model, path).bindSync(changed);
7579 }
7580
7581 @Experimental
7582 void unbind(String name) {
7583 _unbindElement(this, name);
7584 }
7585
7586 static _unbindElement(Element self, String name) {
7587 if (self._unbindTemplate(name)) return;
7588 if (self._attributeBindings != null) {
7589 var binding = self._attributeBindings.remove(name);
7590 if (binding != null) binding.cancel();
7591 }
7592 }
7593
7594 @Experimental
7595 void unbindAll() {
7596 _unbindAllElement(this);
7597 }
7598
7599 static void _unbindAllElement(Element self) {
7600 self._unbindAllTemplate();
7601
7602 if (self._attributeBindings != null) {
7603 for (var binding in self._attributeBindings.values) {
7604 binding.cancel();
7605 }
7606 self._attributeBindings = null;
7607 }
7608 }
7609
7610 // TODO(jmesserly): unlike the JS polyfill, we can't mixin
7611 // HTMLTemplateElement at runtime into things that are semantically template
7612 // elements. So instead we implement it here with a runtime check.
7613 // If the bind succeeds, we return true, otherwise we return false and let
7614 // the normal Element.bind logic kick in.
7615 bool _bindTemplate(String name, model, String path) {
7616 if (isTemplate) {
7617 switch (name) {
7618 case 'bind':
7619 case 'repeat':
7620 case 'if':
7621 _ensureTemplate();
7622 if (_templateIterator == null) {
7623 _templateIterator = new _TemplateIterator(this);
7624 }
7625 _templateIterator.inputs.bind(name, model, path);
7626 return true;
7627 }
7628 }
7629 return false;
7630 }
7631
7632 bool _unbindTemplate(String name) {
7633 if (isTemplate) {
7634 switch (name) {
7635 case 'bind':
7636 case 'repeat':
7637 case 'if':
7638 _ensureTemplate();
7639 if (_templateIterator != null) {
7640 _templateIterator.inputs.unbind(name);
7641 }
7642 return true;
7643 }
7644 }
7645 return false;
7646 }
7647
7648 void _unbindAllTemplate() {
7649 if (isTemplate) {
7650 unbind('bind');
7651 unbind('repeat');
7652 unbind('if');
7653 }
7654 }
7655
7656 /**
7657 * Gets the template this node refers to.
7658 * This is only supported if [isTemplate] is true.
7659 */
7660 @Experimental
7661 Element get ref {
7662 _ensureTemplate();
7663
7664 Element ref = null;
7665 var refId = attributes['ref'];
7666 if (refId != null) {
7667 ref = document.getElementById(refId);
7668 }
7669
7670 return ref != null ? ref : _templateInstanceRef;
7671 }
7672
7673 /**
7674 * Gets the content of this template.
7675 * This is only supported if [isTemplate] is true.
7676 */
7677 @Experimental
7678 DocumentFragment get content {
7679 _ensureTemplate();
7680 return _templateContent;
7681 }
7682
7683 /**
7684 * Creates an instance of the template.
7685 * This is only supported if [isTemplate] is true.
7686 */
7687 @Experimental
7688 DocumentFragment createInstance() {
7689 _ensureTemplate();
7690
7691 var template = ref;
7692 if (template == null) template = this;
7693
7694 var instance = _createDeepCloneAndDecorateTemplates(template.content,
7695 attributes['syntax']);
7696
7697 if (TemplateElement._instanceCreated != null) {
7698 TemplateElement._instanceCreated.add(instance);
7699 }
7700 return instance;
7701 }
7702
7703 /**
7704 * The data model which is inherited through the tree.
7705 * This is only supported if [isTemplate] is true.
7706 *
7707 * Setting this will destructive propagate the value to all descendant nodes,
7708 * and reinstantiate all of the nodes expanded by this template.
7709 *
7710 * Currently this does not support propagation through Shadow DOMs.
7711 */
7712 @Experimental
7713 get model => _model;
7714
7715 @Experimental
7716 void set model(value) {
7717 _ensureTemplate();
7718
7719 _model = value;
7720 _addBindings(this, model);
7721 }
7722
7723 // TODO(jmesserly): const set would be better
7724 static const _TABLE_TAGS = const {
7725 'caption': null,
7726 'col': null,
7727 'colgroup': null,
7728 'tbody': null,
7729 'td': null,
7730 'tfoot': null,
7731 'th': null,
7732 'thead': null,
7733 'tr': null,
7734 };
7735
7736 bool get _isAttributeTemplate => attributes.containsKey('template') &&
7737 (localName == 'option' || _TABLE_TAGS.containsKey(localName));
7738
7739 /**
7740 * Returns true if this node is a template.
7741 *
7742 * A node is a template if [tagName] is TEMPLATE, or the node has the
7743 * 'template' attribute and this tag supports attribute form for backwards
7744 * compatibility with existing HTML parsers. The nodes that can use attribute
7745 * form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP
7746 * and COL) and OPTION.
7747 */
7748 // TODO(jmesserly): this is not a public MDV API, but it seems like a useful
7749 // place to document which tags our polyfill considers to be templates.
7750 // Otherwise I'd be repeating it in several other places.
7751 // See if we can replace this with a TemplateMixin.
7752 @Experimental
7753 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate;
7754
7755 void _ensureTemplate() {
7756 if (!isTemplate) {
7757 throw new UnsupportedError('$this is not a template.');
7758 }
7759 TemplateElement.decorate(this);
7760 }
7761
7762 7510
7763 @DomName('Element.abortEvent') 7511 @DomName('Element.abortEvent')
7764 @DocsEditable 7512 @DocsEditable
7765 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort'); 7513 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort');
7766 7514
7767 @DomName('Element.beforecopyEvent') 7515 @DomName('Element.beforecopyEvent')
7768 @DocsEditable 7516 @DocsEditable
7769 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy'); 7517 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy');
7770 7518
7771 @DomName('Element.beforecutEvent') 7519 @DomName('Element.beforecutEvent')
(...skipping 724 matching lines...) Expand 10 before | Expand all | Expand 10 after
8496 @DomName('Element.onwebkitfullscreenchange') 8244 @DomName('Element.onwebkitfullscreenchange')
8497 @DocsEditable 8245 @DocsEditable
8498 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); 8246 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this);
8499 8247
8500 @DomName('Element.onwebkitfullscreenerror') 8248 @DomName('Element.onwebkitfullscreenerror')
8501 @DocsEditable 8249 @DocsEditable
8502 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); 8250 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this);
8503 8251
8504 } 8252 }
8505 8253
8506
8507 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); 8254 final _START_TAG_REGEXP = new RegExp('<(\\w+)');
8508 class _ElementFactoryProvider { 8255 class _ElementFactoryProvider {
8509 static const _CUSTOM_PARENT_TAG_MAP = const { 8256 static const _CUSTOM_PARENT_TAG_MAP = const {
8510 'body' : 'html', 8257 'body' : 'html',
8511 'head' : 'html', 8258 'head' : 'html',
8512 'caption' : 'table', 8259 'caption' : 'table',
8513 'td': 'tr', 8260 'td': 'tr',
8514 'th': 'tr', 8261 'th': 'tr',
8515 'colgroup': 'table', 8262 'colgroup': 'table',
8516 'col' : 'colgroup', 8263 'col' : 'colgroup',
8517 'tr' : 'tbody', 8264 'tr' : 'tbody',
8518 'tbody' : 'table', 8265 'tbody' : 'table',
8519 'tfoot' : 'table', 8266 'tfoot' : 'table',
8520 'thead' : 'table', 8267 'thead' : 'table',
8521 'track' : 'audio', 8268 'track' : 'audio',
8522 }; 8269 };
8523 8270
8271 // TODO(jmesserly): const set would be better
8272 static const _TABLE_TAGS = const {
8273 'caption': null,
8274 'col': null,
8275 'colgroup': null,
8276 'tbody': null,
8277 'td': null,
8278 'tfoot': null,
8279 'th': null,
8280 'thead': null,
8281 'tr': null,
8282 };
8283
8524 @DomName('Document.createElement') 8284 @DomName('Document.createElement')
8525 static Element createElement_html(String html) { 8285 static Element createElement_html(String html) {
8526 // TODO(jacobr): this method can be made more robust and performant. 8286 // TODO(jacobr): this method can be made more robust and performant.
8527 // 1) Cache the dummy parent elements required to use innerHTML rather than 8287 // 1) Cache the dummy parent elements required to use innerHTML rather than
8528 // creating them every call. 8288 // creating them every call.
8529 // 2) Verify that the html does not contain leading or trailing text nodes. 8289 // 2) Verify that the html does not contain leading or trailing text nodes.
8530 // 3) Verify that the html does not contain both <head> and <body> tags. 8290 // 3) Verify that the html does not contain both <head> and <body> tags.
8531 // 4) Detatch the created element from its dummy parent. 8291 // 4) Detatch the created element from its dummy parent.
8532 String parentTag = 'div'; 8292 String parentTag = 'div';
8533 String tag; 8293 String tag;
8534 final match = _START_TAG_REGEXP.firstMatch(html); 8294 final match = _START_TAG_REGEXP.firstMatch(html);
8535 if (match != null) { 8295 if (match != null) {
8536 tag = match.group(1).toLowerCase(); 8296 tag = match.group(1).toLowerCase();
8537 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { 8297 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) {
8538 return _createTableForIE(html, tag); 8298 return _createTableForIE(html, tag);
8539 } 8299 }
8540 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; 8300 parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
8541 if (parentTag == null) parentTag = 'div'; 8301 if (parentTag == null) parentTag = 'div';
8542 } 8302 }
8543 8303
8544 final temp = new Element.tag(parentTag); 8304 final temp = new Element.tag(parentTag);
8545 temp.innerHtml = html; 8305 temp.innerHtml = html;
8546 8306
8547 Element element; 8307 Element element;
(...skipping 1897 matching lines...) Expand 10 before | Expand all | Expand 10 after
10445 @SupportedBrowser(SupportedBrowser.SAFARI) 10205 @SupportedBrowser(SupportedBrowser.SAFARI)
10446 @Experimental 10206 @Experimental
10447 Element get pointerLockElement => 10207 Element get pointerLockElement =>
10448 $dom_webkitPointerLockElement; 10208 $dom_webkitPointerLockElement;
10449 10209
10450 @DomName('Document.webkitVisibilityState') 10210 @DomName('Document.webkitVisibilityState')
10451 @SupportedBrowser(SupportedBrowser.CHROME) 10211 @SupportedBrowser(SupportedBrowser.CHROME)
10452 @SupportedBrowser(SupportedBrowser.SAFARI) 10212 @SupportedBrowser(SupportedBrowser.SAFARI)
10453 @Experimental 10213 @Experimental
10454 String get visibilityState => $dom_webkitVisibilityState; 10214 String get visibilityState => $dom_webkitVisibilityState;
10455
10456
10457 @Creates('Null') // Set from Dart code; does not instantiate a native type.
10458 // Note: used to polyfill <template>
10459 Document _templateContentsOwner;
10460 } 10215 }
10461 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 10216 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
10462 // for details. All rights reserved. Use of this source code is governed by a 10217 // for details. All rights reserved. Use of this source code is governed by a
10463 // BSD-style license that can be found in the LICENSE file. 10218 // BSD-style license that can be found in the LICENSE file.
10464 10219
10465 10220
10466 @DocsEditable 10221 @DocsEditable
10467 @DomName('HTMLHtmlElement') 10222 @DomName('HTMLHtmlElement')
10468 class HtmlElement extends Element native "HTMLHtmlElement" { 10223 class HtmlElement extends Element native "HTMLHtmlElement" {
10469 10224
(...skipping 816 matching lines...) Expand 10 before | Expand all | Expand 10 after
11286 var e = document.$dom_createElement("input"); 11041 var e = document.$dom_createElement("input");
11287 if (type != null) { 11042 if (type != null) {
11288 try { 11043 try {
11289 // IE throws an exception for unknown types. 11044 // IE throws an exception for unknown types.
11290 e.type = type; 11045 e.type = type;
11291 } catch(_) {} 11046 } catch(_) {}
11292 } 11047 }
11293 return e; 11048 return e;
11294 } 11049 }
11295 11050
11296 @Creates('Null') // Set from Dart code; does not instantiate a native type.
11297 _ValueBinding _valueBinding;
11298
11299 @Creates('Null') // Set from Dart code; does not instantiate a native type.
11300 _CheckedBinding _checkedBinding;
11301
11302 @Experimental
11303 void bind(String name, model, String path) {
11304 switch (name) {
11305 case 'value':
11306 unbind('value');
11307 attributes.remove('value');
11308 _valueBinding = new _ValueBinding(this, model, path);
11309 break;
11310 case 'checked':
11311 unbind('checked');
11312 attributes.remove('checked');
11313 _checkedBinding = new _CheckedBinding(this, model, path);
11314 break;
11315 default:
11316 // TODO(jmesserly): this should be "super" (http://dartbug.com/10166).
11317 // Similar issue for unbind/unbindAll below.
11318 Element._bindElement(this, name, model, path);
11319 break;
11320 }
11321 }
11322
11323 @Experimental
11324 void unbind(String name) {
11325 switch (name) {
11326 case 'value':
11327 if (_valueBinding != null) {
11328 _valueBinding.unbind();
11329 _valueBinding = null;
11330 }
11331 break;
11332 case 'checked':
11333 if (_checkedBinding != null) {
11334 _checkedBinding.unbind();
11335 _checkedBinding = null;
11336 }
11337 break;
11338 default:
11339 Element._unbindElement(this, name);
11340 break;
11341 }
11342 }
11343
11344 @Experimental
11345 void unbindAll() {
11346 unbind('value');
11347 unbind('checked');
11348 Element._unbindAllElement(this);
11349 }
11350
11351
11352 @DomName('HTMLInputElement.webkitSpeechChangeEvent') 11051 @DomName('HTMLInputElement.webkitSpeechChangeEvent')
11353 @DocsEditable 11052 @DocsEditable
11354 @SupportedBrowser(SupportedBrowser.CHROME) 11053 @SupportedBrowser(SupportedBrowser.CHROME)
11355 @SupportedBrowser(SupportedBrowser.SAFARI) 11054 @SupportedBrowser(SupportedBrowser.SAFARI)
11356 @Experimental 11055 @Experimental
11357 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange'); 11056 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange');
11358 11057
11359 @DomName('HTMLInputElement.accept') 11058 @DomName('HTMLInputElement.accept')
11360 @DocsEditable 11059 @DocsEditable
11361 String accept; 11060 String accept;
(...skipping 3251 matching lines...) Expand 10 before | Expand all | Expand 10 after
14613 _this.$dom_removeChild(node); 14312 _this.$dom_removeChild(node);
14614 return true; 14313 return true;
14615 } 14314 }
14616 14315
14617 void _filter(bool test(Node node), bool removeMatching) { 14316 void _filter(bool test(Node node), bool removeMatching) {
14618 // This implementation of removeWhere/retainWhere is more efficient 14317 // This implementation of removeWhere/retainWhere is more efficient
14619 // than the default in ListBase. Child nodes can be removed in constant 14318 // than the default in ListBase. Child nodes can be removed in constant
14620 // time. 14319 // time.
14621 Node child = _this.$dom_firstChild; 14320 Node child = _this.$dom_firstChild;
14622 while (child != null) { 14321 while (child != null) {
14623 Node nextChild = child.nextNode; 14322 Node nextChild = child.nextSibling;
14624 if (test(child) == removeMatching) { 14323 if (test(child) == removeMatching) {
14625 _this.$dom_removeChild(child); 14324 _this.$dom_removeChild(child);
14626 } 14325 }
14627 child = nextChild; 14326 child = nextChild;
14628 } 14327 }
14629 } 14328 }
14630 14329
14631 void removeWhere(bool test(Node node)) { 14330 void removeWhere(bool test(Node node)) {
14632 _filter(test, true); 14331 _filter(test, true);
14633 } 14332 }
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
14738 // Should use $dom_firstChild, Bug 8886. 14437 // Should use $dom_firstChild, Bug 8886.
14739 this.insertBefore(newNodes[0], refChild); 14438 this.insertBefore(newNodes[0], refChild);
14740 } 14439 }
14741 } else { 14440 } else {
14742 for (var node in newNodes) { 14441 for (var node in newNodes) {
14743 this.insertBefore(node, refChild); 14442 this.insertBefore(node, refChild);
14744 } 14443 }
14745 } 14444 }
14746 } 14445 }
14747 14446
14447 // Note that this may either be the locally set model or a cached value
14448 // of the inherited model. This is cached to minimize model change
14449 // notifications.
14450 @Creates('Null')
14451 var _model;
14452 bool _hasLocalModel;
14453 Set<StreamController<Node>> _modelChangedStreams;
14454
14455 /**
14456 * The data model which is inherited through the tree.
14457 *
14458 * Setting this will propagate the value to all descendant nodes. If the
14459 * model is not set on this node then it will be inherited from ancestor
14460 * nodes.
14461 *
14462 * Currently this does not support propagation through Shadow DOMs.
14463 *
14464 * [clearModel] must be used to remove the model property from this node
14465 * and have the model inherit from ancestor nodes.
14466 */
14467 @Experimental
14468 get model {
14469 // If we have a change handler then we've cached the model locally.
14470 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) {
14471 return _model;
14472 }
14473 // Otherwise start looking up the tree.
14474 for (var node = this; node != null; node = node.parentNode) {
14475 if (node._hasLocalModel == true) {
14476 return node._model;
14477 }
14478 }
14479 return null;
14480 }
14481
14482 @Experimental
14483 void set model(value) {
14484 var changed = model != value;
14485 _model = value;
14486 _hasLocalModel = true;
14487 _ModelTreeObserver.initialize();
14488
14489 if (changed) {
14490 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) {
14491 _modelChangedStreams.toList().forEach((stream) => stream.add(this));
14492 }
14493 // Propagate new model to all descendants.
14494 _ModelTreeObserver.propagateModel(this, value, false);
14495 }
14496 }
14497
14498 /**
14499 * Clears the locally set model and makes this model be inherited from parent
14500 * nodes.
14501 */
14502 @Experimental
14503 void clearModel() {
14504 if (_hasLocalModel == true) {
14505 _hasLocalModel = false;
14506
14507 // Propagate new model to all descendants.
14508 if (parentNode != null) {
14509 _ModelTreeObserver.propagateModel(this, parentNode.model, false);
14510 } else {
14511 _ModelTreeObserver.propagateModel(this, null, false);
14512 }
14513 }
14514 }
14515
14516 /**
14517 * Get a stream of models, whenever the model changes.
14518 */
14519 Stream<Node> get onModelChanged {
14520 if (_modelChangedStreams == null) {
14521 _modelChangedStreams = new Set<StreamController<Node>>();
14522 }
14523 var controller;
14524 controller = new StreamController(
14525 onListen: () { _modelChangedStreams.add(controller); },
14526 onCancel: () { _modelChangedStreams.remove(controller); });
14527 return controller.stream;
14528 }
14529
14748 /** 14530 /**
14749 * Print out a String representation of this Node. 14531 * Print out a String representation of this Node.
14750 */ 14532 */
14751 String toString() => localName == null ? 14533 String toString() => localName == null ?
14752 (nodeValue == null ? super.toString() : nodeValue) : localName; 14534 (nodeValue == null ? super.toString() : nodeValue) : localName;
14753 14535
14754 /**
14755 * Binds the attribute [name] to the [path] of the [model].
14756 * Path is a String of accessors such as `foo.bar.baz`.
14757 */
14758 @Experimental
14759 void bind(String name, model, String path) {
14760 // TODO(jmesserly): should we throw instead?
14761 window.console.error('Unhandled binding to Node: '
14762 '$this $name $model $path');
14763 }
14764
14765 /** Unbinds the attribute [name]. */
14766 @Experimental
14767 void unbind(String name) {}
14768
14769 /** Unbinds all bound attributes. */
14770 @Experimental
14771 void unbindAll() {}
14772
14773 TemplateInstance _templateInstance;
14774
14775 // TODO(arv): Consider storing all "NodeRareData" on a single object?
14776 int __instanceTerminatorCount;
14777 int get _instanceTerminatorCount {
14778 if (__instanceTerminatorCount == null) return 0;
14779 return __instanceTerminatorCount;
14780 }
14781 set _instanceTerminatorCount(int value) {
14782 if (value == 0) value = null;
14783 __instanceTerminatorCount = value;
14784 }
14785
14786 /** Gets the template instance that instantiated this node, if any. */
14787 @Experimental
14788 TemplateInstance get templateInstance =>
14789 _templateInstance != null ? _templateInstance :
14790 (parent != null ? parent.templateInstance : null);
14791
14792 14536
14793 static const int ATTRIBUTE_NODE = 2; 14537 static const int ATTRIBUTE_NODE = 2;
14794 14538
14795 static const int CDATA_SECTION_NODE = 4; 14539 static const int CDATA_SECTION_NODE = 4;
14796 14540
14797 static const int COMMENT_NODE = 8; 14541 static const int COMMENT_NODE = 8;
14798 14542
14799 static const int DOCUMENT_FRAGMENT_NODE = 11; 14543 static const int DOCUMENT_FRAGMENT_NODE = 11;
14800 14544
14801 static const int DOCUMENT_NODE = 9; 14545 static const int DOCUMENT_NODE = 9;
(...skipping 3857 matching lines...) Expand 10 before | Expand all | Expand 10 after
18659 18403
18660 @JSName('insertRow') 18404 @JSName('insertRow')
18661 @DomName('HTMLTableSectionElement.insertRow') 18405 @DomName('HTMLTableSectionElement.insertRow')
18662 @DocsEditable 18406 @DocsEditable
18663 Element $dom_insertRow(int index) native; 18407 Element $dom_insertRow(int index) native;
18664 } 18408 }
18665 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 18409 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
18666 // for details. All rights reserved. Use of this source code is governed by a 18410 // for details. All rights reserved. Use of this source code is governed by a
18667 // BSD-style license that can be found in the LICENSE file. 18411 // BSD-style license that can be found in the LICENSE file.
18668 18412
18669 // WARNING: Do not edit - generated code.
18670 18413
18671 18414 @DocsEditable
18672 @Experimental
18673 @DomName('HTMLTemplateElement') 18415 @DomName('HTMLTemplateElement')
18674 @SupportedBrowser(SupportedBrowser.CHROME) 18416 @SupportedBrowser(SupportedBrowser.CHROME)
18675 @Experimental 18417 @Experimental
18676 class TemplateElement extends Element native "HTMLTemplateElement" { 18418 class TemplateElement extends Element native "HTMLTemplateElement" {
18677 18419
18678 @DomName('HTMLTemplateElement.HTMLTemplateElement') 18420 @DomName('HTMLTemplateElement.HTMLTemplateElement')
18679 @DocsEditable 18421 @DocsEditable
18680 factory TemplateElement() => document.$dom_createElement("template"); 18422 factory TemplateElement() => document.$dom_createElement("template");
18681 18423
18682 /// Checks if this type is supported on the current platform. 18424 /// Checks if this type is supported on the current platform.
18683 static bool get supported => Element.isTagSupported('template'); 18425 static bool get supported => Element.isTagSupported('template');
18684 18426
18685 @JSName('content')
18686 @DomName('HTMLTemplateElement.content') 18427 @DomName('HTMLTemplateElement.content')
18687 @DocsEditable 18428 @DocsEditable
18688 final DocumentFragment $dom_content; 18429 final DocumentFragment content;
18689
18690
18691 // For real TemplateElement use the actual DOM .content field instead of
18692 // our polyfilled expando.
18693 @Experimental
18694 DocumentFragment get content => $dom_content;
18695
18696 static StreamController<DocumentFragment> _instanceCreated;
18697
18698 /**
18699 * *Warning*: This is an implementation helper for Model-Driven Views and
18700 * should not be used in your code.
18701 *
18702 * This event is fired whenever a template is instantiated via
18703 * [createInstance].
18704 */
18705 // TODO(rafaelw): This is a hack, and is neccesary for the polyfill
18706 // because custom elements are not upgraded during clone()
18707 @Experimental
18708 static Stream<DocumentFragment> get instanceCreated {
18709 if (_instanceCreated == null) {
18710 _instanceCreated = new StreamController<DocumentFragment>();
18711 }
18712 return _instanceCreated.stream;
18713 }
18714
18715 /**
18716 * Ensures proper API and content model for template elements.
18717 *
18718 * [instanceRef] can be used to set the [Element.ref] property of [template],
18719 * and use the ref's content will be used as source when createInstance() is
18720 * invoked.
18721 *
18722 * Returns true if this template was just decorated, or false if it was
18723 * already decorated.
18724 */
18725 @Experimental
18726 static bool decorate(Element template, [Element instanceRef]) {
18727 // == true check because it starts as a null field.
18728 if (template._templateIsDecorated == true) return false;
18729
18730 template._templateIsDecorated = true;
18731
18732 _injectStylesheet();
18733
18734 // Create content
18735 if (template is! TemplateElement) {
18736 var doc = _getTemplateContentsOwner(template.document);
18737 template._templateContent = doc.createDocumentFragment();
18738 }
18739
18740 if (instanceRef != null) {
18741 template._templateInstanceRef = instanceRef;
18742 return true; // content is empty.
18743 }
18744
18745 if (template is TemplateElement) {
18746 _bootstrapTemplatesRecursivelyFrom(template.content);
18747 } else {
18748 _liftNonNativeTemplateChildrenIntoContent(template);
18749 }
18750
18751 return true;
18752 }
18753
18754 /**
18755 * This used to decorate recursively all templates from a given node.
18756 *
18757 * By default [decorate] will be called on templates lazily when certain
18758 * properties such as [model] are accessed, but it can be run eagerly to
18759 * decorate an entire tree recursively.
18760 */
18761 // TODO(rafaelw): Review whether this is the right public API.
18762 @Experimental
18763 static void bootstrap(Node content) {
18764 _bootstrapTemplatesRecursivelyFrom(content);
18765 }
18766
18767 static bool _initStyles;
18768
18769 static void _injectStylesheet() {
18770 if (_initStyles == true) return;
18771 _initStyles = true;
18772
18773 var style = new StyleElement();
18774 style.text = r'''
18775 template,
18776 thead[template],
18777 tbody[template],
18778 tfoot[template],
18779 th[template],
18780 tr[template],
18781 td[template],
18782 caption[template],
18783 colgroup[template],
18784 col[template],
18785 option[template] {
18786 display: none;
18787 }''';
18788 document.head.append(style);
18789 }
18790
18791 /**
18792 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for
18793 * more information.
18794 */
18795 @Experimental
18796 static Map<String, CustomBindingSyntax> syntax = {};
18797 } 18430 }
18798 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 18431 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
18799 // for details. All rights reserved. Use of this source code is governed by a 18432 // for details. All rights reserved. Use of this source code is governed by a
18800 // BSD-style license that can be found in the LICENSE file. 18433 // BSD-style license that can be found in the LICENSE file.
18801 18434
18802 // WARNING: Do not edit - generated code. 18435 // WARNING: Do not edit - generated code.
18803 18436
18804 18437
18805 @DomName('Text') 18438 @DomName('Text')
18806 class Text extends CharacterData native "Text" { 18439 class Text extends CharacterData native "Text" {
(...skipping 12 matching lines...) Expand all
18819 final String wholeText; 18452 final String wholeText;
18820 18453
18821 @DomName('Text.replaceWholeText') 18454 @DomName('Text.replaceWholeText')
18822 @DocsEditable 18455 @DocsEditable
18823 Text replaceWholeText(String content) native; 18456 Text replaceWholeText(String content) native;
18824 18457
18825 @DomName('Text.splitText') 18458 @DomName('Text.splitText')
18826 @DocsEditable 18459 @DocsEditable
18827 Text splitText(int offset) native; 18460 Text splitText(int offset) native;
18828 18461
18829
18830 @Creates('Null') // Set from Dart code; does not instantiate a native type.
18831 StreamSubscription _textBinding;
18832
18833 @Experimental
18834 void bind(String name, model, String path) {
18835 if (name != 'text') {
18836 super.bind(name, model, path);
18837 return;
18838 }
18839
18840 unbind('text');
18841
18842 _textBinding = new PathObserver(model, path).bindSync((value) {
18843 text = value == null ? '' : '$value';
18844 });
18845 }
18846
18847 @Experimental
18848 void unbind(String name) {
18849 if (name != 'text') {
18850 super.unbind(name);
18851 return;
18852 }
18853
18854 if (_textBinding == null) return;
18855
18856 _textBinding.cancel();
18857 _textBinding = null;
18858 }
18859
18860 @Experimental
18861 void unbindAll() {
18862 unbind('text');
18863 super.unbindAll();
18864 }
18865 } 18462 }
18866 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 18463 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
18867 // for details. All rights reserved. Use of this source code is governed by a 18464 // for details. All rights reserved. Use of this source code is governed by a
18868 // BSD-style license that can be found in the LICENSE file. 18465 // BSD-style license that can be found in the LICENSE file.
18869 18466
18870 18467
18871 @DocsEditable 18468 @DocsEditable
18872 @DomName('HTMLTextAreaElement') 18469 @DomName('HTMLTextAreaElement')
18873 class TextAreaElement extends Element native "HTMLTextAreaElement" { 18470 class TextAreaElement extends Element native "HTMLTextAreaElement" {
18874 18471
(...skipping 5555 matching lines...) Expand 10 before | Expand all | Expand 10 after
24430 * Key value used when an implementation is unable to identify another key 24027 * Key value used when an implementation is unable to identify another key
24431 * value, due to either hardware, platform, or software constraints 24028 * value, due to either hardware, platform, or software constraints
24432 */ 24029 */
24433 static const String UNIDENTIFIED = "Unidentified"; 24030 static const String UNIDENTIFIED = "Unidentified";
24434 } 24031 }
24435 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 24032 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
24436 // for details. All rights reserved. Use of this source code is governed by a 24033 // for details. All rights reserved. Use of this source code is governed by a
24437 // BSD-style license that can be found in the LICENSE file. 24034 // BSD-style license that can be found in the LICENSE file.
24438 24035
24439 24036
24440 // This code is inspired by ChangeSummary: 24037 class _ModelTreeObserver {
24441 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js 24038 static bool _initialized = false;
24442 // ...which underlies MDV. Since we don't need the functionality of
24443 // ChangeSummary, we just implement what we need for data bindings.
24444 // This allows our implementation to be much simpler.
24445
24446 // TODO(jmesserly): should we make these types stronger, and require
24447 // Observable objects? Currently, it is fine to say something like:
24448 // var path = new PathObserver(123, '');
24449 // print(path.value); // "123"
24450 //
24451 // Furthermore this degenerate case is allowed:
24452 // var path = new PathObserver(123, 'foo.bar.baz.qux');
24453 // print(path.value); // "null"
24454 //
24455 // Here we see that any invalid (i.e. not Observable) value will break the
24456 // path chain without producing an error or exception.
24457 //
24458 // Now the real question: should we do this? For the former case, the behavior
24459 // is correct but we could chose to handle it in the dart:html bindings layer.
24460 // For the latter case, it might be better to throw an error so users can find
24461 // the problem.
24462
24463
24464 // TODO(jmesserly): the primary reason to have this object exposed is because
24465 // we have get/set for value. Ideally "observePath" could just return the
24466 // stream.
24467 /**
24468 * A data-bound path starting from a view-model or model object, for example
24469 * `foo.bar.baz`.
24470 *
24471 * When the [values] stream is being listened to, this will observe changes to
24472 * the object and any intermediate object along the path, and send [values]
24473 * accordingly. When all listeners are unregistered it will stop observing
24474 * the objects.
24475 *
24476 * This class is used to implement [Node.bind] and similar functionality.
24477 */
24478 @Experimental
24479 class PathObserver {
24480 /** The object being observed. */
24481 final object;
24482
24483 /** The path string. */
24484 final String path;
24485
24486 /** True if the path is valid, otherwise false. */
24487 final bool _isValid;
24488
24489 // TODO(jmesserly): same issue here as ObservableMixin: is there an easier
24490 // way to get a broadcast stream?
24491 StreamController _values;
24492 Stream _valueStream;
24493
24494 _PropertyObserver _observer, _lastObserver;
24495
24496 Object _lastValue;
24497 bool _scheduled = false;
24498 24039
24499 /** 24040 /**
24500 * Observes [path] on [object] for changes. This returns an object that can be 24041 * Start an observer watching the document for tree changes to automatically
24501 * used to get the changes and get/set the value at this path. 24042 * propagate model changes.
24502 * See [PathObserver.values] and [PathObserver.value]. 24043 *
24044 * Currently this does not support propagation through Shadow DOMs.
24503 */ 24045 */
24504 PathObserver(this.object, String path) 24046 static void initialize() {
24505 : path = path, 24047 if (!_initialized) {
24506 _isValid = _isPathValid(path) { 24048 _initialized = true;
24507 24049
24508 // TODO(jmesserly): if the path is empty, or the object is! Observable, we 24050 if (MutationObserver.supported) {
24509 // can optimize the PathObserver to be more lightweight. 24051 var observer = new MutationObserver(_processTreeChange);
24510 24052 observer.observe(document, childList: true, subtree: true);
24511 _values = new StreamController(onListen: _observe, onCancel: _unobserve); 24053 } else {
24512 24054 document.on['DOMNodeInserted'].listen(_handleNodeInserted);
24513 if (_isValid) { 24055 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved);
24514 var segments = [];
24515 for (var segment in path.trim().split('.')) {
24516 if (segment == '') continue;
24517 var index = int.parse(segment, onError: (_) {});
24518 segments.add(index != null ? index : new Symbol(segment));
24519 }
24520
24521 // Create the property observer linked list.
24522 // Note that the structure of a path can't change after it is initially
24523 // constructed, even though the objects along the path can change.
24524 for (int i = segments.length - 1; i >= 0; i--) {
24525 _observer = new _PropertyObserver(this, segments[i], _observer);
24526 if (_lastObserver == null) _lastObserver = _observer;
24527 } 24056 }
24528 } 24057 }
24529 } 24058 }
24530 24059
24531 // TODO(jmesserly): we could try adding the first value to the stream, but 24060 static void _processTreeChange(List<MutationRecord> mutations,
24532 // that delivers the first record async. 24061 MutationObserver observer) {
24533 /** 24062 for (var record in mutations) {
24534 * Listens to the stream, and invokes the [callback] immediately with the 24063 for (var node in record.addedNodes) {
24535 * current [value]. This is useful for bindings, which want to be up-to-date 24064 // When nodes enter the document we need to make sure that all of the
24536 * immediately. 24065 // models are properly propagated through the entire sub-tree.
24537 */ 24066 propagateModel(node, _calculatedModel(node), true);
24538 StreamSubscription bindSync(void callback(value)) { 24067 }
24539 var result = values.listen(callback); 24068 for (var node in record.removedNodes) {
24540 callback(value); 24069 propagateModel(node, _calculatedModel(node), false);
24541 return result; 24070 }
24542 }
24543
24544 // TODO(jmesserly): should this be a change record with the old value?
24545 // TODO(jmesserly): should this be a broadcast stream? We only need
24546 // single-subscription in the bindings system, so single sub saves overhead.
24547 /**
24548 * Gets the stream of values that were observed at this path.
24549 * This returns a single-subscription stream.
24550 */
24551 Stream get values => _values.stream;
24552
24553 /** Force synchronous delivery of [values]. */
24554 void _deliverValues() {
24555 _scheduled = false;
24556
24557 var newValue = value;
24558 if (!identical(_lastValue, newValue)) {
24559 _values.add(newValue);
24560 _lastValue = newValue;
24561 } 24071 }
24562 } 24072 }
24563 24073
24564 void _observe() { 24074 static void _handleNodeInserted(MutationEvent e) {
24565 if (_observer != null) { 24075 var node = e.target;
24566 _lastValue = value; 24076 window.setImmediate(() {
24567 _observer.observe(); 24077 propagateModel(node, _calculatedModel(node), true);
24568 } 24078 });
24569 } 24079 }
24570 24080
24571 void _unobserve() { 24081 static void _handleNodeRemoved(MutationEvent e) {
24572 if (_observer != null) _observer.unobserve(); 24082 var node = e.target;
24083 window.setImmediate(() {
24084 propagateModel(node, _calculatedModel(node), false);
24085 });
24573 } 24086 }
24574 24087
24575 void _notifyChange() { 24088 /**
24576 if (_scheduled) return; 24089 * Figures out what the model should be for a node, avoiding any cached
24577 _scheduled = true; 24090 * model values.
24578 24091 */
24579 // TODO(jmesserly): should we have a guarenteed order with respect to other 24092 static _calculatedModel(node) {
24580 // paths? If so, we could implement this fairly easily by sorting instances 24093 if (node._hasLocalModel == true) {
24581 // of this class by birth order before delivery. 24094 return node._model;
24582 queueChangeRecords(_deliverValues); 24095 } else if (node.parentNode != null) {
24096 return node.parentNode._model;
24097 }
24098 return null;
24583 } 24099 }
24584 24100
24585 /** Gets the last reported value at this path. */ 24101 /**
24586 get value { 24102 * Pushes model changes down through the tree.
24587 if (!_isValid) return null; 24103 *
24588 if (_observer == null) return object; 24104 * Set fullTree to true if the state of the tree is unknown and model changes
24589 _observer.ensureValue(object); 24105 * should be propagated through the entire tree.
24590 return _lastObserver.value; 24106 */
24591 } 24107 static void propagateModel(Node node, model, bool fullTree) {
24592 24108 // Calling into user code with the != call could generate exceptions.
24593 /** Sets the value at this path. */ 24109 // Catch and report them a global exceptions.
24594 void set value(Object value) { 24110 try {
24595 // TODO(jmesserly): throw if property cannot be set? 24111 if (node._hasLocalModel != true && node._model != model &&
24596 // MDV seems tolerant of these error. 24112 node._modelChangedStreams != null &&
24597 if (_observer == null || !_isValid) return; 24113 !node._modelChangedStreams.isEmpty) {
24598 _observer.ensureValue(object); 24114 node._model = model;
24599 var last = _lastObserver; 24115 node._modelChangedStreams.toList()
24600 if (_setObjectProperty(last._object, last._property, value)) { 24116 .forEach((controller) => controller.add(node));
24601 // Technically, this would get updated asynchronously via a change record. 24117 }
24602 // However, it is nice if calling the getter will yield the same value 24118 } catch (e, s) {
24603 // that was just set. So we use this opportunity to update our cache. 24119 new Future.error(e, s);
24604 last.value = value;
24605 } 24120 }
24606 } 24121 for (var child = node.$dom_firstChild; child != null;
24607 } 24122 child = child.nextNode) {
24608 24123 if (child._hasLocalModel != true) {
24609 // TODO(jmesserly): these should go away in favor of mirrors! 24124 propagateModel(child, model, fullTree);
24610 _getObjectProperty(object, property) { 24125 } else if (fullTree) {
24611 if (object is List && property is int) { 24126 propagateModel(child, child._model, true);
24612 if (property >= 0 && property < object.length) {
24613 return object[property];
24614 } else {
24615 return null;
24616 }
24617 }
24618
24619 // TODO(jmesserly): what about length?
24620 if (object is Map) return object[property];
24621
24622 if (object is Observable) return object.getValueWorkaround(property);
24623
24624 return null;
24625 }
24626
24627 bool _setObjectProperty(object, property, value) {
24628 if (object is List && property is int) {
24629 object[property] = value;
24630 } else if (object is Map) {
24631 object[property] = value;
24632 } else if (object is Observable) {
24633 (object as Observable).setValueWorkaround(property, value);
24634 } else {
24635 return false;
24636 }
24637 return true;
24638 }
24639
24640
24641 class _PropertyObserver {
24642 final PathObserver _path;
24643 final _property;
24644 final Symbol _symbol;
24645 final _PropertyObserver _next;
24646
24647 // TODO(jmesserly): would be nice not to store both of these.
24648 Object _object;
24649 Object _value;
24650 StreamSubscription _sub;
24651
24652 _PropertyObserver(this._path, this._property, this._next);
24653
24654 get value => _value;
24655
24656 void set value(Object newValue) {
24657 _value = newValue;
24658 if (_next != null) {
24659 if (_sub != null) _next.unobserve();
24660 _next.ensureValue(_value);
24661 if (_sub != null) _next.observe();
24662 }
24663 }
24664
24665 void ensureValue(object) {
24666 // If we're observing, values should be up to date already.
24667 if (_sub != null) return;
24668
24669 _object = object;
24670 value = _getObjectProperty(object, _property);
24671 }
24672
24673 void observe() {
24674 if (_object is Observable) {
24675 assert(_sub == null);
24676 _sub = (_object as Observable).changes.listen(_onChange);
24677 }
24678 if (_next != null) _next.observe();
24679 }
24680
24681 void unobserve() {
24682 if (_sub == null) return;
24683
24684 _sub.cancel();
24685 _sub = null;
24686 if (_next != null) _next.unobserve();
24687 }
24688
24689 void _onChange(List<ChangeRecord> changes) {
24690 for (var change in changes) {
24691 // TODO(jmesserly): what to do about "new Symbol" here?
24692 // Ideally this would only preserve names if the user has opted in to
24693 // them being preserved.
24694 // TODO(jmesserly): should we drop observable maps with String keys?
24695 // If so then we only need one check here.
24696 if (change.changes(_property)) {
24697 value = _getObjectProperty(_object, _property);
24698 _path._notifyChange();
24699 return;
24700 } 24127 }
24701 } 24128 }
24702 } 24129 }
24703 } 24130 }
24704
24705 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
24706
24707 const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*';
24708 final _pathRegExp = new RegExp('^'
24709 '(?:#?' + _pathIndentPart + ')?'
24710 '(?:'
24711 '(?:\\.' + _pathIndentPart + ')'
24712 ')*'
24713 r'$', caseSensitive: false);
24714
24715 final _spacesRegExp = new RegExp(r'\s');
24716
24717 bool _isPathValid(String s) {
24718 s = s.replaceAll(_spacesRegExp, '');
24719
24720 if (s == '') return true;
24721 if (s[0] == '.') return false;
24722 return _pathRegExp.hasMatch(s);
24723 }
24724 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 24131 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
24725 // for details. All rights reserved. Use of this source code is governed by a 24132 // for details. All rights reserved. Use of this source code is governed by a
24726 // BSD-style license that can be found in the LICENSE file. 24133 // BSD-style license that can be found in the LICENSE file.
24727 24134
24728 24135
24729 /** 24136 /**
24730 * A utility class for representing two-dimensional positions. 24137 * A utility class for representing two-dimensional positions.
24731 */ 24138 */
24732 class Point { 24139 class Point {
24733 final num x; 24140 final num x;
(...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after
24936 * Truncates coordinates to integers and returns the result as a new 24343 * Truncates coordinates to integers and returns the result as a new
24937 * rectangle. 24344 * rectangle.
24938 */ 24345 */
24939 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), 24346 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
24940 height.toInt()); 24347 height.toInt());
24941 24348
24942 Point get topLeft => new Point(this.left, this.top); 24349 Point get topLeft => new Point(this.left, this.top);
24943 Point get bottomRight => new Point(this.left + this.width, 24350 Point get bottomRight => new Point(this.left + this.width,
24944 this.top + this.height); 24351 this.top + this.height);
24945 } 24352 }
24946 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
24947 // for details. All rights reserved. Use of this source code is governed by a
24948 // BSD-style license that can be found in the LICENSE file.
24949
24950
24951 // This code is a port of Model-Driven-Views:
24952 // https://github.com/toolkitchen/mdv
24953 // The code mostly comes from src/template_element.js
24954
24955 typedef void _ChangeHandler(value);
24956
24957 /**
24958 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
24959 * but (by design) don't attempt to implement a wide array of specialized
24960 * behaviors.
24961 *
24962 * Enabling these features in MDV is a matter of implementing and registering an
24963 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more
24964 * delegation functions which implement specialized behavior. This object is
24965 * registered with MDV via [TemplateElement.syntax]:
24966 *
24967 *
24968 * HTML:
24969 * <template bind syntax="MySyntax">
24970 * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
24971 * </template>
24972 *
24973 * Dart:
24974 * class MySyntax extends CustomBindingSyntax {
24975 * getBinding(model, path, name, node) {
24976 * // The magic happens here!
24977 * }
24978 * }
24979 *
24980 * ...
24981 *
24982 * TemplateElement.syntax['MySyntax'] = new MySyntax();
24983 *
24984 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more
24985 * information about Custom Syntax.
24986 */
24987 // TODO(jmesserly): if this is just one method, a function type would make it
24988 // more Dart-friendly.
24989 @Experimental
24990 abstract class CustomBindingSyntax {
24991 // TODO(jmesserly): I had to remove type annotations from "name" and "node"
24992 // Normally they are String and Node respectively. But sometimes it will pass
24993 // (int name, CompoundBinding node). That seems very confusing; we may want
24994 // to change this API.
24995 getBinding(model, String path, name, node);
24996 }
24997
24998 /** The callback used in the [CompoundBinding.combinator] field. */
24999 @Experimental
25000 typedef Object CompoundBindingCombinator(Map objects);
25001
25002 /** Information about the instantiated template. */
25003 @Experimental
25004 class TemplateInstance {
25005 // TODO(rafaelw): firstNode & lastNode should be read-synchronous
25006 // in cases where script has modified the template instance boundary.
25007
25008 /** The first node of this template instantiation. */
25009 final Node firstNode;
25010
25011 /**
25012 * The last node of this template instantiation.
25013 * This could be identical to [firstNode] if the template only expanded to a
25014 * single node.
25015 */
25016 final Node lastNode;
25017
25018 /** The model used to instantiate the template. */
25019 final model;
25020
25021 TemplateInstance(this.firstNode, this.lastNode, this.model);
25022 }
25023
25024 /**
25025 * Model-Driven Views contains a helper object which is useful for the
25026 * implementation of a Custom Syntax.
25027 *
25028 * var binding = new CompoundBinding((values) {
25029 * var combinedValue;
25030 * // compute combinedValue based on the current values which are provided
25031 * return combinedValue;
25032 * });
25033 * binding.bind('name1', obj1, path1);
25034 * binding.bind('name2', obj2, path2);
25035 * //...
25036 * binding.bind('nameN', objN, pathN);
25037 *
25038 * CompoundBinding is an object which knows how to listen to multiple path
25039 * values (registered via [bind]) and invoke its [combinator] when one or more
25040 * of the values have changed and set its [value] property to the return value
25041 * of the function. When any value has changed, all current values are provided
25042 * to the [combinator] in the single `values` argument.
25043 *
25044 * See [CustomBindingSyntax] for more information.
25045 */
25046 // TODO(jmesserly): what is the public API surface here? I just guessed;
25047 // most of it seemed non-public.
25048 @Experimental
25049 class CompoundBinding extends ObservableBase {
25050 CompoundBindingCombinator _combinator;
25051
25052 // TODO(jmesserly): ideally these would be String keys, but sometimes we
25053 // use integers.
25054 Map<dynamic, StreamSubscription> _bindings = new Map();
25055 Map _values = new Map();
25056 bool _scheduled = false;
25057 bool _disposed = false;
25058 Object _value;
25059
25060 CompoundBinding([CompoundBindingCombinator combinator]) {
25061 // TODO(jmesserly): this is a tweak to the original code, it seemed to me
25062 // that passing the combinator to the constructor should be equivalent to
25063 // setting it via the property.
25064 // I also added a null check to the combinator setter.
25065 this.combinator = combinator;
25066 }
25067
25068 CompoundBindingCombinator get combinator => _combinator;
25069
25070 set combinator(CompoundBindingCombinator combinator) {
25071 _combinator = combinator;
25072 if (combinator != null) _scheduleResolve();
25073 }
25074
25075 static const _VALUE = const Symbol('value');
25076
25077 get value => _value;
25078
25079 void set value(newValue) {
25080 _value = notifyPropertyChange(_VALUE, _value, newValue);
25081 }
25082
25083 // TODO(jmesserly): remove these workarounds when dart2js supports mirrors!
25084 getValueWorkaround(key) {
25085 if (key == _VALUE) return value;
25086 return null;
25087 }
25088 setValueWorkaround(key, val) {
25089 if (key == _VALUE) value = val;
25090 }
25091
25092 void bind(name, model, String path) {
25093 unbind(name);
25094
25095 _bindings[name] = new PathObserver(model, path).bindSync((value) {
25096 _values[name] = value;
25097 _scheduleResolve();
25098 });
25099 }
25100
25101 void unbind(name, {bool suppressResolve: false}) {
25102 var binding = _bindings.remove(name);
25103 if (binding == null) return;
25104
25105 binding.cancel();
25106 _values.remove(name);
25107 if (!suppressResolve) _scheduleResolve();
25108 }
25109
25110 // TODO(rafaelw): Is this the right processing model?
25111 // TODO(rafaelw): Consider having a seperate ChangeSummary for
25112 // CompoundBindings so to excess dirtyChecks.
25113 void _scheduleResolve() {
25114 if (_scheduled) return;
25115 _scheduled = true;
25116 queueChangeRecords(resolve);
25117 }
25118
25119 void resolve() {
25120 if (_disposed) return;
25121 _scheduled = false;
25122
25123 if (_combinator == null) {
25124 throw new StateError(
25125 'CompoundBinding attempted to resolve without a combinator');
25126 }
25127
25128 value = _combinator(_values);
25129 }
25130
25131 void dispose() {
25132 for (var binding in _bindings.values) {
25133 binding.cancel();
25134 }
25135 _bindings.clear();
25136 _values.clear();
25137
25138 _disposed = true;
25139 value = null;
25140 }
25141 }
25142
25143 Stream<Event> _getStreamForInputType(InputElement element) {
25144 switch (element.type) {
25145 case 'checkbox':
25146 return element.onClick;
25147 case 'radio':
25148 case 'select-multiple':
25149 case 'select-one':
25150 return element.onChange;
25151 default:
25152 return element.onInput;
25153 }
25154 }
25155
25156 abstract class _InputBinding {
25157 final InputElement element;
25158 PathObserver binding;
25159 StreamSubscription _pathSub;
25160 StreamSubscription _eventSub;
25161
25162 _InputBinding(this.element, model, String path) {
25163 binding = new PathObserver(model, path);
25164 _pathSub = binding.bindSync(valueChanged);
25165 _eventSub = _getStreamForInputType(element).listen(updateBinding);
25166 }
25167
25168 void valueChanged(newValue);
25169
25170 void updateBinding(e);
25171
25172 void unbind() {
25173 binding = null;
25174 _pathSub.cancel();
25175 _eventSub.cancel();
25176 }
25177 }
25178
25179 class _ValueBinding extends _InputBinding {
25180 _ValueBinding(element, model, path) : super(element, model, path);
25181
25182 void valueChanged(value) {
25183 element.value = value == null ? '' : '$value';
25184 }
25185
25186 void updateBinding(e) {
25187 binding.value = element.value;
25188 }
25189 }
25190
25191 // TODO(jmesserly): not sure what kind of boolean conversion rules to
25192 // apply for template data-binding. HTML attributes are true if they're present.
25193 // However Dart only treats "true" as true. Since this is HTML we'll use
25194 // something closer to the HTML rules: null (missing) and false are false,
25195 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59
25196 bool _templateBooleanConversion(value) => null != value && false != value;
25197
25198 class _CheckedBinding extends _InputBinding {
25199 _CheckedBinding(element, model, path) : super(element, model, path);
25200
25201 void valueChanged(value) {
25202 element.checked = _templateBooleanConversion(value);
25203 }
25204
25205 void updateBinding(e) {
25206 binding.value = element.checked;
25207
25208 // Only the radio button that is getting checked gets an event. We
25209 // therefore find all the associated radio buttons and update their
25210 // CheckedBinding manually.
25211 if (element is InputElement && element.type == 'radio') {
25212 for (var r in _getAssociatedRadioButtons(element)) {
25213 var checkedBinding = r._checkedBinding;
25214 if (checkedBinding != null) {
25215 // Set the value directly to avoid an infinite call stack.
25216 checkedBinding.binding.value = false;
25217 }
25218 }
25219 }
25220 }
25221 }
25222
25223 // TODO(jmesserly): polyfill document.contains API instead of doing it here
25224 bool _isNodeInDocument(Node node) {
25225 // On non-IE this works:
25226 // return node.document.contains(node);
25227 var document = node.document;
25228 if (node == document || node.parentNode == document) return true;
25229 return document.documentElement.contains(node);
25230 }
25231
25232 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
25233 // Returns an array containing all radio buttons other than |element| that
25234 // have the same |name|, either in the form that |element| belongs to or,
25235 // if no form, in the document tree to which |element| belongs.
25236 //
25237 // This implementation is based upon the HTML spec definition of a
25238 // "radio button group":
25239 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht ml#radio-button-group
25240 //
25241 Iterable _getAssociatedRadioButtons(element) {
25242 if (!_isNodeInDocument(element)) return [];
25243 if (element.form != null) {
25244 return element.form.nodes.where((el) {
25245 return el != element &&
25246 el is InputElement &&
25247 el.type == 'radio' &&
25248 el.name == element.name;
25249 });
25250 } else {
25251 var radios = element.document.queryAll(
25252 'input[type="radio"][name="${element.name}"]');
25253 return radios.where((el) => el != element && el.form == null);
25254 }
25255 }
25256
25257 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
25258 var clone = node.clone(false); // Shallow clone.
25259 if (clone is Element && clone.isTemplate) {
25260 TemplateElement.decorate(clone, node);
25261 if (syntax != null) {
25262 clone.attributes.putIfAbsent('syntax', () => syntax);
25263 }
25264 }
25265
25266 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
25267 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax));
25268 }
25269 return clone;
25270 }
25271
25272 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df n-template-contents-owner
25273 Document _getTemplateContentsOwner(Document doc) {
25274 if (doc.window == null) {
25275 return doc;
25276 }
25277 var d = doc._templateContentsOwner;
25278 if (d == null) {
25279 // TODO(arv): This should either be a Document or HTMLDocument depending
25280 // on doc.
25281 d = doc.implementation.createHtmlDocument('');
25282 while (d.$dom_lastChild != null) {
25283 d.$dom_lastChild.remove();
25284 }
25285 doc._templateContentsOwner = d;
25286 }
25287 return d;
25288 }
25289
25290 Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
25291 var clone = templateElement.clone(false);
25292 var attributes = templateElement.attributes;
25293 for (var name in attributes.keys.toList()) {
25294 switch (name) {
25295 case 'template':
25296 case 'repeat':
25297 case 'bind':
25298 case 'ref':
25299 clone.attributes.remove(name);
25300 break;
25301 default:
25302 attributes.remove(name);
25303 break;
25304 }
25305 }
25306
25307 return clone;
25308 }
25309
25310 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) {
25311 var content = templateElement.content;
25312
25313 if (!templateElement._isAttributeTemplate) {
25314 var child;
25315 while ((child = templateElement.$dom_firstChild) != null) {
25316 content.append(child);
25317 }
25318 return;
25319 }
25320
25321 // For attribute templates we copy the whole thing into the content and
25322 // we move the non template attributes into the content.
25323 //
25324 // <tr foo template>
25325 //
25326 // becomes
25327 //
25328 // <tr template>
25329 // + #document-fragment
25330 // + <tr foo>
25331 //
25332 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
25333 var child;
25334 while ((child = templateElement.$dom_firstChild) != null) {
25335 newRoot.append(child);
25336 }
25337 content.append(newRoot);
25338 }
25339
25340 void _bootstrapTemplatesRecursivelyFrom(Node node) {
25341 void bootstrap(template) {
25342 if (!TemplateElement.decorate(template)) {
25343 _bootstrapTemplatesRecursivelyFrom(template.content);
25344 }
25345 }
25346
25347 // Need to do this first as the contents may get lifted if |node| is
25348 // template.
25349 // TODO(jmesserly): node is DocumentFragment or Element
25350 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors);
25351 if (node is Element && node.isTemplate) bootstrap(node);
25352
25353 templateDescendents.forEach(bootstrap);
25354 }
25355
25356 final String _allTemplatesSelectors = 'template, option[template], ' +
25357 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
25358
25359 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) {
25360 if (node is Element) {
25361 _addAttributeBindings(node, model, syntax);
25362 } else if (node is Text) {
25363 _parseAndBind(node, node.text, 'text', model, syntax);
25364 }
25365
25366 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
25367 _addBindings(c, model, syntax);
25368 }
25369 }
25370
25371
25372 void _addAttributeBindings(Element element, model, syntax) {
25373 element.attributes.forEach((name, value) {
25374 if (value == '' && (name == 'bind' || name == 'repeat')) {
25375 value = '{{}}';
25376 }
25377 _parseAndBind(element, value, name, model, syntax);
25378 });
25379 }
25380
25381 void _parseAndBind(Node node, String text, String name, model,
25382 CustomBindingSyntax syntax) {
25383
25384 var tokens = _parseMustacheTokens(text);
25385 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
25386 return;
25387 }
25388
25389 if (tokens.length == 1 && tokens[0].isBinding) {
25390 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
25391 return;
25392 }
25393
25394 var replacementBinding = new CompoundBinding();
25395 for (var i = 0; i < tokens.length; i++) {
25396 var token = tokens[i];
25397 if (token.isBinding) {
25398 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
25399 }
25400 }
25401
25402 replacementBinding.combinator = (values) {
25403 var newValue = new StringBuffer();
25404
25405 for (var i = 0; i < tokens.length; i++) {
25406 var token = tokens[i];
25407 if (token.isText) {
25408 newValue.write(token.value);
25409 } else {
25410 var value = values[i];
25411 if (value != null) {
25412 newValue.write(value);
25413 }
25414 }
25415 }
25416
25417 return newValue.toString();
25418 };
25419
25420 node.bind(name, replacementBinding, 'value');
25421 }
25422
25423 void _bindOrDelegate(node, name, model, String path,
25424 CustomBindingSyntax syntax) {
25425
25426 if (syntax != null) {
25427 var delegateBinding = syntax.getBinding(model, path, name, node);
25428 if (delegateBinding != null) {
25429 model = delegateBinding;
25430 path = 'value';
25431 }
25432 }
25433
25434 node.bind(name, model, path);
25435 }
25436
25437 class _BindingToken {
25438 final String value;
25439 final bool isBinding;
25440
25441 _BindingToken(this.value, {this.isBinding: false});
25442
25443 bool get isText => !isBinding;
25444 }
25445
25446 List<_BindingToken> _parseMustacheTokens(String s) {
25447 var result = [];
25448 var length = s.length;
25449 var index = 0, lastIndex = 0;
25450 while (lastIndex < length) {
25451 index = s.indexOf('{{', lastIndex);
25452 if (index < 0) {
25453 result.add(new _BindingToken(s.substring(lastIndex)));
25454 break;
25455 } else {
25456 // There is a non-empty text run before the next path token.
25457 if (index > 0 && lastIndex < index) {
25458 result.add(new _BindingToken(s.substring(lastIndex, index)));
25459 }
25460 lastIndex = index + 2;
25461 index = s.indexOf('}}', lastIndex);
25462 if (index < 0) {
25463 var text = s.substring(lastIndex - 2);
25464 if (result.length > 0 && result.last.isText) {
25465 result.last.value += text;
25466 } else {
25467 result.add(new _BindingToken(text));
25468 }
25469 break;
25470 }
25471
25472 var value = s.substring(lastIndex, index).trim();
25473 result.add(new _BindingToken(value, isBinding: true));
25474 lastIndex = index + 2;
25475 }
25476 }
25477 return result;
25478 }
25479
25480 void _addTemplateInstanceRecord(fragment, model) {
25481 if (fragment.$dom_firstChild == null) {
25482 return;
25483 }
25484
25485 var instanceRecord = new TemplateInstance(
25486 fragment.$dom_firstChild, fragment.$dom_lastChild, model);
25487
25488 var node = instanceRecord.firstNode;
25489 while (node != null) {
25490 node._templateInstance = instanceRecord;
25491 node = node.nextNode;
25492 }
25493 }
25494
25495 void _removeAllBindingsRecursively(Node node) {
25496 node.unbindAll();
25497 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
25498 _removeAllBindingsRecursively(c);
25499 }
25500 }
25501
25502 void _removeTemplateChild(Node parent, Node child) {
25503 child._templateInstance = null;
25504 if (child is Element && child.isTemplate) {
25505 // Make sure we stop observing when we remove an element.
25506 var templateIterator = child._templateIterator;
25507 if (templateIterator != null) {
25508 templateIterator.abandon();
25509 child._templateIterator = null;
25510 }
25511 }
25512 child.remove();
25513 _removeAllBindingsRecursively(child);
25514 }
25515
25516 class _InstanceCursor {
25517 final Element _template;
25518 Node _terminator;
25519 Node _previousTerminator;
25520 int _previousIndex = -1;
25521 int _index = 0;
25522
25523 _InstanceCursor(this._template, [index]) {
25524 _terminator = _template;
25525 if (index != null) {
25526 while (index-- > 0) {
25527 next();
25528 }
25529 }
25530 }
25531
25532 void next() {
25533 _previousTerminator = _terminator;
25534 _previousIndex = _index;
25535 _index++;
25536
25537 while (_index > _terminator._instanceTerminatorCount) {
25538 _index -= _terminator._instanceTerminatorCount;
25539 _terminator = _terminator.nextNode;
25540 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
25541 _index += _instanceCount(_terminator);
25542 }
25543 }
25544 }
25545
25546 void abandon() {
25547 assert(_instanceCount(_template) > 0);
25548 assert(_terminator._instanceTerminatorCount > 0);
25549 assert(_index > 0);
25550
25551 _terminator._instanceTerminatorCount--;
25552 _index--;
25553 }
25554
25555 void insert(fragment) {
25556 assert(_template.parentNode != null);
25557
25558 _previousTerminator = _terminator;
25559 _previousIndex = _index;
25560 _index++;
25561
25562 _terminator = fragment.$dom_lastChild;
25563 if (_terminator == null) _terminator = _previousTerminator;
25564 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
25565
25566 _terminator._instanceTerminatorCount++;
25567 if (_terminator != _previousTerminator) {
25568 while (_previousTerminator._instanceTerminatorCount >
25569 _previousIndex) {
25570 _previousTerminator._instanceTerminatorCount--;
25571 _terminator._instanceTerminatorCount++;
25572 }
25573 }
25574 }
25575
25576 void remove() {
25577 assert(_previousIndex != -1);
25578 assert(_previousTerminator != null &&
25579 (_previousIndex > 0 || _previousTerminator == _template));
25580 assert(_terminator != null && _index > 0);
25581 assert(_template.parentNode != null);
25582 assert(_instanceCount(_template) > 0);
25583
25584 if (_previousTerminator == _terminator) {
25585 assert(_index == _previousIndex + 1);
25586 _terminator._instanceTerminatorCount--;
25587 _terminator = _template;
25588 _previousTerminator = null;
25589 _previousIndex = -1;
25590 return;
25591 }
25592
25593 _terminator._instanceTerminatorCount--;
25594
25595 var parent = _template.parentNode;
25596 while (_previousTerminator.nextNode != _terminator) {
25597 _removeTemplateChild(parent, _previousTerminator.nextNode);
25598 }
25599 _removeTemplateChild(parent, _terminator);
25600
25601 _terminator = _previousTerminator;
25602 _index = _previousIndex;
25603 _previousTerminator = null;
25604 _previousIndex = -1; // 0?
25605 }
25606 }
25607
25608
25609 class _TemplateIterator {
25610 final Element _templateElement;
25611 int instanceCount = 0;
25612 List iteratedValue;
25613 bool observing = false;
25614 final CompoundBinding inputs;
25615
25616 StreamSubscription _sub;
25617 StreamSubscription _valueBinding;
25618
25619 _TemplateIterator(this._templateElement)
25620 : inputs = new CompoundBinding(resolveInputs) {
25621
25622 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged);
25623 }
25624
25625 static Object resolveInputs(Map values) {
25626 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) {
25627 return null;
25628 }
25629
25630 if (values.containsKey('repeat')) {
25631 return values['repeat'];
25632 }
25633
25634 if (values.containsKey('bind')) {
25635 return [values['bind']];
25636 }
25637
25638 return null;
25639 }
25640
25641 void valueChanged(value) {
25642 clear();
25643 if (value is! List) return;
25644
25645 iteratedValue = value;
25646
25647 if (value is Observable) {
25648 _sub = value.changes.listen(_handleChanges);
25649 }
25650
25651 int len = iteratedValue.length;
25652 if (len > 0) {
25653 _handleChanges([new ListChangeRecord(0, addedCount: len)]);
25654 }
25655 }
25656
25657 // TODO(jmesserly): port MDV v3.
25658 getInstanceModel(model, syntax) => model;
25659 getInstanceFragment(syntax) => _templateElement.createInstance();
25660
25661 void _handleChanges(List<ListChangeRecord> splices) {
25662 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
25663
25664 for (var splice in splices) {
25665 if (splice is! ListChangeRecord) continue;
25666
25667 for (int i = 0; i < splice.removedCount; i++) {
25668 var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
25669 cursor.remove();
25670 instanceCount--;
25671 }
25672
25673 for (var addIndex = splice.index;
25674 addIndex < splice.index + splice.addedCount;
25675 addIndex++) {
25676
25677 var model = getInstanceModel(iteratedValue[addIndex], syntax);
25678 var fragment = getInstanceFragment(syntax);
25679
25680 _addBindings(fragment, model, syntax);
25681 _addTemplateInstanceRecord(fragment, model);
25682
25683 var cursor = new _InstanceCursor(_templateElement, addIndex);
25684 cursor.insert(fragment);
25685 instanceCount++;
25686 }
25687 }
25688 }
25689
25690 void unobserve() {
25691 if (_sub == null) return;
25692 _sub.cancel();
25693 _sub = null;
25694 }
25695
25696 void clear() {
25697 unobserve();
25698
25699 iteratedValue = null;
25700 if (instanceCount == 0) return;
25701
25702 for (var i = 0; i < instanceCount; i++) {
25703 var cursor = new _InstanceCursor(_templateElement, 1);
25704 cursor.remove();
25705 }
25706
25707 instanceCount = 0;
25708 }
25709
25710 void abandon() {
25711 unobserve();
25712 _valueBinding.cancel();
25713 inputs.dispose();
25714 }
25715 }
25716
25717 int _instanceCount(Element element) {
25718 var templateIterator = element._templateIterator;
25719 return templateIterator != null ? templateIterator.instanceCount : 0;
25720 }
25721 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 24353 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
25722 // for details. All rights reserved. Use of this source code is governed by a 24354 // for details. All rights reserved. Use of this source code is governed by a
25723 // BSD-style license that can be found in the LICENSE file. 24355 // BSD-style license that can be found in the LICENSE file.
25724 24356
25725 24357
25726 class _HttpRequestUtils { 24358 class _HttpRequestUtils {
25727 24359
25728 // Helper for factory HttpRequest.get 24360 // Helper for factory HttpRequest.get
25729 static HttpRequest get(String url, 24361 static HttpRequest get(String url,
25730 onComplete(HttpRequest request), 24362 onComplete(HttpRequest request),
(...skipping 763 matching lines...) Expand 10 before | Expand all | Expand 10 after
26494 DateTime _convertNativeToDart_DateTime(date) { 25126 DateTime _convertNativeToDart_DateTime(date) {
26495 var millisSinceEpoch = JS('int', '#.getTime()', date); 25127 var millisSinceEpoch = JS('int', '#.getTime()', date);
26496 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); 25128 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true);
26497 } 25129 }
26498 25130
26499 _convertDartToNative_DateTime(DateTime date) { 25131 _convertDartToNative_DateTime(DateTime date) {
26500 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); 25132 return JS('', 'new Date(#)', date.millisecondsSinceEpoch);
26501 } 25133 }
26502 25134
26503 WindowBase _convertNativeToDart_Window(win) { 25135 WindowBase _convertNativeToDart_Window(win) {
26504 if (win == null) return null;
26505 return _DOMWindowCrossFrame._createSafe(win); 25136 return _DOMWindowCrossFrame._createSafe(win);
26506 } 25137 }
26507 25138
26508 EventTarget _convertNativeToDart_EventTarget(e) { 25139 EventTarget _convertNativeToDart_EventTarget(e) {
26509 if (e == null) { 25140 if (e == null) {
26510 return null; 25141 return null;
26511 } 25142 }
26512 // Assume it's a Window if it contains the setInterval property. It may be 25143 // Assume it's a Window if it contains the setInterval property. It may be
26513 // from a different frame - without a patched prototype - so we cannot 25144 // from a different frame - without a patched prototype - so we cannot
26514 // rely on Dart type checking. 25145 // rely on Dart type checking.
(...skipping 424 matching lines...) Expand 10 before | Expand all | Expand 10 after
26939 _position = nextPosition; 25570 _position = nextPosition;
26940 return true; 25571 return true;
26941 } 25572 }
26942 _current = null; 25573 _current = null;
26943 _position = _array.length; 25574 _position = _array.length;
26944 return false; 25575 return false;
26945 } 25576 }
26946 25577
26947 T get current => _current; 25578 T get current => _current;
26948 } 25579 }
OLDNEW
« no previous file with comments | « sdk/lib/_internal/libraries.dart ('k') | sdk/lib/html/dartium/html_dartium.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698