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

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
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 observe; 5 part of observe;
6 6
7 // This code is inspired by ChangeSummary: 7 // This code is inspired by ChangeSummary:
8 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js 8 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
9 // ...which underlies MDV. Since we don't need the functionality of 9 // ...which underlies MDV. Since we don't need the functionality of
10 // ChangeSummary, we just implement what we need for data bindings. 10 // ChangeSummary, we just implement what we need for data bindings.
11 // This allows our implementation to be much simpler. 11 // This allows our implementation to be much simpler.
12 12
13 /** 13 /**
14 * A data-bound path starting from a view-model or model object, for example 14 * A data-bound path starting from a view-model or model object, for example
15 * `foo.bar.baz`. 15 * `foo.bar.baz`.
16 * 16 *
17 * When the [values] stream is being listened to, this will observe changes to 17 * When the [values] stream is being listened to, this will observe changes to
18 * the object and any intermediate object along the path, and send [values] 18 * the object and any intermediate object along the path, and send [values]
19 * accordingly. When all listeners are unregistered it will stop observing 19 * accordingly. When all listeners are unregistered it will stop observing
20 * the objects. 20 * the objects.
21 * 21 *
22 * This class is used to implement [Node.bind] and similar functionality. 22 * This class is used to implement [Node.bind] and similar functionality.
23 */ 23 */
24 // TODO(jmesserly): consider specialized subclasses for:
25 // * single token in path, e.g. "foo"
26 // * specifically: "value"
Siggi Cherem (dart-lang) 2013/10/29 21:00:07 +1
Jennifer Messerly 2013/10/29 22:35:07 Done.
24 class PathObserver extends ChangeNotifier { 27 class PathObserver extends ChangeNotifier {
25 /** The path string. */ 28 /** The path string. */
26 final String path; 29 final String path;
27 30
28 /** True if the path is valid, otherwise false. */ 31 /** True if the path is valid, otherwise false. */
29 final bool _isValid; 32 final bool _isValid;
30 33
31 final List<Object> _segments; 34 final List<Object> _segments;
32 List<Object> _values; 35 List<Object> _values;
33 List<StreamSubscription> _subs; 36 List<StreamSubscription> _subs;
34 37
38 // TODO(jmesserly): I'm not sure about these APIs. They do increase
39 // performance but at the cost of complexity and perhaps having too much
40 // functionality inside PathObserver.
Siggi Cherem (dart-lang) 2013/10/29 21:00:07 I think it's worth it. IIUC, if we have a compile-
Jennifer Messerly 2013/10/29 22:35:07 agree. removed the TODO. original it was getValue/
41 final Function _getValue;
42
35 /** 43 /**
36 * Observes [path] on [object] for changes. This returns an object that can be 44 * Observes [path] on [object] for changes. This returns an object that can be
37 * used to get the changes and get/set the value at this path. 45 * used to get the changes and get/set the value at this path.
38 * See [PathObserver.bindSync] and [PathObserver.value]. 46 * See [PathObserver.bindSync] and [PathObserver.value].
Siggi Cherem (dart-lang) 2013/10/29 21:00:07 add short description for getValue?
Jennifer Messerly 2013/10/29 22:35:07 Done.
39 */ 47 */
40 PathObserver(Object object, String path) 48 PathObserver(Object object, String path, {getValue(x)})
41 : path = path, 49 : path = path,
50 _getValue = getValue,
42 _isValid = _isPathValid(path), 51 _isValid = _isPathValid(path),
43 _segments = <Object>[] { 52 _segments = <Object>[] {
44 53
45 if (_isValid) { 54 if (_isValid) {
46 for (var segment in path.trim().split('.')) { 55 for (var segment in path.trim().split('.')) {
47 if (segment == '') continue; 56 if (segment == '') continue;
48 var index = int.parse(segment, radix: 10, onError: (_) => null); 57 var index = int.parse(segment, radix: 10, onError: (_) => null);
49 _segments.add(index != null ? index : new Symbol(segment)); 58 _segments.add(index != null ? index : new Symbol(segment));
50 } 59 }
51 } 60 }
52 61
53 // Initialize arrays. 62 // Initialize arrays.
54 // Note that the path itself can't change after it is initially 63 // Note that the path itself can't change after it is initially
55 // constructed, even though the objects along the path can change. 64 // constructed, even though the objects along the path can change.
56 _values = new List<Object>(_segments.length + 1); 65 _values = new List<Object>(_segments.length + 1);
66
67 if (_segments.isEmpty && getValue != null) object = getValue(object);
Siggi Cherem (dart-lang) 2013/10/29 21:00:07 reading this and the lines below it's a bit confus
Jennifer Messerly 2013/10/29 22:35:07 tried to clarify in a comment. changed argument na
57 _values[0] = object; 68 _values[0] = object;
58 _subs = new List<StreamSubscription>(_segments.length); 69 _subs = new List<StreamSubscription>(_segments.length);
59 } 70 }
60 71
61 /** The object being observed. */ 72 /** The object being observed. */
62 get object => _values[0]; 73 get object => _values[0];
63 74
64 /** Gets the last reported value at this path. */ 75 /** Gets the last reported value at this path. */
65 @reflectable get value { 76 @reflectable get value {
66 if (!_isValid) return null; 77 if (!_isValid) return null;
67 if (!hasObservers) _updateValues(); 78 if (!hasObservers) _updateValues();
68 return _values.last; 79 return _values.last;
69 } 80 }
70 81
71 /** Sets the value at this path. */ 82 /** Sets the value at this path. */
72 @reflectable void set value(Object value) { 83 @reflectable void set value(Object newValue) {
73 int len = _segments.length; 84 int len = _segments.length;
74 85
75 // TODO(jmesserly): throw if property cannot be set? 86 // TODO(jmesserly): throw if property cannot be set?
76 // MDV seems tolerant of these errors. 87 // MDV seems tolerant of these errors.
77 if (len == 0) return; 88 if (len == 0) return;
78 if (!hasObservers) _updateValues(); 89 if (!hasObservers) _updateValues();
79 90
80 if (_setObjectProperty(_values[len - 1], _segments[len - 1], value)) { 91 if (_setObjectProperty(_values[len - 1], _segments[len - 1], newValue)) {
81 // Technically, this would get updated asynchronously via a change record. 92 // Technically, this would get updated asynchronously via a change record.
82 // However, it is nice if calling the getter will yield the same value 93 // However, it is nice if calling the getter will yield the same value
83 // that was just set. So we use this opportunity to update our cache. 94 // that was just set. So we use this opportunity to update our cache.
84 _values[len] = value; 95 _values[len] = newValue;
85 } 96 }
86 } 97 }
87 98
88 /** 99 /**
89 * Invokes the [callback] immediately with the current [value], and every time 100 * Invokes the [callback] immediately with the current [value], and every time
90 * the value changes. This is useful for bindings, which want to be up-to-date 101 * the value changes. This is useful for bindings, which want to be up-to-date
91 * immediately and stay bound to the value of the path. 102 * immediately and stay bound to the value of the path.
92 */ 103 */
93 StreamSubscription bindSync(void callback(value)) { 104 StreamSubscription bindSync(void callback(value)) {
94 var result = changes.listen((records) { callback(value); }); 105 var result = changes.listen((records) { callback(value); });
(...skipping 11 matching lines...) Expand all
106 for (int i = 0; i < _subs.length; i++) { 117 for (int i = 0; i < _subs.length; i++) {
107 if (_subs[i] != null) { 118 if (_subs[i] != null) {
108 _subs[i].cancel(); 119 _subs[i].cancel();
109 _subs[i] = null; 120 _subs[i] = null;
110 } 121 }
111 } 122 }
112 } 123 }
113 124
114 // TODO(jmesserly): should we be caching these values if not observing? 125 // TODO(jmesserly): should we be caching these values if not observing?
115 void _updateValues() { 126 void _updateValues() {
116 for (int i = 0; i < _segments.length; i++) { 127 for (int i = 0, last = _segments.length - 1; i <= last; i++) {
117 _values[i + 1] = _getObjectProperty(_values[i], _segments[i]); 128 var newValue = _getObjectProperty(_values[i], _segments[i]);
129 if (i == last && _getValue != null) newValue = _getValue(newValue);
130 _values[i + 1] = newValue;
118 } 131 }
119 } 132 }
120 133
121 void _updateObservedValues([int start = 0]) { 134 void _updateObservedValues([int start = 0]) {
122 var oldValue, newValue; 135 var oldValue, newValue;
123 for (int i = start; i < _segments.length; i++) { 136 for (int i = start, last = _segments.length - 1; i <= last; i++) {
124 oldValue = _values[i + 1]; 137 oldValue = _values[i + 1];
125 newValue = _getObjectProperty(_values[i], _segments[i]); 138 newValue = _getObjectProperty(_values[i], _segments[i]);
139 if (i == last && _getValue != null) newValue = _getValue(newValue);
126 if (identical(oldValue, newValue)) { 140 if (identical(oldValue, newValue)) {
127 _observePath(start, i); 141 _observePath(start, i);
128 return; 142 return;
129 } 143 }
130 _values[i + 1] = newValue; 144 _values[i + 1] = newValue;
131 } 145 }
132 146
133 _observePath(start); 147 _observePath(start);
134 notifyPropertyChange(#value, oldValue, newValue); 148 notifyPropertyChange(#value, oldValue, newValue);
135 } 149 }
136 150
137 void _observePath([int start = 0, int end]) { 151 void _observePath([int start = 0, int end]) {
138 if (end == null) end = _segments.length; 152 if (end == null) end = _segments.length;
139 153
140 for (int i = start; i < end; i++) { 154 for (int i = start; i < end; i++) {
141 if (_subs[i] != null) _subs[i].cancel(); 155 if (_subs[i] != null) _subs[i].cancel();
142 _observeIndex(i); 156 _observeIndex(i);
143 } 157 }
144 } 158 }
145 159
146 void _observeIndex(int i) { 160 void _observeIndex(int i) {
147 final object = _values[i]; 161 final object = _values[i];
148 if (object is Observable) { 162 if (object is Observable) {
149 // TODO(jmesserly): rather than allocating a new closure for each 163 // TODO(jmesserly): rather than allocating a new closure for each
150 // property, we could try and have one for the entire path. In that case, 164 // property, we could try and have one for the entire path. However we'd
151 // we would lose information about which object changed (note: unless 165 // need to do a linear scan to find the index as soon as we got a change.
152 // PropertyChangeRecord is modified to includes the sender object), so 166 // Also we need to fix ListChangeRecord and MapChangeRecord to contain
153 // we would need to re-evaluate the entire path. Need to evaluate perf. 167 // the target. Not sure if it's worth it.
154 _subs[i] = object.changes.listen((List<ChangeRecord> records) { 168 _subs[i] = object.changes.listen((List<ChangeRecord> records) {
155 if (!identical(_values[i], object)) {
156 // Ignore this object if we're now tracking something else.
157 return;
158 }
159
160 for (var record in records) { 169 for (var record in records) {
161 if (_changeRecordMatches(record, _segments[i])) { 170 if (_changeRecordMatches(record, _segments[i])) {
162 _updateObservedValues(i); 171 _updateObservedValues(i);
163 return; 172 return;
164 } 173 }
165 } 174 }
166 }); 175 });
167 } 176 }
168 } 177 }
169 } 178 }
(...skipping 24 matching lines...) Expand all
194 return null; 203 return null;
195 } 204 }
196 } 205 }
197 206
198 if (property is Symbol) { 207 if (property is Symbol) {
199 var mirror = reflect(object); 208 var mirror = reflect(object);
200 var result = _tryGetField(mirror, property); 209 var result = _tryGetField(mirror, property);
201 if (result != null) return result.reflectee; 210 if (result != null) return result.reflectee;
202 } 211 }
203 212
213 // TODO(jmesserly): need to fix NoSuchMethodErrors from _tryGetField; right
214 // now it's painful to debug (and probably really slow) to use maps.
204 if (object is Map) { 215 if (object is Map) {
205 if (property is Symbol) property = MirrorSystem.getName(property); 216 if (property is Symbol) property = MirrorSystem.getName(property);
206 return object[property]; 217 return object[property];
207 } 218 }
208 219
220 // TODO(jmesserly): log a binding that fails?
209 return null; 221 return null;
210 } 222 }
211 223
212 bool _setObjectProperty(object, property, value) { 224 bool _setObjectProperty(object, property, value) {
213 if (object is List && property is int) { 225 if (object is List && property is int) {
214 if (property >= 0 && property < object.length) { 226 if (property >= 0 && property < object.length) {
215 object[property] = value; 227 object[property] = value;
216 return true; 228 return true;
217 } else { 229 } else {
218 return false; 230 return false;
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
300 312
301 final _spacesRegExp = new RegExp(r'\s'); 313 final _spacesRegExp = new RegExp(r'\s');
302 314
303 bool _isPathValid(String s) { 315 bool _isPathValid(String s) {
304 s = s.replaceAll(_spacesRegExp, ''); 316 s = s.replaceAll(_spacesRegExp, '');
305 317
306 if (s == '') return true; 318 if (s == '') return true;
307 if (s[0] == '.') return false; 319 if (s[0] == '.') return false;
308 return _pathRegExp.hasMatch(s); 320 return _pathRegExp.hasMatch(s);
309 } 321 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698