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

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: try upload again Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 /// The Dart HTML library. 1 /// The Dart HTML library.
2 library dart.dom.html; 2 library dart.dom.html;
3 3
4 import 'dart:async'; 4 import 'dart:async';
5 import 'dart:collection'; 5 import 'dart:collection';
6 import 'dart:_collection-dev'; 6 import 'dart:_collection-dev' hide Symbol;
7 import 'dart:html_common'; 7 import 'dart:html_common';
8 import 'dart:indexed_db'; 8 import 'dart:indexed_db';
9 import 'dart:isolate'; 9 import 'dart:isolate';
10 import 'dart:json' as json; 10 import 'dart:json' as json;
11 import 'dart:math'; 11 import 'dart:math';
12 import 'dart: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.nodes.addAll(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 }
(...skipping 361 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 5555 matching lines...) Expand 10 before | Expand all | Expand 10 after
24027 * Key value used when an implementation is unable to identify another key 24430 * Key value used when an implementation is unable to identify another key
24028 * value, due to either hardware, platform, or software constraints 24431 * value, due to either hardware, platform, or software constraints
24029 */ 24432 */
24030 static const String UNIDENTIFIED = "Unidentified"; 24433 static const String UNIDENTIFIED = "Unidentified";
24031 } 24434 }
24032 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 24435 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
24033 // for details. All rights reserved. Use of this source code is governed by a 24436 // for details. All rights reserved. Use of this source code is governed by a
24034 // BSD-style license that can be found in the LICENSE file. 24437 // BSD-style license that can be found in the LICENSE file.
24035 24438
24036 24439
24037 class _ModelTreeObserver { 24440 // This code is inspired by ChangeSummary:
24038 static bool _initialized = false; 24441 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
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 /**
24465 * A data-bound path starting from a view-model or model object, for example
24466 * `foo.bar.baz`.
24467 *
24468 * When the [values] stream is being listened to, this will observe changes to
24469 * the object and any intermediate object along the path, and send [values]
24470 * accordingly. When all listeners are unregistered it will stop observing
24471 * the objects.
24472 *
24473 * This class is used to implement [Node.bind] and similar functionality.
24474 */
24475 // TODO(jmesserly): find a better home for this type.
24476 @Experimental
24477 class PathObserver {
24478 /** The object being observed. */
24479 final object;
24480
24481 /** The path string. */
24482 final String path;
24483
24484 /** True if the path is valid, otherwise false. */
24485 final bool _isValid;
24486
24487 // TODO(jmesserly): same issue here as ObservableMixin: is there an easier
24488 // way to get a broadcast stream?
24489 StreamController _values;
24490 Stream _valueStream;
24491
24492 _PropertyObserver _observer, _lastObserver;
24493
24494 Object _lastValue;
24495 bool _scheduled = false;
24039 24496
24040 /** 24497 /**
24041 * Start an observer watching the document for tree changes to automatically 24498 * Observes [path] on [object] for changes. This returns an object that can be
24042 * propagate model changes. 24499 * used to get the changes and get/set the value at this path.
24043 * 24500 * See [PathObserver.values] and [PathObserver.value].
24044 * Currently this does not support propagation through Shadow DOMs.
24045 */ 24501 */
24046 static void initialize() { 24502 PathObserver(this.object, String path)
24047 if (!_initialized) { 24503 : path = path, _isValid = _isPathValid(path) {
24048 _initialized = true; 24504
24049 24505 // TODO(jmesserly): if the path is empty, or the object is! Observable, we
24050 if (MutationObserver.supported) { 24506 // can optimize the PathObserver to be more lightweight.
24051 var observer = new MutationObserver(_processTreeChange); 24507
24052 observer.observe(document, childList: true, subtree: true); 24508 _values = new StreamController(onListen: _observe, onCancel: _unobserve);
24053 } else { 24509
24054 document.on['DOMNodeInserted'].listen(_handleNodeInserted); 24510 if (_isValid) {
24055 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved); 24511 var segments = [];
24512 for (var segment in path.trim().split('.')) {
24513 if (segment == '') continue;
24514 var index = int.parse(segment, onError: (_) {});
24515 segments.add(index != null ? index : new Symbol(segment));
24056 } 24516 }
24057 } 24517
24058 } 24518 // Create the property observer linked list.
24059 24519 // Note that the structure of a path can't change after it is initially
24060 static void _processTreeChange(List<MutationRecord> mutations, 24520 // constructed, even though the objects along the path can change.
24061 MutationObserver observer) { 24521 for (int i = segments.length - 1; i >= 0; i--) {
24062 for (var record in mutations) { 24522 _observer = new _PropertyObserver(this, segments[i], _observer);
24063 for (var node in record.addedNodes) { 24523 if (_lastObserver == null) _lastObserver = _observer;
24064 // When nodes enter the document we need to make sure that all of the
24065 // models are properly propagated through the entire sub-tree.
24066 propagateModel(node, _calculatedModel(node), true);
24067 } 24524 }
24068 for (var node in record.removedNodes) { 24525 }
24069 propagateModel(node, _calculatedModel(node), false); 24526 }
24527
24528 // TODO(jmesserly): we could try adding the first value to the stream, but
24529 // that delivers the first record async.
24530 /**
24531 * Listens to the stream, and invokes the [callback] immediately with the
24532 * current [value]. This is useful for bindings, which want to be up-to-date
24533 * immediately.
24534 */
24535 StreamSubscription bindSync(void callback(value)) {
24536 var result = values.listen(callback);
24537 callback(value);
24538 return result;
24539 }
24540
24541 // TODO(jmesserly): should this be a change record with the old value?
24542 // TODO(jmesserly): should this be a broadcast stream? We only need
24543 // single-subscription in the bindings system, so single sub saves overhead.
24544 /**
24545 * Gets the stream of values that were observed at this path.
24546 * This returns a single-subscription stream.
24547 */
24548 Stream get values => _values.stream;
24549
24550 /** Force synchronous delivery of [values]. */
24551 void _deliverValues() {
24552 _scheduled = false;
24553
24554 var newValue = value;
24555 if (!identical(_lastValue, newValue)) {
24556 _values.add(newValue);
24557 _lastValue = newValue;
24558 }
24559 }
24560
24561 void _observe() {
24562 if (_observer != null) {
24563 _lastValue = value;
24564 _observer.observe();
24565 }
24566 }
24567
24568 void _unobserve() {
24569 if (_observer != null) _observer.unobserve();
24570 }
24571
24572 void _notifyChange() {
24573 if (_scheduled) return;
24574 _scheduled = true;
24575
24576 // TODO(jmesserly): should we have a guarenteed order with respect to other
24577 // paths? If so, we could implement this fairly easily by sorting instances
24578 // of this class by birth order before delivery.
24579 queueChangeRecords(_deliverValues);
24580 }
24581
24582 /** Gets the last reported value at this path. */
24583 get value {
24584 if (!_isValid) return null;
24585 if (_observer == null) return object;
24586 _observer.ensureValue(object);
24587 return _lastObserver.value;
24588 }
24589
24590 /** Sets the value at this path. */
24591 void set value(Object value) {
24592 // TODO(jmesserly): throw if property cannot be set?
24593 // MDV seems tolerant of these error.
24594 if (_observer == null || !_isValid) return;
24595 _observer.ensureValue(object);
24596 var last = _lastObserver;
24597 if (_setObjectProperty(last._object, last._property, value)) {
24598 // Technically, this would get updated asynchronously via a change record.
24599 // However, it is nice if calling the getter will yield the same value
24600 // that was just set. So we use this opportunity to update our cache.
24601 last.value = value;
24602 }
24603 }
24604 }
24605
24606 // TODO(jmesserly): these should go away in favor of mirrors!
24607 _getObjectProperty(object, property) {
24608 if (object is List && property is int) {
24609 if (property >= 0 && property < object.length) {
24610 return object[property];
24611 } else {
24612 return null;
24613 }
24614 }
24615
24616 // TODO(jmesserly): what about length?
24617 if (object is Map) return object[property];
24618
24619 if (object is Observable) return object.getValueWorkaround(property);
24620
24621 return null;
24622 }
24623
24624 bool _setObjectProperty(object, property, value) {
24625 if (object is List && property is int) {
24626 object[property] = value;
24627 } else if (object is Map) {
24628 object[property] = value;
24629 } else if (object is Observable) {
24630 (object as Observable).setValueWorkaround(property, value);
24631 } else {
24632 return false;
24633 }
24634 return true;
24635 }
24636
24637
24638 class _PropertyObserver {
24639 final PathObserver _path;
24640 final _property;
24641 final _PropertyObserver _next;
24642
24643 // TODO(jmesserly): would be nice not to store both of these.
24644 Object _object;
24645 Object _value;
24646 StreamSubscription _sub;
24647
24648 _PropertyObserver(this._path, this._property, this._next);
24649
24650 get value => _value;
24651
24652 void set value(Object newValue) {
24653 _value = newValue;
24654 if (_next != null) {
24655 if (_sub != null) _next.unobserve();
24656 _next.ensureValue(_value);
24657 if (_sub != null) _next.observe();
24658 }
24659 }
24660
24661 void ensureValue(object) {
24662 // If we're observing, values should be up to date already.
24663 if (_sub != null) return;
24664
24665 _object = object;
24666 value = _getObjectProperty(object, _property);
24667 }
24668
24669 void observe() {
24670 if (_object is Observable) {
24671 assert(_sub == null);
24672 _sub = (_object as Observable).changes.listen(_onChange);
24673 }
24674 if (_next != null) _next.observe();
24675 }
24676
24677 void unobserve() {
24678 if (_sub == null) return;
24679
24680 _sub.cancel();
24681 _sub = null;
24682 if (_next != null) _next.unobserve();
24683 }
24684
24685 void _onChange(List<ChangeRecord> changes) {
24686 for (var change in changes) {
24687 // TODO(jmesserly): what to do about "new Symbol" here?
24688 // Ideally this would only preserve names if the user has opted in to
24689 // them being preserved.
24690 // TODO(jmesserly): should we drop observable maps with String keys?
24691 // If so then we only need one check here.
24692 if (change.changes(_property)) {
24693 value = _getObjectProperty(_object, _property);
24694 _path._notifyChange();
24695 return;
24070 } 24696 }
24071 } 24697 }
24072 } 24698 }
24073 24699 }
24074 static void _handleNodeInserted(MutationEvent e) { 24700
24075 var node = e.target; 24701 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
24076 window.setImmediate(() { 24702
24077 propagateModel(node, _calculatedModel(node), true); 24703 const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*';
24078 }); 24704 final _pathRegExp = new RegExp('^'
24079 } 24705 '(?:#?' + _pathIndentPart + ')?'
24080 24706 '(?:'
24081 static void _handleNodeRemoved(MutationEvent e) { 24707 '(?:\\.' + _pathIndentPart + ')'
24082 var node = e.target; 24708 ')*'
24083 window.setImmediate(() { 24709 r'$', caseSensitive: false);
24084 propagateModel(node, _calculatedModel(node), false); 24710
24085 }); 24711 final _spacesRegExp = new RegExp(r'\s');
24086 } 24712
24087 24713 bool _isPathValid(String s) {
24088 /** 24714 s = s.replaceAll(_spacesRegExp, '');
24089 * Figures out what the model should be for a node, avoiding any cached 24715
24090 * model values. 24716 if (s == '') return true;
24091 */ 24717 if (s[0] == '.') return false;
24092 static _calculatedModel(node) { 24718 return _pathRegExp.hasMatch(s);
24093 if (node._hasLocalModel == true) {
24094 return node._model;
24095 } else if (node.parentNode != null) {
24096 return node.parentNode._model;
24097 }
24098 return null;
24099 }
24100
24101 /**
24102 * Pushes model changes down through the tree.
24103 *
24104 * Set fullTree to true if the state of the tree is unknown and model changes
24105 * should be propagated through the entire tree.
24106 */
24107 static void propagateModel(Node node, model, bool fullTree) {
24108 // Calling into user code with the != call could generate exceptions.
24109 // Catch and report them a global exceptions.
24110 try {
24111 if (node._hasLocalModel != true && node._model != model &&
24112 node._modelChangedStreams != null &&
24113 !node._modelChangedStreams.isEmpty) {
24114 node._model = model;
24115 node._modelChangedStreams.toList()
24116 .forEach((controller) => controller.add(node));
24117 }
24118 } catch (e, s) {
24119 new Future.error(e, s);
24120 }
24121 for (var child = node.$dom_firstChild; child != null;
24122 child = child.nextNode) {
24123 if (child._hasLocalModel != true) {
24124 propagateModel(child, model, fullTree);
24125 } else if (fullTree) {
24126 propagateModel(child, child._model, true);
24127 }
24128 }
24129 }
24130 } 24719 }
24131 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 24720 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
24132 // for details. All rights reserved. Use of this source code is governed by a 24721 // for details. All rights reserved. Use of this source code is governed by a
24133 // BSD-style license that can be found in the LICENSE file. 24722 // BSD-style license that can be found in the LICENSE file.
24134 24723
24135 24724
24136 /** 24725 /**
24137 * A utility class for representing two-dimensional positions. 24726 * A utility class for representing two-dimensional positions.
24138 */ 24727 */
24139 class Point { 24728 class Point {
(...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after
24343 * Truncates coordinates to integers and returns the result as a new 24932 * Truncates coordinates to integers and returns the result as a new
24344 * rectangle. 24933 * rectangle.
24345 */ 24934 */
24346 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), 24935 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
24347 height.toInt()); 24936 height.toInt());
24348 24937
24349 Point get topLeft => new Point(this.left, this.top); 24938 Point get topLeft => new Point(this.left, this.top);
24350 Point get bottomRight => new Point(this.left + this.width, 24939 Point get bottomRight => new Point(this.left + this.width,
24351 this.top + this.height); 24940 this.top + this.height);
24352 } 24941 }
24942 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
24943 // for details. All rights reserved. Use of this source code is governed by a
24944 // BSD-style license that can be found in the LICENSE file.
24945
24946
24947 // This code is a port of Model-Driven-Views:
24948 // https://github.com/toolkitchen/mdv
24949 // The code mostly comes from src/template_element.js
24950
24951 typedef void _ChangeHandler(value);
24952
24953 /**
24954 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
24955 * but (by design) don't attempt to implement a wide array of specialized
24956 * behaviors.
24957 *
24958 * Enabling these features in MDV is a matter of implementing and registering an
24959 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more
24960 * delegation functions which implement specialized behavior. This object is
24961 * registered with MDV via [TemplateElement.syntax]:
24962 *
24963 *
24964 * HTML:
24965 * <template bind syntax="MySyntax">
24966 * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
24967 * </template>
24968 *
24969 * Dart:
24970 * class MySyntax extends CustomBindingSyntax {
24971 * getBinding(model, path, name, node) {
24972 * // The magic happens here!
24973 * }
24974 * }
24975 *
24976 * ...
24977 *
24978 * TemplateElement.syntax['MySyntax'] = new MySyntax();
24979 *
24980 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more
24981 * information about Custom Syntax.
24982 */
24983 // TODO(jmesserly): if this is just one method, a function type would make it
24984 // more Dart-friendly.
24985 @Experimental
24986 abstract class CustomBindingSyntax {
24987 // TODO(jmesserly): I had to remove type annotations from "name" and "node"
24988 // Normally they are String and Node respectively. But sometimes it will pass
24989 // (int name, CompoundBinding node). That seems very confusing; we may want
24990 // to change this API.
24991 getBinding(model, String path, name, node);
24992 }
24993
24994 /** The callback used in the [CompoundBinding.combinator] field. */
24995 @Experimental
24996 typedef Object CompoundBindingCombinator(Map objects);
24997
24998 /** Information about the instantiated template. */
24999 @Experimental
25000 class TemplateInstance {
25001 // TODO(rafaelw): firstNode & lastNode should be read-synchronous
25002 // in cases where script has modified the template instance boundary.
25003
25004 /** The first node of this template instantiation. */
25005 final Node firstNode;
25006
25007 /**
25008 * The last node of this template instantiation.
25009 * This could be identical to [firstNode] if the template only expanded to a
25010 * single node.
25011 */
25012 final Node lastNode;
25013
25014 /** The model used to instantiate the template. */
25015 final model;
25016
25017 TemplateInstance(this.firstNode, this.lastNode, this.model);
25018 }
25019
25020 /**
25021 * Model-Driven Views contains a helper object which is useful for the
25022 * implementation of a Custom Syntax.
25023 *
25024 * var binding = new CompoundBinding((values) {
25025 * var combinedValue;
25026 * // compute combinedValue based on the current values which are provided
25027 * return combinedValue;
25028 * });
25029 * binding.bind('name1', obj1, path1);
25030 * binding.bind('name2', obj2, path2);
25031 * //...
25032 * binding.bind('nameN', objN, pathN);
25033 *
25034 * CompoundBinding is an object which knows how to listen to multiple path
25035 * values (registered via [bind]) and invoke its [combinator] when one or more
25036 * of the values have changed and set its [value] property to the return value
25037 * of the function. When any value has changed, all current values are provided
25038 * to the [combinator] in the single `values` argument.
25039 *
25040 * See [CustomBindingSyntax] for more information.
25041 */
25042 // TODO(jmesserly): what is the public API surface here? I just guessed;
25043 // most of it seemed non-public.
25044 @Experimental
25045 class CompoundBinding extends ObservableBase {
25046 CompoundBindingCombinator _combinator;
25047
25048 // TODO(jmesserly): ideally these would be String keys, but sometimes we
25049 // use integers.
25050 Map<dynamic, StreamSubscription> _bindings = new Map();
25051 Map _values = new Map();
25052 bool _scheduled = false;
25053 bool _disposed = false;
25054 Object _value;
25055
25056 CompoundBinding([CompoundBindingCombinator combinator]) {
25057 // TODO(jmesserly): this is a tweak to the original code, it seemed to me
25058 // that passing the combinator to the constructor should be equivalent to
25059 // setting it via the property.
25060 // I also added a null check to the combinator setter.
25061 this.combinator = combinator;
25062 }
25063
25064 CompoundBindingCombinator get combinator => _combinator;
25065
25066 set combinator(CompoundBindingCombinator combinator) {
25067 _combinator = combinator;
25068 if (combinator != null) _scheduleResolve();
25069 }
25070
25071 static const _VALUE = const Symbol('value');
25072
25073 get value => _value;
25074
25075 void set value(newValue) {
25076 _value = notifyPropertyChange(_VALUE, _value, newValue);
25077 }
25078
25079 // TODO(jmesserly): remove these workarounds when dart2js supports mirrors!
25080 getValueWorkaround(key) {
25081 if (key == _VALUE) return value;
25082 return null;
25083 }
25084 setValueWorkaround(key, val) {
25085 if (key == _VALUE) value = val;
25086 }
25087
25088 void bind(name, model, String path) {
25089 unbind(name);
25090
25091 _bindings[name] = new PathObserver(model, path).bindSync((value) {
25092 _values[name] = value;
25093 _scheduleResolve();
25094 });
25095 }
25096
25097 void unbind(name, {bool suppressResolve: false}) {
25098 var binding = _bindings.remove(name);
25099 if (binding == null) return;
25100
25101 binding.cancel();
25102 _values.remove(name);
25103 if (!suppressResolve) _scheduleResolve();
25104 }
25105
25106 // TODO(rafaelw): Is this the right processing model?
25107 // TODO(rafaelw): Consider having a seperate ChangeSummary for
25108 // CompoundBindings so to excess dirtyChecks.
25109 void _scheduleResolve() {
25110 if (_scheduled) return;
25111 _scheduled = true;
25112 queueChangeRecords(resolve);
25113 }
25114
25115 void resolve() {
25116 if (_disposed) return;
25117 _scheduled = false;
25118
25119 if (_combinator == null) {
25120 throw new StateError(
25121 'CompoundBinding attempted to resolve without a combinator');
25122 }
25123
25124 value = _combinator(_values);
25125 }
25126
25127 void dispose() {
25128 for (var binding in _bindings.values) {
25129 binding.cancel();
25130 }
25131 _bindings.clear();
25132 _values.clear();
25133
25134 _disposed = true;
25135 value = null;
25136 }
25137 }
25138
25139 Stream<Event> _getStreamForInputType(InputElement element) {
25140 switch (element.type) {
25141 case 'checkbox':
25142 return element.onClick;
25143 case 'radio':
25144 case 'select-multiple':
25145 case 'select-one':
25146 return element.onChange;
25147 default:
25148 return element.onInput;
25149 }
25150 }
25151
25152 abstract class _InputBinding {
25153 final InputElement element;
25154 PathObserver binding;
25155 StreamSubscription _pathSub;
25156 StreamSubscription _eventSub;
25157
25158 _InputBinding(this.element, model, String path) {
25159 binding = new PathObserver(model, path);
25160 _pathSub = binding.bindSync(valueChanged);
25161 _eventSub = _getStreamForInputType(element).listen(updateBinding);
25162 }
25163
25164 void valueChanged(newValue);
25165
25166 void updateBinding(e);
25167
25168 void unbind() {
25169 binding = null;
25170 _pathSub.cancel();
25171 _eventSub.cancel();
25172 }
25173 }
25174
25175 class _ValueBinding extends _InputBinding {
25176 _ValueBinding(element, model, path) : super(element, model, path);
25177
25178 void valueChanged(value) {
25179 element.value = value == null ? '' : '$value';
25180 }
25181
25182 void updateBinding(e) {
25183 binding.value = element.value;
25184 }
25185 }
25186
25187 // TODO(jmesserly): not sure what kind of boolean conversion rules to
25188 // apply for template data-binding. HTML attributes are true if they're present.
25189 // However Dart only treats "true" as true. Since this is HTML we'll use
25190 // something closer to the HTML rules: null (missing) and false are false,
25191 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59
25192 bool _templateBooleanConversion(value) => null != value && false != value;
25193
25194 class _CheckedBinding extends _InputBinding {
25195 _CheckedBinding(element, model, path) : super(element, model, path);
25196
25197 void valueChanged(value) {
25198 element.checked = _templateBooleanConversion(value);
25199 }
25200
25201 void updateBinding(e) {
25202 binding.value = element.checked;
25203
25204 // Only the radio button that is getting checked gets an event. We
25205 // therefore find all the associated radio buttons and update their
25206 // CheckedBinding manually.
25207 if (element is InputElement && element.type == 'radio') {
25208 for (var r in _getAssociatedRadioButtons(element)) {
25209 var checkedBinding = r._checkedBinding;
25210 if (checkedBinding != null) {
25211 // Set the value directly to avoid an infinite call stack.
25212 checkedBinding.binding.value = false;
25213 }
25214 }
25215 }
25216 }
25217 }
25218
25219 // TODO(jmesserly): polyfill document.contains API instead of doing it here
25220 bool _isNodeInDocument(Node node) {
25221 // On non-IE this works:
25222 // return node.document.contains(node);
25223 var document = node.document;
25224 if (node == document || node.parentNode == document) return true;
25225 return document.documentElement.contains(node);
25226 }
25227
25228 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
25229 // Returns an array containing all radio buttons other than |element| that
25230 // have the same |name|, either in the form that |element| belongs to or,
25231 // if no form, in the document tree to which |element| belongs.
25232 //
25233 // This implementation is based upon the HTML spec definition of a
25234 // "radio button group":
25235 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht ml#radio-button-group
25236 //
25237 Iterable _getAssociatedRadioButtons(element) {
25238 if (!_isNodeInDocument(element)) return [];
25239 if (element.form != null) {
25240 return element.form.nodes.where((el) {
25241 return el != element &&
25242 el is InputElement &&
25243 el.type == 'radio' &&
25244 el.name == element.name;
25245 });
25246 } else {
25247 var radios = element.document.queryAll(
25248 'input[type="radio"][name="${element.name}"]');
25249 return radios.where((el) => el != element && el.form == null);
25250 }
25251 }
25252
25253 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
25254 var clone = node.clone(false); // Shallow clone.
25255 if (clone is Element && clone.isTemplate) {
25256 TemplateElement.decorate(clone, node);
25257 if (syntax != null) {
25258 clone.attributes.putIfAbsent('syntax', () => syntax);
25259 }
25260 }
25261
25262 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
25263 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax));
25264 }
25265 return clone;
25266 }
25267
25268 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df n-template-contents-owner
25269 Document _getTemplateContentsOwner(Document doc) {
25270 if (doc.window == null) {
25271 return doc;
25272 }
25273 var d = doc._templateContentsOwner;
25274 if (d == null) {
25275 // TODO(arv): This should either be a Document or HTMLDocument depending
25276 // on doc.
25277 d = doc.implementation.createHtmlDocument('');
25278 while (d.$dom_lastChild != null) {
25279 d.$dom_lastChild.remove();
25280 }
25281 doc._templateContentsOwner = d;
25282 }
25283 return d;
25284 }
25285
25286 Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
25287 var clone = templateElement.clone(false);
25288 var attributes = templateElement.attributes;
25289 for (var name in attributes.keys.toList()) {
25290 switch (name) {
25291 case 'template':
25292 case 'repeat':
25293 case 'bind':
25294 case 'ref':
25295 clone.attributes.remove(name);
25296 break;
25297 default:
25298 attributes.remove(name);
25299 break;
25300 }
25301 }
25302
25303 return clone;
25304 }
25305
25306 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) {
25307 var content = templateElement.content;
25308
25309 if (!templateElement._isAttributeTemplate) {
25310 var child;
25311 while ((child = templateElement.$dom_firstChild) != null) {
25312 content.append(child);
25313 }
25314 return;
25315 }
25316
25317 // For attribute templates we copy the whole thing into the content and
25318 // we move the non template attributes into the content.
25319 //
25320 // <tr foo template>
25321 //
25322 // becomes
25323 //
25324 // <tr template>
25325 // + #document-fragment
25326 // + <tr foo>
25327 //
25328 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
25329 var child;
25330 while ((child = templateElement.$dom_firstChild) != null) {
25331 newRoot.append(child);
25332 }
25333 content.append(newRoot);
25334 }
25335
25336 void _bootstrapTemplatesRecursivelyFrom(Node node) {
25337 void bootstrap(template) {
25338 if (!TemplateElement.decorate(template)) {
25339 _bootstrapTemplatesRecursivelyFrom(template.content);
25340 }
25341 }
25342
25343 // Need to do this first as the contents may get lifted if |node| is
25344 // template.
25345 // TODO(jmesserly): node is DocumentFragment or Element
25346 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors);
25347 if (node is Element && node.isTemplate) bootstrap(node);
25348
25349 templateDescendents.forEach(bootstrap);
25350 }
25351
25352 final String _allTemplatesSelectors = 'template, option[template], ' +
25353 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
25354
25355 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) {
25356 if (node is Element) {
25357 _addAttributeBindings(node, model, syntax);
25358 } else if (node is Text) {
25359 _parseAndBind(node, node.text, 'text', model, syntax);
25360 }
25361
25362 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
25363 _addBindings(c, model, syntax);
25364 }
25365 }
25366
25367
25368 void _addAttributeBindings(Element element, model, syntax) {
25369 element.attributes.forEach((name, value) {
25370 if (value == '' && (name == 'bind' || name == 'repeat')) {
25371 value = '{{}}';
25372 }
25373 _parseAndBind(element, value, name, model, syntax);
25374 });
25375 }
25376
25377 void _parseAndBind(Node node, String text, String name, model,
25378 CustomBindingSyntax syntax) {
25379
25380 var tokens = _parseMustacheTokens(text);
25381 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
25382 return;
25383 }
25384
25385 if (tokens.length == 1 && tokens[0].isBinding) {
25386 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
25387 return;
25388 }
25389
25390 var replacementBinding = new CompoundBinding();
25391 for (var i = 0; i < tokens.length; i++) {
25392 var token = tokens[i];
25393 if (token.isBinding) {
25394 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
25395 }
25396 }
25397
25398 replacementBinding.combinator = (values) {
25399 var newValue = new StringBuffer();
25400
25401 for (var i = 0; i < tokens.length; i++) {
25402 var token = tokens[i];
25403 if (token.isText) {
25404 newValue.write(token.value);
25405 } else {
25406 var value = values[i];
25407 if (value != null) {
25408 newValue.write(value);
25409 }
25410 }
25411 }
25412
25413 return newValue.toString();
25414 };
25415
25416 node.bind(name, replacementBinding, 'value');
25417 }
25418
25419 void _bindOrDelegate(node, name, model, String path,
25420 CustomBindingSyntax syntax) {
25421
25422 if (syntax != null) {
25423 var delegateBinding = syntax.getBinding(model, path, name, node);
25424 if (delegateBinding != null) {
25425 model = delegateBinding;
25426 path = 'value';
25427 }
25428 }
25429
25430 node.bind(name, model, path);
25431 }
25432
25433 class _BindingToken {
25434 final String value;
25435 final bool isBinding;
25436
25437 _BindingToken(this.value, {this.isBinding: false});
25438
25439 bool get isText => !isBinding;
25440 }
25441
25442 List<_BindingToken> _parseMustacheTokens(String s) {
25443 var result = [];
25444 var length = s.length;
25445 var index = 0, lastIndex = 0;
25446 while (lastIndex < length) {
25447 index = s.indexOf('{{', lastIndex);
25448 if (index < 0) {
25449 result.add(new _BindingToken(s.substring(lastIndex)));
25450 break;
25451 } else {
25452 // There is a non-empty text run before the next path token.
25453 if (index > 0 && lastIndex < index) {
25454 result.add(new _BindingToken(s.substring(lastIndex, index)));
25455 }
25456 lastIndex = index + 2;
25457 index = s.indexOf('}}', lastIndex);
25458 if (index < 0) {
25459 var text = s.substring(lastIndex - 2);
25460 if (result.length > 0 && result.last.isText) {
25461 result.last.value += text;
25462 } else {
25463 result.add(new _BindingToken(text));
25464 }
25465 break;
25466 }
25467
25468 var value = s.substring(lastIndex, index).trim();
25469 result.add(new _BindingToken(value, isBinding: true));
25470 lastIndex = index + 2;
25471 }
25472 }
25473 return result;
25474 }
25475
25476 void _addTemplateInstanceRecord(fragment, model) {
25477 if (fragment.$dom_firstChild == null) {
25478 return;
25479 }
25480
25481 var instanceRecord = new TemplateInstance(
25482 fragment.$dom_firstChild, fragment.$dom_lastChild, model);
25483
25484 var node = instanceRecord.firstNode;
25485 while (node != null) {
25486 node._templateInstance = instanceRecord;
25487 node = node.nextNode;
25488 }
25489 }
25490
25491 void _removeAllBindingsRecursively(Node node) {
25492 node.unbindAll();
25493 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
25494 _removeAllBindingsRecursively(c);
25495 }
25496 }
25497
25498 void _removeTemplateChild(Node parent, Node child) {
25499 child._templateInstance = null;
25500 if (child is Element && child.isTemplate) {
25501 // Make sure we stop observing when we remove an element.
25502 var templateIterator = child._templateIterator;
25503 if (templateIterator != null) {
25504 templateIterator.abandon();
25505 child._templateIterator = null;
25506 }
25507 }
25508 child.remove();
25509 _removeAllBindingsRecursively(child);
25510 }
25511
25512 class _InstanceCursor {
25513 final Element _template;
25514 Node _terminator;
25515 Node _previousTerminator;
25516 int _previousIndex = -1;
25517 int _index = 0;
25518
25519 _InstanceCursor(this._template, [index]) {
25520 _terminator = _template;
25521 if (index != null) {
25522 while (index-- > 0) {
25523 next();
25524 }
25525 }
25526 }
25527
25528 void next() {
25529 _previousTerminator = _terminator;
25530 _previousIndex = _index;
25531 _index++;
25532
25533 while (_index > _terminator._instanceTerminatorCount) {
25534 _index -= _terminator._instanceTerminatorCount;
25535 _terminator = _terminator.nextNode;
25536 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
25537 _index += _instanceCount(_terminator);
25538 }
25539 }
25540 }
25541
25542 void abandon() {
25543 assert(_instanceCount(_template) > 0);
25544 assert(_terminator._instanceTerminatorCount > 0);
25545 assert(_index > 0);
25546
25547 _terminator._instanceTerminatorCount--;
25548 _index--;
25549 }
25550
25551 void insert(fragment) {
25552 assert(_template.parentNode != null);
25553
25554 _previousTerminator = _terminator;
25555 _previousIndex = _index;
25556 _index++;
25557
25558 _terminator = fragment.$dom_lastChild;
25559 if (_terminator == null) _terminator = _previousTerminator;
25560 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
25561
25562 _terminator._instanceTerminatorCount++;
25563 if (_terminator != _previousTerminator) {
25564 while (_previousTerminator._instanceTerminatorCount >
25565 _previousIndex) {
25566 _previousTerminator._instanceTerminatorCount--;
25567 _terminator._instanceTerminatorCount++;
25568 }
25569 }
25570 }
25571
25572 void remove() {
25573 assert(_previousIndex != -1);
25574 assert(_previousTerminator != null &&
25575 (_previousIndex > 0 || _previousTerminator == _template));
25576 assert(_terminator != null && _index > 0);
25577 assert(_template.parentNode != null);
25578 assert(_instanceCount(_template) > 0);
25579
25580 if (_previousTerminator == _terminator) {
25581 assert(_index == _previousIndex + 1);
25582 _terminator._instanceTerminatorCount--;
25583 _terminator = _template;
25584 _previousTerminator = null;
25585 _previousIndex = -1;
25586 return;
25587 }
25588
25589 _terminator._instanceTerminatorCount--;
25590
25591 var parent = _template.parentNode;
25592 while (_previousTerminator.nextNode != _terminator) {
25593 _removeTemplateChild(parent, _previousTerminator.nextNode);
25594 }
25595 _removeTemplateChild(parent, _terminator);
25596
25597 _terminator = _previousTerminator;
25598 _index = _previousIndex;
25599 _previousTerminator = null;
25600 _previousIndex = -1; // 0?
25601 }
25602 }
25603
25604
25605 class _TemplateIterator {
25606 final Element _templateElement;
25607 int instanceCount = 0;
25608 List iteratedValue;
25609 bool observing = false;
25610 final CompoundBinding inputs;
25611
25612 StreamSubscription _sub;
25613 StreamSubscription _valueBinding;
25614
25615 _TemplateIterator(this._templateElement)
25616 : inputs = new CompoundBinding(resolveInputs) {
25617
25618 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged);
25619 }
25620
25621 static Object resolveInputs(Map values) {
25622 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) {
25623 return null;
25624 }
25625
25626 if (values.containsKey('repeat')) {
25627 return values['repeat'];
25628 }
25629
25630 if (values.containsKey('bind')) {
25631 return [values['bind']];
25632 }
25633
25634 return null;
25635 }
25636
25637 void valueChanged(value) {
25638 clear();
25639 if (value is! List) return;
25640
25641 iteratedValue = value;
25642
25643 if (value is Observable) {
25644 _sub = value.changes.listen(_handleChanges);
25645 }
25646
25647 int len = iteratedValue.length;
25648 if (len > 0) {
25649 _handleChanges([new ListChangeRecord(0, addedCount: len)]);
25650 }
25651 }
25652
25653 // TODO(jmesserly): port MDV v3.
25654 getInstanceModel(model, syntax) => model;
25655 getInstanceFragment(syntax) => _templateElement.createInstance();
25656
25657 void _handleChanges(List<ListChangeRecord> splices) {
25658 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
25659
25660 for (var splice in splices) {
25661 if (splice is! ListChangeRecord) continue;
25662
25663 for (int i = 0; i < splice.removedCount; i++) {
25664 var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
25665 cursor.remove();
25666 instanceCount--;
25667 }
25668
25669 for (var addIndex = splice.index;
25670 addIndex < splice.index + splice.addedCount;
25671 addIndex++) {
25672
25673 var model = getInstanceModel(iteratedValue[addIndex], syntax);
25674 var fragment = getInstanceFragment(syntax);
25675
25676 _addBindings(fragment, model, syntax);
25677 _addTemplateInstanceRecord(fragment, model);
25678
25679 var cursor = new _InstanceCursor(_templateElement, addIndex);
25680 cursor.insert(fragment);
25681 instanceCount++;
25682 }
25683 }
25684 }
25685
25686 void unobserve() {
25687 if (_sub == null) return;
25688 _sub.cancel();
25689 _sub = null;
25690 }
25691
25692 void clear() {
25693 unobserve();
25694
25695 iteratedValue = null;
25696 if (instanceCount == 0) return;
25697
25698 for (var i = 0; i < instanceCount; i++) {
25699 var cursor = new _InstanceCursor(_templateElement, 1);
25700 cursor.remove();
25701 }
25702
25703 instanceCount = 0;
25704 }
25705
25706 void abandon() {
25707 unobserve();
25708 _valueBinding.cancel();
25709 inputs.dispose();
25710 }
25711 }
25712
25713 int _instanceCount(Element element) {
25714 var templateIterator = element._templateIterator;
25715 return templateIterator != null ? templateIterator.instanceCount : 0;
25716 }
24353 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 25717 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
24354 // for details. All rights reserved. Use of this source code is governed by a 25718 // for details. All rights reserved. Use of this source code is governed by a
24355 // BSD-style license that can be found in the LICENSE file. 25719 // BSD-style license that can be found in the LICENSE file.
24356 25720
24357 25721
24358 class _HttpRequestUtils { 25722 class _HttpRequestUtils {
24359 25723
24360 // Helper for factory HttpRequest.get 25724 // Helper for factory HttpRequest.get
24361 static HttpRequest get(String url, 25725 static HttpRequest get(String url,
24362 onComplete(HttpRequest request), 25726 onComplete(HttpRequest request),
(...skipping 763 matching lines...) Expand 10 before | Expand all | Expand 10 after
25126 DateTime _convertNativeToDart_DateTime(date) { 26490 DateTime _convertNativeToDart_DateTime(date) {
25127 var millisSinceEpoch = JS('int', '#.getTime()', date); 26491 var millisSinceEpoch = JS('int', '#.getTime()', date);
25128 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); 26492 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true);
25129 } 26493 }
25130 26494
25131 _convertDartToNative_DateTime(DateTime date) { 26495 _convertDartToNative_DateTime(DateTime date) {
25132 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); 26496 return JS('', 'new Date(#)', date.millisecondsSinceEpoch);
25133 } 26497 }
25134 26498
25135 WindowBase _convertNativeToDart_Window(win) { 26499 WindowBase _convertNativeToDart_Window(win) {
26500 if (win == null) return null;
25136 return _DOMWindowCrossFrame._createSafe(win); 26501 return _DOMWindowCrossFrame._createSafe(win);
25137 } 26502 }
25138 26503
25139 EventTarget _convertNativeToDart_EventTarget(e) { 26504 EventTarget _convertNativeToDart_EventTarget(e) {
25140 if (e == null) { 26505 if (e == null) {
25141 return null; 26506 return null;
25142 } 26507 }
25143 // Assume it's a Window if it contains the setInterval property. It may be 26508 // Assume it's a Window if it contains the setInterval property. It may be
25144 // from a different frame - without a patched prototype - so we cannot 26509 // from a different frame - without a patched prototype - so we cannot
25145 // rely on Dart type checking. 26510 // rely on Dart type checking.
(...skipping 424 matching lines...) Expand 10 before | Expand all | Expand 10 after
25570 _position = nextPosition; 26935 _position = nextPosition;
25571 return true; 26936 return true;
25572 } 26937 }
25573 _current = null; 26938 _current = null;
25574 _position = _array.length; 26939 _position = _array.length;
25575 return false; 26940 return false;
25576 } 26941 }
25577 26942
25578 T get current => _current; 26943 T get current => _current;
25579 } 26944 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698