| 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 |