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