| 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 library observe.src.observable; | 5 library observe.src.observable; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 // Note: ObservableProperty is in this list only for the unusual use case of | 10 // Note: ObservableProperty is in this list only for the unusual use case of |
| 11 // dart2js without deploy tool. The deploy tool (see "transformer.dart") will | 11 // dart2js without deploy tool. The deploy tool (see "transformer.dart") will |
| 12 // add the @reflectable annotation, which makes it work with Polymer's | 12 // add the @reflectable annotation, which makes it work with Polymer's |
| 13 // @published. | 13 // @published. |
| 14 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty], | 14 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty], |
| 15 override: 'observe.src.observable') | 15 override: 'observe.src.observable') |
| 16 import 'dart:mirrors'; | 16 import 'dart:mirrors'; |
| 17 | 17 |
| 18 import 'package:observe/observe.dart'; | 18 import 'package:observe/observe.dart'; |
| 19 | 19 |
| 20 // Note: this is an internal library so we can import it from tests. | 20 // Note: this is an internal library so we can import it from tests. |
| 21 // TODO(jmesserly): ideally we could import this with a prefix, but it caused | 21 // TODO(jmesserly): ideally we could import this with a prefix, but it caused |
| 22 // strange problems on the VM when I tested out the dirty-checking example | 22 // strange problems on the VM when I tested out the dirty-checking example |
| 23 // above. | 23 // above. |
| 24 import 'dirty_check.dart'; | 24 import 'dirty_check.dart'; |
| 25 | 25 |
| 26 /** | 26 /// Represents an object with observable properties. This is used by data in |
| 27 * Represents an object with observable properties. This is used by data in | 27 /// model-view architectures to notify interested parties of [changes] to the |
| 28 * model-view architectures to notify interested parties of [changes] to the | 28 /// object's properties (fields or getter/setter pairs). |
| 29 * object's properties (fields or getter/setter pairs). | 29 /// |
| 30 * | 30 /// The interface does not require any specific technique to implement |
| 31 * The interface does not require any specific technique to implement | 31 /// observability. You can implement it in the following ways: |
| 32 * observability. You can implement it in the following ways: | 32 /// |
| 33 * | 33 /// - extend or mixin this class, and let the application call [dirtyCheck] |
| 34 * - extend or mixin this class, and let the application call [dirtyCheck] | 34 /// periodically to check for changes to your object. |
| 35 * periodically to check for changes to your object. | 35 /// - extend or mixin [ChangeNotifier], and implement change notifications |
| 36 * - extend or mixin [ChangeNotifier], and implement change notifications | 36 /// manually by calling [notifyPropertyChange] from your setters. |
| 37 * manually by calling [notifyPropertyChange] from your setters. | 37 /// - implement this interface and provide your own implementation. |
| 38 * - implement this interface and provide your own implementation. | |
| 39 */ | |
| 40 abstract class Observable { | 38 abstract class Observable { |
| 41 /** | 39 /// Performs dirty checking of objects that inherit from [Observable]. |
| 42 * Performs dirty checking of objects that inherit from [Observable]. | 40 /// This scans all observed objects using mirrors and determines if any fields |
| 43 * This scans all observed objects using mirrors and determines if any fields | 41 /// have changed. If they have, it delivers the changes for the object. |
| 44 * have changed. If they have, it delivers the changes for the object. | |
| 45 */ | |
| 46 static void dirtyCheck() => dirtyCheckObservables(); | 42 static void dirtyCheck() => dirtyCheckObservables(); |
| 47 | 43 |
| 48 StreamController _changes; | 44 StreamController _changes; |
| 49 InstanceMirror _mirror; | 45 InstanceMirror _mirror; |
| 50 | 46 |
| 51 Map<Symbol, Object> _values; | 47 Map<Symbol, Object> _values; |
| 52 List<ChangeRecord> _records; | 48 List<ChangeRecord> _records; |
| 53 | 49 |
| 54 /** | 50 /// The stream of change records to this object. Records will be delivered |
| 55 * The stream of change records to this object. Records will be delivered | 51 /// asynchronously. |
| 56 * asynchronously. | 52 /// |
| 57 * | 53 /// [deliverChanges] can be called to force synchronous delivery. |
| 58 * [deliverChanges] can be called to force synchronous delivery. | |
| 59 */ | |
| 60 Stream<List<ChangeRecord>> get changes { | 54 Stream<List<ChangeRecord>> get changes { |
| 61 if (_changes == null) { | 55 if (_changes == null) { |
| 62 _changes = new StreamController.broadcast(sync: true, | 56 _changes = new StreamController.broadcast(sync: true, |
| 63 onListen: _observed, onCancel: _unobserved); | 57 onListen: _observed, onCancel: _unobserved); |
| 64 } | 58 } |
| 65 return _changes.stream; | 59 return _changes.stream; |
| 66 } | 60 } |
| 67 | 61 |
| 68 /** | 62 /// True if this object has any observers, and should call |
| 69 * True if this object has any observers, and should call | 63 /// [notifyChange] for changes. |
| 70 * [notifyChange] for changes. | |
| 71 */ | |
| 72 bool get hasObservers => _changes != null && _changes.hasListener; | 64 bool get hasObservers => _changes != null && _changes.hasListener; |
| 73 | 65 |
| 74 void _observed() { | 66 void _observed() { |
| 75 // Register this object for dirty checking purposes. | 67 // Register this object for dirty checking purposes. |
| 76 registerObservable(this); | 68 registerObservable(this); |
| 77 | 69 |
| 78 var mirror = reflect(this); | 70 var mirror = reflect(this); |
| 79 var values = new Map<Symbol, Object>(); | 71 var values = new Map<Symbol, Object>(); |
| 80 | 72 |
| 81 // Note: we scan for @observable regardless of whether the base type | 73 // Note: we scan for @observable regardless of whether the base type |
| (...skipping 15 matching lines...) Expand all Loading... |
| 97 break; | 89 break; |
| 98 } | 90 } |
| 99 } | 91 } |
| 100 } | 92 } |
| 101 } | 93 } |
| 102 | 94 |
| 103 _mirror = mirror; | 95 _mirror = mirror; |
| 104 _values = values; | 96 _values = values; |
| 105 } | 97 } |
| 106 | 98 |
| 107 /** Release data associated with observation. */ | 99 /// Release data associated with observation. |
| 108 void _unobserved() { | 100 void _unobserved() { |
| 109 // Note: we don't need to explicitly unregister from the dirty check list. | 101 // Note: we don't need to explicitly unregister from the dirty check list. |
| 110 // This will happen automatically at the next call to dirtyCheck. | 102 // This will happen automatically at the next call to dirtyCheck. |
| 111 if (_values != null) { | 103 if (_values != null) { |
| 112 _mirror = null; | 104 _mirror = null; |
| 113 _values = null; | 105 _values = null; |
| 114 } | 106 } |
| 115 } | 107 } |
| 116 | 108 |
| 117 /** | 109 /// Synchronously deliver pending [changes]. Returns true if any records were |
| 118 * Synchronously deliver pending [changes]. Returns true if any records were | 110 /// delivered, otherwise false. |
| 119 * delivered, otherwise false. | |
| 120 */ | |
| 121 // TODO(jmesserly): this is a bit different from the ES Harmony version, which | 111 // TODO(jmesserly): this is a bit different from the ES Harmony version, which |
| 122 // allows delivery of changes to a particular observer: | 112 // allows delivery of changes to a particular observer: |
| 123 // http://wiki.ecmascript.org/doku.php?id=harmony:observe#object.deliverchange
records | 113 // http://wiki.ecmascript.org/doku.php?id=harmony:observe#object.deliverchange
records |
| 124 // | 114 // |
| 125 // The rationale for that, and for async delivery in general, is the principal | 115 // The rationale for that, and for async delivery in general, is the principal |
| 126 // that you shouldn't run code (observers) when it doesn't expect to be run. | 116 // that you shouldn't run code (observers) when it doesn't expect to be run. |
| 127 // If you do that, you risk violating invariants that the code assumes. | 117 // If you do that, you risk violating invariants that the code assumes. |
| 128 // | 118 // |
| 129 // For this reason, we need to match the ES Harmony version. The way we can do | 119 // For this reason, we need to match the ES Harmony version. The way we can do |
| 130 // this in Dart is to add a method on StreamSubscription (possibly by | 120 // this in Dart is to add a method on StreamSubscription (possibly by |
| (...skipping 21 matching lines...) Expand all Loading... |
| 152 _values[name] = newValue; | 142 _values[name] = newValue; |
| 153 } | 143 } |
| 154 }); | 144 }); |
| 155 | 145 |
| 156 if (records == null) return false; | 146 if (records == null) return false; |
| 157 | 147 |
| 158 _changes.add(new UnmodifiableListView<ChangeRecord>(records)); | 148 _changes.add(new UnmodifiableListView<ChangeRecord>(records)); |
| 159 return true; | 149 return true; |
| 160 } | 150 } |
| 161 | 151 |
| 162 /** | 152 /// Notify that the field [name] of this object has been changed. |
| 163 * Notify that the field [name] of this object has been changed. | 153 /// |
| 164 * | 154 /// The [oldValue] and [newValue] are also recorded. If the two values are |
| 165 * The [oldValue] and [newValue] are also recorded. If the two values are | 155 /// equal, no change will be recorded. |
| 166 * equal, no change will be recorded. | 156 /// |
| 167 * | 157 /// For convenience this returns [newValue]. |
| 168 * For convenience this returns [newValue]. | |
| 169 */ | |
| 170 notifyPropertyChange(Symbol field, Object oldValue, Object newValue) | 158 notifyPropertyChange(Symbol field, Object oldValue, Object newValue) |
| 171 => notifyPropertyChangeHelper(this, field, oldValue, newValue); | 159 => notifyPropertyChangeHelper(this, field, oldValue, newValue); |
| 172 | 160 |
| 173 /** | 161 /// Notify observers of a change. |
| 174 * Notify observers of a change. | 162 /// |
| 175 * | 163 /// For most objects [Observable.notifyPropertyChange] is more convenient, but |
| 176 * For most objects [Observable.notifyPropertyChange] is more convenient, but | 164 /// collections sometimes deliver other types of changes such as a |
| 177 * collections sometimes deliver other types of changes such as a | 165 /// [ListChangeRecord]. |
| 178 * [ListChangeRecord]. | 166 /// |
| 179 * | 167 /// Notes: |
| 180 * Notes: | 168 /// - This is *not* required for fields if you mixin or extend [Observable], |
| 181 * - This is *not* required for fields if you mixin or extend [Observable], | 169 /// but you can use it for computed properties. |
| 182 * but you can use it for computed properties. | 170 /// - Unlike [ChangeNotifier] this will not schedule [deliverChanges]; use |
| 183 * - Unlike [ChangeNotifier] this will not schedule [deliverChanges]; use | 171 /// [Observable.dirtyCheck] instead. |
| 184 * [Observable.dirtyCheck] instead. | |
| 185 */ | |
| 186 void notifyChange(ChangeRecord record) { | 172 void notifyChange(ChangeRecord record) { |
| 187 if (!hasObservers) return; | 173 if (!hasObservers) return; |
| 188 | 174 |
| 189 if (_records == null) _records = []; | 175 if (_records == null) _records = []; |
| 190 _records.add(record); | 176 _records.add(record); |
| 191 } | 177 } |
| 192 } | 178 } |
| 193 | 179 |
| 194 // TODO(jmesserly): remove the instance method and make this top-level method | 180 // TODO(jmesserly): remove the instance method and make this top-level method |
| 195 // public instead? | 181 // public instead? |
| 196 // NOTE: this is not exported publically. | 182 // NOTE: this is not exported publically. |
| 197 notifyPropertyChangeHelper(Observable obj, Symbol field, Object oldValue, | 183 notifyPropertyChangeHelper(Observable obj, Symbol field, Object oldValue, |
| 198 Object newValue) { | 184 Object newValue) { |
| 199 | 185 |
| 200 if (obj.hasObservers && oldValue != newValue) { | 186 if (obj.hasObservers && oldValue != newValue) { |
| 201 obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue)); | 187 obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue)); |
| 202 } | 188 } |
| 203 return newValue; | 189 return newValue; |
| 204 } | 190 } |
| 205 | 191 |
| 206 // NOTE: this is not exported publically. | 192 // NOTE: this is not exported publically. |
| 207 final objectType = reflectClass(Object); | 193 final objectType = reflectClass(Object); |
| OLD | NEW |