| Index: pkg/observe/lib/src/path_observer.dart
|
| diff --git a/pkg/observe/lib/src/path_observer.dart b/pkg/observe/lib/src/path_observer.dart
|
| index 0d631a0c192829fb51726b968157ebbf13c3b554..cfaab187764caf34f630bca86db670e101073256 100644
|
| --- a/pkg/observe/lib/src/path_observer.dart
|
| +++ b/pkg/observe/lib/src/path_observer.dart
|
| @@ -82,8 +82,12 @@ class PathObserver extends _Observer implements Bindable {
|
| }
|
|
|
| /// A dot-delimieted property path such as "foo.bar" or "foo.10.bar".
|
| +///
|
| /// The path specifies how to get a particular value from an object graph, where
|
| -/// the graph can include arrays.
|
| +/// the graph can include arrays and maps. Each segment of the path describes
|
| +/// how to take a single step in the object graph. Properties like 'foo' or
|
| +/// 'bar' are read as properties on objects, or as keys if the object is a [Map]
|
| +/// or a [Indexable], while integer values are read as indexes in a [List].
|
| // TODO(jmesserly): consider specialized subclasses for:
|
| // * empty path
|
| // * "value"
|
| @@ -180,7 +184,7 @@ class PropertyPath {
|
| return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
| }
|
|
|
| - /// Returns the current of the path from the provided [obj]ect.
|
| + /// Returns the current value of the path from the provided [obj]ect.
|
| getValueFrom(Object obj) {
|
| if (!isValid) return null;
|
| for (var segment in _segments) {
|
| @@ -233,6 +237,12 @@ bool _changeRecordMatches(record, key) {
|
| return false;
|
| }
|
|
|
| +/// Properties in [Map] that need to be read as properties and not as keys in
|
| +/// the map. We exclude methods ('containsValue', 'containsKey', 'putIfAbsent',
|
| +/// 'addAll', 'remove', 'clear', 'forEach') because there is no use in reading
|
| +/// them as part of path-observer segments.
|
| +const _MAP_PROPERTIES = const [#keys, #values, #length, #isEmpty, #isNotEmpty];
|
| +
|
| _getObjectProperty(object, property) {
|
| if (object == null) return null;
|
|
|
| @@ -241,21 +251,24 @@ _getObjectProperty(object, property) {
|
| return object[property];
|
| }
|
| } else if (property is Symbol) {
|
| - final type = object.runtimeType;
|
| + // Support indexer if available, e.g. Maps or polymer_expressions Scope.
|
| + // This is the default syntax used by polymer/nodebind and
|
| + // polymer/observe-js PathObserver.
|
| + // TODO(sigmund): should we also support using checking dynamically for
|
| + // whether the type practically implements the indexer API
|
| + // (smoke.hasInstanceMethod(type, const Symbol('[]')))?
|
| + if (object is Indexable<String, dynamic> ||
|
| + object is Map<String, dynamic> && !_MAP_PROPERTIES.contains(property)) {
|
| + return object[smoke.symbolToName(property)];
|
| + }
|
| try {
|
| - if (smoke.hasGetter(type, property) || smoke.hasNoSuchMethod(type)) {
|
| - return smoke.read(object, property);
|
| - }
|
| - // Support indexer if available, e.g. Maps or polymer_expressions Scope.
|
| - // This is the default syntax used by polymer/nodebind and
|
| - // polymer/observe-js PathObserver.
|
| - if (smoke.hasInstanceMethod(type, const Symbol('[]'))) {
|
| - return object[smoke.symbolToName(property)];
|
| - }
|
| + return smoke.read(object, property);
|
| } on NoSuchMethodError catch (e) {
|
| // Rethrow, unless the type implements noSuchMethod, in which case we
|
| // interpret the exception as a signal that the method was not found.
|
| - if (!smoke.hasNoSuchMethod(type)) rethrow;
|
| + // Dart note: getting invalid properties is an error, unlike in JS where
|
| + // it returns undefined.
|
| + if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow;
|
| }
|
| }
|
|
|
| @@ -274,19 +287,17 @@ bool _setObjectProperty(object, property, value) {
|
| return true;
|
| }
|
| } else if (property is Symbol) {
|
| - final type = object.runtimeType;
|
| + // Support indexer if available, e.g. Maps or polymer_expressions Scope.
|
| + if (object is Indexable<String, dynamic> ||
|
| + object is Map<String, dynamic> && !_MAP_PROPERTIES.contains(property)) {
|
| + object[smoke.symbolToName(property)] = value;
|
| + return true;
|
| + }
|
| try {
|
| - if (smoke.hasSetter(type, property) || smoke.hasNoSuchMethod(type)) {
|
| - smoke.write(object, property, value);
|
| - return true;
|
| - }
|
| - // Support indexer if available, e.g. Maps or polymer_expressions Scope.
|
| - if (smoke.hasInstanceMethod(type, const Symbol('[]='))) {
|
| - object[smoke.symbolToName(property)] = value;
|
| - return true;
|
| - }
|
| - } on NoSuchMethodError catch (e) {
|
| - if (!smoke.hasNoSuchMethod(type)) rethrow;
|
| + smoke.write(object, property, value);
|
| + return true;
|
| + } on NoSuchMethodError catch (e, s) {
|
| + if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow;
|
| }
|
| }
|
|
|
| @@ -470,6 +481,13 @@ class CompoundObserver extends _Observer implements Bindable {
|
| }
|
| }
|
|
|
| +/// An object accepted by [PropertyPath] where properties are read and written
|
| +/// as indexing operations, just like a [Map].
|
| +abstract class Indexable<K, V> {
|
| + V operator [](K key);
|
| + operator []=(K key, V value);
|
| +}
|
| +
|
| const _observerSentinel = const _ObserverSentinel();
|
| class _ObserverSentinel { const _ObserverSentinel(); }
|
|
|
|
|