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

Unified Diff: pkg/polymer/lib/src/instance.dart

Issue 420673002: Roll polymer packages to version 0.3.4 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/polymer/lib/src/events.dart ('k') | pkg/polymer/lib/src/js/polymer/README.md » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/polymer/lib/src/instance.dart
diff --git a/pkg/polymer/lib/src/instance.dart b/pkg/polymer/lib/src/instance.dart
index 3cc2e1a55a02a3e86afe83ccbbd454869e04f94f..b63ef93e49428d28ba012fa92d514d7d0a8ce9ea 100644
--- a/pkg/polymer/lib/src/instance.dart
+++ b/pkg/polymer/lib/src/instance.dart
@@ -4,7 +4,7 @@
part of polymer;
-/// Use this annotation to publish a field as an attribute.
+/// Use this annotation to publish a property as an attribute.
///
/// You can also use [PublishedProperty] to provide additional information,
/// such as automatically syncing the property back to the attribute.
@@ -24,15 +24,31 @@ part of polymer;
/// //
/// // If the template is instantiated or given a model, `x` will be
/// // used for this field and updated whenever `volume` changes.
-/// @published double volume;
+/// @published
+/// double get volume => readValue(#volume);
+/// set volume(double newValue) => writeValue(#volume, newValue);
///
/// // This will be available as an HTML attribute, like above, but it
/// // will also serialize values set to the property to the attribute.
/// // In other words, attributes['volume2'] will contain a serialized
/// // version of this field.
-/// @PublishedProperty(reflect: true) double volume2;
+/// @PublishedProperty(reflect: true)
+/// double get volume2 => readValue(#volume2);
+/// set volume2(double newValue) => writeValue(#volume2, newValue);
/// }
///
+/// **Important note**: the pattern using `readValue` and `writeValue`
+/// guarantees that reading the property will give you the latest value at any
+/// given time, even if change notifications have not been propagated.
+///
+/// We still support using @published on a field, but this will not
+/// provide the same guarantees, so this is discouraged. For example:
+///
+/// // Avoid this if possible. This will be available as an HTML
+/// // attribute too, but you might need to delay reading volume3
+/// // asynchronously to guarantee that you read the latest value
+/// // set through bindings.
+/// @published double volume3;
const published = const PublishedProperty();
/// An annotation used to publish a field as an attribute. See [published].
@@ -72,6 +88,45 @@ class ObserveProperty {
const ObserveProperty(this._names);
}
+/// Use this to create computed properties that are updated automatically. The
+/// annotation includes a polymer expression that describes how this property
+/// value can be expressed in terms of the values of other properties. For
+/// example:
+///
+/// class MyPlaybackElement extends PolymerElement {
+/// @observable int x;
+///
+/// // Reading xTimes2 will return x * 2.
+/// @ComputedProperty('x * 2')
+/// int get xTimes2 => readValue(#xTimes2);
+///
+/// If the polymer expression is assignable, you can also define a setter for
+/// it. For example:
+///
+/// // Reading c will return a.b, writing c will update a.b.
+/// @ComputedProperty('a.b')
+/// get c => readValue(#c);
+/// set c(newValue) => writeValue(#c, newValue);
+///
+/// The expression can do anything that is allowed in a polymer expresssion,
+/// even making calls to methods in your element. However, dependencies that are
+/// only used within those methods and that are not visible in the polymer
+/// expression, will not be observed. For example:
+///
+/// // Because `x` only appears inside method `m`, we will not notice
+/// // that `d` has changed if `x` is modified. However, `d` will be
+/// // updated whenever `c` changes.
+/// @ComputedProperty('m(c)')
+/// get d => readValue(#d);
+///
+/// m(c) => c + x;
+class ComputedProperty {
+ /// A polymer expression, evaluated in the context of the custom element where
+ /// this annotation is used.
+ final String expression;
+
+ const ComputedProperty(this.expression);
+}
/// Base class for PolymerElements deriving from HtmlElement.
///
@@ -174,7 +229,7 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
@deprecated PolymerDeclaration get declaration => _element;
Map<String, StreamSubscription> _namedObservers;
- List<Iterable<Bindable>> _observers = [];
+ List<Bindable> _observers = [];
bool _unbound; // lazy-initialized
PolymerJob _unbindAllJob;
@@ -238,6 +293,53 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
/// prevent that from happening.
bool get preventDispose => false;
+ /// Properties exposed by this element.
+ // Dart note: unlike Javascript we can't override the original property on
+ // the object, so we use this mechanism instead to define properties. See more
+ // details in [_PropertyAccessor].
+ Map<Symbol, _PropertyAccessor> _properties = {};
+
+ /// Helper to implement a property with the given [name]. This is used for
+ /// normal and computed properties. Normal properties can provide the initial
+ /// value using the [initialValue] function. Computed properties ignore
+ /// [initialValue], their value is derived from the expression in the
+ /// [ComputedProperty] annotation that appears above the getter that uses this
+ /// helper.
+ readValue(Symbol name, [initialValue()]) {
+ var property = _properties[name];
+ if (property == null) {
+ var value;
+ // Dart note: most computed properties are created in advance in
+ // createComputedProperties, but if one computed property depends on
+ // another, the declaration order might matter. Rather than trying to
+ // register them in order, we include here also the option of lazily
+ // creating the property accessor on the first read.
+ var binding = _getBindingForComputedProperty(name);
+ if (binding == null) { // normal property
+ value = initialValue != null ? initialValue() : null;
+ } else {
+ value = binding.value;
+ }
+ property = _properties[name] = new _PropertyAccessor(name, this, value);
+ }
+ return property.value;
+ }
+
+ /// Helper to implement a setter of a property with the given [name] on a
+ /// polymer element. This can be used on normal properties and also on
+ /// computed properties, as long as the expression used for the computed
+ /// property is assignable (see [ComputedProperty]).
+ writeValue(Symbol name, newValue) {
+ var property = _properties[name];
+ if (property == null) {
+ // Note: computed properties are created in advance in
+ // createComputedProperties, so we should only need to create here
+ // non-computed properties.
+ property = _properties[name] = new _PropertyAccessor(name, this, null);
+ }
+ property.value = newValue;
+ }
+
/// If this class is used as a mixin, this method must be called from inside
/// of the `created()` constructor.
///
@@ -291,6 +393,7 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
makeElementReady() {
if (_readied) return;
_readied = true;
+ createComputedProperties();
// TODO(sorvell): We could create an entry point here
// for the user to compute property values.
@@ -554,7 +657,7 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
syntax = element.syntax;
}
var dom = t.createInstance(this, syntax);
- registerObservers(getTemplateInstanceBindings(dom));
+ _observers.addAll(getTemplateInstanceBindings(dom));
return dom;
}
@@ -640,7 +743,7 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
if (observe != null) {
var o = _propertyObserver = new CompoundObserver();
// keep track of property observer so we can shut it down
- registerObservers([o]);
+ _observers.add(o);
for (var path in observe.keys) {
o.addPath(this, path);
@@ -656,10 +759,13 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
if (_propertyObserver != null) {
_propertyObserver.open(notifyPropertyChanges);
}
- // Dart note: we need an extra listener.
- // see comment on [_propertyChange].
+
+ // Dart note: we need an extra listener only to continue supporting
+ // @published properties that follow the old syntax until we get rid of it.
+ // This workaround has timing issues so we prefer the new, not so nice,
+ // syntax.
if (_element._publish != null) {
- changes.listen(_propertyChange);
+ changes.listen(_propertyChangeWorkaround);
}
}
@@ -705,19 +811,26 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
}
}
- // Dart note: this is not called by observe-js because we don't have
- // the mechanism for defining properties on our proto.
- // TODO(jmesserly): this has similar timing issues as our @published
- // properties do generally -- it's async when it should be sync.
- void _propertyChange(List<ChangeRecord> records) {
+ // Dart note: this workaround is only for old-style @published properties,
+ // which have timing issues. See _bindOldStylePublishedProperty below.
+ // TODO(sigmund): deprecate this.
+ void _propertyChangeWorkaround(List<ChangeRecord> records) {
for (var record in records) {
if (record is! PropertyChangeRecord) continue;
- final name = smoke.symbolToName(record.name);
- final reflect = _element._reflect;
- if (reflect != null && reflect.contains(name)) {
- reflectPropertyToAttribute(name);
- }
+ var name = record.name;
+ // The setter of a new-style property will create an accessor in
+ // _properties[name]. We can skip the workaround for those properties.
+ if (_properties[name] != null) continue;
+ _propertyChange(name);
+ }
+ }
+
+ void _propertyChange(Symbol nameSymbol) {
+ var name = smoke.symbolToName(nameSymbol);
+ var reflect = _element._reflect;
+ if (reflect != null && reflect.contains(name)) {
+ reflectPropertyToAttribute(name);
}
}
@@ -751,19 +864,97 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
}
}
- void registerObservers(Iterable<Bindable> observers) {
- _observers.add(observers);
+ emitPropertyChangeRecord(Symbol name, newValue, oldValue) {
+ if (identical(oldValue, newValue)) return;
+ _propertyChange(name);
}
- void closeObservers() {
- _observers.forEach(closeObserverList);
- _observers = [];
+ bindToAccessor(Symbol name, Bindable bindable, {resolveBindingValue: false}) {
+ // Dart note: our pattern is to declare the initial value in the getter. We
+ // read it via smoke to ensure that the value is initialized correctly.
+ var oldValue = smoke.read(this, name);
+ var property = _properties[name];
+ if (property == null) {
+ // We know that _properties[name] is null only for old-style @published
+ // properties. This fallback is here to make it easier to deprecate the
+ // old-style of published properties, which have bad timing guarantees
+ // (see comment in _PolymerBinding).
+ return _bindOldStylePublishedProperty(name, bindable, oldValue);
+ }
+
+ property.bindable = bindable;
+ var value = bindable.open(property.updateValue);
+
+ if (resolveBindingValue) {
+ // capture A's value if B's value is null or undefined,
+ // otherwise use B's value
+ var v = (value == null ? oldValue : value);
+ if (!identical(value, oldValue)) {
+ bindable.value = value = v;
+ }
+ }
+
+ property.updateValue(value);
+ var o = new _CloseOnlyBinding(property);
+ _observers.add(o);
+ return o;
+ }
+
+ // Dart note: this fallback uses our old-style binding mechanism to be able to
+ // link @published properties with bindings. This mechanism is backwards from
+ // what Javascript does because we can't override the original property. This
+ // workaround also brings some timing issues which are described in detail in
+ // dartbug.com/18343.
+ // TODO(sigmund): deprecate old-style @published properties.
+ _bindOldStylePublishedProperty(Symbol name, Bindable bindable, oldValue) {
+ // capture A's value if B's value is null or undefined,
+ // otherwise use B's value
+ if (bindable.value == null) bindable.value = oldValue;
+
+ var o = new _PolymerBinding(this, name, bindable);
+ _observers.add(o);
+ return o;
+ }
+
+ _getBindingForComputedProperty(Symbol name) {
+ var exprString = element._computed[name];
+ if (exprString == null) return null;
+ var expr = PolymerExpressions.getExpression(exprString);
+ return PolymerExpressions.getBinding(expr, this,
+ globals: element.syntax.globals);
+ }
+
+ createComputedProperties() {
+ var computed = this.element._computed;
+ for (var name in computed.keys) {
+ try {
+ // Dart note: this is done in Javascript by modifying the prototype in
+ // declaration/properties.js, we can't do that, so we do it here.
+ var binding = _getBindingForComputedProperty(name);
+
+ // Follow up note: ideally we would only create the accessor object
+ // here, but some computed properties might depend on others and
+ // evaluating `binding.value` could try to read the value of another
+ // computed property that we haven't created yet. For this reason we
+ // also allow to also create the accessor in [readValue].
+ if (_properties[name] == null) {
+ _properties[name] = new _PropertyAccessor(name, this, binding.value);
+ }
+ bindToAccessor(name, binding);
+ } catch (e) {
+ window.console.error('Failed to create computed property $name'
+ ' (${computed[name]}): $e');
+ }
+ }
}
- void closeObserverList(Iterable<Bindable> observers) {
- for (var o in observers) {
+ // Dart note: to simplify the code above we made registerObserver calls
+ // directly invoke _observers.add/addAll.
+ void closeObservers() {
+ for (var o in _observers) {
if (o != null) o.close();
}
+ _observers = [];
}
/// Bookkeeping observers for memory management.
@@ -800,7 +991,7 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
/// bindProperty(#myProperty,
/// new PathObserver(this, 'myModel.path.to.otherProp'));
/// }
- Bindable bindProperty(Symbol name, Bindable bindable, {oneTime: false}) {
+ Bindable bindProperty(Symbol name, bindableOrValue, {oneTime: false}) {
// Dart note: normally we only reach this code when we know it's a
// property, but if someone uses bindProperty directly they might get a
// NoSuchMethodError either from the getField below, or from the setField
@@ -808,23 +999,20 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
// difference from Polymer.js behavior.
if (_bindLog.isLoggable(Level.FINE)) {
- _bindLog.fine('bindProperty: [$bindable] to [$_name].[$name]');
+ _bindLog.fine('bindProperty: [$bindableOrValue] to [$_name].[$name]');
}
- // capture A's value if B's value is null or undefined,
- // otherwise use B's value
- // TODO(sorvell): need to review, can do with ObserverTransform
- var v = bindable.value;
- if (v == null) {
- bindable.value = smoke.read(this, name);
+ if (oneTime) {
+ if (bindableOrValue is Bindable) {
+ _bindLog.warning('bindProperty: expected non-bindable value '
+ 'on a one-time binding to [$_name].[$name], '
+ 'but found $bindableOrValue.');
+ }
+ smoke.write(this, name, bindableOrValue);
+ return null;
}
- // TODO(jmesserly): we need to fix this -- it doesn't work like Polymer.js
- // bindings. https://code.google.com/p/dart/issues/detail?id=18343
- // apply Polymer two-way reference binding
- //return Observer.bindToInstance(inA, inProperty, observable,
- // resolveBindingValue);
- return new _PolymerBinding(this, name, bindable);
+ return bindToAccessor(name, bindableOrValue, resolveBindingValue: true);
}
/// Attach event listeners on the host (this) element.
@@ -997,9 +1185,7 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
_STYLE_CONTROLLER_SCOPE);
applyStyleToScope(style, scope);
// cache that this style has been applied
- Set styles = _scopeStyles[scope];
- if (styles == null) _scopeStyles[scope] = styles = new Set();
- styles.add('$_name$name');
+ styleCacheForScope(scope).add('$_name$name');
}
Node findStyleScope([node]) {
@@ -1012,9 +1198,23 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
return n;
}
- bool scopeHasNamedStyle(Node scope, String name) {
- Set styles = _scopeStyles[scope];
- return styles != null && styles.contains(name);
+ bool scopeHasNamedStyle(Node scope, String name) =>
+ styleCacheForScope(scope).contains(name);
+
+ Map _polyfillScopeStyleCache = {};
+
+ Set styleCacheForScope(Node scope) {
+ var styles;
+ if (_hasShadowDomPolyfill) {
+ var name = scope is ShadowRoot ? scope.host.localName
+ : (scope as Element).localName;
+ var styles = _polyfillScopeStyleCache[name];
+ if (styles == null) _polyfillScopeStyleCache[name] = styles = new Set();
+ } else {
+ styles = _scopeStyles[scope];
+ if (styles == null) _scopeStyles[scope] = styles = new Set();
+ }
+ return styles;
}
static final _scopeStyles = new Expando();
@@ -1079,12 +1279,14 @@ abstract class Polymer implements Element, Observable, NodeBindExtension {
}
}
-// Dart note: Polymer addresses n-way bindings by metaprogramming: redefine
-// the property on the PolymerElement instance to always get its value from the
-// model@path. We can't replicate this in Dart so we do the next best thing:
-// listen to changes on both sides and update the values.
-// TODO(jmesserly): our approach leads to race conditions in the bindings.
-// See http://code.google.com/p/dart/issues/detail?id=13567
+// Dart note: this is related to _bindOldStylePublishedProperty. Polymer
+// addresses n-way bindings by metaprogramming: redefine the property on the
+// PolymerElement instance to always get its value from the model@path. This is
+// supported in Dart using a new style of @published property declaration using
+// the `readValue` and `writeValue` methods above. In the past we used to work
+// around this by listening to changes on both sides and updating the values.
+// This object provides the hooks to do this.
+// TODO(sigmund,jmesserly): delete after a deprecation period.
class _PolymerBinding extends Bindable {
final Polymer _target;
final Symbol _property;
@@ -1100,6 +1302,8 @@ class _PolymerBinding extends Bindable {
void _updateNode(newValue) {
_lastValue = newValue;
smoke.write(_target, _property, newValue);
+ // Note: we don't invoke emitPropertyChangeRecord here because that's
+ // done by listening on changes on the PolymerElement.
}
void _propertyValueChanged(List<ChangeRecord> records) {
@@ -1127,6 +1331,24 @@ class _PolymerBinding extends Bindable {
}
}
+// Ported from an inline object in instance/properties.js#bindToAccessor.
+class _CloseOnlyBinding extends Bindable {
+ final _PropertyAccessor accessor;
+
+ _CloseOnlyBinding(this.accessor);
+
+ open(callback) {}
+ get value => null;
+ set value(newValue) {}
+ deliver() {}
+
+ void close() {
+ if (accessor.bindable == null) return;
+ accessor.bindable.close();
+ accessor.bindable = null;
+ }
+}
+
bool _toBoolean(value) => null != value && false != value;
final Logger _observeLog = new Logger('polymer.observe');
« no previous file with comments | « pkg/polymer/lib/src/events.dart ('k') | pkg/polymer/lib/src/js/polymer/README.md » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698