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 /// Use this annotation to publish a field as an attribute. | 7 /// Use this annotation to publish a property as an attribute. |
| 8 /// | 8 /// |
| 9 /// You can also use [PublishedProperty] to provide additional information, | 9 /// You can also use [PublishedProperty] to provide additional information, |
| 10 /// such as automatically syncing the property back to the attribute. | 10 /// such as automatically syncing the property back to the attribute. |
| 11 /// | 11 /// |
| 12 /// For example: | 12 /// For example: |
| 13 /// | 13 /// |
| 14 /// class MyPlaybackElement extends PolymerElement { | 14 /// class MyPlaybackElement extends PolymerElement { |
| 15 /// // This will be available as an HTML attribute, for example: | 15 /// // This will be available as an HTML attribute, for example: |
| 16 /// // | 16 /// // |
| 17 /// // <my-playback volume="11"> | 17 /// // <my-playback volume="11"> |
| 18 /// // | 18 /// // |
| 19 /// // It will support initialization and data-binding via <template>: | 19 /// // It will support initialization and data-binding via <template>: |
| 20 /// // | 20 /// // |
| 21 /// // <template> | 21 /// // <template> |
| 22 /// // <my-playback volume="{{x}}"> | 22 /// // <my-playback volume="{{x}}"> |
| 23 /// // </template> | 23 /// // </template> |
| 24 /// // | 24 /// // |
| 25 /// // If the template is instantiated or given a model, `x` will be | 25 /// // If the template is instantiated or given a model, `x` will be |
| 26 /// // used for this field and updated whenever `volume` changes. | 26 /// // used for this field and updated whenever `volume` changes. |
| 27 /// @published double volume; | 27 /// @published |
| 28 /// double get volume => readValue(#volume); | |
| 29 /// set volume(double newValue) => writeValue(#volume, newValue); | |
| 28 /// | 30 /// |
| 29 /// // This will be available as an HTML attribute, like above, but it | 31 /// // This will be available as an HTML attribute, like above, but it |
| 30 /// // will also serialize values set to the property to the attribute. | 32 /// // will also serialize values set to the property to the attribute. |
| 31 /// // In other words, attributes['volume2'] will contain a serialized | 33 /// // In other words, attributes['volume2'] will contain a serialized |
| 32 /// // version of this field. | 34 /// // version of this field. |
| 33 /// @PublishedProperty(reflect: true) double volume2; | 35 /// @PublishedProperty(reflect: true) |
| 36 /// double get volume2 => readValue(#volume2); | |
| 37 /// set volume2(double newValue) => writeValue(#volume2, newValue); | |
| 34 /// } | 38 /// } |
| 35 /// | 39 /// |
| 40 /// **Important note**: the pattern using `readValue` and `writeValue` | |
| 41 /// guarantees that reading the property will give you the latest value at any | |
| 42 /// given time, even if change notifications have not been propagated. | |
| 43 /// | |
| 44 /// We still support using @published on a field, but this will not | |
| 45 /// provide the same guarantees, so this is discouraged. For example: | |
| 46 /// | |
| 47 /// // Avoid this if possible. This will be available as an HTML | |
| 48 /// // attribute too, but you might need to delay reading volume3 | |
| 49 /// // asynchronously to guarantee that you read the latest value | |
| 50 /// // set through bindings. | |
| 51 /// @published double volume3; | |
|
jakemac
2014/07/25 16:40:34
we are going to make sure we update our examples/e
Siggi Cherem (dart-lang)
2014/07/25 17:45:12
yeah, we need to coordinate with Kathy too.
| |
| 36 const published = const PublishedProperty(); | 52 const published = const PublishedProperty(); |
| 37 | 53 |
| 38 /// An annotation used to publish a field as an attribute. See [published]. | 54 /// An annotation used to publish a field as an attribute. See [published]. |
| 39 class PublishedProperty extends ObservableProperty { | 55 class PublishedProperty extends ObservableProperty { |
| 40 /// Whether the property value should be reflected back to the HTML attribute. | 56 /// Whether the property value should be reflected back to the HTML attribute. |
| 41 final bool reflect; | 57 final bool reflect; |
| 42 | 58 |
| 43 const PublishedProperty({this.reflect: false}); | 59 const PublishedProperty({this.reflect: false}); |
| 44 } | 60 } |
| 45 | 61 |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 65 if (n is! Iterable) { | 81 if (n is! Iterable) { |
| 66 throw new UnsupportedError('ObserveProperty takes either an Iterable of ' | 82 throw new UnsupportedError('ObserveProperty takes either an Iterable of ' |
| 67 'names, or a space separated String, instead of `$n`.'); | 83 'names, or a space separated String, instead of `$n`.'); |
| 68 } | 84 } |
| 69 return n; | 85 return n; |
| 70 } | 86 } |
| 71 | 87 |
| 72 const ObserveProperty(this._names); | 88 const ObserveProperty(this._names); |
| 73 } | 89 } |
| 74 | 90 |
| 91 /// Use this to create computed properties that are updated automatically. The | |
| 92 /// annotation includes a polymer expression that describes how this property | |
| 93 /// value can be expressed in terms of the values of other properties. For | |
| 94 /// example: | |
| 95 /// | |
| 96 /// class MyPlaybackElement extends PolymerElement { | |
| 97 /// @observable int x; | |
| 98 /// | |
| 99 /// // Reading xTimes2 will return x * 2. | |
| 100 /// @ComputedProperty('x * 2') | |
| 101 /// int get xTimes2 => readValue(#xTimes2); | |
| 102 /// | |
| 103 /// If the polymer expression is assignable, you can also define a setter for | |
| 104 /// it. For example: | |
| 105 /// | |
| 106 /// // Reading c will return a.b, writing c will update a.b. | |
| 107 /// @ComputedProperty('a.b') | |
| 108 /// get c => readValue(#c); | |
| 109 /// set c(newValue) => writeValue(#c, newValue); | |
| 110 /// | |
| 111 /// The expression can do anything that is allowed in a polymer expresssion, | |
| 112 /// even making calls to methods in your element. However, dependencies that are | |
| 113 /// only used within those methods and that are not visible in the polymer | |
| 114 /// expression, will not be observed. For example: | |
| 115 /// | |
| 116 /// // Because `x` only appears inside method `m`, we will not notice | |
|
jakemac
2014/07/25 16:40:34
Is there any way currently to work around this? Do
Siggi Cherem (dart-lang)
2014/07/25 17:45:12
Not really. JS will have the same issue. We used t
| |
| 117 /// // that `d` has changed if `x` is modified. However, `d` will be | |
| 118 /// // updated whenever `c` changes. | |
| 119 /// @ComputedProperty('m(c)') | |
| 120 /// get d => readValue(#d); | |
| 121 /// | |
| 122 /// m(c) => c + x; | |
| 123 class ComputedProperty { | |
| 124 /// A polymer expression, evaluated in the context of the custom element where | |
| 125 /// this annotation is used. | |
| 126 final String expression; | |
| 127 | |
| 128 const ComputedProperty(this.expression); | |
| 129 } | |
| 75 | 130 |
| 76 /// Base class for PolymerElements deriving from HtmlElement. | 131 /// Base class for PolymerElements deriving from HtmlElement. |
| 77 /// | 132 /// |
| 78 /// See [Polymer]. | 133 /// See [Polymer]. |
| 79 class PolymerElement extends HtmlElement with Polymer, Observable { | 134 class PolymerElement extends HtmlElement with Polymer, Observable { |
| 80 PolymerElement.created() : super.created() { | 135 PolymerElement.created() : super.created() { |
| 81 polymerCreated(); | 136 polymerCreated(); |
| 82 } | 137 } |
| 83 } | 138 } |
| 84 | 139 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 167 static Future get onReady => _onReady.future; | 222 static Future get onReady => _onReady.future; |
| 168 | 223 |
| 169 /// The most derived `<polymer-element>` declaration for this element. | 224 /// The most derived `<polymer-element>` declaration for this element. |
| 170 PolymerDeclaration get element => _element; | 225 PolymerDeclaration get element => _element; |
| 171 PolymerDeclaration _element; | 226 PolymerDeclaration _element; |
| 172 | 227 |
| 173 /// Deprecated: use [element] instead. | 228 /// Deprecated: use [element] instead. |
| 174 @deprecated PolymerDeclaration get declaration => _element; | 229 @deprecated PolymerDeclaration get declaration => _element; |
| 175 | 230 |
| 176 Map<String, StreamSubscription> _namedObservers; | 231 Map<String, StreamSubscription> _namedObservers; |
| 177 List<Iterable<Bindable>> _observers = []; | 232 List<Bindable> _observers = []; |
| 178 | 233 |
| 179 bool _unbound; // lazy-initialized | 234 bool _unbound; // lazy-initialized |
| 180 PolymerJob _unbindAllJob; | 235 PolymerJob _unbindAllJob; |
| 181 | 236 |
| 182 CompoundObserver _propertyObserver; | 237 CompoundObserver _propertyObserver; |
| 183 bool _readied = false; | 238 bool _readied = false; |
| 184 | 239 |
| 185 JsObject _jsElem; | 240 JsObject _jsElem; |
| 186 | 241 |
| 187 /// Returns the object that should be used as the event controller for | 242 /// Returns the object that should be used as the event controller for |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 231 if (_element != null) return _element.name; | 286 if (_element != null) return _element.name; |
| 232 var isAttr = attributes['is']; | 287 var isAttr = attributes['is']; |
| 233 return (isAttr == null || isAttr == '') ? localName : isAttr; | 288 return (isAttr == null || isAttr == '') ? localName : isAttr; |
| 234 } | 289 } |
| 235 | 290 |
| 236 /// By default the data bindings will be cleaned up when this custom element | 291 /// By default the data bindings will be cleaned up when this custom element |
| 237 /// is detached from the document. Overriding this to return `true` will | 292 /// is detached from the document. Overriding this to return `true` will |
| 238 /// prevent that from happening. | 293 /// prevent that from happening. |
| 239 bool get preventDispose => false; | 294 bool get preventDispose => false; |
| 240 | 295 |
| 296 /// Properties exposed by this element. | |
| 297 // Dart note: unlike Javascript we can't override the original property on | |
| 298 // the object, so we use this mechanism instead to define properties. See more | |
| 299 // details in [_PropertyAccessor]. | |
| 300 Map<Symbol, _PropertyAccessor> _properties = {}; | |
| 301 | |
| 302 /// Helper to implement a property with the given [name]. This is used for | |
| 303 /// normal and computed properties. Normal properties can provide the initial | |
| 304 /// value using the [initialValue] function. Computed properties ignore | |
| 305 /// [initialValue], their value is derived from the expression in the | |
| 306 /// [ComputedProperty] annotation that appears above the getter that uses this | |
| 307 /// helper. | |
| 308 readValue(Symbol name, [initialValue()]) { | |
| 309 var property = _properties[name]; | |
| 310 if (property == null) { | |
| 311 var value; | |
| 312 // Dart note: most computed properties are created in advance in | |
| 313 // createComputedProperties, but if one computed property depends on | |
| 314 // another, the declaration order might matter. Rather than trying to | |
| 315 // register them in order, we include here also the option of lazily | |
| 316 // creating the property accessor on the first read. | |
| 317 var binding = _getBindingForComputedProperty(name); | |
| 318 if (binding == null) { // normal property | |
| 319 value = initialValue != null ? initialValue() : null; | |
| 320 } else { | |
| 321 value = binding.value; | |
| 322 } | |
| 323 property = _properties[name] = new _PropertyAccessor(name, this, value); | |
| 324 } | |
| 325 return property.value; | |
| 326 } | |
| 327 | |
| 328 /// Helper to implement a setter of a property with the given [name] on a | |
| 329 /// polymer element. This can be used on normal properties and also on | |
| 330 /// computed properties, as long as the expression used for the computed | |
| 331 /// property is assignable (see [ComputedProperty]). | |
| 332 writeValue(Symbol name, newValue) { | |
| 333 var property = _properties[name]; | |
| 334 if (property == null) { | |
| 335 // Note: computed properties are created in advance in | |
| 336 // createComputedProperties, so we should only need to create here | |
| 337 // non-computed properties. | |
| 338 property = _properties[name] = new _PropertyAccessor(name, this, null); | |
| 339 } | |
| 340 property.value = newValue; | |
| 341 } | |
| 342 | |
| 241 /// If this class is used as a mixin, this method must be called from inside | 343 /// If this class is used as a mixin, this method must be called from inside |
| 242 /// of the `created()` constructor. | 344 /// of the `created()` constructor. |
| 243 /// | 345 /// |
| 244 /// If this class is a superclass, calling `super.created()` is sufficient. | 346 /// If this class is a superclass, calling `super.created()` is sufficient. |
| 245 void polymerCreated() { | 347 void polymerCreated() { |
| 246 var t = nodeBind(this).templateInstance; | 348 var t = nodeBind(this).templateInstance; |
| 247 if (t != null && t.model != null) { | 349 if (t != null && t.model != null) { |
| 248 window.console.warn('Attributes on $_name were data bound ' | 350 window.console.warn('Attributes on $_name were data bound ' |
| 249 'prior to Polymer upgrading the element. This may result in ' | 351 'prior to Polymer upgrading the element. This may result in ' |
| 250 'incorrect binding types.'); | 352 'incorrect binding types.'); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 284 | 386 |
| 285 /// Initialize JS interop for this element. For now we just initialize the | 387 /// Initialize JS interop for this element. For now we just initialize the |
| 286 // JsObject, but in the future we could also initialize JS APIs here. | 388 // JsObject, but in the future we could also initialize JS APIs here. |
| 287 _initJsObject() { | 389 _initJsObject() { |
| 288 _jsElem = new JsObject.fromBrowserObject(this); | 390 _jsElem = new JsObject.fromBrowserObject(this); |
| 289 } | 391 } |
| 290 | 392 |
| 291 makeElementReady() { | 393 makeElementReady() { |
| 292 if (_readied) return; | 394 if (_readied) return; |
| 293 _readied = true; | 395 _readied = true; |
| 396 createComputedProperties(); | |
| 294 | 397 |
| 295 // TODO(sorvell): We could create an entry point here | 398 // TODO(sorvell): We could create an entry point here |
| 296 // for the user to compute property values. | 399 // for the user to compute property values. |
| 297 // process declarative resources | 400 // process declarative resources |
| 298 parseDeclarations(_element); | 401 parseDeclarations(_element); |
| 299 // TODO(sorvell): CE polyfill uses unresolved attribute to simulate | 402 // TODO(sorvell): CE polyfill uses unresolved attribute to simulate |
| 300 // :unresolved; remove this attribute to be compatible with native | 403 // :unresolved; remove this attribute to be compatible with native |
| 301 // CE. | 404 // CE. |
| 302 attributes.remove('unresolved'); | 405 attributes.remove('unresolved'); |
| 303 // user entry point | 406 // user entry point |
| (...skipping 243 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 547 /// | 650 /// |
| 548 /// You can override this method to change the instantiation behavior of the | 651 /// You can override this method to change the instantiation behavior of the |
| 549 /// template, for example to use a different data-binding syntax. | 652 /// template, for example to use a different data-binding syntax. |
| 550 DocumentFragment instanceTemplate(Element template) { | 653 DocumentFragment instanceTemplate(Element template) { |
| 551 var syntax = this.syntax; | 654 var syntax = this.syntax; |
| 552 var t = templateBind(template); | 655 var t = templateBind(template); |
| 553 if (syntax == null && t.bindingDelegate == null) { | 656 if (syntax == null && t.bindingDelegate == null) { |
| 554 syntax = element.syntax; | 657 syntax = element.syntax; |
| 555 } | 658 } |
| 556 var dom = t.createInstance(this, syntax); | 659 var dom = t.createInstance(this, syntax); |
| 557 registerObservers(getTemplateInstanceBindings(dom)); | 660 _observers.addAll(getTemplateInstanceBindings(dom)); |
| 558 return dom; | 661 return dom; |
| 559 } | 662 } |
| 560 | 663 |
| 561 Bindable bind(String name, bindable, {bool oneTime: false}) { | 664 Bindable bind(String name, bindable, {bool oneTime: false}) { |
| 562 var decl = propertyForAttribute(name); | 665 var decl = propertyForAttribute(name); |
| 563 if (decl == null) { | 666 if (decl == null) { |
| 564 // Cannot call super.bind because template_binding is its own package | 667 // Cannot call super.bind because template_binding is its own package |
| 565 return nodeBindFallback(this).bind(name, bindable, oneTime: oneTime); | 668 return nodeBindFallback(this).bind(name, bindable, oneTime: oneTime); |
| 566 } else { | 669 } else { |
| 567 // use n-way Polymer binding | 670 // use n-way Polymer binding |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 633 _forNodeTree(child, callback); | 736 _forNodeTree(child, callback); |
| 634 } | 737 } |
| 635 } | 738 } |
| 636 | 739 |
| 637 /// Set up property observers. | 740 /// Set up property observers. |
| 638 void createPropertyObserver() { | 741 void createPropertyObserver() { |
| 639 final observe = _element._observe; | 742 final observe = _element._observe; |
| 640 if (observe != null) { | 743 if (observe != null) { |
| 641 var o = _propertyObserver = new CompoundObserver(); | 744 var o = _propertyObserver = new CompoundObserver(); |
| 642 // keep track of property observer so we can shut it down | 745 // keep track of property observer so we can shut it down |
| 643 registerObservers([o]); | 746 _observers.add(o); |
| 644 | 747 |
| 645 for (var path in observe.keys) { | 748 for (var path in observe.keys) { |
| 646 o.addPath(this, path); | 749 o.addPath(this, path); |
| 647 | 750 |
| 648 // TODO(jmesserly): on the Polymer side it doesn't look like they | 751 // TODO(jmesserly): on the Polymer side it doesn't look like they |
| 649 // will observe arrays unless it is a length == 1 path. | 752 // will observe arrays unless it is a length == 1 path. |
| 650 observeArrayValue(path, path.getValueFrom(this), null); | 753 observeArrayValue(path, path.getValueFrom(this), null); |
| 651 } | 754 } |
| 652 } | 755 } |
| 653 } | 756 } |
| 654 | 757 |
| 655 void openPropertyObserver() { | 758 void openPropertyObserver() { |
| 656 if (_propertyObserver != null) { | 759 if (_propertyObserver != null) { |
| 657 _propertyObserver.open(notifyPropertyChanges); | 760 _propertyObserver.open(notifyPropertyChanges); |
| 658 } | 761 } |
| 659 // Dart note: we need an extra listener. | 762 |
| 660 // see comment on [_propertyChange]. | 763 // Dart note: we need an extra listener only to continue supporting |
| 764 // @published properties that follow the old syntax until we get rid of it. | |
| 765 // This workaround has timing issues so we prefer the new, not so nice, | |
| 766 // syntax. | |
| 661 if (_element._publish != null) { | 767 if (_element._publish != null) { |
| 662 changes.listen(_propertyChange); | 768 changes.listen(_propertyChangeWorkaround); |
| 663 } | 769 } |
| 664 } | 770 } |
| 665 | 771 |
| 666 /// Responds to property changes on this element. | 772 /// Responds to property changes on this element. |
| 667 void notifyPropertyChanges(List newValues, Map oldValues, List paths) { | 773 void notifyPropertyChanges(List newValues, Map oldValues, List paths) { |
| 668 final observe = _element._observe; | 774 final observe = _element._observe; |
| 669 final called = new HashSet(); | 775 final called = new HashSet(); |
| 670 | 776 |
| 671 oldValues.forEach((i, oldValue) { | 777 oldValues.forEach((i, oldValue) { |
| 672 final newValue = newValues[i]; | 778 final newValue = newValues[i]; |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 698 // Dart note: had to rename this to avoid colliding with | 804 // Dart note: had to rename this to avoid colliding with |
| 699 // Observable.deliverChanges. Even worse, super calls aren't possible or | 805 // Observable.deliverChanges. Even worse, super calls aren't possible or |
| 700 // it prevents Polymer from being a mixin, so we can't override it even if | 806 // it prevents Polymer from being a mixin, so we can't override it even if |
| 701 // we wanted to. | 807 // we wanted to. |
| 702 void deliverPropertyChanges() { | 808 void deliverPropertyChanges() { |
| 703 if (_propertyObserver != null) { | 809 if (_propertyObserver != null) { |
| 704 _propertyObserver.deliver(); | 810 _propertyObserver.deliver(); |
| 705 } | 811 } |
| 706 } | 812 } |
| 707 | 813 |
| 708 // Dart note: this is not called by observe-js because we don't have | 814 // Dart note: this workaround is only for old-style @published properties, |
| 709 // the mechanism for defining properties on our proto. | 815 // which have timing issues. See _bindOldStylePublishedProperty below. |
| 710 // TODO(jmesserly): this has similar timing issues as our @published | 816 // TODO(sigmund): depreacte this. |
|
jakemac
2014/07/25 16:40:34
s/depreacte/depricate
Siggi Cherem (dart-lang)
2014/07/25 17:45:12
Done. but s/depricate/deprecate :-)
| |
| 711 // properties do generally -- it's async when it should be sync. | 817 void _propertyChangeWorkaround(List<ChangeRecord> records) { |
| 712 void _propertyChange(List<ChangeRecord> records) { | |
| 713 for (var record in records) { | 818 for (var record in records) { |
| 714 if (record is! PropertyChangeRecord) continue; | 819 if (record is! PropertyChangeRecord) continue; |
| 715 | 820 |
| 716 final name = smoke.symbolToName(record.name); | 821 var name = record.name; |
| 717 final reflect = _element._reflect; | 822 // The setter of a new-style property will create an accessor in |
| 718 if (reflect != null && reflect.contains(name)) { | 823 // _properties[name]. We can skip the workaround for those properties. |
| 719 reflectPropertyToAttribute(name); | 824 if (_properties[name] != null) continue; |
| 720 } | 825 _propertyChange(name); |
| 721 } | 826 } |
| 722 } | 827 } |
| 723 | 828 |
| 829 void _propertyChange(Symbol nameSymbol) { | |
| 830 var name = smoke.symbolToName(nameSymbol); | |
| 831 var reflect = _element._reflect; | |
| 832 if (reflect != null && reflect.contains(name)) { | |
| 833 reflectPropertyToAttribute(name); | |
| 834 } | |
| 835 } | |
| 836 | |
| 724 void observeArrayValue(PropertyPath name, Object value, Object old) { | 837 void observeArrayValue(PropertyPath name, Object value, Object old) { |
| 725 final observe = _element._observe; | 838 final observe = _element._observe; |
| 726 if (observe == null) return; | 839 if (observe == null) return; |
| 727 | 840 |
| 728 // we only care if there are registered side-effects | 841 // we only care if there are registered side-effects |
| 729 var callbacks = observe[name]; | 842 var callbacks = observe[name]; |
| 730 if (callbacks == null) return; | 843 if (callbacks == null) return; |
| 731 | 844 |
| 732 // if we are observing the previous value, stop | 845 // if we are observing the previous value, stop |
| 733 if (old is ObservableList) { | 846 if (old is ObservableList) { |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 744 } | 857 } |
| 745 var sub = value.listChanges.listen((changes) { | 858 var sub = value.listChanges.listen((changes) { |
| 746 for (var callback in callbacks) { | 859 for (var callback in callbacks) { |
| 747 smoke.invoke(this, callback, [old], adjust: true); | 860 smoke.invoke(this, callback, [old], adjust: true); |
| 748 } | 861 } |
| 749 }); | 862 }); |
| 750 registerNamedObserver('${name}__array', sub); | 863 registerNamedObserver('${name}__array', sub); |
| 751 } | 864 } |
| 752 } | 865 } |
| 753 | 866 |
| 754 void registerObservers(Iterable<Bindable> observers) { | 867 emitPropertyChangeRecord(Symbol name, oldValue, newValue) { |
| 755 _observers.add(observers); | 868 if (identical(oldValue, newValue)) return; |
| 869 _propertyChange(name); | |
| 756 } | 870 } |
| 757 | 871 |
| 872 bindToAccessor(Symbol name, Bindable bindable, {resolveBindingValue: false}) { | |
| 873 // Dart note: our pattern is to declare the initial value in the getter. We | |
| 874 // read it via smoke to ensure that the value is initialized correctly. | |
| 875 var oldValue = smoke.read(this, name); | |
| 876 var property = _properties[name]; | |
| 877 if (property == null) { | |
| 878 // We know that _properties[name] is null only for old-style @published | |
| 879 // properties. This fallback is here to make it easier to deprecate the | |
| 880 // old-style of published properties, which have bad timing guarantees | |
| 881 // (see comment in _PolymerBinding). | |
| 882 return _bindOldStylePublishedProperty(name, bindable, oldValue); | |
| 883 } | |
| 884 | |
| 885 property.bindable = bindable; | |
| 886 var value = bindable.open(property.updateValue); | |
| 887 | |
| 888 if (resolveBindingValue) { | |
| 889 // capture A's value if B's value is null or undefined, | |
| 890 // otherwise use B's value | |
| 891 var v = (value == null ? oldValue : value); | |
| 892 if (!identical(value, oldValue)) { | |
| 893 bindable.value = value = v; | |
| 894 } | |
| 895 } | |
| 896 | |
| 897 property.updateValue(value); | |
| 898 var o = new _CloseOnlyBinding(property); | |
| 899 _observers.add(o); | |
| 900 return o; | |
| 901 } | |
| 902 | |
| 903 // Dart note: this fallback uses our old-style binding mechanism to be able to | |
| 904 // link @published properties with bindings. This mechanism is backwards from | |
| 905 // what Javascript does because we can't override the original property. This | |
| 906 // workaround also brings some timing issues which are described in detail in | |
| 907 // dartbug.com/18343. | |
| 908 // TODO(sigmund): deprecate old-style @published properties. | |
| 909 _bindOldStylePublishedProperty(Symbol name, Bindable bindable, oldValue) { | |
| 910 // capture A's value if B's value is null or undefined, | |
| 911 // otherwise use B's value | |
| 912 if (bindable.value == null) bindable.value = oldValue; | |
| 913 | |
| 914 var o = new _PolymerBinding(this, name, bindable); | |
| 915 _observers.add(o); | |
| 916 return o; | |
| 917 } | |
| 918 | |
| 919 _getBindingForComputedProperty(Symbol name) { | |
| 920 var exprString = element._computed[name]; | |
| 921 if (exprString == null) return null; | |
| 922 var expr = PolymerExpressions.getExpression(exprString); | |
| 923 return PolymerExpressions.getBinding(expr, this, | |
| 924 globals: element.syntax.globals); | |
| 925 } | |
| 926 | |
| 927 createComputedProperties() { | |
| 928 var computed = this.element._computed; | |
| 929 for (var name in computed.keys) { | |
| 930 try { | |
| 931 // Dart note: this is done in Javascript by modifying the prototype in | |
| 932 // declaration/properties.js, we can't do that, so we do it here. | |
| 933 var binding = _getBindingForComputedProperty(name); | |
| 934 | |
| 935 // Follow up note: ideally we would only create the accessor object | |
| 936 // here, but some computed properties might depend on others and | |
| 937 // evaluating `binding.value` could try to read the value of another | |
| 938 // computed property that we haven't created yet. For this reason we | |
| 939 // also allow to also create the accessor in [readValue]. | |
| 940 if (_properties[name] == null) { | |
| 941 _properties[name] = new _PropertyAccessor(name, this, binding.value); | |
| 942 } | |
| 943 bindToAccessor(name, binding); | |
| 944 } catch (e) { | |
| 945 window.console.error('Failed to create computed property $name' | |
| 946 ' (${computed[name]}): $e'); | |
| 947 } | |
| 948 } | |
| 949 } | |
| 950 | |
| 951 // Dart note: to simplify the code above we made registerObserver calls | |
| 952 // directly invoke _observers.add/addAll. | |
| 758 void closeObservers() { | 953 void closeObservers() { |
| 759 _observers.forEach(closeObserverList); | 954 for (var o in _observers) { |
| 955 if (o != null) o.close(); | |
| 956 } | |
| 760 _observers = []; | 957 _observers = []; |
| 761 } | 958 } |
| 762 | 959 |
| 763 void closeObserverList(Iterable<Bindable> observers) { | |
| 764 for (var o in observers) { | |
| 765 if (o != null) o.close(); | |
| 766 } | |
| 767 } | |
| 768 | |
| 769 /// Bookkeeping observers for memory management. | 960 /// Bookkeeping observers for memory management. |
| 770 void registerNamedObserver(String name, StreamSubscription sub) { | 961 void registerNamedObserver(String name, StreamSubscription sub) { |
| 771 if (_namedObservers == null) { | 962 if (_namedObservers == null) { |
| 772 _namedObservers = new Map<String, StreamSubscription>(); | 963 _namedObservers = new Map<String, StreamSubscription>(); |
| 773 } | 964 } |
| 774 _namedObservers[name] = sub; | 965 _namedObservers[name] = sub; |
| 775 } | 966 } |
| 776 | 967 |
| 777 bool closeNamedObserver(String name) { | 968 bool closeNamedObserver(String name) { |
| 778 var sub = _namedObservers.remove(name); | 969 var sub = _namedObservers.remove(name); |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 793 /// Bind the [name] property in this element to [bindable]. *Note* in Dart it | 984 /// Bind the [name] property in this element to [bindable]. *Note* in Dart it |
| 794 /// is necessary to also define the field: | 985 /// is necessary to also define the field: |
| 795 /// | 986 /// |
| 796 /// var myProperty; | 987 /// var myProperty; |
| 797 /// | 988 /// |
| 798 /// ready() { | 989 /// ready() { |
| 799 /// super.ready(); | 990 /// super.ready(); |
| 800 /// bindProperty(#myProperty, | 991 /// bindProperty(#myProperty, |
| 801 /// new PathObserver(this, 'myModel.path.to.otherProp')); | 992 /// new PathObserver(this, 'myModel.path.to.otherProp')); |
| 802 /// } | 993 /// } |
| 803 Bindable bindProperty(Symbol name, Bindable bindable, {oneTime: false}) { | 994 Bindable bindProperty(Symbol name, bindableOrValue, {oneTime: false}) { |
| 804 // Dart note: normally we only reach this code when we know it's a | 995 // Dart note: normally we only reach this code when we know it's a |
| 805 // property, but if someone uses bindProperty directly they might get a | 996 // property, but if someone uses bindProperty directly they might get a |
| 806 // NoSuchMethodError either from the getField below, or from the setField | 997 // NoSuchMethodError either from the getField below, or from the setField |
| 807 // inside PolymerBinding. That doesn't seem unreasonable, but it's a slight | 998 // inside PolymerBinding. That doesn't seem unreasonable, but it's a slight |
| 808 // difference from Polymer.js behavior. | 999 // difference from Polymer.js behavior. |
| 809 | 1000 |
| 810 if (_bindLog.isLoggable(Level.FINE)) { | 1001 if (_bindLog.isLoggable(Level.FINE)) { |
| 811 _bindLog.fine('bindProperty: [$bindable] to [$_name].[$name]'); | 1002 _bindLog.fine('bindProperty: [$bindableOrValue] to [$_name].[$name]'); |
| 812 } | 1003 } |
| 813 | 1004 |
| 814 // capture A's value if B's value is null or undefined, | 1005 if (oneTime) { |
| 815 // otherwise use B's value | 1006 if (bindableOrValue is Bindable) { |
| 816 // TODO(sorvell): need to review, can do with ObserverTransform | 1007 _bindLog.warning('bindProperty: expected non-bindable value ' |
| 817 var v = bindable.value; | 1008 'on a one-time binding to [$_name].[$name], ' |
| 818 if (v == null) { | 1009 'but found $bindableOrValue.'); |
| 819 bindable.value = smoke.read(this, name); | 1010 } |
| 1011 smoke.write(this, name, bindableOrValue); | |
| 1012 return null; | |
| 820 } | 1013 } |
| 821 | 1014 |
| 822 // TODO(jmesserly): we need to fix this -- it doesn't work like Polymer.js | 1015 return bindToAccessor(name, bindableOrValue, resolveBindingValue: true); |
| 823 // bindings. https://code.google.com/p/dart/issues/detail?id=18343 | |
| 824 // apply Polymer two-way reference binding | |
| 825 //return Observer.bindToInstance(inA, inProperty, observable, | |
| 826 // resolveBindingValue); | |
| 827 return new _PolymerBinding(this, name, bindable); | |
| 828 } | 1016 } |
| 829 | 1017 |
| 830 /// Attach event listeners on the host (this) element. | 1018 /// Attach event listeners on the host (this) element. |
| 831 void addHostListeners() { | 1019 void addHostListeners() { |
| 832 var events = _element._eventDelegates; | 1020 var events = _element._eventDelegates; |
| 833 if (events.isEmpty) return; | 1021 if (events.isEmpty) return; |
| 834 | 1022 |
| 835 if (_eventsLog.isLoggable(Level.FINE)) { | 1023 if (_eventsLog.isLoggable(Level.FINE)) { |
| 836 _eventsLog.fine('[$_name] addHostListeners: $events'); | 1024 _eventsLog.fine('[$_name] addHostListeners: $events'); |
| 837 } | 1025 } |
| (...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 990 | 1178 |
| 991 if (scope == null) return; | 1179 if (scope == null) return; |
| 992 | 1180 |
| 993 if (_ShadowCss != null) { | 1181 if (_ShadowCss != null) { |
| 994 cssText = _shimCssText(cssText, scope is ShadowRoot ? scope.host : null); | 1182 cssText = _shimCssText(cssText, scope is ShadowRoot ? scope.host : null); |
| 995 } | 1183 } |
| 996 var style = element.cssTextToScopeStyle(cssText, | 1184 var style = element.cssTextToScopeStyle(cssText, |
| 997 _STYLE_CONTROLLER_SCOPE); | 1185 _STYLE_CONTROLLER_SCOPE); |
| 998 applyStyleToScope(style, scope); | 1186 applyStyleToScope(style, scope); |
| 999 // cache that this style has been applied | 1187 // cache that this style has been applied |
| 1000 Set styles = _scopeStyles[scope]; | 1188 styleCacheForScope(scope).add('$_name$name'); |
| 1001 if (styles == null) _scopeStyles[scope] = styles = new Set(); | |
| 1002 styles.add('$_name$name'); | |
| 1003 } | 1189 } |
| 1004 | 1190 |
| 1005 Node findStyleScope([node]) { | 1191 Node findStyleScope([node]) { |
| 1006 // find the shadow root that contains this element | 1192 // find the shadow root that contains this element |
| 1007 var n = node; | 1193 var n = node; |
| 1008 if (n == null) n = this; | 1194 if (n == null) n = this; |
| 1009 while (n.parentNode != null) { | 1195 while (n.parentNode != null) { |
| 1010 n = n.parentNode; | 1196 n = n.parentNode; |
| 1011 } | 1197 } |
| 1012 return n; | 1198 return n; |
| 1013 } | 1199 } |
| 1014 | 1200 |
| 1015 bool scopeHasNamedStyle(Node scope, String name) { | 1201 bool scopeHasNamedStyle(Node scope, String name) => |
| 1016 Set styles = _scopeStyles[scope]; | 1202 styleCacheForScope(scope).contains(name); |
| 1017 return styles != null && styles.contains(name); | 1203 |
| 1204 Map _polyfillScopeStyleCache = {}; | |
| 1205 | |
| 1206 Set styleCacheForScope(Node scope) { | |
| 1207 var styles; | |
| 1208 if (_hasShadowDomPolyfill) { | |
| 1209 var name = scope is ShadowRoot ? scope.host.localName | |
| 1210 : (scope as Element).localName; | |
| 1211 var styles = _polyfillScopeStyleCache[name]; | |
| 1212 if (styles == null) _polyfillScopeStyleCache[name] = styles = new Set(); | |
| 1213 } else { | |
| 1214 styles = _scopeStyles[scope]; | |
| 1215 if (styles == null) _scopeStyles[scope] = styles = new Set(); | |
| 1216 } | |
| 1217 return styles; | |
| 1018 } | 1218 } |
| 1019 | 1219 |
| 1020 static final _scopeStyles = new Expando(); | 1220 static final _scopeStyles = new Expando(); |
| 1021 | 1221 |
| 1022 static String _shimCssText(String cssText, [Element host]) { | 1222 static String _shimCssText(String cssText, [Element host]) { |
| 1023 var name = ''; | 1223 var name = ''; |
| 1024 var is_ = false; | 1224 var is_ = false; |
| 1025 if (host != null) { | 1225 if (host != null) { |
| 1026 name = host.localName; | 1226 name = host.localName; |
| 1027 is_ = host.attributes.containsKey('is'); | 1227 is_ = host.attributes.containsKey('is'); |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1072 /// | 1272 /// |
| 1073 /// Returns the newly created job. | 1273 /// Returns the newly created job. |
| 1074 // Dart note: renamed to scheduleJob to be a bit more consistent with Dart. | 1274 // Dart note: renamed to scheduleJob to be a bit more consistent with Dart. |
| 1075 PolymerJob scheduleJob(PolymerJob job, void callback(), [Duration wait]) { | 1275 PolymerJob scheduleJob(PolymerJob job, void callback(), [Duration wait]) { |
| 1076 if (job == null) job = new PolymerJob._(); | 1276 if (job == null) job = new PolymerJob._(); |
| 1077 // Dart note: made start smarter, so we don't need to call stop. | 1277 // Dart note: made start smarter, so we don't need to call stop. |
| 1078 return job..start(callback, wait); | 1278 return job..start(callback, wait); |
| 1079 } | 1279 } |
| 1080 } | 1280 } |
| 1081 | 1281 |
| 1082 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine | 1282 // Dart note: this is related to _bindOldStylePublishedProperty. Polymer |
| 1083 // the property on the PolymerElement instance to always get its value from the | 1283 // addresses n-way bindings by metaprogramming: redefine the property on the |
| 1084 // model@path. We can't replicate this in Dart so we do the next best thing: | 1284 // PolymerElement instance to always get its value from the model@path. This is |
| 1085 // listen to changes on both sides and update the values. | 1285 // supported in Dart using a new style of @publised property declaration using |
|
jakemac
2014/07/25 16:40:34
@publised/@published
Siggi Cherem (dart-lang)
2014/07/25 17:45:12
Done. Thx!
| |
| 1086 // TODO(jmesserly): our approach leads to race conditions in the bindings. | 1286 // the `readValue` and `writeValue` methods above. In the past we used to work |
| 1087 // See http://code.google.com/p/dart/issues/detail?id=13567 | 1287 // around this by listening to changes on both sides and updating the values. |
| 1288 // This object provides the hooks to do this. | |
| 1289 // TODO(sigmund,jmesserly): delete after a deprecation period. | |
| 1088 class _PolymerBinding extends Bindable { | 1290 class _PolymerBinding extends Bindable { |
| 1089 final Polymer _target; | 1291 final Polymer _target; |
| 1090 final Symbol _property; | 1292 final Symbol _property; |
| 1091 final Bindable _bindable; | 1293 final Bindable _bindable; |
| 1092 StreamSubscription _sub; | 1294 StreamSubscription _sub; |
| 1093 Object _lastValue; | 1295 Object _lastValue; |
| 1094 | 1296 |
| 1095 _PolymerBinding(this._target, this._property, this._bindable) { | 1297 _PolymerBinding(this._target, this._property, this._bindable) { |
| 1096 _sub = _target.changes.listen(_propertyValueChanged); | 1298 _sub = _target.changes.listen(_propertyValueChanged); |
| 1097 _updateNode(open(_updateNode)); | 1299 _updateNode(open(_updateNode)); |
| 1098 } | 1300 } |
| 1099 | 1301 |
| 1100 void _updateNode(newValue) { | 1302 void _updateNode(newValue) { |
| 1101 _lastValue = newValue; | 1303 _lastValue = newValue; |
| 1102 smoke.write(_target, _property, newValue); | 1304 smoke.write(_target, _property, newValue); |
| 1305 // Note: we don't invoke emitPropertyChangeRecord here because that's | |
| 1306 // done by listening on changes on the PolymerElement. | |
| 1103 } | 1307 } |
| 1104 | 1308 |
| 1105 void _propertyValueChanged(List<ChangeRecord> records) { | 1309 void _propertyValueChanged(List<ChangeRecord> records) { |
| 1106 for (var record in records) { | 1310 for (var record in records) { |
| 1107 if (record is PropertyChangeRecord && record.name == _property) { | 1311 if (record is PropertyChangeRecord && record.name == _property) { |
| 1108 final newValue = smoke.read(_target, _property); | 1312 final newValue = smoke.read(_target, _property); |
| 1109 if (!identical(_lastValue, newValue)) { | 1313 if (!identical(_lastValue, newValue)) { |
| 1110 this.value = newValue; | 1314 this.value = newValue; |
| 1111 } | 1315 } |
| 1112 return; | 1316 return; |
| 1113 } | 1317 } |
| 1114 } | 1318 } |
| 1115 } | 1319 } |
| 1116 | 1320 |
| 1117 open(callback(value)) => _bindable.open(callback); | 1321 open(callback(value)) => _bindable.open(callback); |
| 1118 get value => _bindable.value; | 1322 get value => _bindable.value; |
| 1119 set value(newValue) => _bindable.value = newValue; | 1323 set value(newValue) => _bindable.value = newValue; |
| 1120 | 1324 |
| 1121 void close() { | 1325 void close() { |
| 1122 if (_sub != null) { | 1326 if (_sub != null) { |
| 1123 _sub.cancel(); | 1327 _sub.cancel(); |
| 1124 _sub = null; | 1328 _sub = null; |
| 1125 } | 1329 } |
| 1126 _bindable.close(); | 1330 _bindable.close(); |
| 1127 } | 1331 } |
| 1128 } | 1332 } |
| 1129 | 1333 |
| 1334 // Ported from an inline object in instance/properties.js#bindToAccessor. | |
| 1335 class _CloseOnlyBinding extends Bindable { | |
| 1336 final _PropertyAccessor accessor; | |
| 1337 | |
| 1338 _CloseOnlyBinding(this.accessor); | |
| 1339 | |
| 1340 open(callback) {} | |
| 1341 get value => null; | |
| 1342 set value(newValue) {} | |
| 1343 deliver() {} | |
| 1344 | |
| 1345 void close() { | |
| 1346 if (accessor.bindable == null) return; | |
| 1347 accessor.bindable.close(); | |
| 1348 accessor.bindable = null; | |
| 1349 } | |
| 1350 } | |
| 1351 | |
| 1130 bool _toBoolean(value) => null != value && false != value; | 1352 bool _toBoolean(value) => null != value && false != value; |
| 1131 | 1353 |
| 1132 final Logger _observeLog = new Logger('polymer.observe'); | 1354 final Logger _observeLog = new Logger('polymer.observe'); |
| 1133 final Logger _eventsLog = new Logger('polymer.events'); | 1355 final Logger _eventsLog = new Logger('polymer.events'); |
| 1134 final Logger _unbindLog = new Logger('polymer.unbind'); | 1356 final Logger _unbindLog = new Logger('polymer.unbind'); |
| 1135 final Logger _bindLog = new Logger('polymer.bind'); | 1357 final Logger _bindLog = new Logger('polymer.bind'); |
| 1136 | 1358 |
| 1137 final Expando _eventHandledTable = new Expando<Set<Node>>(); | 1359 final Expando _eventHandledTable = new Expando<Set<Node>>(); |
| 1138 | 1360 |
| 1139 final JsObject _PolymerGestures = js.context['PolymerGestures']; | 1361 final JsObject _PolymerGestures = js.context['PolymerGestures']; |
| 1140 | 1362 |
| OLD | NEW |