| 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 part of observe; | 5 library observe.src.observable; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 9 |
| 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 |
| 12 // add the @reflectable annotation, which makes it work with Polymer's |
| 13 // @published. |
| 14 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty], |
| 15 override: 'observe.src.observable') |
| 16 import 'dart:mirrors'; |
| 17 |
| 18 import 'package:observe/observe.dart'; |
| 19 |
| 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 |
| 22 // strange problems on the VM when I tested out the dirty-checking example |
| 23 // above. |
| 24 import 'dirty_check.dart'; |
| 6 | 25 |
| 7 /** | 26 /** |
| 8 * 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 |
| 9 * model-view architectures to notify interested parties of [changes] to the | 28 * model-view architectures to notify interested parties of [changes] to the |
| 10 * object's properties (fields or getter/setter pairs). | 29 * object's properties (fields or getter/setter pairs). |
| 11 * | 30 * |
| 12 * The interface does not require any specific technique to implement | 31 * The interface does not require any specific technique to implement |
| 13 * observability. You can implement it in the following ways: | 32 * observability. You can implement it in the following ways: |
| 14 * | 33 * |
| 15 * - extend or mixin this class, and let the application call [dirtyCheck] | 34 * - extend or mixin this class, and let the application call [dirtyCheck] |
| 16 * periodically to check for changes to your object. | 35 * periodically to check for changes to your object. |
| 17 * - extend or mixin [ChangeNotifier], and implement change notifications | 36 * - extend or mixin [ChangeNotifier], and implement change notifications |
| 18 * manually by calling [notifyPropertyChange] from your setters. | 37 * manually by calling [notifyPropertyChange] from your setters. |
| 19 * - implement this interface and provide your own implementation. | 38 * - implement this interface and provide your own implementation. |
| 20 */ | 39 */ |
| 21 abstract class Observable { | 40 abstract class Observable { |
| 22 /** | 41 /** |
| 23 * Performs dirty checking of objects that inherit from [Observable]. | 42 * Performs dirty checking of objects that inherit from [Observable]. |
| 24 * 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 |
| 25 * 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. |
| 26 */ | 45 */ |
| 27 static void dirtyCheck() => dirtyCheckObservables(); | 46 static void dirtyCheck() => dirtyCheckObservables(); |
| 28 | 47 |
| 29 StreamController _changes; | 48 StreamController _changes; |
| 30 InstanceMirror _mirror; | 49 InstanceMirror _mirror; |
| 31 | 50 |
| 32 Map<Symbol, Object> _values; | 51 Map<Symbol, Object> _values; |
| 33 List<ChangeRecord> _records; | 52 List<ChangeRecord> _records; |
| 34 | 53 |
| 35 static final _objectType = reflectClass(Object); | |
| 36 | |
| 37 /** | 54 /** |
| 38 * 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 |
| 39 * asynchronously. | 56 * asynchronously. |
| 40 * | 57 * |
| 41 * [deliverChanges] can be called to force synchronous delivery. | 58 * [deliverChanges] can be called to force synchronous delivery. |
| 42 */ | 59 */ |
| 43 Stream<List<ChangeRecord>> get changes { | 60 Stream<List<ChangeRecord>> get changes { |
| 44 if (_changes == null) { | 61 if (_changes == null) { |
| 45 _changes = new StreamController.broadcast(sync: true, | 62 _changes = new StreamController.broadcast(sync: true, |
| 46 onListen: _observed, onCancel: _unobserved); | 63 onListen: _observed, onCancel: _unobserved); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 57 void _observed() { | 74 void _observed() { |
| 58 // Register this object for dirty checking purposes. | 75 // Register this object for dirty checking purposes. |
| 59 registerObservable(this); | 76 registerObservable(this); |
| 60 | 77 |
| 61 var mirror = reflect(this); | 78 var mirror = reflect(this); |
| 62 var values = new Map<Symbol, Object>(); | 79 var values = new Map<Symbol, Object>(); |
| 63 | 80 |
| 64 // Note: we scan for @observable regardless of whether the base type | 81 // Note: we scan for @observable regardless of whether the base type |
| 65 // actually includes this mixin. While perhaps too inclusive, it lets us | 82 // actually includes this mixin. While perhaps too inclusive, it lets us |
| 66 // avoid complex logic that walks "with" and "implements" clauses. | 83 // avoid complex logic that walks "with" and "implements" clauses. |
| 67 for (var type = mirror.type; type != _objectType; type = type.superclass) { | 84 for (var type = mirror.type; type != objectType; type = type.superclass) { |
| 68 for (var field in type.variables.values) { | 85 for (var field in type.variables.values) { |
| 69 if (field.isFinal || field.isStatic || field.isPrivate) continue; | 86 if (field.isFinal || field.isStatic || field.isPrivate) continue; |
| 70 | 87 |
| 71 for (var meta in field.metadata) { | 88 for (var meta in field.metadata) { |
| 72 if (meta.reflectee is ObservableProperty) { | 89 if (meta.reflectee is ObservableProperty) { |
| 73 var name = field.simpleName; | 90 var name = field.simpleName; |
| 74 // Note: since this is a field, getting the value shouldn't execute | 91 // Note: since this is a field, getting the value shouldn't execute |
| 75 // user code, so we don't need to worry about errors. | 92 // user code, so we don't need to worry about errors. |
| 76 values[name] = mirror.getField(name).reflectee; | 93 values[name] = mirror.getField(name).reflectee; |
| 77 break; | 94 break; |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 141 | 158 |
| 142 /** | 159 /** |
| 143 * Notify that the field [name] of this object has been changed. | 160 * Notify that the field [name] of this object has been changed. |
| 144 * | 161 * |
| 145 * The [oldValue] and [newValue] are also recorded. If the two values are | 162 * The [oldValue] and [newValue] are also recorded. If the two values are |
| 146 * equal, no change will be recorded. | 163 * equal, no change will be recorded. |
| 147 * | 164 * |
| 148 * For convenience this returns [newValue]. | 165 * For convenience this returns [newValue]. |
| 149 */ | 166 */ |
| 150 notifyPropertyChange(Symbol field, Object oldValue, Object newValue) | 167 notifyPropertyChange(Symbol field, Object oldValue, Object newValue) |
| 151 => _notifyPropertyChange(this, field, oldValue, newValue); | 168 => notifyPropertyChangeHelper(this, field, oldValue, newValue); |
| 152 | 169 |
| 153 /** | 170 /** |
| 154 * Notify observers of a change. | 171 * Notify observers of a change. |
| 155 * | 172 * |
| 156 * For most objects [Observable.notifyPropertyChange] is more convenient, but | 173 * For most objects [Observable.notifyPropertyChange] is more convenient, but |
| 157 * collections sometimes deliver other types of changes such as a | 174 * collections sometimes deliver other types of changes such as a |
| 158 * [ListChangeRecord]. | 175 * [ListChangeRecord]. |
| 159 * | 176 * |
| 160 * Notes: | 177 * Notes: |
| 161 * - This is *not* required for fields if you mixin or extend [Observable], | 178 * - This is *not* required for fields if you mixin or extend [Observable], |
| (...skipping 19 matching lines...) Expand all Loading... |
| 181 * | 198 * |
| 182 * target.notifyChange(new PropertyChangeRecord(target, name, null, null)); | 199 * target.notifyChange(new PropertyChangeRecord(target, name, null, null)); |
| 183 */ | 200 */ |
| 184 @deprecated | 201 @deprecated |
| 185 void notifyProperty(Observable target, Symbol name) { | 202 void notifyProperty(Observable target, Symbol name) { |
| 186 target.notifyChange(new PropertyChangeRecord(target, name, null, null)); | 203 target.notifyChange(new PropertyChangeRecord(target, name, null, null)); |
| 187 } | 204 } |
| 188 | 205 |
| 189 // TODO(jmesserly): remove the instance method and make this top-level method | 206 // TODO(jmesserly): remove the instance method and make this top-level method |
| 190 // public instead? | 207 // public instead? |
| 191 _notifyPropertyChange(Observable obj, Symbol field, Object oldValue, | 208 // NOTE: this is not exported publically. |
| 209 notifyPropertyChangeHelper(Observable obj, Symbol field, Object oldValue, |
| 192 Object newValue) { | 210 Object newValue) { |
| 193 | 211 |
| 194 if (obj.hasObservers && oldValue != newValue) { | 212 if (obj.hasObservers && oldValue != newValue) { |
| 195 obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue)); | 213 obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue)); |
| 196 } | 214 } |
| 197 return newValue; | 215 return newValue; |
| 198 } | 216 } |
| 217 |
| 218 // NOTE: this is not exported publically. |
| 219 final objectType = reflectClass(Object); |
| OLD | NEW |