Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(145)

Side by Side Diff: pkg/observe/lib/src/observable.dart

Issue 27618002: package:observe fix various api issues (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/observe/lib/src/metadata.dart ('k') | pkg/observe/lib/src/observable_box.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 part of observe;
6 6
7 /** 7 /**
8 * Interface representing an observable object. This is used by data in 8 * Represents an object with observable properties. This is used by data in
9 * model-view architectures to notify interested parties of [changes]. 9 * model-view architectures to notify interested parties of [changes] to the
10 * object's properties (fields or getter/setter pairs).
10 * 11 *
11 * This object does not require any specific technique to implement 12 * The interface does not require any specific technique to implement
12 * observability. If you mixin [ObservableMixin], [dirtyCheck] will know to 13 * observability. You can implement it in the following ways:
13 * check for changes on the object. You may also implement change notification
14 * yourself, by calling [notifyChange].
15 * 14 *
16 * You can use [ObservableBase] or [ObservableMixin] to implement this. 15 * - extend or mixin this class, and let the application call [dirtyCheck]
16 * periodically to check for changes to your object.
17 * - extend or mixin [ChangeNotifier], and implement change notifications
18 * manually by calling [notifyPropertyChange] from your setters.
19 * - implement this interface and provide your own implementation.
17 */ 20 */
18 abstract class Observable { 21 abstract class Observable {
19 /** 22 /**
20 * The stream of change records to this object. Records will be delivered 23 * Performs dirty checking of objects that inherit from [Observable].
21 * asynchronously.
22 *
23 * [deliverChanges] can be called to force synchronous delivery.
24 */
25 Stream<List<ChangeRecord>> get changes;
26
27 /**
28 * Synchronously deliver pending [changes]. Returns true if any records were
29 * delivered, otherwise false.
30 */
31 // TODO(jmesserly): this is a bit different from the ES Harmony version, which
32 // allows delivery of changes to a particular observer:
33 // http://wiki.ecmascript.org/doku.php?id=harmony:observe#object.deliverchange records
34 //
35 // The rationale for that, and for async delivery in general, is the principal
36 // that you shouldn't run code (observers) when it doesn't expect to be run.
37 // If you do that, you risk violating invariants that the code assumes.
38 //
39 // For this reason, we need to match the ES Harmony version. The way we can do
40 // this in Dart is to add a method on StreamSubscription (possibly by
41 // subclassing Stream* types) that immediately delivers records for only
42 // that subscription. Alternatively, we could consider using something other
43 // than Stream to deliver the multicast change records, and provide an
44 // Observable->Stream adapter.
45 //
46 // Also: we should be delivering changes to the observer (subscription) based
47 // on the birth order of the observer. This is for compatibility with ES
48 // Harmony as well as predictability for app developers.
49 bool deliverChanges();
50
51 /**
52 * Notify observers of a change.
53 *
54 * For most objects [ObservableMixin.notifyPropertyChange] is more
55 * convenient, but collections sometimes deliver other types of changes such
56 * as a [ListChangeRecord].
57 */
58 void notifyChange(ChangeRecord record);
59
60 /**
61 * True if this object has any observers, and should call
62 * [notifyChange] for changes.
63 */
64 bool get hasObservers;
65
66 /**
67 * Performs dirty checking of objects that inherit from [ObservableMixin].
68 * This scans all observed objects using mirrors and determines if any fields 24 * This scans all observed objects using mirrors and determines if any fields
69 * have changed. If they have, it delivers the changes for the object. 25 * have changed. If they have, it delivers the changes for the object.
70 */ 26 */
71 static void dirtyCheck() => dirtyCheckObservables(); 27 static void dirtyCheck() => dirtyCheckObservables();
72 }
73 28
74 /**
75 * Base class implementing [Observable].
76 *
77 * When a field, property, or indexable item is changed, the change record
78 * will be sent to [changes].
79 */
80 class ObservableBase = Object with ObservableMixin;
81
82 /**
83 * Mixin for implementing [Observable] objects.
84 *
85 * When a field, property, or indexable item is changed, the change record
86 * will be sent to [changes].
87 */
88 abstract class ObservableMixin implements Observable {
89 StreamController _changes; 29 StreamController _changes;
90 InstanceMirror _mirror; 30 InstanceMirror _mirror;
91 31
92 Map<Symbol, Object> _values; 32 Map<Symbol, Object> _values;
93 List<ChangeRecord> _records; 33 List<ChangeRecord> _records;
94 34
95 static final _objectType = reflectClass(Object); 35 static final _objectType = reflectClass(Object);
96 36
37 /**
38 * The stream of change records to this object. Records will be delivered
39 * asynchronously.
40 *
41 * [deliverChanges] can be called to force synchronous delivery.
42 */
97 Stream<List<ChangeRecord>> get changes { 43 Stream<List<ChangeRecord>> get changes {
98 if (_changes == null) { 44 if (_changes == null) {
99 _changes = new StreamController.broadcast(sync: true, 45 _changes = new StreamController.broadcast(sync: true,
100 onListen: _observed, onCancel: _unobserved); 46 onListen: _observed, onCancel: _unobserved);
101 } 47 }
102 return _changes.stream; 48 return _changes.stream;
103 } 49 }
104 50
51 /**
52 * True if this object has any observers, and should call
53 * [notifyChange] for changes.
54 */
105 bool get hasObservers => _changes != null && _changes.hasListener; 55 bool get hasObservers => _changes != null && _changes.hasListener;
106 56
107 void _observed() { 57 void _observed() {
108 // Register this object for dirty checking purposes. 58 // Register this object for dirty checking purposes.
109 registerObservable(this); 59 registerObservable(this);
110 60
111 var mirror = reflect(this); 61 var mirror = reflect(this);
112 var values = new Map<Symbol, Object>(); 62 var values = new Map<Symbol, Object>();
113 63
114 // Note: we scan for @observable regardless of whether the base type 64 // Note: we scan for @observable regardless of whether the base type
(...skipping 22 matching lines...) Expand all
137 /** Release data associated with observation. */ 87 /** Release data associated with observation. */
138 void _unobserved() { 88 void _unobserved() {
139 // Note: we don't need to explicitly unregister from the dirty check list. 89 // Note: we don't need to explicitly unregister from the dirty check list.
140 // This will happen automatically at the next call to dirtyCheck. 90 // This will happen automatically at the next call to dirtyCheck.
141 if (_values != null) { 91 if (_values != null) {
142 _mirror = null; 92 _mirror = null;
143 _values = null; 93 _values = null;
144 } 94 }
145 } 95 }
146 96
97 /**
98 * Synchronously deliver pending [changes]. Returns true if any records were
99 * delivered, otherwise false.
100 */
101 // TODO(jmesserly): this is a bit different from the ES Harmony version, which
102 // allows delivery of changes to a particular observer:
103 // http://wiki.ecmascript.org/doku.php?id=harmony:observe#object.deliverchange records
104 //
105 // The rationale for that, and for async delivery in general, is the principal
106 // that you shouldn't run code (observers) when it doesn't expect to be run.
107 // If you do that, you risk violating invariants that the code assumes.
108 //
109 // For this reason, we need to match the ES Harmony version. The way we can do
110 // this in Dart is to add a method on StreamSubscription (possibly by
111 // subclassing Stream* types) that immediately delivers records for only
112 // that subscription. Alternatively, we could consider using something other
113 // than Stream to deliver the multicast change records, and provide an
114 // Observable->Stream adapter.
115 //
116 // Also: we should be delivering changes to the observer (subscription) based
117 // on the birth order of the observer. This is for compatibility with ES
118 // Harmony as well as predictability for app developers.
147 bool deliverChanges() { 119 bool deliverChanges() {
148 if (_values == null || !hasObservers) return false; 120 if (_values == null || !hasObservers) return false;
149 121
150 // Start with manually notified records (computed properties, etc), 122 // Start with manually notified records (computed properties, etc),
151 // then scan all fields for additional changes. 123 // then scan all fields for additional changes.
152 List records = _records; 124 List records = _records;
153 _records = null; 125 _records = null;
154 126
155 _values.forEach((name, oldValue) { 127 _values.forEach((name, oldValue) {
156 var newValue = _mirror.getField(name).reflectee; 128 var newValue = _mirror.getField(name).reflectee;
157 if (!identical(oldValue, newValue)) { 129 if (oldValue != newValue) {
158 if (records == null) records = []; 130 if (records == null) records = [];
159 records.add(new PropertyChangeRecord(name)); 131 records.add(new PropertyChangeRecord(this, name, oldValue, newValue));
160 _values[name] = newValue; 132 _values[name] = newValue;
161 } 133 }
162 }); 134 });
163 135
164 if (records == null) return false; 136 if (records == null) return false;
165 137
166 _changes.add(new UnmodifiableListView<ChangeRecord>(records)); 138 _changes.add(new UnmodifiableListView<ChangeRecord>(records));
167 return true; 139 return true;
168 } 140 }
169 141
170 /** 142 /**
171 * Notify that the field [name] of this object has been changed. 143 * Notify that the field [name] of this object has been changed.
172 * 144 *
173 * The [oldValue] and [newValue] are also recorded. If the two values are 145 * The [oldValue] and [newValue] are also recorded. If the two values are
174 * identical, no change will be recorded. 146 * equal, no change will be recorded.
175 * 147 *
176 * For convenience this returns [newValue]. 148 * For convenience this returns [newValue].
177 */ 149 */
178 notifyPropertyChange(Symbol field, Object oldValue, Object newValue) 150 notifyPropertyChange(Symbol field, Object oldValue, Object newValue)
179 => _notifyPropertyChange(this, field, oldValue, newValue); 151 => _notifyPropertyChange(this, field, oldValue, newValue);
180 152
181 /** 153 /**
182 * Notify a change manually. This is *not* required for fields, but can be 154 * Notify observers of a change.
183 * used for computed properties. *Note*: unlike [ChangeNotifierMixin] this 155 *
184 * will not schedule [deliverChanges]; use [Observable.dirtyCheck] instead. 156 * For most objects [Observable.notifyPropertyChange] is more convenient, but
157 * collections sometimes deliver other types of changes such as a
158 * [ListChangeRecord].
159 *
160 * Notes:
161 * - This is *not* required for fields if you mixin or extend [Observable],
162 * but you can use it for computed properties.
163 * - Unlike [ChangeNotifier] this will not schedule [deliverChanges]; use
164 * [Observable.dirtyCheck] instead.
185 */ 165 */
186 void notifyChange(ChangeRecord record) { 166 void notifyChange(ChangeRecord record) {
187 if (!hasObservers) return; 167 if (!hasObservers) return;
188 168
189 if (_records == null) _records = []; 169 if (_records == null) _records = [];
190 _records.add(record); 170 _records.add(record);
191 } 171 }
192 } 172 }
193 173
194 /** 174 /**
175 * *Deprecated* use [Observable.notifyPropertyChange] instead.
176 *
177 * This API should not be used as it creates a
178 * [PropertyChangeRecord] without oldValue and newValue.
179 *
195 * Notify the property change. Shorthand for: 180 * Notify the property change. Shorthand for:
196 * 181 *
197 * target.notifyChange(new PropertyChangeRecord(targetName)); 182 * target.notifyChange(new PropertyChangeRecord(target, name, null, null));
198 */ 183 */
199 void notifyProperty(Observable target, Symbol targetName) { 184 @deprecated
200 target.notifyChange(new PropertyChangeRecord(targetName)); 185 void notifyProperty(Observable target, Symbol name) {
186 target.notifyChange(new PropertyChangeRecord(target, name, null, null));
201 } 187 }
202 188
203 // TODO(jmesserly): remove the instance method and make this top-level method 189 // TODO(jmesserly): remove the instance method and make this top-level method
204 // public instead? 190 // public instead?
205 _notifyPropertyChange(Observable obj, Symbol field, Object oldValue, 191 _notifyPropertyChange(Observable obj, Symbol field, Object oldValue,
206 Object newValue) { 192 Object newValue) {
207 193
208 // TODO(jmesserly): should this be == instead of identical, to prevent 194 if (obj.hasObservers && oldValue != newValue) {
209 // spurious loops? 195 obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue));
210 if (obj.hasObservers && !identical(oldValue, newValue)) {
211 obj.notifyChange(new PropertyChangeRecord(field));
212 } 196 }
213 return newValue; 197 return newValue;
214 } 198 }
OLDNEW
« no previous file with comments | « pkg/observe/lib/src/metadata.dart ('k') | pkg/observe/lib/src/observable_box.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698