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

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

Issue 14732003: Implement Model-Driven-Views spec for Dart (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: merged Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 /// The Dart HTML library. 1 /// The Dart HTML library.
2 library dart.dom.html; 2 library dart.dom.html;
3 3
4 import 'dart:async'; 4 import 'dart:async';
5 import 'dart:collection'; 5 import 'dart:collection';
6 import 'dart:_collection-dev'; 6 import 'dart:_collection-dev' hide Symbol;
7 import 'dart:html_common'; 7 import 'dart:html_common';
8 import 'dart:indexed_db'; 8 import 'dart:indexed_db';
9 import 'dart:isolate'; 9 import 'dart:isolate';
10 import 'dart:json' as json; 10 import 'dart:json' as json;
11 import 'dart:math'; 11 import 'dart:math';
12 import 'dart:mdv_observe_impl';
12 import 'dart:typed_data'; 13 import 'dart:typed_data';
13 import 'dart:svg' as svg; 14 import 'dart:svg' as svg;
14 import 'dart:web_audio' as web_audio; 15 import 'dart:web_audio' as web_audio;
15 import 'dart:web_gl' as gl; 16 import 'dart:web_gl' as gl;
16 import 'dart:web_sql'; 17 import 'dart:web_sql';
17 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin gBehavior, JSName, Null, Returns; 18 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin gBehavior, JSName, Null, Returns;
18 import 'dart:_isolate_helper' show IsolateNatives; 19 import 'dart:_isolate_helper' show IsolateNatives;
19 import 'dart:_foreign_helper' show JS; 20 import 'dart:_foreign_helper' show JS;
20 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 21 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
21 // for details. All rights reserved. Use of this source code is governed by a 22 // 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
6030 @DomName('Document.cookie') 6031 @DomName('Document.cookie')
6031 @DocsEditable 6032 @DocsEditable
6032 String cookie; 6033 String cookie;
6033 6034
6034 WindowBase get window => _convertNativeToDart_Window(this._get_window); 6035 WindowBase get window => _convertNativeToDart_Window(this._get_window);
6035 @JSName('defaultView') 6036 @JSName('defaultView')
6036 @DomName('Document.window') 6037 @DomName('Document.window')
6037 @DocsEditable 6038 @DocsEditable
6038 @Creates('Window|=Object') 6039 @Creates('Window|=Object')
6039 @Returns('Window|=Object') 6040 @Returns('Window|=Object')
6041 @Creates('Window|=Object|Null')
6042 @Returns('Window|=Object|Null')
6040 final dynamic _get_window; 6043 final dynamic _get_window;
6041 6044
6042 @DomName('Document.documentElement') 6045 @DomName('Document.documentElement')
6043 @DocsEditable 6046 @DocsEditable
6044 final Element documentElement; 6047 final Element documentElement;
6045 6048
6046 @DomName('Document.domain') 6049 @DomName('Document.domain')
6047 @DocsEditable 6050 @DocsEditable
6048 final String domain; 6051 final String domain;
6049 6052
(...skipping 586 matching lines...) Expand 10 before | Expand all | Expand 10 after
6636 6639
6637 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or 6640 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or
6638 // SVG strings? 6641 // SVG strings?
6639 void set innerHtml(String value) { 6642 void set innerHtml(String value) {
6640 this.nodes.clear(); 6643 this.nodes.clear();
6641 6644
6642 final e = new Element.tag("div"); 6645 final e = new Element.tag("div");
6643 e.innerHtml = value; 6646 e.innerHtml = value;
6644 6647
6645 // Copy list first since we don't want liveness during iteration. 6648 // Copy list first since we don't want liveness during iteration.
6646 List nodes = new List.from(e.nodes); 6649 List nodes = new List.from(e.nodes, growable: false);
6647 this.nodes.addAll(nodes); 6650 this.append(nodes);
6648 } 6651 }
6649 6652
6650 /** 6653 /**
6651 * Adds the specified text as a text node after the last child of this 6654 * Adds the specified text as a text node after the last child of this
6652 * document fragment. 6655 * document fragment.
6653 */ 6656 */
6654 void appendText(String text) { 6657 void appendText(String text) {
6655 this.append(new Text(text)); 6658 this.append(new Text(text));
6656 } 6659 }
6657 6660
(...skipping 360 matching lines...) Expand 10 before | Expand all | Expand 10 after
7018 if (result == null) throw new StateError("No elements"); 7021 if (result == null) throw new StateError("No elements");
7019 return result; 7022 return result;
7020 } 7023 }
7021 7024
7022 Element get single { 7025 Element get single {
7023 if (length > 1) throw new StateError("More than one element"); 7026 if (length > 1) throw new StateError("More than one element");
7024 return first; 7027 return first;
7025 } 7028 }
7026 } 7029 }
7027 7030
7028 /** 7031 /**
7029 * An immutable list containing HTML elements. This list contains some 7032 * An immutable list containing HTML elements. This list contains some
7030 * additional methods for ease of CSS manipulation on a group of elements. 7033 * additional methods for ease of CSS manipulation on a group of elements.
7031 */ 7034 */
7032 abstract class ElementList<T extends Element> extends ListBase<T> { 7035 abstract class ElementList<T extends Element> extends ListBase<T> {
7033 /** 7036 /**
7034 * The union of all CSS classes applied to the elements in this list. 7037 * The union of all CSS classes applied to the elements in this list.
7035 * 7038 *
7036 * This set makes it easy to add, remove or toggle (add if not present, remove 7039 * This set makes it easy to add, remove or toggle (add if not present, remove
7037 * if present) the classes applied to a collection of elements. 7040 * if present) the classes applied to a collection of elements.
7038 * 7041 *
(...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after
7500 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { 7503 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) {
7501 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); 7504 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors);
7502 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { 7505 } else if (JS('bool', '!!#.mozMatchesSelector', this)) {
7503 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); 7506 return JS('bool', '#.mozMatchesSelector(#)', this, selectors);
7504 } else if (JS('bool', '!!#.msMatchesSelector', this)) { 7507 } else if (JS('bool', '!!#.msMatchesSelector', this)) {
7505 return JS('bool', '#.msMatchesSelector(#)', this, selectors); 7508 return JS('bool', '#.msMatchesSelector(#)', this, selectors);
7506 } 7509 }
7507 throw new UnsupportedError("Not supported on this platform"); 7510 throw new UnsupportedError("Not supported on this platform");
7508 } 7511 }
7509 7512
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
7510 7762
7511 @DomName('Element.abortEvent') 7763 @DomName('Element.abortEvent')
7512 @DocsEditable 7764 @DocsEditable
7513 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort'); 7765 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort');
7514 7766
7515 @DomName('Element.beforecopyEvent') 7767 @DomName('Element.beforecopyEvent')
7516 @DocsEditable 7768 @DocsEditable
7517 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy'); 7769 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy');
7518 7770
7519 @DomName('Element.beforecutEvent') 7771 @DomName('Element.beforecutEvent')
(...skipping 724 matching lines...) Expand 10 before | Expand all | Expand 10 after
8244 @DomName('Element.onwebkitfullscreenchange') 8496 @DomName('Element.onwebkitfullscreenchange')
8245 @DocsEditable 8497 @DocsEditable
8246 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); 8498 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this);
8247 8499
8248 @DomName('Element.onwebkitfullscreenerror') 8500 @DomName('Element.onwebkitfullscreenerror')
8249 @DocsEditable 8501 @DocsEditable
8250 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); 8502 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this);
8251 8503
8252 } 8504 }
8253 8505
8506
8254 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); 8507 final _START_TAG_REGEXP = new RegExp('<(\\w+)');
8255 class _ElementFactoryProvider { 8508 class _ElementFactoryProvider {
8256 static const _CUSTOM_PARENT_TAG_MAP = const { 8509 static const _CUSTOM_PARENT_TAG_MAP = const {
8257 'body' : 'html', 8510 'body' : 'html',
8258 'head' : 'html', 8511 'head' : 'html',
8259 'caption' : 'table', 8512 'caption' : 'table',
8260 'td': 'tr', 8513 'td': 'tr',
8261 'th': 'tr', 8514 'th': 'tr',
8262 'colgroup': 'table', 8515 'colgroup': 'table',
8263 'col' : 'colgroup', 8516 'col' : 'colgroup',
8264 'tr' : 'tbody', 8517 'tr' : 'tbody',
8265 'tbody' : 'table', 8518 'tbody' : 'table',
8266 'tfoot' : 'table', 8519 'tfoot' : 'table',
8267 'thead' : 'table', 8520 'thead' : 'table',
8268 'track' : 'audio', 8521 'track' : 'audio',
8269 }; 8522 };
8270 8523
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
8284 @DomName('Document.createElement') 8524 @DomName('Document.createElement')
8285 static Element createElement_html(String html) { 8525 static Element createElement_html(String html) {
8286 // TODO(jacobr): this method can be made more robust and performant. 8526 // TODO(jacobr): this method can be made more robust and performant.
8287 // 1) Cache the dummy parent elements required to use innerHTML rather than 8527 // 1) Cache the dummy parent elements required to use innerHTML rather than
8288 // creating them every call. 8528 // creating them every call.
8289 // 2) Verify that the html does not contain leading or trailing text nodes. 8529 // 2) Verify that the html does not contain leading or trailing text nodes.
8290 // 3) Verify that the html does not contain both <head> and <body> tags. 8530 // 3) Verify that the html does not contain both <head> and <body> tags.
8291 // 4) Detatch the created element from its dummy parent. 8531 // 4) Detatch the created element from its dummy parent.
8292 String parentTag = 'div'; 8532 String parentTag = 'div';
8293 String tag; 8533 String tag;
8294 final match = _START_TAG_REGEXP.firstMatch(html); 8534 final match = _START_TAG_REGEXP.firstMatch(html);
8295 if (match != null) { 8535 if (match != null) {
8296 tag = match.group(1).toLowerCase(); 8536 tag = match.group(1).toLowerCase();
8297 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { 8537 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) {
8298 return _createTableForIE(html, tag); 8538 return _createTableForIE(html, tag);
8299 } 8539 }
8300 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; 8540 parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
8301 if (parentTag == null) parentTag = 'div'; 8541 if (parentTag == null) parentTag = 'div';
8302 } 8542 }
8303 8543
8304 final temp = new Element.tag(parentTag); 8544 final temp = new Element.tag(parentTag);
8305 temp.innerHtml = html; 8545 temp.innerHtml = html;
8306 8546
8307 Element element; 8547 Element element;
(...skipping 1897 matching lines...) Expand 10 before | Expand all | Expand 10 after
10205 @SupportedBrowser(SupportedBrowser.SAFARI) 10445 @SupportedBrowser(SupportedBrowser.SAFARI)
10206 @Experimental 10446 @Experimental
10207 Element get pointerLockElement => 10447 Element get pointerLockElement =>
10208 $dom_webkitPointerLockElement; 10448 $dom_webkitPointerLockElement;
10209 10449
10210 @DomName('Document.webkitVisibilityState') 10450 @DomName('Document.webkitVisibilityState')
10211 @SupportedBrowser(SupportedBrowser.CHROME) 10451 @SupportedBrowser(SupportedBrowser.CHROME)
10212 @SupportedBrowser(SupportedBrowser.SAFARI) 10452 @SupportedBrowser(SupportedBrowser.SAFARI)
10213 @Experimental 10453 @Experimental
10214 String get visibilityState => $dom_webkitVisibilityState; 10454 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;
10215 } 10460 }
10216 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 10461 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
10217 // for details. All rights reserved. Use of this source code is governed by a 10462 // for details. All rights reserved. Use of this source code is governed by a
10218 // BSD-style license that can be found in the LICENSE file. 10463 // BSD-style license that can be found in the LICENSE file.
10219 10464
10220 10465
10221 @DocsEditable 10466 @DocsEditable
10222 @DomName('HTMLHtmlElement') 10467 @DomName('HTMLHtmlElement')
10223 class HtmlElement extends Element native "HTMLHtmlElement" { 10468 class HtmlElement extends Element native "HTMLHtmlElement" {
10224 10469
(...skipping 816 matching lines...) Expand 10 before | Expand all | Expand 10 after
11041 var e = document.$dom_createElement("input"); 11286 var e = document.$dom_createElement("input");
11042 if (type != null) { 11287 if (type != null) {
11043 try { 11288 try {
11044 // IE throws an exception for unknown types. 11289 // IE throws an exception for unknown types.
11045 e.type = type; 11290 e.type = type;
11046 } catch(_) {} 11291 } catch(_) {}
11047 } 11292 }
11048 return e; 11293 return e;
11049 } 11294 }
11050 11295
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
11051 @DomName('HTMLInputElement.webkitSpeechChangeEvent') 11352 @DomName('HTMLInputElement.webkitSpeechChangeEvent')
11052 @DocsEditable 11353 @DocsEditable
11053 @SupportedBrowser(SupportedBrowser.CHROME) 11354 @SupportedBrowser(SupportedBrowser.CHROME)
11054 @SupportedBrowser(SupportedBrowser.SAFARI) 11355 @SupportedBrowser(SupportedBrowser.SAFARI)
11055 @Experimental 11356 @Experimental
11056 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange'); 11357 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange');
11057 11358
11058 @DomName('HTMLInputElement.accept') 11359 @DomName('HTMLInputElement.accept')
11059 @DocsEditable 11360 @DocsEditable
11060 String accept; 11361 String accept;
(...skipping 3251 matching lines...) Expand 10 before | Expand all | Expand 10 after
14312 _this.$dom_removeChild(node); 14613 _this.$dom_removeChild(node);
14313 return true; 14614 return true;
14314 } 14615 }
14315 14616
14316 void _filter(bool test(Node node), bool removeMatching) { 14617 void _filter(bool test(Node node), bool removeMatching) {
14317 // This implementation of removeWhere/retainWhere is more efficient 14618 // This implementation of removeWhere/retainWhere is more efficient
14318 // than the default in ListBase. Child nodes can be removed in constant 14619 // than the default in ListBase. Child nodes can be removed in constant
14319 // time. 14620 // time.
14320 Node child = _this.$dom_firstChild; 14621 Node child = _this.$dom_firstChild;
14321 while (child != null) { 14622 while (child != null) {
14322 Node nextChild = child.nextSibling; 14623 Node nextChild = child.nextNode;
14323 if (test(child) == removeMatching) { 14624 if (test(child) == removeMatching) {
14324 _this.$dom_removeChild(child); 14625 _this.$dom_removeChild(child);
14325 } 14626 }
14326 child = nextChild; 14627 child = nextChild;
14327 } 14628 }
14328 } 14629 }
14329 14630
14330 void removeWhere(bool test(Node node)) { 14631 void removeWhere(bool test(Node node)) {
14331 _filter(test, true); 14632 _filter(test, true);
14332 } 14633 }
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
14437 // Should use $dom_firstChild, Bug 8886. 14738 // Should use $dom_firstChild, Bug 8886.
14438 this.insertBefore(newNodes[0], refChild); 14739 this.insertBefore(newNodes[0], refChild);
14439 } 14740 }
14440 } else { 14741 } else {
14441 for (var node in newNodes) { 14742 for (var node in newNodes) {
14442 this.insertBefore(node, refChild); 14743 this.insertBefore(node, refChild);
14443 } 14744 }
14444 } 14745 }
14445 } 14746 }
14446 14747
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
14530 /** 14748 /**
14531 * Print out a String representation of this Node. 14749 * Print out a String representation of this Node.
14532 */ 14750 */
14533 String toString() => localName == null ? 14751 String toString() => localName == null ?
14534 (nodeValue == null ? super.toString() : nodeValue) : localName; 14752 (nodeValue == null ? super.toString() : nodeValue) : localName;
14535 14753
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
14536 14792
14537 static const int ATTRIBUTE_NODE = 2; 14793 static const int ATTRIBUTE_NODE = 2;
14538 14794
14539 static const int CDATA_SECTION_NODE = 4; 14795 static const int CDATA_SECTION_NODE = 4;
14540 14796
14541 static const int COMMENT_NODE = 8; 14797 static const int COMMENT_NODE = 8;
14542 14798
14543 static const int DOCUMENT_FRAGMENT_NODE = 11; 14799 static const int DOCUMENT_FRAGMENT_NODE = 11;
14544 14800
14545 static const int DOCUMENT_NODE = 9; 14801 static const int DOCUMENT_NODE = 9;
(...skipping 3857 matching lines...) Expand 10 before | Expand all | Expand 10 after
18403 18659
18404 @JSName('insertRow') 18660 @JSName('insertRow')
18405 @DomName('HTMLTableSectionElement.insertRow') 18661 @DomName('HTMLTableSectionElement.insertRow')
18406 @DocsEditable 18662 @DocsEditable
18407 Element $dom_insertRow(int index) native; 18663 Element $dom_insertRow(int index) native;
18408 } 18664 }
18409 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 18665 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
18410 // for details. All rights reserved. Use of this source code is governed by a 18666 // for details. All rights reserved. Use of this source code is governed by a
18411 // BSD-style license that can be found in the LICENSE file. 18667 // BSD-style license that can be found in the LICENSE file.
18412 18668
18669 // WARNING: Do not edit - generated code.
18413 18670
18414 @DocsEditable 18671
18672 @Experimental
18415 @DomName('HTMLTemplateElement') 18673 @DomName('HTMLTemplateElement')
18416 @SupportedBrowser(SupportedBrowser.CHROME) 18674 @SupportedBrowser(SupportedBrowser.CHROME)
18417 @Experimental 18675 @Experimental
18418 class TemplateElement extends Element native "HTMLTemplateElement" { 18676 class TemplateElement extends Element native "HTMLTemplateElement" {
18419 18677
18420 @DomName('HTMLTemplateElement.HTMLTemplateElement') 18678 @DomName('HTMLTemplateElement.HTMLTemplateElement')
18421 @DocsEditable 18679 @DocsEditable
18422 factory TemplateElement() => document.$dom_createElement("template"); 18680 factory TemplateElement() => document.$dom_createElement("template");
18423 18681
18424 /// Checks if this type is supported on the current platform. 18682 /// Checks if this type is supported on the current platform.
18425 static bool get supported => Element.isTagSupported('template'); 18683 static bool get supported => Element.isTagSupported('template');
18426 18684
18685 @JSName('content')
18427 @DomName('HTMLTemplateElement.content') 18686 @DomName('HTMLTemplateElement.content')
18428 @DocsEditable 18687 @DocsEditable
18429 final DocumentFragment content; 18688 final DocumentFragment $dom_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 = {};
18430 } 18797 }
18431 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 18798 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
18432 // for details. All rights reserved. Use of this source code is governed by a 18799 // for details. All rights reserved. Use of this source code is governed by a
18433 // BSD-style license that can be found in the LICENSE file. 18800 // BSD-style license that can be found in the LICENSE file.
18434 18801
18435 // WARNING: Do not edit - generated code. 18802 // WARNING: Do not edit - generated code.
18436 18803
18437 18804
18438 @DomName('Text') 18805 @DomName('Text')
18439 class Text extends CharacterData native "Text" { 18806 class Text extends CharacterData native "Text" {
(...skipping 12 matching lines...) Expand all
18452 final String wholeText; 18819 final String wholeText;
18453 18820
18454 @DomName('Text.replaceWholeText') 18821 @DomName('Text.replaceWholeText')
18455 @DocsEditable 18822 @DocsEditable
18456 Text replaceWholeText(String content) native; 18823 Text replaceWholeText(String content) native;
18457 18824
18458 @DomName('Text.splitText') 18825 @DomName('Text.splitText')
18459 @DocsEditable 18826 @DocsEditable
18460 Text splitText(int offset) native; 18827 Text splitText(int offset) native;
18461 18828
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 }
18462 } 18865 }
18463 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 18866 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
18464 // for details. All rights reserved. Use of this source code is governed by a 18867 // for details. All rights reserved. Use of this source code is governed by a
18465 // BSD-style license that can be found in the LICENSE file. 18868 // BSD-style license that can be found in the LICENSE file.
18466 18869
18467 18870
18468 @DocsEditable 18871 @DocsEditable
18469 @DomName('HTMLTextAreaElement') 18872 @DomName('HTMLTextAreaElement')
18470 class TextAreaElement extends Element native "HTMLTextAreaElement" { 18873 class TextAreaElement extends Element native "HTMLTextAreaElement" {
18471 18874
(...skipping 5558 matching lines...) Expand 10 before | Expand all | Expand 10 after
24030 * Key value used when an implementation is unable to identify another key 24433 * Key value used when an implementation is unable to identify another key
24031 * value, due to either hardware, platform, or software constraints 24434 * value, due to either hardware, platform, or software constraints
24032 */ 24435 */
24033 static const String UNIDENTIFIED = "Unidentified"; 24436 static const String UNIDENTIFIED = "Unidentified";
24034 } 24437 }
24035 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 24438 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
24036 // for details. All rights reserved. Use of this source code is governed by a 24439 // for details. All rights reserved. Use of this source code is governed by a
24037 // BSD-style license that can be found in the LICENSE file. 24440 // BSD-style license that can be found in the LICENSE file.
24038 24441
24039 24442
24040 class _ModelTreeObserver { 24443 // This code is inspired by ChangeSummary:
24041 static bool _initialized = false; 24444 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
24445 // ...which underlies MDV. Since we don't need the functionality of
24446 // ChangeSummary, we just implement what we need for data bindings.
24447 // This allows our implementation to be much simpler.
24448
24449 // TODO(jmesserly): should we make these types stronger, and require
24450 // Observable objects? Currently, it is fine to say something like:
24451 // var path = new PathObserver(123, '');
24452 // print(path.value); // "123"
24453 //
24454 // Furthermore this degenerate case is allowed:
24455 // var path = new PathObserver(123, 'foo.bar.baz.qux');
24456 // print(path.value); // "null"
24457 //
24458 // Here we see that any invalid (i.e. not Observable) value will break the
24459 // path chain without producing an error or exception.
24460 //
24461 // Now the real question: should we do this? For the former case, the behavior
24462 // is correct but we could chose to handle it in the dart:html bindings layer.
24463 // For the latter case, it might be better to throw an error so users can find
24464 // the problem.
24465
24466
24467 // TODO(jmesserly): the primary reason to have this object exposed is because
24468 // we have get/set for value. Ideally "observePath" could just return the
24469 // stream.
24470 /**
24471 * A data-bound path starting from a view-model or model object, for example
24472 * `foo.bar.baz`.
24473 *
24474 * When the [values] stream is being listened to, this will observe changes to
24475 * the object and any intermediate object along the path, and send [values]
24476 * accordingly. When all listeners are unregistered it will stop observing
24477 * the objects.
24478 *
24479 * This class is used to implement [Node.bind] and similar functionality.
24480 */
24481 @Experimental
24482 class PathObserver {
24483 /** The object being observed. */
24484 final object;
24485
24486 /** The path string. */
24487 final String path;
24488
24489 /** True if the path is valid, otherwise false. */
24490 final bool _isValid;
24491
24492 // TODO(jmesserly): same issue here as ObservableMixin: is there an easier
24493 // way to get a broadcast stream?
24494 StreamController _values;
24495 Stream _valueStream;
24496
24497 _PropertyObserver _observer, _lastObserver;
24498
24499 Object _lastValue;
24500 bool _scheduled = false;
24042 24501
24043 /** 24502 /**
24044 * Start an observer watching the document for tree changes to automatically 24503 * Observes [path] on [object] for changes. This returns an object that can be
24045 * propagate model changes. 24504 * used to get the changes and get/set the value at this path.
24046 * 24505 * See [PathObserver.values] and [PathObserver.value].
24047 * Currently this does not support propagation through Shadow DOMs.
24048 */ 24506 */
24049 static void initialize() { 24507 PathObserver(this.object, String path)
24050 if (!_initialized) { 24508 : path = path,
24051 _initialized = true; 24509 _isValid = _isPathValid(path) {
24052 24510
24053 if (MutationObserver.supported) { 24511 // TODO(jmesserly): if the path is empty, or the object is! Observable, we
24054 var observer = new MutationObserver(_processTreeChange); 24512 // can optimize the PathObserver to be more lightweight.
24055 observer.observe(document, childList: true, subtree: true); 24513
24056 } else { 24514 _values = new StreamController(onListen: _observe, onCancel: _unobserve);
24057 document.on['DOMNodeInserted'].listen(_handleNodeInserted); 24515
24058 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved); 24516 if (_isValid) {
24517 var segments = [];
24518 for (var segment in path.trim().split('.')) {
24519 if (segment == '') continue;
24520 var index = int.parse(segment, onError: (_) {});
24521 segments.add(index != null ? index : new Symbol(segment));
24059 } 24522 }
24060 } 24523
24061 } 24524 // Create the property observer linked list.
24062 24525 // Note that the structure of a path can't change after it is initially
24063 static void _processTreeChange(List<MutationRecord> mutations, 24526 // constructed, even though the objects along the path can change.
24064 MutationObserver observer) { 24527 for (int i = segments.length - 1; i >= 0; i--) {
24065 for (var record in mutations) { 24528 _observer = new _PropertyObserver(this, segments[i], _observer);
24066 for (var node in record.addedNodes) { 24529 if (_lastObserver == null) _lastObserver = _observer;
24067 // When nodes enter the document we need to make sure that all of the
24068 // models are properly propagated through the entire sub-tree.
24069 propagateModel(node, _calculatedModel(node), true);
24070 } 24530 }
24071 for (var node in record.removedNodes) { 24531 }
24072 propagateModel(node, _calculatedModel(node), false); 24532 }
24533
24534 // TODO(jmesserly): we could try adding the first value to the stream, but
24535 // that delivers the first record async.
24536 /**
24537 * Listens to the stream, and invokes the [callback] immediately with the
24538 * current [value]. This is useful for bindings, which want to be up-to-date
24539 * immediately.
24540 */
24541 StreamSubscription bindSync(void callback(value)) {
24542 var result = values.listen(callback);
24543 callback(value);
24544 return result;
24545 }
24546
24547 // TODO(jmesserly): should this be a change record with the old value?
24548 // TODO(jmesserly): should this be a broadcast stream? We only need
24549 // single-subscription in the bindings system, so single sub saves overhead.
24550 /**
24551 * Gets the stream of values that were observed at this path.
24552 * This returns a single-subscription stream.
24553 */
24554 Stream get values => _values.stream;
24555
24556 /** Force synchronous delivery of [values]. */
24557 void _deliverValues() {
24558 _scheduled = false;
24559
24560 var newValue = value;
24561 if (!identical(_lastValue, newValue)) {
24562 _values.add(newValue);
24563 _lastValue = newValue;
24564 }
24565 }
24566
24567 void _observe() {
24568 if (_observer != null) {
24569 _lastValue = value;
24570 _observer.observe();
24571 }
24572 }
24573
24574 void _unobserve() {
24575 if (_observer != null) _observer.unobserve();
24576 }
24577
24578 void _notifyChange() {
24579 if (_scheduled) return;
24580 _scheduled = true;
24581
24582 // TODO(jmesserly): should we have a guarenteed order with respect to other
24583 // paths? If so, we could implement this fairly easily by sorting instances
24584 // of this class by birth order before delivery.
24585 queueChangeRecords(_deliverValues);
24586 }
24587
24588 /** Gets the last reported value at this path. */
24589 get value {
24590 if (!_isValid) return null;
24591 if (_observer == null) return object;
24592 _observer.ensureValue(object);
24593 return _lastObserver.value;
24594 }
24595
24596 /** Sets the value at this path. */
24597 void set value(Object value) {
24598 // TODO(jmesserly): throw if property cannot be set?
24599 // MDV seems tolerant of these error.
24600 if (_observer == null || !_isValid) return;
24601 _observer.ensureValue(object);
24602 var last = _lastObserver;
24603 if (_setObjectProperty(last._object, last._property, value)) {
24604 // Technically, this would get updated asynchronously via a change record.
24605 // However, it is nice if calling the getter will yield the same value
24606 // that was just set. So we use this opportunity to update our cache.
24607 last.value = value;
24608 }
24609 }
24610 }
24611
24612 // TODO(jmesserly): these should go away in favor of mirrors!
24613 _getObjectProperty(object, property) {
24614 if (object is List && property is int) {
24615 if (property >= 0 && property < object.length) {
24616 return object[property];
24617 } else {
24618 return null;
24619 }
24620 }
24621
24622 // TODO(jmesserly): what about length?
24623 if (object is Map) return object[property];
24624
24625 if (object is Observable) return object.getValueWorkaround(property);
24626
24627 return null;
24628 }
24629
24630 bool _setObjectProperty(object, property, value) {
24631 if (object is List && property is int) {
24632 object[property] = value;
24633 } else if (object is Map) {
24634 object[property] = value;
24635 } else if (object is Observable) {
24636 (object as Observable).setValueWorkaround(property, value);
24637 } else {
24638 return false;
24639 }
24640 return true;
24641 }
24642
24643
24644 class _PropertyObserver {
24645 final PathObserver _path;
24646 final _property;
24647 final Symbol _symbol;
24648 final _PropertyObserver _next;
24649
24650 // TODO(jmesserly): would be nice not to store both of these.
24651 Object _object;
24652 Object _value;
24653 StreamSubscription _sub;
24654
24655 _PropertyObserver(this._path, this._property, this._next);
24656
24657 get value => _value;
24658
24659 void set value(Object newValue) {
24660 _value = newValue;
24661 if (_next != null) {
24662 if (_sub != null) _next.unobserve();
24663 _next.ensureValue(_value);
24664 if (_sub != null) _next.observe();
24665 }
24666 }
24667
24668 void ensureValue(object) {
24669 // If we're observing, values should be up to date already.
24670 if (_sub != null) return;
24671
24672 _object = object;
24673 value = _getObjectProperty(object, _property);
24674 }
24675
24676 void observe() {
24677 if (_object is Observable) {
24678 assert(_sub == null);
24679 _sub = (_object as Observable).changes.listen(_onChange);
24680 }
24681 if (_next != null) _next.observe();
24682 }
24683
24684 void unobserve() {
24685 if (_sub == null) return;
24686
24687 _sub.cancel();
24688 _sub = null;
24689 if (_next != null) _next.unobserve();
24690 }
24691
24692 void _onChange(List<ChangeRecord> changes) {
24693 for (var change in changes) {
24694 // TODO(jmesserly): what to do about "new Symbol" here?
24695 // Ideally this would only preserve names if the user has opted in to
24696 // them being preserved.
24697 // TODO(jmesserly): should we drop observable maps with String keys?
24698 // If so then we only need one check here.
24699 if (change.changes(_property)) {
24700 value = _getObjectProperty(_object, _property);
24701 _path._notifyChange();
24702 return;
24073 } 24703 }
24074 } 24704 }
24075 } 24705 }
24076 24706 }
24077 static void _handleNodeInserted(MutationEvent e) { 24707
24078 var node = e.target; 24708 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
24079 window.setImmediate(() { 24709
24080 propagateModel(node, _calculatedModel(node), true); 24710 const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*';
24081 }); 24711 final _pathRegExp = new RegExp('^'
24082 } 24712 '(?:#?' + _pathIndentPart + ')?'
24083 24713 '(?:'
24084 static void _handleNodeRemoved(MutationEvent e) { 24714 '(?:\\.' + _pathIndentPart + ')'
24085 var node = e.target; 24715 ')*'
24086 window.setImmediate(() { 24716 r'$', caseSensitive: false);
24087 propagateModel(node, _calculatedModel(node), false); 24717
24088 }); 24718 final _spacesRegExp = new RegExp(r'\s');
24089 } 24719
24090 24720 bool _isPathValid(String s) {
24091 /** 24721 s = s.replaceAll(_spacesRegExp, '');
24092 * Figures out what the model should be for a node, avoiding any cached 24722
24093 * model values. 24723 if (s == '') return true;
24094 */ 24724 if (s[0] == '.') return false;
24095 static _calculatedModel(node) { 24725 return _pathRegExp.hasMatch(s);
24096 if (node._hasLocalModel == true) {
24097 return node._model;
24098 } else if (node.parentNode != null) {
24099 return node.parentNode._model;
24100 }
24101 return null;
24102 }
24103
24104 /**
24105 * Pushes model changes down through the tree.
24106 *
24107 * Set fullTree to true if the state of the tree is unknown and model changes
24108 * should be propagated through the entire tree.
24109 */
24110 static void propagateModel(Node node, model, bool fullTree) {
24111 // Calling into user code with the != call could generate exceptions.
24112 // Catch and report them a global exceptions.
24113 try {
24114 if (node._hasLocalModel != true && node._model != model &&
24115 node._modelChangedStreams != null &&
24116 !node._modelChangedStreams.isEmpty) {
24117 node._model = model;
24118 node._modelChangedStreams.toList()
24119 .forEach((controller) => controller.add(node));
24120 }
24121 } catch (e, s) {
24122 new Future.error(e, s);
24123 }
24124 for (var child = node.$dom_firstChild; child != null;
24125 child = child.nextNode) {
24126 if (child._hasLocalModel != true) {
24127 propagateModel(child, model, fullTree);
24128 } else if (fullTree) {
24129 propagateModel(child, child._model, true);
24130 }
24131 }
24132 }
24133 } 24726 }
24134 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 24727 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
24135 // for details. All rights reserved. Use of this source code is governed by a 24728 // for details. All rights reserved. Use of this source code is governed by a
24136 // BSD-style license that can be found in the LICENSE file. 24729 // BSD-style license that can be found in the LICENSE file.
24137 24730
24138 24731
24139 /** 24732 /**
24140 * A utility class for representing two-dimensional positions. 24733 * A utility class for representing two-dimensional positions.
24141 */ 24734 */
24142 class Point { 24735 class Point {
(...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after
24346 * Truncates coordinates to integers and returns the result as a new 24939 * Truncates coordinates to integers and returns the result as a new
24347 * rectangle. 24940 * rectangle.
24348 */ 24941 */
24349 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), 24942 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
24350 height.toInt()); 24943 height.toInt());
24351 24944
24352 Point get topLeft => new Point(this.left, this.top); 24945 Point get topLeft => new Point(this.left, this.top);
24353 Point get bottomRight => new Point(this.left + this.width, 24946 Point get bottomRight => new Point(this.left + this.width,
24354 this.top + this.height); 24947 this.top + this.height);
24355 } 24948 }
24949 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
24950 // for details. All rights reserved. Use of this source code is governed by a
24951 // BSD-style license that can be found in the LICENSE file.
24952
24953
24954 // This code is a port of Model-Driven-Views:
24955 // https://github.com/toolkitchen/mdv
24956 // The code mostly comes from src/template_element.js
24957
24958 typedef void _ChangeHandler(value);
24959
24960 /**
24961 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
24962 * but (by design) don't attempt to implement a wide array of specialized
24963 * behaviors.
24964 *
24965 * Enabling these features in MDV is a matter of implementing and registering an
24966 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more
24967 * delegation functions which implement specialized behavior. This object is
24968 * registered with MDV via [TemplateElement.syntax]:
24969 *
24970 *
24971 * HTML:
24972 * <template bind syntax="MySyntax">
24973 * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
24974 * </template>
24975 *
24976 * Dart:
24977 * class MySyntax extends CustomBindingSyntax {
24978 * getBinding(model, path, name, node) {
24979 * // The magic happens here!
24980 * }
24981 * }
24982 *
24983 * ...
24984 *
24985 * TemplateElement.syntax['MySyntax'] = new MySyntax();
24986 *
24987 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more
24988 * information about Custom Syntax.
24989 */
24990 // TODO(jmesserly): if this is just one method, a function type would make it
24991 // more Dart-friendly.
24992 @Experimental
24993 abstract class CustomBindingSyntax {
24994 // TODO(jmesserly): I had to remove type annotations from "name" and "node"
24995 // Normally they are String and Node respectively. But sometimes it will pass
24996 // (int name, CompoundBinding node). That seems very confusing; we may want
24997 // to change this API.
24998 getBinding(model, String path, name, node);
24999 }
25000
25001 /** The callback used in the [CompoundBinding.combinator] field. */
25002 @Experimental
25003 typedef Object CompoundBindingCombinator(Map objects);
25004
25005 /** Information about the instantiated template. */
25006 @Experimental
25007 class TemplateInstance {
25008 // TODO(rafaelw): firstNode & lastNode should be read-synchronous
25009 // in cases where script has modified the template instance boundary.
25010
25011 /** The first node of this template instantiation. */
25012 final Node firstNode;
25013
25014 /**
25015 * The last node of this template instantiation.
25016 * This could be identical to [firstNode] if the template only expanded to a
25017 * single node.
25018 */
25019 final Node lastNode;
25020
25021 /** The model used to instantiate the template. */
25022 final model;
25023
25024 TemplateInstance(this.firstNode, this.lastNode, this.model);
25025 }
25026
25027 /**
25028 * Model-Driven Views contains a helper object which is useful for the
25029 * implementation of a Custom Syntax.
25030 *
25031 * var binding = new CompoundBinding((values) {
25032 * var combinedValue;
25033 * // compute combinedValue based on the current values which are provided
25034 * return combinedValue;
25035 * });
25036 * binding.bind('name1', obj1, path1);
25037 * binding.bind('name2', obj2, path2);
25038 * //...
25039 * binding.bind('nameN', objN, pathN);
25040 *
25041 * CompoundBinding is an object which knows how to listen to multiple path
25042 * values (registered via [bind]) and invoke its [combinator] when one or more
25043 * of the values have changed and set its [value] property to the return value
25044 * of the function. When any value has changed, all current values are provided
25045 * to the [combinator] in the single `values` argument.
25046 *
25047 * See [CustomBindingSyntax] for more information.
25048 */
25049 // TODO(jmesserly): what is the public API surface here? I just guessed;
25050 // most of it seemed non-public.
25051 @Experimental
25052 class CompoundBinding extends ObservableBase {
25053 CompoundBindingCombinator _combinator;
25054
25055 // TODO(jmesserly): ideally these would be String keys, but sometimes we
25056 // use integers.
25057 Map<dynamic, StreamSubscription> _bindings = new Map();
25058 Map _values = new Map();
25059 bool _scheduled = false;
25060 bool _disposed = false;
25061 Object _value;
25062
25063 CompoundBinding([CompoundBindingCombinator combinator]) {
25064 // TODO(jmesserly): this is a tweak to the original code, it seemed to me
25065 // that passing the combinator to the constructor should be equivalent to
25066 // setting it via the property.
25067 // I also added a null check to the combinator setter.
25068 this.combinator = combinator;
25069 }
25070
25071 CompoundBindingCombinator get combinator => _combinator;
25072
25073 set combinator(CompoundBindingCombinator combinator) {
25074 _combinator = combinator;
25075 if (combinator != null) _scheduleResolve();
25076 }
25077
25078 static const _VALUE = const Symbol('value');
25079
25080 get value => _value;
25081
25082 void set value(newValue) {
25083 _value = notifyPropertyChange(_VALUE, _value, newValue);
25084 }
25085
25086 // TODO(jmesserly): remove these workarounds when dart2js supports mirrors!
25087 getValueWorkaround(key) {
25088 if (key == _VALUE) return value;
25089 return null;
25090 }
25091 setValueWorkaround(key, val) {
25092 if (key == _VALUE) value = val;
25093 }
25094
25095 void bind(name, model, String path) {
25096 unbind(name);
25097
25098 _bindings[name] = new PathObserver(model, path).bindSync((value) {
25099 _values[name] = value;
25100 _scheduleResolve();
25101 });
25102 }
25103
25104 void unbind(name, {bool suppressResolve: false}) {
25105 var binding = _bindings.remove(name);
25106 if (binding == null) return;
25107
25108 binding.cancel();
25109 _values.remove(name);
25110 if (!suppressResolve) _scheduleResolve();
25111 }
25112
25113 // TODO(rafaelw): Is this the right processing model?
25114 // TODO(rafaelw): Consider having a seperate ChangeSummary for
25115 // CompoundBindings so to excess dirtyChecks.
25116 void _scheduleResolve() {
25117 if (_scheduled) return;
25118 _scheduled = true;
25119 queueChangeRecords(resolve);
25120 }
25121
25122 void resolve() {
25123 if (_disposed) return;
25124 _scheduled = false;
25125
25126 if (_combinator == null) {
25127 throw new StateError(
25128 'CompoundBinding attempted to resolve without a combinator');
25129 }
25130
25131 value = _combinator(_values);
25132 }
25133
25134 void dispose() {
25135 for (var binding in _bindings.values) {
25136 binding.cancel();
25137 }
25138 _bindings.clear();
25139 _values.clear();
25140
25141 _disposed = true;
25142 value = null;
25143 }
25144 }
25145
25146 Stream<Event> _getStreamForInputType(InputElement element) {
25147 switch (element.type) {
25148 case 'checkbox':
25149 return element.onClick;
25150 case 'radio':
25151 case 'select-multiple':
25152 case 'select-one':
25153 return element.onChange;
25154 default:
25155 return element.onInput;
25156 }
25157 }
25158
25159 abstract class _InputBinding {
25160 final InputElement element;
25161 PathObserver binding;
25162 StreamSubscription _pathSub;
25163 StreamSubscription _eventSub;
25164
25165 _InputBinding(this.element, model, String path) {
25166 binding = new PathObserver(model, path);
25167 _pathSub = binding.bindSync(valueChanged);
25168 _eventSub = _getStreamForInputType(element).listen(updateBinding);
25169 }
25170
25171 void valueChanged(newValue);
25172
25173 void updateBinding(e);
25174
25175 void unbind() {
25176 binding = null;
25177 _pathSub.cancel();
25178 _eventSub.cancel();
25179 }
25180 }
25181
25182 class _ValueBinding extends _InputBinding {
25183 _ValueBinding(element, model, path) : super(element, model, path);
25184
25185 void valueChanged(value) {
25186 element.value = value == null ? '' : '$value';
25187 }
25188
25189 void updateBinding(e) {
25190 binding.value = element.value;
25191 }
25192 }
25193
25194 // TODO(jmesserly): not sure what kind of boolean conversion rules to
25195 // apply for template data-binding. HTML attributes are true if they're present.
25196 // However Dart only treats "true" as true. Since this is HTML we'll use
25197 // something closer to the HTML rules: null (missing) and false are false,
25198 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59
25199 bool _templateBooleanConversion(value) => null != value && false != value;
25200
25201 class _CheckedBinding extends _InputBinding {
25202 _CheckedBinding(element, model, path) : super(element, model, path);
25203
25204 void valueChanged(value) {
25205 element.checked = _templateBooleanConversion(value);
25206 }
25207
25208 void updateBinding(e) {
25209 binding.value = element.checked;
25210
25211 // Only the radio button that is getting checked gets an event. We
25212 // therefore find all the associated radio buttons and update their
25213 // CheckedBinding manually.
25214 if (element is InputElement && element.type == 'radio') {
25215 for (var r in _getAssociatedRadioButtons(element)) {
25216 var checkedBinding = r._checkedBinding;
25217 if (checkedBinding != null) {
25218 // Set the value directly to avoid an infinite call stack.
25219 checkedBinding.binding.value = false;
25220 }
25221 }
25222 }
25223 }
25224 }
25225
25226 // TODO(jmesserly): polyfill document.contains API instead of doing it here
25227 bool _isNodeInDocument(Node node) {
25228 // On non-IE this works:
25229 // return node.document.contains(node);
25230 var document = node.document;
25231 if (node == document || node.parentNode == document) return true;
25232 return document.documentElement.contains(node);
25233 }
25234
25235 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
25236 // Returns an array containing all radio buttons other than |element| that
25237 // have the same |name|, either in the form that |element| belongs to or,
25238 // if no form, in the document tree to which |element| belongs.
25239 //
25240 // This implementation is based upon the HTML spec definition of a
25241 // "radio button group":
25242 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht ml#radio-button-group
25243 //
25244 Iterable _getAssociatedRadioButtons(element) {
25245 if (!_isNodeInDocument(element)) return [];
25246 if (element.form != null) {
25247 return element.form.nodes.where((el) {
25248 return el != element &&
25249 el is InputElement &&
25250 el.type == 'radio' &&
25251 el.name == element.name;
25252 });
25253 } else {
25254 var radios = element.document.queryAll(
25255 'input[type="radio"][name="${element.name}"]');
25256 return radios.where((el) => el != element && el.form == null);
25257 }
25258 }
25259
25260 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
25261 var clone = node.clone(false); // Shallow clone.
25262 if (clone is Element && clone.isTemplate) {
25263 TemplateElement.decorate(clone, node);
25264 if (syntax != null) {
25265 clone.attributes.putIfAbsent('syntax', () => syntax);
25266 }
25267 }
25268
25269 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
25270 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax));
25271 }
25272 return clone;
25273 }
25274
25275 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df n-template-contents-owner
25276 Document _getTemplateContentsOwner(Document doc) {
25277 if (doc.window == null) {
25278 return doc;
25279 }
25280 var d = doc._templateContentsOwner;
25281 if (d == null) {
25282 // TODO(arv): This should either be a Document or HTMLDocument depending
25283 // on doc.
25284 d = doc.implementation.createHtmlDocument('');
25285 while (d.$dom_lastChild != null) {
25286 d.$dom_lastChild.remove();
25287 }
25288 doc._templateContentsOwner = d;
25289 }
25290 return d;
25291 }
25292
25293 Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
25294 var clone = templateElement.clone(false);
25295 var attributes = templateElement.attributes;
25296 for (var name in attributes.keys.toList()) {
25297 switch (name) {
25298 case 'template':
25299 case 'repeat':
25300 case 'bind':
25301 case 'ref':
25302 clone.attributes.remove(name);
25303 break;
25304 default:
25305 attributes.remove(name);
25306 break;
25307 }
25308 }
25309
25310 return clone;
25311 }
25312
25313 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) {
25314 var content = templateElement.content;
25315
25316 if (!templateElement._isAttributeTemplate) {
25317 var child;
25318 while ((child = templateElement.$dom_firstChild) != null) {
25319 content.append(child);
25320 }
25321 return;
25322 }
25323
25324 // For attribute templates we copy the whole thing into the content and
25325 // we move the non template attributes into the content.
25326 //
25327 // <tr foo template>
25328 //
25329 // becomes
25330 //
25331 // <tr template>
25332 // + #document-fragment
25333 // + <tr foo>
25334 //
25335 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
25336 var child;
25337 while ((child = templateElement.$dom_firstChild) != null) {
25338 newRoot.append(child);
25339 }
25340 content.append(newRoot);
25341 }
25342
25343 void _bootstrapTemplatesRecursivelyFrom(Node node) {
25344 void bootstrap(template) {
25345 if (!TemplateElement.decorate(template)) {
25346 _bootstrapTemplatesRecursivelyFrom(template.content);
25347 }
25348 }
25349
25350 // Need to do this first as the contents may get lifted if |node| is
25351 // template.
25352 // TODO(jmesserly): node is DocumentFragment or Element
25353 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors);
25354 if (node is Element && node.isTemplate) bootstrap(node);
25355
25356 templateDescendents.forEach(bootstrap);
25357 }
25358
25359 final String _allTemplatesSelectors = 'template, option[template], ' +
25360 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
25361
25362 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) {
25363 if (node is Element) {
25364 _addAttributeBindings(node, model, syntax);
25365 } else if (node is Text) {
25366 _parseAndBind(node, node.text, 'text', model, syntax);
25367 }
25368
25369 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
25370 _addBindings(c, model, syntax);
25371 }
25372 }
25373
25374
25375 void _addAttributeBindings(Element element, model, syntax) {
25376 element.attributes.forEach((name, value) {
25377 if (value == '' && (name == 'bind' || name == 'repeat')) {
25378 value = '{{}}';
25379 }
25380 _parseAndBind(element, value, name, model, syntax);
25381 });
25382 }
25383
25384 void _parseAndBind(Node node, String text, String name, model,
25385 CustomBindingSyntax syntax) {
25386
25387 var tokens = _parseMustacheTokens(text);
25388 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
25389 return;
25390 }
25391
25392 if (tokens.length == 1 && tokens[0].isBinding) {
25393 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
25394 return;
25395 }
25396
25397 var replacementBinding = new CompoundBinding();
25398 for (var i = 0; i < tokens.length; i++) {
25399 var token = tokens[i];
25400 if (token.isBinding) {
25401 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
25402 }
25403 }
25404
25405 replacementBinding.combinator = (values) {
25406 var newValue = new StringBuffer();
25407
25408 for (var i = 0; i < tokens.length; i++) {
25409 var token = tokens[i];
25410 if (token.isText) {
25411 newValue.write(token.value);
25412 } else {
25413 var value = values[i];
25414 if (value != null) {
25415 newValue.write(value);
25416 }
25417 }
25418 }
25419
25420 return newValue.toString();
25421 };
25422
25423 node.bind(name, replacementBinding, 'value');
25424 }
25425
25426 void _bindOrDelegate(node, name, model, String path,
25427 CustomBindingSyntax syntax) {
25428
25429 if (syntax != null) {
25430 var delegateBinding = syntax.getBinding(model, path, name, node);
25431 if (delegateBinding != null) {
25432 model = delegateBinding;
25433 path = 'value';
25434 }
25435 }
25436
25437 node.bind(name, model, path);
25438 }
25439
25440 class _BindingToken {
25441 final String value;
25442 final bool isBinding;
25443
25444 _BindingToken(this.value, {this.isBinding: false});
25445
25446 bool get isText => !isBinding;
25447 }
25448
25449 List<_BindingToken> _parseMustacheTokens(String s) {
25450 var result = [];
25451 var length = s.length;
25452 var index = 0, lastIndex = 0;
25453 while (lastIndex < length) {
25454 index = s.indexOf('{{', lastIndex);
25455 if (index < 0) {
25456 result.add(new _BindingToken(s.substring(lastIndex)));
25457 break;
25458 } else {
25459 // There is a non-empty text run before the next path token.
25460 if (index > 0 && lastIndex < index) {
25461 result.add(new _BindingToken(s.substring(lastIndex, index)));
25462 }
25463 lastIndex = index + 2;
25464 index = s.indexOf('}}', lastIndex);
25465 if (index < 0) {
25466 var text = s.substring(lastIndex - 2);
25467 if (result.length > 0 && result.last.isText) {
25468 result.last.value += text;
25469 } else {
25470 result.add(new _BindingToken(text));
25471 }
25472 break;
25473 }
25474
25475 var value = s.substring(lastIndex, index).trim();
25476 result.add(new _BindingToken(value, isBinding: true));
25477 lastIndex = index + 2;
25478 }
25479 }
25480 return result;
25481 }
25482
25483 void _addTemplateInstanceRecord(fragment, model) {
25484 if (fragment.$dom_firstChild == null) {
25485 return;
25486 }
25487
25488 var instanceRecord = new TemplateInstance(
25489 fragment.$dom_firstChild, fragment.$dom_lastChild, model);
25490
25491 var node = instanceRecord.firstNode;
25492 while (node != null) {
25493 node._templateInstance = instanceRecord;
25494 node = node.nextNode;
25495 }
25496 }
25497
25498 void _removeAllBindingsRecursively(Node node) {
25499 node.unbindAll();
25500 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
25501 _removeAllBindingsRecursively(c);
25502 }
25503 }
25504
25505 void _removeTemplateChild(Node parent, Node child) {
25506 child._templateInstance = null;
25507 if (child is Element && child.isTemplate) {
25508 // Make sure we stop observing when we remove an element.
25509 var templateIterator = child._templateIterator;
25510 if (templateIterator != null) {
25511 templateIterator.abandon();
25512 child._templateIterator = null;
25513 }
25514 }
25515 child.remove();
25516 _removeAllBindingsRecursively(child);
25517 }
25518
25519 class _InstanceCursor {
25520 final Element _template;
25521 Node _terminator;
25522 Node _previousTerminator;
25523 int _previousIndex = -1;
25524 int _index = 0;
25525
25526 _InstanceCursor(this._template, [index]) {
25527 _terminator = _template;
25528 if (index != null) {
25529 while (index-- > 0) {
25530 next();
25531 }
25532 }
25533 }
25534
25535 void next() {
25536 _previousTerminator = _terminator;
25537 _previousIndex = _index;
25538 _index++;
25539
25540 while (_index > _terminator._instanceTerminatorCount) {
25541 _index -= _terminator._instanceTerminatorCount;
25542 _terminator = _terminator.nextNode;
25543 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
25544 _index += _instanceCount(_terminator);
25545 }
25546 }
25547 }
25548
25549 void abandon() {
25550 assert(_instanceCount(_template) > 0);
25551 assert(_terminator._instanceTerminatorCount > 0);
25552 assert(_index > 0);
25553
25554 _terminator._instanceTerminatorCount--;
25555 _index--;
25556 }
25557
25558 void insert(fragment) {
25559 assert(_template.parentNode != null);
25560
25561 _previousTerminator = _terminator;
25562 _previousIndex = _index;
25563 _index++;
25564
25565 _terminator = fragment.$dom_lastChild;
25566 if (_terminator == null) _terminator = _previousTerminator;
25567 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
25568
25569 _terminator._instanceTerminatorCount++;
25570 if (_terminator != _previousTerminator) {
25571 while (_previousTerminator._instanceTerminatorCount >
25572 _previousIndex) {
25573 _previousTerminator._instanceTerminatorCount--;
25574 _terminator._instanceTerminatorCount++;
25575 }
25576 }
25577 }
25578
25579 void remove() {
25580 assert(_previousIndex != -1);
25581 assert(_previousTerminator != null &&
25582 (_previousIndex > 0 || _previousTerminator == _template));
25583 assert(_terminator != null && _index > 0);
25584 assert(_template.parentNode != null);
25585 assert(_instanceCount(_template) > 0);
25586
25587 if (_previousTerminator == _terminator) {
25588 assert(_index == _previousIndex + 1);
25589 _terminator._instanceTerminatorCount--;
25590 _terminator = _template;
25591 _previousTerminator = null;
25592 _previousIndex = -1;
25593 return;
25594 }
25595
25596 _terminator._instanceTerminatorCount--;
25597
25598 var parent = _template.parentNode;
25599 while (_previousTerminator.nextNode != _terminator) {
25600 _removeTemplateChild(parent, _previousTerminator.nextNode);
25601 }
25602 _removeTemplateChild(parent, _terminator);
25603
25604 _terminator = _previousTerminator;
25605 _index = _previousIndex;
25606 _previousTerminator = null;
25607 _previousIndex = -1; // 0?
25608 }
25609 }
25610
25611
25612 class _TemplateIterator {
25613 final Element _templateElement;
25614 int instanceCount = 0;
25615 List iteratedValue;
25616 bool observing = false;
25617 final CompoundBinding inputs;
25618
25619 StreamSubscription _sub;
25620 StreamSubscription _valueBinding;
25621
25622 _TemplateIterator(this._templateElement)
25623 : inputs = new CompoundBinding(resolveInputs) {
25624
25625 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged);
25626 }
25627
25628 static Object resolveInputs(Map values) {
25629 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) {
25630 return null;
25631 }
25632
25633 if (values.containsKey('repeat')) {
25634 return values['repeat'];
25635 }
25636
25637 if (values.containsKey('bind')) {
25638 return [values['bind']];
25639 }
25640
25641 return null;
25642 }
25643
25644 void valueChanged(value) {
25645 clear();
25646 if (value is! List) return;
25647
25648 iteratedValue = value;
25649
25650 if (value is Observable) {
25651 _sub = value.changes.listen(_handleChanges);
25652 }
25653
25654 int len = iteratedValue.length;
25655 if (len > 0) {
25656 _handleChanges([new ListChangeRecord(0, addedCount: len)]);
25657 }
25658 }
25659
25660 // TODO(jmesserly): port MDV v3.
25661 getInstanceModel(model, syntax) => model;
25662 getInstanceFragment(syntax) => _templateElement.createInstance();
25663
25664 void _handleChanges(List<ListChangeRecord> splices) {
25665 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
25666
25667 for (var splice in splices) {
25668 if (splice is! ListChangeRecord) continue;
25669
25670 for (int i = 0; i < splice.removedCount; i++) {
25671 var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
25672 cursor.remove();
25673 instanceCount--;
25674 }
25675
25676 for (var addIndex = splice.index;
25677 addIndex < splice.index + splice.addedCount;
25678 addIndex++) {
25679
25680 var model = getInstanceModel(iteratedValue[addIndex], syntax);
25681 var fragment = getInstanceFragment(syntax);
25682
25683 _addBindings(fragment, model, syntax);
25684 _addTemplateInstanceRecord(fragment, model);
25685
25686 var cursor = new _InstanceCursor(_templateElement, addIndex);
25687 cursor.insert(fragment);
25688 instanceCount++;
25689 }
25690 }
25691 }
25692
25693 void unobserve() {
25694 if (_sub == null) return;
25695 _sub.cancel();
25696 _sub = null;
25697 }
25698
25699 void clear() {
25700 unobserve();
25701
25702 iteratedValue = null;
25703 if (instanceCount == 0) return;
25704
25705 for (var i = 0; i < instanceCount; i++) {
25706 var cursor = new _InstanceCursor(_templateElement, 1);
25707 cursor.remove();
25708 }
25709
25710 instanceCount = 0;
25711 }
25712
25713 void abandon() {
25714 unobserve();
25715 _valueBinding.cancel();
25716 inputs.dispose();
25717 }
25718 }
25719
25720 int _instanceCount(Element element) {
25721 var templateIterator = element._templateIterator;
25722 return templateIterator != null ? templateIterator.instanceCount : 0;
25723 }
24356 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 25724 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
24357 // for details. All rights reserved. Use of this source code is governed by a 25725 // for details. All rights reserved. Use of this source code is governed by a
24358 // BSD-style license that can be found in the LICENSE file. 25726 // BSD-style license that can be found in the LICENSE file.
24359 25727
24360 25728
24361 class _HttpRequestUtils { 25729 class _HttpRequestUtils {
24362 25730
24363 // Helper for factory HttpRequest.get 25731 // Helper for factory HttpRequest.get
24364 static HttpRequest get(String url, 25732 static HttpRequest get(String url,
24365 onComplete(HttpRequest request), 25733 onComplete(HttpRequest request),
(...skipping 763 matching lines...) Expand 10 before | Expand all | Expand 10 after
25129 DateTime _convertNativeToDart_DateTime(date) { 26497 DateTime _convertNativeToDart_DateTime(date) {
25130 var millisSinceEpoch = JS('int', '#.getTime()', date); 26498 var millisSinceEpoch = JS('int', '#.getTime()', date);
25131 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); 26499 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true);
25132 } 26500 }
25133 26501
25134 _convertDartToNative_DateTime(DateTime date) { 26502 _convertDartToNative_DateTime(DateTime date) {
25135 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); 26503 return JS('', 'new Date(#)', date.millisecondsSinceEpoch);
25136 } 26504 }
25137 26505
25138 WindowBase _convertNativeToDart_Window(win) { 26506 WindowBase _convertNativeToDart_Window(win) {
26507 if (win == null) return null;
25139 return _DOMWindowCrossFrame._createSafe(win); 26508 return _DOMWindowCrossFrame._createSafe(win);
25140 } 26509 }
25141 26510
25142 EventTarget _convertNativeToDart_EventTarget(e) { 26511 EventTarget _convertNativeToDart_EventTarget(e) {
25143 if (e == null) { 26512 if (e == null) {
25144 return null; 26513 return null;
25145 } 26514 }
25146 // Assume it's a Window if it contains the setInterval property. It may be 26515 // Assume it's a Window if it contains the setInterval property. It may be
25147 // from a different frame - without a patched prototype - so we cannot 26516 // from a different frame - without a patched prototype - so we cannot
25148 // rely on Dart type checking. 26517 // rely on Dart type checking.
(...skipping 424 matching lines...) Expand 10 before | Expand all | Expand 10 after
25573 _position = nextPosition; 26942 _position = nextPosition;
25574 return true; 26943 return true;
25575 } 26944 }
25576 _current = null; 26945 _current = null;
25577 _position = _array.length; 26946 _position = _array.length;
25578 return false; 26947 return false;
25579 } 26948 }
25580 26949
25581 T get current => _current; 26950 T get current => _current;
25582 } 26951 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698