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

Side by Side Diff: sdk/lib/observe_impl/observe_impl.dart

Issue 14732003: Implement Model-Driven-Views spec for Dart (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: comment tweaks Created 7 years, 7 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
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 // This library itself is undocumented and not supported for end use.
6 // Because dart:html must use some of this functionality, it has to be available
7 // via a dart:* library. The public APIs are reexported via package:observe.
8 // Generally we try to keep this library minimal, with utility types and
9 // functions in the package.
10 library dart.observe_impl;
11
12 import 'dart:async';
13 import 'dart:collection';
14
15 /**
16 * Interface representing an observable object. This is used by data in
17 * model-view architectures to notify interested parties of [changes].
18 *
19 * This object does not require any specific technique to implement
20 * observability.
21 *
22 * You can use [ObservableMixin] as a base class or mixin to implement this.
23 */
24 abstract class Observable {
25 /**
26 * The stream of change records to this object.
27 *
28 * Changes should be delivered in asynchronous batches by calling
29 * [queueChangeRecords].
30 *
31 * [deliverChangeRecords] can be called to force delivery.
32 */
33 Stream<List<ChangeRecord>> get changes;
34
35 // TODO(jmesserly): remove these ASAP.
36 /**
37 * *Warning*: this method is temporary until dart2js supports mirrors.
38 * Gets the value of a field or index. This should return null if it was
39 * not found.
40 */
41 getValueWorkaround(key);
42
43 /**
44 * *Warning*: this method is temporary until dart2js supports mirrors.
45 * Sets the value of a field or index. This should have no effect if the field
46 * was not found.
47 */
48 void setValueWorkaround(key, Object value);
49 }
50
51 /**
52 * Base class implementing [Observable].
53 *
54 * When a field, property, or indexable item is changed, a derived class should
55 * call [notifyField]. See that method for an example.
56 */
57 typedef ObservableBase = Object with ObservableMixin;
58
59 /**
60 * Mixin for implementing [Observable] objects.
61 *
62 * When a field, property, or indexable item is changed, a derived class should
63 * call [notifyField]. See that method for an example.
64 */
65 abstract class ObservableMixin implements Observable {
66 StreamController<List<ChangeRecord>> _observers;
67 Stream<List<ChangeRecord>> _stream;
68 List<ChangeRecord> _changes;
69
70 Stream<List<ChangeRecord>> get changes {
71 if (_observers == null) {
72 _observers = new StreamController<List<ChangeRecord>>();
73 _stream = _observers.stream.asBroadcastStream();
74 }
75 return _stream;
76 }
77
78 void _deliverChanges() {
79 var changes = _changes;
80 _changes = null;
81 if (hasObservers && changes != null) {
82 // TODO(jmesserly): make "changes" immutable
83 _observers.add(changes);
84 }
85 }
86
87 /**
88 * True if this object has any observers, and should call [notifyField] for
89 * changes.
90 */
91 bool get hasObservers => _observers != null && _observers.hasListener;
92
93 /**
94 * Notify that the field [name] of this object has been changed.
95 *
96 * The [oldValue] and [newValue] are also recorded. If the two values are
97 * identical, no change will be recorded.
98 *
99 * For convenience this returns [newValue]. This makes it easy to use in a
100 * setter:
101 *
102 * var _myField;
103 * get myField => _myField;
104 * set myField(value) {
105 * _myField = notifyField(const Symbol('myField'), _myField, value);
106 * }
107 */
108 Object notifyField(Symbol field, Object oldValue, Object newValue) {
109 if (!identical(oldValue, newValue)) {
110 notifyChange(new ObjectChangeRecord(field));
111 return newValue;
112 }
113 }
114
115 /**
116 * Notify observers of a change. For most objects [notifyChange] is more
117 * convenient, but collections sometimes deliver other types of changes
118 * such as a [ListChangeRecord].
119 */
120 void notifyChange(ChangeRecord record) {
121 if (!hasObservers) return;
122
123 if (_changes == null) {
124 _changes = [];
125 queueChangeRecords(_deliverChanges);
126 }
127 _changes.add(record);
128 }
129 }
130
131
132 /** Records a change to an [Observable]. */
133 abstract class ChangeRecord {
134 /** True if the change affected the given item, otherwise false. */
135 bool change(key);
136 }
137
138 /** A change record to an observable object. */
139 class ObjectChangeRecord extends ChangeRecord {
140 /** The field that was changed. */
141 final Symbol field;
142
143 ObjectChangeRecord(this.field);
144
145 bool changes(key) => identical(field, key);
146
147 String toString() => '#<ObjectChangeRecord $field>';
148 }
149
150 /** A change record for an observable list. */
151 class ListChangeRecord extends ChangeRecord {
152 /** The starting index of the change. */
153 final int index;
154
155 /** The number of items removed. */
156 final int removedCount;
157
158 /** The number of items added. */
159 final int addedCount;
160
161 ListChangeRecord(this.index, {this.removedCount: 0, this.addedCount: 0}) {
162 if (addedCount == 0 && removedCount == 0) {
163 throw new ArgumentError('added and removed counts should not both be '
164 'zero. Use 1 if this was a single item update.');
165 }
166 }
167
168 /** Returns true if the provided index was changed by this operation. */
169 bool changes(key) {
170 // If key isn't an int, or before the index, then it wasn't changed.
171 if (key is! int || key < index) return false;
172
173 // If this was a shift operation, anything after index is changed.
174 if (addedCount != removedCount) return true;
175
176 // Otherwise, anything in the update range was changed.
177 return key < index + addedCount;
178 }
179
180 String toString() => '#<ListChangeRecord index: $index, '
181 'removed: $removedCount, addedCount: $addedCount>';
182 }
183
184 /**
185 * Synchronously deliver [Observable.changes] for all observables.
186 * If new changes are added as a result of delivery, this will keep running
187 * until all pending change records are delivered.
188 */
189 // TODO(jmesserly): this is a bit different from the ES Harmony version, which
190 // allows delivery of changes to a particular observer:
191 // http://wiki.ecmascript.org/doku.php?id=harmony:observe#object.deliverchangere cords
192 // However the binding system needs delivery of everything, along the lines of:
193 // https://github.com/toolkitchen/mdv/blob/stable/src/model.js#L19
194 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js#L590
195 // TODO(jmesserly): in the future, we can use this to trigger dirty checking.
196 void deliverChangeRecords() {
197 if (_deliverCallbacks == null) return;
198
199 while (!_deliverCallbacks.isEmpty) {
200 var deliver = _deliverCallbacks.removeFirst();
201
202 try {
203 deliver();
204 } catch (e, s) {
205 // Schedule the error to be top-leveled later.
206 new Completer().completeError(e, s);
207 }
208 }
209
210 // Null it out, so [queueChangeRecords] will reschedule this method.
211 _deliverCallbacks = null;
212 }
213
214 /** Queues an action to happen during the [deliverChangeRecords] timeslice. */
215 void queueChangeRecords(void deliverChanges()) {
216 if (_deliverCallbacks == null) {
217 _deliverCallbacks = new Queue<Function>();
218 runAsync(deliverChangeRecords);
219 }
220 _deliverCallbacks.add(deliverChanges);
221 }
222
223 Queue _deliverCallbacks;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698