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