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

Side by Side Diff: pkg/observe/lib/src/path_observer.dart

Issue 50203004: port TemplateBinding to ed3266266e751b5ab1f75f8e0509d0d5f0ef35d8 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 1 month 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/observe/lib/src/compound_path_observer.dart ('k') | pkg/pkg.status » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 library observe.src.path_observer; 5 library observe.src.path_observer;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty], 8 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty],
9 override: 'observe.src.path_observer') 9 override: 'observe.src.path_observer')
10 import 'dart:mirrors'; 10 import 'dart:mirrors';
(...skipping 11 matching lines...) Expand all
22 * A data-bound path starting from a view-model or model object, for example 22 * A data-bound path starting from a view-model or model object, for example
23 * `foo.bar.baz`. 23 * `foo.bar.baz`.
24 * 24 *
25 * When the [values] stream is being listened to, this will observe changes to 25 * When the [values] stream is being listened to, this will observe changes to
26 * the object and any intermediate object along the path, and send [values] 26 * the object and any intermediate object along the path, and send [values]
27 * accordingly. When all listeners are unregistered it will stop observing 27 * accordingly. When all listeners are unregistered it will stop observing
28 * the objects. 28 * the objects.
29 * 29 *
30 * This class is used to implement [Node.bind] and similar functionality. 30 * This class is used to implement [Node.bind] and similar functionality.
31 */ 31 */
32 // TODO(jmesserly): consider specialized subclasses for:
33 // * empty path
34 // * "value"
35 // * single token in path, e.g. "foo"
32 class PathObserver extends ChangeNotifier { 36 class PathObserver extends ChangeNotifier {
33 /** The path string. */ 37 /** The path string. */
34 final String path; 38 final String path;
35 39
36 /** True if the path is valid, otherwise false. */ 40 /** True if the path is valid, otherwise false. */
37 final bool _isValid; 41 final bool _isValid;
38 42
39 final List<Object> _segments; 43 final List<Object> _segments;
40 List<Object> _values; 44 List<Object> _values;
41 List<StreamSubscription> _subs; 45 List<StreamSubscription> _subs;
42 46
47 final Function _computeValue;
48
43 /** 49 /**
44 * Observes [path] on [object] for changes. This returns an object that can be 50 * Observes [path] on [object] for changes. This returns an object that can be
45 * used to get the changes and get/set the value at this path. 51 * used to get the changes and get/set the value at this path.
52 *
53 * You can optionally use [computeValue] to apply a function to the result of
54 * evaluating the path. The function should be pure, as PathObserver will not
55 * know to observe any of its dependencies. If you need to observe mutliple
56 * values, use [CompoundPathObserver] instead.
57 *
46 * See [PathObserver.bindSync] and [PathObserver.value]. 58 * See [PathObserver.bindSync] and [PathObserver.value].
47 */ 59 */
48 PathObserver(Object object, String path) 60 PathObserver(Object object, String path, {computeValue(newValue)})
49 : path = path, 61 : path = path,
62 _computeValue = computeValue,
50 _isValid = _isPathValid(path), 63 _isValid = _isPathValid(path),
51 _segments = <Object>[] { 64 _segments = <Object>[] {
52 65
53 if (_isValid) { 66 if (_isValid) {
54 for (var segment in path.trim().split('.')) { 67 for (var segment in path.trim().split('.')) {
55 if (segment == '') continue; 68 if (segment == '') continue;
56 var index = int.parse(segment, radix: 10, onError: (_) => null); 69 var index = int.parse(segment, radix: 10, onError: (_) => null);
57 _segments.add(index != null ? index : new Symbol(segment)); 70 _segments.add(index != null ? index : new Symbol(segment));
58 } 71 }
59 } 72 }
60 73
61 // Initialize arrays. 74 // Initialize arrays.
62 // Note that the path itself can't change after it is initially 75 // Note that the path itself can't change after it is initially
63 // constructed, even though the objects along the path can change. 76 // constructed, even though the objects along the path can change.
64 _values = new List<Object>(_segments.length + 1); 77 _values = new List<Object>(_segments.length + 1);
78
79 // If we have an empty path, we need to apply the transformation function
80 // to the value. The "value" property should always show the transformed
81 // value.
82 if (_segments.isEmpty && computeValue != null) object = computeValue(object) ;
Siggi Cherem (dart-lang) 2013/10/29 23:20:19 80 (below too) :-(
Jennifer Messerly 2013/10/29 23:26:04 Done.
83
65 _values[0] = object; 84 _values[0] = object;
66 _subs = new List<StreamSubscription>(_segments.length); 85 _subs = new List<StreamSubscription>(_segments.length);
67 } 86 }
68 87
69 /** The object being observed. */ 88 /** The object being observed. If the path is empty this will be [value]. */
70 get object => _values[0]; 89 get object => _values[0];
71 90
72 /** Gets the last reported value at this path. */ 91 /** Gets the last reported value at this path. */
73 @reflectable get value { 92 @reflectable get value {
74 if (!_isValid) return null; 93 if (!_isValid) return null;
75 if (!hasObservers) _updateValues(); 94 if (!hasObservers) _updateValues();
76 return _values.last; 95 return _values.last;
77 } 96 }
78 97
79 /** Sets the value at this path. */ 98 /** Sets the value at this path. */
80 @reflectable void set value(Object value) { 99 @reflectable void set value(Object newValue) {
81 int len = _segments.length; 100 int len = _segments.length;
82 101
83 // TODO(jmesserly): throw if property cannot be set? 102 // TODO(jmesserly): throw if property cannot be set?
84 // MDV seems tolerant of these errors. 103 // MDV seems tolerant of these errors.
85 if (len == 0) return; 104 if (len == 0) return;
86 if (!hasObservers) _updateValues(end: len - 1); 105 if (!hasObservers) _updateValues(end: len - 1);
87 106
88 if (_setObjectProperty(_values[len - 1], _segments[len - 1], value)) { 107 if (_setObjectProperty(_values[len - 1], _segments[len - 1], newValue)) {
89 // Technically, this would get updated asynchronously via a change record. 108 // Technically, this would get updated asynchronously via a change record.
90 // However, it is nice if calling the getter will yield the same value 109 // However, it is nice if calling the getter will yield the same value
91 // that was just set. So we use this opportunity to update our cache. 110 // that was just set. So we use this opportunity to update our cache.
92 _values[len] = value; 111 _values[len] = newValue;
93 } 112 }
94 } 113 }
95 114
96 /** 115 /**
97 * Invokes the [callback] immediately with the current [value], and every time 116 * Invokes the [callback] immediately with the current [value], and every time
98 * the value changes. This is useful for bindings, which want to be up-to-date 117 * the value changes. This is useful for bindings, which want to be up-to-date
99 * immediately and stay bound to the value of the path. 118 * immediately and stay bound to the value of the path.
100 */ 119 */
101 StreamSubscription bindSync(void callback(value)) { 120 StreamSubscription bindSync(void callback(value)) {
102 var result = changes.listen((records) { callback(value); }); 121 var result = changes.listen((records) { callback(value); });
(...skipping 13 matching lines...) Expand all
116 _subs[i].cancel(); 135 _subs[i].cancel();
117 _subs[i] = null; 136 _subs[i] = null;
118 } 137 }
119 } 138 }
120 super.unobserved(); 139 super.unobserved();
121 } 140 }
122 141
123 // TODO(jmesserly): should we be caching these values if not observing? 142 // TODO(jmesserly): should we be caching these values if not observing?
124 void _updateValues({int end}) { 143 void _updateValues({int end}) {
125 if (end == null) end = _segments.length; 144 if (end == null) end = _segments.length;
145 int last = _segments.length - 1;
126 for (int i = 0; i < end; i++) { 146 for (int i = 0; i < end; i++) {
127 _values[i + 1] = _getObjectProperty(_values[i], _segments[i]); 147 var newValue = _getObjectProperty(_values[i], _segments[i]);
148 if (i == last && _computeValue != null) newValue = _computeValue(newValue) ;
149 _values[i + 1] = newValue;
128 } 150 }
129 } 151 }
130 152
131 void _updateObservedValues({int start: 0}) { 153 void _updateObservedValues({int start: 0}) {
132 var oldValue, newValue; 154 var oldValue, newValue;
133 for (int i = start; i < _segments.length; i++) { 155 for (int i = start, last = _segments.length - 1; i <= last; i++) {
134 oldValue = _values[i + 1]; 156 oldValue = _values[i + 1];
135 newValue = _getObjectProperty(_values[i], _segments[i]); 157 newValue = _getObjectProperty(_values[i], _segments[i]);
158 if (i == last && _computeValue != null) newValue = _computeValue(newValue) ;
136 if (identical(oldValue, newValue)) { 159 if (identical(oldValue, newValue)) {
137 _observePath(start, i); 160 _observePath(start, i);
138 return; 161 return;
139 } 162 }
140 _values[i + 1] = newValue; 163 _values[i + 1] = newValue;
141 } 164 }
142 165
143 _observePath(start); 166 _observePath(start);
144 notifyPropertyChange(#value, oldValue, newValue); 167 notifyPropertyChange(#value, oldValue, newValue);
145 } 168 }
146 169
147 void _observePath([int start = 0, int end]) { 170 void _observePath([int start = 0, int end]) {
148 if (end == null) end = _segments.length; 171 if (end == null) end = _segments.length;
149 172
150 for (int i = start; i < end; i++) { 173 for (int i = start; i < end; i++) {
151 if (_subs[i] != null) _subs[i].cancel(); 174 if (_subs[i] != null) _subs[i].cancel();
152 _observeIndex(i); 175 _observeIndex(i);
153 } 176 }
154 } 177 }
155 178
156 void _observeIndex(int i) { 179 void _observeIndex(int i) {
157 final object = _values[i]; 180 final object = _values[i];
158 if (object is Observable) { 181 if (object is Observable) {
159 // TODO(jmesserly): rather than allocating a new closure for each 182 // TODO(jmesserly): rather than allocating a new closure for each
160 // property, we could try and have one for the entire path. In that case, 183 // property, we could try and have one for the entire path. However we'd
161 // we would lose information about which object changed (note: unless 184 // need to do a linear scan to find the index as soon as we got a change.
162 // PropertyChangeRecord is modified to includes the sender object), so 185 // Also we need to fix ListChangeRecord and MapChangeRecord to contain
163 // we would need to re-evaluate the entire path. Need to evaluate perf. 186 // the target. Not sure if it's worth it.
164 _subs[i] = object.changes.listen((List<ChangeRecord> records) { 187 _subs[i] = object.changes.listen((List<ChangeRecord> records) {
165 if (!identical(_values[i], object)) {
166 // Ignore this object if we're now tracking something else.
167 return;
168 }
169
170 for (var record in records) { 188 for (var record in records) {
171 if (_changeRecordMatches(record, _segments[i])) { 189 if (_changeRecordMatches(record, _segments[i])) {
172 _updateObservedValues(start: i); 190 _updateObservedValues(start: i);
173 return; 191 return;
174 } 192 }
175 } 193 }
176 }); 194 });
177 } 195 }
178 } 196 }
179 } 197 }
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after
323 341
324 bool _isPathValid(String s) { 342 bool _isPathValid(String s) {
325 s = s.replaceAll(_spacesRegExp, ''); 343 s = s.replaceAll(_spacesRegExp, '');
326 344
327 if (s == '') return true; 345 if (s == '') return true;
328 if (s[0] == '.') return false; 346 if (s[0] == '.') return false;
329 return _pathRegExp.hasMatch(s); 347 return _pathRegExp.hasMatch(s);
330 } 348 }
331 349
332 final _logger = new Logger('observe.PathObserver'); 350 final _logger = new Logger('observe.PathObserver');
OLDNEW
« no previous file with comments | « pkg/observe/lib/src/compound_path_observer.dart ('k') | pkg/pkg.status » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698