OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 /** | 5 /** |
6 * A library to observe changes on Dart objects. | 6 * A library to observe changes on Dart objects. |
7 * | 7 * |
8 * Similar to the principle of watchers in AngularJS, this library provides the | 8 * Similar to the principle of watchers in AngularJS, this library provides the |
9 * mechanisms to observe and react to changes that happen in an application's | 9 * mechanisms to observe and react to changes that happen in an application's |
10 * data model. | 10 * data model. |
(...skipping 29 matching lines...) Expand all Loading... |
40 * You can watch several kinds of expressions, including lists. See [watch] for | 40 * You can watch several kinds of expressions, including lists. See [watch] for |
41 * more details. | 41 * more details. |
42 * | 42 * |
43 * A common design pattern for MVC applications is to call [dispatch] at the end | 43 * A common design pattern for MVC applications is to call [dispatch] at the end |
44 * of each event loop (e.g. after each UI event is fired). Our view library does | 44 * of each event loop (e.g. after each UI event is fired). Our view library does |
45 * this automatically. | 45 * this automatically. |
46 */ | 46 */ |
47 library watcher; | 47 library watcher; |
48 | 48 |
49 import 'dart:async'; | 49 import 'dart:async'; |
| 50 import 'observe.dart'; |
| 51 |
| 52 /** |
| 53 * True to use the [observe] library instead of watchers. |
| 54 * |
| 55 * Observers require the [observable] annotation on objects and for collection |
| 56 * types to be observable, such as [ObservableList]. But in return they offer |
| 57 * better performance and more precise change tracking. [dispatch] is not |
| 58 * required with observers, and changes to observable objects are always |
| 59 * detected. |
| 60 * |
| 61 * Currently this flag is experimental, but it may be the default in the future. |
| 62 */ |
| 63 bool useObservers = false; |
50 | 64 |
51 /** | 65 /** |
52 * Watch for changes in [target]. The [callback] function will be called when | 66 * Watch for changes in [target]. The [callback] function will be called when |
53 * [dispatch] is called and the value represented by [target] had changed. The | 67 * [dispatch] is called and the value represented by [target] had changed. The |
54 * returned function can be used to unregister this watcher. | 68 * returned function can be used to unregister this watcher. |
55 * | 69 * |
56 * There are several values you can use for [target]: | 70 * There are several values you can use for [target]: |
57 * | 71 * |
58 * * A [Getter] function. | 72 * * A [Getter] function. |
59 * Use this to watch expressions as they change. For instance, to watch | 73 * Use this to watch expressions as they change. For instance, to watch |
60 * whether `a.b.c` changes, wrap it in a getter and call [watch] as follows: | 74 * whether `a.b.c` changes, wrap it in a getter and call [watch] as follows: |
61 * watch(() => a.b.c, ...) | 75 * watch(() => a.b.c, ...) |
62 * These targets are tracked to check for equality. If calling `target()` | 76 * These targets are tracked to check for equality. If calling `target()` |
63 * returns the same result, then the [callback] will not be invoked. In the | 77 * returns the same result, then the [callback] will not be invoked. In the |
64 * special case whe the getter returns a [List], we will treat the value in a | 78 * special case whe the getter returns a [List], we will treat the value in a |
65 * special way, similar to passing [List] directly as [target]. | 79 * special way, similar to passing [List] directly as [target]. |
66 * **Important**: this library assumes that [Getter] is a read-only function | 80 * **Important**: this library assumes that [Getter] is a read-only function |
67 * and that it will consistently return the same value if called multiple | 81 * and that it will consistently return the same value if called multiple |
68 * times in a row. | 82 * times in a row. |
69 * | 83 * |
70 * * A [List]. | 84 * * A [List]. |
71 * Use this to watch whether a list object changes. For instance, to detect | 85 * Use this to watch whether a list object changes. For instance, to detect |
72 * if an element is added or changed in a list can call [watch] as follows: | 86 * if an element is added or changed in a list can call [watch] as follows: |
73 * watch(list, ...) | 87 * watch(list, ...) |
74 * | 88 * |
75 * * A [Handle]. | 89 * * A [Handle]. |
76 * This is syntactic sugar for using the getter portion of a [Handle]. | 90 * This is syntactic sugar for using the getter portion of a [Handle]. |
77 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)` | 91 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)` |
78 */ | 92 */ |
79 WatcherDisposer watch(var target, ValueWatcher callback, [String debugName]) { | 93 ChangeUnobserver watch(target, ExpressionObserver callback, |
| 94 [String debugName]) { |
| 95 if (useObservers) return observe(target, callback); |
| 96 |
80 if (callback == null) return () {}; // no use in passing null as a callback. | 97 if (callback == null) return () {}; // no use in passing null as a callback. |
81 if (_watchers == null) _watchers = []; | 98 if (_watchers == null) _watchers = []; |
82 Function exp; | 99 Function exp; |
83 bool isList = false; | 100 bool isList = false; |
84 if (target is Handle) { | 101 if (target is Handle) { |
85 exp = (target as Handle)._getter; | 102 exp = (target as Handle)._getter; |
86 } else if (target is Function) { | 103 } else if (target is Function) { |
87 exp = target; | 104 exp = target; |
88 try { | 105 try { |
89 isList = (target() is List); | 106 isList = (target() is List); |
(...skipping 12 matching lines...) Expand all Loading... |
102 : new _Watcher(exp, callback, debugName); | 119 : new _Watcher(exp, callback, debugName); |
103 _watchers.add(watcher); | 120 _watchers.add(watcher); |
104 return () => _unregister(watcher); | 121 return () => _unregister(watcher); |
105 } | 122 } |
106 | 123 |
107 /** | 124 /** |
108 * Add a watcher for [exp] and immediatly invoke [callback]. The watch event | 125 * Add a watcher for [exp] and immediatly invoke [callback]. The watch event |
109 * passed to [callback] will have `null` as the old value, and the current | 126 * passed to [callback] will have `null` as the old value, and the current |
110 * evaluation of [exp] as the new value. | 127 * evaluation of [exp] as the new value. |
111 */ | 128 */ |
112 WatcherDisposer watchAndInvoke(exp, callback, [debugName]) { | 129 ChangeUnobserver watchAndInvoke(exp, callback, [debugName]) { |
113 var res = watch(exp, callback, debugName); | 130 var res = watch(exp, callback, debugName); |
114 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed. | 131 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed. |
115 if (exp is Function) { | 132 if (exp is Function) { |
116 callback(new WatchEvent(null, exp())); | 133 callback(new ExpressionChange(null, exp())); |
117 } else { | 134 } else { |
118 callback(new WatchEvent(null, exp)); | 135 callback(new ExpressionChange(null, exp)); |
119 } | 136 } |
120 return res; | 137 return res; |
121 } | 138 } |
122 | 139 |
123 /** Callback fired when an expression changes. */ | |
124 typedef void ValueWatcher(WatchEvent e); | |
125 | |
126 /** A function that unregisters a watcher. */ | |
127 typedef void WatcherDisposer(); | |
128 | |
129 /** Event passed to [ValueMatcher] showing what changed. */ | |
130 class WatchEvent { | |
131 | |
132 /** Previous value seen on the watched expression. */ | |
133 final oldValue; | |
134 | |
135 /** New value seen on the watched expression. */ | |
136 final newValue; | |
137 | |
138 WatchEvent(this.oldValue, this.newValue); | |
139 } | |
140 | |
141 /** Internal set of active watchers. */ | 140 /** Internal set of active watchers. */ |
142 List<_Watcher> _watchers; | 141 List<_Watcher> _watchers; |
143 | 142 |
144 /** | 143 /** |
145 * An internal representation of a watcher. Contains the expression it watches, | 144 * An internal representation of a watcher. Contains the expression it watches, |
146 * the last value seen for it, and a callback to invoke when a change is | 145 * the last value seen for it, and a callback to invoke when a change is |
147 * detected. | 146 * detected. |
148 */ | 147 */ |
149 class _Watcher { | 148 class _Watcher { |
150 /** Name used to debug. */ | 149 /** Name used to debug. */ |
151 final String debugName; | 150 final String debugName; |
152 | 151 |
153 /** Function that retrieves the value being watched. */ | 152 /** Function that retrieves the value being watched. */ |
154 final Getter _getter; | 153 final Getter _getter; |
155 | 154 |
156 /** Callback to invoke when the value changes. */ | 155 /** Callback to invoke when the value changes. */ |
157 final ValueWatcher _callback; | 156 final ExpressionObserver _callback; |
158 | 157 |
159 /** Last value observed on the matched expression. */ | 158 /** Last value observed on the matched expression. */ |
160 var _lastValue; | 159 var _lastValue; |
161 | 160 |
162 _Watcher(this._getter, this._callback, this.debugName) { | 161 _Watcher(this._getter, this._callback, this.debugName) { |
163 _lastValue = _getter(); | 162 _lastValue = _getter(); |
164 } | 163 } |
165 | 164 |
166 String toString() => debugName == null ? '<unnamed>' : debugName; | 165 String toString() => debugName == null ? '<unnamed>' : debugName; |
167 | 166 |
168 /** Detect if any changes occurred and if so invoke [_callback]. */ | 167 /** Detect if any changes occurred and if so invoke [_callback]. */ |
169 bool compareAndNotify() { | 168 bool compareAndNotify() { |
170 var currentValue = _safeRead(); | 169 var currentValue = _safeRead(); |
171 if (_compare(currentValue)) { | 170 if (_compare(currentValue)) { |
172 var oldValue = _lastValue; | 171 var oldValue = _lastValue; |
173 _update(currentValue); | 172 _update(currentValue); |
174 _callback(new WatchEvent(oldValue, currentValue)); | 173 _callback(new ExpressionChange(oldValue, currentValue)); |
175 return true; | 174 return true; |
176 } | 175 } |
177 return false; | 176 return false; |
178 } | 177 } |
179 | 178 |
180 bool get _hasChanged => _compare(_safeRead()); | 179 bool get _hasChanged => _compare(_safeRead()); |
181 | 180 |
182 void _updateAndNotify() { | 181 void _updateAndNotify() { |
183 var currentValue = _safeRead(); | 182 var currentValue = _safeRead(); |
184 var oldValue = _lastValue; | 183 var oldValue = _lastValue; |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
280 | 279 |
281 void set value(T value) { | 280 void set value(T value) { |
282 if (_setter != null) { | 281 if (_setter != null) { |
283 _setter(value); | 282 _setter(value); |
284 } else { | 283 } else { |
285 throw new Exception('Sorry - this handle has no setter.'); | 284 throw new Exception('Sorry - this handle has no setter.'); |
286 } | 285 } |
287 } | 286 } |
288 } | 287 } |
289 | 288 |
290 /** | 289 /** |
291 * A watcher for list objects. It stores as the last value a shallow copy of the | 290 * A watcher for list objects. It stores as the last value a shallow copy of the |
292 * list as it was when we last detected any changes. | 291 * list as it was when we last detected any changes. |
293 */ | 292 */ |
294 class _ListWatcher<T> extends _Watcher { | 293 class _ListWatcher<T> extends _Watcher { |
295 | 294 |
296 _ListWatcher(getter, ValueWatcher callback, String debugName) | 295 _ListWatcher(getter, ExpressionObserver callback, String debugName) |
297 : super(getter, callback, debugName) { | 296 : super(getter, callback, debugName) { |
298 _update(_safeRead()); | 297 _update(_safeRead()); |
299 } | 298 } |
300 | 299 |
301 bool _compare(List<T> currentValue) { | 300 bool _compare(List<T> currentValue) { |
302 if (_lastValue.length != currentValue.length) return true; | 301 if (_lastValue.length != currentValue.length) return true; |
303 | 302 |
304 for (int i = 0 ; i < _lastValue.length; i++) { | 303 for (int i = 0 ; i < _lastValue.length; i++) { |
305 if (_lastValue[i] != currentValue[i]) return true; | 304 if (_lastValue[i] != currentValue[i]) return true; |
306 } | 305 } |
307 return false; | 306 return false; |
308 } | 307 } |
309 | 308 |
310 void _update(currentValue) { | 309 void _update(currentValue) { |
311 _lastValue = new List<T>.from(currentValue); | 310 _lastValue = new List<T>.from(currentValue); |
312 } | 311 } |
313 } | 312 } |
OLD | NEW |