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 |