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? | 16 // TODO(jmesserly): does @published imply @observable or vice versa? |
| 17 const published = const PublishedProperty(); | 17 const published = const PublishedProperty(); |
| 18 | 18 |
| 19 /** An annotation used to publish a field as an attribute. See [published]. */ | 19 /** An annotation used to publish a field as an attribute. See [published]. */ |
| 20 class PublishedProperty extends ObservableProperty { | 20 class PublishedProperty extends ObservableProperty { |
| 21 const PublishedProperty(); | 21 const PublishedProperty(); |
| 22 } | 22 } |
| 23 | 23 |
| 24 /** | 24 /** |
| 25 * The mixin class for Polymer elements. It provides convenience features on top | 25 * The mixin class for Polymer elements. It provides convenience features on top |
| 26 * of the custom elements web standard. | 26 * of the custom elements web standard. |
| 27 */ | 27 */ |
| 28 abstract class Polymer implements Element, Observable, NodeBindExtension { | 28 abstract class Polymer implements Element, Observable, NodeBindExtension { |
| 29 // Fully ported from revision: | 29 // Fully ported from revision: |
| 30 // https://github.com/Polymer/polymer/blob/00e2982c78fcd396adaebff3118e94029a2 b9fb0 | 30 // https://github.com/Polymer/polymer/blob/b7200854b2441a22ce89f6563963f36c50f 5150d |
| 31 // | 31 // |
| 32 // src/boot.js (static APIs on "Polymer" object) | 32 // src/boot.js (static APIs on "Polymer" object) |
| 33 // src/instance/attributes.js | 33 // src/instance/attributes.js |
| 34 // src/instance/base.js | 34 // src/instance/base.js |
| 35 // src/instance/events.js | 35 // src/instance/events.js |
| 36 // src/instance/mdv.js | 36 // src/instance/mdv.js |
| 37 // src/instance/properties.js | 37 // src/instance/properties.js |
| 38 // src/instance/style.js | 38 // src/instance/style.js |
| 39 // src/instance/utils.js | 39 // src/instance/utils.js |
| 40 | 40 |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 56 static void register(String name, [Type type]) { | 56 static void register(String name, [Type type]) { |
| 57 //console.log('registering [' + name + ']'); | 57 //console.log('registering [' + name + ']'); |
| 58 if (type == null) type = PolymerElement; | 58 if (type == null) type = PolymerElement; |
| 59 | 59 |
| 60 _typesByName[name] = type; | 60 _typesByName[name] = type; |
| 61 // notify the registrar waiting for 'name', if any | 61 // notify the registrar waiting for 'name', if any |
| 62 _notifyType(name); | 62 _notifyType(name); |
| 63 } | 63 } |
| 64 | 64 |
| 65 /// The one syntax to rule them all. | 65 /// The one syntax to rule them all. |
| 66 static final BindingDelegate _polymerSyntax = new PolymerExpressions(); | 66 static final BindingDelegate _polymerSyntax = |
| 67 new _PolymerExpressionsWithEventDelegate(); | |
| 67 | 68 |
| 68 static int _preparingElements = 0; | 69 static int _preparingElements = 0; |
| 69 | 70 |
| 70 static final Completer _ready = new Completer(); | 71 static final Completer _ready = new Completer(); |
| 71 | 72 |
| 72 /** | 73 /** |
| 73 * Future indicating that the Polymer library has been loaded and is ready | 74 * Future indicating that the Polymer library has been loaded and is ready |
| 74 * for use. | 75 * for use. |
| 75 */ | 76 */ |
| 76 static Future get onReady => _ready.future; | 77 static Future get onReady => _ready.future; |
| 77 | 78 |
| 78 PolymerDeclaration _declaration; | 79 PolymerDeclaration _declaration; |
| 79 | 80 |
| 80 /** The most derived `<polymer-element>` declaration for this element. */ | 81 /** The most derived `<polymer-element>` declaration for this element. */ |
| 81 PolymerDeclaration get declaration => _declaration; | 82 PolymerDeclaration get declaration => _declaration; |
| 82 | 83 |
| 83 Map<String, StreamSubscription> _observers; | 84 Map<String, StreamSubscription> _observers; |
| 84 bool _unbound; // lazy-initialized | 85 bool _unbound; // lazy-initialized |
| 85 Job _unbindAllJob; | 86 Job _unbindAllJob; |
| 86 | 87 |
| 87 StreamSubscription _propertyObserver; | 88 StreamSubscription _propertyObserver; |
| 88 | 89 |
| 89 bool get _elementPrepared => _declaration != null; | 90 bool get _elementPrepared => _declaration != null; |
| 90 | 91 |
| 91 bool get applyAuthorStyles => false; | 92 bool get applyAuthorStyles => false; |
| 92 bool get resetStyleInheritance => false; | 93 bool get resetStyleInheritance => false; |
| 93 bool get alwaysPrepare => false; | 94 bool get alwaysPrepare => false; |
| 94 bool get preventDispose => false; | 95 bool get preventDispose => false; |
| 95 | 96 |
| 97 BindingDelegate syntax = _polymerSyntax; | |
| 98 | |
| 96 /** | 99 /** |
| 97 * Shadow roots created by [parseElement]. See [getShadowRoot]. | 100 * Shadow roots created by [parseElement]. See [getShadowRoot]. |
| 98 */ | 101 */ |
| 99 final _shadowRoots = new HashMap<String, ShadowRoot>(); | 102 final _shadowRoots = new HashMap<String, ShadowRoot>(); |
| 100 | 103 |
| 101 /** Map of items in the shadow root(s) by their [Element.id]. */ | 104 /** Map of items in the shadow root(s) by their [Element.id]. */ |
| 102 // TODO(jmesserly): various issues: | 105 // TODO(jmesserly): various issues: |
| 103 // * wrap in UnmodifiableMapView? | 106 // * wrap in UnmodifiableMapView? |
| 104 // * should we have an object that implements noSuchMethod? | 107 // * should we have an object that implements noSuchMethod? |
| 105 // * should the map have a key order (e.g. LinkedHash or SplayTree)? | 108 // * should the map have a key order (e.g. LinkedHash or SplayTree)? |
| (...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 272 root.append(dom); | 275 root.append(dom); |
| 273 // perform post-construction initialization tasks on shadow root | 276 // perform post-construction initialization tasks on shadow root |
| 274 shadowRootReady(root, template); | 277 shadowRootReady(root, template); |
| 275 // return the created shadow root | 278 // return the created shadow root |
| 276 return root; | 279 return root; |
| 277 } | 280 } |
| 278 | 281 |
| 279 void shadowRootReady(Node root, Element template) { | 282 void shadowRootReady(Node root, Element template) { |
| 280 // locate nodes with id and store references to them in this.$ hash | 283 // locate nodes with id and store references to them in this.$ hash |
| 281 marshalNodeReferences(root); | 284 marshalNodeReferences(root); |
| 282 // add local events of interest... | |
| 283 addInstanceListeners(root, template); | |
| 284 // TODO(jmesserly): port this | 285 // TODO(jmesserly): port this |
| 285 // set up pointer gestures | 286 // set up pointer gestures |
| 286 // PointerGestures.register(root); | 287 // PointerGestures.register(root); |
| 287 } | 288 } |
| 288 | 289 |
| 289 /** Locate nodes with id and store references to them in [$] hash. */ | 290 /** Locate nodes with id and store references to them in [$] hash. */ |
| 290 void marshalNodeReferences(Node root) { | 291 void marshalNodeReferences(Node root) { |
| 291 if (root == null) return; | 292 if (root == null) return; |
| 292 for (var n in (root as dynamic).queryAll('[id]')) { | 293 for (var n in (root as dynamic).queryAll('[id]')) { |
| 293 $[n.id] = n; | 294 $[n.id] = n; |
| (...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 413 * templateBind(template).createInstance(this, polymerSyntax); | 414 * templateBind(template).createInstance(this, polymerSyntax); |
| 414 * | 415 * |
| 415 * Where polymerSyntax is a singleton `PolymerExpressions` instance from the | 416 * Where polymerSyntax is a singleton `PolymerExpressions` instance from the |
| 416 * [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions ) | 417 * [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions ) |
| 417 * package. | 418 * package. |
| 418 * | 419 * |
| 419 * You can override this method to change the instantiation behavior of the | 420 * You can override this method to change the instantiation behavior of the |
| 420 * template, for example to use a different data-binding syntax. | 421 * template, for example to use a different data-binding syntax. |
| 421 */ | 422 */ |
| 422 DocumentFragment instanceTemplate(Element template) => | 423 DocumentFragment instanceTemplate(Element template) => |
| 423 templateBind(template).createInstance(this, _polymerSyntax); | 424 templateBind(template).createInstance(this, syntax); |
| 424 | 425 |
| 425 NodeBinding createBinding(String name, model, String path) => | 426 NodeBinding createBinding(String name, model, String path) => |
| 426 nodeBindFallback(this).createBinding(name, model, path); | 427 nodeBindFallback(this).createBinding(name, model, path); |
| 427 | 428 |
| 428 NodeBinding bind(String name, model, String path) { | 429 NodeBinding bind(String name, model, String path) { |
| 429 // note: binding is a prepare signal. This allows us to be sure that any | 430 // note: binding is a prepare signal. This allows us to be sure that any |
| 430 // property changes that occur as a result of binding will be observed. | 431 // property changes that occur as a result of binding will be observed. |
| 431 if (!_elementPrepared) prepareElement(); | 432 if (!_elementPrepared) prepareElement(); |
| 432 | 433 |
| 433 var property = propertyForAttribute(name); | 434 var property = propertyForAttribute(name); |
| 434 if (property != null) { | 435 if (property == null) { |
| 436 // Cannot call super.bind because template_binding is its own package | |
| 437 return nodeBindFallback(this).bind(name, model, path); | |
| 438 } else { | |
| 439 // clean out the closets | |
| 435 unbind(name); | 440 unbind(name); |
| 436 // use n-way Polymer binding | 441 // use n-way Polymer binding |
| 437 var observer = bindProperty(property.simpleName, model, path); | 442 var observer = bindProperty(property.simpleName, model, path); |
| 438 // reflect bound property to attribute when binding | 443 // reflect bound property to attribute when binding |
| 439 // to ensure binding is not left on attribute if property | 444 // to ensure binding is not left on attribute if property |
| 440 // does not update due to not changing. | 445 // does not update due to not changing. |
| 441 // Dart note: we include this patch: | 446 // Dart note: we include this patch: |
| 442 // https://github.com/Polymer/polymer/pull/319 | 447 // https://github.com/Polymer/polymer/pull/319 |
| 443 reflectPropertyToAttribute(property.simpleName); | 448 reflectPropertyToAttribute(property.simpleName); |
| 444 return bindings[name] = observer; | 449 return bindings[name] = observer; |
| 445 } else { | |
| 446 // Cannot call super.bind because template_binding is its own package | |
| 447 return nodeBindFallback(this).bind(name, model, path); | |
| 448 } | 450 } |
| 449 } | 451 } |
| 450 | 452 |
| 451 Map<String, NodeBinding> get bindings => nodeBindFallback(this).bindings; | 453 Map<String, NodeBinding> get bindings => nodeBindFallback(this).bindings; |
| 452 | 454 |
| 453 void unbind(String name) => nodeBindFallback(this).unbind(name); | 455 void unbind(String name) => nodeBindFallback(this).unbind(name); |
| 454 | 456 |
| 455 void asyncUnbindAll() { | 457 void asyncUnbindAll() { |
| 456 if (_unbound == true) return; | 458 if (_unbound == true) return; |
| 457 _unbindLog.fine('[$localName] asyncUnbindAll'); | 459 _unbindLog.fine('[$localName] asyncUnbindAll'); |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 549 } | 551 } |
| 550 if (observe == null) return; | 552 if (observe == null) return; |
| 551 | 553 |
| 552 var method = observe[name]; | 554 var method = observe[name]; |
| 553 if (method != null) { | 555 if (method != null) { |
| 554 // observes the value if it is an array | 556 // observes the value if it is an array |
| 555 observeArrayValue(name, pair.newValue, pair.oldValue); | 557 observeArrayValue(name, pair.newValue, pair.oldValue); |
| 556 // TODO(jmesserly): the JS code tries to avoid calling the same method | 558 // TODO(jmesserly): the JS code tries to avoid calling the same method |
| 557 // twice, but I don't see how that is possible. | 559 // twice, but I don't see how that is possible. |
| 558 // Dart note: JS also passes "arguments", so we pass all change records. | 560 // Dart note: JS also passes "arguments", so we pass all change records. |
| 559 invokeMethod(method, [pair.oldValue, pair.newValue, changes]); | 561 invokeMethod(this, method, [pair.oldValue, pair.newValue, changes]); |
| 560 } | 562 } |
| 561 }); | 563 }); |
| 562 } | 564 } |
| 563 | 565 |
| 564 void observeArrayValue(Symbol name, Object value, Object old) { | 566 void observeArrayValue(Symbol name, Object value, Object old) { |
| 565 final observe = _declaration._observe; | 567 final observe = _declaration._observe; |
| 566 if (observe == null) return; | 568 if (observe == null) return; |
| 567 | 569 |
| 568 // we only care if there are registered side-effects | 570 // we only care if there are registered side-effects |
| 569 var callbackName = observe[name]; | 571 var callbackName = observe[name]; |
| 570 if (callbackName == null) return; | 572 if (callbackName == null) return; |
| 571 | 573 |
| 572 // if we are observing the previous value, stop | 574 // if we are observing the previous value, stop |
| 573 if (old is ObservableList) { | 575 if (old is ObservableList) { |
| 574 if (_observeLog.isLoggable(Level.FINE)) { | 576 if (_observeLog.isLoggable(Level.FINE)) { |
| 575 _observeLog.fine('[$localName] observeArrayValue: unregister observer ' | 577 _observeLog.fine('[$localName] observeArrayValue: unregister observer ' |
| 576 '$name'); | 578 '$name'); |
| 577 } | 579 } |
| 578 | 580 |
| 579 unregisterObserver('${MirrorSystem.getName(name)}__array'); | 581 unregisterObserver('${MirrorSystem.getName(name)}__array'); |
| 580 } | 582 } |
| 581 // if the new value is an array, being observing it | 583 // if the new value is an array, being observing it |
| 582 if (value is ObservableList) { | 584 if (value is ObservableList) { |
| 583 if (_observeLog.isLoggable(Level.FINE)) { | 585 if (_observeLog.isLoggable(Level.FINE)) { |
| 584 _observeLog.fine('[$localName] observeArrayValue: register observer ' | 586 _observeLog.fine('[$localName] observeArrayValue: register observer ' |
| 585 '$name'); | 587 '$name'); |
| 586 } | 588 } |
| 587 var sub = (value as ObservableList).changes.listen((changes) { | 589 var sub = (value as ObservableList).changes.listen((changes) { |
| 588 invokeMethod(callbackName, [old]); | 590 invokeMethod(this, callbackName, [old]); |
| 589 }); | 591 }); |
| 590 registerObserver('${MirrorSystem.getName(name)}__array', sub); | 592 registerObserver('${MirrorSystem.getName(name)}__array', sub); |
| 591 } | 593 } |
| 592 } | 594 } |
| 593 | 595 |
| 594 void unbindProperty(String name) => unregisterObserver(name); | 596 void unbindProperty(String name) => unregisterObserver(name); |
| 595 | 597 |
| 596 void unbindAllProperties() { | 598 void unbindAllProperties() { |
| 597 if (_propertyObserver != null) { | 599 if (_propertyObserver != null) { |
| 598 _propertyObserver.cancel(); | 600 _propertyObserver.cancel(); |
| 599 _propertyObserver = null; | 601 _propertyObserver = null; |
| 600 } | 602 } |
| 601 unregisterObservers(); | 603 unregisterObservers(); |
| 602 } | 604 } |
| 603 | 605 |
| 604 /** Bookkeeping observers for memory management. */ | 606 /** Bookkeeping observers for memory management. */ |
| 605 void registerObserver(String name, StreamSubscription sub) { | 607 void registerObserver(String name, StreamSubscription sub) { |
| 606 if (_observers == null) { | 608 if (_observers == null) { |
| 607 _observers = new Map<String, StreamSubscription>(); | 609 _observers = new Map<String, StreamSubscription>(); |
| 608 } | 610 } |
| 609 _observers[name] = sub; | 611 _observers[name] = sub; |
| 610 } | 612 } |
| 611 | 613 |
| 612 bool unregisterObserver(String name) { | 614 bool unregisterObserver(String name) { |
| 613 var sub = _observers.remove(name); | 615 var sub = _observers.remove(name); |
| 614 if (sub == null) return false; | 616 if (sub == null) return false; |
| 615 subl.cancel(); | 617 sub.cancel(); |
|
Jennifer Messerly
2013/10/25 22:00:41
ouch! nice catch
Siggi Cherem (dart-lang)
2013/10/26 00:02:35
dart2js warning =)
| |
| 616 return true; | 618 return true; |
| 617 } | 619 } |
| 618 | 620 |
| 619 void unregisterObservers() { | 621 void unregisterObservers() { |
| 620 if (_observers == null) return; | 622 if (_observers == null) return; |
| 621 for (var sub in _observers.values) sub.cancel(); | 623 for (var sub in _observers.values) sub.cancel(); |
| 622 _observers.clear(); | 624 _observers.clear(); |
| 623 _observers = null; | 625 _observers = null; |
| 624 } | 626 } |
| 625 | 627 |
| 626 /** | 628 /** |
| 627 * Bind a [property] in this object to a [path] in model. *Note* in Dart it | 629 * Bind a [property] in this object to a [path] in model. *Note* in Dart it |
| 628 * is necessary to also define the field: | 630 * is necessary to also define the field: |
| 629 * | 631 * |
| 630 * var myProperty; | 632 * var myProperty; |
| 631 * | 633 * |
| 632 * created() { | 634 * ready() { |
| 633 * super.created(); | 635 * super.ready(); |
| 634 * bindProperty(#myProperty, this, 'myModel.path.to.otherProp'); | 636 * bindProperty(#myProperty, this, 'myModel.path.to.otherProp'); |
| 635 * } | 637 * } |
| 636 */ | 638 */ |
| 637 // TODO(jmesserly): replace with something more localized, like: | 639 // TODO(jmesserly): replace with something more localized, like: |
| 638 // @ComputedField('myModel.path.to.otherProp'); | 640 // @ComputedField('myModel.path.to.otherProp'); |
| 639 NodeBinding bindProperty(Symbol name, Object model, String path) => | 641 NodeBinding bindProperty(Symbol name, Object model, String path) => |
| 640 // apply Polymer two-way reference binding | 642 // apply Polymer two-way reference binding |
| 641 _bindProperties(this, name, model, path); | 643 _bindProperties(this, name, model, path); |
| 642 | 644 |
| 643 /** | 645 /** |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 671 void addHostListeners() { | 673 void addHostListeners() { |
| 672 var events = _declaration._eventDelegates; | 674 var events = _declaration._eventDelegates; |
| 673 if (events.isEmpty) return; | 675 if (events.isEmpty) return; |
| 674 | 676 |
| 675 if (_eventsLog.isLoggable(Level.FINE)) { | 677 if (_eventsLog.isLoggable(Level.FINE)) { |
| 676 _eventsLog.fine('[$localName] addHostListeners: $events'); | 678 _eventsLog.fine('[$localName] addHostListeners: $events'); |
| 677 } | 679 } |
| 678 addNodeListeners(this, events.keys, hostEventListener); | 680 addNodeListeners(this, events.keys, hostEventListener); |
| 679 } | 681 } |
| 680 | 682 |
| 681 /** Attach event listeners inside a shadow [root]. */ | |
| 682 void addInstanceListeners(Node root, Element template) { | |
| 683 var templateDelegates = _declaration._templateDelegates; | |
| 684 if (templateDelegates == null) return; | |
| 685 var events = templateDelegates[template]; | |
| 686 if (events == null) return; | |
| 687 | |
| 688 if (_eventsLog.isLoggable(Level.FINE)) { | |
| 689 _eventsLog.fine('[$localName] addInstanceListeners: $events'); | |
| 690 } | |
| 691 addNodeListeners(root, events, instanceEventListener); | |
| 692 } | |
| 693 | |
| 694 void addNodeListeners(Node node, Iterable<String> events, | 683 void addNodeListeners(Node node, Iterable<String> events, |
| 695 void listener(Event e)) { | 684 void listener(Event e)) { |
| 696 | 685 |
| 697 for (var name in events) { | 686 for (var name in events) { |
| 698 addNodeListener(node, name, listener); | 687 addNodeListener(node, name, listener); |
| 699 } | 688 } |
| 700 } | 689 } |
| 701 | 690 |
| 702 void addNodeListener(Node node, String event, void listener(Event e)) { | 691 void addNodeListener(Node node, String event, void listener(Event e)) { |
| 703 node.on[event].listen(listener); | 692 node.on[event].listen(listener); |
| 704 } | 693 } |
| 705 | 694 |
| 706 void hostEventListener(Event event) { | 695 void hostEventListener(Event event) { |
| 707 // TODO(jmesserly): do we need this check? It was using cancelBubble, see: | 696 // TODO(jmesserly): do we need this check? It was using cancelBubble, see: |
| 708 // https://github.com/Polymer/polymer/issues/292 | 697 // https://github.com/Polymer/polymer/issues/292 |
| 709 if (!event.bubbles) return; | 698 if (!event.bubbles) return; |
| 710 | 699 |
| 711 bool log = _eventsLog.isLoggable(Level.FINE); | 700 bool log = _eventsLog.isLoggable(Level.FINE); |
| 712 if (log) { | 701 if (log) { |
| 713 _eventsLog.fine('>>> [$localName]: hostEventListener(${event.type})'); | 702 _eventsLog.fine('>>> [$localName]: hostEventListener(${event.type})'); |
| 714 } | 703 } |
| 715 | 704 |
| 716 var h = findEventDelegate(event); | 705 var h = findEventDelegate(event); |
| 717 if (h != null) { | 706 if (h != null) { |
| 718 if (log) _eventsLog.fine('[$localName] found host handler name [$h]'); | 707 if (log) _eventsLog.fine('[$localName] found host handler name [$h]'); |
| 719 var detail = event is CustomEvent ? | 708 var detail = event is CustomEvent ? |
| 720 (event as CustomEvent).detail : null; | 709 (event as CustomEvent).detail : null; |
| 721 // TODO(jmesserly): cache the symbols? | 710 // TODO(jmesserly): cache the symbols? |
| 722 dispatchMethod(new Symbol(h), [event, detail, this]); | 711 dispatchMethod(this, h, [event, detail, this]); |
| 723 } | 712 } |
| 724 | 713 |
| 725 if (log) { | 714 if (log) { |
| 726 _eventsLog.fine('<<< [$localName]: hostEventListener(${event.type})'); | 715 _eventsLog.fine('<<< [$localName]: hostEventListener(${event.type})'); |
| 727 } | 716 } |
| 728 } | 717 } |
| 729 | 718 |
| 730 String findEventDelegate(Event event) => | 719 String findEventDelegate(Event event) => |
| 731 _declaration._eventDelegates[_eventNameFromType(event.type)]; | 720 _declaration._eventDelegates[_eventNameFromType(event.type)]; |
| 732 | 721 |
| 733 /** Call [methodName] method on [this] with [args], if the method exists. */ | 722 /** |
| 734 // TODO(jmesserly): I removed the [node] argument as it was unused. Reconcile. | 723 * Calls [methodOrCallback] with [args] if it is a closure, otherwise, treat |
| 735 void dispatchMethod(Symbol methodName, List args) { | 724 * it as a method name in [object], and invoke it. |
| 725 */ | |
| 726 void dispatchMethod(object, callbackOrMethod, List args) { | |
| 736 bool log = _eventsLog.isLoggable(Level.FINE); | 727 bool log = _eventsLog.isLoggable(Level.FINE); |
| 737 if (log) _eventsLog.fine('>>> [$localName]: dispatch $methodName'); | 728 if (log) _eventsLog.fine('>>> [$localName]: dispatch $callbackOrMethod'); |
| 738 | 729 |
| 739 invokeMethod(methodName, args); | 730 if (callbackOrMethod is Function) { |
| 731 Function.apply(callbackOrMethod, args); | |
| 732 } else if (callbackOrMethod is String) { | |
| 733 invokeMethod(object, new Symbol(callbackOrMethod), args); | |
| 734 } else { | |
| 735 _eventsLog.warning('invalid callback'); | |
| 736 } | |
| 740 | 737 |
| 741 if (log) _eventsLog.info('<<< [$localName]: dispatch $methodName'); | 738 if (log) _eventsLog.info('<<< [$localName]: dispatch $callbackOrMethod'); |
| 742 } | 739 } |
| 743 | 740 |
| 744 invokeMethod(Symbol methodName, List args) { | 741 /** Call [methodName] method on [receiver] with [args]. */ |
| 742 static invokeMethod(receiver, Symbol methodName, List args) { | |
|
Jennifer Messerly
2013/10/25 22:00:41
fyi -- this is an instance method in Polymer:
htt
Siggi Cherem (dart-lang)
2013/10/26 00:02:35
Done.
| |
| 745 // TODO(sigmund): consider making callbacks list all arguments | 743 // TODO(sigmund): consider making callbacks list all arguments |
| 746 // explicitly. Unless VM mirrors are optimized first, this will be expensive | 744 // explicitly. Unless VM mirrors are optimized first, this will be expensive |
| 747 // once custom elements extend directly from Element (see issue 11108). | 745 // once custom elements extend directly from Element (see issue 11108). |
| 748 var self = reflect(this); | 746 var receiverMirror = reflect(receiver); |
| 749 var method = self.type.methods[methodName]; | 747 var method = receiverMirror.type.methods[methodName]; |
| 750 if (method != null) { | 748 if (method != null) { |
| 751 // This will either truncate the argument list or extend it with extra | 749 // This will either truncate the argument list or extend it with extra |
| 752 // null arguments, so it will match the signature. | 750 // null arguments, so it will match the signature. |
| 753 // TODO(sigmund): consider accepting optional arguments when we can tell | 751 // TODO(sigmund): consider accepting optional arguments when we can tell |
| 754 // them appart from named arguments (see http://dartbug.com/11334) | 752 // them appart from named arguments (see http://dartbug.com/11334) |
| 755 args.length = method.parameters.where((p) => !p.isOptional).length; | 753 args.length = method.parameters.where((p) => !p.isOptional).length; |
| 756 } | 754 } |
| 757 return self.invoke(methodName, args).reflectee; | 755 return receiverMirror.invoke(methodName, args).reflectee; |
| 758 } | |
| 759 | |
| 760 void instanceEventListener(Event event) { | |
| 761 _listenLocal(this, event); | |
| 762 } | |
| 763 | |
| 764 // TODO(sjmiles): much of the below privatized only because of the vague | |
| 765 // notion this code is too fiddly and we need to revisit the core feature | |
| 766 void _listenLocal(Polymer host, Event event) { | |
|
Jennifer Messerly
2013/10/25 22:00:41
yay! so much code gone :)
| |
| 767 // TODO(jmesserly): do we need this check? It was using cancelBubble, see: | |
| 768 // https://github.com/Polymer/polymer/issues/292 | |
| 769 if (!event.bubbles) return; | |
| 770 | |
| 771 bool log = _eventsLog.isLoggable(Level.FINE); | |
| 772 if (log) _eventsLog.fine('>>> [$localName]: listenLocal [${event.type}]'); | |
| 773 | |
| 774 final eventOn = '$_EVENT_PREFIX${_eventNameFromType(event.type)}'; | |
| 775 if (event.path == null) { | |
| 776 _listenLocalNoEventPath(host, event, eventOn); | |
| 777 } else { | |
| 778 _listenLocalEventPath(host, event, eventOn); | |
| 779 } | |
| 780 | |
| 781 if (log) _eventsLog.fine('<<< [$localName]: listenLocal [${event.type}]'); | |
| 782 } | |
| 783 | |
| 784 static void _listenLocalEventPath(Polymer host, Event event, String eventOn) { | |
| 785 var c = null; | |
| 786 for (var target in event.path) { | |
| 787 // if we hit host, stop | |
| 788 if (identical(target, host)) return; | |
| 789 | |
| 790 // find a controller for the target, unless we already found `host` | |
| 791 // as a controller | |
| 792 c = identical(c, host) ? c : _findController(target); | |
| 793 | |
| 794 // if we have a controller, dispatch the event, and stop if the handler | |
| 795 // returns true | |
| 796 if (c != null && _handleEvent(c, target, event, eventOn)) { | |
| 797 return; | |
| 798 } | |
| 799 } | |
| 800 } | |
| 801 | |
| 802 // TODO(sorvell): remove when ShadowDOM polyfill supports event path. | |
| 803 // Note that _findController will not return the expected controller when the | |
| 804 // event target is a distributed node. This is because we cannot traverse | |
| 805 // from a composed node to a node in shadowRoot. | |
| 806 // This will be addressed via an event path api | |
| 807 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066 | |
| 808 static void _listenLocalNoEventPath(Polymer host, Event event, | |
| 809 String eventOn) { | |
| 810 | |
| 811 if (_eventsLog.isLoggable(Level.FINE)) { | |
| 812 _eventsLog.fine('event.path() not supported for ${event.type}'); | |
| 813 } | |
| 814 | |
| 815 var target = event.target; | |
| 816 var c = null; | |
| 817 // if we hit dirt or host, stop | |
| 818 while (target != null && target != host) { | |
| 819 // find a controller for target `t`, unless we already found `host` | |
| 820 // as a controller | |
| 821 c = identical(c, host) ? c : _findController(target); | |
| 822 | |
| 823 // if we have a controller, dispatch the event, return 'true' if | |
| 824 // handler returns true | |
| 825 if (c != null && _handleEvent(c, target, event, eventOn)) { | |
| 826 return; | |
| 827 } | |
| 828 target = target.parent; | |
| 829 } | |
| 830 } | |
| 831 | |
| 832 // TODO(jmesserly): this won't find the correct host unless the ShadowRoot | |
| 833 // was created on a PolymerElement. | |
| 834 static Polymer _findController(Node node) { | |
| 835 while (node.parentNode != null) { | |
| 836 node = node.parentNode; | |
| 837 } | |
| 838 return _shadowHost[node]; | |
| 839 } | |
| 840 | |
| 841 static bool _handleEvent(Polymer ctrlr, Node node, Event event, | |
| 842 String eventOn) { | |
| 843 | |
| 844 // Note: local events are listened only in the shadow root. This dynamic | |
| 845 // lookup is used to distinguish determine whether the target actually has a | |
| 846 // listener, and if so, to determine lazily what's the target method. | |
| 847 var name = node is Element ? (node as Element).attributes[eventOn] : null; | |
| 848 if (name != null && _handleIfNotHandled(node, event)) { | |
| 849 if (_eventsLog.isLoggable(Level.FINE)) { | |
| 850 _eventsLog.fine('[${ctrlr.localName}] found handler name [$name]'); | |
| 851 } | |
| 852 var detail = event is CustomEvent ? | |
| 853 (event as CustomEvent).detail : null; | |
| 854 | |
| 855 if (node != null) { | |
| 856 // TODO(jmesserly): cache symbols? | |
| 857 ctrlr.dispatchMethod(new Symbol(name), [event, detail, node]); | |
| 858 } | |
| 859 } | |
| 860 | |
| 861 // TODO(jmesserly): do we need this? It was using cancelBubble, see: | |
| 862 // https://github.com/Polymer/polymer/issues/292 | |
| 863 return !event.bubbles; | |
| 864 } | |
| 865 | |
| 866 // TODO(jmesserly): I don't understand this bit. It seems to be a duplicate | |
| 867 // delivery prevention mechanism? | |
| 868 static bool _handleIfNotHandled(Node node, Event event) { | |
| 869 var list = _eventHandledTable[event]; | |
| 870 if (list == null) _eventHandledTable[event] = list = new Set<Node>(); | |
| 871 if (!list.contains(node)) { | |
| 872 list.add(node); | |
| 873 return true; | |
| 874 } | |
| 875 return false; | |
| 876 } | 756 } |
| 877 | 757 |
| 878 /** | 758 /** |
| 879 * Invokes a function asynchronously. | 759 * Invokes a function asynchronously. |
| 880 * This will call `Platform.flush()` and then return a `new Timer` | 760 * This will call `Platform.flush()` and then return a `new Timer` |
| 881 * with the provided [method] and [timeout]. | 761 * with the provided [method] and [timeout]. |
| 882 * | 762 * |
| 883 * If you would prefer to run the callback using | 763 * If you would prefer to run the callback using |
| 884 * [window.requestAnimationFrame], see the [async] method. | 764 * [window.requestAnimationFrame], see the [async] method. |
| 885 */ | 765 */ |
| (...skipping 252 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1138 class PolymerElement extends HtmlElement with Polymer, Observable { | 1018 class PolymerElement extends HtmlElement with Polymer, Observable { |
| 1139 PolymerElement.created() : super.created() { | 1019 PolymerElement.created() : super.created() { |
| 1140 polymerCreated(); | 1020 polymerCreated(); |
| 1141 } | 1021 } |
| 1142 } | 1022 } |
| 1143 | 1023 |
| 1144 class _PropertyValue { | 1024 class _PropertyValue { |
| 1145 Object oldValue, newValue; | 1025 Object oldValue, newValue; |
| 1146 _PropertyValue(this.oldValue); | 1026 _PropertyValue(this.oldValue); |
| 1147 } | 1027 } |
| 1028 | |
| 1029 // TODO(sorvell): we're patching the syntax while evaluating | |
| 1030 // event bindings. we'll move this to a better spot when that's done | |
| 1031 class _PolymerExpressionsWithEventDelegate extends PolymerExpressions { | |
|
Jennifer Messerly
2013/10/25 22:00:41
I wonder if we should make this functionality avai
Siggi Cherem (dart-lang)
2013/10/26 00:02:35
Good idea, done.
| |
| 1032 /** | |
| 1033 * Bind events via attributes of the form `on-eventName`. This method hooks | |
| 1034 * into the model syntax and does adds event listeners as needed. By default, | |
| 1035 * binding paths are always method names on the root model, the custom element | |
| 1036 * in which the node exists. Adding a '@' in the path directs the event | |
| 1037 * binding to use the model path as the event listener. In both cases, the | |
| 1038 * actual listener is attached to a generic method which evaluates the bound | |
| 1039 * path at event execution time. | |
| 1040 */ | |
| 1041 // from src/instance/event.js#prepareBinding | |
| 1042 // Dart note: template_binding doesn't have the notion of a delegateBinding | |
| 1043 // function, so we implement this by overriding getBinding instead. | |
| 1044 getBinding(model, String path, name, node) { | |
| 1045 // if lhs an event prefix, | |
| 1046 if (name is! String || !_hasEventPrefix(name)) { | |
| 1047 return super.getBinding(model, path, name, node); | |
| 1048 } | |
| 1049 | |
| 1050 // provide an event-binding callback. | |
| 1051 // return (model, name, node) { | |
| 1052 if (_eventsLog.isLoggable(Level.FINE)) { | |
| 1053 _eventsLog.fine('event: [${node.localName}].$name => ' | |
| 1054 '[${model.localName}].$path())'); | |
| 1055 } | |
| 1056 var eventName = _removeEventPrefix(name); | |
| 1057 // TODO(sigmund): polymer.js dropped event translations. reconcile? | |
| 1058 var translated = _eventTranslations[eventName]; | |
| 1059 eventName = translated != null ? translated : eventName; | |
| 1060 return node.on[eventName].listen((event) { | |
| 1061 var ctrlr = _findController(node); | |
| 1062 if (ctrlr is! Polymer) return; | |
|
Jennifer Messerly
2013/10/25 22:00:41
fyi -- _findController should not return something
| |
| 1063 var obj = ctrlr; | |
| 1064 var method = path; | |
| 1065 if (path[0] == '@') { | |
| 1066 obj = model; | |
| 1067 // Dart note: using getBinding gets us the result of evaluating the | |
| 1068 // original path (without the @) as a normal expression. | |
| 1069 method = super.getBinding(model, path.substring(1), name, node).value; | |
| 1070 } | |
| 1071 var detail = event is CustomEvent ? | |
| 1072 (event as CustomEvent).detail : null; | |
| 1073 ctrlr.dispatchMethod(obj, method, [event, detail, node]); | |
| 1074 }); | |
| 1075 } | |
| 1076 | |
| 1077 // TODO(jmesserly): this won't find the correct host unless the ShadowRoot | |
| 1078 // was created on a PolymerElement. | |
| 1079 static Polymer _findController(Node node) { | |
| 1080 while (node.parentNode != null) { | |
| 1081 node = node.parentNode; | |
| 1082 } | |
| 1083 return _shadowHost[node]; | |
| 1084 } | |
| 1085 } | |
| OLD | NEW |