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

Side by Side Diff: pkg/polymer/lib/src/instance.dart

Issue 132403010: big update to observe, template_binding, polymer (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // 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
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
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
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
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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698