OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of polymer; | 5 part of polymer; |
6 | 6 |
7 /** | 7 /** |
8 * Use this annotation to publish a field as an attribute. For example: | 8 * Use this annotation to publish a field as an attribute. For example: |
9 * | 9 * |
10 * class MyPlaybackElement extends PolymerElement { | 10 * class MyPlaybackElement extends PolymerElement { |
(...skipping 345 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |