| 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 520 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 6527 * | 6530 * |
| 6528 * [selectors] should be a string using CSS selector syntax. | 6531 * [selectors] should be a string using CSS selector syntax. |
| 6529 * var items = document.queryAll('.itemClassName'); | 6532 * var items = document.queryAll('.itemClassName'); |
| 6530 * | 6533 * |
| 6531 * For details about CSS selector syntax, see the | 6534 * For details about CSS selector syntax, see the |
| 6532 * [CSS selector specification](http://www.w3.org/TR/css3-selectors/). | 6535 * [CSS selector specification](http://www.w3.org/TR/css3-selectors/). |
| 6533 */ | 6536 */ |
| 6534 List<Element> queryAll(String selectors) { | 6537 List<Element> queryAll(String selectors) { |
| 6535 return new _FrozenElementList._wrap($dom_querySelectorAll(selectors)); | 6538 return new _FrozenElementList._wrap($dom_querySelectorAll(selectors)); |
| 6536 } | 6539 } |
| 6540 |
| 6541 // Note: used to polyfill <template> |
| 6542 Document _templateContentsOwner; |
| 6537 } | 6543 } |
| 6538 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 6544 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 6539 // for details. All rights reserved. Use of this source code is governed by a | 6545 // for details. All rights reserved. Use of this source code is governed by a |
| 6540 // BSD-style license that can be found in the LICENSE file. | 6546 // BSD-style license that can be found in the LICENSE file. |
| 6541 | 6547 |
| 6542 | 6548 |
| 6543 @DomName('DocumentFragment') | 6549 @DomName('DocumentFragment') |
| 6544 class DocumentFragment extends Node native "DocumentFragment" { | 6550 class DocumentFragment extends Node native "DocumentFragment" { |
| 6545 factory DocumentFragment() => _DocumentFragmentFactoryProvider.createDocumentF
ragment(); | 6551 factory DocumentFragment() => _DocumentFragmentFactoryProvider.createDocumentF
ragment(); |
| 6546 | 6552 |
| (...skipping 1850 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 8397 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { | 8403 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { |
| 8398 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); | 8404 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); |
| 8399 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { | 8405 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { |
| 8400 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); | 8406 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); |
| 8401 } else if (JS('bool', '!!#.msMatchesSelector', this)) { | 8407 } else if (JS('bool', '!!#.msMatchesSelector', this)) { |
| 8402 return JS('bool', '#.msMatchesSelector(#)', this, selectors); | 8408 return JS('bool', '#.msMatchesSelector(#)', this, selectors); |
| 8403 } | 8409 } |
| 8404 throw new UnsupportedError("Not supported on this platform"); | 8410 throw new UnsupportedError("Not supported on this platform"); |
| 8405 } | 8411 } |
| 8406 | 8412 |
| 8413 Map<String, _Binding> _attributeBindings; |
| 8414 |
| 8415 // TODO(jmesserly): I'm concerned about adding these to every element. |
| 8416 // Conceptually all of these belong on TemplateElement. They are here to |
| 8417 // support browsers that don't have <template> yet. |
| 8418 // However even in the polyfill they're restricted to certain tags |
| 8419 // (see [isTemplate]). So we can probably convert it to a (public) mixin, and |
| 8420 // only mix it in to the elements that need it. |
| 8421 @Creates('Null') |
| 8422 var _model; |
| 8423 |
| 8424 _TemplateIterator _templateIterator; |
| 8425 |
| 8426 Element _templateInstanceRef; |
| 8427 |
| 8428 // Note: only used if `this is! TemplateElement` |
| 8429 DocumentFragment _templateContent; |
| 8430 |
| 8431 bool _templateIsDecorated; |
| 8432 |
| 8433 // TODO(jmesserly): should path be optional, and default to empty path? |
| 8434 // It is used that way in at least one path in JS TemplateElement tests |
| 8435 // (see "BindImperative" test in original JS code). |
| 8436 @Experimental |
| 8437 void bind(String name, model, String path) { |
| 8438 if (_bindTemplate(name, model, path)) return; |
| 8439 |
| 8440 if (_attributeBindings == null) { |
| 8441 _attributeBindings = new Map<String, _Binding>(); |
| 8442 } |
| 8443 |
| 8444 attributes.remove(name); |
| 8445 |
| 8446 _ChangeHandler changed; |
| 8447 if (name.endsWith('?')) { |
| 8448 name = name.substring(0, name.length - 1); |
| 8449 |
| 8450 changed = (value) { |
| 8451 if (_templateBooleanConversion(value)) { |
| 8452 attributes[name] = ''; |
| 8453 } else { |
| 8454 attributes.remove(name); |
| 8455 } |
| 8456 }; |
| 8457 } else { |
| 8458 changed = (value) { |
| 8459 // TODO(jmesserly): escape value if needed to protect against XSS. |
| 8460 attributes[name] = value == null ? '' : '$value'; |
| 8461 }; |
| 8462 } |
| 8463 |
| 8464 unbind(name); |
| 8465 |
| 8466 _attributeBindings[name] = new _Binding(model, path, changed); |
| 8467 } |
| 8468 |
| 8469 @Experimental |
| 8470 void unbind(String name) { |
| 8471 if (_unbindTemplate(name)) return; |
| 8472 if (_attributeBindings != null) { |
| 8473 var binding = _attributeBindings.remove(name); |
| 8474 if (binding != null) binding.dispose(); |
| 8475 } |
| 8476 } |
| 8477 |
| 8478 @Experimental |
| 8479 void unbindAll() { |
| 8480 _unbindAllTemplate(); |
| 8481 |
| 8482 if (_attributeBindings != null) { |
| 8483 for (var binding in _attributeBindings.values) { |
| 8484 binding.dispose(); |
| 8485 } |
| 8486 _attributeBindings = null; |
| 8487 super.unbindAll(); |
| 8488 } |
| 8489 } |
| 8490 |
| 8491 // TODO(jmesserly): unlike the JS polyfill, we can't mixin |
| 8492 // HTMLTemplateElement at runtime into things that are semantically template |
| 8493 // elements. So instead we implement it here with a runtime check. |
| 8494 // If the bind succeeds, we return true, otherwise we return false and let |
| 8495 // the normal Element.bind logic kick in. |
| 8496 bool _bindTemplate(String name, model, String path) { |
| 8497 if (isTemplate) { |
| 8498 switch (name) { |
| 8499 case 'bind': |
| 8500 case 'repeat': |
| 8501 case 'if': |
| 8502 _ensureTemplate(); |
| 8503 if (_templateIterator == null) { |
| 8504 _templateIterator = new _TemplateIterator(this); |
| 8505 } |
| 8506 _templateIterator.inputs.bind(name, model, path); |
| 8507 return true; |
| 8508 } |
| 8509 } |
| 8510 return false; |
| 8511 } |
| 8512 |
| 8513 bool _unbindTemplate(String name) { |
| 8514 if (isTemplate) { |
| 8515 switch (name) { |
| 8516 case 'bind': |
| 8517 case 'repeat': |
| 8518 case 'if': |
| 8519 _ensureTemplate(); |
| 8520 if (_templateIterator != null) { |
| 8521 _templateIterator.inputs.unbind(name); |
| 8522 } |
| 8523 return true; |
| 8524 } |
| 8525 } |
| 8526 return false; |
| 8527 } |
| 8528 |
| 8529 void _unbindAllTemplate() { |
| 8530 if (isTemplate) { |
| 8531 unbind('bind'); |
| 8532 unbind('repeat'); |
| 8533 unbind('if'); |
| 8534 } |
| 8535 } |
| 8536 |
| 8537 /** |
| 8538 * Gets the template this node refers to. |
| 8539 * This is only supported if [isTemplate] is true. |
| 8540 */ |
| 8541 @Experimental |
| 8542 Element get ref { |
| 8543 _ensureTemplate(); |
| 8544 |
| 8545 Element ref = null; |
| 8546 var refId = attributes['ref']; |
| 8547 if (refId != null) { |
| 8548 ref = document.getElementById(refId); |
| 8549 } |
| 8550 |
| 8551 return ref != null ? ref : _templateInstanceRef; |
| 8552 } |
| 8553 |
| 8554 /** |
| 8555 * Gets the content of this template. |
| 8556 * This is only supported if [isTemplate] is true. |
| 8557 */ |
| 8558 @Experimental |
| 8559 DocumentFragment get content { |
| 8560 _ensureTemplate(); |
| 8561 return _templateContent; |
| 8562 } |
| 8563 |
| 8564 /** |
| 8565 * Creates an instance of the template. |
| 8566 * This is only supported if [isTemplate] is true. |
| 8567 */ |
| 8568 @Experimental |
| 8569 DocumentFragment createInstance() { |
| 8570 _ensureTemplate(); |
| 8571 |
| 8572 var template = ref; |
| 8573 if (template == null) template = this; |
| 8574 |
| 8575 var instance = _createDeepCloneAndDecorateTemplates(template.content, |
| 8576 attributes['syntax']); |
| 8577 |
| 8578 if (TemplateElement._instanceCreated != null) { |
| 8579 TemplateElement._instanceCreated.add(instance); |
| 8580 } |
| 8581 return instance; |
| 8582 } |
| 8583 |
| 8584 /** |
| 8585 * The data model which is inherited through the tree. |
| 8586 * This is only supported if [isTemplate] is true. |
| 8587 * |
| 8588 * Setting this will destructive propagate the value to all descendant nodes, |
| 8589 * and reinstantiate all of the nodes expanded by this template. |
| 8590 * |
| 8591 * Currently this does not support propagation through Shadow DOMs. |
| 8592 */ |
| 8593 @Experimental |
| 8594 get model => _model; |
| 8595 |
| 8596 @Experimental |
| 8597 void set model(value) { |
| 8598 _ensureTemplate(); |
| 8599 |
| 8600 _model = value; |
| 8601 _addBindings(this, model); |
| 8602 } |
| 8603 |
| 8604 // TODO(jmesserly): const set would be better |
| 8605 static const _TABLE_TAGS = const { |
| 8606 'caption': null, |
| 8607 'col': null, |
| 8608 'colgroup': null, |
| 8609 'tbody': null, |
| 8610 'td': null, |
| 8611 'tfoot': null, |
| 8612 'th': null, |
| 8613 'thead': null, |
| 8614 'tr': null, |
| 8615 }; |
| 8616 |
| 8617 bool get _isAttributeTemplate => attributes.containsKey('template') && |
| 8618 (localName == 'option' || _TABLE_TAGS.containsKey(localName)); |
| 8619 |
| 8620 /** |
| 8621 * Returns true if this node is a template. |
| 8622 * |
| 8623 * A node is a template if [tagName] is TEMPLATE, or the node has the |
| 8624 * 'template' attribute and this tag supports attribute form for backwards |
| 8625 * compatibility with existing HTML parsers. The nodes that can use attribute |
| 8626 * form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP |
| 8627 * and COL) and OPTION. |
| 8628 */ |
| 8629 // TODO(jmesserly): this is not a public MDV API, but it seems like a useful |
| 8630 // place to document which tags our polyfill considers to be templates. |
| 8631 // Otherwise I'd be repeating it in several other places. |
| 8632 // See if we can replace this with a TemplateMixin. |
| 8633 @Experimental |
| 8634 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate; |
| 8635 |
| 8636 void _ensureTemplate() { |
| 8637 if (!isTemplate) { |
| 8638 throw new UnsupportedError('$this is not a template.'); |
| 8639 } |
| 8640 TemplateElement.decorate(this); |
| 8641 } |
| 8642 |
| 8407 | 8643 |
| 8408 @DomName('Element.abortEvent') | 8644 @DomName('Element.abortEvent') |
| 8409 @DocsEditable | 8645 @DocsEditable |
| 8410 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider
<Event>('abort'); | 8646 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider
<Event>('abort'); |
| 8411 | 8647 |
| 8412 @DomName('Element.beforecopyEvent') | 8648 @DomName('Element.beforecopyEvent') |
| 8413 @DocsEditable | 8649 @DocsEditable |
| 8414 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro
vider<Event>('beforecopy'); | 8650 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro
vider<Event>('beforecopy'); |
| 8415 | 8651 |
| 8416 @DomName('Element.beforecutEvent') | 8652 @DomName('Element.beforecutEvent') |
| (...skipping 724 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 9141 @DomName('Element.onwebkitfullscreenchange') | 9377 @DomName('Element.onwebkitfullscreenchange') |
| 9142 @DocsEditable | 9378 @DocsEditable |
| 9143 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); | 9379 Stream<Event> get onFullscreenChange => fullscreenChangeEvent.forTarget(this); |
| 9144 | 9380 |
| 9145 @DomName('Element.onwebkitfullscreenerror') | 9381 @DomName('Element.onwebkitfullscreenerror') |
| 9146 @DocsEditable | 9382 @DocsEditable |
| 9147 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); | 9383 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); |
| 9148 | 9384 |
| 9149 } | 9385 } |
| 9150 | 9386 |
| 9387 |
| 9151 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); | 9388 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); |
| 9152 class _ElementFactoryProvider { | 9389 class _ElementFactoryProvider { |
| 9153 static const _CUSTOM_PARENT_TAG_MAP = const { | 9390 static const _CUSTOM_PARENT_TAG_MAP = const { |
| 9154 'body' : 'html', | 9391 'body' : 'html', |
| 9155 'head' : 'html', | 9392 'head' : 'html', |
| 9156 'caption' : 'table', | 9393 'caption' : 'table', |
| 9157 'td': 'tr', | 9394 'td': 'tr', |
| 9158 'th': 'tr', | 9395 'th': 'tr', |
| 9159 'colgroup': 'table', | 9396 'colgroup': 'table', |
| 9160 'col' : 'colgroup', | 9397 'col' : 'colgroup', |
| 9161 'tr' : 'tbody', | 9398 'tr' : 'tbody', |
| 9162 'tbody' : 'table', | 9399 'tbody' : 'table', |
| 9163 'tfoot' : 'table', | 9400 'tfoot' : 'table', |
| 9164 'thead' : 'table', | 9401 'thead' : 'table', |
| 9165 'track' : 'audio', | 9402 'track' : 'audio', |
| 9166 }; | 9403 }; |
| 9167 | 9404 |
| 9168 // TODO(jmesserly): const set would be better | |
| 9169 static const _TABLE_TAGS = const { | |
| 9170 'caption': null, | |
| 9171 'col': null, | |
| 9172 'colgroup': null, | |
| 9173 'tbody': null, | |
| 9174 'td': null, | |
| 9175 'tfoot': null, | |
| 9176 'th': null, | |
| 9177 'thead': null, | |
| 9178 'tr': null, | |
| 9179 }; | |
| 9180 | |
| 9181 @DomName('Document.createElement') | 9405 @DomName('Document.createElement') |
| 9182 static Element createElement_html(String html) { | 9406 static Element createElement_html(String html) { |
| 9183 // TODO(jacobr): this method can be made more robust and performant. | 9407 // TODO(jacobr): this method can be made more robust and performant. |
| 9184 // 1) Cache the dummy parent elements required to use innerHTML rather than | 9408 // 1) Cache the dummy parent elements required to use innerHTML rather than |
| 9185 // creating them every call. | 9409 // creating them every call. |
| 9186 // 2) Verify that the html does not contain leading or trailing text nodes. | 9410 // 2) Verify that the html does not contain leading or trailing text nodes. |
| 9187 // 3) Verify that the html does not contain both <head> and <body> tags. | 9411 // 3) Verify that the html does not contain both <head> and <body> tags. |
| 9188 // 4) Detatch the created element from its dummy parent. | 9412 // 4) Detatch the created element from its dummy parent. |
| 9189 String parentTag = 'div'; | 9413 String parentTag = 'div'; |
| 9190 String tag; | 9414 String tag; |
| 9191 final match = _START_TAG_REGEXP.firstMatch(html); | 9415 final match = _START_TAG_REGEXP.firstMatch(html); |
| 9192 if (match != null) { | 9416 if (match != null) { |
| 9193 tag = match.group(1).toLowerCase(); | 9417 tag = match.group(1).toLowerCase(); |
| 9194 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { | 9418 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { |
| 9195 return _createTableForIE(html, tag); | 9419 return _createTableForIE(html, tag); |
| 9196 } | 9420 } |
| 9197 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; | 9421 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; |
| 9198 if (parentTag == null) parentTag = 'div'; | 9422 if (parentTag == null) parentTag = 'div'; |
| 9199 } | 9423 } |
| 9200 | 9424 |
| 9201 final temp = new Element.tag(parentTag); | 9425 final temp = new Element.tag(parentTag); |
| 9202 temp.innerHtml = html; | 9426 temp.innerHtml = html; |
| 9203 | 9427 |
| 9204 Element element; | 9428 Element element; |
| (...skipping 3297 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 12502 var e = document.$dom_createElement("input"); | 12726 var e = document.$dom_createElement("input"); |
| 12503 if (type != null) { | 12727 if (type != null) { |
| 12504 try { | 12728 try { |
| 12505 // IE throws an exception for unknown types. | 12729 // IE throws an exception for unknown types. |
| 12506 e.type = type; | 12730 e.type = type; |
| 12507 } catch(_) {} | 12731 } catch(_) {} |
| 12508 } | 12732 } |
| 12509 return e; | 12733 return e; |
| 12510 } | 12734 } |
| 12511 | 12735 |
| 12736 _ValueBinding _valueBinding; |
| 12737 _CheckedBinding _checkedBinding; |
| 12738 |
| 12739 @Experimental |
| 12740 void bind(String name, model, String path) { |
| 12741 switch (name) { |
| 12742 case 'value': |
| 12743 unbind('value'); |
| 12744 attributes.remove('value'); |
| 12745 _valueBinding = new _ValueBinding(this, model, path); |
| 12746 break; |
| 12747 case 'checked': |
| 12748 unbind('checked'); |
| 12749 attributes.remove('checked'); |
| 12750 _checkedBinding = new _CheckedBinding(this, model, path); |
| 12751 break; |
| 12752 default: |
| 12753 super.bind(name, model, path); |
| 12754 break; |
| 12755 } |
| 12756 } |
| 12757 |
| 12758 @Experimental |
| 12759 void unbind(String name) { |
| 12760 switch (name) { |
| 12761 case 'value': |
| 12762 if (_valueBinding != null) { |
| 12763 _valueBinding.unbind(); |
| 12764 _valueBinding = null; |
| 12765 } |
| 12766 break; |
| 12767 case 'checked': |
| 12768 if (_checkedBinding != null) { |
| 12769 _checkedBinding.unbind(); |
| 12770 _checkedBinding = null; |
| 12771 } |
| 12772 break; |
| 12773 default: |
| 12774 super.unbind(name); |
| 12775 break; |
| 12776 } |
| 12777 } |
| 12778 |
| 12779 @Experimental |
| 12780 void unbindAll() { |
| 12781 unbind('value'); |
| 12782 unbind('checked'); |
| 12783 super.unbindAll(); |
| 12784 } |
| 12785 |
| 12786 |
| 12512 @DomName('HTMLInputElement.webkitSpeechChangeEvent') | 12787 @DomName('HTMLInputElement.webkitSpeechChangeEvent') |
| 12513 @DocsEditable | 12788 @DocsEditable |
| 12514 @SupportedBrowser(SupportedBrowser.CHROME) | 12789 @SupportedBrowser(SupportedBrowser.CHROME) |
| 12515 @SupportedBrowser(SupportedBrowser.SAFARI) | 12790 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 12516 @Experimental | 12791 @Experimental |
| 12517 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); | 12792 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); |
| 12518 | 12793 |
| 12519 @DomName('HTMLInputElement.accept') | 12794 @DomName('HTMLInputElement.accept') |
| 12520 @DocsEditable | 12795 @DocsEditable |
| 12521 String accept; | 12796 String accept; |
| (...skipping 3162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 15684 _this.$dom_removeChild(node); | 15959 _this.$dom_removeChild(node); |
| 15685 return true; | 15960 return true; |
| 15686 } | 15961 } |
| 15687 | 15962 |
| 15688 void _filter(bool test(Node node), bool removeMatching) { | 15963 void _filter(bool test(Node node), bool removeMatching) { |
| 15689 // This implementation of removeWhere/retainWhere is more efficient | 15964 // This implementation of removeWhere/retainWhere is more efficient |
| 15690 // than the default in ListBase. Child nodes can be removed in constant | 15965 // than the default in ListBase. Child nodes can be removed in constant |
| 15691 // time. | 15966 // time. |
| 15692 Node child = _this.$dom_firstChild; | 15967 Node child = _this.$dom_firstChild; |
| 15693 while (child != null) { | 15968 while (child != null) { |
| 15694 Node nextChild = child.nextSibling; | 15969 Node nextChild = child.nextNode; |
| 15695 if (test(child) == removeMatching) { | 15970 if (test(child) == removeMatching) { |
| 15696 _this.$dom_removeChild(child); | 15971 _this.$dom_removeChild(child); |
| 15697 } | 15972 } |
| 15698 child = nextChild; | 15973 child = nextChild; |
| 15699 } | 15974 } |
| 15700 } | 15975 } |
| 15701 | 15976 |
| 15702 void removeWhere(bool test(Node node)) { | 15977 void removeWhere(bool test(Node node)) { |
| 15703 _filter(test, true); | 15978 _filter(test, true); |
| 15704 } | 15979 } |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 15809 // Should use $dom_firstChild, Bug 8886. | 16084 // Should use $dom_firstChild, Bug 8886. |
| 15810 this.insertBefore(newNodes[0], refChild); | 16085 this.insertBefore(newNodes[0], refChild); |
| 15811 } | 16086 } |
| 15812 } else { | 16087 } else { |
| 15813 for (var node in newNodes) { | 16088 for (var node in newNodes) { |
| 15814 this.insertBefore(node, refChild); | 16089 this.insertBefore(node, refChild); |
| 15815 } | 16090 } |
| 15816 } | 16091 } |
| 15817 } | 16092 } |
| 15818 | 16093 |
| 15819 // Note that this may either be the locally set model or a cached value | |
| 15820 // of the inherited model. This is cached to minimize model change | |
| 15821 // notifications. | |
| 15822 @Creates('Null') | |
| 15823 var _model; | |
| 15824 bool _hasLocalModel; | |
| 15825 Set<StreamController<Node>> _modelChangedStreams; | |
| 15826 | |
| 15827 /** | |
| 15828 * The data model which is inherited through the tree. | |
| 15829 * | |
| 15830 * Setting this will propagate the value to all descendant nodes. If the | |
| 15831 * model is not set on this node then it will be inherited from ancestor | |
| 15832 * nodes. | |
| 15833 * | |
| 15834 * Currently this does not support propagation through Shadow DOMs. | |
| 15835 * | |
| 15836 * [clearModel] must be used to remove the model property from this node | |
| 15837 * and have the model inherit from ancestor nodes. | |
| 15838 */ | |
| 15839 @Experimental | |
| 15840 get model { | |
| 15841 // If we have a change handler then we've cached the model locally. | |
| 15842 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) { | |
| 15843 return _model; | |
| 15844 } | |
| 15845 // Otherwise start looking up the tree. | |
| 15846 for (var node = this; node != null; node = node.parentNode) { | |
| 15847 if (node._hasLocalModel == true) { | |
| 15848 return node._model; | |
| 15849 } | |
| 15850 } | |
| 15851 return null; | |
| 15852 } | |
| 15853 | |
| 15854 @Experimental | |
| 15855 void set model(value) { | |
| 15856 var changed = model != value; | |
| 15857 _model = value; | |
| 15858 _hasLocalModel = true; | |
| 15859 _ModelTreeObserver.initialize(); | |
| 15860 | |
| 15861 if (changed) { | |
| 15862 if (_modelChangedStreams != null && !_modelChangedStreams.isEmpty) { | |
| 15863 _modelChangedStreams.toList().forEach((stream) => stream.add(this)); | |
| 15864 } | |
| 15865 // Propagate new model to all descendants. | |
| 15866 _ModelTreeObserver.propagateModel(this, value, false); | |
| 15867 } | |
| 15868 } | |
| 15869 | |
| 15870 /** | |
| 15871 * Clears the locally set model and makes this model be inherited from parent | |
| 15872 * nodes. | |
| 15873 */ | |
| 15874 @Experimental | |
| 15875 void clearModel() { | |
| 15876 if (_hasLocalModel == true) { | |
| 15877 _hasLocalModel = false; | |
| 15878 | |
| 15879 // Propagate new model to all descendants. | |
| 15880 if (parentNode != null) { | |
| 15881 _ModelTreeObserver.propagateModel(this, parentNode.model, false); | |
| 15882 } else { | |
| 15883 _ModelTreeObserver.propagateModel(this, null, false); | |
| 15884 } | |
| 15885 } | |
| 15886 } | |
| 15887 | |
| 15888 /** | |
| 15889 * Get a stream of models, whenever the model changes. | |
| 15890 */ | |
| 15891 Stream<Node> get onModelChanged { | |
| 15892 if (_modelChangedStreams == null) { | |
| 15893 _modelChangedStreams = new Set<StreamController<Node>>(); | |
| 15894 } | |
| 15895 var controller; | |
| 15896 controller = new StreamController( | |
| 15897 onListen: () { _modelChangedStreams.add(controller); }, | |
| 15898 onCancel: () { _modelChangedStreams.remove(controller); }); | |
| 15899 return controller.stream; | |
| 15900 } | |
| 15901 | |
| 15902 /** | 16094 /** |
| 15903 * Print out a String representation of this Node. | 16095 * Print out a String representation of this Node. |
| 15904 */ | 16096 */ |
| 15905 String toString() => localName == null ? | 16097 String toString() => localName == null ? |
| 15906 (nodeValue == null ? super.toString() : nodeValue) : localName; | 16098 (nodeValue == null ? super.toString() : nodeValue) : localName; |
| 15907 | 16099 |
| 16100 /** |
| 16101 * Binds the attribute [name] to the [path] of the [model]. |
| 16102 * Path is a String of accessors such as `foo.bar.baz`. |
| 16103 */ |
| 16104 @Experimental |
| 16105 void bind(String name, model, String path) { |
| 16106 // TODO(jmesserly): should we throw instead? |
| 16107 window.console.error('Unhandled binding to Node: ' |
| 16108 '$this $name $model $path'); |
| 16109 } |
| 16110 |
| 16111 /** Unbinds the attribute [name]. */ |
| 16112 @Experimental |
| 16113 void unbind(String name) {} |
| 16114 |
| 16115 /** Unbinds all bound attributes. */ |
| 16116 @Experimental |
| 16117 void unbindAll() {} |
| 16118 |
| 16119 TemplateInstance _templateInstance; |
| 16120 |
| 16121 // TODO(arv): Consider storing all "NodeRareData" on a single object? |
| 16122 int __instanceTerminatorCount; |
| 16123 int get _instanceTerminatorCount { |
| 16124 if (__instanceTerminatorCount == null) return 0; |
| 16125 return __instanceTerminatorCount; |
| 16126 } |
| 16127 set _instanceTerminatorCount(int value) { |
| 16128 if (value == 0) value = null; |
| 16129 __instanceTerminatorCount = value; |
| 16130 } |
| 16131 |
| 16132 /** Gets the template instance that instantiated this node, if any. */ |
| 16133 @Experimental |
| 16134 TemplateInstance get templateInstance => |
| 16135 _templateInstance != null ? _templateInstance : |
| 16136 (parent != null ? parent.templateInstance : null); |
| 16137 |
| 15908 | 16138 |
| 15909 static const int ATTRIBUTE_NODE = 2; | 16139 static const int ATTRIBUTE_NODE = 2; |
| 15910 | 16140 |
| 15911 static const int CDATA_SECTION_NODE = 4; | 16141 static const int CDATA_SECTION_NODE = 4; |
| 15912 | 16142 |
| 15913 static const int COMMENT_NODE = 8; | 16143 static const int COMMENT_NODE = 8; |
| 15914 | 16144 |
| 15915 static const int DOCUMENT_FRAGMENT_NODE = 11; | 16145 static const int DOCUMENT_FRAGMENT_NODE = 11; |
| 15916 | 16146 |
| 15917 static const int DOCUMENT_NODE = 9; | 16147 static const int DOCUMENT_NODE = 9; |
| (...skipping 4132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 20050 | 20280 |
| 20051 @JSName('insertRow') | 20281 @JSName('insertRow') |
| 20052 @DomName('HTMLTableSectionElement.insertRow') | 20282 @DomName('HTMLTableSectionElement.insertRow') |
| 20053 @DocsEditable | 20283 @DocsEditable |
| 20054 Element $dom_insertRow(int index) native; | 20284 Element $dom_insertRow(int index) native; |
| 20055 } | 20285 } |
| 20056 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 20286 // 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 | 20287 // 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. | 20288 // BSD-style license that can be found in the LICENSE file. |
| 20059 | 20289 |
| 20290 // WARNING: Do not edit - generated code. |
| 20060 | 20291 |
| 20061 @DocsEditable | 20292 |
| 20293 @Experimental |
| 20062 @DomName('HTMLTemplateElement') | 20294 @DomName('HTMLTemplateElement') |
| 20063 class TemplateElement extends Element native "HTMLTemplateElement" { | 20295 class TemplateElement extends Element native "HTMLTemplateElement" { |
| 20064 | 20296 |
| 20065 @DomName('HTMLTemplateElement.content') | 20297 @DomName('HTMLTemplateElement.content') |
| 20066 @DocsEditable | 20298 @DocsEditable |
| 20067 final DocumentFragment content; | 20299 final DocumentFragment content; |
| 20300 |
| 20301 |
| 20302 /** |
| 20303 * True if `<template>` element is natively supported. |
| 20304 * |
| 20305 * Note: this primarily affects the HTML parser. Most other features of |
| 20306 * `<template>` work regardless of whether the parser supports template. |
| 20307 * You can workaround the lack of parser support by using the "template" |
| 20308 * attribute on certain nodes, see [isTemplate] for more information. |
| 20309 * |
| 20310 * See also: [content], [decorate], [bind], [unbind], [unbindAll]. |
| 20311 */ |
| 20312 static bool get supported => new Element.tag('template') is! UnknownElement; |
| 20313 |
| 20314 static StreamController<DocumentFragment> _instanceCreated; |
| 20315 |
| 20316 /** |
| 20317 * *Warning*: This is an implementation helper for Model-Driven Views and |
| 20318 * should not be used in your code. |
| 20319 * |
| 20320 * This event is fired whenever a template is instantiated via |
| 20321 * [createInstance]. |
| 20322 */ |
| 20323 // TODO(rafaelw): This is a hack, and is neccesary for the polyfill |
| 20324 // because custom elements are not upgraded during clone() |
| 20325 @Experimental |
| 20326 static Stream<DocumentFragment> get instanceCreated { |
| 20327 if (_instanceCreated == null) { |
| 20328 _instanceCreated = new StreamController<DocumentFragment>(); |
| 20329 } |
| 20330 return _instanceCreated.stream; |
| 20331 } |
| 20332 |
| 20333 /** |
| 20334 * Ensures proper API and content model for template elements. |
| 20335 * |
| 20336 * [instanceRef] can be used to set the [Element.ref] property of [template], |
| 20337 * and use the ref's content will be used as source when createInstance() is |
| 20338 * invoked. |
| 20339 * |
| 20340 * Returns true if this template was just decorated, or false if it was |
| 20341 * already decorated. |
| 20342 */ |
| 20343 @Experimental |
| 20344 static bool decorate(Element template, [Element instanceRef]) { |
| 20345 // == true check because it starts as a null field. |
| 20346 if (template._templateIsDecorated == true) return false; |
| 20347 |
| 20348 template._templateIsDecorated = true; |
| 20349 |
| 20350 _injectStylesheet(); |
| 20351 |
| 20352 // Create content |
| 20353 if (template is! TemplateElement) { |
| 20354 var doc = _getTemplateContentsOwner(template.document); |
| 20355 template._templateContent = doc.createDocumentFragment(); |
| 20356 } |
| 20357 |
| 20358 if (instanceRef != null) { |
| 20359 template._templateInstanceRef = instanceRef; |
| 20360 return true; // content is empty. |
| 20361 } |
| 20362 |
| 20363 if (template is TemplateElement) { |
| 20364 _bootstrapTemplatesRecursivelyFrom(template.content); |
| 20365 } else { |
| 20366 _liftNonNativeTemplateChildrenIntoContent(template); |
| 20367 } |
| 20368 |
| 20369 return true; |
| 20370 } |
| 20371 |
| 20372 /** |
| 20373 * This used to decorate recursively all templates from a given node. |
| 20374 * |
| 20375 * By default [decorate] will be called on templates lazily when certain |
| 20376 * properties such as [model] are accessed, but it can be run eagerly to |
| 20377 * decorate an entire tree recursively. |
| 20378 */ |
| 20379 // TODO(rafaelw): Review whether this is the right public API. |
| 20380 @Experimental |
| 20381 static void bootstrap(Node content) { |
| 20382 _bootstrapTemplatesRecursivelyFrom(content); |
| 20383 } |
| 20384 |
| 20385 static bool _initStyles; |
| 20386 |
| 20387 static void _injectStylesheet() { |
| 20388 if (_initStyles == true) return; |
| 20389 _initStyles = true; |
| 20390 |
| 20391 var style = new StyleElement(); |
| 20392 style.text = r''' |
| 20393 template, |
| 20394 thead[template], |
| 20395 tbody[template], |
| 20396 tfoot[template], |
| 20397 th[template], |
| 20398 tr[template], |
| 20399 td[template], |
| 20400 caption[template], |
| 20401 colgroup[template], |
| 20402 col[template], |
| 20403 option[template] { |
| 20404 display: none; |
| 20405 }'''; |
| 20406 document.head.nodes.add(style); |
| 20407 } |
| 20408 |
| 20409 /** |
| 20410 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for |
| 20411 * more information. |
| 20412 */ |
| 20413 @Experimental |
| 20414 static Map<String, CustomBindingSyntax> syntax = {}; |
| 20068 } | 20415 } |
| 20069 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 20416 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 20070 // for details. All rights reserved. Use of this source code is governed by a | 20417 // for details. All rights reserved. Use of this source code is governed by a |
| 20071 // BSD-style license that can be found in the LICENSE file. | 20418 // BSD-style license that can be found in the LICENSE file. |
| 20072 | 20419 |
| 20073 // WARNING: Do not edit - generated code. | 20420 // WARNING: Do not edit - generated code. |
| 20074 | 20421 |
| 20075 | 20422 |
| 20076 @DomName('Text') | 20423 @DomName('Text') |
| 20077 class Text extends CharacterData native "Text" { | 20424 class Text extends CharacterData native "Text" { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 20090 final String wholeText; | 20437 final String wholeText; |
| 20091 | 20438 |
| 20092 @DomName('Text.replaceWholeText') | 20439 @DomName('Text.replaceWholeText') |
| 20093 @DocsEditable | 20440 @DocsEditable |
| 20094 Text replaceWholeText(String content) native; | 20441 Text replaceWholeText(String content) native; |
| 20095 | 20442 |
| 20096 @DomName('Text.splitText') | 20443 @DomName('Text.splitText') |
| 20097 @DocsEditable | 20444 @DocsEditable |
| 20098 Text splitText(int offset) native; | 20445 Text splitText(int offset) native; |
| 20099 | 20446 |
| 20447 |
| 20448 _Binding _textBinding; |
| 20449 |
| 20450 @Experimental |
| 20451 void bind(String name, model, String path) { |
| 20452 if (name != 'text') { |
| 20453 super.bind(name, model, path); |
| 20454 return; |
| 20455 } |
| 20456 |
| 20457 unbind('text'); |
| 20458 _textBinding = new _Binding(model, path, (value) { text = '$value'; }); |
| 20459 } |
| 20460 |
| 20461 @Experimental |
| 20462 void unbind(String name) { |
| 20463 if (name != 'text') { |
| 20464 super.unbind(name); |
| 20465 return; |
| 20466 } |
| 20467 |
| 20468 if (_textBinding == null) return; |
| 20469 |
| 20470 _textBinding.dispose(); |
| 20471 _textBinding = null; |
| 20472 } |
| 20473 |
| 20474 @Experimental |
| 20475 void unbindAll() { |
| 20476 unbind('text'); |
| 20477 super.unbindAll(); |
| 20478 } |
| 20100 } | 20479 } |
| 20101 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 20480 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 20102 // for details. All rights reserved. Use of this source code is governed by a | 20481 // for details. All rights reserved. Use of this source code is governed by a |
| 20103 // BSD-style license that can be found in the LICENSE file. | 20482 // BSD-style license that can be found in the LICENSE file. |
| 20104 | 20483 |
| 20105 | 20484 |
| 20106 @DocsEditable | 20485 @DocsEditable |
| 20107 @DomName('HTMLTextAreaElement') | 20486 @DomName('HTMLTextAreaElement') |
| 20108 class TextAreaElement extends Element native "HTMLTextAreaElement" { | 20487 class TextAreaElement extends Element native "HTMLTextAreaElement" { |
| 20109 | 20488 |
| (...skipping 7956 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 28066 * Key value used when an implementation is unable to identify another key | 28445 * Key value used when an implementation is unable to identify another key |
| 28067 * value, due to either hardware, platform, or software constraints | 28446 * value, due to either hardware, platform, or software constraints |
| 28068 */ | 28447 */ |
| 28069 static const String UNIDENTIFIED = "Unidentified"; | 28448 static const String UNIDENTIFIED = "Unidentified"; |
| 28070 } | 28449 } |
| 28071 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 28450 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 28072 // for details. All rights reserved. Use of this source code is governed by a | 28451 // for details. All rights reserved. Use of this source code is governed by a |
| 28073 // BSD-style license that can be found in the LICENSE file. | 28452 // BSD-style license that can be found in the LICENSE file. |
| 28074 | 28453 |
| 28075 | 28454 |
| 28076 class _ModelTreeObserver { | |
| 28077 static bool _initialized = false; | |
| 28078 | |
| 28079 /** | |
| 28080 * Start an observer watching the document for tree changes to automatically | |
| 28081 * propagate model changes. | |
| 28082 * | |
| 28083 * Currently this does not support propagation through Shadow DOMs. | |
| 28084 */ | |
| 28085 static void initialize() { | |
| 28086 if (!_initialized) { | |
| 28087 _initialized = true; | |
| 28088 | |
| 28089 if (MutationObserver.supported) { | |
| 28090 var observer = new MutationObserver(_processTreeChange); | |
| 28091 observer.observe(document, childList: true, subtree: true); | |
| 28092 } else { | |
| 28093 document.on['DOMNodeInserted'].listen(_handleNodeInserted); | |
| 28094 document.on['DOMNodeRemoved'].listen(_handleNodeRemoved); | |
| 28095 } | |
| 28096 } | |
| 28097 } | |
| 28098 | |
| 28099 static void _processTreeChange(List<MutationRecord> mutations, | |
| 28100 MutationObserver observer) { | |
| 28101 for (var record in mutations) { | |
| 28102 for (var node in record.addedNodes) { | |
| 28103 // When nodes enter the document we need to make sure that all of the | |
| 28104 // models are properly propagated through the entire sub-tree. | |
| 28105 propagateModel(node, _calculatedModel(node), true); | |
| 28106 } | |
| 28107 for (var node in record.removedNodes) { | |
| 28108 propagateModel(node, _calculatedModel(node), false); | |
| 28109 } | |
| 28110 } | |
| 28111 } | |
| 28112 | |
| 28113 static void _handleNodeInserted(MutationEvent e) { | |
| 28114 var node = e.target; | |
| 28115 window.setImmediate(() { | |
| 28116 propagateModel(node, _calculatedModel(node), true); | |
| 28117 }); | |
| 28118 } | |
| 28119 | |
| 28120 static void _handleNodeRemoved(MutationEvent e) { | |
| 28121 var node = e.target; | |
| 28122 window.setImmediate(() { | |
| 28123 propagateModel(node, _calculatedModel(node), false); | |
| 28124 }); | |
| 28125 } | |
| 28126 | |
| 28127 /** | |
| 28128 * Figures out what the model should be for a node, avoiding any cached | |
| 28129 * model values. | |
| 28130 */ | |
| 28131 static _calculatedModel(node) { | |
| 28132 if (node._hasLocalModel == true) { | |
| 28133 return node._model; | |
| 28134 } else if (node.parentNode != null) { | |
| 28135 return node.parentNode._model; | |
| 28136 } | |
| 28137 return null; | |
| 28138 } | |
| 28139 | |
| 28140 /** | |
| 28141 * Pushes model changes down through the tree. | |
| 28142 * | |
| 28143 * Set fullTree to true if the state of the tree is unknown and model changes | |
| 28144 * should be propagated through the entire tree. | |
| 28145 */ | |
| 28146 static void propagateModel(Node node, model, bool fullTree) { | |
| 28147 // Calling into user code with the != call could generate exceptions. | |
| 28148 // Catch and report them a global exceptions. | |
| 28149 try { | |
| 28150 if (node._hasLocalModel != true && node._model != model && | |
| 28151 node._modelChangedStreams != null && | |
| 28152 !node._modelChangedStreams.isEmpty) { | |
| 28153 node._model = model; | |
| 28154 node._modelChangedStreams.toList() | |
| 28155 .forEach((controller) => controller.add(node)); | |
| 28156 } | |
| 28157 } catch (e, s) { | |
| 28158 new Future.error(e, s); | |
| 28159 } | |
| 28160 for (var child = node.$dom_firstChild; child != null; | |
| 28161 child = child.nextNode) { | |
| 28162 if (child._hasLocalModel != true) { | |
| 28163 propagateModel(child, model, fullTree); | |
| 28164 } else if (fullTree) { | |
| 28165 propagateModel(child, child._model, true); | |
| 28166 } | |
| 28167 } | |
| 28168 } | |
| 28169 } | |
| 28170 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 28171 // for details. All rights reserved. Use of this source code is governed by a | |
| 28172 // BSD-style license that can be found in the LICENSE file. | |
| 28173 | |
| 28174 | |
| 28175 /** | 28455 /** |
| 28176 * A utility class for representing two-dimensional positions. | 28456 * A utility class for representing two-dimensional positions. |
| 28177 */ | 28457 */ |
| 28178 class Point { | 28458 class Point { |
| 28179 final num x; | 28459 final num x; |
| 28180 final num y; | 28460 final num y; |
| 28181 | 28461 |
| 28182 const Point([num x = 0, num y = 0]): x = x, y = y; | 28462 const Point([num x = 0, num y = 0]): x = x, y = y; |
| 28183 | 28463 |
| 28184 String toString() => '($x, $y)'; | 28464 String toString() => '($x, $y)'; |
| (...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 28382 * Truncates coordinates to integers and returns the result as a new | 28662 * Truncates coordinates to integers and returns the result as a new |
| 28383 * rectangle. | 28663 * rectangle. |
| 28384 */ | 28664 */ |
| 28385 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), | 28665 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), |
| 28386 height.toInt()); | 28666 height.toInt()); |
| 28387 | 28667 |
| 28388 Point get topLeft => new Point(this.left, this.top); | 28668 Point get topLeft => new Point(this.left, this.top); |
| 28389 Point get bottomRight => new Point(this.left + this.width, | 28669 Point get bottomRight => new Point(this.left + this.width, |
| 28390 this.top + this.height); | 28670 this.top + this.height); |
| 28391 } | 28671 } |
| 28672 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 28673 // for details. All rights reserved. Use of this source code is governed by a |
| 28674 // BSD-style license that can be found in the LICENSE file. |
| 28675 |
| 28676 |
| 28677 // This code is a port of Model-Driven-Views: |
| 28678 // https://github.com/toolkitchen/mdv |
| 28679 // The code mostly comes from src/template_element.js |
| 28680 |
| 28681 typedef void _ChangeHandler(value); |
| 28682 |
| 28683 /** |
| 28684 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases, |
| 28685 * but (by design) don't attempt to implement a wide array of specialized |
| 28686 * behaviors. |
| 28687 * |
| 28688 * Enabling these features in MDV is a matter of implementing and registering an |
| 28689 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more |
| 28690 * delegation functions which implement specialized behavior. This object is |
| 28691 * registered with MDV via [TemplateElement.syntax]: |
| 28692 * |
| 28693 * |
| 28694 * HTML: |
| 28695 * <template bind syntax="MySyntax"> |
| 28696 * {{ What!Ever('crazy')->thing^^^I+Want(data) }} |
| 28697 * </template> |
| 28698 * |
| 28699 * Dart: |
| 28700 * class MySyntax extends CustomBindingSyntax { |
| 28701 * getBinding(model, path, name, node) { |
| 28702 * // The magic happens here! |
| 28703 * } |
| 28704 * } |
| 28705 * |
| 28706 * ... |
| 28707 * |
| 28708 * TemplateElement.syntax['MySyntax'] = new MySyntax(); |
| 28709 * |
| 28710 * See <https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md> for more |
| 28711 * information about Custom Syntax. |
| 28712 */ |
| 28713 // TODO(jmesserly): if this is just one method, a function type would make it |
| 28714 // more Dart-friendly. |
| 28715 @Experimental |
| 28716 abstract class CustomBindingSyntax { |
| 28717 // TODO(jmesserly): I had to remove type annotations from "name" and "node" |
| 28718 // Normally they are String and Node respectively. But sometimes it will pass |
| 28719 // (int name, CompoundBinding node). That seems very confusing; we may want |
| 28720 // to change this API. |
| 28721 getBinding(model, String path, name, node); |
| 28722 } |
| 28723 |
| 28724 /** The callback used in the [CompoundBinding.combinator] field. */ |
| 28725 @Experimental |
| 28726 typedef Object CompoundBindingCombinator(Map objects); |
| 28727 |
| 28728 /** Information about the instantiated template. */ |
| 28729 @Experimental |
| 28730 class TemplateInstance { |
| 28731 // TODO(rafaelw): firstNode & lastNode should be read-synchronous |
| 28732 // in cases where script has modified the template instance boundary. |
| 28733 |
| 28734 /** The first node of this template instantiation. */ |
| 28735 final Node firstNode; |
| 28736 |
| 28737 /** |
| 28738 * The last node of this template instantiation. |
| 28739 * This could be identical to [firstNode] if the template only expanded to a |
| 28740 * single node. |
| 28741 */ |
| 28742 final Node lastNode; |
| 28743 |
| 28744 /** The model used to instantiate the template. */ |
| 28745 final model; |
| 28746 |
| 28747 TemplateInstance(this.firstNode, this.lastNode, this.model); |
| 28748 } |
| 28749 |
| 28750 /** |
| 28751 * Model-Driven Views contains a helper object which is useful for the |
| 28752 * implementation of a Custom Syntax. |
| 28753 * |
| 28754 * var binding = new CompoundBinding((values) { |
| 28755 * var combinedValue; |
| 28756 * // compute combinedValue based on the current values which are provided |
| 28757 * return combinedValue; |
| 28758 * }); |
| 28759 * binding.bind('name1', obj1, path1); |
| 28760 * binding.bind('name2', obj2, path2); |
| 28761 * //... |
| 28762 * binding.bind('nameN', objN, pathN); |
| 28763 * |
| 28764 * CompoundBinding is an object which knows how to listen to multiple path |
| 28765 * values (registered via [bind]) and invoke its [combinator] when one or more |
| 28766 * of the values have changed and set its [value] property to the return value |
| 28767 * of the function. When any value has changed, all current values are provided |
| 28768 * to the [combinator] in the single `values` argument. |
| 28769 * |
| 28770 * See [CustomBindingSyntax] for more information. |
| 28771 */ |
| 28772 // TODO(jmesserly): what is the public API surface here? I just guessed; |
| 28773 // most of it seemed non-public. |
| 28774 // TODO(jmesserly): should this move into dart:observe? |
| 28775 @Experimental |
| 28776 class CompoundBinding extends ObservableMixin { |
| 28777 CompoundBindingCombinator _combinator; |
| 28778 |
| 28779 // TODO(jmesserly): ideally these would be String keys, but sometimes we |
| 28780 // use integers. |
| 28781 Map<dynamic, _Binding> _bindings = {}; |
| 28782 Map _values = {}; |
| 28783 bool _scheduled = false; |
| 28784 bool _disposed = false; |
| 28785 Object _value; |
| 28786 |
| 28787 CompoundBinding([CompoundBindingCombinator combinator]) { |
| 28788 // TODO(jmesserly): this is a tweak to the original code, it seemed to me |
| 28789 // that passing the combinator to the constructor should be equivalent to |
| 28790 // setting it via the property. |
| 28791 // I also added a null check to the combinator setter. |
| 28792 this.combinator = combinator; |
| 28793 } |
| 28794 |
| 28795 CompoundBindingCombinator get combinator => _combinator; |
| 28796 |
| 28797 set combinator(CompoundBindingCombinator combinator) { |
| 28798 _combinator = combinator; |
| 28799 if (combinator != null) _scheduleResolve(); |
| 28800 } |
| 28801 |
| 28802 static const _VALUE = 'value'; |
| 28803 |
| 28804 get value => _value; |
| 28805 |
| 28806 void set value(newValue) { |
| 28807 _value = notifyChange('value', _value, newValue); |
| 28808 } |
| 28809 |
| 28810 // TODO(jmesserly): remove getValue/setValue when dart2js supports mirrors! |
| 28811 getValue(key) { |
| 28812 if (key == _VALUE) return value; |
| 28813 return null; |
| 28814 } |
| 28815 setValue(key, val) { |
| 28816 if (key == _VALUE) value = val; |
| 28817 } |
| 28818 |
| 28819 void bind(name, model, String path) { |
| 28820 unbind(name); |
| 28821 |
| 28822 _bindings[name] = new _Binding(model, path, (value) { |
| 28823 _values[name] = value; |
| 28824 _scheduleResolve(); |
| 28825 }); |
| 28826 } |
| 28827 |
| 28828 void unbind(name, {bool suppressResolve: false}) { |
| 28829 var binding = _bindings.remove(name); |
| 28830 if (binding == null) return; |
| 28831 |
| 28832 binding.dispose(); |
| 28833 _values.remove(name); |
| 28834 if (!suppressResolve) _scheduleResolve(); |
| 28835 } |
| 28836 |
| 28837 // TODO(rafaelw): Is this the right processing model? |
| 28838 // TODO(rafaelw): Consider having a seperate ChangeSummary for |
| 28839 // CompoundBindings so to excess dirtyChecks. |
| 28840 void _scheduleResolve() { |
| 28841 if (_scheduled) return; |
| 28842 _scheduled = true; |
| 28843 queueChangeRecords(resolve); |
| 28844 } |
| 28845 |
| 28846 void resolve() { |
| 28847 if (_disposed) return; |
| 28848 _scheduled = false; |
| 28849 |
| 28850 if (_combinator == null) { |
| 28851 throw new StateError( |
| 28852 'CompoundBinding attempted to resolve without a combinator'); |
| 28853 } |
| 28854 |
| 28855 value = _combinator(_values); |
| 28856 } |
| 28857 |
| 28858 void dispose() { |
| 28859 for (var binding in _bindings.values) { |
| 28860 binding.dispose(); |
| 28861 } |
| 28862 _bindings.clear(); |
| 28863 _values.clear(); |
| 28864 |
| 28865 _disposed = true; |
| 28866 value = null; |
| 28867 } |
| 28868 } |
| 28869 |
| 28870 // TODO(jmesserly): we can probably inline this object as it doesn't do much. |
| 28871 class _Binding { |
| 28872 final _ChangeHandler changed; |
| 28873 final PathObserver _path; |
| 28874 StreamSubscription _sub; |
| 28875 |
| 28876 _Binding(model, String path, _ChangeHandler changed) |
| 28877 : _path = observePath(model, path) { |
| 28878 if (_path == null) { |
| 28879 // TODO(jmesserly): Should we display an error message about invalid path? |
| 28880 changed(null); |
| 28881 return; |
| 28882 } |
| 28883 _sub = _path.values.listen(changed); |
| 28884 changed(_path.value); |
| 28885 } |
| 28886 |
| 28887 void dispose() { |
| 28888 if (_sub != null) _sub.cancel(); |
| 28889 } |
| 28890 |
| 28891 void set value(newValue) { |
| 28892 if (_path != null) _path.value = newValue; |
| 28893 } |
| 28894 } |
| 28895 |
| 28896 Stream<Event> _getStreamForInputType(InputElement element) { |
| 28897 switch (element.type) { |
| 28898 case 'checkbox': |
| 28899 return element.onClick; |
| 28900 case 'radio': |
| 28901 case 'select-multiple': |
| 28902 case 'select-one': |
| 28903 return element.onChange; |
| 28904 default: |
| 28905 return element.onInput; |
| 28906 } |
| 28907 } |
| 28908 |
| 28909 abstract class _InputBinding { |
| 28910 final InputElement element; |
| 28911 _Binding binding; |
| 28912 StreamSubscription _sub; |
| 28913 |
| 28914 _InputBinding(this.element, model, String path) { |
| 28915 binding = new _Binding(model, path, valueChanged); |
| 28916 _sub = _getStreamForInputType(element).listen(updateBinding); |
| 28917 } |
| 28918 |
| 28919 void valueChanged(newValue); |
| 28920 |
| 28921 void updateBinding(e); |
| 28922 |
| 28923 void unbind() { |
| 28924 binding.dispose(); |
| 28925 _sub.cancel(); |
| 28926 } |
| 28927 } |
| 28928 |
| 28929 class _ValueBinding extends _InputBinding { |
| 28930 _ValueBinding(element, model, path) : super(element, model, path); |
| 28931 |
| 28932 void valueChanged(value) { |
| 28933 element.value = value == null ? '' : '$value'; |
| 28934 } |
| 28935 |
| 28936 void updateBinding(e) { |
| 28937 binding.value = element.value; |
| 28938 } |
| 28939 } |
| 28940 |
| 28941 // TODO(jmesserly): not sure what kind of boolean conversion rules to |
| 28942 // apply for template data-binding. HTML attributes are true if they're present. |
| 28943 // However Dart only treats "true" as true. Since this is HTML we'll use |
| 28944 // something closer to the HTML rules: null (missing) and false are false, |
| 28945 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59 |
| 28946 bool _templateBooleanConversion(value) => null != value && false != value; |
| 28947 |
| 28948 class _CheckedBinding extends _InputBinding { |
| 28949 _CheckedBinding(element, model, path) : super(element, model, path); |
| 28950 |
| 28951 void valueChanged(value) { |
| 28952 element.checked = _templateBooleanConversion(value); |
| 28953 } |
| 28954 |
| 28955 void updateBinding(e) { |
| 28956 binding.value = element.checked; |
| 28957 |
| 28958 // Only the radio button that is getting checked gets an event. We |
| 28959 // therefore find all the associated radio buttons and update their |
| 28960 // CheckedBinding manually. |
| 28961 if (element is InputElement && element.type == 'radio') { |
| 28962 for (var r in _getAssociatedRadioButtons(element)) { |
| 28963 var checkedBinding = r._checkedBinding; |
| 28964 if (checkedBinding != null) { |
| 28965 // Set the value directly to avoid an infinite call stack. |
| 28966 checkedBinding.binding.value = false; |
| 28967 } |
| 28968 } |
| 28969 } |
| 28970 } |
| 28971 } |
| 28972 |
| 28973 // TODO(jmesserly): do we need to polyfill document.contains for IE? |
| 28974 bool _isNodeInDocument(Node node) => node.document.contains(node); |
| 28975 |
| 28976 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. |
| 28977 // Returns an array containing all radio buttons other than |element| that |
| 28978 // have the same |name|, either in the form that |element| belongs to or, |
| 28979 // if no form, in the document tree to which |element| belongs. |
| 28980 // |
| 28981 // This implementation is based upon the HTML spec definition of a |
| 28982 // "radio button group": |
| 28983 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht
ml#radio-button-group |
| 28984 // |
| 28985 Iterable<Element> _getAssociatedRadioButtons(element) { |
| 28986 if (!_isNodeInDocument(element)) return []; |
| 28987 if (element.form != null) { |
| 28988 return element.form.nodes.where((el) { |
| 28989 return el != element && |
| 28990 el is InputElement && |
| 28991 el.type == 'radio' && |
| 28992 el.name == element.name; |
| 28993 }); |
| 28994 } else { |
| 28995 var radios = element.document.queryAll( |
| 28996 'input[type="radio"][name="${element.name}"]'); |
| 28997 return radios.where((el) => el != element && el.form == null); |
| 28998 } |
| 28999 } |
| 29000 |
| 29001 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { |
| 29002 var clone = node.clone(false); // Shallow clone. |
| 29003 if (clone is Element && clone.isTemplate) { |
| 29004 TemplateElement.decorate(clone, node); |
| 29005 if (syntax != null) { |
| 29006 clone.attributes.putIfAbsent('syntax', () => syntax); |
| 29007 } |
| 29008 } |
| 29009 |
| 29010 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { |
| 29011 clone.nodes.add(_createDeepCloneAndDecorateTemplates(c, syntax)); |
| 29012 } |
| 29013 return clone; |
| 29014 } |
| 29015 |
| 29016 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df
n-template-contents-owner |
| 29017 Document _getTemplateContentsOwner(Document doc) { |
| 29018 if (doc.window == null) { |
| 29019 return doc; |
| 29020 } |
| 29021 var d = doc._templateContentsOwner; |
| 29022 if (d == null) { |
| 29023 // TODO(arv): This should either be a Document or HTMLDocument depending |
| 29024 // on doc. |
| 29025 d = doc.implementation.createHtmlDocument(''); |
| 29026 while (d.$dom_lastChild != null) { |
| 29027 d.$dom_lastChild.remove(); |
| 29028 } |
| 29029 doc._templateContentsOwner = d; |
| 29030 } |
| 29031 return d; |
| 29032 } |
| 29033 |
| 29034 Element _cloneAndSeperateAttributeTemplate(Element templateElement) { |
| 29035 var clone = templateElement.clone(false); |
| 29036 var attributes = templateElement.attributes; |
| 29037 for (var name in attributes.keys.toList()) { |
| 29038 switch (name) { |
| 29039 case 'template': |
| 29040 case 'repeat': |
| 29041 case 'bind': |
| 29042 case 'ref': |
| 29043 clone.attributes.remove(name); |
| 29044 break; |
| 29045 default: |
| 29046 attributes.remove(name); |
| 29047 break; |
| 29048 } |
| 29049 } |
| 29050 |
| 29051 return clone; |
| 29052 } |
| 29053 |
| 29054 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) { |
| 29055 var content = templateElement.content; |
| 29056 |
| 29057 if (!templateElement._isAttributeTemplate) { |
| 29058 var child; |
| 29059 while ((child = templateElement.$dom_firstChild) != null) { |
| 29060 content.nodes.add(child); |
| 29061 } |
| 29062 return; |
| 29063 } |
| 29064 |
| 29065 // For attribute templates we copy the whole thing into the content and |
| 29066 // we move the non template attributes into the content. |
| 29067 // |
| 29068 // <tr foo template> |
| 29069 // |
| 29070 // becomes |
| 29071 // |
| 29072 // <tr template> |
| 29073 // + #document-fragment |
| 29074 // + <tr foo> |
| 29075 // |
| 29076 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement); |
| 29077 var child; |
| 29078 while ((child = templateElement.$dom_firstChild) != null) { |
| 29079 newRoot.nodes.add(child); |
| 29080 } |
| 29081 content.nodes.add(newRoot); |
| 29082 } |
| 29083 |
| 29084 void _bootstrapTemplatesRecursivelyFrom(Node node) { |
| 29085 void bootstrap(template) { |
| 29086 if (!TemplateElement.decorate(template)) { |
| 29087 _bootstrapTemplatesRecursivelyFrom(template.content); |
| 29088 } |
| 29089 } |
| 29090 |
| 29091 // Need to do this first as the contents may get lifted if |node| is |
| 29092 // template. |
| 29093 // TODO(jmesserly): node is DocumentFragment or Element |
| 29094 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors); |
| 29095 if (node is Element && node.isTemplate) bootstrap(node); |
| 29096 |
| 29097 templateDescendents.forEach(bootstrap); |
| 29098 } |
| 29099 |
| 29100 final String _allTemplatesSelectors = 'template, option[template], ' + |
| 29101 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", "); |
| 29102 |
| 29103 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) { |
| 29104 if (node is Element) { |
| 29105 _addAttributeBindings(node, model, syntax); |
| 29106 } else if (node is Text) { |
| 29107 _parseAndBind(node, node.text, 'text', model, syntax); |
| 29108 } |
| 29109 |
| 29110 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { |
| 29111 _addBindings(c, model, syntax); |
| 29112 } |
| 29113 } |
| 29114 |
| 29115 |
| 29116 void _addAttributeBindings(Element element, model, syntax) { |
| 29117 element.attributes.forEach((name, value) { |
| 29118 if (value == '' && (name == 'bind' || name == 'repeat')) { |
| 29119 value = '{{}}'; |
| 29120 } |
| 29121 _parseAndBind(element, value, name, model, syntax); |
| 29122 }); |
| 29123 } |
| 29124 |
| 29125 void _parseAndBind(Node node, String text, String name, model, |
| 29126 CustomBindingSyntax syntax) { |
| 29127 |
| 29128 var tokens = _parseMustacheTokens(text); |
| 29129 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { |
| 29130 return; |
| 29131 } |
| 29132 |
| 29133 if (tokens.length == 1 && tokens[0].isBinding) { |
| 29134 _bindOrDelegate(node, name, model, tokens[0].value, syntax); |
| 29135 return; |
| 29136 } |
| 29137 |
| 29138 var replacementBinding = new CompoundBinding(); |
| 29139 for (var i = 0; i < tokens.length; i++) { |
| 29140 var token = tokens[i]; |
| 29141 if (token.isBinding) { |
| 29142 _bindOrDelegate(replacementBinding, i, model, token.value, syntax); |
| 29143 } |
| 29144 } |
| 29145 |
| 29146 replacementBinding.combinator = (values) { |
| 29147 var newValue = new StringBuffer(); |
| 29148 |
| 29149 for (var i = 0; i < tokens.length; i++) { |
| 29150 var token = tokens[i]; |
| 29151 if (token.isText) { |
| 29152 newValue.write(token.value); |
| 29153 } else { |
| 29154 var value = values[i]; |
| 29155 if (value != null) { |
| 29156 newValue.write(value); |
| 29157 } |
| 29158 } |
| 29159 } |
| 29160 |
| 29161 return newValue.toString(); |
| 29162 }; |
| 29163 |
| 29164 node.bind(name, replacementBinding, 'value'); |
| 29165 } |
| 29166 |
| 29167 void _bindOrDelegate(node, name, model, String path, |
| 29168 CustomBindingSyntax syntax) { |
| 29169 |
| 29170 if (syntax != null) { |
| 29171 var delegateBinding = syntax.getBinding(model, path, name, node); |
| 29172 if (delegateBinding != null) { |
| 29173 model = delegateBinding; |
| 29174 path = 'value'; |
| 29175 } |
| 29176 } |
| 29177 |
| 29178 node.bind(name, model, path); |
| 29179 } |
| 29180 |
| 29181 class _BindingToken { |
| 29182 final String value; |
| 29183 final bool isBinding; |
| 29184 |
| 29185 _BindingToken(this.value, {this.isBinding: false}); |
| 29186 |
| 29187 bool get isText => !isBinding; |
| 29188 } |
| 29189 |
| 29190 List<_BindingToken> _parseMustacheTokens(String s) { |
| 29191 var result = []; |
| 29192 var length = s.length; |
| 29193 var index = 0, lastIndex = 0; |
| 29194 while (lastIndex < length) { |
| 29195 index = s.indexOf('{{', lastIndex); |
| 29196 if (index < 0) { |
| 29197 result.add(new _BindingToken(s.substring(lastIndex))); |
| 29198 break; |
| 29199 } else { |
| 29200 // There is a non-empty text run before the next path token. |
| 29201 if (index > 0 && lastIndex < index) { |
| 29202 result.add(new _BindingToken(s.substring(lastIndex, index))); |
| 29203 } |
| 29204 lastIndex = index + 2; |
| 29205 index = s.indexOf('}}', lastIndex); |
| 29206 if (index < 0) { |
| 29207 var text = s.substring(lastIndex - 2); |
| 29208 if (result.length > 0 && result.last.isText) { |
| 29209 result.last.value += text; |
| 29210 } else { |
| 29211 result.add(new _BindingToken(text)); |
| 29212 } |
| 29213 break; |
| 29214 } |
| 29215 |
| 29216 var value = s.substring(lastIndex, index).trim(); |
| 29217 result.add(new _BindingToken(value, isBinding: true)); |
| 29218 lastIndex = index + 2; |
| 29219 } |
| 29220 } |
| 29221 return result; |
| 29222 } |
| 29223 |
| 29224 void _addTemplateInstanceRecord(fragment, model) { |
| 29225 if (fragment.$dom_firstChild == null) { |
| 29226 return; |
| 29227 } |
| 29228 |
| 29229 var instanceRecord = new TemplateInstance( |
| 29230 fragment.$dom_firstChild, fragment.$dom_lastChild, model); |
| 29231 |
| 29232 var node = instanceRecord.firstNode; |
| 29233 while (node != null) { |
| 29234 node._templateInstance = instanceRecord; |
| 29235 node = node.nextNode; |
| 29236 } |
| 29237 } |
| 29238 |
| 29239 void _removeAllBindingsRecursively(Node node) { |
| 29240 node.unbindAll(); |
| 29241 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { |
| 29242 _removeAllBindingsRecursively(c); |
| 29243 } |
| 29244 } |
| 29245 |
| 29246 void _removeTemplateChild(Node parent, Node child) { |
| 29247 child._templateInstance = null; |
| 29248 if (child is Element && child.isTemplate) { |
| 29249 // Make sure we stop observing when we remove an element. |
| 29250 var templateIterator = child._templateIterator; |
| 29251 if (templateIterator != null) { |
| 29252 templateIterator.abandon(); |
| 29253 child._templateIterator = null; |
| 29254 } |
| 29255 } |
| 29256 child.remove(); |
| 29257 _removeAllBindingsRecursively(child); |
| 29258 } |
| 29259 |
| 29260 class _InstanceCursor { |
| 29261 final Element _template; |
| 29262 Node _terminator; |
| 29263 Node _previousTerminator; |
| 29264 int _previousIndex = -1; |
| 29265 int _index = 0; |
| 29266 |
| 29267 _InstanceCursor(this._template, [index]) { |
| 29268 _terminator = _template; |
| 29269 if (index != null) { |
| 29270 while (index-- > 0) { |
| 29271 next(); |
| 29272 } |
| 29273 } |
| 29274 } |
| 29275 |
| 29276 void next() { |
| 29277 _previousTerminator = _terminator; |
| 29278 _previousIndex = _index; |
| 29279 _index++; |
| 29280 |
| 29281 while (_index > _terminator._instanceTerminatorCount) { |
| 29282 _index -= _terminator._instanceTerminatorCount; |
| 29283 _terminator = _terminator.nextNode; |
| 29284 if (_terminator is Element && _terminator.tagName == 'TEMPLATE') { |
| 29285 _index += _instanceCount(_terminator); |
| 29286 } |
| 29287 } |
| 29288 } |
| 29289 |
| 29290 void abandon() { |
| 29291 assert(_instanceCount(_template) > 0); |
| 29292 assert(_terminator._instanceTerminatorCount > 0); |
| 29293 assert(_index > 0); |
| 29294 |
| 29295 _terminator._instanceTerminatorCount--; |
| 29296 _index--; |
| 29297 } |
| 29298 |
| 29299 void insert(fragment) { |
| 29300 assert(_template.parentNode != null); |
| 29301 |
| 29302 _previousTerminator = _terminator; |
| 29303 _previousIndex = _index; |
| 29304 _index++; |
| 29305 |
| 29306 _terminator = fragment.$dom_lastChild; |
| 29307 if (_terminator == null) _terminator = _previousTerminator; |
| 29308 _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode); |
| 29309 |
| 29310 _terminator._instanceTerminatorCount++; |
| 29311 if (_terminator != _previousTerminator) { |
| 29312 while (_previousTerminator._instanceTerminatorCount > |
| 29313 _previousIndex) { |
| 29314 _previousTerminator._instanceTerminatorCount--; |
| 29315 _terminator._instanceTerminatorCount++; |
| 29316 } |
| 29317 } |
| 29318 } |
| 29319 |
| 29320 void remove() { |
| 29321 assert(_previousIndex != -1); |
| 29322 assert(_previousTerminator != null && |
| 29323 (_previousIndex > 0 || _previousTerminator == _template)); |
| 29324 assert(_terminator != null && _index > 0); |
| 29325 assert(_template.parentNode != null); |
| 29326 assert(_instanceCount(_template) > 0); |
| 29327 |
| 29328 if (_previousTerminator == _terminator) { |
| 29329 assert(_index == _previousIndex + 1); |
| 29330 _terminator._instanceTerminatorCount--; |
| 29331 _terminator = _template; |
| 29332 _previousTerminator = null; |
| 29333 _previousIndex = -1; |
| 29334 return; |
| 29335 } |
| 29336 |
| 29337 _terminator._instanceTerminatorCount--; |
| 29338 |
| 29339 var parent = _template.parentNode; |
| 29340 while (_previousTerminator.nextNode != _terminator) { |
| 29341 _removeTemplateChild(parent, _previousTerminator.nextNode); |
| 29342 } |
| 29343 _removeTemplateChild(parent, _terminator); |
| 29344 |
| 29345 _terminator = _previousTerminator; |
| 29346 _index = _previousIndex; |
| 29347 _previousTerminator = null; |
| 29348 _previousIndex = -1; // 0? |
| 29349 } |
| 29350 } |
| 29351 |
| 29352 |
| 29353 class _TemplateIterator { |
| 29354 final Element _templateElement; |
| 29355 int instanceCount = 0; |
| 29356 List iteratedValue; |
| 29357 bool observing = false; |
| 29358 CompoundBinding inputs; |
| 29359 _Binding valueBinding; |
| 29360 StreamSubscription _sub; |
| 29361 |
| 29362 _TemplateIterator(this._templateElement) { |
| 29363 inputs = new CompoundBinding(resolveInputs); |
| 29364 valueBinding = new _Binding(inputs, 'value', valueChanged); |
| 29365 } |
| 29366 |
| 29367 Object resolveInputs(Map values) { |
| 29368 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) { |
| 29369 return null; |
| 29370 } |
| 29371 |
| 29372 if (values.containsKey('repeat')) { |
| 29373 return values['repeat']; |
| 29374 } |
| 29375 |
| 29376 if (values.containsKey('bind')) { |
| 29377 return [values['bind']]; |
| 29378 } |
| 29379 |
| 29380 return null; |
| 29381 } |
| 29382 |
| 29383 void valueChanged(value) { |
| 29384 clear(); |
| 29385 if (value is! List) return; |
| 29386 |
| 29387 iteratedValue = value; |
| 29388 |
| 29389 if (value is Observable) { |
| 29390 _sub = value.changes.listen(handleChange); |
| 29391 } |
| 29392 |
| 29393 handleSplices([new ListChangeDelta(0, addedCount: iteratedValue.length)]); |
| 29394 } |
| 29395 |
| 29396 // TODO(jmesserly): these properties appear not be finished. I think it's |
| 29397 // part of custom syntax like repeat="i in items". |
| 29398 getInstanceModel(model, syntax) => model; |
| 29399 getInstanceFragment(syntax) => _templateElement.createInstance(); |
| 29400 |
| 29401 void handleChange(List<ChangeRecord> records) { |
| 29402 print('!!! got records $records'); |
| 29403 |
| 29404 handleSplices(summarizeListChanges(iteratedValue, records)); |
| 29405 } |
| 29406 |
| 29407 void handleSplices(List<ListChangeDelta> splices) { |
| 29408 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']]; |
| 29409 |
| 29410 print('!!! got splices $splices'); |
| 29411 |
| 29412 for (var splice in splices) { |
| 29413 for (var removed in splice.removed) { |
| 29414 var cursor = new _InstanceCursor(_templateElement, splice.index + 1); |
| 29415 cursor.remove(); |
| 29416 instanceCount--; |
| 29417 } |
| 29418 |
| 29419 for (var addIndex = splice.index; |
| 29420 addIndex < splice.index + splice.addedCount; |
| 29421 addIndex++) { |
| 29422 |
| 29423 var model = getInstanceModel(iteratedValue[addIndex], syntax); |
| 29424 var fragment = getInstanceFragment(syntax); |
| 29425 |
| 29426 _addBindings(fragment, model, syntax); |
| 29427 _addTemplateInstanceRecord(fragment, model); |
| 29428 |
| 29429 var cursor = new _InstanceCursor(_templateElement, addIndex); |
| 29430 cursor.insert(fragment); |
| 29431 instanceCount++; |
| 29432 } |
| 29433 } |
| 29434 } |
| 29435 |
| 29436 void unobserve() { |
| 29437 if (_sub == null) return; |
| 29438 _sub.cancel(); |
| 29439 _sub = null; |
| 29440 } |
| 29441 |
| 29442 void clear() { |
| 29443 unobserve(); |
| 29444 |
| 29445 iteratedValue = null; |
| 29446 if (instanceCount == 0) return; |
| 29447 |
| 29448 for (var i = 0; i < instanceCount; i++) { |
| 29449 var cursor = new _InstanceCursor(_templateElement, 1); |
| 29450 cursor.remove(); |
| 29451 } |
| 29452 |
| 29453 instanceCount = 0; |
| 29454 } |
| 29455 |
| 29456 void abandon() { |
| 29457 unobserve(); |
| 29458 valueBinding.dispose(); |
| 29459 inputs.dispose(); |
| 29460 } |
| 29461 } |
| 29462 |
| 29463 int _instanceCount(Element element) { |
| 29464 var templateIterator = element._templateIterator; |
| 29465 return templateIterator != null ? templateIterator.instanceCount : 0; |
| 29466 } |
| 28392 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 29467 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 28393 // for details. All rights reserved. Use of this source code is governed by a | 29468 // for details. All rights reserved. Use of this source code is governed by a |
| 28394 // BSD-style license that can be found in the LICENSE file. | 29469 // BSD-style license that can be found in the LICENSE file. |
| 28395 | 29470 |
| 28396 | 29471 |
| 28397 class _HttpRequestUtils { | 29472 class _HttpRequestUtils { |
| 28398 | 29473 |
| 28399 // Helper for factory HttpRequest.get | 29474 // Helper for factory HttpRequest.get |
| 28400 static HttpRequest get(String url, | 29475 static HttpRequest get(String url, |
| 28401 onComplete(HttpRequest request), | 29476 onComplete(HttpRequest request), |
| (...skipping 763 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 29165 DateTime _convertNativeToDart_DateTime(date) { | 30240 DateTime _convertNativeToDart_DateTime(date) { |
| 29166 var millisSinceEpoch = JS('int', '#.getTime()', date); | 30241 var millisSinceEpoch = JS('int', '#.getTime()', date); |
| 29167 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); | 30242 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); |
| 29168 } | 30243 } |
| 29169 | 30244 |
| 29170 _convertDartToNative_DateTime(DateTime date) { | 30245 _convertDartToNative_DateTime(DateTime date) { |
| 29171 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); | 30246 return JS('', 'new Date(#)', date.millisecondsSinceEpoch); |
| 29172 } | 30247 } |
| 29173 | 30248 |
| 29174 WindowBase _convertNativeToDart_Window(win) { | 30249 WindowBase _convertNativeToDart_Window(win) { |
| 30250 if (win == null) return null; |
| 29175 return _DOMWindowCrossFrame._createSafe(win); | 30251 return _DOMWindowCrossFrame._createSafe(win); |
| 29176 } | 30252 } |
| 29177 | 30253 |
| 29178 EventTarget _convertNativeToDart_EventTarget(e) { | 30254 EventTarget _convertNativeToDart_EventTarget(e) { |
| 29179 if (e == null) { | 30255 if (e == null) { |
| 29180 return null; | 30256 return null; |
| 29181 } | 30257 } |
| 29182 // Assume it's a Window if it contains the setInterval property. It may be | 30258 // Assume it's a Window if it contains the setInterval property. It may be |
| 29183 // from a different frame - without a patched prototype - so we cannot | 30259 // from a different frame - without a patched prototype - so we cannot |
| 29184 // rely on Dart type checking. | 30260 // rely on Dart type checking. |
| (...skipping 407 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 29592 _position = nextPosition; | 30668 _position = nextPosition; |
| 29593 return true; | 30669 return true; |
| 29594 } | 30670 } |
| 29595 _current = null; | 30671 _current = null; |
| 29596 _position = _array.length; | 30672 _position = _array.length; |
| 29597 return false; | 30673 return false; |
| 29598 } | 30674 } |
| 29599 | 30675 |
| 29600 T get current => _current; | 30676 T get current => _current; |
| 29601 } | 30677 } |
| OLD | NEW |