Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 // This library itself is undocumented and not supported for end use. | 5 /** |
| 6 // Because dart:html must use some of this functionality, it has to be available | 6 * *Warning*: this library is experimental, and APIs are subject to change. |
| 7 // via a dart:* library. The public APIs are reexported via package:mdv_observe. | 7 * |
| 8 // Generally we try to keep this library minimal, with utility types and | 8 * This library is used to observe changes to [Observable] types. It also |
| 9 // functions in the package. | 9 * has helpers to implement [Observable] objects. |
| 10 library dart.mdv_observe_impl; | 10 * |
| 11 * For example: | |
| 12 * | |
| 13 * class Monster extends Unit with ObservableMixin { | |
| 14 * int _health = 100; | |
| 15 * get health => _health; | |
| 16 * set health(value) { | |
| 17 * _health = notifyChange(const Symbol('health'), _health, value); | |
| 18 * } | |
| 19 * | |
| 20 * void damage(int amount) { | |
| 21 * print('$this takes $amount damage!'); | |
| 22 * health -= amount; | |
| 23 * } | |
| 24 * | |
| 25 * toString() => 'Monster with $health hit points'; | |
| 26 * } | |
| 27 * | |
| 28 * main() { | |
| 29 * var obj = new Monster(); | |
| 30 * obj.changes.listen((records) { | |
| 31 * print('Changes to $obj were: $records'); | |
| 32 * }); | |
| 33 * // Schedules asynchronous delivery of these changes | |
| 34 * obj.damage(10); | |
| 35 * obj.damage(20); | |
| 36 * print('done!'); | |
| 37 * } | |
| 38 */ | |
| 39 library observe; | |
| 11 | 40 |
| 12 import 'dart:async'; | 41 import 'dart:async'; |
| 13 import 'dart:collection'; | 42 import 'dart:collection'; |
| 43 import 'dart:mirrors'; | |
| 14 | 44 |
| 15 part 'path_observer.dart'; | 45 part 'src/compound_binding.dart'; |
| 46 part 'src/observable_box.dart'; | |
| 47 part 'src/observable_list.dart'; | |
| 48 part 'src/observable_map.dart'; | |
| 49 part 'src/path_observer.dart'; | |
| 16 | 50 |
| 17 /** | 51 /** |
| 18 * Interface representing an observable object. This is used by data in | 52 * Interface representing an observable object. This is used by data in |
| 19 * model-view architectures to notify interested parties of [changes]. | 53 * model-view architectures to notify interested parties of [changes]. |
| 20 * | 54 * |
| 21 * This object does not require any specific technique to implement | 55 * This object does not require any specific technique to implement |
| 22 * observability. | 56 * observability. |
| 23 * | 57 * |
| 24 * You can use [ObservableMixin] as a base class or mixin to implement this. | 58 * You can use [ObservableMixin] as a base class or mixin to implement this. |
| 25 */ | 59 */ |
| 26 abstract class Observable { | 60 abstract class Observable { |
| 27 /** | 61 /** |
| 28 * The stream of change records to this object. | 62 * The stream of change records to this object. |
| 29 * | 63 * |
| 30 * Changes should be delivered in asynchronous batches by calling | 64 * Changes should be delivered in asynchronous batches by calling |
| 31 * [queueChangeRecords]. | 65 * [queueChangeRecords]. |
| 32 * | 66 * |
| 33 * [deliverChangeRecords] can be called to force delivery. | 67 * [deliverChangeRecords] can be called to force delivery. |
| 34 */ | 68 */ |
| 35 Stream<List<ChangeRecord>> get changes; | 69 Stream<List<ChangeRecord>> get changes; |
| 36 | |
| 37 // TODO(jmesserly): remove these ASAP. | |
| 38 /** | |
| 39 * *Warning*: this method is temporary until dart2js supports mirrors. | |
| 40 * Gets the value of a field or index. This should return null if it was | |
| 41 * not found. | |
| 42 */ | |
| 43 getValueWorkaround(key); | |
| 44 | |
| 45 /** | |
| 46 * *Warning*: this method is temporary until dart2js supports mirrors. | |
| 47 * Sets the value of a field or index. This should have no effect if the field | |
| 48 * was not found. | |
| 49 */ | |
| 50 void setValueWorkaround(key, Object value); | |
| 51 } | 70 } |
| 52 | 71 |
| 53 /** | 72 /** |
| 54 * Base class implementing [Observable]. | 73 * Base class implementing [Observable]. |
| 55 * | 74 * |
| 56 * When a field, property, or indexable item is changed, a derived class should | 75 * When a field, property, or indexable item is changed, a derived class should |
| 57 * call [notifyPropertyChange]. See that method for an example. | 76 * call [notifyPropertyChange]. See that method for an example. |
| 58 */ | 77 */ |
| 59 typedef ObservableBase = Object with ObservableMixin; | 78 typedef ObservableBase = Object with ObservableMixin; |
| 60 | 79 |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 219 /** Queues an action to happen during the [deliverChangeRecords] timeslice. */ | 238 /** Queues an action to happen during the [deliverChangeRecords] timeslice. */ |
| 220 void queueChangeRecords(void deliverChanges()) { | 239 void queueChangeRecords(void deliverChanges()) { |
| 221 if (_deliverCallbacks == null) { | 240 if (_deliverCallbacks == null) { |
| 222 _deliverCallbacks = new Queue<Function>(); | 241 _deliverCallbacks = new Queue<Function>(); |
| 223 runAsync(deliverChangeRecords); | 242 runAsync(deliverChangeRecords); |
| 224 } | 243 } |
| 225 _deliverCallbacks.add(deliverChanges); | 244 _deliverCallbacks.add(deliverChanges); |
| 226 } | 245 } |
| 227 | 246 |
| 228 Queue _deliverCallbacks; | 247 Queue _deliverCallbacks; |
| 248 | |
| 249 | |
| 250 /** | |
| 251 * Converts the [Iterable] or [Map] to an [ObservableList] or [ObservableMap], | |
| 252 * respectively. This is a convenience function to make it easier to convert | |
| 253 * literals into the corresponding observable collection type. | |
|
justinfagnani
2013/06/25 23:03:31
It may be obvious, but do you want to mention that
Jennifer Messerly
2013/06/26 23:09:12
attempted to clarify comment
| |
| 254 * | |
| 255 * If [value] is not one of those collection types, or is already [Observable], | |
| 256 * it will be returned unmodified. | |
| 257 * | |
| 258 * If [value] is a [Map], the resulting value will use the appropriate kind of | |
| 259 * backing map: either [HashMap], [LinkedHashMap], or [SplayTreeMap]. | |
| 260 * | |
| 261 * By default this performs a deep conversion, but you can set [deep] to false | |
| 262 * for a shallow conversion. This does not handle circular data structures. | |
| 263 */ | |
| 264 // TODO(jmesserly): ObservableSet? | |
| 265 toObservable(value, {bool deep: true}) => | |
| 266 deep ? _toObservableDeep(value) : _toObservableShallow(value); | |
| 267 | |
| 268 _toObservableShallow(value) { | |
| 269 if (value is Observable) return value; | |
| 270 if (value is Map) return new ObservableMap.from(value); | |
| 271 if (value is Iterable) return new ObservableList.from(value); | |
| 272 return value; | |
| 273 } | |
| 274 | |
| 275 _toObservableDeep(value) { | |
| 276 if (value is Observable) return value; | |
| 277 if (value is Map) { | |
| 278 var result = new ObservableMap._createFromType(value); | |
| 279 value.forEach((k, v) { | |
| 280 result[_toObservableDeep(k)] = _toObservableDeep(v); | |
| 281 }); | |
| 282 return result; | |
| 283 } | |
| 284 if (value is Iterable) { | |
| 285 return new ObservableList.from(value.map(_toObservableDeep)); | |
| 286 } | |
| 287 return value; | |
| 288 } | |
| OLD | NEW |