| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 } |
| OLD | NEW |