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

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

Powered by Google App Engine
This is Rietveld 408576698