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

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

Issue 336013003: Version 1.5.0-dev.4.14 (Closed) Base URL: http://dart.googlecode.com/svn/trunk/
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 | « dart/pkg/polymer/lib/src/events.dart ('k') | dart/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 29 matching lines...) Expand all
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
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
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
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 }
OLDNEW
« no previous file with comments | « dart/pkg/polymer/lib/src/events.dart ('k') | dart/pkg/polymer/lib/src/job.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698