Index: polymer/lib/src/instance.dart |
diff --git a/polymer/lib/src/instance.dart b/polymer/lib/src/instance.dart |
deleted file mode 100644 |
index ca46d8db678934e17009c58c825e39abaa81594c..0000000000000000000000000000000000000000 |
--- a/polymer/lib/src/instance.dart |
+++ /dev/null |
@@ -1,1466 +0,0 @@ |
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-part of polymer; |
- |
-/// 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. |
-/// |
-/// For example: |
-/// |
-/// class MyPlaybackElement extends PolymerElement { |
-/// // This will be available as an HTML attribute, for example: |
-/// // |
-/// // <my-playback volume="11"> |
-/// // |
-/// // It will support initialization and data-binding via <template>: |
-/// // |
-/// // <template> |
-/// // <my-playback volume="{{x}}"> |
-/// // </template> |
-/// // |
-/// // If the template is instantiated or given a model, `x` will be |
-/// // used for this field and updated whenever `volume` changes. |
-/// @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 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]. |
-class PublishedProperty extends ObservableProperty { |
- /// Whether the property value should be reflected back to the HTML attribute. |
- final bool reflect; |
- |
- const PublishedProperty({this.reflect: false}); |
-} |
- |
-/// Use this type to observe a property and have the method be called when it |
-/// changes. For example: |
-/// |
-/// @ObserveProperty('foo.bar baz qux') |
-/// validate() { |
-/// // use this.foo.bar, this.baz, and this.qux in validation |
-/// ... |
-/// } |
-/// |
-/// Note that you can observe a property path, and more than a single property |
-/// can be specified in a space-delimited list or as a constant List. |
-class ObserveProperty { |
- final _names; |
- |
- List<String> get names { |
- var n = _names; |
- // TODO(jmesserly): the bogus '$n' is to workaround a dart2js bug, otherwise |
- // it generates an incorrect call site. |
- if (n is String) return '$n'.split(' '); |
- if (n is! Iterable) { |
- throw new UnsupportedError('ObserveProperty takes either an Iterable of ' |
- 'names, or a space separated String, instead of `$n`.'); |
- } |
- return n; |
- } |
- |
- 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. |
-/// |
-/// See [Polymer]. |
-class PolymerElement extends HtmlElement with Polymer, Observable { |
- PolymerElement.created() : super.created() { |
- polymerCreated(); |
- } |
-} |
- |
-/// The mixin class for Polymer elements. It provides convenience features on |
-/// top of the custom elements web standard. |
-/// |
-/// If this class is used as a mixin, |
-/// you must call `polymerCreated()` from the body of your constructor. |
-abstract class Polymer implements Element, Observable, NodeBindExtension { |
- |
- // TODO(jmesserly): should this really be public? |
- /// Regular expression that matches data-bindings. |
- static final bindPattern = new RegExp(r'\{\{([^{}]*)}}'); |
- |
- /// Like [document.register] but for Polymer elements. |
- /// |
- /// Use the [name] to specify custom elment's tag name, for example: |
- /// "fancy-button" if the tag is used as `<fancy-button>`. |
- /// |
- /// The [type] is the type to construct. If not supplied, it defaults to |
- /// [PolymerElement]. |
- // NOTE: this is called "element" in src/declaration/polymer-element.js, and |
- // exported as "Polymer". |
- static void register(String name, [Type type]) { |
- //console.log('registering [' + name + ']'); |
- if (type == null) type = PolymerElement; |
- |
- _typesByName[name] = type; |
- |
- // Dart note: here we notify JS of the element registration. We don't pass |
- // the Dart type because we will handle that in PolymerDeclaration. |
- // See _hookJsPolymerDeclaration for how this is done. |
- PolymerJs.constructor.apply([name]); |
- (js.context['HTMLElement']['register'] as JsFunction) |
- .apply([name, js.context['HTMLElement']['prototype']]); |
- } |
- |
- /// Register a custom element that has no associated `<polymer-element>`. |
- /// Unlike [register] this will always perform synchronous registration and |
- /// by the time this method returns the element will be available using |
- /// [document.createElement] or by modifying the HTML to include the element. |
- static void registerSync(String name, Type type, |
- {String extendsTag, Document doc, Node template}) { |
- |
- // Our normal registration, this will queue up the name->type association. |
- register(name, type); |
- |
- // Build a polymer-element and initialize it to register |
- if (doc == null) doc = document; |
- var poly = doc.createElement('polymer-element'); |
- poly.attributes['name'] = name; |
- if (extendsTag != null) poly.attributes['extends'] = extendsTag; |
- if (template != null) poly.append(template); |
- |
- // TODO(jmesserly): conceptually this is just: |
- // new JsObject.fromBrowserObject(poly).callMethod('init') |
- // |
- // However doing it that way hits an issue with JS-interop in IE10: we get a |
- // JsObject that wraps something other than `poly`, due to improper caching. |
- // By reusing _polymerElementProto that we used for 'register', we can |
- // then call apply on it to invoke init() with the correct `this` pointer. |
- JsFunction init = _polymerElementProto['init']; |
- init.apply([], thisArg: poly); |
- } |
- |
- // Warning for when people try to use `importElements` or `import`. |
- static const String _DYNAMIC_IMPORT_WARNING = 'Dynamically loading html ' |
- 'imports has very limited support right now in dart, see ' |
- 'http://dartbug.com/17873.'; |
- |
- /// Loads the set of HTMLImports contained in `node`. Returns a future that |
- /// resolves when all the imports have been loaded. This method can be used to |
- /// lazily load imports. For example, given a template: |
- /// |
- /// <template> |
- /// <link rel="import" href="my-import1.html"> |
- /// <link rel="import" href="my-import2.html"> |
- /// </template> |
- /// |
- /// Polymer.importElements(template.content) |
- /// .then((_) => print('imports lazily loaded')); |
- /// |
- /// Dart Note: This has very limited support in dart, http://dartbug.com/17873 |
- // Dart Note: From src/lib/import.js For now proxy to the JS methods, |
- // because we want to share the loader with polymer.js for interop purposes. |
- static Future importElements(Node elementOrFragment) { |
- print(_DYNAMIC_IMPORT_WARNING); |
- return PolymerJs.importElements(elementOrFragment); |
- } |
- |
- /// Loads an HTMLImport for each url specified in the `urls` array. Notifies |
- /// when all the imports have loaded by calling the `callback` function |
- /// argument. This method can be used to lazily load imports. For example, |
- /// For example, |
- /// |
- /// Polymer.import(['my-import1.html', 'my-import2.html']) |
- /// .then((_) => print('imports lazily loaded')); |
- /// |
- /// Dart Note: This has very limited support in dart, http://dartbug.com/17873 |
- // Dart Note: From src/lib/import.js. For now proxy to the JS methods, |
- // because we want to share the loader with polymer.js for interop purposes. |
- static Future import(List urls) { |
- print(_DYNAMIC_IMPORT_WARNING); |
- return PolymerJs.import(urls); |
- } |
- |
- /// Deprecated: Use `import` instead. |
- @deprecated |
- static Future importUrls(List urls) { |
- return import(urls); |
- } |
- |
- /// Completes when polymer js is ready. |
- static final Completer _onReady = new Completer(); |
- |
- /// Completes when all initialization is done. |
- static final Completer _onInitDone = new Completer(); |
- |
- /// Future indicating that the Polymer library has been loaded and is ready |
- /// for use. |
- static Future get onReady => |
- Future.wait([_onReady.future, _onInitDone.future]); |
- |
- /// Returns a list of elements that have had polymer-elements created but |
- /// are not yet ready to register. The list is an array of element |
- /// definitions. |
- static List<Element> get waitingFor => PolymerJs.waitingFor; |
- |
- /// Forces polymer to register any pending elements. Can be used to abort |
- /// waiting for elements that are partially defined. |
- static forceReady([int timeout]) => PolymerJs.forceReady(timeout); |
- |
- /// The most derived `<polymer-element>` declaration for this element. |
- PolymerDeclaration get element => _element; |
- PolymerDeclaration _element; |
- |
- /// Deprecated: use [element] instead. |
- @deprecated PolymerDeclaration get declaration => _element; |
- |
- Map<String, StreamSubscription> _namedObservers; |
- List<Bindable> _observers = []; |
- |
- bool _unbound; // lazy-initialized |
- PolymerJob _unbindAllJob; |
- |
- CompoundObserver _propertyObserver; |
- bool _readied = false; |
- |
- JsObject _jsElem; |
- |
- /// Returns the object that should be used as the event controller for |
- /// event bindings in this element's template. If set, this will override the |
- /// normal controller lookup. |
- // TODO(jmesserly): we need to use a JS-writable property as our backing |
- // store, because of elements such as: |
- // https://github.com/Polymer/core-overlay/blob/eeb14853/core-overlay-layer.html#L78 |
- get eventController => _jsElem['eventController']; |
- set eventController(value) { |
- _jsElem['eventController'] = value; |
- } |
- |
- bool get hasBeenAttached => _hasBeenAttached; |
- bool _hasBeenAttached = false; |
- |
- /// Gets the shadow root associated with the corresponding custom element. |
- /// |
- /// This is identical to [shadowRoot], unless there are multiple levels of |
- /// inheritance and they each have their own shadow root. For example, |
- /// this can happen if the base class and subclass both have `<template>` tags |
- /// in their `<polymer-element>` tags. |
- // TODO(jmesserly): should expose this as an immutable map. |
- // Similar issue as $. |
- final Map<String, ShadowRoot> shadowRoots = |
- new LinkedHashMap<String, ShadowRoot>(); |
- |
- /// Map of items in the shadow root(s) by their [Element.id]. |
- // TODO(jmesserly): various issues: |
- // * wrap in UnmodifiableMapView? |
- // * should we have an object that implements noSuchMethod? |
- // * should the map have a key order (e.g. LinkedHash or SplayTree)? |
- // * should this be a live list? Polymer doesn't, maybe due to JS limitations? |
- // Note: this is observable to support $['someId'] being used in templates. |
- // The template is stamped before $ is populated, so we need observation if |
- // we want it to be usable in bindings. |
- final Map<String, dynamic> $ = new ObservableMap<String, dynamic>(); |
- |
- /// Use to override the default syntax for polymer-elements. |
- /// By default this will be null, which causes [instanceTemplate] to use |
- /// the template's bindingDelegate or the [element.syntax], in that order. |
- PolymerExpressions get syntax => null; |
- |
- bool get _elementPrepared => _element != null; |
- |
- /// Retrieves the custom element name. It should be used instead |
- /// of localName, see: https://github.com/Polymer/polymer-dev/issues/26 |
- String get _name { |
- if (_element != null) return _element.name; |
- var isAttr = attributes['is']; |
- return (isAttr == null || isAttr == '') ? localName : isAttr; |
- } |
- |
- /// By default the data bindings will be cleaned up when this custom element |
- /// is detached from the document. Overriding this to return `true` will |
- /// 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. |
- /// |
- /// If this class is a superclass, calling `super.created()` is sufficient. |
- void polymerCreated() { |
- var t = nodeBind(this).templateInstance; |
- if (t != null && t.model != null) { |
- window.console.warn('Attributes on $_name were data bound ' |
- 'prior to Polymer upgrading the element. This may result in ' |
- 'incorrect binding types.'); |
- } |
- prepareElement(); |
- if (!isTemplateStagingDocument(ownerDocument)) { |
- _makeElementReady(); |
- } |
- } |
- |
- /// *Deprecated* use [shadowRoots] instead. |
- @deprecated |
- ShadowRoot getShadowRoot(String customTagName) => shadowRoots[customTagName]; |
- |
- void prepareElement() { |
- if (_elementPrepared) { |
- window.console.warn('Element already prepared: $_name'); |
- return; |
- } |
- _initJsObject(); |
- // Dart note: get the corresponding <polymer-element> declaration. |
- _element = _getDeclaration(_name); |
- // install property storage |
- createPropertyObserver(); |
- openPropertyObserver(); |
- // install boilerplate attributes |
- copyInstanceAttributes(); |
- // process input attributes |
- takeAttributes(); |
- // add event listeners |
- addHostListeners(); |
- } |
- |
- /// Initialize JS interop for this element. For now we just initialize the |
- /// JsObject, but in the future we could also initialize JS APIs here. |
- _initJsObject() { |
- _jsElem = new JsObject.fromBrowserObject(this); |
- } |
- |
- /// Deprecated: This is no longer a public method. |
- @deprecated |
- makeElementReady() => _makeElementReady(); |
- |
- _makeElementReady() { |
- if (_readied) return; |
- _readied = true; |
- createComputedProperties(); |
- |
- parseDeclarations(_element); |
- // NOTE: Support use of the `unresolved` attribute to help polyfill |
- // custom elements' `:unresolved` feature. |
- attributes.remove('unresolved'); |
- // user entry point |
- _readyLog.info(() => '[$this]: ready'); |
- ready(); |
- } |
- |
- /// Lifecycle method called when the element has populated it's `shadowRoot`, |
- /// prepared data-observation, and made itself ready for API interaction. |
- /// To wait until the element has been attached to the default view, use |
- /// [attached] or [domReady]. |
- void ready() {} |
- |
- /// Implement to access custom elements in dom descendants, ancestors, |
- /// or siblings. Because custom elements upgrade in document order, |
- /// elements accessed in `ready` or `attached` may not be upgraded. When |
- /// `domReady` is called, all registered custom elements are guaranteed |
- /// to have been upgraded. |
- void domReady() {} |
- |
- void attached() { |
- if (!_elementPrepared) { |
- // Dart specific message for a common issue. |
- throw new StateError('polymerCreated was not called for custom element ' |
- '$_name, this should normally be done in the .created() if Polymer ' |
- 'is used as a mixin.'); |
- } |
- |
- cancelUnbindAll(); |
- if (!hasBeenAttached) { |
- _hasBeenAttached = true; |
- async((_) => domReady()); |
- } |
- } |
- |
- void detached() { |
- if (!preventDispose) asyncUnbindAll(); |
- } |
- |
- /// Walks the prototype-chain of this element and allows specific |
- /// classes a chance to process static declarations. |
- /// |
- /// In particular, each polymer-element has it's own `template`. |
- /// `parseDeclarations` is used to accumulate all element `template`s |
- /// from an inheritance chain. |
- /// |
- /// `parseDeclaration` static methods implemented in the chain are called |
- /// recursively, oldest first, with the `<polymer-element>` associated |
- /// with the current prototype passed as an argument. |
- /// |
- /// An element may override this method to customize shadow-root generation. |
- void parseDeclarations(PolymerDeclaration declaration) { |
- if (declaration != null) { |
- parseDeclarations(declaration.superDeclaration); |
- parseDeclaration(declaration.element); |
- } |
- } |
- |
- /// Perform init-time actions based on static information in the |
- /// `<polymer-element>` instance argument. |
- /// |
- /// For example, the standard implementation locates the template associated |
- /// with the given `<polymer-element>` and stamps it into a shadow-root to |
- /// implement shadow inheritance. |
- /// |
- /// An element may override this method for custom behavior. |
- void parseDeclaration(Element elementElement) { |
- var template = fetchTemplate(elementElement); |
- |
- if (template != null) { |
- var root = shadowFromTemplate(template); |
- |
- var name = elementElement.attributes['name']; |
- if (name == null) return; |
- shadowRoots[name] = root; |
- } |
- } |
- |
- /// Given a `<polymer-element>`, find an associated template (if any) to be |
- /// used for shadow-root generation. |
- /// |
- /// An element may override this method for custom behavior. |
- Element fetchTemplate(Element elementElement) => |
- elementElement.querySelector('template'); |
- |
- /// Utility function that stamps a `<template>` into light-dom. |
- Node lightFromTemplate(Element template, [Node refNode]) { |
- if (template == null) return null; |
- |
- // TODO(sorvell): mark this element as an event controller so that |
- // event listeners on bound nodes inside it will be called on it. |
- // Note, the expectation here is that events on all descendants |
- // should be handled by this element. |
- eventController = this; |
- |
- // stamp template |
- // which includes parsing and applying MDV bindings before being |
- // inserted (to avoid {{}} in attribute values) |
- // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
- var dom = instanceTemplate(template); |
- // append to shadow dom |
- if (refNode != null) { |
- append(dom); |
- } else { |
- insertBefore(dom, refNode); |
- } |
- // perform post-construction initialization tasks on ahem, light root |
- shadowRootReady(this); |
- // return the created shadow root |
- return dom; |
- } |
- |
- /// Utility function that creates a shadow root from a `<template>`. |
- /// |
- /// The base implementation will return a [ShadowRoot], but you can replace it |
- /// with your own code and skip ShadowRoot creation. In that case, you should |
- /// return `null`. |
- /// |
- /// In your overridden method, you can use [instanceTemplate] to stamp the |
- /// template and initialize data binding, and [shadowRootReady] to intialize |
- /// other Polymer features like event handlers. It is fine to call |
- /// shadowRootReady with a node other than a ShadowRoot such as with `this`. |
- ShadowRoot shadowFromTemplate(Element template) { |
- if (template == null) return null; |
- // make a shadow root |
- var root = createShadowRoot(); |
- // stamp template |
- // which includes parsing and applying MDV bindings before being |
- // inserted (to avoid {{}} in attribute values) |
- // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
- var dom = instanceTemplate(template); |
- // append to shadow dom |
- root.append(dom); |
- // perform post-construction initialization tasks on shadow root |
- shadowRootReady(root); |
- // return the created shadow root |
- return root; |
- } |
- |
- void shadowRootReady(Node root) { |
- // locate nodes with id and store references to them in this.$ hash |
- marshalNodeReferences(root); |
- } |
- |
- /// Locate nodes with id and store references to them in [$] hash. |
- void marshalNodeReferences(Node root) { |
- if (root == null) return; |
- for (var n in (root as dynamic).querySelectorAll('[id]')) { |
- $[n.id] = n; |
- } |
- } |
- |
- void attributeChanged(String name, String oldValue, String newValue) { |
- if (name != 'class' && name != 'style') { |
- attributeToProperty(name, newValue); |
- } |
- } |
- |
- // TODO(jmesserly): this could be a top level method. |
- /// Returns a future when `node` changes, or when its children or subtree |
- /// changes. |
- /// |
- /// Use [MutationObserver] if you want to listen to a stream of changes. |
- Future<List<MutationRecord>> onMutation(Node node) { |
- var completer = new Completer(); |
- new MutationObserver((mutations, observer) { |
- observer.disconnect(); |
- completer.complete(mutations); |
- })..observe(node, childList: true, subtree: true); |
- return completer.future; |
- } |
- |
- // copy attributes defined in the element declaration to the instance |
- // e.g. <polymer-element name="x-foo" tabIndex="0"> tabIndex is copied |
- // to the element instance here. |
- void copyInstanceAttributes() { |
- _element._instanceAttributes.forEach((name, value) { |
- attributes.putIfAbsent(name, () => value); |
- }); |
- } |
- |
- void takeAttributes() { |
- if (_element._publishLC == null) return; |
- attributes.forEach(attributeToProperty); |
- } |
- |
- /// If attribute [name] is mapped to a property, deserialize |
- /// [value] into that property. |
- void attributeToProperty(String name, String value) { |
- // try to match this attribute to a property (attributes are |
- // all lower-case, so this is case-insensitive search) |
- var decl = propertyForAttribute(name); |
- if (decl == null) return; |
- |
- // filter out 'mustached' values, these are to be |
- // replaced with bound-data and are not yet values |
- // themselves. |
- if (value == null || value.contains(Polymer.bindPattern)) return; |
- |
- final currentValue = smoke.read(this, decl.name); |
- |
- // deserialize Boolean or Number values from attribute |
- var type = decl.type; |
- if ((type == Object || type == dynamic) && currentValue != null) { |
- // Attempt to infer field type from the current value. |
- type = currentValue.runtimeType; |
- } |
- final newValue = deserializeValue(value, currentValue, type); |
- |
- // only act if the value has changed |
- if (!identical(newValue, currentValue)) { |
- // install new value (has side-effects) |
- smoke.write(this, decl.name, newValue); |
- } |
- } |
- |
- /// Return the published property matching name, or null. |
- // TODO(jmesserly): should we just return Symbol here? |
- smoke.Declaration propertyForAttribute(String name) { |
- final publishLC = _element._publishLC; |
- if (publishLC == null) return null; |
- return publishLC[name]; |
- } |
- |
- /// Convert representation of [value] based on [type] and [currentValue]. |
- Object deserializeValue(String value, Object currentValue, Type type) => |
- deserialize.deserializeValue(value, currentValue, type); |
- |
- String serializeValue(Object value) { |
- if (value == null) return null; |
- |
- if (value is bool) { |
- return _toBoolean(value) ? '' : null; |
- } else if (value is String || value is num) { |
- return '$value'; |
- } |
- return null; |
- } |
- |
- void reflectPropertyToAttribute(String path) { |
- // TODO(sjmiles): consider memoizing this |
- // try to intelligently serialize property value |
- final propValue = new PropertyPath(path).getValueFrom(this); |
- final serializedValue = serializeValue(propValue); |
- // boolean properties must reflect as boolean attributes |
- if (serializedValue != null) { |
- attributes[path] = serializedValue; |
- // TODO(sorvell): we should remove attr for all properties |
- // that have undefined serialization; however, we will need to |
- // refine the attr reflection system to achieve this; pica, for example, |
- // relies on having inferredType object properties not removed as |
- // attrs. |
- } else if (propValue is bool) { |
- attributes.remove(path); |
- } |
- } |
- |
- /// Creates the document fragment to use for each instance of the custom |
- /// element, given the `<template>` node. By default this is equivalent to: |
- /// |
- /// templateBind(template).createInstance(this, polymerSyntax); |
- /// |
- /// Where polymerSyntax is a singleton [PolymerExpressions] instance. |
- /// |
- /// You can override this method to change the instantiation behavior of the |
- /// template, for example to use a different data-binding syntax. |
- DocumentFragment instanceTemplate(Element template) { |
- // ensure template is decorated (lets things like <tr template ...> work) |
- TemplateBindExtension.decorate(template); |
- var syntax = this.syntax; |
- var t = templateBind(template); |
- if (syntax == null && t.bindingDelegate == null) { |
- syntax = element.syntax; |
- } |
- var dom = t.createInstance(this, syntax); |
- _observers.addAll(getTemplateInstanceBindings(dom)); |
- return dom; |
- } |
- |
- /// Called by TemplateBinding/NodeBind to setup a binding to the given |
- /// property. It's overridden here to support property bindings in addition to |
- /// attribute bindings that are supported by default. |
- Bindable bind(String name, bindable, {bool oneTime: false}) { |
- var decl = propertyForAttribute(name); |
- if (decl == null) { |
- // Cannot call super.bind because template_binding is its own package |
- return nodeBindFallback(this).bind(name, bindable, oneTime: oneTime); |
- } else { |
- // use n-way Polymer binding |
- var observer = bindProperty(decl.name, bindable, oneTime: oneTime); |
- // NOTE: reflecting binding information is typically required only for |
- // tooling. It has a performance cost so it's opt-in in Node.bind. |
- if (enableBindingsReflection && observer != null) { |
- // Dart note: this is not needed because of how _PolymerBinding works. |
- //observer.path = bindable.path_; |
- _recordBinding(name, observer); |
- } |
- var reflect = _element._reflect; |
- |
- // Get back to the (possibly camel-case) name for the property. |
- var propName = smoke.symbolToName(decl.name); |
- if (reflect != null && reflect.contains(propName)) { |
- reflectPropertyToAttribute(propName); |
- } |
- return observer; |
- } |
- } |
- |
- _recordBinding(String name, observer) { |
- if (bindings == null) bindings = {}; |
- this.bindings[name] = observer; |
- } |
- |
- /// Called by TemplateBinding when all bindings on an element have been |
- /// executed. This signals that all element inputs have been gathered and it's |
- /// safe to ready the element, create shadow-root and start data-observation. |
- bindFinished() => _makeElementReady(); |
- |
- Map<String, Bindable> get bindings => nodeBindFallback(this).bindings; |
- set bindings(Map value) { |
- nodeBindFallback(this).bindings = value; |
- } |
- |
- TemplateInstance get templateInstance => |
- nodeBindFallback(this).templateInstance; |
- |
- /// Called at detached time to signal that an element's bindings should be |
- /// cleaned up. This is done asynchronously so that users have the chance to |
- /// call `cancelUnbindAll` to prevent unbinding. |
- void asyncUnbindAll() { |
- if (_unbound == true) return; |
- _unbindLog.fine(() => '[$_name] asyncUnbindAll'); |
- _unbindAllJob = scheduleJob(_unbindAllJob, unbindAll); |
- } |
- |
- /// This method should rarely be used and only if `cancelUnbindAll` has been |
- /// called to prevent element unbinding. In this case, the element's bindings |
- /// will not be automatically cleaned up and it cannot be garbage collected by |
- /// by the system. If memory pressure is a concern or a large amount of |
- /// elements need to be managed in this way, `unbindAll` can be called to |
- /// deactivate the element's bindings and allow its memory to be reclaimed. |
- void unbindAll() { |
- if (_unbound == true) return; |
- closeObservers(); |
- closeNamedObservers(); |
- _unbound = true; |
- } |
- |
- //// Call in `detached` to prevent the element from unbinding when it is |
- //// detached from the dom. The element is unbound as a cleanup step that |
- //// allows its memory to be reclaimed. If `cancelUnbindAll` is used, consider |
- /// calling `unbindAll` when the element is no longer needed. This will allow |
- /// its memory to be reclaimed. |
- void cancelUnbindAll() { |
- if (_unbound == true) { |
- _unbindLog |
- .warning(() => '[$_name] already unbound, cannot cancel unbindAll'); |
- return; |
- } |
- _unbindLog.fine(() => '[$_name] cancelUnbindAll'); |
- if (_unbindAllJob != null) { |
- _unbindAllJob.stop(); |
- _unbindAllJob = null; |
- } |
- } |
- |
- static void _forNodeTree(Node node, void callback(Node node)) { |
- if (node == null) return; |
- |
- callback(node); |
- for (var child = node.firstChild; child != null; child = child.nextNode) { |
- _forNodeTree(child, callback); |
- } |
- } |
- |
- /// Creates a CompoundObserver to observe property changes. |
- /// NOTE, this is only done if there are any properties in the `_observe` |
- /// object. |
- void createPropertyObserver() { |
- final observe = _element._observe; |
- if (observe != null) { |
- var o = _propertyObserver = new CompoundObserver(); |
- // keep track of property observer so we can shut it down |
- _observers.add(o); |
- |
- for (var path in observe.keys) { |
- o.addPath(this, path); |
- |
- // TODO(jmesserly): on the Polymer side it doesn't look like they |
- // will observe arrays unless it is a length == 1 path. |
- observeArrayValue(path, path.getValueFrom(this), null); |
- } |
- } |
- } |
- |
- /// Start observing property changes. |
- void openPropertyObserver() { |
- if (_propertyObserver != null) { |
- _propertyObserver.open(notifyPropertyChanges); |
- } |
- |
- // 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(_propertyChangeWorkaround); |
- } |
- } |
- |
- /// Handler for property changes; routes changes to observing methods. |
- /// Note: array valued properties are observed for array splices. |
- void notifyPropertyChanges(List newValues, Map oldValues, List paths) { |
- final observe = _element._observe; |
- final called = new HashSet(); |
- |
- oldValues.forEach((i, oldValue) { |
- final newValue = newValues[i]; |
- |
- // Date note: we don't need any special checking for null and undefined. |
- |
- // note: paths is of form [object, path, object, path] |
- final path = paths[2 * i + 1]; |
- if (observe == null) return; |
- |
- var methods = observe[path]; |
- if (methods == null) return; |
- |
- for (var method in methods) { |
- if (!called.add(method)) continue; // don't invoke more than once. |
- |
- observeArrayValue(path, newValue, oldValue); |
- // Dart note: JS passes "arguments", so we pass along our args. |
- // TODO(sorvell): call method with the set of values it's expecting; |
- // e.g. 'foo bar': 'invalidate' expects the new and old values for |
- // foo and bar. Currently we give only one of these and then |
- // deliver all the arguments. |
- smoke.invoke( |
- this, method, [oldValue, newValue, newValues, oldValues, paths], |
- adjust: true); |
- } |
- }); |
- } |
- |
- /// Force any pending property changes to synchronously deliver to handlers |
- /// specified in the `observe` object. |
- /// Note: normally changes are processed at microtask time. |
- /// |
- // Dart note: had to rename this to avoid colliding with |
- // Observable.deliverChanges. Even worse, super calls aren't possible or |
- // it prevents Polymer from being a mixin, so we can't override it even if |
- // we wanted to. |
- void deliverPropertyChanges() { |
- if (_propertyObserver != null) { |
- _propertyObserver.deliver(); |
- } |
- } |
- |
- // 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; |
- |
- 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, record.newValue, record.oldValue); |
- } |
- } |
- |
- void _propertyChange(Symbol nameSymbol, newValue, oldValue) { |
- _watchLog.info( |
- () => '[$this]: $nameSymbol changed from: $oldValue to: $newValue'); |
- var name = smoke.symbolToName(nameSymbol); |
- var reflect = _element._reflect; |
- if (reflect != null && reflect.contains(name)) { |
- reflectPropertyToAttribute(name); |
- } |
- } |
- |
- void observeArrayValue(PropertyPath name, Object value, Object old) { |
- final observe = _element._observe; |
- if (observe == null) return; |
- |
- // we only care if there are registered side-effects |
- var callbacks = observe[name]; |
- if (callbacks == null) return; |
- |
- // if we are observing the previous value, stop |
- if (old is ObservableList) { |
- _observeLog.fine(() => '[$_name] observeArrayValue: unregister $name'); |
- |
- closeNamedObserver('${name}__array'); |
- } |
- // if the new value is an array, begin observing it |
- if (value is ObservableList) { |
- _observeLog.fine(() => '[$_name] observeArrayValue: register $name'); |
- var sub = value.listChanges.listen((changes) { |
- for (var callback in callbacks) { |
- smoke.invoke(this, callback, [changes], adjust: true); |
- } |
- }); |
- registerNamedObserver('${name}__array', sub); |
- } |
- } |
- |
- emitPropertyChangeRecord(Symbol name, newValue, oldValue) { |
- if (identical(oldValue, newValue)) return; |
- _propertyChange(name, newValue, oldValue); |
- } |
- |
- 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'); |
- } |
- } |
- } |
- |
- // 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. |
- void registerNamedObserver(String name, StreamSubscription sub) { |
- if (_namedObservers == null) { |
- _namedObservers = new Map<String, StreamSubscription>(); |
- } |
- _namedObservers[name] = sub; |
- } |
- |
- bool closeNamedObserver(String name) { |
- var sub = _namedObservers.remove(name); |
- if (sub == null) return false; |
- sub.cancel(); |
- return true; |
- } |
- |
- void closeNamedObservers() { |
- if (_namedObservers == null) return; |
- for (var sub in _namedObservers.values) { |
- if (sub != null) sub.cancel(); |
- } |
- _namedObservers.clear(); |
- _namedObservers = null; |
- } |
- |
- /// Bind the [name] property in this element to [bindable]. *Note* in Dart it |
- /// is necessary to also define the field: |
- /// |
- /// var myProperty; |
- /// |
- /// ready() { |
- /// super.ready(); |
- /// bindProperty(#myProperty, |
- /// new PathObserver(this, 'myModel.path.to.otherProp')); |
- /// } |
- 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 |
- // inside PolymerBinding. That doesn't seem unreasonable, but it's a slight |
- // difference from Polymer.js behavior. |
- |
- _bindLog.fine(() => 'bindProperty: [$bindableOrValue] to [$_name].[$name]'); |
- |
- if (oneTime) { |
- if (bindableOrValue is Bindable) { |
- _bindLog.warning(() => |
- 'bindProperty: expected non-bindable value n a one-time binding to ' |
- '[$_name].[$name], but found $bindableOrValue.'); |
- } |
- smoke.write(this, name, bindableOrValue); |
- return null; |
- } |
- |
- return bindToAccessor(name, bindableOrValue, resolveBindingValue: true); |
- } |
- |
- /// Attach event listeners on the host (this) element. |
- void addHostListeners() { |
- var events = _element._eventDelegates; |
- if (events.isEmpty) return; |
- |
- _eventsLog.fine(() => '[$_name] addHostListeners: $events'); |
- |
- // NOTE: host events look like bindings but really are not; |
- // (1) we don't want the attribute to be set and (2) we want to support |
- // multiple event listeners ('host' and 'instance') and Node.bind |
- // by default supports 1 thing being bound. |
- events.forEach((type, methodName) { |
- // Dart note: the getEventHandler method is on our PolymerExpressions. |
- PolymerGesturesJs.addEventListener( |
- this, type, |
- Zone.current.bindUnaryCallback( |
- element.syntax.getEventHandler(this, this, methodName))); |
- }); |
- } |
- |
- /// Calls [methodOrCallback] with [args] if it is a closure, otherwise, treat |
- /// it as a method name in [object], and invoke it. |
- void dispatchMethod(object, callbackOrMethod, List args) { |
- _eventsLog.info(() => '>>> [$_name]: dispatch $callbackOrMethod'); |
- |
- if (callbackOrMethod is Function) { |
- int maxArgs = smoke.maxArgs(callbackOrMethod); |
- if (maxArgs == -1) { |
- _eventsLog.warning( |
- 'invalid callback: expected callback of 0, 1, 2, or 3 arguments'); |
- } |
- args.length = maxArgs; |
- Function.apply(callbackOrMethod, args); |
- } else if (callbackOrMethod is String) { |
- smoke.invoke(object, smoke.nameToSymbol(callbackOrMethod), args, |
- adjust: true); |
- } else { |
- _eventsLog.warning('invalid callback'); |
- } |
- |
- _eventsLog.fine(() => '<<< [$_name]: dispatch $callbackOrMethod'); |
- } |
- |
- /// Call [methodName] method on this object with [args]. |
- invokeMethod(Symbol methodName, List args) => |
- smoke.invoke(this, methodName, args, adjust: true); |
- |
- /// Invokes a function asynchronously. |
- /// This will call `Polymer.flush()` and then return a `new Timer` |
- /// with the provided [method] and [timeout]. |
- /// |
- /// If you would prefer to run the callback using |
- /// [window.requestAnimationFrame], see the [async] method. |
- /// |
- /// To cancel, call [Timer.cancel] on the result of this method. |
- Timer asyncTimer(void method(), Duration timeout) { |
- // Dart note: "async" is split into 2 methods so it can have a sensible type |
- // signatures. Also removed the various features that don't make sense in a |
- // Dart world, like binding to "this" and taking arguments list. |
- |
- // when polyfilling Object.observe, ensure changes |
- // propagate before executing the async method |
- scheduleMicrotask(Observable.dirtyCheck); |
- PolymerJs.flush(); // for polymer-js interop |
- return new Timer(timeout, method); |
- } |
- |
- /// Invokes a function asynchronously. The context of the callback function is |
- /// function is bound to 'this' automatically. Returns a handle which may be |
- /// passed to cancelAsync to cancel the asynchronous call. |
- /// |
- /// If you would prefer to run the callback after a given duration, see |
- /// the [asyncTimer] method. |
- /// |
- /// If you would like to cancel this, use [cancelAsync]. |
- int async(RequestAnimationFrameCallback method) { |
- // when polyfilling Object.observe, ensure changes |
- // propagate before executing the async method |
- scheduleMicrotask(Observable.dirtyCheck); |
- PolymerJs.flush(); // for polymer-js interop |
- return window.requestAnimationFrame(method); |
- } |
- |
- /// Cancel an operation scheduled by [async]. |
- void cancelAsync(int id) => window.cancelAnimationFrame(id); |
- |
- /// Fire a [CustomEvent] targeting [onNode], or `this` if onNode is not |
- /// supplied. Returns the new event. |
- CustomEvent fire(String type, |
- {Object detail, Node onNode, bool canBubble, bool cancelable}) { |
- var node = onNode != null ? onNode : this; |
- var event = new CustomEvent(type, |
- canBubble: canBubble != null ? canBubble : true, |
- cancelable: cancelable != null ? cancelable : true, |
- detail: detail); |
- node.dispatchEvent(event); |
- return event; |
- } |
- |
- /// Fire an event asynchronously. See [async] and [fire]. |
- asyncFire(String type, {Object detail, Node toNode, bool canBubble}) { |
- // TODO(jmesserly): I'm not sure this method adds much in Dart, it's easy to |
- // add "() =>" |
- async((x) => |
- fire(type, detail: detail, onNode: toNode, canBubble: canBubble)); |
- } |
- |
- /// Remove [className] from [old], add class to [anew], if they exist. |
- void classFollows(Element anew, Element old, String className) { |
- if (old != null) { |
- old.classes.remove(className); |
- } |
- if (anew != null) { |
- anew.classes.add(className); |
- } |
- } |
- |
- /// Installs external stylesheets and <style> elements with the attribute |
- /// polymer-scope='controller' into the scope of element. This is intended |
- /// to be called during custom element construction. |
- void installControllerStyles() { |
- var scope = findStyleScope(); |
- if (scope != null && !scopeHasNamedStyle(scope, localName)) { |
- // allow inherited controller styles |
- var decl = _element; |
- var cssText = new StringBuffer(); |
- while (decl != null) { |
- cssText.write(decl.cssTextForScope(_STYLE_CONTROLLER_SCOPE)); |
- decl = decl.superDeclaration; |
- } |
- if (cssText.isNotEmpty) { |
- installScopeCssText('$cssText', scope); |
- } |
- } |
- } |
- |
- void installScopeStyle(style, [String name, Node scope]) { |
- if (scope == null) scope = findStyleScope(); |
- if (name == null) name = ''; |
- |
- if (scope != null && !scopeHasNamedStyle(scope, '$_name$name')) { |
- var cssText = new StringBuffer(); |
- if (style is Iterable) { |
- for (var s in style) { |
- cssText |
- ..writeln(s.text) |
- ..writeln(); |
- } |
- } else { |
- cssText = (style as Node).text; |
- } |
- installScopeCssText('$cssText', scope, name); |
- } |
- } |
- |
- void installScopeCssText(String cssText, [Node scope, String name]) { |
- if (scope == null) scope = findStyleScope(); |
- if (name == null) name = ''; |
- |
- if (scope == null) return; |
- |
- if (_hasShadowDomPolyfill) { |
- cssText = _shimCssText(cssText, scope is ShadowRoot ? scope.host : null); |
- } |
- var style = element.cssTextToScopeStyle(cssText, _STYLE_CONTROLLER_SCOPE); |
- applyStyleToScope(style, scope); |
- // cache that this style has been applied |
- styleCacheForScope(scope).add('$_name$name'); |
- } |
- |
- Node findStyleScope([node]) { |
- // find the shadow root that contains this element |
- var n = node; |
- if (n == null) n = this; |
- while (n.parentNode != null) { |
- n = n.parentNode; |
- } |
- return n; |
- } |
- |
- 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(); |
- |
- static String _shimCssText(String cssText, [Element host]) { |
- var name = ''; |
- var is_ = false; |
- if (host != null) { |
- name = host.localName; |
- is_ = host.attributes.containsKey('is'); |
- } |
- var selector = _ShadowCss.callMethod('makeScopeSelector', [name, is_]); |
- return _ShadowCss.callMethod('shimCssText', [cssText, selector]); |
- } |
- |
- static void applyStyleToScope(StyleElement style, Node scope) { |
- if (style == null) return; |
- |
- if (scope == document) scope = document.head; |
- |
- if (_hasShadowDomPolyfill) scope = document.head; |
- |
- // TODO(sorvell): necessary for IE |
- // see https://connect.microsoft.com/IE/feedback/details/790212/ |
- // cloning-a-style-element-and-adding-to-document-produces |
- // -unexpected-result#details |
- // var clone = style.cloneNode(true); |
- var clone = new StyleElement()..text = style.text; |
- |
- var attr = style.attributes[_STYLE_SCOPE_ATTRIBUTE]; |
- if (attr != null) { |
- clone.attributes[_STYLE_SCOPE_ATTRIBUTE] = attr; |
- } |
- |
- // TODO(sorvell): probably too brittle; try to figure out |
- // where to put the element. |
- var refNode = scope.firstChild; |
- if (scope == document.head) { |
- var selector = 'style[$_STYLE_SCOPE_ATTRIBUTE]'; |
- var styleElement = document.head.querySelectorAll(selector); |
- if (styleElement.isNotEmpty) { |
- refNode = styleElement.last.nextElementSibling; |
- } |
- } |
- scope.insertBefore(clone, refNode); |
- } |
- |
- /// Invoke [callback] in [wait], unless the job is re-registered, |
- /// which resets the timer. If [wait] is not supplied, this will use |
- /// [window.requestAnimationFrame] instead of a [Timer]. |
- /// |
- /// For example: |
- /// |
- /// _myJob = Polymer.scheduleJob(_myJob, callback); |
- /// |
- /// Returns the newly created job. |
- // Dart note: renamed to scheduleJob to be a bit more consistent with Dart. |
- PolymerJob scheduleJob(PolymerJob job, void callback(), [Duration wait]) { |
- if (job == null) job = new PolymerJob._(); |
- // Dart note: made start smarter, so we don't need to call stop. |
- return job..start(callback, wait); |
- } |
- |
- // Deprecated: Please use injectBoundHtml. |
- @deprecated |
- DocumentFragment injectBoundHTML(String html, [Element element]) => |
- injectBoundHtml(html, element: element); |
- |
- /// Inject HTML which contains markup bound to this element into |
- /// a target element (replacing target element content). |
- DocumentFragment injectBoundHtml(String html, {Element element, |
- NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { |
- var template = new TemplateElement() |
- ..setInnerHtml(html, validator: validator, treeSanitizer: treeSanitizer); |
- var fragment = this.instanceTemplate(template); |
- if (element != null) { |
- element.text = ''; |
- element.append(fragment); |
- } |
- return fragment; |
- } |
-} |
- |
-// 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; |
- final Bindable _bindable; |
- StreamSubscription _sub; |
- Object _lastValue; |
- |
- _PolymerBinding(this._target, this._property, this._bindable) { |
- _sub = _target.changes.listen(_propertyValueChanged); |
- _updateNode(open(_updateNode)); |
- } |
- |
- 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) { |
- for (var record in records) { |
- if (record is PropertyChangeRecord && record.name == _property) { |
- final newValue = smoke.read(_target, _property); |
- if (!identical(_lastValue, newValue)) { |
- this.value = newValue; |
- } |
- return; |
- } |
- } |
- } |
- |
- open(callback(value)) => _bindable.open(callback); |
- get value => _bindable.value; |
- set value(newValue) => _bindable.value = newValue; |
- |
- void close() { |
- if (_sub != null) { |
- _sub.cancel(); |
- _sub = null; |
- } |
- _bindable.close(); |
- } |
-} |
- |
-// 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'); |
-final Logger _eventsLog = new Logger('polymer.events'); |
-final Logger _unbindLog = new Logger('polymer.unbind'); |
-final Logger _bindLog = new Logger('polymer.bind'); |
-final Logger _watchLog = new Logger('polymer.watch'); |
-final Logger _readyLog = new Logger('polymer.ready'); |
- |
-final Expando _eventHandledTable = new Expando<Set<Node>>(); |