Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of polymer; | 5 part of polymer; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Use this annotation to publish a field as an attribute. For example: | 8 * Use this annotation to publish a field as an attribute. For example: |
| 9 * | 9 * |
| 10 * class MyPlaybackElement extends PolymerElement { | 10 * class MyPlaybackElement extends PolymerElement { |
| 11 * // This will be available as an HTML attribute, for example: | 11 * // This will be available as an HTML attribute, for example: |
| 12 * // <my-playback volume="11"> | 12 * // <my-playback volume="11"> |
| 13 * @published double volume; | 13 * @published double volume; |
| 14 * } | 14 * } |
| 15 */ | 15 */ |
| 16 // TODO(jmesserly): does @published imply @observable or vice versa? | |
| 17 const published = const PublishedProperty(); | 16 const published = const PublishedProperty(); |
| 18 | 17 |
| 19 /** An annotation used to publish a field as an attribute. See [published]. */ | 18 /** An annotation used to publish a field as an attribute. See [published]. */ |
| 20 class PublishedProperty extends ObservableProperty { | 19 class PublishedProperty extends ObservableProperty { |
| 21 const PublishedProperty(); | 20 const PublishedProperty(); |
| 22 } | 21 } |
| 23 | 22 |
| 24 /** | 23 /** |
| 24 * Use this type to observe a property and have the method be called when it | |
| 25 * changes. For example: | |
| 26 * | |
| 27 * @ObserveProperty('foo.bar baz qux') | |
|
Siggi Cherem (dart-lang)
2014/02/03 22:52:48
this is nice. getting a lot closer to what we need
Jennifer Messerly
2014/02/04 00:33:06
yeah ... I don't love it but it's an improvement.
| |
| 28 * validate() { | |
| 29 * // use this.foo.bar, this.baz, and this.qux in validation | |
| 30 * ... | |
| 31 * } | |
| 32 * | |
| 33 * Note that you can observe a property path, and more than a single property | |
| 34 * can be specified in a space-delimited list or as a constant List. | |
| 35 */ | |
| 36 class ObserveProperty { | |
| 37 final _names; | |
| 38 | |
| 39 List<String> get names { | |
|
Siggi Cherem (dart-lang)
2014/02/03 22:52:48
names or paths?
| |
| 40 var n = _names; | |
| 41 // TODO(jmesserly): the bogus '$n' is to workaround a dart2js bug, otherwise | |
| 42 // it generates an incorrect call site. | |
|
Siggi Cherem (dart-lang)
2014/02/03 22:52:48
maybe include the bug #?
Jennifer Messerly
2014/02/04 00:33:06
I will file one, but making progress on this is mo
| |
| 43 if (n is String) return '$n'.split(' '); | |
| 44 if (n is! Iterable) { | |
| 45 throw new UnsupportedError('ObserveProperty takes either an Iterable of ' | |
| 46 'names, or a space separated String, instead of `$n`.'); | |
| 47 } | |
| 48 return n; | |
| 49 } | |
| 50 | |
| 51 const ObserveProperty(this._names); | |
| 52 } | |
| 53 | |
| 54 /** | |
| 25 * The mixin class for Polymer elements. It provides convenience features on top | 55 * The mixin class for Polymer elements. It provides convenience features on top |
| 26 * of the custom elements web standard. | 56 * of the custom elements web standard. |
| 27 * | 57 * |
| 28 * If this class is used as a mixin, | 58 * If this class is used as a mixin, |
| 29 * you must call `polymerCreated()` from the body of your constructor. | 59 * you must call `polymerCreated()` from the body of your constructor. |
| 30 */ | 60 */ |
| 31 abstract class Polymer implements Element, Observable, NodeBindExtension { | 61 abstract class Polymer implements Element, Observable, NodeBindExtension { |
| 32 // Fully ported from revision: | 62 // Fully ported from revision: |
| 33 // https://github.com/Polymer/polymer/blob/b7200854b2441a22ce89f6563963f36c50f 5150d | 63 // https://github.com/Polymer/polymer/blob/37eea00e13b9f86ab21c85a955585e8e423 7e3d2 |
| 34 // | 64 // |
| 35 // src/boot.js (static APIs on "Polymer" object) | 65 // src/boot.js (static APIs on "Polymer" object) |
| 36 // src/instance/attributes.js | 66 // src/instance/attributes.js |
| 37 // src/instance/base.js | 67 // src/instance/base.js |
| 38 // src/instance/events.js | 68 // src/instance/events.js |
| 39 // src/instance/mdv.js | 69 // src/instance/mdv.js |
| 40 // src/instance/properties.js | 70 // src/instance/properties.js |
| 41 // src/instance/style.js | 71 // src/instance/style.js |
| 42 // src/instance/utils.js | 72 // src/instance/utils.js |
| 43 | 73 |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 81 | 111 |
| 82 PolymerDeclaration _declaration; | 112 PolymerDeclaration _declaration; |
| 83 | 113 |
| 84 /** The most derived `<polymer-element>` declaration for this element. */ | 114 /** The most derived `<polymer-element>` declaration for this element. */ |
| 85 PolymerDeclaration get declaration => _declaration; | 115 PolymerDeclaration get declaration => _declaration; |
| 86 | 116 |
| 87 Map<String, StreamSubscription> _observers; | 117 Map<String, StreamSubscription> _observers; |
| 88 bool _unbound; // lazy-initialized | 118 bool _unbound; // lazy-initialized |
| 89 _Job _unbindAllJob; | 119 _Job _unbindAllJob; |
| 90 | 120 |
| 91 StreamSubscription _propertyObserver; | 121 CompoundObserver _propertyObserver; |
| 92 | 122 |
| 93 bool get _elementPrepared => _declaration != null; | 123 bool get _elementPrepared => _declaration != null; |
| 94 | 124 |
| 95 bool get applyAuthorStyles => false; | 125 bool get applyAuthorStyles => false; |
| 96 bool get resetStyleInheritance => false; | 126 bool get resetStyleInheritance => false; |
| 97 bool get alwaysPrepare => false; | 127 bool get alwaysPrepare => false; |
| 98 bool get preventDispose => false; | 128 bool get preventDispose => false; |
| 99 | 129 |
| 100 BindingDelegate syntax = _polymerSyntax; | 130 BindingDelegate syntax = _polymerSyntax; |
| 101 | 131 |
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 210 | 240 |
| 211 var name = elementElement.attributes['name']; | 241 var name = elementElement.attributes['name']; |
| 212 if (name == null) return; | 242 if (name == null) return; |
| 213 _shadowRoots[name] = root; | 243 _shadowRoots[name] = root; |
| 214 } | 244 } |
| 215 | 245 |
| 216 /** | 246 /** |
| 217 * Return a shadow-root template (if desired), override for custom behavior. | 247 * Return a shadow-root template (if desired), override for custom behavior. |
| 218 */ | 248 */ |
| 219 Element fetchTemplate(Element elementElement) => | 249 Element fetchTemplate(Element elementElement) => |
| 220 elementElement.query('template'); | 250 elementElement.querySelector('template'); |
| 221 | 251 |
| 222 /** | 252 /** |
| 223 * Utility function that stamps a `<template>` into light-dom. | 253 * Utility function that stamps a `<template>` into light-dom. |
| 224 */ | 254 */ |
| 225 Node lightFromTemplate(Element template) { | 255 Node lightFromTemplate(Element template) { |
| 226 if (template == null) return null; | 256 if (template == null) return null; |
| 227 // stamp template | 257 // stamp template |
| 228 // which includes parsing and applying MDV bindings before being | 258 // which includes parsing and applying MDV bindings before being |
| 229 // inserted (to avoid {{}} in attribute values) | 259 // inserted (to avoid {{}} in attribute values) |
| 230 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. | 260 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 372 // TypeMirror, but it is currently impossible to get from a TypeMirror to a | 402 // TypeMirror, but it is currently impossible to get from a TypeMirror to a |
| 373 // ClassMirror. | 403 // ClassMirror. |
| 374 Object deserializeValue(String value, Object currentValue, TypeMirror type) => | 404 Object deserializeValue(String value, Object currentValue, TypeMirror type) => |
| 375 deserialize.deserializeValue(value, currentValue, type); | 405 deserialize.deserializeValue(value, currentValue, type); |
| 376 | 406 |
| 377 String serializeValue(Object value) { | 407 String serializeValue(Object value) { |
| 378 if (value == null) return null; | 408 if (value == null) return null; |
| 379 | 409 |
| 380 if (value is bool) { | 410 if (value is bool) { |
| 381 return _toBoolean(value) ? '' : null; | 411 return _toBoolean(value) ? '' : null; |
| 382 } else if (value is String || value is int || value is double) { | 412 } else if (value is String || value is num) { |
| 383 return '$value'; | 413 return '$value'; |
| 384 } | 414 } |
| 385 return null; | 415 return null; |
| 386 } | 416 } |
| 387 | 417 |
| 388 void reflectPropertyToAttribute(Symbol name) { | 418 void reflectPropertyToAttribute(PropertyPath path) { |
| 389 // TODO(sjmiles): consider memoizing this | 419 // TODO(sjmiles): consider memoizing this |
| 390 final self = reflect(this); | |
| 391 // try to intelligently serialize property value | 420 // try to intelligently serialize property value |
| 392 // TODO(jmesserly): cache symbol? | 421 final propValue = path.getValueFrom(this); |
| 393 final propValue = self.getField(name).reflectee; | |
| 394 final serializedValue = serializeValue(propValue); | 422 final serializedValue = serializeValue(propValue); |
| 395 // boolean properties must reflect as boolean attributes | 423 // boolean properties must reflect as boolean attributes |
| 396 if (serializedValue != null) { | 424 if (serializedValue != null) { |
| 397 attributes[MirrorSystem.getName(name)] = serializedValue; | 425 attributes['$path'] = serializedValue; |
|
Siggi Cherem (dart-lang)
2014/02/03 22:52:48
is 'path' here guaranteed to be a single symbol as
Jennifer Messerly
2014/02/04 00:33:06
it is guaranteed in Polymer. added an ArgumentErro
| |
| 398 // TODO(sorvell): we should remove attr for all properties | 426 // TODO(sorvell): we should remove attr for all properties |
| 399 // that have undefined serialization; however, we will need to | 427 // that have undefined serialization; however, we will need to |
| 400 // refine the attr reflection system to achieve this; pica, for example, | 428 // refine the attr reflection system to achieve this; pica, for example, |
| 401 // relies on having inferredType object properties not removed as | 429 // relies on having inferredType object properties not removed as |
| 402 // attrs. | 430 // attrs. |
| 403 } else if (propValue is bool) { | 431 } else if (propValue is bool) { |
| 404 attributes.remove(MirrorSystem.getName(name)); | 432 attributes.remove('$path'); |
| 405 } | 433 } |
| 406 } | 434 } |
| 407 | 435 |
| 408 /** | 436 /** |
| 409 * Creates the document fragment to use for each instance of the custom | 437 * Creates the document fragment to use for each instance of the custom |
| 410 * element, given the `<template>` node. By default this is equivalent to: | 438 * element, given the `<template>` node. By default this is equivalent to: |
| 411 * | 439 * |
| 412 * templateBind(template).createInstance(this, polymerSyntax); | 440 * templateBind(template).createInstance(this, polymerSyntax); |
| 413 * | 441 * |
| 414 * Where polymerSyntax is a singleton `PolymerExpressions` instance from the | 442 * Where polymerSyntax is a singleton `PolymerExpressions` instance from the |
| 415 * [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions ) | 443 * [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions ) |
| 416 * package. | 444 * package. |
| 417 * | 445 * |
| 418 * You can override this method to change the instantiation behavior of the | 446 * You can override this method to change the instantiation behavior of the |
| 419 * template, for example to use a different data-binding syntax. | 447 * template, for example to use a different data-binding syntax. |
| 420 */ | 448 */ |
| 421 DocumentFragment instanceTemplate(Element template) => | 449 DocumentFragment instanceTemplate(Element template) => |
| 422 templateBind(template).createInstance(this, syntax); | 450 templateBind(template).createInstance(this, syntax); |
| 423 | 451 |
| 424 NodeBinding bind(String name, model, [String path]) { | 452 // TODO(jmesserly): Polymer does not seem to implement the oneTime flag |
| 453 // correctly. File bug. | |
| 454 bind(String name, Bindable bindable, {bool oneTime: false}) { | |
| 425 // note: binding is a prepare signal. This allows us to be sure that any | 455 // note: binding is a prepare signal. This allows us to be sure that any |
| 426 // property changes that occur as a result of binding will be observed. | 456 // property changes that occur as a result of binding will be observed. |
| 427 if (!_elementPrepared) prepareElement(); | 457 if (!_elementPrepared) prepareElement(); |
| 428 | 458 |
| 429 var property = propertyForAttribute(name); | 459 var property = propertyForAttribute(name); |
| 430 if (property == null) { | 460 if (property == null) { |
| 431 // Cannot call super.bind because template_binding is its own package | 461 // Cannot call super.bind because template_binding is its own package |
| 432 return nodeBindFallback(this).bind(name, model, path); | 462 return nodeBindFallback(this).bind(name, bindable, oneTime: oneTime); |
| 433 } else { | 463 } else { |
| 434 // clean out the closets | 464 // clean out the closets |
| 435 unbind(name); | 465 unbind(name); |
| 436 // use n-way Polymer binding | 466 // use n-way Polymer binding |
| 437 var observer = bindProperty(property.simpleName, model, path); | 467 var observer = bindProperty(property.simpleName, bindable); |
| 468 | |
| 438 // reflect bound property to attribute when binding | 469 // reflect bound property to attribute when binding |
| 439 // to ensure binding is not left on attribute if property | 470 // to ensure binding is not left on attribute if property |
| 440 // does not update due to not changing. | 471 // does not update due to not changing. |
| 441 // Dart note: we include this patch: | 472 // Dart note: we include this patch: |
| 442 // https://github.com/Polymer/polymer/pull/319 | 473 // https://github.com/Polymer/polymer/pull/319 |
| 443 reflectPropertyToAttribute(property.simpleName); | 474 |
| 475 // TODO(jmesserly): polymer has the path_ in their observer object, should | |
| 476 // we use that too instead of allocating it here? | |
| 477 reflectPropertyToAttribute(new PropertyPath([property.simpleName])); | |
| 444 return bindings[name] = observer; | 478 return bindings[name] = observer; |
| 445 } | 479 } |
| 446 } | 480 } |
| 447 | 481 |
| 448 Map<String, NodeBinding> get bindings => nodeBindFallback(this).bindings; | 482 Map<String, Bindable> get bindings => nodeBindFallback(this).bindings; |
| 449 TemplateInstance get templateInstance => | 483 TemplateInstance get templateInstance => |
| 450 nodeBindFallback(this).templateInstance; | 484 nodeBindFallback(this).templateInstance; |
| 451 | 485 |
| 452 void unbind(String name) => nodeBindFallback(this).unbind(name); | 486 void unbind(String name) => nodeBindFallback(this).unbind(name); |
| 453 | 487 |
| 454 void asyncUnbindAll() { | 488 void asyncUnbindAll() { |
| 455 if (_unbound == true) return; | 489 if (_unbound == true) return; |
| 456 _unbindLog.fine('[$localName] asyncUnbindAll'); | 490 _unbindLog.fine('[$localName] asyncUnbindAll'); |
| 457 _unbindAllJob = _runJob(_unbindAllJob, unbindAll, Duration.ZERO); | 491 _unbindAllJob = _runJob(_unbindAllJob, unbindAll, Duration.ZERO); |
| 458 } | 492 } |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 501 if (node == null) return; | 535 if (node == null) return; |
| 502 | 536 |
| 503 callback(node); | 537 callback(node); |
| 504 for (var child = node.firstChild; child != null; child = child.nextNode) { | 538 for (var child = node.firstChild; child != null; child = child.nextNode) { |
| 505 _forNodeTree(child, callback); | 539 _forNodeTree(child, callback); |
| 506 } | 540 } |
| 507 } | 541 } |
| 508 | 542 |
| 509 /** Set up property observers. */ | 543 /** Set up property observers. */ |
| 510 void observeProperties() { | 544 void observeProperties() { |
| 511 // TODO(jmesserly): we don't have CompoundPathObserver, so this | |
| 512 // implementation is a little bit different. We also don't expose the | |
| 513 // "generateCompoundPathObserver" method. | |
| 514 final observe = _declaration._observe; | 545 final observe = _declaration._observe; |
| 515 final publish = _declaration._publish; | 546 final publish = _declaration._publish; |
| 516 | 547 |
| 517 if (observe != null) { | 548 // TODO(jmesserly): workaround for a dart2js compiler bug |
| 518 for (var name in observe.keys) { | 549 bool hasObserved = observe != null; |
| 519 observeArrayValue(name, reflect(this).getField(name), null); | 550 |
| 551 if (hasObserved || publish != null) { | |
| 552 var o = _propertyObserver = new CompoundObserver(); | |
| 553 if (hasObserved) { | |
| 554 for (var path in observe.keys) { | |
| 555 o.addPath(this, path); | |
| 556 | |
| 557 // TODO(jmesserly): on the Polymer side it doesn't look like they | |
| 558 // will observe arrays unless it is a length == 1 path. | |
| 559 observeArrayValue(path, path.getValueFrom(this), null); | |
| 560 } | |
| 520 } | 561 } |
| 521 } | 562 if (publish != null) { |
| 522 if (observe != null || publish != null) { | 563 for (var path in publish.keys) { |
| 523 // Instead of using CompoundPathObserver, set up a binding using normal | 564 |
| 524 // change records. | 565 if (!hasObserved || !observe.containsKey(path)) { |
| 525 _propertyObserver = changes.listen(notifyPropertyChanges); | 566 o.addPath(this, path); |
| 567 } | |
| 568 } | |
| 569 } | |
| 570 o.open(notifyPropertyChanges); | |
| 526 } | 571 } |
| 527 } | 572 } |
| 528 | 573 |
| 574 | |
| 529 /** Responds to property changes on this element. */ | 575 /** Responds to property changes on this element. */ |
| 530 // Dart note: this takes a list of changes rather than trying to deal with | 576 void notifyPropertyChanges(List newValues, Map oldValues, List paths) { |
| 531 // what CompoundPathObserver would give us. Simpler and probably faster too. | |
| 532 void notifyPropertyChanges(Iterable<ChangeRecord> changes) { | |
| 533 final observe = _declaration._observe; | 577 final observe = _declaration._observe; |
| 534 final publish = _declaration._publish; | 578 final publish = _declaration._publish; |
| 579 final called = new HashSet(); | |
| 535 | 580 |
| 536 // Summarize old and new values, so we only handle each change once. | 581 oldValues.forEach((i, oldValue) { |
| 537 final valuePairs = new Map<Symbol, _PropertyValue>(); | 582 // note: paths is of form [object, path, object, path] |
| 538 for (var c in changes) { | 583 var path = paths[2 * i + 1]; |
| 539 if (c is! PropertyChangeRecord) continue; | 584 if (publish != null && publish.containsKey(path)) { |
| 540 | 585 reflectPropertyToAttribute(path); |
| 541 valuePairs.putIfAbsent(c.name, () => new _PropertyValue(c.oldValue)) | |
| 542 .newValue = c.newValue; | |
| 543 } | |
| 544 | |
| 545 valuePairs.forEach((name, pair) { | |
| 546 if (publish != null && publish.containsKey(name)) { | |
| 547 reflectPropertyToAttribute(name); | |
| 548 } | 586 } |
| 549 if (observe == null) return; | 587 if (observe == null) return; |
| 550 | 588 |
| 551 var method = observe[name]; | 589 var method = observe[path]; |
| 552 if (method != null) { | 590 if (method != null && called.add(method)) { |
| 591 final newValue = newValues[i]; | |
| 553 // observes the value if it is an array | 592 // observes the value if it is an array |
| 554 observeArrayValue(name, pair.newValue, pair.oldValue); | 593 observeArrayValue(path, newValue, oldValue); |
| 555 // TODO(jmesserly): the JS code tries to avoid calling the same method | 594 // Dart note: JS passes "arguments", so we pass along our args. |
| 556 // twice, but I don't see how that is possible. | 595 invokeMethod(method, [oldValue, newValue, newValues, oldValues, paths]); |
| 557 // Dart note: JS also passes "arguments", so we pass all change records. | |
| 558 invokeMethod(method, [pair.oldValue, pair.newValue, changes]); | |
| 559 } | 596 } |
| 560 }); | 597 }); |
| 561 } | 598 } |
| 562 | 599 |
| 563 void observeArrayValue(Symbol name, Object value, Object old) { | 600 void observeArrayValue(PropertyPath name, Object value, Object old) { |
| 564 final observe = _declaration._observe; | 601 final observe = _declaration._observe; |
| 565 if (observe == null) return; | 602 if (observe == null) return; |
| 566 | 603 |
| 567 // we only care if there are registered side-effects | 604 // we only care if there are registered side-effects |
| 568 var callbackName = observe[name]; | 605 var callbackName = observe[name]; |
| 569 if (callbackName == null) return; | 606 if (callbackName == null) return; |
| 570 | 607 |
| 571 // if we are observing the previous value, stop | 608 // if we are observing the previous value, stop |
| 572 if (old is ObservableList) { | 609 if (old is ObservableList) { |
| 573 if (_observeLog.isLoggable(Level.FINE)) { | 610 if (_observeLog.isLoggable(Level.FINE)) { |
| 574 _observeLog.fine('[$localName] observeArrayValue: unregister observer ' | 611 _observeLog.fine('[$localName] observeArrayValue: unregister observer ' |
| 575 '$name'); | 612 '$name'); |
| 576 } | 613 } |
| 577 | 614 |
| 578 unregisterObserver('${MirrorSystem.getName(name)}__array'); | 615 unregisterObserver('${name}__array'); |
| 579 } | 616 } |
| 580 // if the new value is an array, being observing it | 617 // if the new value is an array, being observing it |
| 581 if (value is ObservableList) { | 618 if (value is ObservableList) { |
| 582 if (_observeLog.isLoggable(Level.FINE)) { | 619 if (_observeLog.isLoggable(Level.FINE)) { |
| 583 _observeLog.fine('[$localName] observeArrayValue: register observer ' | 620 _observeLog.fine('[$localName] observeArrayValue: register observer ' |
| 584 '$name'); | 621 '$name'); |
| 585 } | 622 } |
| 586 var sub = value.listChanges.listen((changes) { | 623 var sub = value.listChanges.listen((changes) { |
| 587 invokeMethod(callbackName, [old]); | 624 invokeMethod(callbackName, [old]); |
| 588 }); | 625 }); |
| 589 registerObserver('${MirrorSystem.getName(name)}__array', sub); | 626 registerObserver('${name}__array', sub); |
| 590 } | 627 } |
| 591 } | 628 } |
| 592 | 629 |
| 593 bool unbindProperty(String name) => unregisterObserver(name); | 630 bool unbindProperty(String name) => unregisterObserver(name); |
| 594 | 631 |
| 595 void unbindAllProperties() { | 632 void unbindAllProperties() { |
| 596 if (_propertyObserver != null) { | 633 if (_propertyObserver != null) { |
| 597 _propertyObserver.cancel(); | 634 _propertyObserver.close(); |
| 598 _propertyObserver = null; | 635 _propertyObserver = null; |
| 599 } | 636 } |
| 600 unregisterObservers(); | 637 unregisterObservers(); |
| 601 } | 638 } |
| 602 | 639 |
| 603 /** Bookkeeping observers for memory management. */ | 640 /** Bookkeeping observers for memory management. */ |
| 604 void registerObserver(String name, StreamSubscription sub) { | 641 void registerObserver(String name, StreamSubscription sub) { |
| 605 if (_observers == null) { | 642 if (_observers == null) { |
| 606 _observers = new Map<String, StreamSubscription>(); | 643 _observers = new Map<String, StreamSubscription>(); |
| 607 } | 644 } |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 626 * Bind a [property] in this object to a [path] in model. *Note* in Dart it | 663 * Bind a [property] in this object to a [path] in model. *Note* in Dart it |
| 627 * is necessary to also define the field: | 664 * is necessary to also define the field: |
| 628 * | 665 * |
| 629 * var myProperty; | 666 * var myProperty; |
| 630 * | 667 * |
| 631 * ready() { | 668 * ready() { |
| 632 * super.ready(); | 669 * super.ready(); |
| 633 * bindProperty(#myProperty, this, 'myModel.path.to.otherProp'); | 670 * bindProperty(#myProperty, this, 'myModel.path.to.otherProp'); |
| 634 * } | 671 * } |
| 635 */ | 672 */ |
| 636 // TODO(jmesserly): replace with something more localized, like: | 673 Bindable bindProperty(Symbol name, Bindable bindable) { |
| 637 // @ComputedField('myModel.path.to.otherProp'); | |
| 638 NodeBinding bindProperty(Symbol name, Object model, [String path]) => | |
| 639 // apply Polymer two-way reference binding | |
| 640 _bindProperties(this, name, model, path); | |
| 641 | |
| 642 /** | |
| 643 * bind a property in A to a path in B by converting A[property] to a | |
| 644 * getter/setter pair that accesses B[...path...] | |
| 645 */ | |
| 646 static NodeBinding _bindProperties(Polymer inA, Symbol inProperty, | |
| 647 Object inB, String inPath) { | |
| 648 | |
| 649 if (_bindLog.isLoggable(Level.FINE)) { | |
| 650 _bindLog.fine('[$inB]: bindProperties: [$inPath] to ' | |
| 651 '[${inA.localName}].[$inProperty]'); | |
| 652 } | |
| 653 | |
| 654 // Dart note: normally we only reach this code when we know it's a | 674 // Dart note: normally we only reach this code when we know it's a |
| 655 // property, but if someone uses bindProperty directly they might get a | 675 // property, but if someone uses bindProperty directly they might get a |
| 656 // NoSuchMethodError either from the getField below, or from the setField | 676 // NoSuchMethodError either from the getField below, or from the setField |
| 657 // inside PolymerBinding. That doesn't seem unreasonable, but it's a slight | 677 // inside PolymerBinding. That doesn't seem unreasonable, but it's a slight |
| 658 // difference from Polymer.js behavior. | 678 // difference from Polymer.js behavior. |
| 659 | 679 |
| 680 if (_bindLog.isLoggable(Level.FINE)) { | |
| 681 _bindLog.fine('bindProperty: [$bindable] to [${localName}].[name]'); | |
| 682 } | |
| 683 | |
| 660 // capture A's value if B's value is null or undefined, | 684 // capture A's value if B's value is null or undefined, |
| 661 // otherwise use B's value | 685 // otherwise use B's value |
| 662 var path = new PathObserver(inB, inPath); | 686 // TODO(sorvell): need to review, can do with ObserverTransform |
| 663 if (path.value == null) { | 687 var v = bindable.value; |
| 664 path.value = reflect(inA).getField(inProperty).reflectee; | 688 if (v == null) { |
| 689 bindable.value = reflect(this).getField(name).reflectee; | |
| 665 } | 690 } |
| 666 return new _PolymerBinding(inA, inProperty, inB, inPath); | 691 |
| 692 // TODO(jmesserly): this will create another subscription. | |
| 693 // It would be nice to pool this with our existing listener inside Polymer. | |
|
Siggi Cherem (dart-lang)
2014/02/03 22:52:48
pool -> pull?
Jennifer Messerly
2014/02/04 00:33:06
err, I did mean "pool" as in "a pool of observers"
| |
| 694 return new _PolymerBinding(this, name, bindable); | |
| 667 } | 695 } |
| 668 | 696 |
| 669 /** Attach event listeners on the host (this) element. */ | 697 /** Attach event listeners on the host (this) element. */ |
| 670 void addHostListeners() { | 698 void addHostListeners() { |
| 671 var events = _declaration._eventDelegates; | 699 var events = _declaration._eventDelegates; |
| 672 if (events.isEmpty) return; | 700 if (events.isEmpty) return; |
| 673 | 701 |
| 674 if (_eventsLog.isLoggable(Level.FINE)) { | 702 if (_eventsLog.isLoggable(Level.FINE)) { |
| 675 _eventsLog.fine('[$localName] addHostListeners: $events'); | 703 _eventsLog.fine('[$localName] addHostListeners: $events'); |
| 676 } | 704 } |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 695 if (!event.bubbles) return; | 723 if (!event.bubbles) return; |
| 696 | 724 |
| 697 bool log = _eventsLog.isLoggable(Level.FINE); | 725 bool log = _eventsLog.isLoggable(Level.FINE); |
| 698 if (log) { | 726 if (log) { |
| 699 _eventsLog.fine('>>> [$localName]: hostEventListener(${event.type})'); | 727 _eventsLog.fine('>>> [$localName]: hostEventListener(${event.type})'); |
| 700 } | 728 } |
| 701 | 729 |
| 702 var h = findEventDelegate(event); | 730 var h = findEventDelegate(event); |
| 703 if (h != null) { | 731 if (h != null) { |
| 704 if (log) _eventsLog.fine('[$localName] found host handler name [$h]'); | 732 if (log) _eventsLog.fine('[$localName] found host handler name [$h]'); |
| 705 var detail = event is CustomEvent ? | 733 var detail = event is CustomEvent ? event.detail : null; |
| 706 (event as CustomEvent).detail : null; | |
| 707 // TODO(jmesserly): cache the symbols? | 734 // TODO(jmesserly): cache the symbols? |
| 708 dispatchMethod(this, h, [event, detail, this]); | 735 dispatchMethod(this, h, [event, detail, this]); |
| 709 } | 736 } |
| 710 | 737 |
| 711 if (log) { | 738 if (log) { |
| 712 _eventsLog.fine('<<< [$localName]: hostEventListener(${event.type})'); | 739 _eventsLog.fine('<<< [$localName]: hostEventListener(${event.type})'); |
| 713 } | 740 } |
| 714 } | 741 } |
| 715 | 742 |
| 716 String findEventDelegate(Event event) => | 743 String findEventDelegate(Event event) => |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 738 /** | 765 /** |
| 739 * Bind events via attributes of the form `on-eventName`. This method can be | 766 * Bind events via attributes of the form `on-eventName`. This method can be |
| 740 * use to hooks into the model syntax and adds event listeners as needed. By | 767 * use to hooks into the model syntax and adds event listeners as needed. By |
| 741 * default, binding paths are always method names on the root model, the | 768 * default, binding paths are always method names on the root model, the |
| 742 * custom element in which the node exists. Adding a '@' in the path directs | 769 * custom element in which the node exists. Adding a '@' in the path directs |
| 743 * the event binding to use the model path as the event listener. In both | 770 * the event binding to use the model path as the event listener. In both |
| 744 * cases, the actual listener is attached to a generic method which evaluates | 771 * cases, the actual listener is attached to a generic method which evaluates |
| 745 * the bound path at event execution time. | 772 * the bound path at event execution time. |
| 746 */ | 773 */ |
| 747 // from src/instance/event.js#prepareBinding | 774 // from src/instance/event.js#prepareBinding |
| 748 // TODO(sorvell): we're patching the syntax while evaluating | 775 static PrepareBindingFunction prepareBinding(String path, String name, node) { |
| 749 // event bindings. we'll move this to a better spot when that's done | |
| 750 static PrepareBindingFunction prepareBinding(String path, String name, node, | |
| 751 originalPrepareBinding) { | |
| 752 | |
| 753 // if lhs an event prefix, | |
| 754 if (!_hasEventPrefix(name)) return originalPrepareBinding(path, name, node); | |
| 755 | 776 |
| 756 // provide an event-binding callback. | 777 // provide an event-binding callback. |
| 757 return (model, node) { | 778 return (model, node, oneTime) { |
| 758 if (_eventsLog.isLoggable(Level.FINE)) { | 779 if (_eventsLog.isLoggable(Level.FINE)) { |
| 759 _eventsLog.fine('event: [$node].$name => [$model].$path())'); | 780 _eventsLog.fine('event: [$node].$name => [$model].$path())'); |
| 760 } | 781 } |
| 761 var eventName = _removeEventPrefix(name); | 782 var eventName = _removeEventPrefix(name); |
| 762 // TODO(sigmund): polymer.js dropped event translations. reconcile? | 783 // TODO(sigmund): polymer.js dropped event translations. reconcile? |
| 763 var translated = _eventTranslations[eventName]; | 784 var translated = _eventTranslations[eventName]; |
| 764 eventName = translated != null ? translated : eventName; | 785 eventName = translated != null ? translated : eventName; |
| 765 | 786 |
| 766 // TODO(jmesserly): we need a place to unregister this. See: | 787 return new _EventBindable(node, eventName, model, path); |
| 767 // https://code.google.com/p/dart/issues/detail?id=15574 | |
| 768 node.on[eventName].listen((event) { | |
| 769 var ctrlr = _findController(node); | |
| 770 if (ctrlr is! Polymer) return; | |
| 771 var obj = ctrlr; | |
| 772 var method = path; | |
| 773 if (path[0] == '@') { | |
| 774 obj = model; | |
| 775 method = new PathObserver(model, path.substring(1)).value; | |
| 776 } | |
| 777 var detail = event is CustomEvent ? | |
| 778 (event as CustomEvent).detail : null; | |
| 779 ctrlr.dispatchMethod(obj, method, [event, detail, node]); | |
| 780 }); | |
| 781 | |
| 782 // TODO(jmesserly): this return value is bogus. Returning null here causes | |
| 783 // the wrong thing to happen in template_binding. | |
| 784 return new ObservableBox(); | |
| 785 }; | 788 }; |
| 786 } | 789 } |
| 787 | 790 |
| 788 // TODO(jmesserly): this won't find the correct host unless the ShadowRoot | |
| 789 // was created on a PolymerElement. | |
| 790 static Polymer _findController(Node node) { | |
| 791 while (node.parentNode != null) { | |
| 792 node = node.parentNode; | |
| 793 } | |
| 794 return _shadowHost[node]; | |
| 795 } | |
| 796 | |
| 797 /** Call [methodName] method on this object with [args]. */ | 791 /** Call [methodName] method on this object with [args]. */ |
| 798 invokeMethod(Symbol methodName, List args) => | 792 invokeMethod(Symbol methodName, List args) => |
| 799 _invokeMethod(this, methodName, args); | 793 _invokeMethod(this, methodName, args); |
| 800 | 794 |
| 801 /** Call [methodName] method on [receiver] with [args]. */ | 795 /** Call [methodName] method on [receiver] with [args]. */ |
| 802 static _invokeMethod(receiver, Symbol methodName, List args) { | 796 static _invokeMethod(receiver, Symbol methodName, List args) { |
| 803 // TODO(sigmund): consider making callbacks list all arguments | 797 // TODO(jmesserly): use function type tests instead of mirrors for dispatch. |
|
Siggi Cherem (dart-lang)
2014/02/03 22:52:48
:) exactly
Jennifer Messerly
2014/02/04 00:33:06
Done.
| |
| 804 // explicitly. Unless VM mirrors are optimized first, this will be expensive | |
| 805 // once custom elements extend directly from Element (see issue 11108). | |
| 806 var receiverMirror = reflect(receiver); | 798 var receiverMirror = reflect(receiver); |
| 807 var method = _findMethod(receiverMirror.type, methodName); | 799 var method = _findMethod(receiverMirror.type, methodName); |
| 808 if (method != null) { | 800 if (method != null) { |
| 809 // This will either truncate the argument list or extend it with extra | 801 // This will either truncate the argument list or extend it with extra |
| 810 // null arguments, so it will match the signature. | 802 // null arguments, so it will match the signature. |
| 811 // TODO(sigmund): consider accepting optional arguments when we can tell | 803 // TODO(sigmund): consider accepting optional arguments when we can tell |
| 812 // them appart from named arguments (see http://dartbug.com/11334) | 804 // them appart from named arguments (see http://dartbug.com/11334) |
| 813 args.length = method.parameters.where((p) => !p.isOptional).length; | 805 args.length = method.parameters.where((p) => !p.isOptional).length; |
| 814 } | 806 } |
| 815 return receiverMirror.invoke(methodName, args).reflectee; | 807 return receiverMirror.invoke(methodName, args).reflectee; |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 992 }); | 984 }); |
| 993 } | 985 } |
| 994 } | 986 } |
| 995 | 987 |
| 996 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine | 988 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine |
| 997 // the property on the PolymerElement instance to always get its value from the | 989 // the property on the PolymerElement instance to always get its value from the |
| 998 // model@path. We can't replicate this in Dart so we do the next best thing: | 990 // model@path. We can't replicate this in Dart so we do the next best thing: |
| 999 // listen to changes on both sides and update the values. | 991 // listen to changes on both sides and update the values. |
| 1000 // TODO(jmesserly): our approach leads to race conditions in the bindings. | 992 // TODO(jmesserly): our approach leads to race conditions in the bindings. |
| 1001 // See http://code.google.com/p/dart/issues/detail?id=13567 | 993 // See http://code.google.com/p/dart/issues/detail?id=13567 |
| 1002 class _PolymerBinding extends NodeBinding { | 994 class _PolymerBinding extends Bindable { |
| 1003 final InstanceMirror _target; | 995 final InstanceMirror _target; |
| 1004 final Symbol _property; | 996 final Symbol _property; |
| 997 final Bindable _bindable; | |
| 1005 StreamSubscription _sub; | 998 StreamSubscription _sub; |
| 1006 Object _lastValue; | 999 Object _lastValue; |
| 1007 | 1000 |
| 1008 _PolymerBinding(Polymer node, Symbol property, model, path) | 1001 _PolymerBinding(Polymer node, this._property, this._bindable) |
| 1009 : _target = reflect(node), | 1002 : _target = reflect(node) { |
| 1010 _property = property, | |
| 1011 super(node, MirrorSystem.getName(property), model, path) { | |
| 1012 | 1003 |
| 1013 _sub = node.changes.listen(_propertyValueChanged); | 1004 _sub = node.changes.listen(_propertyValueChanged); |
| 1005 _updateNode(open(_updateNode)); | |
| 1014 } | 1006 } |
| 1015 | 1007 |
| 1016 void close() { | 1008 void _updateNode(newValue) { |
| 1017 if (closed) return; | |
| 1018 _sub.cancel(); | |
| 1019 super.close(); | |
| 1020 } | |
| 1021 | |
| 1022 void valueChanged(newValue) { | |
| 1023 _lastValue = newValue; | 1009 _lastValue = newValue; |
| 1024 _target.setField(_property, newValue); | 1010 _target.setField(_property, newValue); |
| 1025 } | 1011 } |
| 1026 | 1012 |
| 1027 void _propertyValueChanged(List<ChangeRecord> records) { | 1013 void _propertyValueChanged(List<ChangeRecord> records) { |
| 1028 for (var record in records) { | 1014 for (var record in records) { |
| 1029 if (record is PropertyChangeRecord && record.name == _property) { | 1015 if (record is PropertyChangeRecord && record.name == _property) { |
| 1030 final newValue = _target.getField(_property).reflectee; | 1016 final newValue = _target.getField(_property).reflectee; |
| 1031 if (!identical(_lastValue, newValue)) { | 1017 if (!identical(_lastValue, newValue)) { |
| 1032 value = newValue; | 1018 this.value = newValue; |
| 1033 } | 1019 } |
| 1034 return; | 1020 return; |
| 1035 } | 1021 } |
| 1036 } | 1022 } |
| 1037 } | 1023 } |
| 1024 | |
| 1025 open(callback(value)) => _bindable.open(callback); | |
| 1026 get value => _bindable.value; | |
| 1027 set value(newValue) => _bindable.value = newValue; | |
| 1028 | |
| 1029 void close() { | |
| 1030 if (_sub != null) { | |
| 1031 _sub.cancel(); | |
| 1032 _sub = null; | |
| 1033 } | |
| 1034 _bindable.close(); | |
| 1035 } | |
| 1038 } | 1036 } |
| 1039 | 1037 |
| 1040 bool _toBoolean(value) => null != value && false != value; | 1038 bool _toBoolean(value) => null != value && false != value; |
| 1041 | 1039 |
| 1042 TypeMirror _propertyType(DeclarationMirror property) => | 1040 TypeMirror _propertyType(DeclarationMirror property) => |
| 1043 property is VariableMirror | 1041 property is VariableMirror ? property.type |
| 1044 ? (property as VariableMirror).type | |
| 1045 : (property as MethodMirror).returnType; | 1042 : (property as MethodMirror).returnType; |
| 1046 | 1043 |
| 1047 TypeMirror _inferPropertyType(Object value, DeclarationMirror property) { | 1044 TypeMirror _inferPropertyType(Object value, DeclarationMirror property) { |
| 1048 var type = _propertyType(property); | 1045 var type = _propertyType(property); |
| 1049 if (type.qualifiedName == #dart.core.Object || | 1046 if (type.qualifiedName == #dart.core.Object || |
| 1050 type.qualifiedName == #dynamic) { | 1047 type.qualifiedName == #dynamic) { |
| 1051 // Attempt to infer field type from the default value. | 1048 // Attempt to infer field type from the default value. |
| 1052 if (value != null) { | 1049 if (value != null) { |
| 1053 Type t = _getCoreType(value); | 1050 Type t = _getCoreType(value); |
| 1054 if (t != null) return reflectClass(t); | 1051 if (t != null) return reflectClass(t); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1088 polymerCreated(); | 1085 polymerCreated(); |
| 1089 } | 1086 } |
| 1090 } | 1087 } |
| 1091 | 1088 |
| 1092 class _PropertyValue { | 1089 class _PropertyValue { |
| 1093 Object oldValue, newValue; | 1090 Object oldValue, newValue; |
| 1094 _PropertyValue(this.oldValue); | 1091 _PropertyValue(this.oldValue); |
| 1095 } | 1092 } |
| 1096 | 1093 |
| 1097 class _PolymerExpressionsWithEventDelegate extends PolymerExpressions { | 1094 class _PolymerExpressionsWithEventDelegate extends PolymerExpressions { |
| 1098 prepareBinding(String path, name, node) => | 1095 prepareBinding(String path, name, node) { |
| 1099 Polymer.prepareBinding(path, name, node, super.prepareBinding); | 1096 if (_hasEventPrefix(name)) return Polymer.prepareBinding(path, name, node); |
| 1097 return super.prepareBinding(path, name, node); | |
| 1098 } | |
| 1100 } | 1099 } |
| 1100 | |
| 1101 class _EventBindable extends Bindable { | |
| 1102 final Node _node; | |
| 1103 final String _eventName; | |
| 1104 final _model; | |
| 1105 final String _path; | |
| 1106 StreamSubscription _sub; | |
| 1107 | |
| 1108 _EventBindable(this._node, this._eventName, this._model, this._path); | |
| 1109 | |
| 1110 _listener(event) { | |
| 1111 var ctrlr = _findController(_node); | |
| 1112 if (ctrlr is! Polymer) return; | |
| 1113 var obj = ctrlr; | |
| 1114 var method = _path; | |
| 1115 if (_path.startsWith('@')) { | |
| 1116 obj = _model; | |
| 1117 method = new PropertyPath(_path.substring(1)).getValueFrom(_model); | |
| 1118 } | |
| 1119 var detail = event is CustomEvent ? | |
| 1120 (event as CustomEvent).detail : null; | |
| 1121 ctrlr.dispatchMethod(obj, method, [event, detail, _node]); | |
| 1122 } | |
| 1123 | |
| 1124 // TODO(jmesserly): this won't find the correct host unless the ShadowRoot | |
| 1125 // was created on a PolymerElement. | |
| 1126 static Polymer _findController(Node node) { | |
| 1127 while (node.parentNode != null) { | |
| 1128 node = node.parentNode; | |
| 1129 } | |
| 1130 return _shadowHost[node]; | |
| 1131 } | |
| 1132 | |
| 1133 get value => null; | |
| 1134 | |
| 1135 open(callback) { | |
| 1136 _sub = _node.on[_eventName].listen(_listener); | |
| 1137 } | |
| 1138 | |
| 1139 close() { | |
| 1140 if (_sub != null) { | |
| 1141 if (_eventsLog.isLoggable(Level.FINE)) { | |
| 1142 _eventsLog.fine( | |
| 1143 'event.remove: [$_node].$_eventName => [$_model].$_path())'); | |
| 1144 } | |
| 1145 _sub.cancel(); | |
| 1146 _sub = null; | |
| 1147 } | |
| 1148 } | |
| 1149 } | |
| OLD | NEW |