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

Side by Side Diff: pkg/polymer/lib/src/instance.dart

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

Powered by Google App Engine
This is Rietveld 408576698