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

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

Issue 173003006: Use smoke in polymer and polymer_expressions. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of polymer; 5 part of polymer;
6 6
7 /** 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 {
(...skipping 345 matching lines...) Expand 10 before | Expand all | Expand 10 after
356 attributes.forEach(attributeToProperty); 356 attributes.forEach(attributeToProperty);
357 } 357 }
358 358
359 /** 359 /**
360 * If attribute [name] is mapped to a property, deserialize 360 * If attribute [name] is mapped to a property, deserialize
361 * [value] into that property. 361 * [value] into that property.
362 */ 362 */
363 void attributeToProperty(String name, String value) { 363 void attributeToProperty(String name, String value) {
364 // try to match this attribute to a property (attributes are 364 // try to match this attribute to a property (attributes are
365 // all lower-case, so this is case-insensitive search) 365 // all lower-case, so this is case-insensitive search)
366 var property = propertyForAttribute(name); 366 var decl = propertyForAttribute(name);
367 if (property == null) return; 367 if (decl == null) return;
368 368
369 // filter out 'mustached' values, these are to be 369 // filter out 'mustached' values, these are to be
370 // replaced with bound-data and are not yet values 370 // replaced with bound-data and are not yet values
371 // themselves. 371 // themselves.
372 if (value == null || value.contains(Polymer.bindPattern)) return; 372 if (value == null || value.contains(Polymer.bindPattern)) return;
373 373
374 // get original value 374 final currentValue = smoke.read(this, decl.name);
375 final self = reflect(this);
376 final currentValue = self.getField(property.simpleName).reflectee;
377 375
378 // deserialize Boolean or Number values from attribute 376 // deserialize Boolean or Number values from attribute
379 final newValue = deserializeValue(value, currentValue, 377 var type = decl.type;
380 _inferPropertyType(currentValue, property)); 378 if ((type == Object || type == dynamic) && currentValue != null) {
379 // Attempt to infer field type from the current value.
380 type = currentValue.runtimeType;
381 }
382 final newValue = deserializeValue(value, currentValue, type);
381 383
382 // only act if the value has changed 384 // only act if the value has changed
383 if (!identical(newValue, currentValue)) { 385 if (!identical(newValue, currentValue)) {
384 // install new value (has side-effects) 386 // install new value (has side-effects)
385 self.setField(property.simpleName, newValue); 387 smoke.write(this, decl.name, newValue);
386 } 388 }
387 } 389 }
388 390
389 /** Return the published property matching name, or null. */ 391 /** Return the published property matching name, or null. */
390 // TODO(jmesserly): should we just return Symbol here? 392 // TODO(jmesserly): should we just return Symbol here?
391 DeclarationMirror propertyForAttribute(String name) { 393 smoke.Declaration propertyForAttribute(String name) {
392 final publishLC = _declaration._publishLC; 394 final publishLC = _declaration._publishLC;
393 if (publishLC == null) return null; 395 if (publishLC == null) return null;
394 //console.log('propertyForAttribute:', name, 'matches', match); 396 //console.log('propertyForAttribute:', name, 'matches', match);
395 return publishLC[name]; 397 return publishLC[name];
396 } 398 }
397 399
398 /** 400 /**
399 * Convert representation of [value] based on [type] and [currentValue]. 401 * Convert representation of [value] based on [type] and [currentValue].
400 */ 402 */
401 // TODO(jmesserly): this should probably take a ClassMirror instead of 403 Object deserializeValue(String value, Object currentValue, Type type) =>
402 // TypeMirror, but it is currently impossible to get from a TypeMirror to a
403 // ClassMirror.
404 Object deserializeValue(String value, Object currentValue, TypeMirror type) =>
405 deserialize.deserializeValue(value, currentValue, type); 404 deserialize.deserializeValue(value, currentValue, type);
406 405
407 String serializeValue(Object value) { 406 String serializeValue(Object value) {
408 if (value == null) return null; 407 if (value == null) return null;
409 408
410 if (value is bool) { 409 if (value is bool) {
411 return _toBoolean(value) ? '' : null; 410 return _toBoolean(value) ? '' : null;
412 } else if (value is String || value is num) { 411 } else if (value is String || value is num) {
413 return '$value'; 412 return '$value';
414 } 413 }
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
451 DocumentFragment instanceTemplate(Element template) => 450 DocumentFragment instanceTemplate(Element template) =>
452 templateBind(template).createInstance(this, syntax); 451 templateBind(template).createInstance(this, syntax);
453 452
454 // TODO(jmesserly): Polymer does not seem to implement the oneTime flag 453 // TODO(jmesserly): Polymer does not seem to implement the oneTime flag
455 // correctly. File bug. 454 // correctly. File bug.
456 Bindable bind(String name, Bindable bindable, {bool oneTime: false}) { 455 Bindable bind(String name, Bindable bindable, {bool oneTime: false}) {
457 // note: binding is a prepare signal. This allows us to be sure that any 456 // note: binding is a prepare signal. This allows us to be sure that any
458 // property changes that occur as a result of binding will be observed. 457 // property changes that occur as a result of binding will be observed.
459 if (!_elementPrepared) prepareElement(); 458 if (!_elementPrepared) prepareElement();
460 459
461 var property = propertyForAttribute(name); 460 var decl = propertyForAttribute(name);
462 if (property == null) { 461 if (decl == null) {
463 // Cannot call super.bind because template_binding is its own package 462 // Cannot call super.bind because template_binding is its own package
464 return nodeBindFallback(this).bind(name, bindable, oneTime: oneTime); 463 return nodeBindFallback(this).bind(name, bindable, oneTime: oneTime);
465 } else { 464 } else {
466 // clean out the closets 465 // clean out the closets
467 unbind(name); 466 unbind(name);
468 // use n-way Polymer binding 467 // use n-way Polymer binding
469 var observer = bindProperty(property.simpleName, bindable); 468 var observer = bindProperty(decl.name, bindable);
470 469
471 // reflect bound property to attribute when binding 470 // reflect bound property to attribute when binding
472 // to ensure binding is not left on attribute if property 471 // to ensure binding is not left on attribute if property
473 // does not update due to not changing. 472 // does not update due to not changing.
474 // Dart note: we include this patch: 473 // Dart note: we include this patch:
475 // https://github.com/Polymer/polymer/pull/319 474 // https://github.com/Polymer/polymer/pull/319
476 475
477 // TODO(jmesserly): polymer has the path_ in their observer object, should 476 // TODO(jmesserly): polymer has the path_ in their observer object, should
478 // we use that too instead of allocating it here? 477 // we use that too instead of allocating it here?
479 reflectPropertyToAttribute(new PropertyPath([property.simpleName])); 478 reflectPropertyToAttribute(new PropertyPath([decl.name]));
480 return bindings[name] = observer; 479 return bindings[name] = observer;
481 } 480 }
482 } 481 }
483 482
484 Map<String, Bindable> get bindings => nodeBindFallback(this).bindings; 483 Map<String, Bindable> get bindings => nodeBindFallback(this).bindings;
485 TemplateInstance get templateInstance => 484 TemplateInstance get templateInstance =>
486 nodeBindFallback(this).templateInstance; 485 nodeBindFallback(this).templateInstance;
487 486
488 void unbind(String name) => nodeBindFallback(this).unbind(name); 487 void unbind(String name) => nodeBindFallback(this).unbind(name);
489 488
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
591 var methods = observe[path]; 590 var methods = observe[path];
592 if (methods == null) return; 591 if (methods == null) return;
593 592
594 for (var method in methods) { 593 for (var method in methods) {
595 if (!called.add(method)) continue; // don't invoke more than once. 594 if (!called.add(method)) continue; // don't invoke more than once.
596 595
597 final newValue = newValues[i]; 596 final newValue = newValues[i];
598 // observes the value if it is an array 597 // observes the value if it is an array
599 observeArrayValue(path, newValue, oldValue); 598 observeArrayValue(path, newValue, oldValue);
600 // Dart note: JS passes "arguments", so we pass along our args. 599 // Dart note: JS passes "arguments", so we pass along our args.
601 invokeMethod(method, [oldValue, newValue, newValues, oldValues, paths]); 600 smoke.invoke(this, method,
601 [oldValue, newValue, newValues, oldValues, paths], adjust: true);
602 } 602 }
603 }); 603 });
604 } 604 }
605 605
606 void observeArrayValue(PropertyPath name, Object value, Object old) { 606 void observeArrayValue(PropertyPath name, Object value, Object old) {
607 final observe = _declaration._observe; 607 final observe = _declaration._observe;
608 if (observe == null) return; 608 if (observe == null) return;
609 609
610 // we only care if there are registered side-effects 610 // we only care if there are registered side-effects
611 var callbacks = observe[name]; 611 var callbacks = observe[name];
612 if (callbacks == null) return; 612 if (callbacks == null) return;
613 613
614 // if we are observing the previous value, stop 614 // if we are observing the previous value, stop
615 if (old is ObservableList) { 615 if (old is ObservableList) {
616 if (_observeLog.isLoggable(Level.FINE)) { 616 if (_observeLog.isLoggable(Level.FINE)) {
617 _observeLog.fine('[$localName] observeArrayValue: unregister observer ' 617 _observeLog.fine('[$localName] observeArrayValue: unregister observer '
618 '$name'); 618 '$name');
619 } 619 }
620 620
621 unregisterObserver('${name}__array'); 621 unregisterObserver('${name}__array');
622 } 622 }
623 // if the new value is an array, being observing it 623 // if the new value is an array, being observing it
624 if (value is ObservableList) { 624 if (value is ObservableList) {
625 if (_observeLog.isLoggable(Level.FINE)) { 625 if (_observeLog.isLoggable(Level.FINE)) {
626 _observeLog.fine('[$localName] observeArrayValue: register observer ' 626 _observeLog.fine('[$localName] observeArrayValue: register observer '
627 '$name'); 627 '$name');
628 } 628 }
629 var sub = value.listChanges.listen((changes) { 629 var sub = value.listChanges.listen((changes) {
630 for (var callback in callbacks) { 630 for (var callback in callbacks) {
631 invokeMethod(callback, [old]); 631 smoke.invoke(this, callback, [old], adjust: true);
632 } 632 }
633 }); 633 });
634 registerObserver('${name}__array', sub); 634 registerObserver('${name}__array', sub);
635 } 635 }
636 } 636 }
637 637
638 bool unbindProperty(String name) => unregisterObserver(name); 638 bool unbindProperty(String name) => unregisterObserver(name);
639 639
640 void unbindAllProperties() { 640 void unbindAllProperties() {
641 if (_propertyObserver != null) { 641 if (_propertyObserver != null) {
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
754 754
755 /** 755 /**
756 * Calls [methodOrCallback] with [args] if it is a closure, otherwise, treat 756 * Calls [methodOrCallback] with [args] if it is a closure, otherwise, treat
757 * it as a method name in [object], and invoke it. 757 * it as a method name in [object], and invoke it.
758 */ 758 */
759 void dispatchMethod(object, callbackOrMethod, List args) { 759 void dispatchMethod(object, callbackOrMethod, List args) {
760 bool log = _eventsLog.isLoggable(Level.FINE); 760 bool log = _eventsLog.isLoggable(Level.FINE);
761 if (log) _eventsLog.fine('>>> [$localName]: dispatch $callbackOrMethod'); 761 if (log) _eventsLog.fine('>>> [$localName]: dispatch $callbackOrMethod');
762 762
763 if (callbackOrMethod is Function) { 763 if (callbackOrMethod is Function) {
764 int maxArgs = smoke.maxArgs(callbackOrMethod);
765 if (maxArgs == -1) {
766 _eventsLog.warning(
767 'invalid callback: expected callback of 0, 1, 2, or 3 arguments');
768 }
769 args.length = maxArgs;
764 Function.apply(callbackOrMethod, args); 770 Function.apply(callbackOrMethod, args);
765 } else if (callbackOrMethod is String) { 771 } else if (callbackOrMethod is String) {
766 _invokeMethod(object, new Symbol(callbackOrMethod), args); 772 smoke.invoke(object, smoke.nameToSymbol(callbackOrMethod), args,
773 adjust: true);
767 } else { 774 } else {
768 _eventsLog.warning('invalid callback'); 775 _eventsLog.warning('invalid callback');
769 } 776 }
770 777
771 if (log) _eventsLog.info('<<< [$localName]: dispatch $callbackOrMethod'); 778 if (log) _eventsLog.info('<<< [$localName]: dispatch $callbackOrMethod');
772 } 779 }
773 780
774 /** 781 /**
775 * Bind events via attributes of the form `on-eventName`. This method can be 782 * Bind events via attributes of the form `on-eventName`. This method can be
776 * use to hooks into the model syntax and adds event listeners as needed. By 783 * use to hooks into the model syntax and adds event listeners as needed. By
(...skipping 15 matching lines...) Expand all
792 // TODO(sigmund): polymer.js dropped event translations. reconcile? 799 // TODO(sigmund): polymer.js dropped event translations. reconcile?
793 var translated = _eventTranslations[eventName]; 800 var translated = _eventTranslations[eventName];
794 eventName = translated != null ? translated : eventName; 801 eventName = translated != null ? translated : eventName;
795 802
796 return new _EventBindable(node, eventName, model, path); 803 return new _EventBindable(node, eventName, model, path);
797 }; 804 };
798 } 805 }
799 806
800 /** Call [methodName] method on this object with [args]. */ 807 /** Call [methodName] method on this object with [args]. */
801 invokeMethod(Symbol methodName, List args) => 808 invokeMethod(Symbol methodName, List args) =>
802 _invokeMethod(this, methodName, args); 809 smoke.invoke(this, methodName, args, adjust: true);
803
804 /** Call [methodName] method on [receiver] with [args]. */
805 static _invokeMethod(receiver, Symbol methodName, List args) {
806 // TODO(jmesserly): use function type tests instead of mirrors for dispatch.
807 var receiverMirror = reflect(receiver);
808 var method = _findMethod(receiverMirror.type, methodName);
809 if (method != null) {
810 // This will either truncate the argument list or extend it with extra
811 // null arguments, so it will match the signature.
812 // TODO(sigmund): consider accepting optional arguments when we can tell
813 // them appart from named arguments (see http://dartbug.com/11334)
814 args.length = method.parameters.where((p) => !p.isOptional).length;
815 }
816 return receiverMirror.invoke(methodName, args).reflectee;
817 }
818
819 static MethodMirror _findMethod(ClassMirror type, Symbol name) {
820 do {
821 var member = type.declarations[name];
822 if (member is MethodMirror) return member;
823 type = type.superclass;
824 } while (type != null);
825 return null; // unreachable
826 }
827 810
828 /** 811 /**
829 * Invokes a function asynchronously. 812 * Invokes a function asynchronously.
830 * This will call `Platform.flush()` and then return a `new Timer` 813 * This will call `Platform.flush()` and then return a `new Timer`
831 * with the provided [method] and [timeout]. 814 * with the provided [method] and [timeout].
832 * 815 *
833 * If you would prefer to run the callback using 816 * If you would prefer to run the callback using
834 * [window.requestAnimationFrame], see the [async] method. 817 * [window.requestAnimationFrame], see the [async] method.
835 */ 818 */
836 // Dart note: "async" is split into 2 methods so it can have a sensible type 819 // Dart note: "async" is split into 2 methods so it can have a sensible type
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after
995 } 978 }
996 } 979 }
997 980
998 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine 981 // Dart note: Polymer addresses n-way bindings by metaprogramming: redefine
999 // the property on the PolymerElement instance to always get its value from the 982 // the property on the PolymerElement instance to always get its value from the
1000 // model@path. We can't replicate this in Dart so we do the next best thing: 983 // model@path. We can't replicate this in Dart so we do the next best thing:
1001 // listen to changes on both sides and update the values. 984 // listen to changes on both sides and update the values.
1002 // TODO(jmesserly): our approach leads to race conditions in the bindings. 985 // TODO(jmesserly): our approach leads to race conditions in the bindings.
1003 // See http://code.google.com/p/dart/issues/detail?id=13567 986 // See http://code.google.com/p/dart/issues/detail?id=13567
1004 class _PolymerBinding extends Bindable { 987 class _PolymerBinding extends Bindable {
1005 final InstanceMirror _target; 988 final Polymer _target;
1006 final Symbol _property; 989 final Symbol _property;
1007 final Bindable _bindable; 990 final Bindable _bindable;
1008 StreamSubscription _sub; 991 StreamSubscription _sub;
1009 Object _lastValue; 992 Object _lastValue;
1010 993
1011 _PolymerBinding(Polymer node, this._property, this._bindable) 994 _PolymerBinding(this._target, this._property, this._bindable) {
1012 : _target = reflect(node) { 995 _sub = _target.changes.listen(_propertyValueChanged);
1013
1014 _sub = node.changes.listen(_propertyValueChanged);
1015 _updateNode(open(_updateNode)); 996 _updateNode(open(_updateNode));
1016 } 997 }
1017 998
1018 void _updateNode(newValue) { 999 void _updateNode(newValue) {
1019 _lastValue = newValue; 1000 _lastValue = newValue;
1020 _target.setField(_property, newValue); 1001 smoke.write(_target, _property, newValue);
1021 } 1002 }
1022 1003
1023 void _propertyValueChanged(List<ChangeRecord> records) { 1004 void _propertyValueChanged(List<ChangeRecord> records) {
1024 for (var record in records) { 1005 for (var record in records) {
1025 if (record is PropertyChangeRecord && record.name == _property) { 1006 if (record is PropertyChangeRecord && record.name == _property) {
1026 final newValue = _target.getField(_property).reflectee; 1007 final newValue = smoke.read(_target, _property);
1027 if (!identical(_lastValue, newValue)) { 1008 if (!identical(_lastValue, newValue)) {
1028 this.value = newValue; 1009 this.value = newValue;
1029 } 1010 }
1030 return; 1011 return;
1031 } 1012 }
1032 } 1013 }
1033 } 1014 }
1034 1015
1035 open(callback(value)) => _bindable.open(callback); 1016 open(callback(value)) => _bindable.open(callback);
1036 get value => _bindable.value; 1017 get value => _bindable.value;
1037 set value(newValue) => _bindable.value = newValue; 1018 set value(newValue) => _bindable.value = newValue;
1038 1019
1039 void close() { 1020 void close() {
1040 if (_sub != null) { 1021 if (_sub != null) {
1041 _sub.cancel(); 1022 _sub.cancel();
1042 _sub = null; 1023 _sub = null;
1043 } 1024 }
1044 _bindable.close(); 1025 _bindable.close();
1045 } 1026 }
1046 } 1027 }
1047 1028
1048 bool _toBoolean(value) => null != value && false != value; 1029 bool _toBoolean(value) => null != value && false != value;
1049 1030
1050 TypeMirror _propertyType(DeclarationMirror property) =>
1051 property is VariableMirror ? property.type
1052 : (property as MethodMirror).returnType;
1053
1054 TypeMirror _inferPropertyType(Object value, DeclarationMirror property) {
1055 var type = _propertyType(property);
1056 if (type.qualifiedName == #dart.core.Object ||
1057 type.qualifiedName == #dynamic) {
1058 // Attempt to infer field type from the default value.
1059 if (value != null) {
1060 Type t = _getCoreType(value);
1061 if (t != null) return reflectClass(t);
1062 return reflect(value).type;
1063 }
1064 }
1065 return type;
1066 }
1067
1068 Type _getCoreType(Object value) {
1069 if (value == null) return Null;
1070 if (value is int) return int;
1071 // Avoid "is double" to prevent warning that it won't work in dart2js.
1072 if (value is num) return double;
1073 if (value is bool) return bool;
1074 if (value is String) return String;
1075 if (value is DateTime) return DateTime;
1076 return null;
1077 }
1078
1079 final Logger _observeLog = new Logger('polymer.observe'); 1031 final Logger _observeLog = new Logger('polymer.observe');
1080 final Logger _eventsLog = new Logger('polymer.events'); 1032 final Logger _eventsLog = new Logger('polymer.events');
1081 final Logger _unbindLog = new Logger('polymer.unbind'); 1033 final Logger _unbindLog = new Logger('polymer.unbind');
1082 final Logger _bindLog = new Logger('polymer.bind'); 1034 final Logger _bindLog = new Logger('polymer.bind');
1083 1035
1084 final Expando _shadowHost = new Expando<Polymer>(); 1036 final Expando _shadowHost = new Expando<Polymer>();
1085 1037
1086 final Expando _eventHandledTable = new Expando<Set<Node>>(); 1038 final Expando _eventHandledTable = new Expando<Set<Node>>();
1087 1039
1088 /** 1040 /**
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
1149 close() { 1101 close() {
1150 if (_sub != null) { 1102 if (_sub != null) {
1151 if (_eventsLog.isLoggable(Level.FINE)) { 1103 if (_eventsLog.isLoggable(Level.FINE)) {
1152 _eventsLog.fine( 1104 _eventsLog.fine(
1153 'event.remove: [$_node].$_eventName => [$_model].$_path())'); 1105 'event.remove: [$_node].$_eventName => [$_model].$_path())');
1154 } 1106 }
1155 _sub.cancel(); 1107 _sub.cancel();
1156 _sub = null; 1108 _sub = null;
1157 } 1109 }
1158 } 1110 }
1159 } 1111 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698