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

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

Issue 2312183003: Removed Polymer from Observatory deps (Closed)
Patch Set: Created 4 years, 3 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
« no previous file with comments | « packages/polymer/lib/src/initializers.dart ('k') | packages/polymer/lib/src/job.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of polymer;
6
7 /// Use this annotation to publish a property as an attribute.
8 ///
9 /// You can also use [PublishedProperty] to provide additional information,
10 /// such as automatically syncing the property back to the attribute.
11 ///
12 /// For example:
13 ///
14 /// class MyPlaybackElement extends PolymerElement {
15 /// // This will be available as an HTML attribute, for example:
16 /// //
17 /// // <my-playback volume="11">
18 /// //
19 /// // It will support initialization and data-binding via <template>:
20 /// //
21 /// // <template>
22 /// // <my-playback volume="{{x}}">
23 /// // </template>
24 /// //
25 /// // If the template is instantiated or given a model, `x` will be
26 /// // used for this field and updated whenever `volume` changes.
27 /// @published
28 /// double get volume => readValue(#volume);
29 /// set volume(double newValue) => writeValue(#volume, newValue);
30 ///
31 /// // This will be available as an HTML attribute, like above, but it
32 /// // will also serialize values set to the property to the attribute.
33 /// // In other words, attributes['volume2'] will contain a serialized
34 /// // version of this field.
35 /// @PublishedProperty(reflect: true)
36 /// double get volume2 => readValue(#volume2);
37 /// set volume2(double newValue) => writeValue(#volume2, newValue);
38 /// }
39 ///
40 /// **Important note**: the pattern using `readValue` and `writeValue`
41 /// guarantees that reading the property will give you the latest value at any
42 /// given time, even if change notifications have not been propagated.
43 ///
44 /// We still support using @published on a field, but this will not
45 /// provide the same guarantees, so this is discouraged. For example:
46 ///
47 /// // Avoid this if possible. This will be available as an HTML
48 /// // attribute too, but you might need to delay reading volume3
49 /// // asynchronously to guarantee that you read the latest value
50 /// // set through bindings.
51 /// @published double volume3;
52 const published = const PublishedProperty();
53
54 /// An annotation used to publish a field as an attribute. See [published].
55 class PublishedProperty extends ObservableProperty {
56 /// Whether the property value should be reflected back to the HTML attribute.
57 final bool reflect;
58
59 const PublishedProperty({this.reflect: false});
60 }
61
62 /// Use this type to observe a property and have the method be called when it
63 /// changes. For example:
64 ///
65 /// @ObserveProperty('foo.bar baz qux')
66 /// validate() {
67 /// // use this.foo.bar, this.baz, and this.qux in validation
68 /// ...
69 /// }
70 ///
71 /// Note that you can observe a property path, and more than a single property
72 /// can be specified in a space-delimited list or as a constant List.
73 class ObserveProperty {
74 final _names;
75
76 List<String> get names {
77 var n = _names;
78 // TODO(jmesserly): the bogus '$n' is to workaround a dart2js bug, otherwise
79 // it generates an incorrect call site.
80 if (n is String) return '$n'.split(' ');
81 if (n is! Iterable) {
82 throw new UnsupportedError('ObserveProperty takes either an Iterable of '
83 'names, or a space separated String, instead of `$n`.');
84 }
85 return n;
86 }
87
88 const ObserveProperty(this._names);
89 }
90
91 /// Use this to create computed properties that are updated automatically. The
92 /// annotation includes a polymer expression that describes how this property
93 /// value can be expressed in terms of the values of other properties. For
94 /// example:
95 ///
96 /// class MyPlaybackElement extends PolymerElement {
97 /// @observable int x;
98 ///
99 /// // Reading xTimes2 will return x * 2.
100 /// @ComputedProperty('x * 2')
101 /// int get xTimes2 => readValue(#xTimes2);
102 ///
103 /// If the polymer expression is assignable, you can also define a setter for
104 /// it. For example:
105 ///
106 /// // Reading c will return a.b, writing c will update a.b.
107 /// @ComputedProperty('a.b')
108 /// get c => readValue(#c);
109 /// set c(newValue) => writeValue(#c, newValue);
110 ///
111 /// The expression can do anything that is allowed in a polymer expresssion,
112 /// even making calls to methods in your element. However, dependencies that are
113 /// only used within those methods and that are not visible in the polymer
114 /// expression, will not be observed. For example:
115 ///
116 /// // Because `x` only appears inside method `m`, we will not notice
117 /// // that `d` has changed if `x` is modified. However, `d` will be
118 /// // updated whenever `c` changes.
119 /// @ComputedProperty('m(c)')
120 /// get d => readValue(#d);
121 ///
122 /// m(c) => c + x;
123 class ComputedProperty {
124 /// A polymer expression, evaluated in the context of the custom element where
125 /// this annotation is used.
126 final String expression;
127
128 const ComputedProperty(this.expression);
129 }
130
131 /// Base class for PolymerElements deriving from HtmlElement.
132 ///
133 /// See [Polymer].
134 class PolymerElement extends HtmlElement with Polymer, Observable {
135 PolymerElement.created() : super.created() {
136 polymerCreated();
137 }
138 }
139
140 /// The mixin class for Polymer elements. It provides convenience features on
141 /// top of the custom elements web standard.
142 ///
143 /// If this class is used as a mixin,
144 /// you must call `polymerCreated()` from the body of your constructor.
145 abstract class Polymer implements Element, Observable, NodeBindExtension {
146
147 // TODO(jmesserly): should this really be public?
148 /// Regular expression that matches data-bindings.
149 static final bindPattern = new RegExp(r'\{\{([^{}]*)}}');
150
151 /// Like [document.register] but for Polymer elements.
152 ///
153 /// Use the [name] to specify custom elment's tag name, for example:
154 /// "fancy-button" if the tag is used as `<fancy-button>`.
155 ///
156 /// The [type] is the type to construct. If not supplied, it defaults to
157 /// [PolymerElement].
158 // NOTE: this is called "element" in src/declaration/polymer-element.js, and
159 // exported as "Polymer".
160 static void register(String name, [Type type]) {
161 //console.log('registering [' + name + ']');
162 if (type == null) type = PolymerElement;
163
164 _typesByName[name] = type;
165
166 // Dart note: here we notify JS of the element registration. We don't pass
167 // the Dart type because we will handle that in PolymerDeclaration.
168 // See _hookJsPolymerDeclaration for how this is done.
169 PolymerJs.constructor.apply([name]);
170 (js.context['HTMLElement']['register'] as JsFunction)
171 .apply([name, js.context['HTMLElement']['prototype']]);
172 }
173
174 /// Register a custom element that has no associated `<polymer-element>`.
175 /// Unlike [register] this will always perform synchronous registration and
176 /// by the time this method returns the element will be available using
177 /// [document.createElement] or by modifying the HTML to include the element.
178 static void registerSync(String name, Type type,
179 {String extendsTag, Document doc, Node template}) {
180
181 // Our normal registration, this will queue up the name->type association.
182 register(name, type);
183
184 // Build a polymer-element and initialize it to register
185 if (doc == null) doc = document;
186 var poly = doc.createElement('polymer-element');
187 poly.attributes['name'] = name;
188 if (extendsTag != null) poly.attributes['extends'] = extendsTag;
189 if (template != null) poly.append(template);
190
191 // TODO(jmesserly): conceptually this is just:
192 // new JsObject.fromBrowserObject(poly).callMethod('init')
193 //
194 // However doing it that way hits an issue with JS-interop in IE10: we get a
195 // JsObject that wraps something other than `poly`, due to improper caching.
196 // By reusing _polymerElementProto that we used for 'register', we can
197 // then call apply on it to invoke init() with the correct `this` pointer.
198 JsFunction init = _polymerElementProto['init'];
199 init.apply([], thisArg: poly);
200 }
201
202 // Warning for when people try to use `importElements` or `import`.
203 static const String _DYNAMIC_IMPORT_WARNING = 'Dynamically loading html '
204 'imports has very limited support right now in dart, see '
205 'http://dartbug.com/17873.';
206
207 /// Loads the set of HTMLImports contained in `node`. Returns a future that
208 /// resolves when all the imports have been loaded. This method can be used to
209 /// lazily load imports. For example, given a template:
210 ///
211 /// <template>
212 /// <link rel="import" href="my-import1.html">
213 /// <link rel="import" href="my-import2.html">
214 /// </template>
215 ///
216 /// Polymer.importElements(template.content)
217 /// .then((_) => print('imports lazily loaded'));
218 ///
219 /// Dart Note: This has very limited support in dart, http://dartbug.com/17873
220 // Dart Note: From src/lib/import.js For now proxy to the JS methods,
221 // because we want to share the loader with polymer.js for interop purposes.
222 static Future importElements(Node elementOrFragment) {
223 print(_DYNAMIC_IMPORT_WARNING);
224 return PolymerJs.importElements(elementOrFragment);
225 }
226
227 /// Loads an HTMLImport for each url specified in the `urls` array. Notifies
228 /// when all the imports have loaded by calling the `callback` function
229 /// argument. This method can be used to lazily load imports. For example,
230 /// For example,
231 ///
232 /// Polymer.import(['my-import1.html', 'my-import2.html'])
233 /// .then((_) => print('imports lazily loaded'));
234 ///
235 /// Dart Note: This has very limited support in dart, http://dartbug.com/17873
236 // Dart Note: From src/lib/import.js. For now proxy to the JS methods,
237 // because we want to share the loader with polymer.js for interop purposes.
238 static Future import(List urls) {
239 print(_DYNAMIC_IMPORT_WARNING);
240 return PolymerJs.import(urls);
241 }
242
243 /// Deprecated: Use `import` instead.
244 @deprecated
245 static Future importUrls(List urls) {
246 return import(urls);
247 }
248
249 /// Completes when polymer js is ready.
250 static final Completer _onReady = new Completer();
251
252 /// Completes when all initialization is done.
253 static final Completer _onInitDone = new Completer();
254
255 /// Future indicating that the Polymer library has been loaded and is ready
256 /// for use.
257 static Future get onReady =>
258 Future.wait([_onReady.future, _onInitDone.future]);
259
260 /// Returns a list of elements that have had polymer-elements created but
261 /// are not yet ready to register. The list is an array of element
262 /// definitions.
263 static List<Element> get waitingFor => PolymerJs.waitingFor;
264
265 /// Forces polymer to register any pending elements. Can be used to abort
266 /// waiting for elements that are partially defined.
267 static forceReady([int timeout]) => PolymerJs.forceReady(timeout);
268
269 /// The most derived `<polymer-element>` declaration for this element.
270 PolymerDeclaration get element => _element;
271 PolymerDeclaration _element;
272
273 /// Deprecated: use [element] instead.
274 @deprecated PolymerDeclaration get declaration => _element;
275
276 Map<String, StreamSubscription> _namedObservers;
277 List<Bindable> _observers = [];
278
279 bool _unbound; // lazy-initialized
280 PolymerJob _unbindAllJob;
281
282 CompoundObserver _propertyObserver;
283 bool _readied = false;
284
285 JsObject _jsElem;
286
287 /// Returns the object that should be used as the event controller for
288 /// event bindings in this element's template. If set, this will override the
289 /// normal controller lookup.
290 // TODO(jmesserly): we need to use a JS-writable property as our backing
291 // store, because of elements such as:
292 // https://github.com/Polymer/core-overlay/blob/eeb14853/core-overlay-layer.ht ml#L78
293 get eventController => _jsElem['eventController'];
294 set eventController(value) {
295 _jsElem['eventController'] = value;
296 }
297
298 bool get hasBeenAttached => _hasBeenAttached;
299 bool _hasBeenAttached = false;
300
301 /// Gets the shadow root associated with the corresponding custom element.
302 ///
303 /// This is identical to [shadowRoot], unless there are multiple levels of
304 /// inheritance and they each have their own shadow root. For example,
305 /// this can happen if the base class and subclass both have `<template>` tags
306 /// in their `<polymer-element>` tags.
307 // TODO(jmesserly): should expose this as an immutable map.
308 // Similar issue as $.
309 final Map<String, ShadowRoot> shadowRoots =
310 new LinkedHashMap<String, ShadowRoot>();
311
312 /// Map of items in the shadow root(s) by their [Element.id].
313 // TODO(jmesserly): various issues:
314 // * wrap in UnmodifiableMapView?
315 // * should we have an object that implements noSuchMethod?
316 // * should the map have a key order (e.g. LinkedHash or SplayTree)?
317 // * should this be a live list? Polymer doesn't, maybe due to JS limitations?
318 // Note: this is observable to support $['someId'] being used in templates.
319 // The template is stamped before $ is populated, so we need observation if
320 // we want it to be usable in bindings.
321 final Map<String, dynamic> $ = new ObservableMap<String, dynamic>();
322
323 /// Use to override the default syntax for polymer-elements.
324 /// By default this will be null, which causes [instanceTemplate] to use
325 /// the template's bindingDelegate or the [element.syntax], in that order.
326 PolymerExpressions get syntax => null;
327
328 bool get _elementPrepared => _element != null;
329
330 /// Retrieves the custom element name. It should be used instead
331 /// of localName, see: https://github.com/Polymer/polymer-dev/issues/26
332 String get _name {
333 if (_element != null) return _element.name;
334 var isAttr = attributes['is'];
335 return (isAttr == null || isAttr == '') ? localName : isAttr;
336 }
337
338 /// By default the data bindings will be cleaned up when this custom element
339 /// is detached from the document. Overriding this to return `true` will
340 /// prevent that from happening.
341 bool get preventDispose => false;
342
343 /// Properties exposed by this element.
344 // Dart note: unlike Javascript we can't override the original property on
345 // the object, so we use this mechanism instead to define properties. See more
346 // details in [_PropertyAccessor].
347 Map<Symbol, _PropertyAccessor> _properties = {};
348
349 /// Helper to implement a property with the given [name]. This is used for
350 /// normal and computed properties. Normal properties can provide the initial
351 /// value using the [initialValue] function. Computed properties ignore
352 /// [initialValue], their value is derived from the expression in the
353 /// [ComputedProperty] annotation that appears above the getter that uses this
354 /// helper.
355 readValue(Symbol name, [initialValue()]) {
356 var property = _properties[name];
357 if (property == null) {
358 var value;
359 // Dart note: most computed properties are created in advance in
360 // createComputedProperties, but if one computed property depends on
361 // another, the declaration order might matter. Rather than trying to
362 // register them in order, we include here also the option of lazily
363 // creating the property accessor on the first read.
364 var binding = _getBindingForComputedProperty(name);
365 if (binding == null) {
366 // normal property
367 value = initialValue != null ? initialValue() : null;
368 } else {
369 value = binding.value;
370 }
371 property = _properties[name] = new _PropertyAccessor(name, this, value);
372 }
373 return property.value;
374 }
375
376 /// Helper to implement a setter of a property with the given [name] on a
377 /// polymer element. This can be used on normal properties and also on
378 /// computed properties, as long as the expression used for the computed
379 /// property is assignable (see [ComputedProperty]).
380 writeValue(Symbol name, newValue) {
381 var property = _properties[name];
382 if (property == null) {
383 // Note: computed properties are created in advance in
384 // createComputedProperties, so we should only need to create here
385 // non-computed properties.
386 property = _properties[name] = new _PropertyAccessor(name, this, null);
387 }
388 property.value = newValue;
389 }
390
391 /// If this class is used as a mixin, this method must be called from inside
392 /// of the `created()` constructor.
393 ///
394 /// If this class is a superclass, calling `super.created()` is sufficient.
395 void polymerCreated() {
396 var t = nodeBind(this).templateInstance;
397 if (t != null && t.model != null) {
398 window.console.warn('Attributes on $_name were data bound '
399 'prior to Polymer upgrading the element. This may result in '
400 'incorrect binding types.');
401 }
402 prepareElement();
403 if (!isTemplateStagingDocument(ownerDocument)) {
404 _makeElementReady();
405 }
406 }
407
408 /// *Deprecated* use [shadowRoots] instead.
409 @deprecated
410 ShadowRoot getShadowRoot(String customTagName) => shadowRoots[customTagName];
411
412 void prepareElement() {
413 if (_elementPrepared) {
414 window.console.warn('Element already prepared: $_name');
415 return;
416 }
417 _initJsObject();
418 // Dart note: get the corresponding <polymer-element> declaration.
419 _element = _getDeclaration(_name);
420 // install property storage
421 createPropertyObserver();
422 openPropertyObserver();
423 // install boilerplate attributes
424 copyInstanceAttributes();
425 // process input attributes
426 takeAttributes();
427 // add event listeners
428 addHostListeners();
429 }
430
431 /// Initialize JS interop for this element. For now we just initialize the
432 /// JsObject, but in the future we could also initialize JS APIs here.
433 _initJsObject() {
434 _jsElem = new JsObject.fromBrowserObject(this);
435 }
436
437 /// Deprecated: This is no longer a public method.
438 @deprecated
439 makeElementReady() => _makeElementReady();
440
441 _makeElementReady() {
442 if (_readied) return;
443 _readied = true;
444 createComputedProperties();
445
446 parseDeclarations(_element);
447 // NOTE: Support use of the `unresolved` attribute to help polyfill
448 // custom elements' `:unresolved` feature.
449 attributes.remove('unresolved');
450 // user entry point
451 _readyLog.info(() => '[$this]: ready');
452 ready();
453 }
454
455 /// Lifecycle method called when the element has populated it's `shadowRoot`,
456 /// prepared data-observation, and made itself ready for API interaction.
457 /// To wait until the element has been attached to the default view, use
458 /// [attached] or [domReady].
459 void ready() {}
460
461 /// Implement to access custom elements in dom descendants, ancestors,
462 /// or siblings. Because custom elements upgrade in document order,
463 /// elements accessed in `ready` or `attached` may not be upgraded. When
464 /// `domReady` is called, all registered custom elements are guaranteed
465 /// to have been upgraded.
466 void domReady() {}
467
468 void attached() {
469 if (!_elementPrepared) {
470 // Dart specific message for a common issue.
471 throw new StateError('polymerCreated was not called for custom element '
472 '$_name, this should normally be done in the .created() if Polymer '
473 'is used as a mixin.');
474 }
475
476 cancelUnbindAll();
477 if (!hasBeenAttached) {
478 _hasBeenAttached = true;
479 async((_) => domReady());
480 }
481 }
482
483 void detached() {
484 if (!preventDispose) asyncUnbindAll();
485 }
486
487 /// Walks the prototype-chain of this element and allows specific
488 /// classes a chance to process static declarations.
489 ///
490 /// In particular, each polymer-element has it's own `template`.
491 /// `parseDeclarations` is used to accumulate all element `template`s
492 /// from an inheritance chain.
493 ///
494 /// `parseDeclaration` static methods implemented in the chain are called
495 /// recursively, oldest first, with the `<polymer-element>` associated
496 /// with the current prototype passed as an argument.
497 ///
498 /// An element may override this method to customize shadow-root generation.
499 void parseDeclarations(PolymerDeclaration declaration) {
500 if (declaration != null) {
501 parseDeclarations(declaration.superDeclaration);
502 parseDeclaration(declaration.element);
503 }
504 }
505
506 /// Perform init-time actions based on static information in the
507 /// `<polymer-element>` instance argument.
508 ///
509 /// For example, the standard implementation locates the template associated
510 /// with the given `<polymer-element>` and stamps it into a shadow-root to
511 /// implement shadow inheritance.
512 ///
513 /// An element may override this method for custom behavior.
514 void parseDeclaration(Element elementElement) {
515 var template = fetchTemplate(elementElement);
516
517 if (template != null) {
518 var root = shadowFromTemplate(template);
519
520 var name = elementElement.attributes['name'];
521 if (name == null) return;
522 shadowRoots[name] = root;
523 }
524 }
525
526 /// Given a `<polymer-element>`, find an associated template (if any) to be
527 /// used for shadow-root generation.
528 ///
529 /// An element may override this method for custom behavior.
530 Element fetchTemplate(Element elementElement) =>
531 elementElement.querySelector('template');
532
533 /// Utility function that stamps a `<template>` into light-dom.
534 Node lightFromTemplate(Element template, [Node refNode]) {
535 if (template == null) return null;
536
537 // TODO(sorvell): mark this element as an event controller so that
538 // event listeners on bound nodes inside it will be called on it.
539 // Note, the expectation here is that events on all descendants
540 // should be handled by this element.
541 eventController = this;
542
543 // stamp template
544 // which includes parsing and applying MDV bindings before being
545 // inserted (to avoid {{}} in attribute values)
546 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
547 var dom = instanceTemplate(template);
548 // append to shadow dom
549 if (refNode != null) {
550 append(dom);
551 } else {
552 insertBefore(dom, refNode);
553 }
554 // perform post-construction initialization tasks on ahem, light root
555 shadowRootReady(this);
556 // return the created shadow root
557 return dom;
558 }
559
560 /// Utility function that creates a shadow root from a `<template>`.
561 ///
562 /// The base implementation will return a [ShadowRoot], but you can replace it
563 /// with your own code and skip ShadowRoot creation. In that case, you should
564 /// return `null`.
565 ///
566 /// In your overridden method, you can use [instanceTemplate] to stamp the
567 /// template and initialize data binding, and [shadowRootReady] to intialize
568 /// other Polymer features like event handlers. It is fine to call
569 /// shadowRootReady with a node other than a ShadowRoot such as with `this`.
570 ShadowRoot shadowFromTemplate(Element template) {
571 if (template == null) return null;
572 // make a shadow root
573 var root = createShadowRoot();
574 // stamp template
575 // which includes parsing and applying MDV bindings before being
576 // inserted (to avoid {{}} in attribute values)
577 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
578 var dom = instanceTemplate(template);
579 // append to shadow dom
580 root.append(dom);
581 // perform post-construction initialization tasks on shadow root
582 shadowRootReady(root);
583 // return the created shadow root
584 return root;
585 }
586
587 void shadowRootReady(Node root) {
588 // locate nodes with id and store references to them in this.$ hash
589 marshalNodeReferences(root);
590 }
591
592 /// Locate nodes with id and store references to them in [$] hash.
593 void marshalNodeReferences(Node root) {
594 if (root == null) return;
595 for (var n in (root as dynamic).querySelectorAll('[id]')) {
596 $[n.id] = n;
597 }
598 }
599
600 void attributeChanged(String name, String oldValue, String newValue) {
601 if (name != 'class' && name != 'style') {
602 attributeToProperty(name, newValue);
603 }
604 }
605
606 // TODO(jmesserly): this could be a top level method.
607 /// Returns a future when `node` changes, or when its children or subtree
608 /// changes.
609 ///
610 /// Use [MutationObserver] if you want to listen to a stream of changes.
611 Future<List<MutationRecord>> onMutation(Node node) {
612 var completer = new Completer();
613 new MutationObserver((mutations, observer) {
614 observer.disconnect();
615 completer.complete(mutations);
616 })..observe(node, childList: true, subtree: true);
617 return completer.future;
618 }
619
620 // copy attributes defined in the element declaration to the instance
621 // e.g. <polymer-element name="x-foo" tabIndex="0"> tabIndex is copied
622 // to the element instance here.
623 void copyInstanceAttributes() {
624 _element._instanceAttributes.forEach((name, value) {
625 attributes.putIfAbsent(name, () => value);
626 });
627 }
628
629 void takeAttributes() {
630 if (_element._publishLC == null) return;
631 attributes.forEach(attributeToProperty);
632 }
633
634 /// If attribute [name] is mapped to a property, deserialize
635 /// [value] into that property.
636 void attributeToProperty(String name, String value) {
637 // try to match this attribute to a property (attributes are
638 // all lower-case, so this is case-insensitive search)
639 var decl = propertyForAttribute(name);
640 if (decl == null) return;
641
642 // filter out 'mustached' values, these are to be
643 // replaced with bound-data and are not yet values
644 // themselves.
645 if (value == null || value.contains(Polymer.bindPattern)) return;
646
647 final currentValue = smoke.read(this, decl.name);
648
649 // deserialize Boolean or Number values from attribute
650 var type = decl.type;
651 if ((type == Object || type == dynamic) && currentValue != null) {
652 // Attempt to infer field type from the current value.
653 type = currentValue.runtimeType;
654 }
655 final newValue = deserializeValue(value, currentValue, type);
656
657 // only act if the value has changed
658 if (!identical(newValue, currentValue)) {
659 // install new value (has side-effects)
660 smoke.write(this, decl.name, newValue);
661 }
662 }
663
664 /// Return the published property matching name, or null.
665 // TODO(jmesserly): should we just return Symbol here?
666 smoke.Declaration propertyForAttribute(String name) {
667 final publishLC = _element._publishLC;
668 if (publishLC == null) return null;
669 return publishLC[name];
670 }
671
672 /// Convert representation of [value] based on [type] and [currentValue].
673 Object deserializeValue(String value, Object currentValue, Type type) =>
674 deserialize.deserializeValue(value, currentValue, type);
675
676 String serializeValue(Object value) {
677 if (value == null) return null;
678
679 if (value is bool) {
680 return _toBoolean(value) ? '' : null;
681 } else if (value is String || value is num) {
682 return '$value';
683 }
684 return null;
685 }
686
687 void reflectPropertyToAttribute(String path) {
688 // TODO(sjmiles): consider memoizing this
689 // try to intelligently serialize property value
690 final propValue = new PropertyPath(path).getValueFrom(this);
691 final serializedValue = serializeValue(propValue);
692 // boolean properties must reflect as boolean attributes
693 if (serializedValue != null) {
694 attributes[path] = serializedValue;
695 // TODO(sorvell): we should remove attr for all properties
696 // that have undefined serialization; however, we will need to
697 // refine the attr reflection system to achieve this; pica, for example,
698 // relies on having inferredType object properties not removed as
699 // attrs.
700 } else if (propValue is bool) {
701 attributes.remove(path);
702 }
703 }
704
705 /// Creates the document fragment to use for each instance of the custom
706 /// element, given the `<template>` node. By default this is equivalent to:
707 ///
708 /// templateBind(template).createInstance(this, polymerSyntax);
709 ///
710 /// Where polymerSyntax is a singleton [PolymerExpressions] instance.
711 ///
712 /// You can override this method to change the instantiation behavior of the
713 /// template, for example to use a different data-binding syntax.
714 DocumentFragment instanceTemplate(Element template) {
715 // ensure template is decorated (lets things like <tr template ...> work)
716 TemplateBindExtension.decorate(template);
717 var syntax = this.syntax;
718 var t = templateBind(template);
719 if (syntax == null && t.bindingDelegate == null) {
720 syntax = element.syntax;
721 }
722 var dom = t.createInstance(this, syntax);
723 _observers.addAll(getTemplateInstanceBindings(dom));
724 return dom;
725 }
726
727 /// Called by TemplateBinding/NodeBind to setup a binding to the given
728 /// property. It's overridden here to support property bindings in addition to
729 /// attribute bindings that are supported by default.
730 Bindable bind(String name, bindable, {bool oneTime: false}) {
731 var decl = propertyForAttribute(name);
732 if (decl == null) {
733 // Cannot call super.bind because template_binding is its own package
734 return nodeBindFallback(this).bind(name, bindable, oneTime: oneTime);
735 } else {
736 // use n-way Polymer binding
737 var observer = bindProperty(decl.name, bindable, oneTime: oneTime);
738 // NOTE: reflecting binding information is typically required only for
739 // tooling. It has a performance cost so it's opt-in in Node.bind.
740 if (enableBindingsReflection && observer != null) {
741 // Dart note: this is not needed because of how _PolymerBinding works.
742 //observer.path = bindable.path_;
743 _recordBinding(name, observer);
744 }
745 var reflect = _element._reflect;
746
747 // Get back to the (possibly camel-case) name for the property.
748 var propName = smoke.symbolToName(decl.name);
749 if (reflect != null && reflect.contains(propName)) {
750 reflectPropertyToAttribute(propName);
751 }
752 return observer;
753 }
754 }
755
756 _recordBinding(String name, observer) {
757 if (bindings == null) bindings = {};
758 this.bindings[name] = observer;
759 }
760
761 /// Called by TemplateBinding when all bindings on an element have been
762 /// executed. This signals that all element inputs have been gathered and it's
763 /// safe to ready the element, create shadow-root and start data-observation.
764 bindFinished() => _makeElementReady();
765
766 Map<String, Bindable> get bindings => nodeBindFallback(this).bindings;
767 set bindings(Map value) {
768 nodeBindFallback(this).bindings = value;
769 }
770
771 TemplateInstance get templateInstance =>
772 nodeBindFallback(this).templateInstance;
773
774 /// Called at detached time to signal that an element's bindings should be
775 /// cleaned up. This is done asynchronously so that users have the chance to
776 /// call `cancelUnbindAll` to prevent unbinding.
777 void asyncUnbindAll() {
778 if (_unbound == true) return;
779 _unbindLog.fine(() => '[$_name] asyncUnbindAll');
780 _unbindAllJob = scheduleJob(_unbindAllJob, unbindAll);
781 }
782
783 /// This method should rarely be used and only if `cancelUnbindAll` has been
784 /// called to prevent element unbinding. In this case, the element's bindings
785 /// will not be automatically cleaned up and it cannot be garbage collected by
786 /// by the system. If memory pressure is a concern or a large amount of
787 /// elements need to be managed in this way, `unbindAll` can be called to
788 /// deactivate the element's bindings and allow its memory to be reclaimed.
789 void unbindAll() {
790 if (_unbound == true) return;
791 closeObservers();
792 closeNamedObservers();
793 _unbound = true;
794 }
795
796 //// Call in `detached` to prevent the element from unbinding when it is
797 //// detached from the dom. The element is unbound as a cleanup step that
798 //// allows its memory to be reclaimed. If `cancelUnbindAll` is used, consider
799 /// calling `unbindAll` when the element is no longer needed. This will allow
800 /// its memory to be reclaimed.
801 void cancelUnbindAll() {
802 if (_unbound == true) {
803 _unbindLog
804 .warning(() => '[$_name] already unbound, cannot cancel unbindAll');
805 return;
806 }
807 _unbindLog.fine(() => '[$_name] cancelUnbindAll');
808 if (_unbindAllJob != null) {
809 _unbindAllJob.stop();
810 _unbindAllJob = null;
811 }
812 }
813
814 static void _forNodeTree(Node node, void callback(Node node)) {
815 if (node == null) return;
816
817 callback(node);
818 for (var child = node.firstChild; child != null; child = child.nextNode) {
819 _forNodeTree(child, callback);
820 }
821 }
822
823 /// Creates a CompoundObserver to observe property changes.
824 /// NOTE, this is only done if there are any properties in the `_observe`
825 /// object.
826 void createPropertyObserver() {
827 final observe = _element._observe;
828 if (observe != null) {
829 var o = _propertyObserver = new CompoundObserver();
830 // keep track of property observer so we can shut it down
831 _observers.add(o);
832
833 for (var path in observe.keys) {
834 o.addPath(this, path);
835
836 // TODO(jmesserly): on the Polymer side it doesn't look like they
837 // will observe arrays unless it is a length == 1 path.
838 observeArrayValue(path, path.getValueFrom(this), null);
839 }
840 }
841 }
842
843 /// Start observing property changes.
844 void openPropertyObserver() {
845 if (_propertyObserver != null) {
846 _propertyObserver.open(notifyPropertyChanges);
847 }
848
849 // Dart note: we need an extra listener only to continue supporting
850 // @published properties that follow the old syntax until we get rid of it.
851 // This workaround has timing issues so we prefer the new, not so nice,
852 // syntax.
853 if (_element._publish != null) {
854 changes.listen(_propertyChangeWorkaround);
855 }
856 }
857
858 /// Handler for property changes; routes changes to observing methods.
859 /// Note: array valued properties are observed for array splices.
860 void notifyPropertyChanges(List newValues, Map oldValues, List paths) {
861 final observe = _element._observe;
862 final called = new HashSet();
863
864 oldValues.forEach((i, oldValue) {
865 final newValue = newValues[i];
866
867 // Date note: we don't need any special checking for null and undefined.
868
869 // note: paths is of form [object, path, object, path]
870 final path = paths[2 * i + 1];
871 if (observe == null) return;
872
873 var methods = observe[path];
874 if (methods == null) return;
875
876 for (var method in methods) {
877 if (!called.add(method)) continue; // don't invoke more than once.
878
879 observeArrayValue(path, newValue, oldValue);
880 // Dart note: JS passes "arguments", so we pass along our args.
881 // TODO(sorvell): call method with the set of values it's expecting;
882 // e.g. 'foo bar': 'invalidate' expects the new and old values for
883 // foo and bar. Currently we give only one of these and then
884 // deliver all the arguments.
885 smoke.invoke(
886 this, method, [oldValue, newValue, newValues, oldValues, paths],
887 adjust: true);
888 }
889 });
890 }
891
892 /// Force any pending property changes to synchronously deliver to handlers
893 /// specified in the `observe` object.
894 /// Note: normally changes are processed at microtask time.
895 ///
896 // Dart note: had to rename this to avoid colliding with
897 // Observable.deliverChanges. Even worse, super calls aren't possible or
898 // it prevents Polymer from being a mixin, so we can't override it even if
899 // we wanted to.
900 void deliverPropertyChanges() {
901 if (_propertyObserver != null) {
902 _propertyObserver.deliver();
903 }
904 }
905
906 // Dart note: this workaround is only for old-style @published properties,
907 // which have timing issues. See _bindOldStylePublishedProperty below.
908 // TODO(sigmund): deprecate this.
909 void _propertyChangeWorkaround(List<ChangeRecord> records) {
910 for (var record in records) {
911 if (record is! PropertyChangeRecord) continue;
912
913 var name = record.name;
914 // The setter of a new-style property will create an accessor in
915 // _properties[name]. We can skip the workaround for those properties.
916 if (_properties[name] != null) continue;
917 _propertyChange(name, record.newValue, record.oldValue);
918 }
919 }
920
921 void _propertyChange(Symbol nameSymbol, newValue, oldValue) {
922 _watchLog.info(
923 () => '[$this]: $nameSymbol changed from: $oldValue to: $newValue');
924 var name = smoke.symbolToName(nameSymbol);
925 var reflect = _element._reflect;
926 if (reflect != null && reflect.contains(name)) {
927 reflectPropertyToAttribute(name);
928 }
929 }
930
931 void observeArrayValue(PropertyPath name, Object value, Object old) {
932 final observe = _element._observe;
933 if (observe == null) return;
934
935 // we only care if there are registered side-effects
936 var callbacks = observe[name];
937 if (callbacks == null) return;
938
939 // if we are observing the previous value, stop
940 if (old is ObservableList) {
941 _observeLog.fine(() => '[$_name] observeArrayValue: unregister $name');
942
943 closeNamedObserver('${name}__array');
944 }
945 // if the new value is an array, begin observing it
946 if (value is ObservableList) {
947 _observeLog.fine(() => '[$_name] observeArrayValue: register $name');
948 var sub = value.listChanges.listen((changes) {
949 for (var callback in callbacks) {
950 smoke.invoke(this, callback, [changes], adjust: true);
951 }
952 });
953 registerNamedObserver('${name}__array', sub);
954 }
955 }
956
957 emitPropertyChangeRecord(Symbol name, newValue, oldValue) {
958 if (identical(oldValue, newValue)) return;
959 _propertyChange(name, newValue, oldValue);
960 }
961
962 bindToAccessor(Symbol name, Bindable bindable, {resolveBindingValue: false}) {
963 // Dart note: our pattern is to declare the initial value in the getter. We
964 // read it via smoke to ensure that the value is initialized correctly.
965 var oldValue = smoke.read(this, name);
966 var property = _properties[name];
967 if (property == null) {
968 // We know that _properties[name] is null only for old-style @published
969 // properties. This fallback is here to make it easier to deprecate the
970 // old-style of published properties, which have bad timing guarantees
971 // (see comment in _PolymerBinding).
972 return _bindOldStylePublishedProperty(name, bindable, oldValue);
973 }
974
975 property.bindable = bindable;
976 var value = bindable.open(property.updateValue);
977
978 if (resolveBindingValue) {
979 // capture A's value if B's value is null or undefined,
980 // otherwise use B's value
981 var v = (value == null ? oldValue : value);
982 if (!identical(value, oldValue)) {
983 bindable.value = value = v;
984 }
985 }
986
987 property.updateValue(value);
988 var o = new _CloseOnlyBinding(property);
989 _observers.add(o);
990 return o;
991 }
992
993 // Dart note: this fallback uses our old-style binding mechanism to be able to
994 // link @published properties with bindings. This mechanism is backwards from
995 // what Javascript does because we can't override the original property. This
996 // workaround also brings some timing issues which are described in detail in
997 // dartbug.com/18343.
998 // TODO(sigmund): deprecate old-style @published properties.
999 _bindOldStylePublishedProperty(Symbol name, Bindable bindable, oldValue) {
1000 // capture A's value if B's value is null or undefined,
1001 // otherwise use B's value
1002 if (bindable.value == null) bindable.value = oldValue;
1003
1004 var o = new _PolymerBinding(this, name, bindable);
1005 _observers.add(o);
1006 return o;
1007 }
1008
1009 _getBindingForComputedProperty(Symbol name) {
1010 var exprString = element._computed[name];
1011 if (exprString == null) return null;
1012 var expr = PolymerExpressions.getExpression(exprString);
1013 return PolymerExpressions.getBinding(expr, this,
1014 globals: element.syntax.globals);
1015 }
1016
1017 createComputedProperties() {
1018 var computed = this.element._computed;
1019 for (var name in computed.keys) {
1020 try {
1021 // Dart note: this is done in Javascript by modifying the prototype in
1022 // declaration/properties.js, we can't do that, so we do it here.
1023 var binding = _getBindingForComputedProperty(name);
1024
1025 // Follow up note: ideally we would only create the accessor object
1026 // here, but some computed properties might depend on others and
1027 // evaluating `binding.value` could try to read the value of another
1028 // computed property that we haven't created yet. For this reason we
1029 // also allow to also create the accessor in [readValue].
1030 if (_properties[name] == null) {
1031 _properties[name] = new _PropertyAccessor(name, this, binding.value);
1032 }
1033 bindToAccessor(name, binding);
1034 } catch (e) {
1035 window.console.error('Failed to create computed property $name'
1036 ' (${computed[name]}): $e');
1037 }
1038 }
1039 }
1040
1041 // Dart note: to simplify the code above we made registerObserver calls
1042 // directly invoke _observers.add/addAll.
1043 void closeObservers() {
1044 for (var o in _observers) {
1045 if (o != null) o.close();
1046 }
1047 _observers = [];
1048 }
1049
1050 /// Bookkeeping observers for memory management.
1051 void registerNamedObserver(String name, StreamSubscription sub) {
1052 if (_namedObservers == null) {
1053 _namedObservers = new Map<String, StreamSubscription>();
1054 }
1055 _namedObservers[name] = sub;
1056 }
1057
1058 bool closeNamedObserver(String name) {
1059 var sub = _namedObservers.remove(name);
1060 if (sub == null) return false;
1061 sub.cancel();
1062 return true;
1063 }
1064
1065 void closeNamedObservers() {
1066 if (_namedObservers == null) return;
1067 for (var sub in _namedObservers.values) {
1068 if (sub != null) sub.cancel();
1069 }
1070 _namedObservers.clear();
1071 _namedObservers = null;
1072 }
1073
1074 /// Bind the [name] property in this element to [bindable]. *Note* in Dart it
1075 /// is necessary to also define the field:
1076 ///
1077 /// var myProperty;
1078 ///
1079 /// ready() {
1080 /// super.ready();
1081 /// bindProperty(#myProperty,
1082 /// new PathObserver(this, 'myModel.path.to.otherProp'));
1083 /// }
1084 Bindable bindProperty(Symbol name, bindableOrValue, {oneTime: false}) {
1085 // Dart note: normally we only reach this code when we know it's a
1086 // property, but if someone uses bindProperty directly they might get a
1087 // NoSuchMethodError either from the getField below, or from the setField
1088 // inside PolymerBinding. That doesn't seem unreasonable, but it's a slight
1089 // difference from Polymer.js behavior.
1090
1091 _bindLog.fine(() => 'bindProperty: [$bindableOrValue] to [$_name].[$name]');
1092
1093 if (oneTime) {
1094 if (bindableOrValue is Bindable) {
1095 _bindLog.warning(() =>
1096 'bindProperty: expected non-bindable value n a one-time binding to '
1097 '[$_name].[$name], but found $bindableOrValue.');
1098 }
1099 smoke.write(this, name, bindableOrValue);
1100 return null;
1101 }
1102
1103 return bindToAccessor(name, bindableOrValue, resolveBindingValue: true);
1104 }
1105
1106 /// Attach event listeners on the host (this) element.
1107 void addHostListeners() {
1108 var events = _element._eventDelegates;
1109 if (events.isEmpty) return;
1110
1111 _eventsLog.fine(() => '[$_name] addHostListeners: $events');
1112
1113 // NOTE: host events look like bindings but really are not;
1114 // (1) we don't want the attribute to be set and (2) we want to support
1115 // multiple event listeners ('host' and 'instance') and Node.bind
1116 // by default supports 1 thing being bound.
1117 events.forEach((type, methodName) {
1118 // Dart note: the getEventHandler method is on our PolymerExpressions.
1119 PolymerGesturesJs.addEventListener(
1120 this, type,
1121 Zone.current.bindUnaryCallback(
1122 element.syntax.getEventHandler(this, this, methodName)));
1123 });
1124 }
1125
1126 /// Calls [methodOrCallback] with [args] if it is a closure, otherwise, treat
1127 /// it as a method name in [object], and invoke it.
1128 void dispatchMethod(object, callbackOrMethod, List args) {
1129 _eventsLog.info(() => '>>> [$_name]: dispatch $callbackOrMethod');
1130
1131 if (callbackOrMethod is Function) {
1132 int maxArgs = smoke.maxArgs(callbackOrMethod);
1133 if (maxArgs == -1) {
1134 _eventsLog.warning(
1135 'invalid callback: expected callback of 0, 1, 2, or 3 arguments');
1136 }
1137 args.length = maxArgs;
1138 Function.apply(callbackOrMethod, args);
1139 } else if (callbackOrMethod is String) {
1140 smoke.invoke(object, smoke.nameToSymbol(callbackOrMethod), args,
1141 adjust: true);
1142 } else {
1143 _eventsLog.warning('invalid callback');
1144 }
1145
1146 _eventsLog.fine(() => '<<< [$_name]: dispatch $callbackOrMethod');
1147 }
1148
1149 /// Call [methodName] method on this object with [args].
1150 invokeMethod(Symbol methodName, List args) =>
1151 smoke.invoke(this, methodName, args, adjust: true);
1152
1153 /// Invokes a function asynchronously.
1154 /// This will call `Polymer.flush()` and then return a `new Timer`
1155 /// with the provided [method] and [timeout].
1156 ///
1157 /// If you would prefer to run the callback using
1158 /// [window.requestAnimationFrame], see the [async] method.
1159 ///
1160 /// To cancel, call [Timer.cancel] on the result of this method.
1161 Timer asyncTimer(void method(), Duration timeout) {
1162 // Dart note: "async" is split into 2 methods so it can have a sensible type
1163 // signatures. Also removed the various features that don't make sense in a
1164 // Dart world, like binding to "this" and taking arguments list.
1165
1166 // when polyfilling Object.observe, ensure changes
1167 // propagate before executing the async method
1168 scheduleMicrotask(Observable.dirtyCheck);
1169 PolymerJs.flush(); // for polymer-js interop
1170 return new Timer(timeout, method);
1171 }
1172
1173 /// Invokes a function asynchronously. The context of the callback function is
1174 /// function is bound to 'this' automatically. Returns a handle which may be
1175 /// passed to cancelAsync to cancel the asynchronous call.
1176 ///
1177 /// If you would prefer to run the callback after a given duration, see
1178 /// the [asyncTimer] method.
1179 ///
1180 /// If you would like to cancel this, use [cancelAsync].
1181 int async(RequestAnimationFrameCallback method) {
1182 // when polyfilling Object.observe, ensure changes
1183 // propagate before executing the async method
1184 scheduleMicrotask(Observable.dirtyCheck);
1185 PolymerJs.flush(); // for polymer-js interop
1186 return window.requestAnimationFrame(method);
1187 }
1188
1189 /// Cancel an operation scheduled by [async].
1190 void cancelAsync(int id) => window.cancelAnimationFrame(id);
1191
1192 /// Fire a [CustomEvent] targeting [onNode], or `this` if onNode is not
1193 /// supplied. Returns the new event.
1194 CustomEvent fire(String type,
1195 {Object detail, Node onNode, bool canBubble, bool cancelable}) {
1196 var node = onNode != null ? onNode : this;
1197 var event = new CustomEvent(type,
1198 canBubble: canBubble != null ? canBubble : true,
1199 cancelable: cancelable != null ? cancelable : true,
1200 detail: detail);
1201 node.dispatchEvent(event);
1202 return event;
1203 }
1204
1205 /// Fire an event asynchronously. See [async] and [fire].
1206 asyncFire(String type, {Object detail, Node toNode, bool canBubble}) {
1207 // TODO(jmesserly): I'm not sure this method adds much in Dart, it's easy to
1208 // add "() =>"
1209 async((x) =>
1210 fire(type, detail: detail, onNode: toNode, canBubble: canBubble));
1211 }
1212
1213 /// Remove [className] from [old], add class to [anew], if they exist.
1214 void classFollows(Element anew, Element old, String className) {
1215 if (old != null) {
1216 old.classes.remove(className);
1217 }
1218 if (anew != null) {
1219 anew.classes.add(className);
1220 }
1221 }
1222
1223 /// Installs external stylesheets and <style> elements with the attribute
1224 /// polymer-scope='controller' into the scope of element. This is intended
1225 /// to be called during custom element construction.
1226 void installControllerStyles() {
1227 var scope = findStyleScope();
1228 if (scope != null && !scopeHasNamedStyle(scope, localName)) {
1229 // allow inherited controller styles
1230 var decl = _element;
1231 var cssText = new StringBuffer();
1232 while (decl != null) {
1233 cssText.write(decl.cssTextForScope(_STYLE_CONTROLLER_SCOPE));
1234 decl = decl.superDeclaration;
1235 }
1236 if (cssText.isNotEmpty) {
1237 installScopeCssText('$cssText', scope);
1238 }
1239 }
1240 }
1241
1242 void installScopeStyle(style, [String name, Node scope]) {
1243 if (scope == null) scope = findStyleScope();
1244 if (name == null) name = '';
1245
1246 if (scope != null && !scopeHasNamedStyle(scope, '$_name$name')) {
1247 var cssText = new StringBuffer();
1248 if (style is Iterable) {
1249 for (var s in style) {
1250 cssText
1251 ..writeln(s.text)
1252 ..writeln();
1253 }
1254 } else {
1255 cssText = (style as Node).text;
1256 }
1257 installScopeCssText('$cssText', scope, name);
1258 }
1259 }
1260
1261 void installScopeCssText(String cssText, [Node scope, String name]) {
1262 if (scope == null) scope = findStyleScope();
1263 if (name == null) name = '';
1264
1265 if (scope == null) return;
1266
1267 if (_hasShadowDomPolyfill) {
1268 cssText = _shimCssText(cssText, scope is ShadowRoot ? scope.host : null);
1269 }
1270 var style = element.cssTextToScopeStyle(cssText, _STYLE_CONTROLLER_SCOPE);
1271 applyStyleToScope(style, scope);
1272 // cache that this style has been applied
1273 styleCacheForScope(scope).add('$_name$name');
1274 }
1275
1276 Node findStyleScope([node]) {
1277 // find the shadow root that contains this element
1278 var n = node;
1279 if (n == null) n = this;
1280 while (n.parentNode != null) {
1281 n = n.parentNode;
1282 }
1283 return n;
1284 }
1285
1286 bool scopeHasNamedStyle(Node scope, String name) =>
1287 styleCacheForScope(scope).contains(name);
1288
1289 Map _polyfillScopeStyleCache = {};
1290
1291 Set styleCacheForScope(Node scope) {
1292 var styles;
1293 if (_hasShadowDomPolyfill) {
1294 var name = scope is ShadowRoot
1295 ? scope.host.localName
1296 : (scope as Element).localName;
1297 var styles = _polyfillScopeStyleCache[name];
1298 if (styles == null) _polyfillScopeStyleCache[name] = styles = new Set();
1299 } else {
1300 styles = _scopeStyles[scope];
1301 if (styles == null) _scopeStyles[scope] = styles = new Set();
1302 }
1303 return styles;
1304 }
1305
1306 static final _scopeStyles = new Expando();
1307
1308 static String _shimCssText(String cssText, [Element host]) {
1309 var name = '';
1310 var is_ = false;
1311 if (host != null) {
1312 name = host.localName;
1313 is_ = host.attributes.containsKey('is');
1314 }
1315 var selector = _ShadowCss.callMethod('makeScopeSelector', [name, is_]);
1316 return _ShadowCss.callMethod('shimCssText', [cssText, selector]);
1317 }
1318
1319 static void applyStyleToScope(StyleElement style, Node scope) {
1320 if (style == null) return;
1321
1322 if (scope == document) scope = document.head;
1323
1324 if (_hasShadowDomPolyfill) scope = document.head;
1325
1326 // TODO(sorvell): necessary for IE
1327 // see https://connect.microsoft.com/IE/feedback/details/790212/
1328 // cloning-a-style-element-and-adding-to-document-produces
1329 // -unexpected-result#details
1330 // var clone = style.cloneNode(true);
1331 var clone = new StyleElement()..text = style.text;
1332
1333 var attr = style.attributes[_STYLE_SCOPE_ATTRIBUTE];
1334 if (attr != null) {
1335 clone.attributes[_STYLE_SCOPE_ATTRIBUTE] = attr;
1336 }
1337
1338 // TODO(sorvell): probably too brittle; try to figure out
1339 // where to put the element.
1340 var refNode = scope.firstChild;
1341 if (scope == document.head) {
1342 var selector = 'style[$_STYLE_SCOPE_ATTRIBUTE]';
1343 var styleElement = document.head.querySelectorAll(selector);
1344 if (styleElement.isNotEmpty) {
1345 refNode = styleElement.last.nextElementSibling;
1346 }
1347 }
1348 scope.insertBefore(clone, refNode);
1349 }
1350
1351 /// Invoke [callback] in [wait], unless the job is re-registered,
1352 /// which resets the timer. If [wait] is not supplied, this will use
1353 /// [window.requestAnimationFrame] instead of a [Timer].
1354 ///
1355 /// For example:
1356 ///
1357 /// _myJob = Polymer.scheduleJob(_myJob, callback);
1358 ///
1359 /// Returns the newly created job.
1360 // Dart note: renamed to scheduleJob to be a bit more consistent with Dart.
1361 PolymerJob scheduleJob(PolymerJob job, void callback(), [Duration wait]) {
1362 if (job == null) job = new PolymerJob._();
1363 // Dart note: made start smarter, so we don't need to call stop.
1364 return job..start(callback, wait);
1365 }
1366
1367 // Deprecated: Please use injectBoundHtml.
1368 @deprecated
1369 DocumentFragment injectBoundHTML(String html, [Element element]) =>
1370 injectBoundHtml(html, element: element);
1371
1372 /// Inject HTML which contains markup bound to this element into
1373 /// a target element (replacing target element content).
1374 DocumentFragment injectBoundHtml(String html, {Element element,
1375 NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
1376 var template = new TemplateElement()
1377 ..setInnerHtml(html, validator: validator, treeSanitizer: treeSanitizer);
1378 var fragment = this.instanceTemplate(template);
1379 if (element != null) {
1380 element.text = '';
1381 element.append(fragment);
1382 }
1383 return fragment;
1384 }
1385 }
1386
1387 // Dart note: this is related to _bindOldStylePublishedProperty. Polymer
1388 // addresses n-way bindings by metaprogramming: redefine the property on the
1389 // PolymerElement instance to always get its value from the model@path. This is
1390 // supported in Dart using a new style of @published property declaration using
1391 // the `readValue` and `writeValue` methods above. In the past we used to work
1392 // around this by listening to changes on both sides and updating the values.
1393 // This object provides the hooks to do this.
1394 // TODO(sigmund,jmesserly): delete after a deprecation period.
1395 class _PolymerBinding extends Bindable {
1396 final Polymer _target;
1397 final Symbol _property;
1398 final Bindable _bindable;
1399 StreamSubscription _sub;
1400 Object _lastValue;
1401
1402 _PolymerBinding(this._target, this._property, this._bindable) {
1403 _sub = _target.changes.listen(_propertyValueChanged);
1404 _updateNode(open(_updateNode));
1405 }
1406
1407 void _updateNode(newValue) {
1408 _lastValue = newValue;
1409 smoke.write(_target, _property, newValue);
1410 // Note: we don't invoke emitPropertyChangeRecord here because that's
1411 // done by listening on changes on the PolymerElement.
1412 }
1413
1414 void _propertyValueChanged(List<ChangeRecord> records) {
1415 for (var record in records) {
1416 if (record is PropertyChangeRecord && record.name == _property) {
1417 final newValue = smoke.read(_target, _property);
1418 if (!identical(_lastValue, newValue)) {
1419 this.value = newValue;
1420 }
1421 return;
1422 }
1423 }
1424 }
1425
1426 open(callback(value)) => _bindable.open(callback);
1427 get value => _bindable.value;
1428 set value(newValue) => _bindable.value = newValue;
1429
1430 void close() {
1431 if (_sub != null) {
1432 _sub.cancel();
1433 _sub = null;
1434 }
1435 _bindable.close();
1436 }
1437 }
1438
1439 // Ported from an inline object in instance/properties.js#bindToAccessor.
1440 class _CloseOnlyBinding extends Bindable {
1441 final _PropertyAccessor accessor;
1442
1443 _CloseOnlyBinding(this.accessor);
1444
1445 open(callback) {}
1446 get value => null;
1447 set value(newValue) {}
1448 deliver() {}
1449
1450 void close() {
1451 if (accessor.bindable == null) return;
1452 accessor.bindable.close();
1453 accessor.bindable = null;
1454 }
1455 }
1456
1457 bool _toBoolean(value) => null != value && false != value;
1458
1459 final Logger _observeLog = new Logger('polymer.observe');
1460 final Logger _eventsLog = new Logger('polymer.events');
1461 final Logger _unbindLog = new Logger('polymer.unbind');
1462 final Logger _bindLog = new Logger('polymer.bind');
1463 final Logger _watchLog = new Logger('polymer.watch');
1464 final Logger _readyLog = new Logger('polymer.ready');
1465
1466 final Expando _eventHandledTable = new Expando<Set<Node>>();
OLDNEW
« no previous file with comments | « packages/polymer/lib/src/initializers.dart ('k') | packages/polymer/lib/src/job.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698