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; |
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 |
| 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): deprecate this. |
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, newValue, oldValue) { |
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 @published property declaration using |
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 |