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

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

Issue 24149003: Port of github.com/polymer/polymer. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: rebase Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/polymer/lib/src/declaration.dart ('k') | pkg/polymer/lib/src/loader.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 /**
8 * Use this annotation to publish a field as an attribute. For example:
9 *
10 * class MyPlaybackElement extends PolymerElement {
11 * // This will be available as an HTML attribute, for example:
12 * // <my-playback volume="11">
13 * @published double volume;
14 * }
15 */
16 // TODO(jmesserly): does @published imply @observable or vice versa?
17 const published = const PublishedProperty();
18
19 /** An annotation used to publish a field as an attribute. See [published]. */
20 class PublishedProperty extends ObservableProperty {
21 const PublishedProperty();
22 }
23
24 // TODO(jmesserly): make this the mixin so we can have Polymer type extensions,
25 // and move the implementation of PolymerElement in here. Once done it will look
26 // like:
27 // abstract class Polymer { ... all the things ... }
28 // typedef PolymerElement = HtmlElement with Polymer, Observable;
29 abstract class Polymer {
30 // TODO(jmesserly): should this really be public?
31 /** Regular expression that matches data-bindings. */
32 static final bindPattern = new RegExp(r'\{\{([^{}]*)}}');
33
34 /**
35 * Like [document.register] but for Polymer elements.
36 *
37 * Use the [name] to specify custom elment's tag name, for example:
38 * "fancy-button" if the tag is used as `<fancy-button>`.
39 *
40 * The [type] is the type to construct. If not supplied, it defaults to
41 * [PolymerElement].
42 */
43 // NOTE: this is called "element" in src/declaration/polymer-element.js, and
44 // exported as "Polymer".
45 static void register(String name, [Type type]) {
46 //console.log('registering [' + name + ']');
47 if (type == null) type = PolymerElement;
48 _registerClassMirror(name, reflectClass(type));
49 }
50
51 // TODO(jmesserly): we use ClassMirror internall for now, until it is possible
52 // to get from ClassMirror -> Type.
53 static void _registerClassMirror(String name, ClassMirror type) {
54 _typesByName[name] = type;
55 // notify the registrar waiting for 'name', if any
56 _notifyType(name);
57 }
58 }
59
60 /**
61 * The base class for Polymer elements. It provides convience features on top
62 * of the custom elements web standard.
63 */
64 class PolymerElement extends CustomElement with ObservableMixin {
65 // Fully ported from revision:
66 // https://github.com/Polymer/polymer/blob/4dc481c11505991a7c43228d3797d28f212 67779
67 //
68 // src/instance/attributes.js
69 // src/instance/base.js
70 // src/instance/events.js
71 // src/instance/mdv.js
72 // src/instance/properties.js
73 // src/instance/utils.js
74 //
75 // Not yet ported:
76 // src/instance/style.js -- blocked on ShadowCSS.shimPolyfillDirectives
77
78 /// The one syntax to rule them all.
79 static final BindingDelegate _polymerSyntax = new PolymerExpressions();
80
81 static int _preparingElements = 0;
82
83 PolymerDeclaration _declaration;
84
85 /** The most derived `<polymer-element>` declaration for this element. */
86 PolymerDeclaration get declaration => _declaration;
87
88 Map<String, StreamSubscription> _elementObservers;
89 bool _unbound; // lazy-initialized
90 Job _unbindAllJob;
91
92 bool get _elementPrepared => _declaration != null;
93
94 bool get applyAuthorStyles => false;
95 bool get resetStyleInheritance => false;
96 bool get alwaysPrepare => false;
97
98 /**
99 * Shadow roots created by [parseElement]. See [getShadowRoot].
100 */
101 final _shadowRoots = new HashMap<String, ShadowRoot>();
102
103 /** Map of items in the shadow root(s) by their [Element.id]. */
104 // TODO(jmesserly): various issues:
105 // * wrap in UnmodifiableMapView?
106 // * should we have an object that implements noSuchMethod?
107 // * should the map have a key order (e.g. LinkedHash or SplayTree)?
108 // * should this be a live list? Polymer doesn't, maybe due to JS limitations?
109 // For now I picked the most performant choice: non-live HashMap.
110 final Map<String, Element> $ = new HashMap<String, Element>();
111
112 /**
113 * Gets the shadow root associated with the corresponding custom element.
114 *
115 * This is identical to [shadowRoot], unless there are multiple levels of
116 * inheritance and they each have their own shadow root. For example,
117 * this can happen if the base class and subclass both have `<template>` tags
118 * in their `<polymer-element>` tags.
119 */
120 // TODO(jmesserly): Polymer does not have this feature. Reconcile.
121 ShadowRoot getShadowRoot(String customTagName) => _shadowRoots[customTagName];
122
123 ShadowRoot createShadowRoot([name]) {
124 if (name != null) {
125 throw new ArgumentError('name argument must not be supplied.');
126 }
127
128 // Provides ability to traverse from ShadowRoot to the host.
129 // TODO(jmessery): remove once we have this ability on the DOM.
130 final root = super.createShadowRoot();
131 _shadowHost[root] = host;
132 return root;
133 }
134
135 /**
136 * Invoke [callback] in [wait], unless the job is re-registered,
137 * which resets the timer. For example:
138 *
139 * _myJob = job(_myJob, callback, const Duration(milliseconds: 100));
140 *
141 * Returns a job handle which can be used to re-register a job.
142 */
143 Job job(Job job, void callback(), Duration wait) =>
144 runJob(job, callback, wait);
145
146 // TODO(jmesserly): I am not sure if we should have the
147 // created/createdCallback distinction. See post here:
148 // https://groups.google.com/d/msg/polymer-dev/W0ZUpU5caIM/v5itFnvnehEJ
149 // Same issue with inserted and removed.
150 void created() {
151 if (document.window != null || alwaysPrepare || _preparingElements > 0) {
152 prepareElement();
153 }
154 }
155
156 void prepareElement() {
157 // Dart note: get the _declaration, which also marks _elementPrepared
158 _declaration = _getDeclaration(reflect(this).type);
159 // do this first so we can observe changes during initialization
160 observeProperties();
161 // install boilerplate attributes
162 copyInstanceAttributes();
163 // process input attributes
164 takeAttributes();
165 // add event listeners
166 addHostListeners();
167 // guarantees that while preparing, any sub-elements will also be prepared
168 _preparingElements++;
169 // process declarative resources
170 parseDeclarations(_declaration);
171 _preparingElements--;
172 // user entry point
173 ready();
174 }
175
176 /** Called when [prepareElement] is finished. */
177 void ready() {}
178
179 void inserted() {
180 if (!_elementPrepared) {
181 prepareElement();
182 }
183 cancelUnbindAll(preventCascade: true);
184 }
185
186 void removed() {
187 asyncUnbindAll();
188 }
189
190 /** Recursive ancestral <element> initialization, oldest first. */
191 void parseDeclarations(PolymerDeclaration declaration) {
192 if (declaration != null) {
193 parseDeclarations(declaration.superDeclaration);
194 parseDeclaration(declaration.host);
195 }
196 }
197
198 /**
199 * Parse input `<polymer-element>` as needed, override for custom behavior.
200 */
201 void parseDeclaration(Element elementElement) {
202 var root = shadowFromTemplate(fetchTemplate(elementElement));
203
204 // Dart note: this is extra code compared to Polymer to support
205 // the getShadowRoot method.
206 if (root == null) return;
207
208 var name = elementElement.attributes['name'];
209 if (name == null) return;
210 _shadowRoots[name] = root;
211 }
212
213 /**
214 * Return a shadow-root template (if desired), override for custom behavior.
215 */
216 Element fetchTemplate(Element elementElement) =>
217 elementElement.query('template');
218
219 /** Utility function that creates a shadow root from a `<template>`. */
220 ShadowRoot shadowFromTemplate(Element template) {
221 if (template == null) return null;
222 // cache elder shadow root (if any)
223 var elderRoot = this.shadowRoot;
224 // make a shadow root
225 var root = createShadowRoot();
226 // migrate flag(s)(
227 root.applyAuthorStyles = applyAuthorStyles;
228 root.resetStyleInheritance = resetStyleInheritance;
229 // stamp template
230 // which includes parsing and applying MDV bindings before being
231 // inserted (to avoid {{}} in attribute values)
232 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
233 var dom = instanceTemplate(template);
234 // append to shadow dom
235 root.append(dom);
236 // perform post-construction initialization tasks on shadow root
237 shadowRootReady(root, template);
238 // return the created shadow root
239 return root;
240 }
241
242 void shadowRootReady(ShadowRoot root, Element template) {
243 // locate nodes with id and store references to them in this.$ hash
244 marshalNodeReferences(root);
245 // add local events of interest...
246 addInstanceListeners(root, template);
247 // TODO(jmesserly): port this
248 // set up pointer gestures
249 // PointerGestures.register(root);
250 }
251
252 /** Locate nodes with id and store references to them in [$] hash. */
253 void marshalNodeReferences(ShadowRoot root) {
254 if (root == null) return;
255 for (var n in root.queryAll('[id]')) {
256 $[n.id] = n;
257 }
258 }
259
260 void attributeChanged(String name, String oldValue) {
261 if (name != 'class' && name != 'style') {
262 attributeToProperty(name, attributes[name]);
263 }
264 }
265
266 // TODO(jmesserly): use stream or future here?
267 void onMutation(Node node, void listener(MutationObserver obs)) {
268 new MutationObserver((records, MutationObserver observer) {
269 listener(observer);
270 observer.disconnect();
271 })..observe(node, childList: true, subtree: true);
272 }
273
274 void copyInstanceAttributes() {
275 _declaration._instanceAttributes.forEach((name, value) {
276 attributes[name] = value;
277 });
278 }
279
280 void takeAttributes() {
281 if (_declaration._publishLC == null) return;
282 attributes.forEach(attributeToProperty);
283 }
284
285 /**
286 * If attribute [name] is mapped to a property, deserialize
287 * [value] into that property.
288 */
289 void attributeToProperty(String name, String value) {
290 // try to match this attribute to a property (attributes are
291 // all lower-case, so this is case-insensitive search)
292 var property = propertyForAttribute(name);
293 if (property == null) return;
294
295 // filter out 'mustached' values, these are to be
296 // replaced with bound-data and are not yet values
297 // themselves.
298 if (value == null || value.contains(Polymer.bindPattern)) return;
299
300 // get original value
301 final self = reflect(this);
302 final defaultValue = self.getField(property.simpleName).reflectee;
303
304 // deserialize Boolean or Number values from attribute
305 final newValue = deserializeValue(value, defaultValue,
306 _inferPropertyType(defaultValue, property));
307
308 // only act if the value has changed
309 if (!identical(newValue, defaultValue)) {
310 // install new value (has side-effects)
311 self.setField(property.simpleName, newValue);
312 }
313 }
314
315 /** Return the published property matching name, or null. */
316 // TODO(jmesserly): should we just return Symbol here?
317 DeclarationMirror propertyForAttribute(String name) {
318 final publishLC = _declaration._publishLC;
319 if (publishLC == null) return null;
320 //console.log('propertyForAttribute:', name, 'matches', match);
321 return publishLC[name];
322 }
323
324 /**
325 * Convert representation of [value] based on [type] and [defaultValue].
326 */
327 // TODO(jmesserly): this should probably take a ClassMirror instead of
328 // TypeMirror, but it is currently impossible to get from a TypeMirror to a
329 // ClassMirror.
330 Object deserializeValue(String value, Object defaultValue, TypeMirror type) =>
331 deserialize.deserializeValue(value, defaultValue, type);
332
333 String serializeValue(Object value, TypeMirror inferredType) {
334 if (value == null) return null;
335
336 final type = inferredType.qualifiedName;
337 if (type == const Symbol('dart.core.bool')) {
338 return _toBoolean(value) ? '' : null;
339 } else if (type == const Symbol('dart.core.String')
340 || type == const Symbol('dart.core.int')
341 || type == const Symbol('dart.core.double')) {
342 return '$value';
343 }
344 return null;
345 }
346
347 void reflectPropertyToAttribute(String name) {
348 // TODO(sjmiles): consider memoizing this
349 final self = reflect(this);
350 // try to intelligently serialize property value
351 // TODO(jmesserly): cache symbol?
352 final propValue = self.getField(new Symbol(name)).reflectee;
353 final property = _declaration._publish[name];
354 var inferredType = _inferPropertyType(propValue, property);
355 final serializedValue = serializeValue(propValue, inferredType);
356 // boolean properties must reflect as boolean attributes
357 if (serializedValue != null) {
358 attributes[name] = serializedValue;
359 // TODO(sorvell): we should remove attr for all properties
360 // that have undefined serialization; however, we will need to
361 // refine the attr reflection system to achieve this; pica, for example,
362 // relies on having inferredType object properties not removed as
363 // attrs.
364 } else if (inferredType.qualifiedName == const Symbol('dart.core.bool')) {
365 attributes.remove(name);
366 }
367 }
368
369 /**
370 * Creates the document fragment to use for each instance of the custom
371 * element, given the `<template>` node. By default this is equivalent to:
372 *
373 * template.createInstance(this, polymerSyntax);
374 *
375 * Where polymerSyntax is a singleton `PolymerExpressions` instance from the
376 * [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions )
377 * package.
378 *
379 * You can override this method to change the instantiation behavior of the
380 * template, for example to use a different data-binding syntax.
381 */
382 DocumentFragment instanceTemplate(Element template) =>
383 template.createInstance(this, _polymerSyntax);
384
385 NodeBinding bind(String name, model, String path) {
386 // note: binding is a prepare signal. This allows us to be sure that any
387 // property changes that occur as a result of binding will be observed.
388 if (!_elementPrepared) prepareElement();
389
390 var property = propertyForAttribute(name);
391 if (property != null) {
392 unbind(name);
393 // use n-way Polymer binding
394 var observer = bindProperty(property.simpleName, model, path);
395 // reflect bound property to attribute when binding
396 // to ensure binding is not left on attribute if property
397 // does not update due to not changing.
398 reflectPropertyToAttribute(name);
399 return bindings[name] = observer;
400 } else {
401 return super.bind(name, model, path);
402 }
403 }
404
405 void asyncUnbindAll() {
406 if (_unbound == true) return;
407 _unbindLog.info('[$localName] asyncUnbindAll');
408 _unbindAllJob = job(_unbindAllJob, unbindAll, const Duration(seconds: 0));
409 }
410
411 void unbindAll() {
412 if (_unbound == true) return;
413
414 unbindAllProperties();
415 super.unbindAll();
416 _unbindNodeTree(shadowRoot);
417 // TODO(sjmiles): must also unbind inherited shadow roots
418 _unbound = true;
419 }
420
421 void cancelUnbindAll({bool preventCascade}) {
422 if (_unbound == true) {
423 _unbindLog.warning(
424 '[$localName] already unbound, cannot cancel unbindAll');
425 return;
426 }
427 _unbindLog.info('[$localName] cancelUnbindAll');
428 if (_unbindAllJob != null) {
429 _unbindAllJob.stop();
430 _unbindAllJob = null;
431 }
432
433 // cancel unbinding our shadow tree iff we're not in the process of
434 // cascading our tree (as we do, for example, when the element is inserted).
435 if (preventCascade == true) return;
436 _forNodeTree(shadowRoot, (n) {
437 if (n is PolymerElement) {
438 (n as PolymerElement).cancelUnbindAll();
439 }
440 });
441 }
442
443 static void _unbindNodeTree(Node node) {
444 _forNodeTree(node, (node) => node.unbindAll());
445 }
446
447 static void _forNodeTree(Node node, void callback(Node node)) {
448 if (node == null) return;
449
450 callback(node);
451 for (var child = node.firstChild; child != null; child = child.nextNode) {
452 _forNodeTree(child, callback);
453 }
454 }
455
456 /** Set up property observers. */
457 void observeProperties() {
458 // TODO(sjmiles):
459 // we observe published properties so we can reflect them to attributes
460 // ~100% of our team's applications would work without this reflection,
461 // perhaps we can make it optional somehow
462 //
463 // add user's observers
464 final observe = _declaration._observe;
465 final publish = _declaration._publish;
466 if (observe != null) {
467 observe.forEach((name, value) {
468 if (publish != null && publish.containsKey(name)) {
469 observeBoth(name, value);
470 } else {
471 observeProperty(name, value);
472 }
473 });
474 }
475 // add observers for published properties
476 if (publish != null) {
477 publish.forEach((name, value) {
478 if (observe == null || !observe.containsKey(name)) {
479 observeAttributeProperty(name);
480 }
481 });
482 }
483 }
484
485 void _observe(String name, void callback(newValue, oldValue)) {
486 _observeLog.info('[$localName] watching [$name]');
487 // TODO(jmesserly): this is a little different than the JS version so we
488 // can pass the oldValue, which is missing from Dart's PathObserver.
489 // This probably gives us worse performance.
490 var path = new PathObserver(this, name);
491 Object oldValue = null;
492 _registerObserver(name, path.changes.listen((_) {
493 final newValue = path.value;
494 final old = oldValue;
495 oldValue = newValue;
496 callback(newValue, old);
497 }));
498 }
499
500 void _registerObserver(String name, StreamSubscription sub) {
501 if (_elementObservers == null) {
502 _elementObservers = new Map<String, StreamSubscription>();
503 }
504 _elementObservers[name] = sub;
505 }
506
507 void observeAttributeProperty(String name) {
508 _observe(name, (value, old) => reflectPropertyToAttribute(name));
509 }
510
511 void observeProperty(String name, Symbol method) {
512 final self = reflect(this);
513 _observe(name, (value, old) => self.invoke(method, [old]));
514 }
515
516 void observeBoth(String name, Symbol methodName) {
517 final self = reflect(this);
518 _observe(name, (value, old) {
519 reflectPropertyToAttribute(name);
520 self.invoke(methodName, [old]);
521 });
522 }
523
524 void unbindProperty(String name) {
525 if (_elementObservers == null) return;
526 var sub = _elementObservers.remove(name);
527 if (sub != null) sub.cancel();
528 }
529
530 void unbindAllProperties() {
531 if (_elementObservers == null) return;
532 for (var sub in _elementObservers.values) sub.cancel();
533 _elementObservers.clear();
534 }
535
536 /**
537 * Bind a [property] in this object to a [path] in model. *Note* in Dart it
538 * is necessary to also define the field:
539 *
540 * var myProperty;
541 *
542 * created() {
543 * super.created();
544 * bindProperty(#myProperty, this, 'myModel.path.to.otherProp');
545 * }
546 */
547 // TODO(jmesserly): replace with something more localized, like:
548 // @ComputedField('myModel.path.to.otherProp');
549 NodeBinding bindProperty(Symbol name, Object model, String path) =>
550 // apply Polymer two-way reference binding
551 _bindProperties(this, name, model, path);
552
553 /**
554 * bind a property in A to a path in B by converting A[property] to a
555 * getter/setter pair that accesses B[...path...]
556 */
557 static NodeBinding _bindProperties(PolymerElement inA, Symbol inProperty,
558 Object inB, String inPath) {
559
560 if (_bindLog.isLoggable(Level.INFO)) {
561 _bindLog.info('[$inB]: bindProperties: [$inPath] to '
562 '[${inA.localName}].[$inProperty]');
563 }
564
565 // Dart note: normally we only reach this code when we know it's a
566 // property, but if someone uses bindProperty directly they might get a
567 // NoSuchMethodError either from the getField below, or from the setField
568 // inside PolymerBinding. That doesn't seem unreasonable, but it's a slight
569 // difference from Polymer.js behavior.
570
571 // capture A's value if B's value is null or undefined,
572 // otherwise use B's value
573 var path = new PathObserver(inB, inPath);
574 if (path.value == null) {
575 path.value = reflect(inA).getField(inProperty).reflectee;
576 }
577 return new _PolymerBinding(inA, inProperty, inB, inPath);
578 }
579
580 /** Attach event listeners on the host (this) element. */
581 void addHostListeners() {
582 var events = _declaration._eventDelegates;
583 if (events.isEmpty) return;
584
585 if (_eventsLog.isLoggable(Level.INFO)) {
586 _eventsLog.info('[$localName] addHostListeners: $events');
587 }
588 addNodeListeners(this, events.keys, hostEventListener);
589 }
590
591 /** Attach event listeners inside a shadow [root]. */
592 void addInstanceListeners(ShadowRoot root, Element template) {
593 var templateDelegates = _declaration._templateDelegates;
594 if (templateDelegates == null) return;
595 var events = templateDelegates[template];
596 if (events == null) return;
597
598 if (_eventsLog.isLoggable(Level.INFO)) {
599 _eventsLog.info('[$localName] addInstanceListeners: $events');
600 }
601 addNodeListeners(root, events, instanceEventListener);
602 }
603
604 void addNodeListeners(Node node, Iterable<String> events,
605 void listener(Event e)) {
606
607 for (var name in events) {
608 addNodeListener(node, name, listener);
609 }
610 }
611
612 void addNodeListener(Node node, String event, void listener(Event e)) {
613 node.on[event].listen(listener);
614 }
615
616 void hostEventListener(Event event) {
617 // TODO(jmesserly): do we need this check? It was using cancelBubble, see:
618 // https://github.com/Polymer/polymer/issues/292
619 if (!event.bubbles) return;
620
621 bool log = _eventsLog.isLoggable(Level.INFO);
622 if (log) {
623 _eventsLog.info('>>> [$localName]: hostEventListener(${event.type})');
624 }
625
626 var h = findEventDelegate(event);
627 if (h != null) {
628 if (log) _eventsLog.info('[$localName] found host handler name [$h]');
629 var detail = event is CustomEvent ?
630 (event as CustomEvent).detail : null;
631 // TODO(jmesserly): cache the symbols?
632 dispatchMethod(new Symbol(h), [event, detail, this]);
633 }
634
635 if (log) {
636 _eventsLog.info('<<< [$localName]: hostEventListener(${event.type})');
637 }
638 }
639
640 String findEventDelegate(Event event) =>
641 _declaration._eventDelegates[_eventNameFromType(event.type)];
642
643 /** Call [methodName] method on [this] with [args], if the method exists. */
644 // TODO(jmesserly): I removed the [node] argument as it was unused. Reconcile.
645 void dispatchMethod(Symbol methodName, List args) {
646 bool log = _eventsLog.isLoggable(Level.INFO);
647 if (log) _eventsLog.info('>>> [$localName]: dispatch $methodName');
648
649 // TODO(sigmund): consider making event listeners list all arguments
650 // explicitly. Unless VM mirrors are optimized first, this reflectClass call
651 // will be expensive once custom elements extend directly from Element (see
652 // dartbug.com/11108).
653 var self = reflect(this);
654 var method = self.type.methods[methodName];
655 if (method != null) {
656 // This will either truncate the argument list or extend it with extra
657 // null arguments, so it will match the signature.
658 // TODO(sigmund): consider accepting optional arguments when we can tell
659 // them appart from named arguments (see http://dartbug.com/11334)
660 args.length = method.parameters.where((p) => !p.isOptional).length;
661 }
662 self.invoke(methodName, args);
663
664 if (log) _eventsLog.info('<<< [$localName]: dispatch $methodName');
665
666 // TODO(jmesserly): workaround for HTML events not supporting zones.
667 performMicrotaskCheckpoint();
668 }
669
670 void instanceEventListener(Event event) {
671 _listenLocal(host, event);
672 }
673
674 // TODO(sjmiles): much of the below privatized only because of the vague
675 // notion this code is too fiddly and we need to revisit the core feature
676 void _listenLocal(Element host, Event event) {
677 // TODO(jmesserly): do we need this check? It was using cancelBubble, see:
678 // https://github.com/Polymer/polymer/issues/292
679 if (!event.bubbles) return;
680
681 bool log = _eventsLog.isLoggable(Level.INFO);
682 if (log) _eventsLog.info('>>> [$localName]: listenLocal [${event.type}]');
683
684 final eventOn = '$_EVENT_PREFIX${_eventNameFromType(event.type)}';
685 if (event.path == null) {
686 _listenLocalNoEventPath(host, event, eventOn);
687 } else {
688 _listenLocalEventPath(host, event, eventOn);
689 }
690
691 if (log) _eventsLog.info('<<< [$localName]: listenLocal [${event.type}]');
692 }
693
694 static void _listenLocalEventPath(Element host, Event event, String eventOn) {
695 var c = null;
696 for (var target in event.path) {
697 // if we hit host, stop
698 if (identical(target, host)) return;
699
700 // find a controller for the target, unless we already found `host`
701 // as a controller
702 c = identical(c, host) ? c : _findController(target);
703
704 // if we have a controller, dispatch the event, and stop if the handler
705 // returns true
706 if (c != null && _handleEvent(c, target, event, eventOn)) {
707 return;
708 }
709 }
710 }
711
712 // TODO(sorvell): remove when ShadowDOM polyfill supports event path.
713 // Note that _findController will not return the expected controller when the
714 // event target is a distributed node. This is because we cannot traverse
715 // from a composed node to a node in shadowRoot.
716 // This will be addressed via an event path api
717 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066
718 static void _listenLocalNoEventPath(Element host, Event event,
719 String eventOn) {
720
721 if (_eventsLog.isLoggable(Level.INFO)) {
722 _eventsLog.info('event.path() not supported for ${event.type}');
723 }
724
725 var target = event.target;
726 var c = null;
727 // if we hit dirt or host, stop
728 while (target != null && target != host) {
729 // find a controller for target `t`, unless we already found `host`
730 // as a controller
731 c = identical(c, host) ? c : _findController(target);
732
733 // if we have a controller, dispatch the event, return 'true' if
734 // handler returns true
735 if (c != null && _handleEvent(c, target, event, eventOn)) {
736 return;
737 }
738 target = target.parent;
739 }
740 }
741
742 // TODO(jmesserly): this won't find the correct host unless the ShadowRoot
743 // was created on a PolymerElement.
744 static Element _findController(Node node) {
745 while (node.parentNode != null) {
746 node = node.parentNode;
747 }
748 return _shadowHost[node];
749 }
750
751 static bool _handleEvent(Element ctrlr, Node node, Event event,
752 String eventOn) {
753
754 // Note: local events are listened only in the shadow root. This dynamic
755 // lookup is used to distinguish determine whether the target actually has a
756 // listener, and if so, to determine lazily what's the target method.
757 var name = node is Element ? (node as Element).attributes[eventOn] : null;
758 if (name != null && _handleIfNotHandled(node, event)) {
759 if (_eventsLog.isLoggable(Level.INFO)) {
760 _eventsLog.info('[${ctrlr.localName}] found handler name [$name]');
761 }
762 var detail = event is CustomEvent ?
763 (event as CustomEvent).detail : null;
764
765 if (node != null) {
766 // TODO(jmesserly): cache symbols?
767 ctrlr.xtag.dispatchMethod(new Symbol(name), [event, detail, node]);
768 }
769 }
770
771 // TODO(jmesserly): do we need this? It was using cancelBubble, see:
772 // https://github.com/Polymer/polymer/issues/292
773 return !event.bubbles;
774 }
775
776 // TODO(jmesserly): I don't understand this bit. It seems to be a duplicate
777 // delivery prevention mechanism?
778 static bool _handleIfNotHandled(Node node, Event event) {
779 var list = _eventHandledTable[event];
780 if (list == null) _eventHandledTable[event] = list = new Set<Node>();
781 if (!list.contains(node)) {
782 list.add(node);
783 return true;
784 }
785 return false;
786 }
787
788 /**
789 * Invokes a function asynchronously.
790 * This will call `Platform.flush()` and then return a `new Timer`
791 * with the provided [method] and [timeout].
792 *
793 * If you would prefer to run the callback using
794 * [window.requestAnimationFrame], see the [async] method.
795 */
796 // Dart note: "async" is split into 2 methods so it can have a sensible type
797 // signatures. Also removed the various features that don't make sense in a
798 // Dart world, like binding to "this" and taking arguments list.
799 Timer asyncTimer(void method(), Duration timeout) {
800 // when polyfilling Object.observe, ensure changes
801 // propagate before executing the async method
802 platform.flush();
803 return new Timer(timeout, method);
804 }
805
806 /**
807 * Invokes a function asynchronously.
808 * This will call `Platform.flush()` and then call
809 * [window.requestAnimationFrame] with the provided [method] and return the
810 * result.
811 *
812 * If you would prefer to run the callback after a given duration, see
813 * the [asyncTimer] method.
814 */
815 int async(RequestAnimationFrameCallback method) {
816 // when polyfilling Object.observe, ensure changes
817 // propagate before executing the async method
818 platform.flush();
819 return window.requestAnimationFrame(method);
820 }
821
822 /**
823 * Fire a [CustomEvent] targeting [toNode], or this if toNode is not
824 * supplied. Returns the [detail] object.
825 */
826 Object fire(String type, {Object detail, Node toNode, bool canBubble}) {
827 var node = toNode != null ? toNode : this;
828 //log.events && console.log('[%s]: sending [%s]', node.localName, inType);
829 node.dispatchEvent(new CustomEvent(
830 type,
831 canBubble: canBubble != null ? canBubble : true,
832 detail: detail
833 ));
834 return detail;
835 }
836
837 /**
838 * Fire an event asynchronously. See [async] and [fire].
839 */
840 asyncFire(String type, {Object detail, Node toNode, bool canBubble}) {
841 // TODO(jmesserly): I'm not sure this method adds much in Dart, it's easy to
842 // add "() =>"
843 async((x) => fire(
844 type, detail: detail, toNode: toNode, canBubble: canBubble));
845 }
846
847 /**
848 * Remove [className] from [old], add class to [anew], if they exist.
849 */
850 void classFollows(Element anew, Element old, String className) {
851 if (old != null) {
852 old.classes.remove(className);
853 }
854 if (anew != null) {
855 anew.classes.add(className);
856 }
857 }
858 }
859
860 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine
861 // the property on the PolymerElement instance to always get its value from the
862 // model@path. We can't replicate this in Dart so we do the next best thing:
863 // listen to changes on both sides and update the values.
864 // TODO(jmesserly): our approach leads to race conditions in the bindings.
865 // See http://code.google.com/p/dart/issues/detail?id=13567
866 class _PolymerBinding extends NodeBinding {
867 final InstanceMirror _target;
868 final Symbol _property;
869 StreamSubscription _sub;
870 Object _lastValue;
871
872 _PolymerBinding(PolymerElement node, Symbol property, model, path)
873 : _target = reflect(node),
874 _property = property,
875 super(node, MirrorSystem.getName(property), model, path) {
876
877 _sub = node.changes.listen(_propertyValueChanged);
878 }
879
880 void close() {
881 if (closed) return;
882 _sub.cancel();
883 super.close();
884 }
885
886 void boundValueChanged(newValue) {
887 _lastValue = newValue;
888 _target.setField(_property, newValue);
889 }
890
891 void _propertyValueChanged(List<ChangeRecord> records) {
892 for (var record in records) {
893 if (record.changes(_property)) {
894 final newValue = _target.getField(_property).reflectee;
895 if (!identical(_lastValue, newValue)) {
896 value = newValue;
897 }
898 return;
899 }
900 }
901 }
902 }
903
904 bool _toBoolean(value) => null != value && false != value;
905
906 TypeMirror _propertyType(DeclarationMirror property) =>
907 property is VariableMirror
908 ? (property as VariableMirror).type
909 : (property as MethodMirror).returnType;
910
911 TypeMirror _inferPropertyType(Object value, DeclarationMirror property) {
912 var type = _propertyType(property);
913 if (type.qualifiedName == const Symbol('dart.core.Object') ||
914 type.qualifiedName == const Symbol('dynamic')) {
915 // Attempt to infer field type from the default value.
916 if (value != null) {
917 type = reflect(value).type;
918 }
919 }
920 return type;
921 }
922
923 final Logger _observeLog = new Logger('polymer.observe');
924 final Logger _eventsLog = new Logger('polymer.events');
925 final Logger _unbindLog = new Logger('polymer.unbind');
926 final Logger _bindLog = new Logger('polymer.bind');
927
928 final Expando _shadowHost = new Expando<Element>();
929
930 final Expando _eventHandledTable = new Expando<Set<Node>>();
OLDNEW
« no previous file with comments | « pkg/polymer/lib/src/declaration.dart ('k') | pkg/polymer/lib/src/loader.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698