| OLD | NEW |
| 1 /// The Dart HTML library. | 1 /// The Dart HTML library. |
| 2 /// | 2 /// |
| 3 /// For examples, see | 3 /// For examples, see |
| 4 /// [Dart HTML5 Samples](https://github.com/dart-lang/dart-html5-samples) | 4 /// [Dart HTML5 Samples](https://github.com/dart-lang/dart-html5-samples) |
| 5 /// on Github. | 5 /// on Github. |
| 6 library dart.dom.html; | 6 library dart.dom.html; |
| 7 | 7 |
| 8 import 'dart:async'; | 8 import 'dart:async'; |
| 9 import 'dart:collection'; | 9 import 'dart:collection'; |
| 10 import 'dart:_collection-dev' hide Symbol; | 10 import 'dart:_collection-dev' hide Symbol; |
| 11 import 'dart:html_common'; | 11 import 'dart:html_common'; |
| 12 import 'dart:indexed_db'; | 12 import 'dart:indexed_db'; |
| 13 import 'dart:isolate'; | 13 import 'dart:isolate'; |
| 14 import 'dart:json' as json; | 14 import 'dart:json' as json; |
| 15 import 'dart:math'; | 15 import 'dart:math'; |
| 16 import 'dart:mdv_observe_impl'; | |
| 17 import 'dart:typed_data'; | 16 import 'dart:typed_data'; |
| 18 import 'dart:svg' as svg; | 17 import 'dart:svg' as svg; |
| 19 import 'dart:web_audio' as web_audio; | 18 import 'dart:web_audio' as web_audio; |
| 20 import 'dart:web_gl' as gl; | 19 import 'dart:web_gl' as gl; |
| 21 import 'dart:web_sql'; | 20 import 'dart:web_sql'; |
| 22 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin
gBehavior, JSName, Null, Returns; | 21 import 'dart:_js_helper' show convertDartClosureToJS, Creates, JavaScriptIndexin
gBehavior, JSName, Null, Returns; |
| 23 import 'dart:_interceptors' show Interceptor, JSExtendableArray; | 22 import 'dart:_interceptors' show Interceptor, JSExtendableArray; |
| 24 import 'dart:_isolate_helper' show IsolateNatives; | 23 import 'dart:_isolate_helper' show IsolateNatives; |
| 25 import 'dart:_foreign_helper' show JS; | 24 import 'dart:_foreign_helper' show JS; |
| 26 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 25 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| (...skipping 8246 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 8273 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { | 8272 } else if (JS('bool', '!!#.webkitMatchesSelector', this)) { |
| 8274 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); | 8273 return JS('bool', '#.webkitMatchesSelector(#)', this, selectors); |
| 8275 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { | 8274 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { |
| 8276 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); | 8275 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); |
| 8277 } else if (JS('bool', '!!#.msMatchesSelector', this)) { | 8276 } else if (JS('bool', '!!#.msMatchesSelector', this)) { |
| 8278 return JS('bool', '#.msMatchesSelector(#)', this, selectors); | 8277 return JS('bool', '#.msMatchesSelector(#)', this, selectors); |
| 8279 } | 8278 } |
| 8280 throw new UnsupportedError("Not supported on this platform"); | 8279 throw new UnsupportedError("Not supported on this platform"); |
| 8281 } | 8280 } |
| 8282 | 8281 |
| 8283 @Creates('Null') // Set from Dart code; does not instantiate a native type. | |
| 8284 Map<String, StreamSubscription> _attributeBindings; | |
| 8285 | |
| 8286 // TODO(jmesserly): I'm concerned about adding these to every element. | |
| 8287 // Conceptually all of these belong on TemplateElement. They are here to | |
| 8288 // support browsers that don't have <template> yet. | |
| 8289 // However even in the polyfill they're restricted to certain tags | |
| 8290 // (see [isTemplate]). So we can probably convert it to a (public) mixin, and | |
| 8291 // only mix it in to the elements that need it. | |
| 8292 @Creates('Null') // Set from Dart code; does not instantiate a native type. | |
| 8293 var _model; | |
| 8294 | |
| 8295 @Creates('Null') // Set from Dart code; does not instantiate a native type. | |
| 8296 _TemplateIterator _templateIterator; | |
| 8297 | |
| 8298 @Creates('Null') // Set from Dart code; does not instantiate a native type. | 8282 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 8299 Element _templateInstanceRef; | 8283 Element _templateInstanceRef; |
| 8300 | 8284 |
| 8301 // Note: only used if `this is! TemplateElement` | 8285 // Note: only used if `this is! TemplateElement` |
| 8302 @Creates('Null') // Set from Dart code; does not instantiate a native type. | 8286 @Creates('Null') // Set from Dart code; does not instantiate a native type. |
| 8303 DocumentFragment _templateContent; | 8287 DocumentFragment _templateContent; |
| 8304 | 8288 |
| 8305 bool _templateIsDecorated; | 8289 bool _templateIsDecorated; |
| 8306 | 8290 |
| 8307 // TODO(jmesserly): should path be optional, and default to empty path? | |
| 8308 // It is used that way in at least one path in JS TemplateElement tests | |
| 8309 // (see "BindImperative" test in original JS code). | |
| 8310 @Experimental | |
| 8311 void bind(String name, model, String path) { | |
| 8312 _bindElement(this, name, model, path); | |
| 8313 } | |
| 8314 | |
| 8315 // TODO(jmesserly): this is static to work around http://dartbug.com/10166 | |
| 8316 // Similar issue for unbind/unbindAll below. | |
| 8317 static void _bindElement(Element self, String name, model, String path) { | |
| 8318 if (self._bindTemplate(name, model, path)) return; | |
| 8319 | |
| 8320 if (self._attributeBindings == null) { | |
| 8321 self._attributeBindings = new Map<String, StreamSubscription>(); | |
| 8322 } | |
| 8323 | |
| 8324 self.xtag.attributes.remove(name); | |
| 8325 | |
| 8326 var changed; | |
| 8327 if (name.endsWith('?')) { | |
| 8328 name = name.substring(0, name.length - 1); | |
| 8329 | |
| 8330 changed = (value) { | |
| 8331 if (_Bindings._toBoolean(value)) { | |
| 8332 self.xtag.attributes[name] = ''; | |
| 8333 } else { | |
| 8334 self.xtag.attributes.remove(name); | |
| 8335 } | |
| 8336 }; | |
| 8337 } else { | |
| 8338 changed = (value) { | |
| 8339 // TODO(jmesserly): escape value if needed to protect against XSS. | |
| 8340 // See https://github.com/polymer-project/mdv/issues/58 | |
| 8341 self.xtag.attributes[name] = value == null ? '' : '$value'; | |
| 8342 }; | |
| 8343 } | |
| 8344 | |
| 8345 self.unbind(name); | |
| 8346 | |
| 8347 self._attributeBindings[name] = | |
| 8348 new PathObserver(model, path).bindSync(changed); | |
| 8349 } | |
| 8350 | |
| 8351 @Experimental | |
| 8352 void unbind(String name) { | |
| 8353 _unbindElement(this, name); | |
| 8354 } | |
| 8355 | |
| 8356 static _unbindElement(Element self, String name) { | |
| 8357 if (self._unbindTemplate(name)) return; | |
| 8358 if (self._attributeBindings != null) { | |
| 8359 var binding = self._attributeBindings.remove(name); | |
| 8360 if (binding != null) binding.cancel(); | |
| 8361 } | |
| 8362 } | |
| 8363 | |
| 8364 @Experimental | |
| 8365 void unbindAll() { | |
| 8366 _unbindAllElement(this); | |
| 8367 } | |
| 8368 | |
| 8369 static void _unbindAllElement(Element self) { | |
| 8370 self._unbindAllTemplate(); | |
| 8371 | |
| 8372 if (self._attributeBindings != null) { | |
| 8373 for (var binding in self._attributeBindings.values) { | |
| 8374 binding.cancel(); | |
| 8375 } | |
| 8376 self._attributeBindings = null; | |
| 8377 } | |
| 8378 } | |
| 8379 | |
| 8380 // TODO(jmesserly): unlike the JS polyfill, we can't mixin | |
| 8381 // HTMLTemplateElement at runtime into things that are semantically template | |
| 8382 // elements. So instead we implement it here with a runtime check. | |
| 8383 // If the bind succeeds, we return true, otherwise we return false and let | |
| 8384 // the normal Element.bind logic kick in. | |
| 8385 bool _bindTemplate(String name, model, String path) { | |
| 8386 if (isTemplate) { | |
| 8387 switch (name) { | |
| 8388 case 'bind': | |
| 8389 case 'repeat': | |
| 8390 case 'if': | |
| 8391 _ensureTemplate(); | |
| 8392 if (_templateIterator == null) { | |
| 8393 _templateIterator = new _TemplateIterator(this); | |
| 8394 } | |
| 8395 _templateIterator.inputs.bind(name, model, path); | |
| 8396 return true; | |
| 8397 } | |
| 8398 } | |
| 8399 return false; | |
| 8400 } | |
| 8401 | |
| 8402 bool _unbindTemplate(String name) { | |
| 8403 if (isTemplate) { | |
| 8404 switch (name) { | |
| 8405 case 'bind': | |
| 8406 case 'repeat': | |
| 8407 case 'if': | |
| 8408 _ensureTemplate(); | |
| 8409 if (_templateIterator != null) { | |
| 8410 _templateIterator.inputs.unbind(name); | |
| 8411 } | |
| 8412 return true; | |
| 8413 } | |
| 8414 } | |
| 8415 return false; | |
| 8416 } | |
| 8417 | |
| 8418 void _unbindAllTemplate() { | |
| 8419 if (isTemplate) { | |
| 8420 unbind('bind'); | |
| 8421 unbind('repeat'); | |
| 8422 unbind('if'); | |
| 8423 } | |
| 8424 } | |
| 8425 | |
| 8426 /** | 8291 /** |
| 8427 * Gets the template this node refers to. | 8292 * Gets the template this node refers to. |
| 8428 * This is only supported if [isTemplate] is true. | 8293 * This is only supported if [isTemplate] is true. |
| 8429 */ | 8294 */ |
| 8430 @Experimental | 8295 @Experimental |
| 8431 Element get ref { | 8296 Element get ref { |
| 8432 _ensureTemplate(); | 8297 _ensureTemplate(); |
| 8433 | 8298 |
| 8434 Element ref = null; | 8299 Element ref = null; |
| 8435 var refId = attributes['ref']; | 8300 var refId = attributes['ref']; |
| (...skipping 14 matching lines...) Expand all Loading... |
| 8450 return _templateContent; | 8315 return _templateContent; |
| 8451 } | 8316 } |
| 8452 | 8317 |
| 8453 /** | 8318 /** |
| 8454 * Creates an instance of the template. | 8319 * Creates an instance of the template. |
| 8455 * This is only supported if [isTemplate] is true. | 8320 * This is only supported if [isTemplate] is true. |
| 8456 */ | 8321 */ |
| 8457 @Experimental | 8322 @Experimental |
| 8458 DocumentFragment createInstance() { | 8323 DocumentFragment createInstance() { |
| 8459 _ensureTemplate(); | 8324 _ensureTemplate(); |
| 8460 | 8325 return TemplateElement.mdvPackage(this).createInstance(); |
| 8461 var template = ref; | |
| 8462 if (template == null) template = this; | |
| 8463 | |
| 8464 var instance = _Bindings._createDeepCloneAndDecorateTemplates( | |
| 8465 template.content, attributes['syntax']); | |
| 8466 | |
| 8467 if (TemplateElement._instanceCreated != null) { | |
| 8468 TemplateElement._instanceCreated.add(instance); | |
| 8469 } | |
| 8470 return instance; | |
| 8471 } | 8326 } |
| 8472 | 8327 |
| 8473 /** | 8328 /** |
| 8474 * The data model which is inherited through the tree. | 8329 * The data model which is inherited through the tree. |
| 8475 * This is only supported if [isTemplate] is true. | 8330 * This is only supported if [isTemplate] is true. |
| 8476 * | |
| 8477 * Setting this will destructive propagate the value to all descendant nodes, | |
| 8478 * and reinstantiate all of the nodes expanded by this template. | |
| 8479 * | |
| 8480 * Currently this does not support propagation through Shadow DOMs. | |
| 8481 */ | 8331 */ |
| 8482 @Experimental | 8332 @Experimental |
| 8483 get model => _model; | 8333 get model => TemplateElement.mdvPackage(this).model; |
| 8484 | 8334 |
| 8485 @Experimental | 8335 @Experimental |
| 8486 void set model(value) { | 8336 void set model(value) { |
| 8487 _ensureTemplate(); | 8337 _ensureTemplate(); |
| 8488 | 8338 TemplateElement.mdvPackage(this).model = value; |
| 8489 var syntax = TemplateElement.syntax[attributes['syntax']]; | |
| 8490 _model = value; | |
| 8491 _Bindings._addBindings(this, model, syntax); | |
| 8492 } | 8339 } |
| 8493 | 8340 |
| 8494 // TODO(jmesserly): const set would be better | 8341 // TODO(jmesserly): const set would be better |
| 8495 static const _TABLE_TAGS = const { | 8342 static const _TABLE_TAGS = const { |
| 8496 'caption': null, | 8343 'caption': null, |
| 8497 'col': null, | 8344 'col': null, |
| 8498 'colgroup': null, | 8345 'colgroup': null, |
| 8499 'tbody': null, | 8346 'tbody': null, |
| 8500 'td': null, | 8347 'td': null, |
| 8501 'tfoot': null, | 8348 'tfoot': null, |
| (...skipping 4015 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 12517 var e = document.$dom_createElement("input"); | 12364 var e = document.$dom_createElement("input"); |
| 12518 if (type != null) { | 12365 if (type != null) { |
| 12519 try { | 12366 try { |
| 12520 // IE throws an exception for unknown types. | 12367 // IE throws an exception for unknown types. |
| 12521 e.type = type; | 12368 e.type = type; |
| 12522 } catch(_) {} | 12369 } catch(_) {} |
| 12523 } | 12370 } |
| 12524 return e; | 12371 return e; |
| 12525 } | 12372 } |
| 12526 | 12373 |
| 12527 @Creates('Null') // Set from Dart code; does not instantiate a native type. | |
| 12528 _ValueBinding _valueBinding; | |
| 12529 | |
| 12530 @Creates('Null') // Set from Dart code; does not instantiate a native type. | |
| 12531 _CheckedBinding _checkedBinding; | |
| 12532 | |
| 12533 @Experimental | |
| 12534 void bind(String name, model, String path) { | |
| 12535 switch (name) { | |
| 12536 case 'value': | |
| 12537 unbind('value'); | |
| 12538 attributes.remove('value'); | |
| 12539 _valueBinding = new _ValueBinding(this, model, path); | |
| 12540 break; | |
| 12541 case 'checked': | |
| 12542 unbind('checked'); | |
| 12543 attributes.remove('checked'); | |
| 12544 _checkedBinding = new _CheckedBinding(this, model, path); | |
| 12545 break; | |
| 12546 default: | |
| 12547 // TODO(jmesserly): this should be "super" (http://dartbug.com/10166). | |
| 12548 // Similar issue for unbind/unbindAll below. | |
| 12549 Element._bindElement(this, name, model, path); | |
| 12550 break; | |
| 12551 } | |
| 12552 } | |
| 12553 | |
| 12554 @Experimental | |
| 12555 void unbind(String name) { | |
| 12556 switch (name) { | |
| 12557 case 'value': | |
| 12558 if (_valueBinding != null) { | |
| 12559 _valueBinding.unbind(); | |
| 12560 _valueBinding = null; | |
| 12561 } | |
| 12562 break; | |
| 12563 case 'checked': | |
| 12564 if (_checkedBinding != null) { | |
| 12565 _checkedBinding.unbind(); | |
| 12566 _checkedBinding = null; | |
| 12567 } | |
| 12568 break; | |
| 12569 default: | |
| 12570 Element._unbindElement(this, name); | |
| 12571 break; | |
| 12572 } | |
| 12573 } | |
| 12574 | |
| 12575 @Experimental | |
| 12576 void unbindAll() { | |
| 12577 unbind('value'); | |
| 12578 unbind('checked'); | |
| 12579 Element._unbindAllElement(this); | |
| 12580 } | |
| 12581 | |
| 12582 | 12374 |
| 12583 @DomName('HTMLInputElement.webkitSpeechChangeEvent') | 12375 @DomName('HTMLInputElement.webkitSpeechChangeEvent') |
| 12584 @DocsEditable | 12376 @DocsEditable |
| 12585 @SupportedBrowser(SupportedBrowser.CHROME) | 12377 @SupportedBrowser(SupportedBrowser.CHROME) |
| 12586 @SupportedBrowser(SupportedBrowser.SAFARI) | 12378 @SupportedBrowser(SupportedBrowser.SAFARI) |
| 12587 @Experimental | 12379 @Experimental |
| 12588 // http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/a
pi-draft.html#extending_html_elements | 12380 // http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/a
pi-draft.html#extending_html_elements |
| 12589 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); | 12381 static const EventStreamProvider<Event> speechChangeEvent = const EventStreamP
rovider<Event>('webkitSpeechChange'); |
| 12590 | 12382 |
| 12591 @DomName('HTMLInputElement.accept') | 12383 @DomName('HTMLInputElement.accept') |
| (...skipping 3898 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 16490 int get length => _this.$dom_childNodes.length; | 16282 int get length => _this.$dom_childNodes.length; |
| 16491 | 16283 |
| 16492 void set length(int value) { | 16284 void set length(int value) { |
| 16493 throw new UnsupportedError( | 16285 throw new UnsupportedError( |
| 16494 "Cannot set length on immutable List."); | 16286 "Cannot set length on immutable List."); |
| 16495 } | 16287 } |
| 16496 | 16288 |
| 16497 Node operator[](int index) => _this.$dom_childNodes[index]; | 16289 Node operator[](int index) => _this.$dom_childNodes[index]; |
| 16498 } | 16290 } |
| 16499 | 16291 |
| 16292 /** Information about the instantiated template. */ |
| 16293 class TemplateInstance { |
| 16294 // TODO(rafaelw): firstNode & lastNode should be read-synchronous |
| 16295 // in cases where script has modified the template instance boundary. |
| 16296 |
| 16297 /** The first node of this template instantiation. */ |
| 16298 final Node firstNode; |
| 16299 |
| 16300 /** |
| 16301 * The last node of this template instantiation. |
| 16302 * This could be identical to [firstNode] if the template only expanded to a |
| 16303 * single node. |
| 16304 */ |
| 16305 final Node lastNode; |
| 16306 |
| 16307 /** The model used to instantiate the template. */ |
| 16308 final model; |
| 16309 |
| 16310 TemplateInstance(this.firstNode, this.lastNode, this.model); |
| 16311 } |
| 16312 |
| 16500 @DomName('Node') | 16313 @DomName('Node') |
| 16501 class Node extends EventTarget native "Node" { | 16314 class Node extends EventTarget native "Node" { |
| 16502 List<Node> get nodes { | 16315 List<Node> get nodes { |
| 16503 return new _ChildNodeListLazy(this); | 16316 return new _ChildNodeListLazy(this); |
| 16504 } | 16317 } |
| 16505 | 16318 |
| 16506 void set nodes(Iterable<Node> value) { | 16319 void set nodes(Iterable<Node> value) { |
| 16507 // Copy list first since we don't want liveness during iteration. | 16320 // Copy list first since we don't want liveness during iteration. |
| 16508 // TODO(jacobr): there is a better way to do this. | 16321 // TODO(jacobr): there is a better way to do this. |
| 16509 List copy = new List.from(value); | 16322 List copy = new List.from(value); |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 16569 * Print out a String representation of this Node. | 16382 * Print out a String representation of this Node. |
| 16570 */ | 16383 */ |
| 16571 String toString() => nodeValue == null ? super.toString() : nodeValue; | 16384 String toString() => nodeValue == null ? super.toString() : nodeValue; |
| 16572 | 16385 |
| 16573 /** | 16386 /** |
| 16574 * Binds the attribute [name] to the [path] of the [model]. | 16387 * Binds the attribute [name] to the [path] of the [model]. |
| 16575 * Path is a String of accessors such as `foo.bar.baz`. | 16388 * Path is a String of accessors such as `foo.bar.baz`. |
| 16576 */ | 16389 */ |
| 16577 @Experimental | 16390 @Experimental |
| 16578 void bind(String name, model, String path) { | 16391 void bind(String name, model, String path) { |
| 16579 // TODO(jmesserly): should we throw instead? | 16392 TemplateElement.mdvPackage(this).bind(name, model, path); |
| 16580 window.console.error('Unhandled binding to Node: ' | |
| 16581 '$this $name $model $path'); | |
| 16582 } | 16393 } |
| 16583 | 16394 |
| 16584 /** Unbinds the attribute [name]. */ | 16395 /** Unbinds the attribute [name]. */ |
| 16585 @Experimental | 16396 @Experimental |
| 16586 void unbind(String name) {} | 16397 void unbind(String name) { |
| 16398 TemplateElement.mdvPackage(this).unbind(name); |
| 16399 } |
| 16587 | 16400 |
| 16588 /** Unbinds all bound attributes. */ | 16401 /** Unbinds all bound attributes. */ |
| 16589 @Experimental | 16402 @Experimental |
| 16590 void unbindAll() {} | 16403 void unbindAll() { |
| 16591 | 16404 TemplateElement.mdvPackage(this).unbindAll(); |
| 16592 TemplateInstance _templateInstance; | 16405 } |
| 16593 | 16406 |
| 16594 /** Gets the template instance that instantiated this node, if any. */ | 16407 /** Gets the template instance that instantiated this node, if any. */ |
| 16595 @Experimental | 16408 @Experimental |
| 16596 TemplateInstance get templateInstance => | 16409 TemplateInstance get templateInstance => mdvPackage(this).templateInstance; |
| 16597 _templateInstance != null ? _templateInstance : | |
| 16598 (parent != null ? parent.templateInstance : null); | |
| 16599 | 16410 |
| 16600 | 16411 |
| 16601 @DomName('Node.ATTRIBUTE_NODE') | 16412 @DomName('Node.ATTRIBUTE_NODE') |
| 16602 @DocsEditable | 16413 @DocsEditable |
| 16603 static const int ATTRIBUTE_NODE = 2; | 16414 static const int ATTRIBUTE_NODE = 2; |
| 16604 | 16415 |
| 16605 @DomName('Node.CDATA_SECTION_NODE') | 16416 @DomName('Node.CDATA_SECTION_NODE') |
| 16606 @DocsEditable | 16417 @DocsEditable |
| 16607 static const int CDATA_SECTION_NODE = 4; | 16418 static const int CDATA_SECTION_NODE = 4; |
| 16608 | 16419 |
| (...skipping 4417 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 21026 @DocsEditable | 20837 @DocsEditable |
| 21027 Element $dom_insertRow(int index) native; | 20838 Element $dom_insertRow(int index) native; |
| 21028 } | 20839 } |
| 21029 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 20840 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 21030 // for details. All rights reserved. Use of this source code is governed by a | 20841 // for details. All rights reserved. Use of this source code is governed by a |
| 21031 // BSD-style license that can be found in the LICENSE file. | 20842 // BSD-style license that can be found in the LICENSE file. |
| 21032 | 20843 |
| 21033 // WARNING: Do not edit - generated code. | 20844 // WARNING: Do not edit - generated code. |
| 21034 | 20845 |
| 21035 | 20846 |
| 20847 |
| 20848 /** |
| 20849 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases, |
| 20850 * but (by design) don't attempt to implement a wide array of specialized |
| 20851 * behaviors. |
| 20852 * |
| 20853 * Enabling these features in MDV is a matter of implementing and registering an |
| 20854 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more |
| 20855 * delegation functions which implement specialized behavior. This object is |
| 20856 * registered with MDV via [TemplateElement.syntax]: |
| 20857 * |
| 20858 * |
| 20859 * HTML: |
| 20860 * <template bind syntax="MySyntax"> |
| 20861 * {{ What!Ever('crazy')->thing^^^I+Want(data) }} |
| 20862 * </template> |
| 20863 * |
| 20864 * Dart: |
| 20865 * class MySyntax extends CustomBindingSyntax { |
| 20866 * getBinding(model, path, name, node) { |
| 20867 * // The magic happens here! |
| 20868 * } |
| 20869 * } |
| 20870 * |
| 20871 * ... |
| 20872 * |
| 20873 * TemplateElement.syntax['MySyntax'] = new MySyntax(); |
| 20874 * |
| 20875 * See <https://github.com/polymer-project/mdv/blob/master/docs/syntax.md> for |
| 20876 * more information about Custom Syntax. |
| 20877 */ |
| 20878 // TODO(jmesserly): if this is just one method, a function type would make it |
| 20879 // more Dart-friendly. |
| 20880 @Experimental |
| 20881 abstract class CustomBindingSyntax { |
| 20882 /** |
| 20883 * This syntax method allows for a custom interpretation of the contents of |
| 20884 * mustaches (`{{` ... `}}`). |
| 20885 * |
| 20886 * When a template is inserting an instance, it will invoke this method for |
| 20887 * each mustache which is encountered. The function is invoked with four |
| 20888 * arguments: |
| 20889 * |
| 20890 * - [model]: The data context for which this instance is being created. |
| 20891 * - [path]: The text contents (trimmed of outer whitespace) of the mustache. |
| 20892 * - [name]: The context in which the mustache occurs. Within element |
| 20893 * attributes, this will be the name of the attribute. Within text, |
| 20894 * this will be 'text'. |
| 20895 * - [node]: A reference to the node to which this binding will be created. |
| 20896 * |
| 20897 * If the method wishes to handle binding, it is required to return an object |
| 20898 * which has at least a `value` property that can be observed. If it does, |
| 20899 * then MDV will call [Node.bind on the node: |
| 20900 * |
| 20901 * node.bind(name, retval, 'value'); |
| 20902 * |
| 20903 * If the 'getBinding' does not wish to override the binding, it should return |
| 20904 * null. |
| 20905 */ |
| 20906 // TODO(jmesserly): I had to remove type annotations from "name" and "node" |
| 20907 // Normally they are String and Node respectively. But sometimes it will pass |
| 20908 // (int name, CompoundBinding node). That seems very confusing; we may want |
| 20909 // to change this API. |
| 20910 getBinding(model, String path, name, node) => null; |
| 20911 |
| 20912 /** |
| 20913 * This syntax method allows a syntax to provide an alterate model than the |
| 20914 * one the template would otherwise use when producing an instance. |
| 20915 * |
| 20916 * When a template is about to create an instance, it will invoke this method |
| 20917 * The function is invoked with two arguments: |
| 20918 * |
| 20919 * - [template]: The template element which is about to create and insert an |
| 20920 * instance. |
| 20921 * - [model]: The data context for which this instance is being created. |
| 20922 * |
| 20923 * The template element will always use the return value of `getInstanceModel` |
| 20924 * as the model for the new instance. If the syntax does not wish to override |
| 20925 * the value, it should simply return the `model` value it was passed. |
| 20926 */ |
| 20927 getInstanceModel(Element template, model) => model; |
| 20928 |
| 20929 /** |
| 20930 * This syntax method allows a syntax to provide an alterate expansion of |
| 20931 * the [template] contents. When the template wants to create an instance, |
| 20932 * it will call this method with the template element. |
| 20933 * |
| 20934 * By default this will call `template.createInstance()`. |
| 20935 */ |
| 20936 getInstanceFragment(Element template) => template.createInstance(); |
| 20937 } |
| 20938 |
| 20939 |
| 21036 @Experimental | 20940 @Experimental |
| 21037 @DomName('HTMLTemplateElement') | 20941 @DomName('HTMLTemplateElement') |
| 21038 @SupportedBrowser(SupportedBrowser.CHROME) | 20942 @SupportedBrowser(SupportedBrowser.CHROME) |
| 21039 @Experimental | 20943 @Experimental |
| 21040 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#t
emplate-element | 20944 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#t
emplate-element |
| 21041 class TemplateElement extends _HTMLElement native "HTMLTemplateElement" { | 20945 class TemplateElement extends _HTMLElement native "HTMLTemplateElement" { |
| 21042 | 20946 |
| 21043 @DomName('HTMLTemplateElement.HTMLTemplateElement') | 20947 @DomName('HTMLTemplateElement.HTMLTemplateElement') |
| 21044 @DocsEditable | 20948 @DocsEditable |
| 21045 factory TemplateElement() => document.$dom_createElement("template"); | 20949 factory TemplateElement() => document.$dom_createElement("template"); |
| 21046 | 20950 |
| 21047 /// Checks if this type is supported on the current platform. | 20951 /// Checks if this type is supported on the current platform. |
| 21048 static bool get supported => Element.isTagSupported('template'); | 20952 static bool get supported => Element.isTagSupported('template'); |
| 21049 | 20953 |
| 21050 @JSName('content') | 20954 @JSName('content') |
| 21051 @DomName('HTMLTemplateElement.content') | 20955 @DomName('HTMLTemplateElement.content') |
| 21052 @DocsEditable | 20956 @DocsEditable |
| 21053 final DocumentFragment $dom_content; | 20957 final DocumentFragment $dom_content; |
| 21054 | 20958 |
| 21055 | 20959 |
| 21056 // For real TemplateElement use the actual DOM .content field instead of | 20960 // For real TemplateElement use the actual DOM .content field instead of |
| 21057 // our polyfilled expando. | 20961 // our polyfilled expando. |
| 21058 @Experimental | 20962 @Experimental |
| 21059 DocumentFragment get content => $dom_content; | 20963 DocumentFragment get content => $dom_content; |
| 21060 | 20964 |
| 21061 static StreamController<DocumentFragment> _instanceCreated; | |
| 21062 | 20965 |
| 21063 /** | 20966 /** |
| 21064 * *Warning*: This is an implementation helper for Model-Driven Views and | 20967 * The MDV package, if available. |
| 21065 * should not be used in your code. | |
| 21066 * | 20968 * |
| 21067 * This event is fired whenever a template is instantiated via | 20969 * This can be used to initialize MDV support via: |
| 21068 * [createInstance]. | 20970 * |
| 20971 * import 'dart:html'; |
| 20972 * import 'package:mdv/mdv.dart' as mdv; |
| 20973 * main() { |
| 20974 * mdv.initialize(); |
| 20975 * } |
| 21069 */ | 20976 */ |
| 21070 // TODO(rafaelw): This is a hack, and is neccesary for the polyfill | 20977 static Function mdvPackage = (node) { |
| 21071 // because custom elements are not upgraded during clone() | 20978 throw new UnsupportedError("The MDV package is not available. " |
| 21072 @Experimental | 20979 "You can enable it with `import 'package:mdv/mdv.dart' as mdv;` and " |
| 21073 static Stream<DocumentFragment> get instanceCreated { | 20980 "`mdv.initialize()`"); |
| 21074 if (_instanceCreated == null) { | 20981 }; |
| 21075 _instanceCreated = new StreamController<DocumentFragment>(sync: true); | |
| 21076 } | |
| 21077 return _instanceCreated.stream; | |
| 21078 } | |
| 21079 | 20982 |
| 21080 /** | 20983 /** |
| 21081 * Ensures proper API and content model for template elements. | 20984 * Ensures proper API and content model for template elements. |
| 21082 * | 20985 * |
| 21083 * [instanceRef] can be used to set the [Element.ref] property of [template], | 20986 * [instanceRef] can be used to set the [Element.ref] property of [template], |
| 21084 * and use the ref's content will be used as source when createInstance() is | 20987 * and use the ref's content will be used as source when createInstance() is |
| 21085 * invoked. | 20988 * invoked. |
| 21086 * | 20989 * |
| 21087 * Returns true if this template was just decorated, or false if it was | 20990 * Returns true if this template was just decorated, or false if it was |
| 21088 * already decorated. | 20991 * already decorated. |
| 21089 */ | 20992 */ |
| 21090 @Experimental | 20993 @Experimental |
| 21091 static bool decorate(Element template, [Element instanceRef]) { | 20994 static bool decorate(Element template, [Element instanceRef]) { |
| 21092 // == true check because it starts as a null field. | 20995 // == true check because it starts as a null field. |
| 21093 if (template._templateIsDecorated == true) return false; | 20996 if (template._templateIsDecorated == true) return false; |
| 21094 | 20997 |
| 21095 template._templateIsDecorated = true; | 20998 template._templateIsDecorated = true; |
| 21096 | 20999 |
| 21097 _injectStylesheet(); | 21000 _injectStylesheet(); |
| 21098 | 21001 |
| 21099 // Create content | 21002 // Create content |
| 21100 if (template is! TemplateElement) { | 21003 if (template is! TemplateElement) { |
| 21101 var doc = _Bindings._getTemplateContentsOwner(template.document); | 21004 var doc = _getTemplateContentsOwner(template.document); |
| 21102 template._templateContent = doc.createDocumentFragment(); | 21005 template._templateContent = doc.createDocumentFragment(); |
| 21103 } | 21006 } |
| 21104 | 21007 |
| 21105 if (instanceRef != null) { | 21008 if (instanceRef != null) { |
| 21106 template._templateInstanceRef = instanceRef; | 21009 template._templateInstanceRef = instanceRef; |
| 21107 return true; // content is empty. | 21010 return true; // content is empty. |
| 21108 } | 21011 } |
| 21109 | 21012 |
| 21110 if (template is TemplateElement) { | 21013 if (template is TemplateElement) { |
| 21111 bootstrap(template.content); | 21014 bootstrap(template.content); |
| 21112 } else { | 21015 } else { |
| 21113 _Bindings._liftNonNativeChildrenIntoContent(template); | 21016 _liftNonNativeChildrenIntoContent(template); |
| 21114 } | 21017 } |
| 21115 | 21018 |
| 21116 return true; | 21019 return true; |
| 21117 } | 21020 } |
| 21118 | 21021 |
| 21022 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner |
| 21023 static Document _getTemplateContentsOwner(HtmlDocument doc) { |
| 21024 if (doc.window == null) { |
| 21025 return doc; |
| 21026 } |
| 21027 var d = doc._templateContentsOwner; |
| 21028 if (d == null) { |
| 21029 // TODO(arv): This should either be a Document or HTMLDocument depending |
| 21030 // on doc. |
| 21031 d = doc.implementation.createHtmlDocument(''); |
| 21032 while (d.lastChild != null) { |
| 21033 d.lastChild.remove(); |
| 21034 } |
| 21035 doc._templateContentsOwner = d; |
| 21036 } |
| 21037 return d; |
| 21038 } |
| 21039 |
| 21040 static Element _cloneAndSeperateAttributeTemplate(Element templateElement) { |
| 21041 var clone = templateElement.clone(false); |
| 21042 var attributes = templateElement.attributes; |
| 21043 for (var name in attributes.keys.toList()) { |
| 21044 switch (name) { |
| 21045 case 'template': |
| 21046 case 'repeat': |
| 21047 case 'bind': |
| 21048 case 'ref': |
| 21049 clone.attributes.remove(name); |
| 21050 break; |
| 21051 default: |
| 21052 attributes.remove(name); |
| 21053 break; |
| 21054 } |
| 21055 } |
| 21056 |
| 21057 return clone; |
| 21058 } |
| 21059 |
| 21060 static void _liftNonNativeChildrenIntoContent(Element templateElement) { |
| 21061 var content = templateElement.content; |
| 21062 |
| 21063 if (!templateElement._isAttributeTemplate) { |
| 21064 var child; |
| 21065 while ((child = templateElement.firstChild) != null) { |
| 21066 content.append(child); |
| 21067 } |
| 21068 return; |
| 21069 } |
| 21070 |
| 21071 // For attribute templates we copy the whole thing into the content and |
| 21072 // we move the non template attributes into the content. |
| 21073 // |
| 21074 // <tr foo template> |
| 21075 // |
| 21076 // becomes |
| 21077 // |
| 21078 // <tr template> |
| 21079 // + #document-fragment |
| 21080 // + <tr foo> |
| 21081 // |
| 21082 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement); |
| 21083 var child; |
| 21084 while ((child = templateElement.firstChild) != null) { |
| 21085 newRoot.append(child); |
| 21086 } |
| 21087 content.append(newRoot); |
| 21088 } |
| 21089 |
| 21119 /** | 21090 /** |
| 21120 * This used to decorate recursively all templates from a given node. | 21091 * This used to decorate recursively all templates from a given node. |
| 21121 * | 21092 * |
| 21122 * By default [decorate] will be called on templates lazily when certain | 21093 * By default [decorate] will be called on templates lazily when certain |
| 21123 * properties such as [model] are accessed, but it can be run eagerly to | 21094 * properties such as [model] are accessed, but it can be run eagerly to |
| 21124 * decorate an entire tree recursively. | 21095 * decorate an entire tree recursively. |
| 21125 */ | 21096 */ |
| 21126 // TODO(rafaelw): Review whether this is the right public API. | 21097 // TODO(rafaelw): Review whether this is the right public API. |
| 21127 @Experimental | 21098 @Experimental |
| 21128 static void bootstrap(Node content) { | 21099 static void bootstrap(Node content) { |
| 21129 _Bindings._bootstrapTemplatesRecursivelyFrom(content); | 21100 void _bootstrap(template) { |
| 21101 if (!TemplateElement.decorate(template)) { |
| 21102 bootstrap(template.content); |
| 21103 } |
| 21104 } |
| 21105 |
| 21106 // Need to do this first as the contents may get lifted if |node| is |
| 21107 // template. |
| 21108 // TODO(jmesserly): content is DocumentFragment or Element |
| 21109 var descendents = (content as dynamic).queryAll(_allTemplatesSelectors); |
| 21110 if (content is Element && (content as Element).isTemplate) { |
| 21111 _bootstrap(content); |
| 21112 } |
| 21113 |
| 21114 descendents.forEach(_bootstrap); |
| 21130 } | 21115 } |
| 21131 | 21116 |
| 21132 /** | 21117 static final String _allTemplatesSelectors = 'template, option[template], ' + |
| 21133 * Binds all mustaches recursively starting from the [root] node. | 21118 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", "); |
| 21134 * | |
| 21135 * Note: this is not an official Model-Driven-Views API; it is intended to | |
| 21136 * support binding the [ShadowRoot]'s content to a model. | |
| 21137 */ | |
| 21138 // TODO(jmesserly): this is needed to avoid two <template> nodes when using | |
| 21139 // bindings in a custom element's template. See also: | |
| 21140 // https://github.com/polymer-project/polymer/blob/master/src/bindMDV.js#L68 | |
| 21141 // Called from: | |
| 21142 // https://github.com/polymer-project/polymer/blob/master/src/register.js#L99 | |
| 21143 @Experimental | |
| 21144 static void bindModel(Node root, model, [CustomBindingSyntax syntax]) { | |
| 21145 _Bindings._addBindings(root, model, syntax); | |
| 21146 } | |
| 21147 | 21119 |
| 21148 static bool _initStyles; | 21120 static bool _initStyles; |
| 21149 | 21121 |
| 21150 static void _injectStylesheet() { | 21122 static void _injectStylesheet() { |
| 21151 if (_initStyles == true) return; | 21123 if (_initStyles == true) return; |
| 21152 _initStyles = true; | 21124 _initStyles = true; |
| 21153 | 21125 |
| 21154 var style = new StyleElement(); | 21126 var style = new StyleElement(); |
| 21155 style.text = r''' | 21127 style.text = r''' |
| 21156 template, | 21128 template, |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 21194 @DomName('Text.replaceWholeText') | 21166 @DomName('Text.replaceWholeText') |
| 21195 @DocsEditable | 21167 @DocsEditable |
| 21196 // http://dom.spec.whatwg.org/#dom-text-replacewholetext | 21168 // http://dom.spec.whatwg.org/#dom-text-replacewholetext |
| 21197 @deprecated // deprecated | 21169 @deprecated // deprecated |
| 21198 Text replaceWholeText(String content) native; | 21170 Text replaceWholeText(String content) native; |
| 21199 | 21171 |
| 21200 @DomName('Text.splitText') | 21172 @DomName('Text.splitText') |
| 21201 @DocsEditable | 21173 @DocsEditable |
| 21202 Text splitText(int offset) native; | 21174 Text splitText(int offset) native; |
| 21203 | 21175 |
| 21204 | |
| 21205 @Creates('Null') // Set from Dart code; does not instantiate a native type. | |
| 21206 StreamSubscription _textBinding; | |
| 21207 | |
| 21208 @Experimental | |
| 21209 void bind(String name, model, String path) { | |
| 21210 if (name != 'text') { | |
| 21211 super.bind(name, model, path); | |
| 21212 return; | |
| 21213 } | |
| 21214 | |
| 21215 unbind('text'); | |
| 21216 | |
| 21217 _textBinding = new PathObserver(model, path).bindSync((value) { | |
| 21218 text = value == null ? '' : '$value'; | |
| 21219 }); | |
| 21220 } | |
| 21221 | |
| 21222 @Experimental | |
| 21223 void unbind(String name) { | |
| 21224 if (name != 'text') { | |
| 21225 super.unbind(name); | |
| 21226 return; | |
| 21227 } | |
| 21228 | |
| 21229 if (_textBinding == null) return; | |
| 21230 | |
| 21231 _textBinding.cancel(); | |
| 21232 _textBinding = null; | |
| 21233 } | |
| 21234 | |
| 21235 @Experimental | |
| 21236 void unbindAll() { | |
| 21237 unbind('text'); | |
| 21238 super.unbindAll(); | |
| 21239 } | |
| 21240 } | 21176 } |
| 21241 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 21177 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 21242 // for details. All rights reserved. Use of this source code is governed by a | 21178 // for details. All rights reserved. Use of this source code is governed by a |
| 21243 // BSD-style license that can be found in the LICENSE file. | 21179 // BSD-style license that can be found in the LICENSE file. |
| 21244 | 21180 |
| 21245 | 21181 |
| 21246 @DocsEditable | 21182 @DocsEditable |
| 21247 @DomName('HTMLTextAreaElement') | 21183 @DomName('HTMLTextAreaElement') |
| 21248 class TextAreaElement extends _HTMLElement native "HTMLTextAreaElement" { | 21184 class TextAreaElement extends _HTMLElement native "HTMLTextAreaElement" { |
| 21249 | 21185 |
| (...skipping 6445 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 27695 * Truncates coordinates to integers and returns the result as a new | 27631 * Truncates coordinates to integers and returns the result as a new |
| 27696 * rectangle. | 27632 * rectangle. |
| 27697 */ | 27633 */ |
| 27698 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), | 27634 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), |
| 27699 height.toInt()); | 27635 height.toInt()); |
| 27700 | 27636 |
| 27701 Point get topLeft => new Point(this.left, this.top); | 27637 Point get topLeft => new Point(this.left, this.top); |
| 27702 Point get bottomRight => new Point(this.left + this.width, | 27638 Point get bottomRight => new Point(this.left + this.width, |
| 27703 this.top + this.height); | 27639 this.top + this.height); |
| 27704 } | 27640 } |
| 27705 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 27706 // for details. All rights reserved. Use of this source code is governed by a | |
| 27707 // BSD-style license that can be found in the LICENSE file. | |
| 27708 | |
| 27709 | |
| 27710 // This code is a port of Model-Driven-Views: | |
| 27711 // https://github.com/polymer-project/mdv | |
| 27712 // The code mostly comes from src/template_element.js | |
| 27713 | |
| 27714 typedef void _ChangeHandler(value); | |
| 27715 | |
| 27716 /** | |
| 27717 * Model-Driven Views (MDV)'s native features enables a wide-range of use cases, | |
| 27718 * but (by design) don't attempt to implement a wide array of specialized | |
| 27719 * behaviors. | |
| 27720 * | |
| 27721 * Enabling these features in MDV is a matter of implementing and registering an | |
| 27722 * MDV Custom Syntax. A Custom Syntax is an object which contains one or more | |
| 27723 * delegation functions which implement specialized behavior. This object is | |
| 27724 * registered with MDV via [TemplateElement.syntax]: | |
| 27725 * | |
| 27726 * | |
| 27727 * HTML: | |
| 27728 * <template bind syntax="MySyntax"> | |
| 27729 * {{ What!Ever('crazy')->thing^^^I+Want(data) }} | |
| 27730 * </template> | |
| 27731 * | |
| 27732 * Dart: | |
| 27733 * class MySyntax extends CustomBindingSyntax { | |
| 27734 * getBinding(model, path, name, node) { | |
| 27735 * // The magic happens here! | |
| 27736 * } | |
| 27737 * } | |
| 27738 * | |
| 27739 * ... | |
| 27740 * | |
| 27741 * TemplateElement.syntax['MySyntax'] = new MySyntax(); | |
| 27742 * | |
| 27743 * See <https://github.com/polymer-project/mdv/blob/master/docs/syntax.md> for m
ore | |
| 27744 * information about Custom Syntax. | |
| 27745 */ | |
| 27746 // TODO(jmesserly): if this is just one method, a function type would make it | |
| 27747 // more Dart-friendly. | |
| 27748 @Experimental | |
| 27749 abstract class CustomBindingSyntax { | |
| 27750 /** | |
| 27751 * This syntax method allows for a custom interpretation of the contents of | |
| 27752 * mustaches (`{{` ... `}}`). | |
| 27753 * | |
| 27754 * When a template is inserting an instance, it will invoke this method for | |
| 27755 * each mustache which is encountered. The function is invoked with four | |
| 27756 * arguments: | |
| 27757 * | |
| 27758 * - [model]: The data context for which this instance is being created. | |
| 27759 * - [path]: The text contents (trimmed of outer whitespace) of the mustache. | |
| 27760 * - [name]: The context in which the mustache occurs. Within element | |
| 27761 * attributes, this will be the name of the attribute. Within text, | |
| 27762 * this will be 'text'. | |
| 27763 * - [node]: A reference to the node to which this binding will be created. | |
| 27764 * | |
| 27765 * If the method wishes to handle binding, it is required to return an object | |
| 27766 * which has at least a `value` property that can be observed. If it does, | |
| 27767 * then MDV will call [Node.bind on the node: | |
| 27768 * | |
| 27769 * node.bind(name, retval, 'value'); | |
| 27770 * | |
| 27771 * If the 'getBinding' does not wish to override the binding, it should return | |
| 27772 * null. | |
| 27773 */ | |
| 27774 // TODO(jmesserly): I had to remove type annotations from "name" and "node" | |
| 27775 // Normally they are String and Node respectively. But sometimes it will pass | |
| 27776 // (int name, CompoundBinding node). That seems very confusing; we may want | |
| 27777 // to change this API. | |
| 27778 getBinding(model, String path, name, node) => null; | |
| 27779 | |
| 27780 /** | |
| 27781 * This syntax method allows a syntax to provide an alterate model than the | |
| 27782 * one the template would otherwise use when producing an instance. | |
| 27783 * | |
| 27784 * When a template is about to create an instance, it will invoke this method | |
| 27785 * The function is invoked with two arguments: | |
| 27786 * | |
| 27787 * - [template]: The template element which is about to create and insert an | |
| 27788 * instance. | |
| 27789 * - [model]: The data context for which this instance is being created. | |
| 27790 * | |
| 27791 * The template element will always use the return value of `getInstanceModel` | |
| 27792 * as the model for the new instance. If the syntax does not wish to override | |
| 27793 * the value, it should simply return the `model` value it was passed. | |
| 27794 */ | |
| 27795 getInstanceModel(Element template, model) => model; | |
| 27796 | |
| 27797 /** | |
| 27798 * This syntax method allows a syntax to provide an alterate expansion of | |
| 27799 * the [template] contents. When the template wants to create an instance, | |
| 27800 * it will call this method with the template element. | |
| 27801 * | |
| 27802 * By default this will call `template.createInstance()`. | |
| 27803 */ | |
| 27804 getInstanceFragment(Element template) => template.createInstance(); | |
| 27805 } | |
| 27806 | |
| 27807 /** The callback used in the [CompoundBinding.combinator] field. */ | |
| 27808 @Experimental | |
| 27809 typedef Object CompoundBindingCombinator(Map objects); | |
| 27810 | |
| 27811 /** Information about the instantiated template. */ | |
| 27812 @Experimental | |
| 27813 class TemplateInstance { | |
| 27814 // TODO(rafaelw): firstNode & lastNode should be read-synchronous | |
| 27815 // in cases where script has modified the template instance boundary. | |
| 27816 | |
| 27817 /** The first node of this template instantiation. */ | |
| 27818 final Node firstNode; | |
| 27819 | |
| 27820 /** | |
| 27821 * The last node of this template instantiation. | |
| 27822 * This could be identical to [firstNode] if the template only expanded to a | |
| 27823 * single node. | |
| 27824 */ | |
| 27825 final Node lastNode; | |
| 27826 | |
| 27827 /** The model used to instantiate the template. */ | |
| 27828 final model; | |
| 27829 | |
| 27830 TemplateInstance(this.firstNode, this.lastNode, this.model); | |
| 27831 } | |
| 27832 | |
| 27833 /** | |
| 27834 * Model-Driven Views contains a helper object which is useful for the | |
| 27835 * implementation of a Custom Syntax. | |
| 27836 * | |
| 27837 * var binding = new CompoundBinding((values) { | |
| 27838 * var combinedValue; | |
| 27839 * // compute combinedValue based on the current values which are provided | |
| 27840 * return combinedValue; | |
| 27841 * }); | |
| 27842 * binding.bind('name1', obj1, path1); | |
| 27843 * binding.bind('name2', obj2, path2); | |
| 27844 * //... | |
| 27845 * binding.bind('nameN', objN, pathN); | |
| 27846 * | |
| 27847 * CompoundBinding is an object which knows how to listen to multiple path | |
| 27848 * values (registered via [bind]) and invoke its [combinator] when one or more | |
| 27849 * of the values have changed and set its [value] property to the return value | |
| 27850 * of the function. When any value has changed, all current values are provided | |
| 27851 * to the [combinator] in the single `values` argument. | |
| 27852 * | |
| 27853 * See [CustomBindingSyntax] for more information. | |
| 27854 */ | |
| 27855 // TODO(jmesserly): what is the public API surface here? I just guessed; | |
| 27856 // most of it seemed non-public. | |
| 27857 @Experimental | |
| 27858 class CompoundBinding extends ObservableBase { | |
| 27859 CompoundBindingCombinator _combinator; | |
| 27860 | |
| 27861 // TODO(jmesserly): ideally these would be String keys, but sometimes we | |
| 27862 // use integers. | |
| 27863 Map<dynamic, StreamSubscription> _bindings = new Map(); | |
| 27864 Map _values = new Map(); | |
| 27865 bool _scheduled = false; | |
| 27866 bool _disposed = false; | |
| 27867 Object _value; | |
| 27868 | |
| 27869 CompoundBinding([CompoundBindingCombinator combinator]) { | |
| 27870 // TODO(jmesserly): this is a tweak to the original code, it seemed to me | |
| 27871 // that passing the combinator to the constructor should be equivalent to | |
| 27872 // setting it via the property. | |
| 27873 // I also added a null check to the combinator setter. | |
| 27874 this.combinator = combinator; | |
| 27875 } | |
| 27876 | |
| 27877 CompoundBindingCombinator get combinator => _combinator; | |
| 27878 | |
| 27879 set combinator(CompoundBindingCombinator combinator) { | |
| 27880 _combinator = combinator; | |
| 27881 if (combinator != null) _scheduleResolve(); | |
| 27882 } | |
| 27883 | |
| 27884 static const _VALUE = const Symbol('value'); | |
| 27885 | |
| 27886 get value => _value; | |
| 27887 | |
| 27888 void set value(newValue) { | |
| 27889 _value = notifyPropertyChange(_VALUE, _value, newValue); | |
| 27890 } | |
| 27891 | |
| 27892 // TODO(jmesserly): remove these workarounds when dart2js supports mirrors! | |
| 27893 getValueWorkaround(key) { | |
| 27894 if (key == _VALUE) return value; | |
| 27895 return null; | |
| 27896 } | |
| 27897 setValueWorkaround(key, val) { | |
| 27898 if (key == _VALUE) value = val; | |
| 27899 } | |
| 27900 | |
| 27901 void bind(name, model, String path) { | |
| 27902 unbind(name); | |
| 27903 | |
| 27904 _bindings[name] = new PathObserver(model, path).bindSync((value) { | |
| 27905 _values[name] = value; | |
| 27906 _scheduleResolve(); | |
| 27907 }); | |
| 27908 } | |
| 27909 | |
| 27910 void unbind(name, {bool suppressResolve: false}) { | |
| 27911 var binding = _bindings.remove(name); | |
| 27912 if (binding == null) return; | |
| 27913 | |
| 27914 binding.cancel(); | |
| 27915 _values.remove(name); | |
| 27916 if (!suppressResolve) _scheduleResolve(); | |
| 27917 } | |
| 27918 | |
| 27919 // TODO(rafaelw): Is this the right processing model? | |
| 27920 // TODO(rafaelw): Consider having a seperate ChangeSummary for | |
| 27921 // CompoundBindings so to excess dirtyChecks. | |
| 27922 void _scheduleResolve() { | |
| 27923 if (_scheduled) return; | |
| 27924 _scheduled = true; | |
| 27925 queueChangeRecords(resolve); | |
| 27926 } | |
| 27927 | |
| 27928 void resolve() { | |
| 27929 if (_disposed) return; | |
| 27930 _scheduled = false; | |
| 27931 | |
| 27932 if (_combinator == null) { | |
| 27933 throw new StateError( | |
| 27934 'CompoundBinding attempted to resolve without a combinator'); | |
| 27935 } | |
| 27936 | |
| 27937 value = _combinator(_values); | |
| 27938 } | |
| 27939 | |
| 27940 void dispose() { | |
| 27941 for (var binding in _bindings.values) { | |
| 27942 binding.cancel(); | |
| 27943 } | |
| 27944 _bindings.clear(); | |
| 27945 _values.clear(); | |
| 27946 | |
| 27947 _disposed = true; | |
| 27948 value = null; | |
| 27949 } | |
| 27950 } | |
| 27951 | |
| 27952 abstract class _InputBinding { | |
| 27953 final InputElement element; | |
| 27954 PathObserver binding; | |
| 27955 StreamSubscription _pathSub; | |
| 27956 StreamSubscription _eventSub; | |
| 27957 | |
| 27958 _InputBinding(this.element, model, String path) { | |
| 27959 binding = new PathObserver(model, path); | |
| 27960 _pathSub = binding.bindSync(valueChanged); | |
| 27961 _eventSub = _getStreamForInputType(element).listen(updateBinding); | |
| 27962 } | |
| 27963 | |
| 27964 void valueChanged(newValue); | |
| 27965 | |
| 27966 void updateBinding(e); | |
| 27967 | |
| 27968 void unbind() { | |
| 27969 binding = null; | |
| 27970 _pathSub.cancel(); | |
| 27971 _eventSub.cancel(); | |
| 27972 } | |
| 27973 | |
| 27974 | |
| 27975 static Stream<Event> _getStreamForInputType(InputElement element) { | |
| 27976 switch (element.type) { | |
| 27977 case 'checkbox': | |
| 27978 return element.onClick; | |
| 27979 case 'radio': | |
| 27980 case 'select-multiple': | |
| 27981 case 'select-one': | |
| 27982 return element.onChange; | |
| 27983 default: | |
| 27984 return element.onInput; | |
| 27985 } | |
| 27986 } | |
| 27987 } | |
| 27988 | |
| 27989 class _ValueBinding extends _InputBinding { | |
| 27990 _ValueBinding(element, model, path) : super(element, model, path); | |
| 27991 | |
| 27992 void valueChanged(value) { | |
| 27993 element.value = value == null ? '' : '$value'; | |
| 27994 } | |
| 27995 | |
| 27996 void updateBinding(e) { | |
| 27997 binding.value = element.value; | |
| 27998 } | |
| 27999 } | |
| 28000 | |
| 28001 class _CheckedBinding extends _InputBinding { | |
| 28002 _CheckedBinding(element, model, path) : super(element, model, path); | |
| 28003 | |
| 28004 void valueChanged(value) { | |
| 28005 element.checked = _Bindings._toBoolean(value); | |
| 28006 } | |
| 28007 | |
| 28008 void updateBinding(e) { | |
| 28009 binding.value = element.checked; | |
| 28010 | |
| 28011 // Only the radio button that is getting checked gets an event. We | |
| 28012 // therefore find all the associated radio buttons and update their | |
| 28013 // CheckedBinding manually. | |
| 28014 if (element is InputElement && element.type == 'radio') { | |
| 28015 for (var r in _getAssociatedRadioButtons(element)) { | |
| 28016 var checkedBinding = r._checkedBinding; | |
| 28017 if (checkedBinding != null) { | |
| 28018 // Set the value directly to avoid an infinite call stack. | |
| 28019 checkedBinding.binding.value = false; | |
| 28020 } | |
| 28021 } | |
| 28022 } | |
| 28023 } | |
| 28024 | |
| 28025 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. | |
| 28026 // Returns an array containing all radio buttons other than |element| that | |
| 28027 // have the same |name|, either in the form that |element| belongs to or, | |
| 28028 // if no form, in the document tree to which |element| belongs. | |
| 28029 // | |
| 28030 // This implementation is based upon the HTML spec definition of a | |
| 28031 // "radio button group": | |
| 28032 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group | |
| 28033 // | |
| 28034 static Iterable _getAssociatedRadioButtons(element) { | |
| 28035 if (!_isNodeInDocument(element)) return []; | |
| 28036 if (element.form != null) { | |
| 28037 return element.form.nodes.where((el) { | |
| 28038 return el != element && | |
| 28039 el is InputElement && | |
| 28040 el.type == 'radio' && | |
| 28041 el.name == element.name; | |
| 28042 }); | |
| 28043 } else { | |
| 28044 var radios = element.document.queryAll( | |
| 28045 'input[type="radio"][name="${element.name}"]'); | |
| 28046 return radios.where((el) => el != element && el.form == null); | |
| 28047 } | |
| 28048 } | |
| 28049 | |
| 28050 // TODO(jmesserly): polyfill document.contains API instead of doing it here | |
| 28051 static bool _isNodeInDocument(Node node) { | |
| 28052 // On non-IE this works: | |
| 28053 // return node.document.contains(node); | |
| 28054 var document = node.document; | |
| 28055 if (node == document || node.parentNode == document) return true; | |
| 28056 return document.documentElement.contains(node); | |
| 28057 } | |
| 28058 } | |
| 28059 | |
| 28060 class _Bindings { | |
| 28061 // TODO(jmesserly): not sure what kind of boolean conversion rules to | |
| 28062 // apply for template data-binding. HTML attributes are true if they're | |
| 28063 // present. However Dart only treats "true" as true. Since this is HTML we'll | |
| 28064 // use something closer to the HTML rules: null (missing) and false are false, | |
| 28065 // everything else is true. See: https://github.com/polymer-project/mdv/issues
/59 | |
| 28066 static bool _toBoolean(value) => null != value && false != value; | |
| 28067 | |
| 28068 static Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { | |
| 28069 var clone = node.clone(false); // Shallow clone. | |
| 28070 if (clone is Element && clone.isTemplate) { | |
| 28071 TemplateElement.decorate(clone, node); | |
| 28072 if (syntax != null) { | |
| 28073 clone.attributes.putIfAbsent('syntax', () => syntax); | |
| 28074 } | |
| 28075 } | |
| 28076 | |
| 28077 for (var c = node.firstChild; c != null; c = c.nextNode) { | |
| 28078 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax)); | |
| 28079 } | |
| 28080 return clone; | |
| 28081 } | |
| 28082 | |
| 28083 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner | |
| 28084 static Document _getTemplateContentsOwner(HtmlDocument doc) { | |
| 28085 if (doc.window == null) { | |
| 28086 return doc; | |
| 28087 } | |
| 28088 var d = doc._templateContentsOwner; | |
| 28089 if (d == null) { | |
| 28090 // TODO(arv): This should either be a Document or HTMLDocument depending | |
| 28091 // on doc. | |
| 28092 d = doc.implementation.createHtmlDocument(''); | |
| 28093 while (d.lastChild != null) { | |
| 28094 d.lastChild.remove(); | |
| 28095 } | |
| 28096 doc._templateContentsOwner = d; | |
| 28097 } | |
| 28098 return d; | |
| 28099 } | |
| 28100 | |
| 28101 static Element _cloneAndSeperateAttributeTemplate(Element templateElement) { | |
| 28102 var clone = templateElement.clone(false); | |
| 28103 var attributes = templateElement.attributes; | |
| 28104 for (var name in attributes.keys.toList()) { | |
| 28105 switch (name) { | |
| 28106 case 'template': | |
| 28107 case 'repeat': | |
| 28108 case 'bind': | |
| 28109 case 'ref': | |
| 28110 clone.attributes.remove(name); | |
| 28111 break; | |
| 28112 default: | |
| 28113 attributes.remove(name); | |
| 28114 break; | |
| 28115 } | |
| 28116 } | |
| 28117 | |
| 28118 return clone; | |
| 28119 } | |
| 28120 | |
| 28121 static void _liftNonNativeChildrenIntoContent(Element templateElement) { | |
| 28122 var content = templateElement.content; | |
| 28123 | |
| 28124 if (!templateElement._isAttributeTemplate) { | |
| 28125 var child; | |
| 28126 while ((child = templateElement.firstChild) != null) { | |
| 28127 content.append(child); | |
| 28128 } | |
| 28129 return; | |
| 28130 } | |
| 28131 | |
| 28132 // For attribute templates we copy the whole thing into the content and | |
| 28133 // we move the non template attributes into the content. | |
| 28134 // | |
| 28135 // <tr foo template> | |
| 28136 // | |
| 28137 // becomes | |
| 28138 // | |
| 28139 // <tr template> | |
| 28140 // + #document-fragment | |
| 28141 // + <tr foo> | |
| 28142 // | |
| 28143 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement); | |
| 28144 var child; | |
| 28145 while ((child = templateElement.firstChild) != null) { | |
| 28146 newRoot.append(child); | |
| 28147 } | |
| 28148 content.append(newRoot); | |
| 28149 } | |
| 28150 | |
| 28151 static void _bootstrapTemplatesRecursivelyFrom(Node node) { | |
| 28152 void bootstrap(template) { | |
| 28153 if (!TemplateElement.decorate(template)) { | |
| 28154 _bootstrapTemplatesRecursivelyFrom(template.content); | |
| 28155 } | |
| 28156 } | |
| 28157 | |
| 28158 // Need to do this first as the contents may get lifted if |node| is | |
| 28159 // template. | |
| 28160 // TODO(jmesserly): node is DocumentFragment or Element | |
| 28161 var descendents = (node as dynamic).queryAll(_allTemplatesSelectors); | |
| 28162 if (node is Element && (node as Element).isTemplate) bootstrap(node); | |
| 28163 | |
| 28164 descendents.forEach(bootstrap); | |
| 28165 } | |
| 28166 | |
| 28167 static final String _allTemplatesSelectors = 'template, option[template], ' + | |
| 28168 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", "); | |
| 28169 | |
| 28170 static void _addBindings(Node node, model, [CustomBindingSyntax syntax]) { | |
| 28171 if (node is Element) { | |
| 28172 _addAttributeBindings(node, model, syntax); | |
| 28173 } else if (node is Text) { | |
| 28174 _parseAndBind(node, 'text', node.text, model, syntax); | |
| 28175 } | |
| 28176 | |
| 28177 for (var c = node.firstChild; c != null; c = c.nextNode) { | |
| 28178 _addBindings(c, model, syntax); | |
| 28179 } | |
| 28180 } | |
| 28181 | |
| 28182 static void _addAttributeBindings(Element element, model, syntax) { | |
| 28183 element.attributes.forEach((name, value) { | |
| 28184 if (value == '' && (name == 'bind' || name == 'repeat')) { | |
| 28185 value = '{{}}'; | |
| 28186 } | |
| 28187 _parseAndBind(element, name, value, model, syntax); | |
| 28188 }); | |
| 28189 } | |
| 28190 | |
| 28191 static void _parseAndBind(Node node, String name, String text, model, | |
| 28192 CustomBindingSyntax syntax) { | |
| 28193 | |
| 28194 var tokens = _parseMustacheTokens(text); | |
| 28195 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { | |
| 28196 return; | |
| 28197 } | |
| 28198 | |
| 28199 // If this is a custom element, give the .xtag a change to bind. | |
| 28200 node = _nodeOrCustom(node); | |
| 28201 | |
| 28202 if (tokens.length == 1 && tokens[0].isBinding) { | |
| 28203 _bindOrDelegate(node, name, model, tokens[0].value, syntax); | |
| 28204 return; | |
| 28205 } | |
| 28206 | |
| 28207 var replacementBinding = new CompoundBinding(); | |
| 28208 for (var i = 0; i < tokens.length; i++) { | |
| 28209 var token = tokens[i]; | |
| 28210 if (token.isBinding) { | |
| 28211 _bindOrDelegate(replacementBinding, i, model, token.value, syntax); | |
| 28212 } | |
| 28213 } | |
| 28214 | |
| 28215 replacementBinding.combinator = (values) { | |
| 28216 var newValue = new StringBuffer(); | |
| 28217 | |
| 28218 for (var i = 0; i < tokens.length; i++) { | |
| 28219 var token = tokens[i]; | |
| 28220 if (token.isText) { | |
| 28221 newValue.write(token.value); | |
| 28222 } else { | |
| 28223 var value = values[i]; | |
| 28224 if (value != null) { | |
| 28225 newValue.write(value); | |
| 28226 } | |
| 28227 } | |
| 28228 } | |
| 28229 | |
| 28230 return newValue.toString(); | |
| 28231 }; | |
| 28232 | |
| 28233 node.bind(name, replacementBinding, 'value'); | |
| 28234 } | |
| 28235 | |
| 28236 static void _bindOrDelegate(node, name, model, String path, | |
| 28237 CustomBindingSyntax syntax) { | |
| 28238 | |
| 28239 if (syntax != null) { | |
| 28240 var delegateBinding = syntax.getBinding(model, path, name, node); | |
| 28241 if (delegateBinding != null) { | |
| 28242 model = delegateBinding; | |
| 28243 path = 'value'; | |
| 28244 } | |
| 28245 } | |
| 28246 | |
| 28247 node.bind(name, model, path); | |
| 28248 } | |
| 28249 | |
| 28250 /** | |
| 28251 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns | |
| 28252 * the node. This is used so nodes can override [Node.bind], [Node.unbind], | |
| 28253 * and [Node.unbindAll] like InputElement does. | |
| 28254 */ | |
| 28255 // TODO(jmesserly): remove this when we can extend Element for real. | |
| 28256 static _nodeOrCustom(node) => node is Element ? node.xtag : node; | |
| 28257 | |
| 28258 static List<_BindingToken> _parseMustacheTokens(String s) { | |
| 28259 var result = []; | |
| 28260 var length = s.length; | |
| 28261 var index = 0, lastIndex = 0; | |
| 28262 while (lastIndex < length) { | |
| 28263 index = s.indexOf('{{', lastIndex); | |
| 28264 if (index < 0) { | |
| 28265 result.add(new _BindingToken(s.substring(lastIndex))); | |
| 28266 break; | |
| 28267 } else { | |
| 28268 // There is a non-empty text run before the next path token. | |
| 28269 if (index > 0 && lastIndex < index) { | |
| 28270 result.add(new _BindingToken(s.substring(lastIndex, index))); | |
| 28271 } | |
| 28272 lastIndex = index + 2; | |
| 28273 index = s.indexOf('}}', lastIndex); | |
| 28274 if (index < 0) { | |
| 28275 var text = s.substring(lastIndex - 2); | |
| 28276 if (result.length > 0 && result.last.isText) { | |
| 28277 result.last.value += text; | |
| 28278 } else { | |
| 28279 result.add(new _BindingToken(text)); | |
| 28280 } | |
| 28281 break; | |
| 28282 } | |
| 28283 | |
| 28284 var value = s.substring(lastIndex, index).trim(); | |
| 28285 result.add(new _BindingToken(value, isBinding: true)); | |
| 28286 lastIndex = index + 2; | |
| 28287 } | |
| 28288 } | |
| 28289 return result; | |
| 28290 } | |
| 28291 | |
| 28292 static void _addTemplateInstanceRecord(fragment, model) { | |
| 28293 if (fragment.firstChild == null) { | |
| 28294 return; | |
| 28295 } | |
| 28296 | |
| 28297 var instanceRecord = new TemplateInstance( | |
| 28298 fragment.firstChild, fragment.lastChild, model); | |
| 28299 | |
| 28300 var node = instanceRecord.firstNode; | |
| 28301 while (node != null) { | |
| 28302 node._templateInstance = instanceRecord; | |
| 28303 node = node.nextNode; | |
| 28304 } | |
| 28305 } | |
| 28306 | |
| 28307 static void _removeAllBindingsRecursively(Node node) { | |
| 28308 _nodeOrCustom(node).unbindAll(); | |
| 28309 for (var c = node.firstChild; c != null; c = c.nextNode) { | |
| 28310 _removeAllBindingsRecursively(c); | |
| 28311 } | |
| 28312 } | |
| 28313 | |
| 28314 static void _removeChild(Node parent, Node child) { | |
| 28315 child._templateInstance = null; | |
| 28316 if (child is Element && (child as Element).isTemplate) { | |
| 28317 Element childElement = child; | |
| 28318 // Make sure we stop observing when we remove an element. | |
| 28319 var templateIterator = childElement._templateIterator; | |
| 28320 if (templateIterator != null) { | |
| 28321 templateIterator.abandon(); | |
| 28322 childElement._templateIterator = null; | |
| 28323 } | |
| 28324 } | |
| 28325 child.remove(); | |
| 28326 _removeAllBindingsRecursively(child); | |
| 28327 } | |
| 28328 } | |
| 28329 | |
| 28330 class _BindingToken { | |
| 28331 final String value; | |
| 28332 final bool isBinding; | |
| 28333 | |
| 28334 _BindingToken(this.value, {this.isBinding: false}); | |
| 28335 | |
| 28336 bool get isText => !isBinding; | |
| 28337 } | |
| 28338 | |
| 28339 class _TemplateIterator { | |
| 28340 final Element _templateElement; | |
| 28341 final List<Node> terminators = []; | |
| 28342 final CompoundBinding inputs; | |
| 28343 List iteratedValue; | |
| 28344 | |
| 28345 StreamSubscription _sub; | |
| 28346 StreamSubscription _valueBinding; | |
| 28347 | |
| 28348 _TemplateIterator(this._templateElement) | |
| 28349 : inputs = new CompoundBinding(resolveInputs) { | |
| 28350 | |
| 28351 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); | |
| 28352 } | |
| 28353 | |
| 28354 static Object resolveInputs(Map values) { | |
| 28355 if (values.containsKey('if') && !_Bindings._toBoolean(values['if'])) { | |
| 28356 return null; | |
| 28357 } | |
| 28358 | |
| 28359 if (values.containsKey('repeat')) { | |
| 28360 return values['repeat']; | |
| 28361 } | |
| 28362 | |
| 28363 if (values.containsKey('bind')) { | |
| 28364 return [values['bind']]; | |
| 28365 } | |
| 28366 | |
| 28367 return null; | |
| 28368 } | |
| 28369 | |
| 28370 void valueChanged(value) { | |
| 28371 clear(); | |
| 28372 if (value is! List) return; | |
| 28373 | |
| 28374 iteratedValue = value; | |
| 28375 | |
| 28376 if (value is Observable) { | |
| 28377 _sub = value.changes.listen(_handleChanges); | |
| 28378 } | |
| 28379 | |
| 28380 int len = iteratedValue.length; | |
| 28381 if (len > 0) { | |
| 28382 _handleChanges([new ListChangeRecord(0, addedCount: len)]); | |
| 28383 } | |
| 28384 } | |
| 28385 | |
| 28386 Node getTerminatorAt(int index) { | |
| 28387 if (index == -1) return _templateElement; | |
| 28388 var terminator = terminators[index]; | |
| 28389 if (terminator is! Element) return terminator; | |
| 28390 | |
| 28391 var subIterator = terminator._templateIterator; | |
| 28392 if (subIterator == null) return terminator; | |
| 28393 | |
| 28394 return subIterator.getTerminatorAt(subIterator.terminators.length - 1); | |
| 28395 } | |
| 28396 | |
| 28397 void insertInstanceAt(int index, Node fragment) { | |
| 28398 var previousTerminator = getTerminatorAt(index - 1); | |
| 28399 var terminator = fragment.lastChild; | |
| 28400 if (terminator == null) terminator = previousTerminator; | |
| 28401 | |
| 28402 terminators.insert(index, terminator); | |
| 28403 var parent = _templateElement.parentNode; | |
| 28404 parent.insertBefore(fragment, previousTerminator.nextNode); | |
| 28405 } | |
| 28406 | |
| 28407 void removeInstanceAt(int index) { | |
| 28408 var previousTerminator = getTerminatorAt(index - 1); | |
| 28409 var terminator = getTerminatorAt(index); | |
| 28410 terminators.removeAt(index); | |
| 28411 | |
| 28412 var parent = _templateElement.parentNode; | |
| 28413 while (terminator != previousTerminator) { | |
| 28414 var node = terminator; | |
| 28415 terminator = node.previousNode; | |
| 28416 _Bindings._removeChild(parent, node); | |
| 28417 } | |
| 28418 } | |
| 28419 | |
| 28420 void removeAllInstances() { | |
| 28421 if (terminators.length == 0) return; | |
| 28422 | |
| 28423 var previousTerminator = _templateElement; | |
| 28424 var terminator = getTerminatorAt(terminators.length - 1); | |
| 28425 terminators.length = 0; | |
| 28426 | |
| 28427 var parent = _templateElement.parentNode; | |
| 28428 while (terminator != previousTerminator) { | |
| 28429 var node = terminator; | |
| 28430 terminator = node.previousNode; | |
| 28431 _Bindings._removeChild(parent, node); | |
| 28432 } | |
| 28433 } | |
| 28434 | |
| 28435 void clear() { | |
| 28436 unobserve(); | |
| 28437 removeAllInstances(); | |
| 28438 iteratedValue = null; | |
| 28439 } | |
| 28440 | |
| 28441 getInstanceModel(model, syntax) { | |
| 28442 if (syntax != null) { | |
| 28443 return syntax.getInstanceModel(_templateElement, model); | |
| 28444 } | |
| 28445 return model; | |
| 28446 } | |
| 28447 | |
| 28448 getInstanceFragment(syntax) { | |
| 28449 if (syntax != null) { | |
| 28450 return syntax.getInstanceFragment(_templateElement); | |
| 28451 } | |
| 28452 return _templateElement.createInstance(); | |
| 28453 } | |
| 28454 | |
| 28455 void _handleChanges(List<ListChangeRecord> splices) { | |
| 28456 var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']]; | |
| 28457 | |
| 28458 for (var splice in splices) { | |
| 28459 if (splice is! ListChangeRecord) continue; | |
| 28460 | |
| 28461 for (int i = 0; i < splice.removedCount; i++) { | |
| 28462 removeInstanceAt(splice.index); | |
| 28463 } | |
| 28464 | |
| 28465 for (var addIndex = splice.index; | |
| 28466 addIndex < splice.index + splice.addedCount; | |
| 28467 addIndex++) { | |
| 28468 | |
| 28469 var model = getInstanceModel(iteratedValue[addIndex], syntax); | |
| 28470 | |
| 28471 var fragment = getInstanceFragment(syntax); | |
| 28472 | |
| 28473 _Bindings._addBindings(fragment, model, syntax); | |
| 28474 _Bindings._addTemplateInstanceRecord(fragment, model); | |
| 28475 | |
| 28476 insertInstanceAt(addIndex, fragment); | |
| 28477 } | |
| 28478 } | |
| 28479 } | |
| 28480 | |
| 28481 void unobserve() { | |
| 28482 if (_sub == null) return; | |
| 28483 _sub.cancel(); | |
| 28484 _sub = null; | |
| 28485 } | |
| 28486 | |
| 28487 void abandon() { | |
| 28488 unobserve(); | |
| 28489 _valueBinding.cancel(); | |
| 28490 inputs.dispose(); | |
| 28491 } | |
| 28492 } | |
| 28493 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 27641 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 28494 // for details. All rights reserved. Use of this source code is governed by a | 27642 // for details. All rights reserved. Use of this source code is governed by a |
| 28495 // BSD-style license that can be found in the LICENSE file. | 27643 // BSD-style license that can be found in the LICENSE file. |
| 28496 | 27644 |
| 28497 | 27645 |
| 28498 class _HttpRequestUtils { | 27646 class _HttpRequestUtils { |
| 28499 | 27647 |
| 28500 // Helper for factory HttpRequest.get | 27648 // Helper for factory HttpRequest.get |
| 28501 static HttpRequest get(String url, | 27649 static HttpRequest get(String url, |
| 28502 onComplete(HttpRequest request), | 27650 onComplete(HttpRequest request), |
| (...skipping 1111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 29614 _position = nextPosition; | 28762 _position = nextPosition; |
| 29615 return true; | 28763 return true; |
| 29616 } | 28764 } |
| 29617 _current = null; | 28765 _current = null; |
| 29618 _position = _array.length; | 28766 _position = _array.length; |
| 29619 return false; | 28767 return false; |
| 29620 } | 28768 } |
| 29621 | 28769 |
| 29622 T get current => _current; | 28770 T get current => _current; |
| 29623 } | 28771 } |
| OLD | NEW |