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

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

Issue 30183005: port polymer - 00e2982c78fcd396adaebff3118e94029a2b9fb0 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/polymer/lib/src/declaration.dart ('k') | pkg/polymer/lib/src/loader.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 /** 7 /**
8 * Use this annotation to publish a field as an attribute. For example: 8 * Use this annotation to publish a field as an attribute. For example:
9 * 9 *
10 * class MyPlaybackElement extends PolymerElement { 10 * class MyPlaybackElement extends PolymerElement {
11 * // This will be available as an HTML attribute, for example: 11 * // This will be available as an HTML attribute, for example:
12 * // <my-playback volume="11"> 12 * // <my-playback volume="11">
13 * @published double volume; 13 * @published double volume;
14 * } 14 * }
15 */ 15 */
16 // TODO(jmesserly): does @published imply @observable or vice versa? 16 // TODO(jmesserly): does @published imply @observable or vice versa?
17 const published = const PublishedProperty(); 17 const published = const PublishedProperty();
18 18
19 /** An annotation used to publish a field as an attribute. See [published]. */ 19 /** An annotation used to publish a field as an attribute. See [published]. */
20 class PublishedProperty extends ObservableProperty { 20 class PublishedProperty extends ObservableProperty {
21 const PublishedProperty(); 21 const PublishedProperty();
22 } 22 }
23 23
24 /** 24 /**
25 * The mixin class for Polymer elements. It provides convenience features on top 25 * The mixin class for Polymer elements. It provides convenience features on top
26 * of the custom elements web standard. 26 * of the custom elements web standard.
27 */ 27 */
28 abstract class Polymer implements Element, Observable, NodeBindExtension { 28 abstract class Polymer implements Element, Observable, NodeBindExtension {
29 // Fully ported from revision: 29 // Fully ported from revision:
30 // https://github.com/Polymer/polymer/blob/4dc481c11505991a7c43228d3797d28f212 67779 30 // https://github.com/Polymer/polymer/blob/00e2982c78fcd396adaebff3118e94029a2 b9fb0
31 // 31 //
32 // src/boot.js (static APIs on "Polymer" object)
32 // src/instance/attributes.js 33 // src/instance/attributes.js
33 // src/instance/base.js 34 // src/instance/base.js
34 // src/instance/events.js 35 // src/instance/events.js
35 // src/instance/mdv.js 36 // src/instance/mdv.js
36 // src/instance/properties.js 37 // src/instance/properties.js
38 // src/instance/style.js
37 // src/instance/utils.js 39 // src/instance/utils.js
38 //
39 // Not yet ported:
40 // src/instance/style.js -- blocked on ShadowCSS.shimPolyfillDirectives
41 40
42 // TODO(jmesserly): should this really be public? 41 // TODO(jmesserly): should this really be public?
43 /** Regular expression that matches data-bindings. */ 42 /** Regular expression that matches data-bindings. */
44 static final bindPattern = new RegExp(r'\{\{([^{}]*)}}'); 43 static final bindPattern = new RegExp(r'\{\{([^{}]*)}}');
45 44
46 /** 45 /**
47 * Like [document.register] but for Polymer elements. 46 * Like [document.register] but for Polymer elements.
48 * 47 *
49 * Use the [name] to specify custom elment's tag name, for example: 48 * Use the [name] to specify custom elment's tag name, for example:
50 * "fancy-button" if the tag is used as `<fancy-button>`. 49 * "fancy-button" if the tag is used as `<fancy-button>`.
(...skipping 23 matching lines...) Expand all
74 * Future indicating that the Polymer library has been loaded and is ready 73 * Future indicating that the Polymer library has been loaded and is ready
75 * for use. 74 * for use.
76 */ 75 */
77 static Future get onReady => _ready.future; 76 static Future get onReady => _ready.future;
78 77
79 PolymerDeclaration _declaration; 78 PolymerDeclaration _declaration;
80 79
81 /** The most derived `<polymer-element>` declaration for this element. */ 80 /** The most derived `<polymer-element>` declaration for this element. */
82 PolymerDeclaration get declaration => _declaration; 81 PolymerDeclaration get declaration => _declaration;
83 82
84 Map<String, StreamSubscription> _elementObservers; 83 Map<String, StreamSubscription> _observers;
85 bool _unbound; // lazy-initialized 84 bool _unbound; // lazy-initialized
86 Job _unbindAllJob; 85 Job _unbindAllJob;
87 86
87 StreamSubscription _propertyObserver;
88
88 bool get _elementPrepared => _declaration != null; 89 bool get _elementPrepared => _declaration != null;
89 90
90 bool get applyAuthorStyles => false; 91 bool get applyAuthorStyles => false;
91 bool get resetStyleInheritance => false; 92 bool get resetStyleInheritance => false;
92 bool get alwaysPrepare => false; 93 bool get alwaysPrepare => false;
94 bool get preventDispose => false;
93 95
94 /** 96 /**
95 * Shadow roots created by [parseElement]. See [getShadowRoot]. 97 * Shadow roots created by [parseElement]. See [getShadowRoot].
96 */ 98 */
97 final _shadowRoots = new HashMap<String, ShadowRoot>(); 99 final _shadowRoots = new HashMap<String, ShadowRoot>();
98 100
99 /** Map of items in the shadow root(s) by their [Element.id]. */ 101 /** Map of items in the shadow root(s) by their [Element.id]. */
100 // TODO(jmesserly): various issues: 102 // TODO(jmesserly): various issues:
101 // * wrap in UnmodifiableMapView? 103 // * wrap in UnmodifiableMapView?
102 // * should we have an object that implements noSuchMethod? 104 // * should we have an object that implements noSuchMethod?
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
138 // Dart note: get the _declaration, which also marks _elementPrepared 140 // Dart note: get the _declaration, which also marks _elementPrepared
139 _declaration = _getDeclaration(this.runtimeType); 141 _declaration = _getDeclaration(this.runtimeType);
140 // do this first so we can observe changes during initialization 142 // do this first so we can observe changes during initialization
141 observeProperties(); 143 observeProperties();
142 // install boilerplate attributes 144 // install boilerplate attributes
143 copyInstanceAttributes(); 145 copyInstanceAttributes();
144 // process input attributes 146 // process input attributes
145 takeAttributes(); 147 takeAttributes();
146 // add event listeners 148 // add event listeners
147 addHostListeners(); 149 addHostListeners();
148 // guarantees that while preparing, any sub-elements will also be prepared 150 // guarantees that while preparing, any
151 // sub-elements are also prepared
149 _preparingElements++; 152 _preparingElements++;
150 // process declarative resources 153 // process declarative resources
151 parseDeclarations(_declaration); 154 parseDeclarations(_declaration);
155 // decrement semaphore
152 _preparingElements--; 156 _preparingElements--;
153 // user entry point 157 // user entry point
154 ready(); 158 ready();
155 } 159 }
156 160
157 /** Called when [prepareElement] is finished. */ 161 /** Called when [prepareElement] is finished. */
158 void ready() {} 162 void ready() {}
159 163
160 void enteredView() { 164 void enteredView() {
161 if (!_elementPrepared) { 165 if (!_elementPrepared) {
162 prepareElement(); 166 prepareElement();
163 } 167 }
164 cancelUnbindAll(preventCascade: true); 168 cancelUnbindAll(preventCascade: true);
165 } 169 }
166 170
167 void leftView() { 171 void leftView() {
168 asyncUnbindAll(); 172 if (!preventDispose) asyncUnbindAll();
169 } 173 }
170 174
171 /** Recursive ancestral <element> initialization, oldest first. */ 175 /** Recursive ancestral <element> initialization, oldest first. */
172 void parseDeclarations(PolymerDeclaration declaration) { 176 void parseDeclarations(PolymerDeclaration declaration) {
173 if (declaration != null) { 177 if (declaration != null) {
174 parseDeclarations(declaration.superDeclaration); 178 parseDeclarations(declaration.superDeclaration);
175 parseDeclaration(declaration); 179 parseDeclaration(declaration);
176 } 180 }
177 } 181 }
178 182
179 /** 183 /**
180 * Parse input `<polymer-element>` as needed, override for custom behavior. 184 * Parse input `<polymer-element>` as needed, override for custom behavior.
181 */ 185 */
182 void parseDeclaration(Element elementElement) { 186 void parseDeclaration(Element elementElement) {
183 var root = shadowFromTemplate(fetchTemplate(elementElement)); 187 var template = fetchTemplate(elementElement);
188
189 var root = null;
190 if (template != null) {
191 if (_declaration.attributes.containsKey('lightdom')) {
192 lightFromTemplate(template);
193 } else {
194 root = shadowFromTemplate(template);
195 }
196 }
184 197
185 // Dart note: the following code is to support the getShadowRoot method. 198 // Dart note: the following code is to support the getShadowRoot method.
186 if (root is! ShadowRoot) return; 199 if (root is! ShadowRoot) return;
187 200
188 var name = elementElement.attributes['name']; 201 var name = elementElement.attributes['name'];
189 if (name == null) return; 202 if (name == null) return;
190 _shadowRoots[name] = root; 203 _shadowRoots[name] = root;
191 } 204 }
192 205
193 /** 206 /**
194 * Return a shadow-root template (if desired), override for custom behavior. 207 * Return a shadow-root template (if desired), override for custom behavior.
195 */ 208 */
196 Element fetchTemplate(Element elementElement) => 209 Element fetchTemplate(Element elementElement) =>
197 elementElement.query('template'); 210 elementElement.query('template');
198 211
199 /** 212 /**
213 * Utility function that stamps a `<template>` into light-dom.
214 */
215 Node lightFromTemplate(Element template) {
216 if (template == null) return null;
217 // stamp template
218 // which includes parsing and applying MDV bindings before being
219 // inserted (to avoid {{}} in attribute values)
220 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
221 var dom = instanceTemplate(template);
222 // append to shadow dom
223 append(dom);
224 // perform post-construction initialization tasks on shadow root
225 shadowRootReady(this, template);
226 // return the created shadow root
227 return dom;
228 }
229
230 /**
200 * Utility function that creates a shadow root from a `<template>`. 231 * Utility function that creates a shadow root from a `<template>`.
201 * 232 *
202 * The base implementation will return a [ShadowRoot], but you can replace it 233 * The base implementation will return a [ShadowRoot], but you can replace it
203 * with your own code and skip ShadowRoot creation. In that case, you should 234 * with your own code and skip ShadowRoot creation. In that case, you should
204 * return `null`. 235 * return `null`.
205 * 236 *
206 * In your overridden method, you can use [instanceTemplate] to stamp the 237 * In your overridden method, you can use [instanceTemplate] to stamp the
207 * template and initialize data binding, and [shadowRootReady] to intialize 238 * template and initialize data binding, and [shadowRootReady] to intialize
208 * other Polymer features like event handlers. It is fine to call 239 * other Polymer features like event handlers. It is fine to call
209 * shadowRootReady with a node something other than a ShadowRoot; for example, 240 * shadowRootReady with a node something other than a ShadowRoot; for example,
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
253 $[n.id] = n; 284 $[n.id] = n;
254 } 285 }
255 } 286 }
256 287
257 void attributeChanged(String name, String oldValue, String newValue) { 288 void attributeChanged(String name, String oldValue, String newValue) {
258 if (name != 'class' && name != 'style') { 289 if (name != 'class' && name != 'style') {
259 attributeToProperty(name, newValue); 290 attributeToProperty(name, newValue);
260 } 291 }
261 } 292 }
262 293
263 // TODO(jmesserly): use stream or future here? 294 // TODO(jmesserly): this could be a top level method.
264 /** 295 /**
265 * Run the `listener` callback *once* 296 * Returns a future when `node` changes, or when its children or subtree
266 * when `node` changes, or when its children or subtree changes. 297 * changes.
267 * 298 *
268 * 299 * Use [MutationObserver] if you want to listen to a stream of changes.
269 * See [MutationObserver] if you want to listen to a stream of
270 * changes.
271 */ 300 */
272 void onMutation(Node node, void listener(MutationObserver obs)) { 301 Future<List<MutationRecord>> onMutation(Node node) {
273 new MutationObserver((records, MutationObserver observer) { 302 var completer = new Completer();
274 listener(observer); 303 new MutationObserver((mutations, observer) {
275 observer.disconnect(); 304 observer.disconnect();
305 completer.complete(mutations);
276 })..observe(node, childList: true, subtree: true); 306 })..observe(node, childList: true, subtree: true);
307 return completer.future;
277 } 308 }
278 309
279 void copyInstanceAttributes() { 310 void copyInstanceAttributes() {
280 _declaration._instanceAttributes.forEach((name, value) { 311 _declaration._instanceAttributes.forEach((name, value) {
281 attributes.putIfAbsent(name, () => value); 312 attributes.putIfAbsent(name, () => value);
282 }); 313 });
283 } 314 }
284 315
285 void takeAttributes() { 316 void takeAttributes() {
286 if (_declaration._publishLC == null) return; 317 if (_declaration._publishLC == null) return;
(...skipping 10 matching lines...) Expand all
297 var property = propertyForAttribute(name); 328 var property = propertyForAttribute(name);
298 if (property == null) return; 329 if (property == null) return;
299 330
300 // filter out 'mustached' values, these are to be 331 // filter out 'mustached' values, these are to be
301 // replaced with bound-data and are not yet values 332 // replaced with bound-data and are not yet values
302 // themselves. 333 // themselves.
303 if (value == null || value.contains(Polymer.bindPattern)) return; 334 if (value == null || value.contains(Polymer.bindPattern)) return;
304 335
305 // get original value 336 // get original value
306 final self = reflect(this); 337 final self = reflect(this);
307 final defaultValue = self.getField(property.simpleName).reflectee; 338 final currentValue = self.getField(property.simpleName).reflectee;
308 339
309 // deserialize Boolean or Number values from attribute 340 // deserialize Boolean or Number values from attribute
310 final newValue = deserializeValue(value, defaultValue, 341 final newValue = deserializeValue(value, currentValue,
311 _inferPropertyType(defaultValue, property)); 342 _inferPropertyType(currentValue, property));
312 343
313 // only act if the value has changed 344 // only act if the value has changed
314 if (!identical(newValue, defaultValue)) { 345 if (!identical(newValue, currentValue)) {
315 // install new value (has side-effects) 346 // install new value (has side-effects)
316 self.setField(property.simpleName, newValue); 347 self.setField(property.simpleName, newValue);
317 } 348 }
318 } 349 }
319 350
320 /** Return the published property matching name, or null. */ 351 /** Return the published property matching name, or null. */
321 // TODO(jmesserly): should we just return Symbol here? 352 // TODO(jmesserly): should we just return Symbol here?
322 DeclarationMirror propertyForAttribute(String name) { 353 DeclarationMirror propertyForAttribute(String name) {
323 final publishLC = _declaration._publishLC; 354 final publishLC = _declaration._publishLC;
324 if (publishLC == null) return null; 355 if (publishLC == null) return null;
325 //console.log('propertyForAttribute:', name, 'matches', match); 356 //console.log('propertyForAttribute:', name, 'matches', match);
326 return publishLC[name]; 357 return publishLC[name];
327 } 358 }
328 359
329 /** 360 /**
330 * Convert representation of [value] based on [type] and [defaultValue]. 361 * Convert representation of [value] based on [type] and [currentValue].
331 */ 362 */
332 // TODO(jmesserly): this should probably take a ClassMirror instead of 363 // TODO(jmesserly): this should probably take a ClassMirror instead of
333 // TypeMirror, but it is currently impossible to get from a TypeMirror to a 364 // TypeMirror, but it is currently impossible to get from a TypeMirror to a
334 // ClassMirror. 365 // ClassMirror.
335 Object deserializeValue(String value, Object defaultValue, TypeMirror type) => 366 Object deserializeValue(String value, Object currentValue, TypeMirror type) =>
336 deserialize.deserializeValue(value, defaultValue, type); 367 deserialize.deserializeValue(value, currentValue, type);
337 368
338 String serializeValue(Object value) { 369 String serializeValue(Object value) {
339 if (value == null) return null; 370 if (value == null) return null;
340 371
341 if (value is bool) { 372 if (value is bool) {
342 return _toBoolean(value) ? '' : null; 373 return _toBoolean(value) ? '' : null;
343 } else if (value is String || value is int || value is double) { 374 } else if (value is String || value is int || value is double) {
344 return '$value'; 375 return '$value';
345 } 376 }
346 return null; 377 return null;
347 } 378 }
348 379
349 void reflectPropertyToAttribute(String name) { 380 void reflectPropertyToAttribute(Symbol name) {
350 // TODO(sjmiles): consider memoizing this 381 // TODO(sjmiles): consider memoizing this
351 final self = reflect(this); 382 final self = reflect(this);
352 // try to intelligently serialize property value 383 // try to intelligently serialize property value
353 // TODO(jmesserly): cache symbol? 384 // TODO(jmesserly): cache symbol?
354 final propValue = self.getField(new Symbol(name)).reflectee; 385 final propValue = self.getField(name).reflectee;
355 final serializedValue = serializeValue(propValue); 386 final serializedValue = serializeValue(propValue);
356 // boolean properties must reflect as boolean attributes 387 // boolean properties must reflect as boolean attributes
357 if (serializedValue != null) { 388 if (serializedValue != null) {
358 attributes[name] = serializedValue; 389 attributes[MirrorSystem.getName(name)] = serializedValue;
359 // TODO(sorvell): we should remove attr for all properties 390 // TODO(sorvell): we should remove attr for all properties
360 // that have undefined serialization; however, we will need to 391 // that have undefined serialization; however, we will need to
361 // refine the attr reflection system to achieve this; pica, for example, 392 // refine the attr reflection system to achieve this; pica, for example,
362 // relies on having inferredType object properties not removed as 393 // relies on having inferredType object properties not removed as
363 // attrs. 394 // attrs.
364 } else if (propValue is bool) { 395 } else if (propValue is bool) {
365 attributes.remove(name); 396 attributes.remove(MirrorSystem.getName(name));
366 } 397 }
367 } 398 }
368 399
369 /** 400 /**
370 * Creates the document fragment to use for each instance of the custom 401 * Creates the document fragment to use for each instance of the custom
371 * element, given the `<template>` node. By default this is equivalent to: 402 * element, given the `<template>` node. By default this is equivalent to:
372 * 403 *
373 * templateBind(template).createInstance(this, polymerSyntax); 404 * templateBind(template).createInstance(this, polymerSyntax);
374 * 405 *
375 * Where polymerSyntax is a singleton `PolymerExpressions` instance from the 406 * Where polymerSyntax is a singleton `PolymerExpressions` instance from the
(...skipping 17 matching lines...) Expand all
393 var property = propertyForAttribute(name); 424 var property = propertyForAttribute(name);
394 if (property != null) { 425 if (property != null) {
395 unbind(name); 426 unbind(name);
396 // use n-way Polymer binding 427 // use n-way Polymer binding
397 var observer = bindProperty(property.simpleName, model, path); 428 var observer = bindProperty(property.simpleName, model, path);
398 // reflect bound property to attribute when binding 429 // reflect bound property to attribute when binding
399 // to ensure binding is not left on attribute if property 430 // to ensure binding is not left on attribute if property
400 // does not update due to not changing. 431 // does not update due to not changing.
401 // Dart note: we include this patch: 432 // Dart note: we include this patch:
402 // https://github.com/Polymer/polymer/pull/319 433 // https://github.com/Polymer/polymer/pull/319
403 reflectPropertyToAttribute(MirrorSystem.getName(property.simpleName)); 434 reflectPropertyToAttribute(property.simpleName);
404 return bindings[name] = observer; 435 return bindings[name] = observer;
405 } else { 436 } else {
406 // Cannot call super.bind because template_binding is its own package 437 // Cannot call super.bind because template_binding is its own package
407 return nodeBindFallback(this).bind(name, model, path); 438 return nodeBindFallback(this).bind(name, model, path);
408 } 439 }
409 } 440 }
410 441
411 Map<String, NodeBinding> get bindings => nodeBindFallback(this).bindings; 442 Map<String, NodeBinding> get bindings => nodeBindFallback(this).bindings;
412 443
413 void unbind(String name) => nodeBindFallback(this).unbind(name); 444 void unbind(String name) => nodeBindFallback(this).unbind(name);
414 445
415 void asyncUnbindAll() { 446 void asyncUnbindAll() {
416 if (_unbound == true) return; 447 if (_unbound == true) return;
417 _unbindLog.fine('[$localName] asyncUnbindAll'); 448 _unbindLog.fine('[$localName] asyncUnbindAll');
418 _unbindAllJob = job(_unbindAllJob, unbindAll, const Duration(seconds: 0)); 449 _unbindAllJob = job(_unbindAllJob, unbindAll, const Duration(seconds: 0));
419 } 450 }
420 451
421 void unbindAll() { 452 void unbindAll() {
422 if (_unbound == true) return; 453 if (_unbound == true) return;
423 454
424 unbindAllProperties(); 455 unbindAllProperties();
425 nodeBindFallback(this).unbindAll(); 456 nodeBindFallback(this).unbindAll();
426 457
427 _unbindNodeTree(shadowRoot); 458 var root = shadowRoot;
428 // TODO(sjmiles): must also unbind inherited shadow roots 459 while (root != null) {
460 _unbindNodeTree(root);
461 root = root.olderShadowRoot;
462 }
429 _unbound = true; 463 _unbound = true;
430 } 464 }
431 465
432 void cancelUnbindAll({bool preventCascade}) { 466 void cancelUnbindAll({bool preventCascade}) {
433 if (_unbound == true) { 467 if (_unbound == true) {
434 _unbindLog.warning( 468 _unbindLog.warning(
435 '[$localName] already unbound, cannot cancel unbindAll'); 469 '[$localName] already unbound, cannot cancel unbindAll');
436 return; 470 return;
437 } 471 }
438 _unbindLog.fine('[$localName] cancelUnbindAll'); 472 _unbindLog.fine('[$localName] cancelUnbindAll');
(...skipping 20 matching lines...) Expand all
459 if (node == null) return; 493 if (node == null) return;
460 494
461 callback(node); 495 callback(node);
462 for (var child = node.firstChild; child != null; child = child.nextNode) { 496 for (var child = node.firstChild; child != null; child = child.nextNode) {
463 _forNodeTree(child, callback); 497 _forNodeTree(child, callback);
464 } 498 }
465 } 499 }
466 500
467 /** Set up property observers. */ 501 /** Set up property observers. */
468 void observeProperties() { 502 void observeProperties() {
469 // TODO(sjmiles): 503 // TODO(jmesserly): we don't have CompoundPathObserver, so this
470 // we observe published properties so we can reflect them to attributes 504 // implementation is a little bit different. We also don't expose the
471 // ~100% of our team's applications would work without this reflection, 505 // "generateCompoundPathObserver" method.
472 // perhaps we can make it optional somehow
473 //
474 // add user's observers
475 final observe = _declaration._observe; 506 final observe = _declaration._observe;
476 final publish = _declaration._publish; 507 final publish = _declaration._publish;
508
477 if (observe != null) { 509 if (observe != null) {
478 observe.forEach((name, value) { 510 for (var name in observe.keys) {
479 if (publish != null && publish.containsKey(name)) { 511 observeArrayValue(name, reflect(this).getField(name), null);
480 observeBoth(name, value); 512 }
481 } else {
482 observeProperty(name, value);
483 }
484 });
485 } 513 }
486 // add observers for published properties 514 if (observe != null || publish != null) {
487 if (publish != null) { 515 // Instead of using CompoundPathObserver, set up a binding using normal
488 publish.forEach((name, value) { 516 // change records.
489 if (observe == null || !observe.containsKey(name)) { 517 _propertyObserver = changes.listen(notifyPropertyChanges);
490 observeAttributeProperty(name);
491 }
492 });
493 } 518 }
494 } 519 }
495 520
496 void _observe(String name, void callback(newValue, oldValue)) { 521 /** Responds to property changes on this element. */
497 _observeLog.fine('[$localName] watching [$name]'); 522 // Dart note: this takes a list of changes rather than trying to deal with
498 // TODO(jmesserly): this is a little different than the JS version so we 523 // what CompoundPathObserver would give us. Simpler and probably faster too.
499 // can pass the oldValue, which is missing from Dart's PathObserver. 524 void notifyPropertyChanges(Iterable<ChangeRecord> changes) {
500 // This probably gives us worse performance. 525 final observe = _declaration._observe;
501 var path = new PathObserver(this, name); 526 final publish = _declaration._publish;
502 Object oldValue = null;
503 _registerObserver(name, path.changes.listen((_) {
504 final newValue = path.value;
505 final old = oldValue;
506 oldValue = newValue;
507 callback(newValue, old);
508 }));
509 }
510 527
511 void _registerObserver(String name, StreamSubscription sub) { 528 // Summarize old and new values, so we only handle each change once.
512 if (_elementObservers == null) { 529 final valuePairs = new Map<Symbol, _PropertyValue>();
513 _elementObservers = new Map<String, StreamSubscription>(); 530 for (var c in changes) {
531 if (c is! PropertyChangeRecord) continue;
532
533 valuePairs.putIfAbsent(c.name, () => new _PropertyValue(c.oldValue))
534 .newValue = c.newValue;
514 } 535 }
515 _elementObservers[name] = sub;
516 }
517 536
518 void observeAttributeProperty(String name) { 537 valuePairs.forEach((name, pair) {
519 _observe(name, (value, old) => reflectPropertyToAttribute(name)); 538 if (publish != null && publish.containsKey(name)) {
520 } 539 reflectPropertyToAttribute(name);
540 }
541 if (observe == null) return;
521 542
522 void observeProperty(String name, Symbol method) { 543 var method = observe[name];
523 _observe(name, (value, old) => _invoke(method, [old])); 544 if (method != null) {
524 } 545 // observes the value if it is an array
525 546 observeArrayValue(name, pair.newValue, pair.oldValue);
526 void observeBoth(String name, Symbol methodName) { 547 // TODO(jmesserly): the JS code tries to avoid calling the same method
527 _observe(name, (value, old) { 548 // twice, but I don't see how that is possible.
528 reflectPropertyToAttribute(name); 549 // Dart note: JS also passes "arguments", so we pass all change records.
529 _invoke(methodName, [old]); 550 invokeMethod(method, [pair.oldValue, pair.newValue, changes]);
551 }
530 }); 552 });
531 } 553 }
532 554
533 void unbindProperty(String name) { 555 void observeArrayValue(Symbol name, Object value, Object old) {
534 if (_elementObservers == null) return; 556 final observe = _declaration._observe;
535 var sub = _elementObservers.remove(name); 557 if (observe == null) return;
536 if (sub != null) sub.cancel(); 558
559 // we only care if there are registered side-effects
560 var callbackName = observe[name];
561 if (callbackName == null) return;
562
563 // if we are observing the previous value, stop
564 if (old is ObservableList) {
565 if (_observeLog.isLoggable(Level.FINE)) {
566 _observeLog.fine('[$localName] observeArrayValue: unregister observer '
567 '$name');
568 }
569
570 unregisterObserver('${MirrorSystem.getName(name)}__array');
571 }
572 // if the new value is an array, being observing it
573 if (value is ObservableList) {
574 if (_observeLog.isLoggable(Level.FINE)) {
575 _observeLog.fine('[$localName] observeArrayValue: register observer '
576 '$name');
577 }
578 var sub = (value as ObservableList).changes.listen((changes) {
579 invokeMethod(callbackName, [old]);
580 });
581 registerObserver('${MirrorSystem.getName(name)}__array', sub);
582 }
537 } 583 }
538 584
585 void unbindProperty(String name) => unregisterObserver(name);
586
539 void unbindAllProperties() { 587 void unbindAllProperties() {
540 if (_elementObservers == null) return; 588 if (_propertyObserver != null) {
541 for (var sub in _elementObservers.values) sub.cancel(); 589 _propertyObserver.cancel();
542 _elementObservers.clear(); 590 _propertyObserver = null;
591 }
592 unregisterObservers();
593 }
594
595 /** Bookkeeping observers for memory management. */
596 void registerObserver(String name, StreamSubscription sub) {
597 if (_observers == null) {
598 _observers = new Map<String, StreamSubscription>();
599 }
600 _observers[name] = sub;
601 }
602
603 bool unregisterObserver(String name) {
604 var sub = _observers.remove(name);
605 if (sub == null) return false;
606 subl.cancel();
607 return true;
608 }
609
610 void unregisterObservers() {
611 if (_observers == null) return;
612 for (var sub in _observers.values) sub.cancel();
613 _observers.clear();
614 _observers = null;
543 } 615 }
544 616
545 /** 617 /**
546 * Bind a [property] in this object to a [path] in model. *Note* in Dart it 618 * Bind a [property] in this object to a [path] in model. *Note* in Dart it
547 * is necessary to also define the field: 619 * is necessary to also define the field:
548 * 620 *
549 * var myProperty; 621 * var myProperty;
550 * 622 *
551 * created() { 623 * created() {
552 * super.created(); 624 * super.created();
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
648 720
649 String findEventDelegate(Event event) => 721 String findEventDelegate(Event event) =>
650 _declaration._eventDelegates[_eventNameFromType(event.type)]; 722 _declaration._eventDelegates[_eventNameFromType(event.type)];
651 723
652 /** Call [methodName] method on [this] with [args], if the method exists. */ 724 /** Call [methodName] method on [this] with [args], if the method exists. */
653 // TODO(jmesserly): I removed the [node] argument as it was unused. Reconcile. 725 // TODO(jmesserly): I removed the [node] argument as it was unused. Reconcile.
654 void dispatchMethod(Symbol methodName, List args) { 726 void dispatchMethod(Symbol methodName, List args) {
655 bool log = _eventsLog.isLoggable(Level.FINE); 727 bool log = _eventsLog.isLoggable(Level.FINE);
656 if (log) _eventsLog.fine('>>> [$localName]: dispatch $methodName'); 728 if (log) _eventsLog.fine('>>> [$localName]: dispatch $methodName');
657 729
658 _invoke(methodName, args); 730 invokeMethod(methodName, args);
659 731
660 if (log) _eventsLog.info('<<< [$localName]: dispatch $methodName'); 732 if (log) _eventsLog.info('<<< [$localName]: dispatch $methodName');
661 } 733 }
662 734
663 InstanceMirror _invoke(Symbol methodName, List args) { 735 invokeMethod(Symbol methodName, List args) {
664 // TODO(sigmund): consider making callbacks list all arguments 736 // TODO(sigmund): consider making callbacks list all arguments
665 // explicitly. Unless VM mirrors are optimized first, this will be expensive 737 // explicitly. Unless VM mirrors are optimized first, this will be expensive
666 // once custom elements extend directly from Element (see issue 11108). 738 // once custom elements extend directly from Element (see issue 11108).
667 var self = reflect(this); 739 var self = reflect(this);
668 var method = self.type.methods[methodName]; 740 var method = self.type.methods[methodName];
669 if (method != null) { 741 if (method != null) {
670 // This will either truncate the argument list or extend it with extra 742 // This will either truncate the argument list or extend it with extra
671 // null arguments, so it will match the signature. 743 // null arguments, so it will match the signature.
672 // TODO(sigmund): consider accepting optional arguments when we can tell 744 // TODO(sigmund): consider accepting optional arguments when we can tell
673 // them appart from named arguments (see http://dartbug.com/11334) 745 // them appart from named arguments (see http://dartbug.com/11334)
674 args.length = method.parameters.where((p) => !p.isOptional).length; 746 args.length = method.parameters.where((p) => !p.isOptional).length;
675 } 747 }
676 return self.invoke(methodName, args); 748 return self.invoke(methodName, args).reflectee;
677 } 749 }
678 750
679 void instanceEventListener(Event event) { 751 void instanceEventListener(Event event) {
680 _listenLocal(this, event); 752 _listenLocal(this, event);
681 } 753 }
682 754
683 // TODO(sjmiles): much of the below privatized only because of the vague 755 // TODO(sjmiles): much of the below privatized only because of the vague
684 // notion this code is too fiddly and we need to revisit the core feature 756 // notion this code is too fiddly and we need to revisit the core feature
685 void _listenLocal(Polymer host, Event event) { 757 void _listenLocal(Polymer host, Event event) {
686 // TODO(jmesserly): do we need this check? It was using cancelBubble, see: 758 // TODO(jmesserly): do we need this check? It was using cancelBubble, see:
(...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after
857 * Remove [className] from [old], add class to [anew], if they exist. 929 * Remove [className] from [old], add class to [anew], if they exist.
858 */ 930 */
859 void classFollows(Element anew, Element old, String className) { 931 void classFollows(Element anew, Element old, String className) {
860 if (old != null) { 932 if (old != null) {
861 old.classes.remove(className); 933 old.classes.remove(className);
862 } 934 }
863 if (anew != null) { 935 if (anew != null) {
864 anew.classes.add(className); 936 anew.classes.add(className);
865 } 937 }
866 } 938 }
939
940 /**
941 * Installs external stylesheets and <style> elements with the attribute
942 * polymer-scope='controller' into the scope of element. This is intended
943 * to be a called during custom element construction. Note, this incurs a
944 * per instance cost and should be used sparingly.
945 *
946 * The need for this type of styling should go away when the shadowDOM spec
947 * addresses these issues:
948 *
949 * https://www.w3.org/Bugs/Public/show_bug.cgi?id=21391
950 * https://www.w3.org/Bugs/Public/show_bug.cgi?id=21390
951 * https://www.w3.org/Bugs/Public/show_bug.cgi?id=21389
952 *
953 * @param element The custom element instance into whose controller (parent)
954 * scope styles will be installed.
955 * @param elementElement The <element> containing controller styles.
956 */
957 // TODO(sorvell): remove when spec issues are addressed
958 void installControllerStyles() {
959 var scope = findStyleController();
960 if (scope != null && scopeHasElementStyle(scope, _STYLE_CONTROLLER_SCOPE)) {
961 // allow inherited controller styles
962 var decl = _declaration;
963 var cssText = new StringBuffer();
964 while (decl != null) {
965 cssText.write(decl.cssTextForScope(_STYLE_CONTROLLER_SCOPE));
966 decl = decl.superDeclaration;
967 }
968 if (cssText.length > 0) {
969 var style = this.element.cssTextToScopeStyle(cssText.toString(),
970 _STYLE_CONTROLLER_SCOPE);
971 // TODO(sorvell): for now these styles are not shimmed
972 // but we may need to shim them
973 Polymer.applyStyleToScope(style, scope);
974 }
975 }
976 }
977
978 Node findStyleController() {
979 if (js.context != null && js.context['ShadowDOMPolyfill'] != null) {
980 return document.querySelector('head'); // get wrapped <head>.
981 } else {
982 // find the shadow root that contains this element
983 var n = this;
984 while (n.parentNode) {
985 n = n.parentNode;
986 }
987 return identical(n, document) ? document.head : n;
988 }
989 }
990
991 bool scopeHasElementStyle(scope, descriptor) {
992 var rule = '$_STYLE_SCOPE_ATTRIBUTE=$localName-$descriptor';
993 return scope.querySelector('style[$rule]') != null;
994 }
995
996 static void applyStyleToScope(StyleElement style, Node scope) {
997 if (style == null) return;
998
999 // TODO(sorvell): necessary for IE
1000 // see https://connect.microsoft.com/IE/feedback/details/790212/
1001 // cloning-a-style-element-and-adding-to-document-produces
1002 // -unexpected-result#details
1003 // var clone = style.cloneNode(true);
1004 var clone = new StyleElement()..text = style.text;
1005
1006 var attr = style.attributes[_STYLE_SCOPE_ATTRIBUTE];
1007 if (attr != null) {
1008 clone.attributes[_STYLE_SCOPE_ATTRIBUTE] = attr;
1009 }
1010
1011 scope.append(clone);
1012 }
1013
1014 /**
1015 * Prevents flash of unstyled content
1016 * This is the list of selectors for veiled elements
1017 */
1018 static List<Element> veiledElements = ['body'];
1019
1020 /** Apply unveil class. */
1021 static void unveilElements() {
1022 window.requestAnimationFrame((_) {
1023 var nodes = document.querySelectorAll('.$_VEILED_CLASS');
1024 for (var node in nodes) {
1025 (node.classes)..add(_UNVEIL_CLASS)..remove(_VEILED_CLASS);
1026 }
1027 // NOTE: depends on transition end event to remove 'unveil' class.
1028 if (nodes.isNotEmpty) {
1029 window.onTransitionEnd.first.then((_) {
1030 for (var node in nodes) {
1031 node.classes.remove(_UNVEIL_CLASS);
1032 }
1033 });
1034 }
1035 });
1036 }
867 } 1037 }
868 1038
869 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine 1039 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine
870 // the property on the PolymerElement instance to always get its value from the 1040 // the property on the PolymerElement instance to always get its value from the
871 // model@path. We can't replicate this in Dart so we do the next best thing: 1041 // model@path. We can't replicate this in Dart so we do the next best thing:
872 // listen to changes on both sides and update the values. 1042 // listen to changes on both sides and update the values.
873 // TODO(jmesserly): our approach leads to race conditions in the bindings. 1043 // TODO(jmesserly): our approach leads to race conditions in the bindings.
874 // See http://code.google.com/p/dart/issues/detail?id=13567 1044 // See http://code.google.com/p/dart/issues/detail?id=13567
875 class _PolymerBinding extends NodeBinding { 1045 class _PolymerBinding extends NodeBinding {
876 final InstanceMirror _target; 1046 final InstanceMirror _target;
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
954 /** 1124 /**
955 * Base class for PolymerElements deriving from HtmlElement. 1125 * Base class for PolymerElements deriving from HtmlElement.
956 * 1126 *
957 * See [Polymer]. 1127 * See [Polymer].
958 */ 1128 */
959 class PolymerElement extends HtmlElement with Polymer, Observable { 1129 class PolymerElement extends HtmlElement with Polymer, Observable {
960 PolymerElement.created() : super.created() { 1130 PolymerElement.created() : super.created() {
961 polymerCreated(); 1131 polymerCreated();
962 } 1132 }
963 } 1133 }
1134
1135 class _PropertyValue {
1136 Object oldValue, newValue;
1137 _PropertyValue(this.oldValue);
1138 }
OLDNEW
« no previous file with comments | « pkg/polymer/lib/src/declaration.dart ('k') | pkg/polymer/lib/src/loader.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698