| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of polymer; | 5 part of polymer; |
| 6 | 6 |
| 7 /** | 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 } |
| OLD | NEW |