| 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 f99fed98c652c6980ef92d595d171a139c644aba..6384f6ee4735851af401ca70b290b348f9a9b7d0 100644
|
| --- a/pkg/observe/lib/src/path_observer.dart
|
| +++ b/pkg/observe/lib/src/path_observer.dart
|
| @@ -29,6 +29,10 @@ import 'package:observe/src/observable.dart' show objectType;
|
| *
|
| * This class is used to implement [Node.bind] and similar functionality.
|
| */
|
| +// TODO(jmesserly): consider specialized subclasses for:
|
| +// * empty path
|
| +// * "value"
|
| +// * single token in path, e.g. "foo"
|
| class PathObserver extends ChangeNotifier {
|
| /** The path string. */
|
| final String path;
|
| @@ -40,13 +44,22 @@ class PathObserver extends ChangeNotifier {
|
| List<Object> _values;
|
| List<StreamSubscription> _subs;
|
|
|
| + final Function _computeValue;
|
| +
|
| /**
|
| * Observes [path] on [object] for changes. This returns an object that can be
|
| * used to get the changes and get/set the value at this path.
|
| + *
|
| + * You can optionally use [computeValue] to apply a function to the result of
|
| + * evaluating the path. The function should be pure, as PathObserver will not
|
| + * know to observe any of its dependencies. If you need to observe mutliple
|
| + * values, use [CompoundPathObserver] instead.
|
| + *
|
| * See [PathObserver.bindSync] and [PathObserver.value].
|
| */
|
| - PathObserver(Object object, String path)
|
| + PathObserver(Object object, String path, {computeValue(newValue)})
|
| : path = path,
|
| + _computeValue = computeValue,
|
| _isValid = _isPathValid(path),
|
| _segments = <Object>[] {
|
|
|
| @@ -62,11 +75,19 @@ class PathObserver extends ChangeNotifier {
|
| // Note that the path itself can't change after it is initially
|
| // constructed, even though the objects along the path can change.
|
| _values = new List<Object>(_segments.length + 1);
|
| +
|
| + // If we have an empty path, we need to apply the transformation function
|
| + // to the value. The "value" property should always show the transformed
|
| + // value.
|
| + if (_segments.isEmpty && computeValue != null) {
|
| + object = computeValue(object);
|
| + }
|
| +
|
| _values[0] = object;
|
| _subs = new List<StreamSubscription>(_segments.length);
|
| }
|
|
|
| - /** The object being observed. */
|
| + /** The object being observed. If the path is empty this will be [value]. */
|
| get object => _values[0];
|
|
|
| /** Gets the last reported value at this path. */
|
| @@ -77,7 +98,7 @@ class PathObserver extends ChangeNotifier {
|
| }
|
|
|
| /** Sets the value at this path. */
|
| - @reflectable void set value(Object value) {
|
| + @reflectable void set value(Object newValue) {
|
| int len = _segments.length;
|
|
|
| // TODO(jmesserly): throw if property cannot be set?
|
| @@ -85,11 +106,11 @@ class PathObserver extends ChangeNotifier {
|
| if (len == 0) return;
|
| if (!hasObservers) _updateValues(end: len - 1);
|
|
|
| - if (_setObjectProperty(_values[len - 1], _segments[len - 1], value)) {
|
| + if (_setObjectProperty(_values[len - 1], _segments[len - 1], newValue)) {
|
| // Technically, this would get updated asynchronously via a change record.
|
| // However, it is nice if calling the getter will yield the same value
|
| // that was just set. So we use this opportunity to update our cache.
|
| - _values[len] = value;
|
| + _values[len] = newValue;
|
| }
|
| }
|
|
|
| @@ -123,16 +144,24 @@ class PathObserver extends ChangeNotifier {
|
| // TODO(jmesserly): should we be caching these values if not observing?
|
| void _updateValues({int end}) {
|
| if (end == null) end = _segments.length;
|
| + int last = _segments.length - 1;
|
| for (int i = 0; i < end; i++) {
|
| - _values[i + 1] = _getObjectProperty(_values[i], _segments[i]);
|
| + var newValue = _getObjectProperty(_values[i], _segments[i]);
|
| + if (i == last && _computeValue != null) {
|
| + newValue = _computeValue(newValue);
|
| + }
|
| + _values[i + 1] = newValue;
|
| }
|
| }
|
|
|
| void _updateObservedValues({int start: 0}) {
|
| var oldValue, newValue;
|
| - for (int i = start; i < _segments.length; i++) {
|
| + for (int i = start, last = _segments.length - 1; i <= last; i++) {
|
| oldValue = _values[i + 1];
|
| newValue = _getObjectProperty(_values[i], _segments[i]);
|
| + if (i == last && _computeValue != null) {
|
| + newValue = _computeValue(newValue);
|
| + }
|
| if (identical(oldValue, newValue)) {
|
| _observePath(start, i);
|
| return;
|
| @@ -157,16 +186,11 @@ class PathObserver extends ChangeNotifier {
|
| final object = _values[i];
|
| if (object is Observable) {
|
| // TODO(jmesserly): rather than allocating a new closure for each
|
| - // property, we could try and have one for the entire path. In that case,
|
| - // we would lose information about which object changed (note: unless
|
| - // PropertyChangeRecord is modified to includes the sender object), so
|
| - // we would need to re-evaluate the entire path. Need to evaluate perf.
|
| + // property, we could try and have one for the entire path. However we'd
|
| + // need to do a linear scan to find the index as soon as we got a change.
|
| + // Also we need to fix ListChangeRecord and MapChangeRecord to contain
|
| + // the target. Not sure if it's worth it.
|
| _subs[i] = object.changes.listen((List<ChangeRecord> records) {
|
| - if (!identical(_values[i], object)) {
|
| - // Ignore this object if we're now tracking something else.
|
| - return;
|
| - }
|
| -
|
| for (var record in records) {
|
| if (_changeRecordMatches(record, _segments[i])) {
|
| _updateObservedValues(start: i);
|
|
|