Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of polymer; | 5 part of polymer; |
| 6 | 6 |
| 7 /// Use this annotation to publish a field as an attribute. For example: | 7 /// Use this annotation to publish a field as an attribute. |
| 8 /// | |
| 9 /// You can also use [PublishedProperty] to provide additional information, | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
nit: remove extra space (use _ _ [...)
Jennifer Messerly
2014/06/04 04:42:16
Done.
| |
| 10 /// such as automatically syncing the property back to the attribute. | |
| 11 /// | |
| 12 /// For example: | |
| 8 /// | 13 /// |
| 9 /// class MyPlaybackElement extends PolymerElement { | 14 /// class MyPlaybackElement extends PolymerElement { |
| 10 /// // This will be available as an HTML attribute, for example: | 15 /// // This will be available as an HTML attribute, for example: |
| 11 /// // <my-playback volume="11"> | 16 /// // <my-playback volume="11"> |
| 17 /// // It will support initialization and data-binding. | |
| 12 /// @published double volume; | 18 /// @published double volume; |
| 19 /// | |
| 20 /// // This will be available as an HTML attribute, like above, but it | |
| 21 /// // will also serialize values set to the property to the attribute. | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
also here (property _ _ to the)
Jennifer Messerly
2014/06/04 04:42:16
Done.
| |
| 22 /// @PublishedProperty(reflect: true) double volume2; | |
| 13 /// } | 23 /// } |
| 24 /// | |
| 14 const published = const PublishedProperty(); | 25 const published = const PublishedProperty(); |
| 15 | 26 |
| 16 /// An annotation used to publish a field as an attribute. See [published]. | 27 /// An annotation used to publish a field as an attribute. See [published]. |
| 17 class PublishedProperty extends ObservableProperty { | 28 class PublishedProperty extends ObservableProperty { |
| 18 const PublishedProperty(); | 29 /// Whether the property value should be reflected back to the HTML attribute. |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
Does this mean that basically that @publish is 1 w
Jennifer Messerly
2014/06/04 04:42:16
hmm, not exactly ... I think I was confused when I
Siggi Cherem (dart-lang)
2014/06/04 16:39:10
I see, so this means that @publish is going to hav
Jennifer Messerly
2014/06/04 17:52:16
exactly
| |
| 30 final bool reflect; | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
mmm, the term 'reflect' is a bit unfortunate. Sinc
Jennifer Messerly
2014/06/04 04:42:16
problem is it's documented: http://www.polymer-pro
| |
| 31 | |
| 32 const PublishedProperty({this.reflect: false}); | |
| 19 } | 33 } |
| 20 | 34 |
| 21 /// Use this type to observe a property and have the method be called when it | 35 /// Use this type to observe a property and have the method be called when it |
| 22 /// changes. For example: | 36 /// changes. For example: |
| 23 /// | 37 /// |
| 24 /// @ObserveProperty('foo.bar baz qux') | 38 /// @ObserveProperty('foo.bar baz qux') |
| 25 /// validate() { | 39 /// validate() { |
| 26 /// // use this.foo.bar, this.baz, and this.qux in validation | 40 /// // use this.foo.bar, this.baz, and this.qux in validation |
| 27 /// ... | 41 /// ... |
| 28 /// } | 42 /// } |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 40 if (n is! Iterable) { | 54 if (n is! Iterable) { |
| 41 throw new UnsupportedError('ObserveProperty takes either an Iterable of ' | 55 throw new UnsupportedError('ObserveProperty takes either an Iterable of ' |
| 42 'names, or a space separated String, instead of `$n`.'); | 56 'names, or a space separated String, instead of `$n`.'); |
| 43 } | 57 } |
| 44 return n; | 58 return n; |
| 45 } | 59 } |
| 46 | 60 |
| 47 const ObserveProperty(this._names); | 61 const ObserveProperty(this._names); |
| 48 } | 62 } |
| 49 | 63 |
| 64 | |
| 65 /// Base class for PolymerElements deriving from HtmlElement. | |
| 66 /// | |
| 67 /// See [Polymer]. | |
| 68 class PolymerElement extends HtmlElement with Polymer, Observable { | |
| 69 PolymerElement.created() : super.created() { | |
| 70 polymerCreated(); | |
| 71 } | |
| 72 } | |
| 73 | |
| 74 | |
| 50 /// The mixin class for Polymer elements. It provides convenience features on | 75 /// The mixin class for Polymer elements. It provides convenience features on |
| 51 /// top of the custom elements web standard. | 76 /// top of the custom elements web standard. |
| 52 /// | 77 /// |
| 53 /// If this class is used as a mixin, | 78 /// If this class is used as a mixin, |
| 54 /// you must call `polymerCreated()` from the body of your constructor. | 79 /// you must call `polymerCreated()` from the body of your constructor. |
| 55 abstract class Polymer implements Element, Observable, NodeBindExtension { | 80 abstract class Polymer implements Element, Observable, NodeBindExtension { |
| 56 | 81 |
| 57 // TODO(jmesserly): should this really be public? | 82 // TODO(jmesserly): should this really be public? |
| 58 /// Regular expression that matches data-bindings. | 83 /// Regular expression that matches data-bindings. |
| 59 static final bindPattern = new RegExp(r'\{\{([^{}]*)}}'); | 84 static final bindPattern = new RegExp(r'\{\{([^{}]*)}}'); |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 92 // Build a polymer-element and initialize it to register | 117 // Build a polymer-element and initialize it to register |
| 93 if (doc == null) doc = document; | 118 if (doc == null) doc = document; |
| 94 var poly = doc.createElement('polymer-element'); | 119 var poly = doc.createElement('polymer-element'); |
| 95 poly.attributes['name'] = name; | 120 poly.attributes['name'] = name; |
| 96 if (extendsTag != null) poly.attributes['extends'] = extendsTag; | 121 if (extendsTag != null) poly.attributes['extends'] = extendsTag; |
| 97 if (template != null) poly.append(template); | 122 if (template != null) poly.append(template); |
| 98 | 123 |
| 99 new JsObject.fromBrowserObject(poly).callMethod('init'); | 124 new JsObject.fromBrowserObject(poly).callMethod('init'); |
| 100 } | 125 } |
| 101 | 126 |
| 102 /// The one syntax to rule them all. | 127 // Note: these are from src/declaration/import.js |
| 103 static final BindingDelegate _polymerSyntax = | 128 // For now proxy to the JS methods, because we want to share the loader with |
| 104 new PolymerExpressionsWithEvents(); | 129 // polymer.js for interop purposes. |
| 130 static Future importElements(Node elementOrFragment) { | |
| 131 var completer = new Completer(); | |
| 132 js.context['Polymer'].callMethod('importElements', | |
| 133 [elementOrFragment, () => completer.complete()]); | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
Consider having a single place where we this async
Jennifer Messerly
2014/06/04 04:42:16
Since it's only these 2, are you okay if I wait fo
Siggi Cherem (dart-lang)
2014/06/04 16:39:10
Sure thing, no problem. I think the @import code t
Jennifer Messerly
2014/06/04 17:52:16
yup. Argh, I still can't believe all that dead cod
| |
| 134 return completer.future; | |
| 135 } | |
| 105 | 136 |
| 106 static int _preparingElements = 0; | 137 static Future importUrls(List urls) { |
| 138 var completer = new Completer(); | |
| 139 js.context['Polymer'].callMethod('importUrls', | |
| 140 [urls, () => completer.complete()]); | |
| 141 return completer.future; | |
| 142 } | |
| 107 | 143 |
| 108 static final Completer _ready = new Completer(); | 144 static final Completer _onReady = new Completer(); |
| 109 | 145 |
| 110 /// Future indicating that the Polymer library has been loaded and is ready | 146 /// Future indicating that the Polymer library has been loaded and is ready |
| 111 /// for use. | 147 /// for use. |
| 112 static Future get onReady => _ready.future; | 148 static Future get onReady => _onReady.future; |
| 113 | |
| 114 PolymerDeclaration _declaration; | |
| 115 | 149 |
| 116 /// The most derived `<polymer-element>` declaration for this element. | 150 /// The most derived `<polymer-element>` declaration for this element. |
| 117 PolymerDeclaration get declaration => _declaration; | 151 PolymerDeclaration get element => _element; |
| 152 PolymerDeclaration _element; | |
| 118 | 153 |
| 119 Map<String, StreamSubscription> _observers; | 154 /// Deprecated: use [element] instead. |
| 155 @deprecated PolymerDeclaration get declaration => _element; | |
|
Siggi Cherem (dart-lang)
2014/06/03 17:41:03
:( - I actually liked 'declaration' better, I gues
Jennifer Messerly
2014/06/04 04:42:15
yeah, there's a lot of goodness in having the same
| |
| 156 | |
| 157 Map<String, StreamSubscription> _namedObservers; | |
| 158 List<Iterable<Bindable>> _observers = []; | |
| 159 | |
| 120 bool _unbound; // lazy-initialized | 160 bool _unbound; // lazy-initialized |
| 121 _Job _unbindAllJob; | 161 PolymerJob _unbindAllJob; |
| 122 | 162 |
| 123 CompoundObserver _propertyObserver; | 163 CompoundObserver _propertyObserver; |
| 164 bool _readied = false; | |
| 124 | 165 |
| 125 bool get _elementPrepared => _declaration != null; | 166 /// Returns the object that should be used as the event controller for |
| 167 /// event bindings in this element's template. If set, this will override the | |
| 168 /// normal controller lookup. | |
| 169 // TODO(jmesserly): type seems wrong here? I'm guessing this should be any | |
| 170 // kind of model object. Also, should it be writable by anyone? | |
| 171 Polymer eventController; | |
| 126 | 172 |
| 127 bool get applyAuthorStyles => false; | 173 bool get hasBeenAttached => _hasBeenAttached; |
| 128 bool get resetStyleInheritance => false; | 174 bool _hasBeenAttached = false; |
| 129 bool get alwaysPrepare => false; | |
| 130 bool get preventDispose => false; | |
| 131 | 175 |
| 132 BindingDelegate syntax = _polymerSyntax; | 176 /// Gets the shadow root associated with the corresponding custom element. |
| 133 | 177 /// |
| 134 /// Shadow roots created by [parseElement]. See [getShadowRoot]. | 178 /// This is identical to [shadowRoot], unless there are multiple levels of |
| 135 final _shadowRoots = new HashMap<String, ShadowRoot>(); | 179 /// inheritance and they each have their own shadow root. For example, |
| 180 /// this can happen if the base class and subclass both have `<template>` tags | |
| 181 /// in their `<polymer-element>` tags. | |
| 182 // TODO(jmesserly): should expose this as an immutable map. | |
| 183 // Similar issue as $. | |
| 184 final Map<String, ShadowRoot> shadowRoots = | |
| 185 new LinkedHashMap<String, ShadowRoot>(); | |
| 136 | 186 |
| 137 /// Map of items in the shadow root(s) by their [Element.id]. | 187 /// Map of items in the shadow root(s) by their [Element.id]. |
| 138 // TODO(jmesserly): various issues: | 188 // TODO(jmesserly): various issues: |
| 139 // * wrap in UnmodifiableMapView? | 189 // * wrap in UnmodifiableMapView? |
| 140 // * should we have an object that implements noSuchMethod? | 190 // * should we have an object that implements noSuchMethod? |
| 141 // * should the map have a key order (e.g. LinkedHash or SplayTree)? | 191 // * should the map have a key order (e.g. LinkedHash or SplayTree)? |
| 142 // * should this be a live list? Polymer doesn't, maybe due to JS limitations? | 192 // * should this be a live list? Polymer doesn't, maybe due to JS limitations? |
| 143 // Note: this is observable to support $['someId'] being used in templates. | 193 // Note: this is observable to support $['someId'] being used in templates. |
| 144 // The template is stamped before $ is populated, so we need observation if | 194 // The template is stamped before $ is populated, so we need observation if |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
I was just thinking... we actually populate the en
Jennifer Messerly
2014/06/04 04:42:15
yeah, maybe. If it was immutable I'd feel good abo
Siggi Cherem (dart-lang)
2014/06/04 16:39:10
I'd be happy making it immutable :)
Do they ever
Jennifer Messerly
2014/06/04 17:52:16
honestly have no idea.
opened https://code.google.
| |
| 145 // we want it to be usable in bindings. | 195 // we want it to be usable in bindings. |
| 146 @reflectable final Map<String, Element> $ = | 196 @reflectable final Map<String, Element> $ = |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
oops, forgot to remove it here, but we can probabl
Jennifer Messerly
2014/06/04 04:42:16
+1 on a separate CL. But agreed.
| |
| 147 new ObservableMap<String, Element>(); | 197 new ObservableMap<String, Element>(); |
| 148 | 198 |
| 149 /// Gets the shadow root associated with the corresponding custom element. | 199 /// Use to override the default syntax for polymer-elements. |
| 150 /// | 200 /// By default this will be null, which causes [instanceTemplate] to use |
| 151 /// This is identical to [shadowRoot], unless there are multiple levels of | 201 /// the template's bindingDelegate or the [element.syntax], in that order. |
| 152 /// inheritance and they each have their own shadow root. For example, | 202 BindingDelegate get syntax => null; |
| 153 /// this can happen if the base class and subclass both have `<template>` tags | 203 |
| 154 /// in their `<polymer-element>` tags. | 204 bool get _elementPrepared => _element != null; |
| 155 // TODO(jmesserly): Polymer does not have this feature. Reconcile. | 205 |
| 156 ShadowRoot getShadowRoot(String customTagName) => _shadowRoots[customTagName]; | 206 /// Retrieves the custom element name. It should be used instead |
| 207 /// of localName, see: https://github.com/Polymer/polymer-dev/issues/26 | |
| 208 String get _name { | |
| 209 if (_element != null) return _element.name; | |
| 210 var isAttr = attributes['is']; | |
| 211 return (isAttr == null || isAttr == '') ? localName : isAttr; | |
| 212 } | |
| 213 | |
| 214 /// By default the data bindings will be cleaned up when this custom element | |
| 215 /// is detached from the document. Overriding this to return `true` will | |
| 216 /// prevent that from happening. | |
| 217 bool get preventDispose => false; | |
| 157 | 218 |
| 158 /// If this class is used as a mixin, this method must be called from inside | 219 /// If this class is used as a mixin, this method must be called from inside |
| 159 /// of the `created()` constructor. | 220 /// of the `created()` constructor. |
| 160 /// | 221 /// |
| 161 /// If this class is a superclass, calling `super.created()` is sufficient. | 222 /// If this class is a superclass, calling `super.created()` is sufficient. |
| 162 void polymerCreated() { | 223 void polymerCreated() { |
| 163 if (this.ownerDocument.window != null || alwaysPrepare || | 224 var t = nodeBind(this).templateInstance; |
| 164 _preparingElements > 0) { | 225 if (t != null && t.model != null) { |
| 165 prepareElement(); | 226 window.console.warn('Attributes on $_name were data bound ' |
| 227 'prior to Polymer upgrading the element. This may result in ' | |
| 228 'incorrect binding types.'); | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
what do you mean by "binding types"?
Jennifer Messerly
2014/06/04 04:42:16
your guess is as good as mine :)
it's from: https:
| |
| 229 } | |
| 230 prepareElement(); | |
| 231 | |
| 232 // TODO(sorvell): replace when ShadowDOMPolyfill issue is corrected | |
| 233 // https://github.com/Polymer/ShadowDOM/issues/420 | |
| 234 if (!isTemplateStagingDocument(ownerDocument) || _hasShadowDomPolyfill) { | |
| 235 makeElementReady(); | |
| 166 } | 236 } |
| 167 } | 237 } |
| 168 | 238 |
| 169 /// Retrieves the custom element name by inspecting the host node. | 239 /// *Deprecated* use [shadowRoots] instead. |
| 170 String get _customTagName { | 240 @deprecated |
| 171 var isAttr = attributes['is']; | 241 ShadowRoot getShadowRoot(String customTagName) => shadowRoots[customTagName]; |
| 172 return (isAttr == null || isAttr == '') ? localName : isAttr; | |
| 173 } | |
| 174 | 242 |
| 175 void prepareElement() { | 243 void prepareElement() { |
| 176 // Dart note: get the _declaration, which also marks _elementPrepared | 244 if (_elementPrepared) { |
| 177 _declaration = _getDeclaration(_customTagName); | 245 window.console.warn('Element already prepared: $_name'); |
| 178 // do this first so we can observe changes during initialization | 246 return; |
| 179 observeProperties(); | 247 } |
| 248 // Dart note: get the corresponding polymer-element | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
nit, minor suggestion:
"... polymer-element"
=>
Jennifer Messerly
2014/06/04 04:42:16
Done.
| |
| 249 _element = _getDeclaration(_name); | |
| 250 // install property storage | |
| 251 createPropertyObserver(); | |
| 252 // TODO (sorvell): temporarily open observer when created | |
| 253 openPropertyObserver(); | |
| 180 // install boilerplate attributes | 254 // install boilerplate attributes |
| 181 copyInstanceAttributes(); | 255 copyInstanceAttributes(); |
| 182 // process input attributes | 256 // process input attributes |
| 183 takeAttributes(); | 257 takeAttributes(); |
| 184 // add event listeners | 258 // add event listeners |
| 185 addHostListeners(); | 259 addHostListeners(); |
| 186 // guarantees that while preparing, any | 260 } |
| 187 // sub-elements are also prepared | 261 |
| 188 _preparingElements++; | 262 makeElementReady() { |
| 263 if (_readied) return; | |
| 264 _readied = true; | |
| 265 | |
| 266 // TODO(sorvell): We could create an entry point here | |
| 267 // for the user to compute property values. | |
| 189 // process declarative resources | 268 // process declarative resources |
| 190 parseDeclarations(_declaration); | 269 parseDeclarations(_element); |
| 191 // decrement semaphore | 270 // TODO(sorvell): CE polyfill uses unresolved attribute to simulate |
| 192 _preparingElements--; | 271 // :unresolved; remove this attribute to be compatible with native |
| 272 // CE. | |
| 273 attributes.remove('unresolved'); | |
| 193 // user entry point | 274 // user entry point |
| 194 ready(); | 275 ready(); |
| 195 } | 276 } |
| 196 | 277 |
| 197 /// Called when [prepareElement] is finished. | 278 /// Called when [prepareElement] is finished. |
| 198 void ready() {} | 279 void ready() {} |
| 199 | 280 |
| 200 void enteredView() { | 281 /// domReady can be used to access elements in dom (descendants, |
| 282 /// ancestors, siblings) such that the developer is enured to upgrade | |
| 283 /// ordering. If the element definitions have loaded, domReady | |
| 284 /// can be used to access upgraded elements. | |
| 285 /// | |
| 286 /// To use, override this method in your element. | |
| 287 void domReady() {} | |
| 288 | |
| 289 void attached() { | |
| 201 if (!_elementPrepared) { | 290 if (!_elementPrepared) { |
| 202 prepareElement(); | 291 // Dart specific message for a common issue. |
| 292 throw new StateError('polymerCreated was not called for custom element ' | |
| 293 '$_name, this should normally be done in the .created() if Polymer ' | |
| 294 'is used as a mixin.'); | |
| 203 } | 295 } |
| 204 cancelUnbindAll(preventCascade: true); | 296 |
| 297 cancelUnbindAll(); | |
| 298 if (!hasBeenAttached) { | |
| 299 _hasBeenAttached = true; | |
| 300 async((_) => domReady()); | |
| 301 } | |
| 205 } | 302 } |
| 206 | 303 |
| 207 void leftView() { | 304 void detached() { |
| 208 if (!preventDispose) asyncUnbindAll(); | 305 if (!preventDispose) asyncUnbindAll(); |
| 209 } | 306 } |
| 210 | 307 |
| 211 /// Recursive ancestral <element> initialization, oldest first. | 308 /// Recursive ancestral <element> initialization, oldest first. |
| 212 void parseDeclarations(PolymerDeclaration declaration) { | 309 void parseDeclarations(PolymerDeclaration declaration) { |
| 213 if (declaration != null) { | 310 if (declaration != null) { |
| 214 parseDeclarations(declaration.superDeclaration); | 311 parseDeclarations(declaration.superDeclaration); |
| 215 parseDeclaration(declaration.element); | 312 parseDeclaration(declaration.element); |
| 216 } | 313 } |
| 217 } | 314 } |
| 218 | 315 |
| 219 /// Parse input `<polymer-element>` as needed, override for custom behavior. | 316 /// Parse input `<polymer-element>` as needed, override for custom behavior. |
| 220 void parseDeclaration(Element elementElement) { | 317 void parseDeclaration(Element elementElement) { |
| 221 var template = fetchTemplate(elementElement); | 318 var template = fetchTemplate(elementElement); |
| 222 | 319 |
| 223 var root = null; | |
| 224 if (template != null) { | 320 if (template != null) { |
| 225 if (_declaration.element.attributes.containsKey('lightdom')) { | 321 var root = shadowFromTemplate(template); |
| 226 lightFromTemplate(template); | 322 |
| 227 } else { | 323 var name = elementElement.attributes['name']; |
| 228 root = shadowFromTemplate(template); | 324 if (name == null) return; |
| 229 } | 325 shadowRoots[name] = root; |
| 230 } | 326 } |
| 231 | |
| 232 // Dart note: the following code is to support the getShadowRoot method. | |
| 233 if (root is! ShadowRoot) return; | |
| 234 | |
| 235 var name = elementElement.attributes['name']; | |
| 236 if (name == null) return; | |
| 237 _shadowRoots[name] = root; | |
| 238 } | 327 } |
| 239 | 328 |
| 240 /// Return a shadow-root template (if desired), override for custom behavior. | 329 /// Return a shadow-root template (if desired), override for custom behavior. |
| 241 Element fetchTemplate(Element elementElement) => | 330 Element fetchTemplate(Element elementElement) => |
| 242 elementElement.querySelector('template'); | 331 elementElement.querySelector('template'); |
| 243 | 332 |
| 244 /// Utility function that stamps a `<template>` into light-dom. | 333 /// Utility function that stamps a `<template>` into light-dom. |
| 245 Node lightFromTemplate(Element template) { | 334 Node lightFromTemplate(Element template, [Node refNode]) { |
| 246 if (template == null) return null; | 335 if (template == null) return null; |
| 336 | |
| 337 // TODO(sorvell): mark this element as an event controller so that | |
| 338 // event listeners on bound nodes inside it will be called on it. | |
| 339 // Note, the expectation here is that events on all descendants | |
| 340 // should be handled by this element. | |
| 341 eventController = this; | |
| 342 | |
| 247 // stamp template | 343 // stamp template |
| 248 // which includes parsing and applying MDV bindings before being | 344 // which includes parsing and applying MDV bindings before being |
| 249 // inserted (to avoid {{}} in attribute values) | 345 // inserted (to avoid {{}} in attribute values) |
| 250 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. | 346 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
| 251 var dom = instanceTemplate(template); | 347 var dom = instanceTemplate(template); |
| 252 // append to shadow dom | 348 // append to shadow dom |
| 253 append(dom); | 349 if (refNode != null) { |
| 254 // perform post-construction initialization tasks on shadow root | 350 append(dom); |
| 255 shadowRootReady(this, template); | 351 } else { |
| 352 insertBefore(dom, refNode); | |
| 353 } | |
| 354 // perform post-construction initialization tasks on ahem, light root | |
| 355 shadowRootReady(this); | |
| 256 // return the created shadow root | 356 // return the created shadow root |
| 257 return dom; | 357 return dom; |
| 258 } | 358 } |
| 259 | 359 |
| 260 /// Utility function that creates a shadow root from a `<template>`. | 360 /// Utility function that creates a shadow root from a `<template>`. |
| 261 /// | 361 /// |
| 262 /// The base implementation will return a [ShadowRoot], but you can replace it | 362 /// The base implementation will return a [ShadowRoot], but you can replace it |
| 263 /// with your own code and skip ShadowRoot creation. In that case, you should | 363 /// with your own code and skip ShadowRoot creation. In that case, you should |
| 264 /// return `null`. | 364 /// return `null`. |
| 265 /// | 365 /// |
| 266 /// In your overridden method, you can use [instanceTemplate] to stamp the | 366 /// In your overridden method, you can use [instanceTemplate] to stamp the |
| 267 /// template and initialize data binding, and [shadowRootReady] to intialize | 367 /// template and initialize data binding, and [shadowRootReady] to intialize |
| 268 /// other Polymer features like event handlers. It is fine to call | 368 /// other Polymer features like event handlers. It is fine to call |
| 269 /// shadowRootReady with a node other than a ShadowRoot such as with `this`. | 369 /// shadowRootReady with a node other than a ShadowRoot such as with `this`. |
| 270 ShadowRoot shadowFromTemplate(Element template) { | 370 ShadowRoot shadowFromTemplate(Element template) { |
| 271 if (template == null) return null; | 371 if (template == null) return null; |
| 272 // cache elder shadow root (if any) | |
| 273 var elderRoot = this.shadowRoot; | |
| 274 // make a shadow root | 372 // make a shadow root |
| 275 var root = createShadowRoot(); | 373 var root = createShadowRoot(); |
| 276 | |
| 277 // Provides ability to traverse from ShadowRoot to the host. | |
| 278 // TODO(jmessery): remove once we have this ability on the DOM. | |
| 279 _shadowHost[root] = this; | |
| 280 | |
| 281 // migrate flag(s)( | |
| 282 root.applyAuthorStyles = applyAuthorStyles; | |
| 283 root.resetStyleInheritance = resetStyleInheritance; | |
| 284 // stamp template | 374 // stamp template |
| 285 // which includes parsing and applying MDV bindings before being | 375 // which includes parsing and applying MDV bindings before being |
| 286 // inserted (to avoid {{}} in attribute values) | 376 // inserted (to avoid {{}} in attribute values) |
| 287 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. | 377 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
| 288 var dom = instanceTemplate(template); | 378 var dom = instanceTemplate(template); |
| 289 // append to shadow dom | 379 // append to shadow dom |
| 290 root.append(dom); | 380 root.append(dom); |
| 291 // perform post-construction initialization tasks on shadow root | 381 // perform post-construction initialization tasks on shadow root |
| 292 shadowRootReady(root, template); | 382 shadowRootReady(root); |
| 293 // return the created shadow root | 383 // return the created shadow root |
| 294 return root; | 384 return root; |
| 295 } | 385 } |
| 296 | 386 |
| 297 void shadowRootReady(Node root, Element template) { | 387 void shadowRootReady(Node root) { |
| 298 // locate nodes with id and store references to them in this.$ hash | 388 // locate nodes with id and store references to them in this.$ hash |
| 299 marshalNodeReferences(root); | 389 marshalNodeReferences(root); |
| 300 // TODO(jmesserly): port this | 390 |
| 301 // set up pointer gestures | 391 // set up polymer gestures |
| 302 // PointerGestures.register(root); | 392 if (_PolymerGestures != null) { |
| 393 _PolymerGestures.callMethod('register', [root]); | |
| 394 } | |
| 303 } | 395 } |
| 304 | 396 |
| 305 /// Locate nodes with id and store references to them in [$] hash. | 397 /// Locate nodes with id and store references to them in [$] hash. |
| 306 void marshalNodeReferences(Node root) { | 398 void marshalNodeReferences(Node root) { |
| 307 if (root == null) return; | 399 if (root == null) return; |
| 308 for (var n in (root as dynamic).querySelectorAll('[id]')) { | 400 for (var n in (root as dynamic).querySelectorAll('[id]')) { |
| 309 $[n.id] = n; | 401 $[n.id] = n; |
| 310 } | 402 } |
| 311 } | 403 } |
| 312 | 404 |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 324 Future<List<MutationRecord>> onMutation(Node node) { | 416 Future<List<MutationRecord>> onMutation(Node node) { |
| 325 var completer = new Completer(); | 417 var completer = new Completer(); |
| 326 new MutationObserver((mutations, observer) { | 418 new MutationObserver((mutations, observer) { |
| 327 observer.disconnect(); | 419 observer.disconnect(); |
| 328 completer.complete(mutations); | 420 completer.complete(mutations); |
| 329 })..observe(node, childList: true, subtree: true); | 421 })..observe(node, childList: true, subtree: true); |
| 330 return completer.future; | 422 return completer.future; |
| 331 } | 423 } |
| 332 | 424 |
| 333 void copyInstanceAttributes() { | 425 void copyInstanceAttributes() { |
| 334 _declaration._instanceAttributes.forEach((name, value) { | 426 _element._instanceAttributes.forEach((name, value) { |
| 335 attributes.putIfAbsent(name, () => value); | 427 attributes.putIfAbsent(name, () => value); |
| 336 }); | 428 }); |
| 337 } | 429 } |
| 338 | 430 |
| 339 void takeAttributes() { | 431 void takeAttributes() { |
| 340 if (_declaration._publishLC == null) return; | 432 if (_element._publishLC == null) return; |
| 341 attributes.forEach(attributeToProperty); | 433 attributes.forEach(attributeToProperty); |
| 342 } | 434 } |
| 343 | 435 |
| 344 /// If attribute [name] is mapped to a property, deserialize | 436 /// If attribute [name] is mapped to a property, deserialize |
| 345 /// [value] into that property. | 437 /// [value] into that property. |
| 346 void attributeToProperty(String name, String value) { | 438 void attributeToProperty(String name, String value) { |
| 347 // try to match this attribute to a property (attributes are | 439 // try to match this attribute to a property (attributes are |
| 348 // all lower-case, so this is case-insensitive search) | 440 // all lower-case, so this is case-insensitive search) |
| 349 var decl = propertyForAttribute(name); | 441 var decl = propertyForAttribute(name); |
| 350 if (decl == null) return; | 442 if (decl == null) return; |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 367 // only act if the value has changed | 459 // only act if the value has changed |
| 368 if (!identical(newValue, currentValue)) { | 460 if (!identical(newValue, currentValue)) { |
| 369 // install new value (has side-effects) | 461 // install new value (has side-effects) |
| 370 smoke.write(this, decl.name, newValue); | 462 smoke.write(this, decl.name, newValue); |
| 371 } | 463 } |
| 372 } | 464 } |
| 373 | 465 |
| 374 /// Return the published property matching name, or null. | 466 /// Return the published property matching name, or null. |
| 375 // TODO(jmesserly): should we just return Symbol here? | 467 // TODO(jmesserly): should we just return Symbol here? |
| 376 smoke.Declaration propertyForAttribute(String name) { | 468 smoke.Declaration propertyForAttribute(String name) { |
| 377 final publishLC = _declaration._publishLC; | 469 final publishLC = _element._publishLC; |
| 378 if (publishLC == null) return null; | 470 if (publishLC == null) return null; |
| 379 //console.log('propertyForAttribute:', name, 'matches', match); | 471 //console.log('propertyForAttribute:', name, 'matches', match); |
| 380 return publishLC[name]; | 472 return publishLC[name]; |
| 381 } | 473 } |
| 382 | 474 |
| 383 /// Convert representation of [value] based on [type] and [currentValue]. | 475 /// Convert representation of [value] based on [type] and [currentValue]. |
| 384 Object deserializeValue(String value, Object currentValue, Type type) => | 476 Object deserializeValue(String value, Object currentValue, Type type) => |
| 385 deserialize.deserializeValue(value, currentValue, type); | 477 deserialize.deserializeValue(value, currentValue, type); |
| 386 | 478 |
| 387 String serializeValue(Object value) { | 479 String serializeValue(Object value) { |
| 388 if (value == null) return null; | 480 if (value == null) return null; |
| 389 | 481 |
| 390 if (value is bool) { | 482 if (value is bool) { |
| 391 return _toBoolean(value) ? '' : null; | 483 return _toBoolean(value) ? '' : null; |
| 392 } else if (value is String || value is num) { | 484 } else if (value is String || value is num) { |
| 393 return '$value'; | 485 return '$value'; |
| 394 } | 486 } |
| 395 return null; | 487 return null; |
| 396 } | 488 } |
| 397 | 489 |
| 398 void reflectPropertyToAttribute(PropertyPath path) { | 490 void reflectPropertyToAttribute(String path) { |
| 399 if (path.length != 1) throw new ArgumentError('path must be length 1'); | |
| 400 | |
| 401 // TODO(sjmiles): consider memoizing this | 491 // TODO(sjmiles): consider memoizing this |
| 402 // try to intelligently serialize property value | 492 // try to intelligently serialize property value |
| 403 final propValue = path.getValueFrom(this); | 493 final propValue = new PropertyPath(path).getValueFrom(this); |
| 404 final serializedValue = serializeValue(propValue); | 494 final serializedValue = serializeValue(propValue); |
| 405 // boolean properties must reflect as boolean attributes | 495 // boolean properties must reflect as boolean attributes |
| 406 if (serializedValue != null) { | 496 if (serializedValue != null) { |
| 407 attributes['$path'] = serializedValue; | 497 attributes[path] = serializedValue; |
| 408 // TODO(sorvell): we should remove attr for all properties | 498 // TODO(sorvell): we should remove attr for all properties |
| 409 // that have undefined serialization; however, we will need to | 499 // that have undefined serialization; however, we will need to |
| 410 // refine the attr reflection system to achieve this; pica, for example, | 500 // refine the attr reflection system to achieve this; pica, for example, |
| 411 // relies on having inferredType object properties not removed as | 501 // relies on having inferredType object properties not removed as |
| 412 // attrs. | 502 // attrs. |
| 413 } else if (propValue is bool) { | 503 } else if (propValue is bool) { |
| 414 attributes.remove('$path'); | 504 attributes.remove(path); |
| 415 } | 505 } |
| 416 } | 506 } |
| 417 | 507 |
| 418 /// Creates the document fragment to use for each instance of the custom | 508 /// Creates the document fragment to use for each instance of the custom |
| 419 /// element, given the `<template>` node. By default this is equivalent to: | 509 /// element, given the `<template>` node. By default this is equivalent to: |
| 420 /// | 510 /// |
| 421 /// templateBind(template).createInstance(this, polymerSyntax); | 511 /// templateBind(template).createInstance(this, polymerSyntax); |
| 422 /// | 512 /// |
| 423 /// Where polymerSyntax is a singleton `PolymerExpressions` instance from the | 513 /// Where polymerSyntax is a singleton [PolymerExpressions] instance. |
| 424 /// [polymer_expressions](https://pub.dartlang.org/packages/polymer_expression s) | |
| 425 /// package. | |
| 426 /// | 514 /// |
| 427 /// You can override this method to change the instantiation behavior of the | 515 /// You can override this method to change the instantiation behavior of the |
| 428 /// template, for example to use a different data-binding syntax. | 516 /// template, for example to use a different data-binding syntax. |
| 429 DocumentFragment instanceTemplate(Element template) => | 517 DocumentFragment instanceTemplate(Element template) { |
| 430 templateBind(template).createInstance(this, syntax); | 518 var syntax = this.syntax; |
| 519 var t = templateBind(template); | |
| 520 if (syntax == null && t.bindingDelegate == null) { | |
| 521 syntax = element.syntax; | |
| 522 } | |
| 523 var dom = t.createInstance(this, syntax); | |
| 524 registerObservers(getTemplateInstanceBindings(dom)); | |
| 525 return dom; | |
| 526 } | |
| 431 | 527 |
| 432 // TODO(jmesserly): Polymer does not seem to implement the oneTime flag | |
| 433 // correctly. File bug. | |
| 434 Bindable bind(String name, Bindable bindable, {bool oneTime: false}) { | 528 Bindable bind(String name, Bindable bindable, {bool oneTime: false}) { |
| 435 // note: binding is a prepare signal. This allows us to be sure that any | |
| 436 // property changes that occur as a result of binding will be observed. | |
| 437 if (!_elementPrepared) prepareElement(); | |
| 438 | |
| 439 var decl = propertyForAttribute(name); | 529 var decl = propertyForAttribute(name); |
| 440 if (decl == null) { | 530 if (decl == null) { |
| 441 // Cannot call super.bind because template_binding is its own package | 531 // Cannot call super.bind because template_binding is its own package |
| 442 return nodeBindFallback(this).bind(name, bindable, oneTime: oneTime); | 532 return nodeBindFallback(this).bind(name, bindable, oneTime: oneTime); |
| 443 } else { | 533 } else { |
| 444 // clean out the closets | |
| 445 unbind(name); | |
| 446 // use n-way Polymer binding | 534 // use n-way Polymer binding |
| 447 var observer = bindProperty(decl.name, bindable); | 535 var observer = bindProperty(decl.name, bindable, oneTime: oneTime); |
| 536 // NOTE: reflecting binding information is typically required only for | |
| 537 // tooling. It has a performance cost so it's opt-in in Node.bind. | |
| 538 if (enableBindingsReflection && observer != null) { | |
| 539 // Dart note: this is not needed because of how _PolymerBinding works. | |
| 540 //observer.path = bindable.path_; | |
| 541 _recordBinding(name, observer); | |
| 542 } | |
| 543 var reflect = _element._reflect; | |
| 448 | 544 |
| 449 // reflect bound property to attribute when binding | 545 // Get back to the (possibly camel-case) name for the property. |
| 450 // to ensure binding is not left on attribute if property | 546 var propName = smoke.symbolToName(decl.name); |
| 451 // does not update due to not changing. | 547 if (reflect != null && reflect.contains(propName)) { |
| 452 // Dart note: we include this patch: | 548 reflectPropertyToAttribute(propName); |
| 453 // https://github.com/Polymer/polymer/pull/319 | 549 } |
| 454 | 550 return observer; |
| 455 // TODO(jmesserly): polymer has the path_ in their observer object, should | |
| 456 // we use that too instead of allocating it here? | |
| 457 reflectPropertyToAttribute(new PropertyPath([decl.name])); | |
| 458 return bindings[name] = observer; | |
| 459 } | 551 } |
| 460 } | 552 } |
| 461 | 553 |
| 554 _recordBinding(String name, observer) { | |
| 555 if (bindings == null) bindings = {}; | |
| 556 this.bindings[name] = observer; | |
| 557 } | |
| 558 | |
| 559 bindFinished() => makeElementReady(); | |
| 560 | |
| 462 Map<String, Bindable> get bindings => nodeBindFallback(this).bindings; | 561 Map<String, Bindable> get bindings => nodeBindFallback(this).bindings; |
| 562 set bindings(Map value) { nodeBindFallback(this).bindings = value; } | |
| 563 | |
| 463 TemplateInstance get templateInstance => | 564 TemplateInstance get templateInstance => |
| 464 nodeBindFallback(this).templateInstance; | 565 nodeBindFallback(this).templateInstance; |
| 465 | 566 |
| 466 void unbind(String name) => nodeBindFallback(this).unbind(name); | 567 // TODO(sorvell): unbind/unbindAll has been removed, as public api, from |
| 467 | 568 // TemplateBinding. We still need to close/dispose of observers but perhaps |
| 569 // we should choose a more explicit name. | |
| 468 void asyncUnbindAll() { | 570 void asyncUnbindAll() { |
| 469 if (_unbound == true) return; | 571 if (_unbound == true) return; |
| 470 _unbindLog.fine('[$localName] asyncUnbindAll'); | 572 _unbindLog.fine('[$_name] asyncUnbindAll'); |
| 471 _unbindAllJob = _runJob(_unbindAllJob, unbindAll, Duration.ZERO); | 573 _unbindAllJob = scheduleJob(_unbindAllJob, unbindAll); |
| 472 } | 574 } |
| 473 | 575 |
| 474 void unbindAll() { | 576 void unbindAll() { |
| 475 if (_unbound == true) return; | 577 if (_unbound == true) return; |
| 476 | 578 closeObservers(); |
| 477 unbindAllProperties(); | 579 closeNamedObservers(); |
| 478 nodeBindFallback(this).unbindAll(); | |
| 479 | |
| 480 var root = shadowRoot; | |
| 481 while (root != null) { | |
| 482 _unbindNodeTree(root); | |
| 483 root = root.olderShadowRoot; | |
| 484 } | |
| 485 _unbound = true; | 580 _unbound = true; |
| 486 } | 581 } |
| 487 | 582 |
| 488 void cancelUnbindAll({bool preventCascade}) { | 583 void cancelUnbindAll() { |
| 489 if (_unbound == true) { | 584 if (_unbound == true) { |
| 490 _unbindLog.warning( | 585 _unbindLog.warning('[$_name] already unbound, cannot cancel unbindAll'); |
| 491 '[$localName] already unbound, cannot cancel unbindAll'); | |
| 492 return; | 586 return; |
| 493 } | 587 } |
| 494 _unbindLog.fine('[$localName] cancelUnbindAll'); | 588 _unbindLog.fine('[$_name] cancelUnbindAll'); |
| 495 if (_unbindAllJob != null) { | 589 if (_unbindAllJob != null) { |
| 496 _unbindAllJob.stop(); | 590 _unbindAllJob.stop(); |
| 497 _unbindAllJob = null; | 591 _unbindAllJob = null; |
| 498 } | 592 } |
| 499 | |
| 500 // cancel unbinding our shadow tree iff we're not in the process of | |
| 501 // cascading our tree (as we do, for example, when the element is inserted). | |
| 502 if (preventCascade == true) return; | |
| 503 _forNodeTree(shadowRoot, (n) { | |
| 504 if (n is Polymer) { | |
| 505 (n as Polymer).cancelUnbindAll(); | |
| 506 } | |
| 507 }); | |
| 508 } | |
| 509 | |
| 510 static void _unbindNodeTree(Node node) { | |
| 511 _forNodeTree(node, (node) => nodeBind(node).unbindAll()); | |
| 512 } | 593 } |
| 513 | 594 |
| 514 static void _forNodeTree(Node node, void callback(Node node)) { | 595 static void _forNodeTree(Node node, void callback(Node node)) { |
| 515 if (node == null) return; | 596 if (node == null) return; |
| 516 | 597 |
| 517 callback(node); | 598 callback(node); |
| 518 for (var child = node.firstChild; child != null; child = child.nextNode) { | 599 for (var child = node.firstChild; child != null; child = child.nextNode) { |
| 519 _forNodeTree(child, callback); | 600 _forNodeTree(child, callback); |
| 520 } | 601 } |
| 521 } | 602 } |
| 522 | 603 |
| 604 /* | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
delete comment?
Jennifer Messerly
2014/06/04 04:42:16
good catch!
| |
| 605 var n$ = this._observeNames; | |
| 606 if (n$ && n$.length) { | |
| 607 var o = this._propertyObserver = new CompoundObserver(true); | |
| 608 this.registerObservers([o]); | |
| 609 // TODO(sorvell): may not be kosher to access the value here (this[n]); | |
| 610 // previously we looked at the descriptor on the prototype | |
| 611 // this doesn't work for inheritance and not for accessors without | |
| 612 // a value property | |
| 613 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { | |
| 614 o.addPath(this, n); | |
| 615 this.observeArrayValue(n, this[n], null); | |
| 616 } | |
| 617 } | |
| 618 */ | |
| 619 | |
| 523 /// Set up property observers. | 620 /// Set up property observers. |
| 524 void observeProperties() { | 621 void createPropertyObserver() { |
| 525 final observe = _declaration._observe; | 622 final observe = _element._observe; |
| 526 final publish = _declaration._publish; | 623 if (observe != null) { |
| 624 var o = _propertyObserver = new CompoundObserver(); | |
| 625 // keep track of property observer so we can shut it down | |
| 626 registerObservers([o]); | |
| 527 | 627 |
| 528 // TODO(jmesserly): workaround for a dart2js compiler bug | 628 for (var path in observe.keys) { |
| 529 bool hasObserved = observe != null; | 629 o.addPath(this, path); |
| 530 | 630 |
| 531 if (hasObserved || publish != null) { | 631 // TODO(jmesserly): on the Polymer side it doesn't look like they |
| 532 var o = _propertyObserver = new CompoundObserver(); | 632 // will observe arrays unless it is a length == 1 path. |
| 533 if (hasObserved) { | 633 observeArrayValue(path, path.getValueFrom(this), null); |
| 534 for (var path in observe.keys) { | |
| 535 o.addPath(this, path); | |
| 536 | |
| 537 // TODO(jmesserly): on the Polymer side it doesn't look like they | |
| 538 // will observe arrays unless it is a length == 1 path. | |
| 539 observeArrayValue(path, path.getValueFrom(this), null); | |
| 540 } | |
| 541 } | 634 } |
| 542 if (publish != null) { | |
| 543 for (var path in publish.keys) { | |
| 544 | |
| 545 if (!hasObserved || !observe.containsKey(path)) { | |
| 546 o.addPath(this, path); | |
| 547 } | |
| 548 } | |
| 549 } | |
| 550 o.open(notifyPropertyChanges); | |
| 551 } | 635 } |
| 552 } | 636 } |
| 553 | 637 |
| 638 void openPropertyObserver() { | |
| 639 if (_propertyObserver != null) { | |
| 640 _propertyObserver.open(notifyPropertyChanges); | |
| 641 } | |
| 642 // Dart note: we need an extra listener. | |
| 643 // see comment on [_propertyChange]. | |
| 644 if (_element._publish != null) { | |
| 645 changes.listen(_propertyChange); | |
| 646 } | |
| 647 } | |
| 554 | 648 |
| 555 /// Responds to property changes on this element. | 649 /// Responds to property changes on this element. |
| 556 void notifyPropertyChanges(List newValues, Map oldValues, List paths) { | 650 void notifyPropertyChanges(List newValues, Map oldValues, List paths) { |
| 557 final observe = _declaration._observe; | 651 final observe = _element._observe; |
| 558 final publish = _declaration._publish; | |
| 559 final called = new HashSet(); | 652 final called = new HashSet(); |
| 560 | 653 |
| 561 oldValues.forEach((i, oldValue) { | 654 oldValues.forEach((i, oldValue) { |
| 655 final newValue = newValues[i]; | |
| 656 | |
| 657 // Date note: we don't need any special checking for null and undefined. | |
| 658 | |
| 562 // note: paths is of form [object, path, object, path] | 659 // note: paths is of form [object, path, object, path] |
| 563 var path = paths[2 * i + 1]; | 660 final path = paths[2 * i + 1]; |
| 564 if (publish != null && publish.containsKey(path)) { | |
| 565 reflectPropertyToAttribute(path); | |
| 566 } | |
| 567 if (observe == null) return; | 661 if (observe == null) return; |
| 568 | 662 |
| 569 var methods = observe[path]; | 663 var methods = observe[path]; |
| 570 if (methods == null) return; | 664 if (methods == null) return; |
| 571 | 665 |
| 572 for (var method in methods) { | 666 for (var method in methods) { |
| 573 if (!called.add(method)) continue; // don't invoke more than once. | 667 if (!called.add(method)) continue; // don't invoke more than once. |
| 574 | 668 |
| 575 final newValue = newValues[i]; | |
| 576 // observes the value if it is an array | |
| 577 observeArrayValue(path, newValue, oldValue); | 669 observeArrayValue(path, newValue, oldValue); |
| 578 // Dart note: JS passes "arguments", so we pass along our args. | 670 // Dart note: JS passes "arguments", so we pass along our args. |
| 671 // TODO(sorvell): call method with the set of values it's expecting; | |
| 672 // e.g. 'foo bar': 'invalidate' expects the new and old values for | |
| 673 // foo and bar. Currently we give only one of these and then | |
| 674 // deliver all the arguments. | |
| 579 smoke.invoke(this, method, | 675 smoke.invoke(this, method, |
| 580 [oldValue, newValue, newValues, oldValues, paths], adjust: true); | 676 [oldValue, newValue, newValues, oldValues, paths], adjust: true); |
| 581 } | 677 } |
| 582 }); | 678 }); |
| 583 } | 679 } |
| 584 | 680 |
| 681 // Dart note: had to rename this to avoid colliding with | |
| 682 // Observable.deliverChanges. Even worse, super calls aren't possible or | |
| 683 // it prevents Polymer from being a mixin, so we can't override it even if | |
| 684 // we wanted to. | |
| 685 void deliverPropertyChanges() { | |
| 686 if (_propertyObserver != null) { | |
| 687 _propertyObserver.deliver(); | |
| 688 } | |
| 689 } | |
| 690 | |
| 691 // Dart note: this is not called by observe-js because we don't have | |
| 692 // the mechanism for defining properties on our proto. | |
| 693 // TODO(jmesserly): this has similar timing issues as our @published | |
| 694 // properties do generally -- it's async when it should be sync. | |
| 695 void _propertyChange(List<ChangeRecord> records) { | |
| 696 for (var record in records) { | |
| 697 if (record is! PropertyChangeRecord) continue; | |
| 698 | |
| 699 final name = smoke.symbolToName(record.name); | |
| 700 final reflect = _element._reflect; | |
| 701 if (reflect != null && reflect.contains(name)) { | |
| 702 reflectPropertyToAttribute(name); | |
| 703 } | |
| 704 } | |
| 705 } | |
| 706 | |
| 585 void observeArrayValue(PropertyPath name, Object value, Object old) { | 707 void observeArrayValue(PropertyPath name, Object value, Object old) { |
| 586 final observe = _declaration._observe; | 708 final observe = _element._observe; |
| 587 if (observe == null) return; | 709 if (observe == null) return; |
| 588 | 710 |
| 589 // we only care if there are registered side-effects | 711 // we only care if there are registered side-effects |
| 590 var callbacks = observe[name]; | 712 var callbacks = observe[name]; |
| 591 if (callbacks == null) return; | 713 if (callbacks == null) return; |
| 592 | 714 |
| 593 // if we are observing the previous value, stop | 715 // if we are observing the previous value, stop |
| 594 if (old is ObservableList) { | 716 if (old is ObservableList) { |
| 595 if (_observeLog.isLoggable(Level.FINE)) { | 717 if (_observeLog.isLoggable(Level.FINE)) { |
| 596 _observeLog.fine('[$localName] observeArrayValue: unregister observer ' | 718 _observeLog.fine('[$_name] observeArrayValue: unregister $name'); |
| 597 '$name'); | |
| 598 } | 719 } |
| 599 | 720 |
| 600 unregisterObserver('${name}__array'); | 721 closeNamedObserver('${name}__array'); |
| 601 } | 722 } |
| 602 // if the new value is an array, being observing it | 723 // if the new value is an array, being observing it |
| 603 if (value is ObservableList) { | 724 if (value is ObservableList) { |
| 604 if (_observeLog.isLoggable(Level.FINE)) { | 725 if (_observeLog.isLoggable(Level.FINE)) { |
| 605 _observeLog.fine('[$localName] observeArrayValue: register observer ' | 726 _observeLog.fine('[$_name] observeArrayValue: register $name'); |
| 606 '$name'); | |
| 607 } | 727 } |
| 608 var sub = value.listChanges.listen((changes) { | 728 var sub = value.listChanges.listen((changes) { |
| 609 for (var callback in callbacks) { | 729 for (var callback in callbacks) { |
| 610 smoke.invoke(this, callback, [old], adjust: true); | 730 smoke.invoke(this, callback, [old], adjust: true); |
| 611 } | 731 } |
| 612 }); | 732 }); |
| 613 registerObserver('${name}__array', sub); | 733 registerNamedObserver('${name}__array', sub); |
| 614 } | 734 } |
| 615 } | 735 } |
| 616 | 736 |
| 617 bool unbindProperty(String name) => unregisterObserver(name); | 737 void registerObservers(Iterable<Bindable> observers) { |
| 738 _observers.add(observers); | |
| 739 } | |
| 618 | 740 |
| 619 void unbindAllProperties() { | 741 void closeObservers() { |
| 620 if (_propertyObserver != null) { | 742 _observers.forEach(closeObserverList); |
| 621 _propertyObserver.close(); | 743 _observers = []; |
| 622 _propertyObserver = null; | 744 } |
| 745 | |
| 746 void closeObserverList(Iterable<Bindable> observers) { | |
| 747 for (var o in observers) { | |
| 748 if (o != null) o.close(); | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
I wasn't really expecting a null in the list, I gu
Jennifer Messerly
2014/06/04 04:42:16
TBH, I'm not entirely sure. Just picked this up fr
| |
| 623 } | 749 } |
| 624 unregisterObservers(); | |
| 625 } | 750 } |
| 626 | 751 |
| 627 /// Bookkeeping observers for memory management. | 752 /// Bookkeeping observers for memory management. |
| 628 void registerObserver(String name, StreamSubscription sub) { | 753 void registerNamedObserver(String name, StreamSubscription sub) { |
| 629 if (_observers == null) { | 754 if (_namedObservers == null) { |
| 630 _observers = new Map<String, StreamSubscription>(); | 755 _namedObservers = new Map<String, StreamSubscription>(); |
| 631 } | 756 } |
| 632 _observers[name] = sub; | 757 _namedObservers[name] = sub; |
| 633 } | 758 } |
| 634 | 759 |
| 635 bool unregisterObserver(String name) { | 760 bool closeNamedObserver(String name) { |
| 636 var sub = _observers.remove(name); | 761 var sub = _namedObservers.remove(name); |
| 637 if (sub == null) return false; | 762 if (sub == null) return false; |
| 638 sub.cancel(); | 763 sub.cancel(); |
| 639 return true; | 764 return true; |
| 640 } | 765 } |
| 641 | 766 |
| 642 void unregisterObservers() { | 767 void closeNamedObservers() { |
| 643 if (_observers == null) return; | 768 if (_namedObservers == null) return; |
| 644 for (var sub in _observers.values) sub.cancel(); | 769 for (var sub in _namedObservers.values) { |
| 645 _observers.clear(); | 770 if (sub != null) sub.cancel(); |
| 646 _observers = null; | 771 } |
| 772 _namedObservers.clear(); | |
| 773 _namedObservers = null; | |
| 647 } | 774 } |
| 648 | 775 |
| 649 /// Bind the [name] property in this element to [bindable]. *Note* in Dart it | 776 /// Bind the [name] property in this element to [bindable]. *Note* in Dart it |
| 650 /// is necessary to also define the field: | 777 /// is necessary to also define the field: |
| 651 /// | 778 /// |
| 652 /// var myProperty; | 779 /// var myProperty; |
| 653 /// | 780 /// |
| 654 /// ready() { | 781 /// ready() { |
| 655 /// super.ready(); | 782 /// super.ready(); |
| 656 /// bindProperty(#myProperty, | 783 /// bindProperty(#myProperty, |
| 657 /// new PathObserver(this, 'myModel.path.to.otherProp')); | 784 /// new PathObserver(this, 'myModel.path.to.otherProp')); |
| 658 /// } | 785 /// } |
| 659 Bindable bindProperty(Symbol name, Bindable bindable) { | 786 Bindable bindProperty(Symbol name, Bindable bindable, {oneTime: false}) { |
| 660 // Dart note: normally we only reach this code when we know it's a | 787 // Dart note: normally we only reach this code when we know it's a |
| 661 // property, but if someone uses bindProperty directly they might get a | 788 // property, but if someone uses bindProperty directly they might get a |
| 662 // NoSuchMethodError either from the getField below, or from the setField | 789 // NoSuchMethodError either from the getField below, or from the setField |
| 663 // inside PolymerBinding. That doesn't seem unreasonable, but it's a slight | 790 // inside PolymerBinding. That doesn't seem unreasonable, but it's a slight |
| 664 // difference from Polymer.js behavior. | 791 // difference from Polymer.js behavior. |
| 665 | 792 |
| 666 if (_bindLog.isLoggable(Level.FINE)) { | 793 if (_bindLog.isLoggable(Level.FINE)) { |
| 667 _bindLog.fine('bindProperty: [$bindable] to [${localName}].[name]'); | 794 _bindLog.fine('bindProperty: [$bindable] to [$_name].[$name]'); |
| 668 } | 795 } |
| 669 | 796 |
| 670 // capture A's value if B's value is null or undefined, | 797 // capture A's value if B's value is null or undefined, |
| 671 // otherwise use B's value | 798 // otherwise use B's value |
| 672 // TODO(sorvell): need to review, can do with ObserverTransform | 799 // TODO(sorvell): need to review, can do with ObserverTransform |
| 673 var v = bindable.value; | 800 var v = bindable.value; |
| 674 if (v == null) { | 801 if (v == null) { |
| 675 bindable.value = smoke.read(this, name); | 802 bindable.value = smoke.read(this, name); |
| 676 } | 803 } |
| 677 | 804 |
| 678 // TODO(jmesserly): this will create another subscription. | 805 // TODO(jmesserly): we need to fix this -- it doesn't work like Polymer.js |
| 679 // It would be nice to have this reuse our existing _propertyObserver | 806 // bindings. https://code.google.com/p/dart/issues/detail?id=18343 |
| 680 // created by observeProperties, to avoid more observation overhead. | 807 // apply Polymer two-way reference binding |
| 808 //return Observer.bindToInstance(inA, inProperty, observable, | |
| 809 // resolveBindingValue); | |
| 681 return new _PolymerBinding(this, name, bindable); | 810 return new _PolymerBinding(this, name, bindable); |
| 682 } | 811 } |
| 683 | 812 |
| 684 /// Attach event listeners on the host (this) element. | 813 /// Attach event listeners on the host (this) element. |
| 685 void addHostListeners() { | 814 void addHostListeners() { |
| 686 var events = _declaration._eventDelegates; | 815 var events = _element._eventDelegates; |
| 687 if (events.isEmpty) return; | 816 if (events.isEmpty) return; |
| 688 | 817 |
| 689 if (_eventsLog.isLoggable(Level.FINE)) { | 818 if (_eventsLog.isLoggable(Level.FINE)) { |
| 690 _eventsLog.fine('[$localName] addHostListeners: $events'); | 819 _eventsLog.fine('[$_name] addHostListeners: $events'); |
| 691 } | |
| 692 addNodeListeners(this, events.keys, hostEventListener); | |
| 693 } | |
| 694 | |
| 695 void addNodeListeners(Node node, Iterable<String> events, | |
| 696 void listener(Event e)) { | |
| 697 | |
| 698 for (var name in events) { | |
| 699 addNodeListener(node, name, listener); | |
| 700 } | |
| 701 } | |
| 702 | |
| 703 void addNodeListener(Node node, String event, void listener(Event e)) { | |
| 704 node.on[event].listen(listener); | |
| 705 } | |
| 706 | |
| 707 void hostEventListener(Event event) { | |
| 708 // TODO(jmesserly): do we need this check? It was using cancelBubble, see: | |
| 709 // https://github.com/Polymer/polymer/issues/292 | |
| 710 if (!event.bubbles) return; | |
| 711 | |
| 712 bool log = _eventsLog.isLoggable(Level.FINE); | |
| 713 if (log) { | |
| 714 _eventsLog.fine('>>> [$localName]: hostEventListener(${event.type})'); | |
| 715 } | 820 } |
| 716 | 821 |
| 717 var h = findEventDelegate(event); | 822 // NOTE: host events look like bindings but really are not; |
| 718 if (h != null) { | 823 // (1) we don't want the attribute to be set and (2) we want to support |
| 719 if (log) _eventsLog.fine('[$localName] found host handler name [$h]'); | 824 // multiple event listeners ('host' and 'instance') and Node.bind |
| 720 var detail = event is CustomEvent ? event.detail : null; | 825 // by default supports 1 thing being bound. |
| 721 // TODO(jmesserly): cache the symbols? | 826 events.forEach((type, methodName) { |
| 722 dispatchMethod(this, h, [event, detail, this]); | 827 // Dart note: the getEventHandler method is on our PolymerExpressions. |
| 723 } | 828 // TODO(jmesserly): what if the user overrides the syntax? |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
so far we've told them to invoke prepareBinding (w
Jennifer Messerly
2014/06/04 04:42:16
Done. (via mixin)
Siggi Cherem (dart-lang)
2014/06/04 16:39:10
nice, thanks!
| |
| 724 | 829 var handler = element.syntax.getEventHandler(this, this, methodName); |
| 725 if (log) { | 830 addEventListener(type, handler); |
| 726 _eventsLog.fine('<<< [$localName]: hostEventListener(${event.type})'); | 831 }); |
| 727 } | |
| 728 } | 832 } |
| 729 | 833 |
| 730 String findEventDelegate(Event event) => | |
| 731 _declaration._eventDelegates[_eventNameFromType(event.type)]; | |
| 732 | |
| 733 /// Calls [methodOrCallback] with [args] if it is a closure, otherwise, treat | 834 /// Calls [methodOrCallback] with [args] if it is a closure, otherwise, treat |
| 734 /// it as a method name in [object], and invoke it. | 835 /// it as a method name in [object], and invoke it. |
| 735 void dispatchMethod(object, callbackOrMethod, List args) { | 836 void dispatchMethod(object, callbackOrMethod, List args) { |
| 736 bool log = _eventsLog.isLoggable(Level.FINE); | 837 bool log = _eventsLog.isLoggable(Level.FINE); |
| 737 if (log) _eventsLog.fine('>>> [$localName]: dispatch $callbackOrMethod'); | 838 if (log) _eventsLog.fine('>>> [$_name]: dispatch $callbackOrMethod'); |
| 738 | 839 |
| 739 if (callbackOrMethod is Function) { | 840 if (callbackOrMethod is Function) { |
| 740 int maxArgs = smoke.maxArgs(callbackOrMethod); | 841 int maxArgs = smoke.maxArgs(callbackOrMethod); |
| 741 if (maxArgs == -1) { | 842 if (maxArgs == -1) { |
| 742 _eventsLog.warning( | 843 _eventsLog.warning( |
| 743 'invalid callback: expected callback of 0, 1, 2, or 3 arguments'); | 844 'invalid callback: expected callback of 0, 1, 2, or 3 arguments'); |
| 744 } | 845 } |
| 745 args.length = maxArgs; | 846 args.length = maxArgs; |
| 746 Function.apply(callbackOrMethod, args); | 847 Function.apply(callbackOrMethod, args); |
| 747 } else if (callbackOrMethod is String) { | 848 } else if (callbackOrMethod is String) { |
| 748 smoke.invoke(object, smoke.nameToSymbol(callbackOrMethod), args, | 849 smoke.invoke(object, smoke.nameToSymbol(callbackOrMethod), args, |
| 749 adjust: true); | 850 adjust: true); |
| 750 } else { | 851 } else { |
| 751 _eventsLog.warning('invalid callback'); | 852 _eventsLog.warning('invalid callback'); |
| 752 } | 853 } |
| 753 | 854 |
| 754 if (log) _eventsLog.info('<<< [$localName]: dispatch $callbackOrMethod'); | 855 if (log) _eventsLog.info('<<< [$_name]: dispatch $callbackOrMethod'); |
| 755 } | |
| 756 | |
| 757 /// Bind events via attributes of the form `on-eventName`. This method can be | |
| 758 /// use to hooks into the model syntax and adds event listeners as needed. By | |
| 759 /// default, binding paths are always method names on the root model, the | |
| 760 /// custom element in which the node exists. Adding a '@' in the path directs | |
| 761 /// the event binding to use the model path as the event listener. In both | |
| 762 /// cases, the actual listener is attached to a generic method which evaluates | |
| 763 /// the bound path at event execution time. | |
| 764 // from src/instance/event.js#prepareBinding | |
| 765 static PrepareBindingFunction prepareBinding(String path, String name, node) { | |
| 766 | |
| 767 // provide an event-binding callback. | |
| 768 return (model, node, oneTime) { | |
| 769 if (_eventsLog.isLoggable(Level.FINE)) { | |
| 770 _eventsLog.fine('event: [$node].$name => [$model].$path())'); | |
| 771 } | |
| 772 var eventName = _removeEventPrefix(name); | |
| 773 // TODO(sigmund): polymer.js dropped event translations. reconcile? | |
| 774 var translated = _eventTranslations[eventName]; | |
| 775 eventName = translated != null ? translated : eventName; | |
| 776 | |
| 777 return new _EventBindable(node, eventName, model, path); | |
| 778 }; | |
| 779 } | 856 } |
| 780 | 857 |
| 781 /// Call [methodName] method on this object with [args]. | 858 /// Call [methodName] method on this object with [args]. |
| 782 invokeMethod(Symbol methodName, List args) => | 859 invokeMethod(Symbol methodName, List args) => |
| 783 smoke.invoke(this, methodName, args, adjust: true); | 860 smoke.invoke(this, methodName, args, adjust: true); |
| 784 | 861 |
| 785 /// Invokes a function asynchronously. | 862 /// Invokes a function asynchronously. |
| 786 /// This will call `Platform.flush()` and then return a `new Timer` | 863 /// This will call `Platform.flush()` and then return a `new Timer` |
| 787 /// with the provided [method] and [timeout]. | 864 /// with the provided [method] and [timeout]. |
| 788 /// | 865 /// |
| 789 /// If you would prefer to run the callback using | 866 /// If you would prefer to run the callback using |
| 790 /// [window.requestAnimationFrame], see the [async] method. | 867 /// [window.requestAnimationFrame], see the [async] method. |
| 791 // Dart note: "async" is split into 2 methods so it can have a sensible type | 868 /// |
| 792 // signatures. Also removed the various features that don't make sense in a | 869 /// To cancel, call [Timer.cancel] on the result of this method. |
| 793 // Dart world, like binding to "this" and taking arguments list. | |
| 794 Timer asyncTimer(void method(), Duration timeout) { | 870 Timer asyncTimer(void method(), Duration timeout) { |
| 871 // Dart note: "async" is split into 2 methods so it can have a sensible type | |
| 872 // signatures. Also removed the various features that don't make sense in a | |
| 873 // Dart world, like binding to "this" and taking arguments list. | |
| 874 | |
| 795 // when polyfilling Object.observe, ensure changes | 875 // when polyfilling Object.observe, ensure changes |
| 796 // propagate before executing the async method | 876 // propagate before executing the async method |
| 797 scheduleMicrotask(Observable.dirtyCheck); | 877 scheduleMicrotask(Observable.dirtyCheck); |
| 878 _Platform.callMethod('flush'); // for polymer-js interop | |
| 798 return new Timer(timeout, method); | 879 return new Timer(timeout, method); |
| 799 } | 880 } |
| 800 | 881 |
| 801 /// Invokes a function asynchronously. | 882 /// Invokes a function asynchronously. |
| 802 /// This will call `Platform.flush()` and then call | 883 /// This will call `Platform.flush()` and then call |
| 803 /// [window.requestAnimationFrame] with the provided [method] and return the | 884 /// [window.requestAnimationFrame] with the provided [method] and return the |
| 804 /// result. | 885 /// result. |
| 805 /// | 886 /// |
| 806 /// If you would prefer to run the callback after a given duration, see | 887 /// If you would prefer to run the callback after a given duration, see |
| 807 /// the [asyncTimer] method. | 888 /// the [asyncTimer] method. |
| 889 /// | |
| 890 /// If you would like to cancel this, use [cancelAsync]. | |
| 808 int async(RequestAnimationFrameCallback method) { | 891 int async(RequestAnimationFrameCallback method) { |
| 809 // when polyfilling Object.observe, ensure changes | 892 // when polyfilling Object.observe, ensure changes |
| 810 // propagate before executing the async method | 893 // propagate before executing the async method |
| 811 scheduleMicrotask(Observable.dirtyCheck); | 894 scheduleMicrotask(Observable.dirtyCheck); |
| 895 _Platform.callMethod('flush'); // for polymer-js interop | |
| 812 return window.requestAnimationFrame(method); | 896 return window.requestAnimationFrame(method); |
| 813 } | 897 } |
| 814 | 898 |
| 815 /// Fire a [CustomEvent] targeting [toNode], or this if toNode is not | 899 /// Cancel an operation scenduled by [async]. This is just shorthand for: |
| 816 /// supplied. Returns the [detail] object. | 900 /// window.cancelAnimationFrame(id); |
| 817 Object fire(String type, {Object detail, Node toNode, bool canBubble}) { | 901 void cancelAsync(int id) => window.cancelAnimationFrame(id); |
| 818 var node = toNode != null ? toNode : this; | 902 |
| 819 //log.events && console.log('[%s]: sending [%s]', node.localName, inType); | 903 /// Fire a [CustomEvent] targeting [onNode], or `this` if onNode is not |
| 820 node.dispatchEvent(new CustomEvent( | 904 /// supplied. Returns the new event. |
| 905 CustomEvent fire(String type, {Object detail, Node onNode, bool canBubble, | |
| 906 bool cancelable}) { | |
| 907 var node = onNode != null ? onNode : this; | |
| 908 var event = new CustomEvent( | |
| 821 type, | 909 type, |
| 822 canBubble: canBubble != null ? canBubble : true, | 910 canBubble: canBubble != null ? canBubble : true, |
| 911 cancelable: cancelable != null ? cancelable : true, | |
| 823 detail: detail | 912 detail: detail |
| 824 )); | 913 ); |
| 825 return detail; | 914 node.dispatchEvent(event); |
| 915 return event; | |
| 826 } | 916 } |
| 827 | 917 |
| 828 /// Fire an event asynchronously. See [async] and [fire]. | 918 /// Fire an event asynchronously. See [async] and [fire]. |
| 829 asyncFire(String type, {Object detail, Node toNode, bool canBubble}) { | 919 asyncFire(String type, {Object detail, Node toNode, bool canBubble}) { |
| 830 // TODO(jmesserly): I'm not sure this method adds much in Dart, it's easy to | 920 // TODO(jmesserly): I'm not sure this method adds much in Dart, it's easy to |
| 831 // add "() =>" | 921 // add "() =>" |
| 832 async((x) => fire( | 922 async((x) => fire( |
| 833 type, detail: detail, toNode: toNode, canBubble: canBubble)); | 923 type, detail: detail, onNode: toNode, canBubble: canBubble)); |
| 834 } | 924 } |
| 835 | 925 |
| 836 /// Remove [className] from [old], add class to [anew], if they exist. | 926 /// Remove [className] from [old], add class to [anew], if they exist. |
| 837 void classFollows(Element anew, Element old, String className) { | 927 void classFollows(Element anew, Element old, String className) { |
| 838 if (old != null) { | 928 if (old != null) { |
| 839 old.classes.remove(className); | 929 old.classes.remove(className); |
| 840 } | 930 } |
| 841 if (anew != null) { | 931 if (anew != null) { |
| 842 anew.classes.add(className); | 932 anew.classes.add(className); |
| 843 } | 933 } |
| 844 } | 934 } |
| 845 | 935 |
| 846 /// Installs external stylesheets and <style> elements with the attribute | 936 /// Installs external stylesheets and <style> elements with the attribute |
| 847 /// polymer-scope='controller' into the scope of element. This is intended | 937 /// polymer-scope='controller' into the scope of element. This is intended |
| 848 /// to be a called during custom element construction. Note, this incurs a | 938 /// to be called during custom element construction. |
| 849 /// per instance cost and should be used sparingly. | |
| 850 /// | |
| 851 /// The need for this type of styling should go away when the shadowDOM spec | |
| 852 /// addresses these issues: | |
| 853 /// | |
| 854 /// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21391 | |
| 855 /// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21390 | |
| 856 /// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21389 | |
| 857 /// | |
| 858 /// @param element The custom element instance into whose controller (parent) | |
| 859 /// scope styles will be installed. | |
| 860 /// @param elementElement The <element> containing controller styles. | |
| 861 // TODO(sorvell): remove when spec issues are addressed | |
| 862 void installControllerStyles() { | 939 void installControllerStyles() { |
| 863 var scope = findStyleController(); | 940 var scope = findStyleScope(); |
| 864 if (scope != null && scopeHasElementStyle(scope, _STYLE_CONTROLLER_SCOPE)) { | 941 if (scope != null && !scopeHasNamedStyle(scope, localName)) { |
| 865 // allow inherited controller styles | 942 // allow inherited controller styles |
| 866 var decl = _declaration; | 943 var decl = _element; |
| 867 var cssText = new StringBuffer(); | 944 var cssText = new StringBuffer(); |
| 868 while (decl != null) { | 945 while (decl != null) { |
| 869 cssText.write(decl.cssTextForScope(_STYLE_CONTROLLER_SCOPE)); | 946 cssText.write(decl.cssTextForScope(_STYLE_CONTROLLER_SCOPE)); |
| 870 decl = decl.superDeclaration; | 947 decl = decl.superDeclaration; |
| 871 } | 948 } |
| 872 if (cssText.length > 0) { | 949 if (cssText.isNotEmpty) { |
| 873 var style = decl.cssTextToScopeStyle(cssText.toString(), | 950 installScopeCssText('$cssText', scope); |
| 874 _STYLE_CONTROLLER_SCOPE); | |
| 875 // TODO(sorvell): for now these styles are not shimmed | |
| 876 // but we may need to shim them | |
| 877 Polymer.applyStyleToScope(style, scope); | |
| 878 } | 951 } |
| 879 } | 952 } |
| 880 } | 953 } |
| 881 | 954 |
| 882 Node findStyleController() { | 955 void installScopeStyle(style, [String name, Node scope]) { |
| 883 if (js.context.hasProperty('ShadowDOMPolyfill')) { | 956 if (scope == null) scope = findStyleScope(); |
| 884 return document.querySelector('head'); // get wrapped <head>. | 957 if (name == null) name = ''; |
| 885 } else { | 958 |
| 886 // find the shadow root that contains this element | 959 if (scope != null && !scopeHasNamedStyle(scope, '$_name$name')) { |
| 887 var n = this; | 960 var cssText = new StringBuffer(); |
| 888 while (n.parentNode != null) { | 961 if (style is Iterable) { |
| 889 n = n.parentNode; | 962 for (var s in style) { |
| 963 cssText..writeln(s.text)..writeln(); | |
| 964 } | |
| 965 } else { | |
| 966 cssText = (style as Node).text; | |
| 890 } | 967 } |
| 891 return identical(n, document) ? document.head : n; | 968 installScopeCssText('$cssText', scope, name); |
| 892 } | 969 } |
| 893 } | 970 } |
| 894 | 971 |
| 895 bool scopeHasElementStyle(scope, descriptor) { | 972 void installScopeCssText(String cssText, [Node scope, String name]) { |
| 896 var rule = '$_STYLE_SCOPE_ATTRIBUTE=$localName-$descriptor'; | 973 if (scope == null) scope = findStyleScope(); |
| 897 return scope.querySelector('style[$rule]') != null; | 974 if (name == null) name = ''; |
| 975 | |
| 976 if (scope == null) return; | |
| 977 | |
| 978 if (_ShadowCss != null) { | |
| 979 cssText = _shimCssText(cssText, scope is ShadowRoot ? scope.host : null); | |
| 980 } | |
| 981 var style = element.cssTextToScopeStyle(cssText, | |
| 982 _STYLE_CONTROLLER_SCOPE); | |
| 983 applyStyleToScope(style, scope); | |
| 984 // cache that this style has been applied | |
| 985 Set styles = _scopeStyles[scope]; | |
| 986 if (styles == null) _scopeStyles[scope] = styles = new Set(); | |
| 987 styles.add('$_name$name'); | |
| 988 } | |
| 989 | |
| 990 Node findStyleScope([node]) { | |
| 991 // find the shadow root that contains this element | |
| 992 var n = node; | |
| 993 if (n == null) n = this; | |
| 994 while (n.parentNode != null) { | |
| 995 n = n.parentNode; | |
| 996 } | |
| 997 return n; | |
| 998 } | |
| 999 | |
| 1000 bool scopeHasNamedStyle(Node scope, String name) { | |
| 1001 Set styles = _scopeStyles[scope]; | |
| 1002 return styles != null && styles.contains(name); | |
| 1003 } | |
| 1004 | |
| 1005 static final _scopeStyles = new Expando(); | |
| 1006 | |
| 1007 static String _shimCssText(String cssText, [Element host]) { | |
| 1008 var name = ''; | |
| 1009 var is_ = false; | |
| 1010 if (host != null) { | |
| 1011 name = host.localName; | |
| 1012 is_ = host.attributes.containsKey('is'); | |
| 1013 } | |
| 1014 var selector = _ShadowCss.callMethod('makeScopeSelector', [name, is_]); | |
| 1015 return _ShadowCss.callMethod('shimCssText', [cssText, selector]); | |
| 898 } | 1016 } |
| 899 | 1017 |
| 900 static void applyStyleToScope(StyleElement style, Node scope) { | 1018 static void applyStyleToScope(StyleElement style, Node scope) { |
| 901 if (style == null) return; | 1019 if (style == null) return; |
| 902 | 1020 |
| 1021 if (scope == document) scope = document.head; | |
| 1022 | |
| 1023 if (_hasShadowDomPolyfill) scope = document.head; | |
| 1024 | |
| 903 // TODO(sorvell): necessary for IE | 1025 // TODO(sorvell): necessary for IE |
| 904 // see https://connect.microsoft.com/IE/feedback/details/790212/ | 1026 // see https://connect.microsoft.com/IE/feedback/details/790212/ |
| 905 // cloning-a-style-element-and-adding-to-document-produces | 1027 // cloning-a-style-element-and-adding-to-document-produces |
| 906 // -unexpected-result#details | 1028 // -unexpected-result#details |
| 907 // var clone = style.cloneNode(true); | 1029 // var clone = style.cloneNode(true); |
| 908 var clone = new StyleElement()..text = style.text; | 1030 var clone = new StyleElement()..text = style.text; |
| 909 | 1031 |
| 910 var attr = style.attributes[_STYLE_SCOPE_ATTRIBUTE]; | 1032 var attr = style.attributes[_STYLE_SCOPE_ATTRIBUTE]; |
| 911 if (attr != null) { | 1033 if (attr != null) { |
| 912 clone.attributes[_STYLE_SCOPE_ATTRIBUTE] = attr; | 1034 clone.attributes[_STYLE_SCOPE_ATTRIBUTE] = attr; |
| 913 } | 1035 } |
| 914 | 1036 |
| 915 scope.append(clone); | 1037 // TODO(sorvell): probably too brittle; try to figure out |
| 1038 // where to put the element. | |
| 1039 var refNode = scope.firstChild; | |
| 1040 if (scope == document.head) { | |
| 1041 var selector = 'style[$_STYLE_SCOPE_ATTRIBUTE]'; | |
| 1042 var s$ = document.head.querySelectorAll(selector); | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
suggestion:
s$ => styleElement ?
Jennifer Messerly
2014/06/04 04:42:16
Sure changed. But: keeping names where possible is
Siggi Cherem (dart-lang)
2014/06/04 16:39:10
makes total sense, Just this one was a bit odd bec
| |
| 1043 if (s$.isNotEmpty) { | |
| 1044 refNode = s$.last.nextElementSibling; | |
| 1045 } | |
| 1046 } | |
| 1047 scope.insertBefore(clone, refNode); | |
| 1048 } | |
| 1049 | |
| 1050 /// Invoke [callback] in [wait], unless the job is re-registered, | |
| 1051 /// which resets the timer. If [wait] is not supplied, this will use | |
| 1052 /// [window.requestAnimationFrame] instead of a [Timer]. | |
| 1053 /// | |
| 1054 /// For example: | |
| 1055 /// | |
| 1056 /// _myJob = Polymer.scheduleJob(_myJob, callback); | |
| 1057 /// | |
| 1058 /// Returns the newly created job. | |
| 1059 // Dart note: renamed to scheduleJob to be a bit more consistent with Dart. | |
| 1060 PolymerJob scheduleJob(PolymerJob job, void callback(), [Duration wait]) { | |
| 1061 if (job == null) job = new PolymerJob._(); | |
| 1062 // Dart note: made start smarter, so we don't need to call stop. | |
| 1063 return job..start(callback, wait); | |
| 916 } | 1064 } |
| 917 } | 1065 } |
| 918 | 1066 |
| 919 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine | 1067 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine |
| 920 // the property on the PolymerElement instance to always get its value from the | 1068 // the property on the PolymerElement instance to always get its value from the |
| 921 // model@path. We can't replicate this in Dart so we do the next best thing: | 1069 // model@path. We can't replicate this in Dart so we do the next best thing: |
| 922 // listen to changes on both sides and update the values. | 1070 // listen to changes on both sides and update the values. |
| 923 // TODO(jmesserly): our approach leads to race conditions in the bindings. | 1071 // TODO(jmesserly): our approach leads to race conditions in the bindings. |
| 924 // See http://code.google.com/p/dart/issues/detail?id=13567 | 1072 // See http://code.google.com/p/dart/issues/detail?id=13567 |
| 925 class _PolymerBinding extends Bindable { | 1073 class _PolymerBinding extends Bindable { |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 964 } | 1112 } |
| 965 } | 1113 } |
| 966 | 1114 |
| 967 bool _toBoolean(value) => null != value && false != value; | 1115 bool _toBoolean(value) => null != value && false != value; |
| 968 | 1116 |
| 969 final Logger _observeLog = new Logger('polymer.observe'); | 1117 final Logger _observeLog = new Logger('polymer.observe'); |
| 970 final Logger _eventsLog = new Logger('polymer.events'); | 1118 final Logger _eventsLog = new Logger('polymer.events'); |
| 971 final Logger _unbindLog = new Logger('polymer.unbind'); | 1119 final Logger _unbindLog = new Logger('polymer.unbind'); |
| 972 final Logger _bindLog = new Logger('polymer.bind'); | 1120 final Logger _bindLog = new Logger('polymer.bind'); |
| 973 | 1121 |
| 974 final Expando _shadowHost = new Expando<Polymer>(); | |
| 975 | |
| 976 final Expando _eventHandledTable = new Expando<Set<Node>>(); | 1122 final Expando _eventHandledTable = new Expando<Set<Node>>(); |
| 977 | 1123 |
| 978 /// Base class for PolymerElements deriving from HtmlElement. | 1124 final JsObject _PolymerGestures = js.context['PolymerGestures']; |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
lowercase p? (_polymerGestures)
Jennifer Messerly
2014/06/04 04:42:16
I guess I find that less readable given the JS nam
Siggi Cherem (dart-lang)
2014/06/04 16:39:10
I was just confused initially because when I read
Jennifer Messerly
2014/06/04 17:52:16
yeah I can see that. if we change this one, should
Siggi Cherem (dart-lang)
2014/06/04 18:02:33
true, good point. we would probably want to be con
| |
| 979 /// | |
| 980 /// See [Polymer]. | |
| 981 class PolymerElement extends HtmlElement with Polymer, Observable { | |
| 982 PolymerElement.created() : super.created() { | |
| 983 polymerCreated(); | |
| 984 } | |
| 985 } | |
| 986 | 1125 |
| 987 class _PropertyValue { | |
| 988 Object oldValue, newValue; | |
| 989 _PropertyValue(this.oldValue); | |
| 990 } | |
| 991 | |
| 992 class PolymerExpressionsWithEvents extends PolymerExpressions { | |
| 993 PolymerExpressionsWithEvents({Map<String, Object> globals}) | |
| 994 : super(globals: globals); | |
| 995 | |
| 996 prepareBinding(String path, name, node) { | |
| 997 if (_hasEventPrefix(name)) return Polymer.prepareBinding(path, name, node); | |
| 998 return super.prepareBinding(path, name, node); | |
| 999 } | |
| 1000 } | |
| 1001 | |
| 1002 class _EventBindable extends Bindable { | |
| 1003 final Node _node; | |
| 1004 final String _eventName; | |
| 1005 final _model; | |
| 1006 final String _path; | |
| 1007 StreamSubscription _sub; | |
| 1008 | |
| 1009 _EventBindable(this._node, this._eventName, this._model, this._path); | |
| 1010 | |
| 1011 _listener(event) { | |
| 1012 var ctrlr = _findController(_node); | |
| 1013 if (ctrlr is! Polymer) return; | |
| 1014 var obj = ctrlr; | |
| 1015 var method = _path; | |
| 1016 if (_path.startsWith('@')) { | |
|
Siggi Cherem (dart-lang)
2014/06/03 02:23:44
wow, the '@' is gone?
might be changelog worthy.
Jennifer Messerly
2014/06/04 04:42:15
Done.
| |
| 1017 obj = _model; | |
| 1018 method = new PropertyPath(_path.substring(1)).getValueFrom(_model); | |
| 1019 } | |
| 1020 var detail = event is CustomEvent ? | |
| 1021 (event as CustomEvent).detail : null; | |
| 1022 ctrlr.dispatchMethod(obj, method, [event, detail, _node]); | |
| 1023 } | |
| 1024 | |
| 1025 // TODO(jmesserly): this won't find the correct host unless the ShadowRoot | |
| 1026 // was created on a PolymerElement. | |
| 1027 static Polymer _findController(Node node) { | |
| 1028 while (node.parentNode != null) { | |
| 1029 node = node.parentNode; | |
| 1030 } | |
| 1031 return _shadowHost[node]; | |
| 1032 } | |
| 1033 | |
| 1034 get value => null; | |
| 1035 | |
| 1036 open(callback) { | |
| 1037 _sub = _node.on[_eventName].listen(_listener); | |
| 1038 } | |
| 1039 | |
| 1040 close() { | |
| 1041 if (_sub != null) { | |
| 1042 if (_eventsLog.isLoggable(Level.FINE)) { | |
| 1043 _eventsLog.fine( | |
| 1044 'event.remove: [$_node].$_eventName => [$_model].$_path())'); | |
| 1045 } | |
| 1046 _sub.cancel(); | |
| 1047 _sub = null; | |
| 1048 } | |
| 1049 } | |
| 1050 } | |
| OLD | NEW |