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

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: small fix 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';
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:observe';
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 5965 matching lines...) Expand 10 before | Expand all | Expand 10 after
5987 @DomName('Document.cookie') 5988 @DomName('Document.cookie')
5988 @DocsEditable 5989 @DocsEditable
5989 String cookie; 5990 String cookie;
5990 5991
5991 WindowBase get window => _convertNativeToDart_Window(this._get_window); 5992 WindowBase get window => _convertNativeToDart_Window(this._get_window);
5992 @JSName('defaultView') 5993 @JSName('defaultView')
5993 @DomName('Document.window') 5994 @DomName('Document.window')
5994 @DocsEditable 5995 @DocsEditable
5995 @Creates('Window|=Object') 5996 @Creates('Window|=Object')
5996 @Returns('Window|=Object') 5997 @Returns('Window|=Object')
5998 @Creates('Window|=Object|Null')
5999 @Returns('Window|=Object|Null')
5997 final dynamic _get_window; 6000 final dynamic _get_window;
5998 6001
5999 @DomName('Document.documentElement') 6002 @DomName('Document.documentElement')
6000 @DocsEditable 6003 @DocsEditable
6001 final Element documentElement; 6004 final Element documentElement;
6002 6005
6003 @DomName('Document.domain') 6006 @DomName('Document.domain')
6004 @DocsEditable 6007 @DocsEditable
6005 final String domain; 6008 final String domain;
6006 6009
(...skipping 586 matching lines...) Expand 10 before | Expand all | Expand 10 after
6593 6596
6594 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or 6597 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or
6595 // SVG strings? 6598 // SVG strings?
6596 void set innerHtml(String value) { 6599 void set innerHtml(String value) {
6597 this.nodes.clear(); 6600 this.nodes.clear();
6598 6601
6599 final e = new Element.tag("div"); 6602 final e = new Element.tag("div");
6600 e.innerHtml = value; 6603 e.innerHtml = value;
6601 6604
6602 // Copy list first since we don't want liveness during iteration. 6605 // Copy list first since we don't want liveness during iteration.
6603 List nodes = new List.from(e.nodes); 6606 List nodes = new List.from(e.nodes, growable: false);
6604 this.nodes.addAll(nodes); 6607 this.append(nodes);
6605 } 6608 }
6606 6609
6607 /** 6610 /**
6608 * Adds the specified text as a text node after the last child of this 6611 * Adds the specified text as a text node after the last child of this
6609 * document fragment. 6612 * document fragment.
6610 */ 6613 */
6611 void appendText(String text) { 6614 void appendText(String text) {
6612 this.append(new Text(text)); 6615 this.append(new Text(text));
6613 } 6616 }
6614 6617
(...skipping 1792 matching lines...) Expand 10 before | Expand all | Expand 10 after
8407 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { 8410 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) {
8408 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); 8411 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors);
8409 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { 8412 } else if (JS('bool', '!!#.mozMatchesSelector', this)) {
8410 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); 8413 return JS('bool', '#.mozMatchesSelector(#)', this, selectors);
8411 } else if (JS('bool', '!!#.msMatchesSelector', this)) { 8414 } else if (JS('bool', '!!#.msMatchesSelector', this)) {
8412 return JS('bool', '#.msMatchesSelector(#)', this, selectors); 8415 return JS('bool', '#.msMatchesSelector(#)', this, selectors);
8413 } 8416 }
8414 throw new UnsupportedError("Not supported on this platform"); 8417 throw new UnsupportedError("Not supported on this platform");
8415 } 8418 }
8416 8419
8420 @Creates('Null')
8421 Map<String, _Binding> _attributeBindings;
8422
8423 // TODO(jmesserly): I'm concerned about adding these to every element.
8424 // Conceptually all of these belong on TemplateElement. They are here to
8425 // support browsers that don't have <template> yet.
8426 // However even in the polyfill they're restricted to certain tags
8427 // (see [isTemplate]). So we can probably convert it to a (public) mixin, and
8428 // only mix it in to the elements that need it.
8429 @Creates('Null') // Set from Dart code; does not instantiate a native type.
8430 var _model;
8431
8432 @Creates('Null') // Set from Dart code; does not instantiate a native type.
8433 _TemplateIterator _templateIterator;
8434
8435 @Creates('Null') // Set from Dart code; does not instantiate a native type.
8436 Element _templateInstanceRef;
8437
8438 // Note: only used if `this is! TemplateElement`
8439 @Creates('Null') // Set from Dart code; does not instantiate a native type.
8440 DocumentFragment _templateContent;
8441
8442 bool _templateIsDecorated;
8443
8444 // TODO(jmesserly): should path be optional, and default to empty path?
8445 // It is used that way in at least one path in JS TemplateElement tests
8446 // (see "BindImperative" test in original JS code).
8447 @Experimental
8448 void bind(String name, model, String path) {
8449 _bindElement(this, name, model, path);
8450 }
8451
8452 // TODO(jmesserly): this is static to work around http://dartbug.com/10166
8453 // Similar issue for unbind/unbindAll below.
8454 static void _bindElement(Element self, String name, model, String path) {
8455 if (self._bindTemplate(name, model, path)) return;
8456
8457 if (self._attributeBindings == null) {
8458 self._attributeBindings = new Map<String, _Binding>();
8459 }
8460
8461 self.attributes.remove(name);
8462
8463 _ChangeHandler changed;
8464 if (name.endsWith('?')) {
8465 name = name.substring(0, name.length - 1);
8466
8467 changed = (value) {
8468 if (_templateBooleanConversion(value)) {
8469 self.attributes[name] = '';
8470 } else {
8471 self.attributes.remove(name);
8472 }
8473 };
8474 } else {
8475 changed = (value) {
8476 // TODO(jmesserly): escape value if needed to protect against XSS.
8477 self.attributes[name] = value == null ? '' : '$value';
8478 };
8479 }
8480
8481 self.unbind(name);
8482
8483 self._attributeBindings[name] = new _Binding(model, path, changed);
8484 }
8485
8486 @Experimental
8487 void unbind(String name) {
8488 _unbindElement(this, name);
8489 }
8490
8491 static _unbindElement(Element self, String name) {
8492 if (self._unbindTemplate(name)) return;
8493 if (self._attributeBindings != null) {
8494 var binding = self._attributeBindings.remove(name);
8495 if (binding != null) binding.dispose();
8496 }
8497 }
8498
8499 @Experimental
8500 void unbindAll() {
8501 _unbindAllElement(this);
8502 }
8503
8504 static void _unbindAllElement(Element self) {
8505 self._unbindAllTemplate();
8506
8507 if (self._attributeBindings != null) {
8508 for (var binding in self._attributeBindings.values) {
8509 binding.dispose();
8510 }
8511 self._attributeBindings = null;
8512 }
8513 }
8514
8515 // TODO(jmesserly): unlike the JS polyfill, we can't mixin
8516 // HTMLTemplateElement at runtime into things that are semantically template
8517 // elements. So instead we implement it here with a runtime check.
8518 // If the bind succeeds, we return true, otherwise we return false and let
8519 // the normal Element.bind logic kick in.
8520 bool _bindTemplate(String name, model, String path) {
8521 if (isTemplate) {
8522 switch (name) {
8523 case 'bind':
8524 case 'repeat':
8525 case 'if':
8526 _ensureTemplate();
8527 if (_templateIterator == null) {
8528 _templateIterator = new _TemplateIterator(this);
8529 }
8530 _templateIterator.inputs.bind(name, model, path);
8531 return true;
8532 }
8533 }
8534 return false;
8535 }
8536
8537 bool _unbindTemplate(String name) {
8538 if (isTemplate) {
8539 switch (name) {
8540 case 'bind':
8541 case 'repeat':
8542 case 'if':
8543 _ensureTemplate();
8544 if (_templateIterator != null) {
8545 _templateIterator.inputs.unbind(name);
8546 }
8547 return true;
8548 }
8549 }
8550 return false;
8551 }
8552
8553 void _unbindAllTemplate() {
8554 if (isTemplate) {
8555 unbind('bind');
8556 unbind('repeat');
8557 unbind('if');
8558 }
8559 }
8560
8561 /**
8562 * Gets the template this node refers to.
8563 * This is only supported if [isTemplate] is true.
8564 */
8565 @Experimental
8566 Element get ref {
8567 _ensureTemplate();
8568
8569 Element ref = null;
8570 var refId = attributes['ref'];
8571 if (refId != null) {
8572 ref = document.getElementById(refId);
8573 }
8574
8575 return ref != null ? ref : _templateInstanceRef;
8576 }
8577
8578 /**
8579 * Gets the content of this template.
8580 * This is only supported if [isTemplate] is true.
8581 */
8582 @Experimental
8583 DocumentFragment get content {
8584 _ensureTemplate();
8585 return _templateContent;
8586 }
8587
8588 /**
8589 * Creates an instance of the template.
8590 * This is only supported if [isTemplate] is true.
8591 */
8592 @Experimental
8593 DocumentFragment createInstance() {
8594 _ensureTemplate();
8595
8596 var template = ref;
8597 if (template == null) template = this;
8598
8599 var instance = _createDeepCloneAndDecorateTemplates(template.content,
8600 attributes['syntax']);
8601
8602 if (TemplateElement._instanceCreated != null) {
8603 TemplateElement._instanceCreated.add(instance);
8604 }
8605 return instance;
8606 }
8607
8608 /**
8609 * The data model which is inherited through the tree.
8610 * This is only supported if [isTemplate] is true.
8611 *
8612 * Setting this will destructive propagate the value to all descendant nodes,
8613 * and reinstantiate all of the nodes expanded by this template.
8614 *
8615 * Currently this does not support propagation through Shadow DOMs.
8616 */
8617 @Experimental
8618 get model => _model;
8619
8620 @Experimental
8621 void set model(value) {
8622 _ensureTemplate();
8623
8624 _model = value;
8625 _addBindings(this, model);
8626 }
8627
8628 // TODO(jmesserly): const set would be better
8629 static const _TABLE_TAGS = const {
8630 'caption': null,
8631 'col': null,
8632 'colgroup': null,
8633 'tbody': null,
8634 'td': null,
8635 'tfoot': null,
8636 'th': null,
8637 'thead': null,
8638 'tr': null,
8639 };
8640
8641 bool get _isAttributeTemplate => attributes.containsKey('template') &&
8642 (localName == 'option' || _TABLE_TAGS.containsKey(localName));
8643
8644 /**
8645 * Returns true if this node is a template.
8646 *
8647 * A node is a template if [tagName] is TEMPLATE, or the node has the
8648 * 'template' attribute and this tag supports attribute form for backwards
8649 * compatibility with existing HTML parsers. The nodes that can use attribute
8650 * form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP
8651 * and COL) and OPTION.
8652 */
8653 // TODO(jmesserly): this is not a public MDV API, but it seems like a useful
8654 // place to document which tags our polyfill considers to be templates.
8655 // Otherwise I'd be repeating it in several other places.
8656 // See if we can replace this with a TemplateMixin.
8657 @Experimental
8658 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate;
8659
8660 void _ensureTemplate() {
8661 if (!isTemplate) {
8662 throw new UnsupportedError('$this is not a template.');
8663 }
8664 TemplateElement.decorate(this);
8665 }
8666
8417 8667
8418 @DomName('Element.abortEvent') 8668 @DomName('Element.abortEvent')
8419 @DocsEditable 8669 @DocsEditable
8420 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort'); 8670 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort');
8421 8671
8422 @DomName('Element.beforecopyEvent') 8672 @DomName('Element.beforecopyEvent')
8423 @DocsEditable 8673 @DocsEditable
8424 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy'); 8674 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy');
8425 8675
8426 @DomName('Element.beforecutEvent') 8676 @DomName('Element.beforecutEvent')
(...skipping 724 matching lines...) Expand 10 before | Expand all | Expand 10 after
9151 @DomName('Element.onwebkitfullscreenchange') 9401 @DomName('Element.onwebkitfullscreenchange')
9152 @DocsEditable 9402 @DocsEditable
9153 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); 9403 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this);
9154 9404
9155 @DomName('Element.onwebkitfullscreenerror') 9405 @DomName('Element.onwebkitfullscreenerror')
9156 @DocsEditable 9406 @DocsEditable
9157 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); 9407 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this);
9158 9408
9159 } 9409 }
9160 9410
9411
9161 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); 9412 final _START_TAG_REGEXP = new RegExp('<(\\w+)');
9162 class _ElementFactoryProvider { 9413 class _ElementFactoryProvider {
9163 static const _CUSTOM_PARENT_TAG_MAP = const { 9414 static const _CUSTOM_PARENT_TAG_MAP = const {
9164 'body' : 'html', 9415 'body' : 'html',
9165 'head' : 'html', 9416 'head' : 'html',
9166 'caption' : 'table', 9417 'caption' : 'table',
9167 'td': 'tr', 9418 'td': 'tr',
9168 'th': 'tr', 9419 'th': 'tr',
9169 'colgroup': 'table', 9420 'colgroup': 'table',
9170 'col' : 'colgroup', 9421 'col' : 'colgroup',
9171 'tr' : 'tbody', 9422 'tr' : 'tbody',
9172 'tbody' : 'table', 9423 'tbody' : 'table',
9173 'tfoot' : 'table', 9424 'tfoot' : 'table',
9174 'thead' : 'table', 9425 'thead' : 'table',
9175 'track' : 'audio', 9426 'track' : 'audio',
9176 }; 9427 };
9177 9428
9178 // TODO(jmesserly): const set would be better
9179 static const _TABLE_TAGS = const {
9180 'caption': null,
9181 'col': null,
9182 'colgroup': null,
9183 'tbody': null,
9184 'td': null,
9185 'tfoot': null,
9186 'th': null,
9187 'thead': null,
9188 'tr': null,
9189 };
9190
9191 @DomName('Document.createElement') 9429 @DomName('Document.createElement')
9192 static Element createElement_html(String html) { 9430 static Element createElement_html(String html) {
9193 // TODO(jacobr): this method can be made more robust and performant. 9431 // TODO(jacobr): this method can be made more robust and performant.
9194 // 1) Cache the dummy parent elements required to use innerHTML rather than 9432 // 1) Cache the dummy parent elements required to use innerHTML rather than
9195 // creating them every call. 9433 // creating them every call.
9196 // 2) Verify that the html does not contain leading or trailing text nodes. 9434 // 2) Verify that the html does not contain leading or trailing text nodes.
9197 // 3) Verify that the html does not contain both <head> and <body> tags. 9435 // 3) Verify that the html does not contain both <head> and <body> tags.
9198 // 4) Detatch the created element from its dummy parent. 9436 // 4) Detatch the created element from its dummy parent.
9199 String parentTag = 'div'; 9437 String parentTag = 'div';
9200 String tag; 9438 String tag;
9201 final match = _START_TAG_REGEXP.firstMatch(html); 9439 final match = _START_TAG_REGEXP.firstMatch(html);
9202 if (match != null) { 9440 if (match != null) {
9203 tag = match.group(1).toLowerCase(); 9441 tag = match.group(1).toLowerCase();
9204 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { 9442 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) {
9205 return _createTableForIE(html, tag); 9443 return _createTableForIE(html, tag);
9206 } 9444 }
9207 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; 9445 parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
9208 if (parentTag == null) parentTag = 'div'; 9446 if (parentTag == null) parentTag = 'div';
9209 } 9447 }
9210 9448
9211 final temp = new Element.tag(parentTag); 9449 final temp = new Element.tag(parentTag);
9212 temp.innerHtml = html; 9450 temp.innerHtml = html;
9213 9451
9214 Element element; 9452 Element element;
(...skipping 2452 matching lines...) Expand 10 before | Expand all | Expand 10 after
11667 @SupportedBrowser(SupportedBrowser.SAFARI) 11905 @SupportedBrowser(SupportedBrowser.SAFARI)
11668 @Experimental 11906 @Experimental
11669 Element get pointerLockElement => 11907 Element get pointerLockElement =>
11670 $dom_webkitPointerLockElement; 11908 $dom_webkitPointerLockElement;
11671 11909
11672 @DomName('Document.webkitVisibilityState') 11910 @DomName('Document.webkitVisibilityState')
11673 @SupportedBrowser(SupportedBrowser.CHROME) 11911 @SupportedBrowser(SupportedBrowser.CHROME)
11674 @SupportedBrowser(SupportedBrowser.SAFARI) 11912 @SupportedBrowser(SupportedBrowser.SAFARI)
11675 @Experimental 11913 @Experimental
11676 String get visibilityState => $dom_webkitVisibilityState; 11914 String get visibilityState => $dom_webkitVisibilityState;
11915
11916
11917 @Creates('Null') // Set from Dart code; does not instantiate a native type.
11918 // Note: used to polyfill <template>
11919 Document _templateContentsOwner;
11677 } 11920 }
11678 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 11921 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
11679 // for details. All rights reserved. Use of this source code is governed by a 11922 // for details. All rights reserved. Use of this source code is governed by a
11680 // BSD-style license that can be found in the LICENSE file. 11923 // BSD-style license that can be found in the LICENSE file.
11681 11924
11682 11925
11683 @DocsEditable 11926 @DocsEditable
11684 @DomName('HTMLHtmlElement') 11927 @DomName('HTMLHtmlElement')
11685 class HtmlElement extends Element native "HTMLHtmlElement" { 11928 class HtmlElement extends Element native "HTMLHtmlElement" {
11686 11929
(...skipping 816 matching lines...) Expand 10 before | Expand all | Expand 10 after
12503 var e = document.$dom_createElement("input"); 12746 var e = document.$dom_createElement("input");
12504 if (type != null) { 12747 if (type != null) {
12505 try { 12748 try {
12506 // IE throws an exception for unknown types. 12749 // IE throws an exception for unknown types.
12507 e.type = type; 12750 e.type = type;
12508 } catch(_) {} 12751 } catch(_) {}
12509 } 12752 }
12510 return e; 12753 return e;
12511 } 12754 }
12512 12755
12756 @Creates('Null') // Set from Dart code; does not instantiate a native type.
12757 _ValueBinding _valueBinding;
12758
12759 @Creates('Null') // Set from Dart code; does not instantiate a native type.
12760 _CheckedBinding _checkedBinding;
12761
12762 @Experimental
12763 void bind(String name, model, String path) {
12764 switch (name) {
12765 case 'value':
12766 unbind('value');
12767 attributes.remove('value');
12768 _valueBinding = new _ValueBinding(this, model, path);
12769 break;
12770 case 'checked':
12771 unbind('checked');
12772 attributes.remove('checked');
12773 _checkedBinding = new _CheckedBinding(this, model, path);
12774 break;
12775 default:
12776 // TODO(jmesserly): this should be "super" (http://dartbug.com/10166).
12777 // Similar issue for unbind/unbindAll below.
12778 Element._bindElement(this, name, model, path);
12779 break;
12780 }
12781 }
12782
12783 @Experimental
12784 void unbind(String name) {
12785 switch (name) {
12786 case 'value':
12787 if (_valueBinding != null) {
12788 _valueBinding.unbind();
12789 _valueBinding = null;
12790 }
12791 break;
12792 case 'checked':
12793 if (_checkedBinding != null) {
12794 _checkedBinding.unbind();
12795 _checkedBinding = null;
12796 }
12797 break;
12798 default:
12799 Element._unbindElement(this, name);
12800 break;
12801 }
12802 }
12803
12804 @Experimental
12805 void unbindAll() {
12806 unbind('value');
12807 unbind('checked');
12808 Element._unbindAllElement(this);
12809 }
12810
12811
12513 @DomName('HTMLInputElement.webkitSpeechChangeEvent') 12812 @DomName('HTMLInputElement.webkitSpeechChangeEvent')
12514 @DocsEditable 12813 @DocsEditable
12515 @SupportedBrowser(SupportedBrowser.CHROME) 12814 @SupportedBrowser(SupportedBrowser.CHROME)
12516 @SupportedBrowser(SupportedBrowser.SAFARI) 12815 @SupportedBrowser(SupportedBrowser.SAFARI)
12517 @Experimental 12816 @Experimental
12518 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange'); 12817 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP rovider<Event>('webkitSpeechChange');
12519 12818
12520 @DomName('HTMLInputElement.accept') 12819 @DomName('HTMLInputElement.accept')
12521 @DocsEditable 12820 @DocsEditable
12522 String accept; 12821 String accept;
(...skipping 3162 matching lines...) Expand 10 before | Expand all | Expand 10 after
15685 _this.$dom_removeChild(node); 15984 _this.$dom_removeChild(node);
15686 return true; 15985 return true;
15687 } 15986 }
15688 15987
15689 void _filter(bool test(Node node), bool removeMatching) { 15988 void _filter(bool test(Node node), bool removeMatching) {
15690 // This implementation of removeWhere/retainWhere is more efficient 15989 // This implementation of removeWhere/retainWhere is more efficient
15691 // than the default in ListBase. Child nodes can be removed in constant 15990 // than the default in ListBase. Child nodes can be removed in constant
15692 // time. 15991 // time.
15693 Node child = _this.$dom_firstChild; 15992 Node child = _this.$dom_firstChild;
15694 while (child != null) { 15993 while (child != null) {
15695 Node nextChild = child.nextSibling; 15994 Node nextChild = child.nextNode;
15696 if (test(child) == removeMatching) { 15995 if (test(child) == removeMatching) {
15697 _this.$dom_removeChild(child); 15996 _this.$dom_removeChild(child);
15698 } 15997 }
15699 child = nextChild; 15998 child = nextChild;
15700 } 15999 }
15701 } 16000 }
15702 16001
15703 void removeWhere(bool test(Node node)) { 16002 void removeWhere(bool test(Node node)) {
15704 _filter(test, true); 16003 _filter(test, true);
15705 } 16004 }
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
15810 // Should use $dom_firstChild, Bug 8886. 16109 // Should use $dom_firstChild, Bug 8886.
15811 this.insertBefore(newNodes[0], refChild); 16110 this.insertBefore(newNodes[0], refChild);
15812 } 16111 }
15813 } else { 16112 } else {
15814 for (var node in newNodes) { 16113 for (var node in newNodes) {
15815 this.insertBefore(node, refChild); 16114 this.insertBefore(node, refChild);
15816 } 16115 }
15817 } 16116 }
15818 } 16117 }
15819 16118
15820 // Note that this may either be the locally set model or a cached value
15821 // of the inherited model. This is cached to minimize model change
15822 // notifications.
15823 @Creates('Null')
15824 var _model;
15825 bool _hasLocalModel;
15826 Set<StreamController<Node>> _modelChangedStreams;
15827
15828 /**
15829 * The data model which is inherited through the tree.
15830 *
15831 * Setting this will propagate the value to all descendant nodes. If the
15832 * model is not set on this node then it will be inherited from ancestor
15833 * nodes.
15834 *
15835 * Currently this does not support propagation through Shadow DOMs.
15836 *
15837 * [clearModel] must be used to remove the model property from this node
15838 * and have the model inherit from ancestor nodes.
15839 */
15840 @Experimental
15841 get model {
15842 // If we have a change handler then we've cached the model locally.
15843 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) {
15844 return _model;
15845 }
15846 // Otherwise start looking up the tree.
15847 for (var node = this; node != null; node = node.parentNode) {
15848 if (node._hasLocalModel == true) {
15849 return node._model;
15850 }
15851 }
15852 return null;
15853 }
15854
15855 @Experimental
15856 void set model(value) {
15857 var changed = model != value;
15858 _model = value;
15859 _hasLocalModel = true;
15860 _ModelTreeObserver.initialize();
15861
15862 if (changed) {
15863 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) {
15864 _modelChangedStreams.toList().forEach((stream) => stream.add(this));
15865 }
15866 // Propagate new model to all descendants.
15867 _ModelTreeObserver.propagateModel(this, value, false);
15868 }
15869 }
15870
15871 /**
15872 * Clears the locally set model and makes this model be inherited from parent
15873 * nodes.
15874 */
15875 @Experimental
15876 void clearModel() {
15877 if (_hasLocalModel == true) {
15878 _hasLocalModel = false;
15879
15880 // Propagate new model to all descendants.
15881 if (parentNode != null) {
15882 _ModelTreeObserver.propagateModel(this, parentNode.model, false);
15883 } else {
15884 _ModelTreeObserver.propagateModel(this, null, false);
15885 }
15886 }
15887 }
15888
15889 /**
15890 * Get a stream of models, whenever the model changes.
15891 */
15892 Stream<Node> get onModelChanged {
15893 if (_modelChangedStreams == null) {
15894 _modelChangedStreams = new Set<StreamController<Node>>();
15895 }
15896 var controller;
15897 controller = new StreamController(
15898 onListen: () { _modelChangedStreams.add(controller); },
15899 onCancel: () { _modelChangedStreams.remove(controller); });
15900 return controller.stream;
15901 }
15902
15903 /** 16119 /**
15904 * Print out a String representation of this Node. 16120 * Print out a String representation of this Node.
15905 */ 16121 */
15906 String toString() => localName == null ? 16122 String toString() => localName == null ?
15907 (nodeValue == null ? super.toString() : nodeValue) : localName; 16123 (nodeValue == null ? super.toString() : nodeValue) : localName;
15908 16124
16125 /**
16126 * Binds the attribute [name] to the [path] of the [model].
16127 * Path is a String of accessors such as `foo.bar.baz`.
16128 */
16129 @Experimental
16130 void bind(String name, model, String path) {
16131 // TODO(jmesserly): should we throw instead?
16132 window.console.error('Unhandled binding to Node: '
16133 '$this $name $model $path');
16134 }
16135
16136 /** Unbinds the attribute [name]. */
16137 @Experimental
16138 void unbind(String name) {}
16139
16140 /** Unbinds all bound attributes. */
16141 @Experimental
16142 void unbindAll() {}
16143
16144 TemplateInstance _templateInstance;
16145
16146 // TODO(arv): Consider storing all "NodeRareData" on a single object?
16147 int __instanceTerminatorCount;
16148 int get _instanceTerminatorCount {
16149 if (__instanceTerminatorCount == null) return 0;
16150 return __instanceTerminatorCount;
16151 }
16152 set _instanceTerminatorCount(int value) {
16153 if (value == 0) value = null;
16154 __instanceTerminatorCount = value;
16155 }
16156
16157 /** Gets the template instance that instantiated this node, if any. */
16158 @Experimental
16159 TemplateInstance get templateInstance =>
16160 _templateInstance != null ? _templateInstance :
16161 (parent != null ? parent.templateInstance : null);
16162
15909 16163
15910 static const int ATTRIBUTE_NODE = 2; 16164 static const int ATTRIBUTE_NODE = 2;
15911 16165
15912 static const int CDATA_SECTION_NODE = 4; 16166 static const int CDATA_SECTION_NODE = 4;
15913 16167
15914 static const int COMMENT_NODE = 8; 16168 static const int COMMENT_NODE = 8;
15915 16169
15916 static const int DOCUMENT_FRAGMENT_NODE = 11; 16170 static const int DOCUMENT_FRAGMENT_NODE = 11;
15917 16171
15918 static const int DOCUMENT_NODE = 9; 16172 static const int DOCUMENT_NODE = 9;
(...skipping 4086 matching lines...) Expand 10 before | Expand all | Expand 10 after
20005 20259
20006 @JSName('insertRow') 20260 @JSName('insertRow')
20007 @DomName('HTMLTableSectionElement.insertRow') 20261 @DomName('HTMLTableSectionElement.insertRow')
20008 @DocsEditable 20262 @DocsEditable
20009 Element $dom_insertRow(int index) native; 20263 Element $dom_insertRow(int index) native;
20010 } 20264 }
20011 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20265 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
20012 // for details. All rights reserved. Use of this source code is governed by a 20266 // for details. All rights reserved. Use of this source code is governed by a
20013 // BSD-style license that can be found in the LICENSE file. 20267 // BSD-style license that can be found in the LICENSE file.
20014 20268
20269 // WARNING: Do not edit - generated code.
20015 20270
20016 @DocsEditable 20271
20272 @Experimental
20017 @DomName('HTMLTemplateElement') 20273 @DomName('HTMLTemplateElement')
20018 class TemplateElement extends Element native "HTMLTemplateElement" { 20274 class TemplateElement extends Element native "HTMLTemplateElement" {
20019 20275
20276 /// Checks if this type is supported on the current platform.
20277 static bool get supported => Element.isTagSupported('template');
20278
20279 @JSName('content')
20020 @DomName('HTMLTemplateElement.content') 20280 @DomName('HTMLTemplateElement.content')
20021 @DocsEditable 20281 @DocsEditable
20022 final DocumentFragment content; 20282 final DocumentFragment $dom_content;
20283
20284
20285 // For real TemplateElement use the actual DOM .content field instead of
20286 // our polyfilled expando.
20287 @Experimental
20288 DocumentFragment get content => $dom_content;
20289
20290 static StreamController<DocumentFragment> _instanceCreated;
20291
20292 /**
20293 * *Warning*: This is an implementation helper for Model-Driven Views and
20294 * should not be used in your code.
20295 *
20296 * This event is fired whenever a template is instantiated via
20297 * [createInstance].
20298 */
20299 // TODO(rafaelw): This is a hack, and is neccesary for the polyfill
20300 // because custom elements are not upgraded during clone()
20301 @Experimental
20302 static Stream<DocumentFragment> get instanceCreated {
20303 if (_instanceCreated == null) {
20304 _instanceCreated = new StreamController<DocumentFragment>();
20305 }
20306 return _instanceCreated.stream;
20307 }
20308
20309 /**
20310 * Ensures proper API and content model for template elements.
20311 *
20312 * [instanceRef] can be used to set the [Element.ref] property of [template],
20313 * and use the ref's content will be used as source when createInstance() is
20314 * invoked.
20315 *
20316 * Returns true if this template was just decorated, or false if it was
20317 * already decorated.
20318 */
20319 @Experimental
20320 static bool decorate(Element template, [Element instanceRef]) {
20321 // == true check because it starts as a null field.
20322 if (template._templateIsDecorated == true) return false;
20323
20324 template._templateIsDecorated = true;
20325
20326 _injectStylesheet();
20327
20328 // Create content
20329 if (template is! TemplateElement) {
20330 var doc = _getTemplateContentsOwner(template.document);
20331 template._templateContent = doc.createDocumentFragment();
20332 }
20333
20334 if (instanceRef != null) {
20335 template._templateInstanceRef = instanceRef;
20336 return true; // content is empty.
20337 }
20338
20339 if (template is TemplateElement) {
20340 _bootstrapTemplatesRecursivelyFrom(template.content);
20341 } else {
20342 _liftNonNativeTemplateChildrenIntoContent(template);
20343 }
20344
20345 return true;
20346 }
20347
20348 /**
20349 * This used to decorate recursively all templates from a given node.
20350 *
20351 * By default [decorate] will be called on templates lazily when certain
20352 * properties such as [model] are accessed, but it can be run eagerly to
20353 * decorate an entire tree recursively.
20354 */
20355 // TODO(rafaelw): Review whether this is the right public API.
20356 @Experimental
20357 static void bootstrap(Node content) {
20358 _bootstrapTemplatesRecursivelyFrom(content);
20359 }
20360
20361 static bool _initStyles;
20362
20363 static void _injectStylesheet() {
20364 if (_initStyles == true) return;
20365 _initStyles = true;
20366
20367 var style = new StyleElement();
20368 style.text = r'''
20369 template,
20370 thead[template],
20371 tbody[template],
20372 tfoot[template],
20373 th[template],
20374 tr[template],
20375 td[template],
20376 caption[template],
20377 colgroup[template],
20378 col[template],
20379 option[template] {
20380 display: none;
20381 }''';
20382 document.head.append(style);
20383 }
20384
20385 /**
20386 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for
20387 * more information.
20388 */
20389 @Experimental
20390 static Map<String, CustomBindingSyntax> syntax = {};
20023 } 20391 }
20024 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20392 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
20025 // for details. All rights reserved. Use of this source code is governed by a 20393 // for details. All rights reserved. Use of this source code is governed by a
20026 // BSD-style license that can be found in the LICENSE file. 20394 // BSD-style license that can be found in the LICENSE file.
20027 20395
20028 // WARNING: Do not edit - generated code. 20396 // WARNING: Do not edit - generated code.
20029 20397
20030 20398
20031 @DomName('Text') 20399 @DomName('Text')
20032 class Text extends CharacterData native "Text" { 20400 class Text extends CharacterData native "Text" {
(...skipping 12 matching lines...) Expand all
20045 final String wholeText; 20413 final String wholeText;
20046 20414
20047 @DomName('Text.replaceWholeText') 20415 @DomName('Text.replaceWholeText')
20048 @DocsEditable 20416 @DocsEditable
20049 Text replaceWholeText(String content) native; 20417 Text replaceWholeText(String content) native;
20050 20418
20051 @DomName('Text.splitText') 20419 @DomName('Text.splitText')
20052 @DocsEditable 20420 @DocsEditable
20053 Text splitText(int offset) native; 20421 Text splitText(int offset) native;
20054 20422
20423
20424 @Creates('Null') // Set from Dart code; does not instantiate a native type.
20425 _Binding _textBinding;
20426
20427 @Experimental
20428 void bind(String name, model, String path) {
20429 if (name != 'text') {
20430 super.bind(name, model, path);
20431 return;
20432 }
20433
20434 unbind('text');
20435 _textBinding = new _Binding(model, path, (value) { text = '$value'; });
20436 }
20437
20438 @Experimental
20439 void unbind(String name) {
20440 if (name != 'text') {
20441 super.unbind(name);
20442 return;
20443 }
20444
20445 if (_textBinding == null) return;
20446
20447 _textBinding.dispose();
20448 _textBinding = null;
20449 }
20450
20451 @Experimental
20452 void unbindAll() {
20453 unbind('text');
20454 super.unbindAll();
20455 }
20055 } 20456 }
20056 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20457 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
20057 // for details. All rights reserved. Use of this source code is governed by a 20458 // for details. All rights reserved. Use of this source code is governed by a
20058 // BSD-style license that can be found in the LICENSE file. 20459 // BSD-style license that can be found in the LICENSE file.
20059 20460
20060 20461
20061 @DocsEditable 20462 @DocsEditable
20062 @DomName('HTMLTextAreaElement') 20463 @DomName('HTMLTextAreaElement')
20063 class TextAreaElement extends Element native "HTMLTextAreaElement" { 20464 class TextAreaElement extends Element native "HTMLTextAreaElement" {
20064 20465
(...skipping 7973 matching lines...) Expand 10 before | Expand all | Expand 10 after
28038 * Key value used when an implementation is unable to identify another key 28439 * Key value used when an implementation is unable to identify another key
28039 * value, due to either hardware, platform, or software constraints 28440 * value, due to either hardware, platform, or software constraints
28040 */ 28441 */
28041 static const String UNIDENTIFIED = "Unidentified"; 28442 static const String UNIDENTIFIED = "Unidentified";
28042 } 28443 }
28043 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 28444 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
28044 // for details. All rights reserved. Use of this source code is governed by a 28445 // for details. All rights reserved. Use of this source code is governed by a
28045 // BSD-style license that can be found in the LICENSE file. 28446 // BSD-style license that can be found in the LICENSE file.
28046 28447
28047 28448
28048 class _ModelTreeObserver {
28049 static bool _initialized = false;
28050
28051 /**
28052 * Start an observer watching the document for tree changes to automatically
28053 * propagate model changes.
28054 *
28055 * Currently this does not support propagation through Shadow DOMs.
28056 */
28057 static void initialize() {
28058 if (!_initialized) {
28059 _initialized = true;
28060
28061 if (MutationObserver.supported) {
28062 var observer = new MutationObserver(_processTreeChange);
28063 observer.observe(document, childList: true, subtree: true);
28064 } else {
28065 document.on['DOMNodeInserted'].listen(_handleNodeInserted);
28066 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved);
28067 }
28068 }
28069 }
28070
28071 static void _processTreeChange(List<MutationRecord> mutations,
28072 MutationObserver observer) {
28073 for (var record in mutations) {
28074 for (var node in record.addedNodes) {
28075 // When nodes enter the document we need to make sure that all of the
28076 // models are properly propagated through the entire sub-tree.
28077 propagateModel(node, _calculatedModel(node), true);
28078 }
28079 for (var node in record.removedNodes) {
28080 propagateModel(node, _calculatedModel(node), false);
28081 }
28082 }
28083 }
28084
28085 static void _handleNodeInserted(MutationEvent e) {
28086 var node = e.target;
28087 window.setImmediate(() {
28088 propagateModel(node, _calculatedModel(node), true);
28089 });
28090 }
28091
28092 static void _handleNodeRemoved(MutationEvent e) {
28093 var node = e.target;
28094 window.setImmediate(() {
28095 propagateModel(node, _calculatedModel(node), false);
28096 });
28097 }
28098
28099 /**
28100 * Figures out what the model should be for a node, avoiding any cached
28101 * model values.
28102 */
28103 static _calculatedModel(node) {
28104 if (node._hasLocalModel == true) {
28105 return node._model;
28106 } else if (node.parentNode != null) {
28107 return node.parentNode._model;
28108 }
28109 return null;
28110 }
28111
28112 /**
28113 * Pushes model changes down through the tree.
28114 *
28115 * Set fullTree to true if the state of the tree is unknown and model changes
28116 * should be propagated through the entire tree.
28117 */
28118 static void propagateModel(Node node, model, bool fullTree) {
28119 // Calling into user code with the != call could generate exceptions.
28120 // Catch and report them a global exceptions.
28121 try {
28122 if (node._hasLocalModel != true && node._model != model &&
28123 node._modelChangedStreams != null &&
28124 !node._modelChangedStreams.isEmpty) {
28125 node._model = model;
28126 node._modelChangedStreams.toList()
28127 .forEach((controller) => controller.add(node));
28128 }
28129 } catch (e, s) {
28130 new Future.error(e, s);
28131 }
28132 for (var child = node.$dom_firstChild; child != null;
28133 child = child.nextNode) {
28134 if (child._hasLocalModel != true) {
28135 propagateModel(child, model, fullTree);
28136 } else if (fullTree) {
28137 propagateModel(child, child._model, true);
28138 }
28139 }
28140 }
28141 }
28142 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
28143 // for details. All rights reserved. Use of this source code is governed by a
28144 // BSD-style license that can be found in the LICENSE file.
28145
28146
28147 /** 28449 /**
28148 * A utility class for representing two-dimensional positions. 28450 * A utility class for representing two-dimensional positions.
28149 */ 28451 */
28150 class Point { 28452 class Point {
28151 final num x; 28453 final num x;
28152 final num y; 28454 final num y;
28153 28455
28154 const Point([num x = 0, num y = 0]): x = x, y = y; 28456 const Point([num x = 0, num y = 0]): x = x, y = y;
28155 28457
28156 String toString() => '($x, $y)'; 28458 String toString() => '($x, $y)';
(...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after
28354 * Truncates coordinates to integers and returns the result as a new 28656 * Truncates coordinates to integers and returns the result as a new
28355 * rectangle. 28657 * rectangle.
28356 */ 28658 */
28357 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), 28659 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
28358 height.toInt()); 28660 height.toInt());
28359 28661
28360 Point get topLeft => new Point(this.left, this.top); 28662 Point get topLeft => new Point(this.left, this.top);
28361 Point get bottomRight => new Point(this.left + this.width, 28663 Point get bottomRight => new Point(this.left + this.width,
28362 this.top + this.height); 28664 this.top + this.height);
28363 } 28665 }
28666 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
28667 // for details. All rights reserved. Use of this source code is governed by a
28668 // BSD-style license that can be found in the LICENSE file.
28669
28670
28671 // This code is a port of Model-Driven-Views:
28672 // https://github.com/toolkitchen/mdv
28673 // The code mostly comes from src/template_element.js
28674
28675 typedef void _ChangeHandler(value);
28676
28677 /**
28678 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
28679 * but (by design) don't attempt to implement a wide array of specialized
28680 * behaviors.
28681 *
28682 * Enabling these features in MDV is a matter of implementing and registering an
28683 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more
28684 * delegation functions which implement specialized behavior. This object is
28685 * registered with MDV via [TemplateElement.syntax]:
28686 *
28687 *
28688 * HTML:
28689 * <template bind syntax="MySyntax">
28690 * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
28691 * </template>
28692 *
28693 * Dart:
28694 * class MySyntax extends CustomBindingSyntax {
28695 * getBinding(model, path, name, node) {
28696 * // The magic happens here!
28697 * }
28698 * }
28699 *
28700 * ...
28701 *
28702 * TemplateElement.syntax['MySyntax'] = new MySyntax();
28703 *
28704 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more
28705 * information about Custom Syntax.
28706 */
28707 // TODO(jmesserly): if this is just one method, a function type would make it
28708 // more Dart-friendly.
28709 @Experimental
28710 abstract class CustomBindingSyntax {
28711 // TODO(jmesserly): I had to remove type annotations from "name" and "node"
28712 // Normally they are String and Node respectively. But sometimes it will pass
28713 // (int name, CompoundBinding node). That seems very confusing; we may want
28714 // to change this API.
28715 getBinding(model, String path, name, node);
28716 }
28717
28718 /** The callback used in the [CompoundBinding.combinator] field. */
28719 @Experimental
28720 typedef Object CompoundBindingCombinator(Map objects);
28721
28722 /** Information about the instantiated template. */
28723 @Experimental
28724 class TemplateInstance {
28725 // TODO(rafaelw): firstNode & lastNode should be read-synchronous
28726 // in cases where script has modified the template instance boundary.
28727
28728 /** The first node of this template instantiation. */
28729 final Node firstNode;
28730
28731 /**
28732 * The last node of this template instantiation.
28733 * This could be identical to [firstNode] if the template only expanded to a
28734 * single node.
28735 */
28736 final Node lastNode;
28737
28738 /** The model used to instantiate the template. */
28739 final model;
28740
28741 TemplateInstance(this.firstNode, this.lastNode, this.model);
28742 }
28743
28744 /**
28745 * Model-Driven Views contains a helper object which is useful for the
28746 * implementation of a Custom Syntax.
28747 *
28748 * var binding = new CompoundBinding((values) {
28749 * var combinedValue;
28750 * // compute combinedValue based on the current values which are provided
28751 * return combinedValue;
28752 * });
28753 * binding.bind('name1', obj1, path1);
28754 * binding.bind('name2', obj2, path2);
28755 * //...
28756 * binding.bind('nameN', objN, pathN);
28757 *
28758 * CompoundBinding is an object which knows how to listen to multiple path
28759 * values (registered via [bind]) and invoke its [combinator] when one or more
28760 * of the values have changed and set its [value] property to the return value
28761 * of the function. When any value has changed, all current values are provided
28762 * to the [combinator] in the single `values` argument.
28763 *
28764 * See [CustomBindingSyntax] for more information.
28765 */
28766 // TODO(jmesserly): what is the public API surface here? I just guessed;
28767 // most of it seemed non-public.
28768 // TODO(jmesserly): should this move into dart:observe?
28769 @Experimental
28770 class CompoundBinding extends ObservableMixin {
28771 CompoundBindingCombinator _combinator;
28772
28773 // TODO(jmesserly): ideally these would be String keys, but sometimes we
28774 // use integers.
28775 Map<dynamic, _Binding> _bindings = new Map();
28776 Map _values = new Map();
28777 bool _scheduled = false;
28778 bool _disposed = false;
28779 Object _value;
28780
28781 CompoundBinding([CompoundBindingCombinator combinator]) {
28782 // TODO(jmesserly): this is a tweak to the original code, it seemed to me
28783 // that passing the combinator to the constructor should be equivalent to
28784 // setting it via the property.
28785 // I also added a null check to the combinator setter.
28786 this.combinator = combinator;
28787 }
28788
28789 CompoundBindingCombinator get combinator => _combinator;
28790
28791 set combinator(CompoundBindingCombinator combinator) {
28792 _combinator = combinator;
28793 if (combinator != null) _scheduleResolve();
28794 }
28795
28796 static const _VALUE = 'value';
28797
28798 get value => _value;
28799
28800 void set value(newValue) {
28801 _value = notifyChange('value', _value, newValue);
28802 }
28803
28804 // TODO(jmesserly): remove getValue/setValue when dart2js supports mirrors!
28805 getValue(key) {
28806 if (key == _VALUE) return value;
28807 return null;
28808 }
28809 setValue(key, val) {
28810 if (key == _VALUE) value = val;
28811 }
28812
28813 void bind(name, model, String path) {
28814 unbind(name);
28815
28816 _bindings[name] = new _Binding(model, path, (value) {
28817 _values[name] = value;
28818 _scheduleResolve();
28819 });
28820 }
28821
28822 void unbind(name, {bool suppressResolve: false}) {
28823 var binding = _bindings.remove(name);
28824 if (binding == null) return;
28825
28826 binding.dispose();
28827 _values.remove(name);
28828 if (!suppressResolve) _scheduleResolve();
28829 }
28830
28831 // TODO(rafaelw): Is this the right processing model?
28832 // TODO(rafaelw): Consider having a seperate ChangeSummary for
28833 // CompoundBindings so to excess dirtyChecks.
28834 void _scheduleResolve() {
28835 if (_scheduled) return;
28836 _scheduled = true;
28837 queueChangeRecords(resolve);
28838 }
28839
28840 void resolve() {
28841 if (_disposed) return;
28842 _scheduled = false;
28843
28844 if (_combinator == null) {
28845 throw new StateError(
28846 'CompoundBinding attempted to resolve without a combinator');
28847 }
28848
28849 value = _combinator(_values);
28850 }
28851
28852 void dispose() {
28853 for (var binding in _bindings.values) {
28854 binding.dispose();
28855 }
28856 _bindings.clear();
28857 _values.clear();
28858
28859 _disposed = true;
28860 value = null;
28861 }
28862 }
28863
28864 // TODO(jmesserly): we can probably inline this object as it doesn't do much.
28865 class _Binding {
28866 final PathObserver _path;
28867 StreamSubscription _sub;
28868
28869 _Binding(model, String path, _ChangeHandler changed)
28870 : _path = observePath(model, path) {
28871 if (_path == null) {
28872 // TODO(jmesserly): Should we display an error message about invalid path?
28873 changed(null);
28874 return;
28875 }
28876 _sub = _path.values.listen(changed);
28877 changed(_path.value);
28878 }
28879
28880 void dispose() {
28881 if (_sub != null) _sub.cancel();
28882 }
28883
28884 void set value(newValue) {
28885 if (_path != null) _path.value = newValue;
28886 }
28887 }
28888
28889 Stream<Event> _getStreamForInputType(InputElement element) {
28890 switch (element.type) {
28891 case 'checkbox':
28892 return element.onClick;
28893 case 'radio':
28894 case 'select-multiple':
28895 case 'select-one':
28896 return element.onChange;
28897 default:
28898 return element.onInput;
28899 }
28900 }
28901
28902 abstract class _InputBinding {
28903 final InputElement element;
28904 _Binding binding;
28905 StreamSubscription _sub;
28906
28907 _InputBinding(this.element, model, String path) {
28908 binding = new _Binding(model, path, valueChanged);
28909 _sub = _getStreamForInputType(element).listen(updateBinding);
28910 }
28911
28912 void valueChanged(newValue);
28913
28914 void updateBinding(e);
28915
28916 void unbind() {
28917 binding.dispose();
28918 _sub.cancel();
28919 }
28920 }
28921
28922 class _ValueBinding extends _InputBinding {
28923 _ValueBinding(element, model, path) : super(element, model, path);
28924
28925 void valueChanged(value) {
28926 element.value = value == null ? '' : '$value';
28927 }
28928
28929 void updateBinding(e) {
28930 binding.value = element.value;
28931 }
28932 }
28933
28934 // TODO(jmesserly): not sure what kind of boolean conversion rules to
28935 // apply for template data-binding. HTML attributes are true if they're present.
28936 // However Dart only treats "true" as true. Since this is HTML we'll use
28937 // something closer to the HTML rules: null (missing) and false are false,
28938 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59
28939 bool _templateBooleanConversion(value) => null != value && false != value;
28940
28941 class _CheckedBinding extends _InputBinding {
28942 _CheckedBinding(element, model, path) : super(element, model, path);
28943
28944 void valueChanged(value) {
28945 element.checked = _templateBooleanConversion(value);
28946 }
28947
28948 void updateBinding(e) {
28949 binding.value = element.checked;
28950
28951 // Only the radio button that is getting checked gets an event. We
28952 // therefore find all the associated radio buttons and update their
28953 // CheckedBinding manually.
28954 if (element is InputElement && element.type == 'radio') {
28955 for (var r in _getAssociatedRadioButtons(element)) {
28956 var checkedBinding = r._checkedBinding;
28957 if (checkedBinding != null) {
28958 // Set the value directly to avoid an infinite call stack.
28959 checkedBinding.binding.value = false;
28960 }
28961 }
28962 }
28963 }
28964 }
28965
28966 // TODO(jmesserly): polyfill document.contains API instead of doing it here
28967 bool _isNodeInDocument(Node node) {
28968 // On non-IE this works:
28969 // return node.document.contains(node);
28970 var document = node.document;
28971 if (node == document || node.parentNode == document) return true;
28972 return document.documentElement.contains(node);
28973 }
28974
28975 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
28976 // Returns an array containing all radio buttons other than |element| that
28977 // have the same |name|, either in the form that |element| belongs to or,
28978 // if no form, in the document tree to which |element| belongs.
28979 //
28980 // This implementation is based upon the HTML spec definition of a
28981 // "radio button group":
28982 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht ml#radio-button-group
28983 //
28984 Iterable _getAssociatedRadioButtons(element) {
28985 if (!_isNodeInDocument(element)) return [];
28986 if (element.form != null) {
28987 return element.form.nodes.where((el) {
28988 return el != element &&
28989 el is InputElement &&
28990 el.type == 'radio' &&
28991 el.name == element.name;
28992 });
28993 } else {
28994 var radios = element.document.queryAll(
28995 'input[type="radio"][name="${element.name}"]');
28996 return radios.where((el) => el != element && el.form == null);
28997 }
28998 }
28999
29000 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
29001 var clone = node.clone(false); // Shallow clone.
29002 if (clone is Element && clone.isTemplate) {
29003 TemplateElement.decorate(clone, node);
29004 if (syntax != null) {
29005 clone.attributes.putIfAbsent('syntax', () => syntax);
29006 }
29007 }
29008
29009 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
29010 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax));
29011 }
29012 return clone;
29013 }
29014
29015 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df n-template-contents-owner
29016 Document _getTemplateContentsOwner(Document doc) {
29017 if (doc.window == null) {
29018 return doc;
29019 }
29020 var d = doc._templateContentsOwner;
29021 if (d == null) {
29022 // TODO(arv): This should either be a Document or HTMLDocument depending
29023 // on doc.
29024 d = doc.implementation.createHtmlDocument('');
29025 while (d.$dom_lastChild != null) {
29026 d.$dom_lastChild.remove();
29027 }
29028 doc._templateContentsOwner = d;
29029 }
29030 return d;
29031 }
29032
29033 Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
29034 var clone = templateElement.clone(false);
29035 var attributes = templateElement.attributes;
29036 for (var name in attributes.keys.toList()) {
29037 switch (name) {
29038 case 'template':
29039 case 'repeat':
29040 case 'bind':
29041 case 'ref':
29042 clone.attributes.remove(name);
29043 break;
29044 default:
29045 attributes.remove(name);
29046 break;
29047 }
29048 }
29049
29050 return clone;
29051 }
29052
29053 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) {
29054 var content = templateElement.content;
29055
29056 if (!templateElement._isAttributeTemplate) {
29057 var child;
29058 while ((child = templateElement.$dom_firstChild) != null) {
29059 content.append(child);
29060 }
29061 return;
29062 }
29063
29064 // For attribute templates we copy the whole thing into the content and
29065 // we move the non template attributes into the content.
29066 //
29067 // <tr foo template>
29068 //
29069 // becomes
29070 //
29071 // <tr template>
29072 // + #document-fragment
29073 // + <tr foo>
29074 //
29075 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
29076 var child;
29077 while ((child = templateElement.$dom_firstChild) != null) {
29078 newRoot.append(child);
29079 }
29080 content.append(newRoot);
29081 }
29082
29083 void _bootstrapTemplatesRecursivelyFrom(Node node) {
29084 void bootstrap(template) {
29085 if (!TemplateElement.decorate(template)) {
29086 _bootstrapTemplatesRecursivelyFrom(template.content);
29087 }
29088 }
29089
29090 // Need to do this first as the contents may get lifted if |node| is
29091 // template.
29092 // TODO(jmesserly): node is DocumentFragment or Element
29093 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors);
29094 if (node is Element && node.isTemplate) bootstrap(node);
29095
29096 templateDescendents.forEach(bootstrap);
29097 }
29098
29099 final String _allTemplatesSelectors = 'template, option[template], ' +
29100 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
29101
29102 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) {
29103 if (node is Element) {
29104 _addAttributeBindings(node, model, syntax);
29105 } else if (node is Text) {
29106 _parseAndBind(node, node.text, 'text', model, syntax);
29107 }
29108
29109 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
29110 _addBindings(c, model, syntax);
29111 }
29112 }
29113
29114
29115 void _addAttributeBindings(Element element, model, syntax) {
29116 element.attributes.forEach((name, value) {
29117 if (value == '' && (name == 'bind' || name == 'repeat')) {
29118 value = '{{}}';
29119 }
29120 _parseAndBind(element, value, name, model, syntax);
29121 });
29122 }
29123
29124 void _parseAndBind(Node node, String text, String name, model,
29125 CustomBindingSyntax syntax) {
29126
29127 var tokens = _parseMustacheTokens(text);
29128 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
29129 return;
29130 }
29131
29132 if (tokens.length == 1 && tokens[0].isBinding) {
29133 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
29134 return;
29135 }
29136
29137 var replacementBinding = new CompoundBinding();
29138 for (var i = 0; i < tokens.length; i++) {
29139 var token = tokens[i];
29140 if (token.isBinding) {
29141 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
29142 }
29143 }
29144
29145 replacementBinding.combinator = (values) {
29146 var newValue = new StringBuffer();
29147
29148 for (var i = 0; i < tokens.length; i++) {
29149 var token = tokens[i];
29150 if (token.isText) {
29151 newValue.write(token.value);
29152 } else {
29153 var value = values[i];
29154 if (value != null) {
29155 newValue.write(value);
29156 }
29157 }
29158 }
29159
29160 return newValue.toString();
29161 };
29162
29163 node.bind(name, replacementBinding, 'value');
29164 }
29165
29166 void _bindOrDelegate(node, name, model, String path,
29167 CustomBindingSyntax syntax) {
29168
29169 if (syntax != null) {
29170 var delegateBinding = syntax.getBinding(model, path, name, node);
29171 if (delegateBinding != null) {
29172 model = delegateBinding;
29173 path = 'value';
29174 }
29175 }
29176
29177 node.bind(name, model, path);
29178 }
29179
29180 class _BindingToken {
29181 final String value;
29182 final bool isBinding;
29183
29184 _BindingToken(this.value, {this.isBinding: false});
29185
29186 bool get isText => !isBinding;
29187 }
29188
29189 List<_BindingToken> _parseMustacheTokens(String s) {
29190 var result = [];
29191 var length = s.length;
29192 var index = 0, lastIndex = 0;
29193 while (lastIndex < length) {
29194 index = s.indexOf('{{', lastIndex);
29195 if (index < 0) {
29196 result.add(new _BindingToken(s.substring(lastIndex)));
29197 break;
29198 } else {
29199 // There is a non-empty text run before the next path token.
29200 if (index > 0 && lastIndex < index) {
29201 result.add(new _BindingToken(s.substring(lastIndex, index)));
29202 }
29203 lastIndex = index + 2;
29204 index = s.indexOf('}}', lastIndex);
29205 if (index < 0) {
29206 var text = s.substring(lastIndex - 2);
29207 if (result.length > 0 && result.last.isText) {
29208 result.last.value += text;
29209 } else {
29210 result.add(new _BindingToken(text));
29211 }
29212 break;
29213 }
29214
29215 var value = s.substring(lastIndex, index).trim();
29216 result.add(new _BindingToken(value, isBinding: true));
29217 lastIndex = index + 2;
29218 }
29219 }
29220 return result;
29221 }
29222
29223 void _addTemplateInstanceRecord(fragment, model) {
29224 if (fragment.$dom_firstChild == null) {
29225 return;
29226 }
29227
29228 var instanceRecord = new TemplateInstance(
29229 fragment.$dom_firstChild, fragment.$dom_lastChild, model);
29230
29231 var node = instanceRecord.firstNode;
29232 while (node != null) {
29233 node._templateInstance = instanceRecord;
29234 node = node.nextNode;
29235 }
29236 }
29237
29238 void _removeAllBindingsRecursively(Node node) {
29239 node.unbindAll();
29240 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
29241 _removeAllBindingsRecursively(c);
29242 }
29243 }
29244
29245 void _removeTemplateChild(Node parent, Node child) {
29246 child._templateInstance = null;
29247 if (child is Element && child.isTemplate) {
29248 // Make sure we stop observing when we remove an element.
29249 var templateIterator = child._templateIterator;
29250 if (templateIterator != null) {
29251 templateIterator.abandon();
29252 child._templateIterator = null;
29253 }
29254 }
29255 child.remove();
29256 _removeAllBindingsRecursively(child);
29257 }
29258
29259 class _InstanceCursor {
29260 final Element _template;
29261 Node _terminator;
29262 Node _previousTerminator;
29263 int _previousIndex = -1;
29264 int _index = 0;
29265
29266 _InstanceCursor(this._template, [index]) {
29267 _terminator = _template;
29268 if (index != null) {
29269 while (index-- > 0) {
29270 next();
29271 }
29272 }
29273 }
29274
29275 void next() {
29276 _previousTerminator = _terminator;
29277 _previousIndex = _index;
29278 _index++;
29279
29280 while (_index > _terminator._instanceTerminatorCount) {
29281 _index -= _terminator._instanceTerminatorCount;
29282 _terminator = _terminator.nextNode;
29283 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
29284 _index += _instanceCount(_terminator);
29285 }
29286 }
29287 }
29288
29289 void abandon() {
29290 assert(_instanceCount(_template) > 0);
29291 assert(_terminator._instanceTerminatorCount > 0);
29292 assert(_index > 0);
29293
29294 _terminator._instanceTerminatorCount--;
29295 _index--;
29296 }
29297
29298 void insert(fragment) {
29299 assert(_template.parentNode != null);
29300
29301 _previousTerminator = _terminator;
29302 _previousIndex = _index;
29303 _index++;
29304
29305 _terminator = fragment.$dom_lastChild;
29306 if (_terminator == null) _terminator = _previousTerminator;
29307 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
29308
29309 _terminator._instanceTerminatorCount++;
29310 if (_terminator != _previousTerminator) {
29311 while (_previousTerminator._instanceTerminatorCount >
29312 _previousIndex) {
29313 _previousTerminator._instanceTerminatorCount--;
29314 _terminator._instanceTerminatorCount++;
29315 }
29316 }
29317 }
29318
29319 void remove() {
29320 assert(_previousIndex != -1);
29321 assert(_previousTerminator != null &&
29322 (_previousIndex > 0 || _previousTerminator == _template));
29323 assert(_terminator != null && _index > 0);
29324 assert(_template.parentNode != null);
29325 assert(_instanceCount(_template) > 0);
29326
29327 if (_previousTerminator == _terminator) {
29328 assert(_index == _previousIndex + 1);
29329 _terminator._instanceTerminatorCount--;
29330 _terminator = _template;
29331 _previousTerminator = null;
29332 _previousIndex = -1;
29333 return;
29334 }
29335
29336 _terminator._instanceTerminatorCount--;
29337
29338 var parent = _template.parentNode;
29339 while (_previousTerminator.nextNode != _terminator) {
29340 _removeTemplateChild(parent, _previousTerminator.nextNode);
29341 }
29342 _removeTemplateChild(parent, _terminator);
29343
29344 _terminator = _previousTerminator;
29345 _index = _previousIndex;
29346 _previousTerminator = null;
29347 _previousIndex = -1; // 0?
29348 }
29349 }
29350
29351
29352 class _TemplateIterator {
29353 final Element _templateElement;
29354 int instanceCount = 0;
29355 List iteratedValue;
29356 bool observing = false;
29357 CompoundBinding inputs;
29358 _Binding valueBinding;
29359 StreamSubscription _sub;
29360
29361 _TemplateIterator(this._templateElement) {
29362 inputs = new CompoundBinding(resolveInputs);
29363 valueBinding = new _Binding(inputs, 'value', valueChanged);
29364 }
29365
29366 Object resolveInputs(Map values) {
29367 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) {
29368 return null;
29369 }
29370
29371 if (values.containsKey('repeat')) {
29372 return values['repeat'];
29373 }
29374
29375 if (values.containsKey('bind')) {
29376 return [values['bind']];
29377 }
29378
29379 return null;
29380 }
29381
29382 void valueChanged(value) {
29383 clear();
29384 if (value is! List) return;
29385
29386 iteratedValue = value;
29387
29388 if (value is Observable) {
29389 _sub = value.changes.listen(handleChange);
29390 }
29391
29392 handleSplices([new ListChangeDelta(0, addedCount: iteratedValue.length)]);
29393 }
29394
29395 // TODO(jmesserly): these properties appear not be finished. I think it's
29396 // part of custom syntax like repeat="i in items".
29397 getInstanceModel(model, syntax) => model;
29398 getInstanceFragment(syntax) => _templateElement.createInstance();
29399
29400 void handleChange(List<ChangeRecord> records) {
29401 handleSplices(summarizeListChanges(iteratedValue, records));
29402 }
29403
29404 void handleSplices(List<ListChangeDelta> splices) {
29405 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
29406
29407 for (var splice in splices) {
29408 for (var removed in splice.removed) {
29409 var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
29410 cursor.remove();
29411 instanceCount--;
29412 }
29413
29414 for (var addIndex = splice.index;
29415 addIndex < splice.index + splice.addedCount;
29416 addIndex++) {
29417
29418 var model = getInstanceModel(iteratedValue[addIndex], syntax);
29419 var fragment = getInstanceFragment(syntax);
29420
29421 _addBindings(fragment, model, syntax);
29422 _addTemplateInstanceRecord(fragment, model);
29423
29424 var cursor = new _InstanceCursor(_templateElement, addIndex);
29425 cursor.insert(fragment);
29426 instanceCount++;
29427 }
29428 }
29429 }
29430
29431 void unobserve() {
29432 if (_sub == null) return;
29433 _sub.cancel();
29434 _sub = null;
29435 }
29436
29437 void clear() {
29438 unobserve();
29439
29440 iteratedValue = null;
29441 if (instanceCount == 0) return;
29442
29443 for (var i = 0; i < instanceCount; i++) {
29444 var cursor = new _InstanceCursor(_templateElement, 1);
29445 cursor.remove();
29446 }
29447
29448 instanceCount = 0;
29449 }
29450
29451 void abandon() {
29452 unobserve();
29453 valueBinding.dispose();
29454 inputs.dispose();
29455 }
29456 }
29457
29458 int _instanceCount(Element element) {
29459 var templateIterator = element._templateIterator;
29460 return templateIterator != null ? templateIterator.instanceCount : 0;
29461 }
28364 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 29462 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28365 // for details. All rights reserved. Use of this source code is governed by a 29463 // for details. All rights reserved. Use of this source code is governed by a
28366 // BSD-style license that can be found in the LICENSE file. 29464 // BSD-style license that can be found in the LICENSE file.
28367 29465
28368 29466
28369 class _HttpRequestUtils { 29467 class _HttpRequestUtils {
28370 29468
28371 // Helper for factory HttpRequest.get 29469 // Helper for factory HttpRequest.get
28372 static HttpRequest get(String url, 29470 static HttpRequest get(String url,
28373 onComplete(HttpRequest request), 29471 onComplete(HttpRequest request),
(...skipping 763 matching lines...) Expand 10 before | Expand all | Expand 10 after
29137 DateTime _convertNativeToDart_DateTime(date) { 30235 DateTime _convertNativeToDart_DateTime(date) {
29138 var millisSinceEpoch = JS('int', '#.getTime()', date); 30236 var millisSinceEpoch = JS('int', '#.getTime()', date);
29139 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); 30237 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true);
29140 } 30238 }
29141 30239
29142 _convertDartToNative_DateTime(DateTime date) { 30240 _convertDartToNative_DateTime(DateTime date) {
29143 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); 30241 return JS('', 'new Date(#)', date.millisecondsSinceEpoch);
29144 } 30242 }
29145 30243
29146 WindowBase _convertNativeToDart_Window(win) { 30244 WindowBase _convertNativeToDart_Window(win) {
30245 if (win == null) return null;
29147 return _DOMWindowCrossFrame._createSafe(win); 30246 return _DOMWindowCrossFrame._createSafe(win);
29148 } 30247 }
29149 30248
29150 EventTarget _convertNativeToDart_EventTarget(e) { 30249 EventTarget _convertNativeToDart_EventTarget(e) {
29151 if (e == null) { 30250 if (e == null) {
29152 return null; 30251 return null;
29153 } 30252 }
29154 // Assume it's a Window if it contains the setInterval property. It may be 30253 // Assume it's a Window if it contains the setInterval property. It may be
29155 // from a different frame - without a patched prototype - so we cannot 30254 // from a different frame - without a patched prototype - so we cannot
29156 // rely on Dart type checking. 30255 // rely on Dart type checking.
(...skipping 419 matching lines...) Expand 10 before | Expand all | Expand 10 after
29576 _position = nextPosition; 30675 _position = nextPosition;
29577 return true; 30676 return true;
29578 } 30677 }
29579 _current = null; 30678 _current = null;
29580 _position = _array.length; 30679 _position = _array.length;
29581 return false; 30680 return false;
29582 } 30681 }
29583 30682
29584 T get current => _current; 30683 T get current => _current;
29585 } 30684 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698