| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011, 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 #library('observable'); | |
| 6 | |
| 7 #import('dart:coreimpl'); | |
| 8 | |
| 9 #source('ChangeEvent.dart'); | |
| 10 #source('EventBatch.dart'); | |
| 11 | |
| 12 /** | |
| 13 * An object whose changes are tracked and who can issue events notifying how it | |
| 14 * has been changed. | |
| 15 */ | |
| 16 interface Observable { | |
| 17 /** Returns a globally unique identifier for the object. */ | |
| 18 // TODO(sigmund): remove once dart supports maps with arbitrary keys. | |
| 19 final int uid; | |
| 20 | |
| 21 /** Listeners on this model. */ | |
| 22 final List<ChangeListener> listeners; | |
| 23 | |
| 24 /** The parent observable to notify when this child is changed. */ | |
| 25 final Observable parent; | |
| 26 | |
| 27 /** | |
| 28 * Adds a listener for changes on this observable instance. Returns whether | |
| 29 * the listener was added successfully. | |
| 30 */ | |
| 31 bool addChangeListener(ChangeListener listener); | |
| 32 | |
| 33 /** | |
| 34 * Removes a listener for changes on this observable instance. Returns whether | |
| 35 * the listener was removed successfully. | |
| 36 */ | |
| 37 bool removeChangeListener(ChangeListener listener); | |
| 38 } | |
| 39 | |
| 40 | |
| 41 /** Common functionality for observable objects. */ | |
| 42 class AbstractObservable implements Observable { | |
| 43 | |
| 44 /** Unique id to identify this model in an event batch. */ | |
| 45 final int uid; | |
| 46 | |
| 47 /** The parent observable to notify when this child is changed. */ | |
| 48 final Observable parent; | |
| 49 | |
| 50 /** Listeners on this model. */ | |
| 51 List<ChangeListener> listeners; | |
| 52 | |
| 53 /** Whether this object is currently observed by listeners or propagators. */ | |
| 54 bool get isObserved() { | |
| 55 for (Observable obj = this; obj != null; obj = obj.parent) { | |
| 56 if (listeners.length > 0) { | |
| 57 return true; | |
| 58 } | |
| 59 } | |
| 60 return false; | |
| 61 } | |
| 62 | |
| 63 AbstractObservable([Observable this.parent = null]) | |
| 64 : uid = EventBatch.genUid(), | |
| 65 listeners = new List<ChangeListener>(); | |
| 66 | |
| 67 bool addChangeListener(ChangeListener listener) { | |
| 68 if (listeners.indexOf(listener, 0) == -1) { | |
| 69 listeners.add(listener); | |
| 70 return true; | |
| 71 } | |
| 72 | |
| 73 return false; | |
| 74 } | |
| 75 | |
| 76 bool removeChangeListener(ChangeListener listener) { | |
| 77 // TODO(rnystrom): This is awkward without List.remove(e). | |
| 78 if (listeners.indexOf(listener, 0) != -1) { | |
| 79 bool found = false; | |
| 80 listeners = listeners.filter((e) => found || !(found = (e == listener))); | |
| 81 return true; | |
| 82 } else { | |
| 83 return false; | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 void recordPropertyUpdate(String propertyName, newValue, oldValue) { | |
| 88 recordEvent(new ChangeEvent.property( | |
| 89 this, propertyName, newValue, oldValue)); | |
| 90 } | |
| 91 | |
| 92 void recordListUpdate(int index, newValue, oldValue) { | |
| 93 recordEvent(new ChangeEvent.list( | |
| 94 this, ChangeEvent.UPDATE, index, newValue, oldValue)); | |
| 95 } | |
| 96 | |
| 97 void recordListInsert(int index, newValue) { | |
| 98 recordEvent(new ChangeEvent.list( | |
| 99 this, ChangeEvent.INSERT, index, newValue, null)); | |
| 100 } | |
| 101 | |
| 102 void recordListRemove(int index, oldValue) { | |
| 103 recordEvent(new ChangeEvent.list( | |
| 104 this, ChangeEvent.REMOVE, index, null, oldValue)); | |
| 105 } | |
| 106 | |
| 107 void recordGlobalChange() { | |
| 108 recordEvent(new ChangeEvent.global(this)); | |
| 109 } | |
| 110 | |
| 111 void recordEvent(ChangeEvent event) { | |
| 112 // Bail if no one cares about the event. | |
| 113 if (!isObserved) { | |
| 114 return; | |
| 115 } | |
| 116 | |
| 117 if (EventBatch.current != null) { | |
| 118 // Already in a batch, so just add it. | |
| 119 assert (!EventBatch.current.sealed); | |
| 120 // TODO(sigmund): measure the performance implications of this indirection | |
| 121 // and consider whether caching the summary object in this instance helps. | |
| 122 var summary = EventBatch.current.getEvents(this); | |
| 123 summary.addEvent(event); | |
| 124 } else { | |
| 125 // Not in a batch, so create a one-off one. | |
| 126 // TODO(rnystrom): Needing to do ignore and (null) here is awkward. | |
| 127 EventBatch.wrap((ignore) { recordEvent(event); })(null); | |
| 128 } | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 /** A growable list that fires events when it's modified. */ | |
| 133 class ObservableList<T> | |
| 134 extends AbstractObservable | |
| 135 implements List<T>, Observable { | |
| 136 | |
| 137 /** Underlying list. */ | |
| 138 // TODO(rnystrom): Make this final if we get list.remove(). | |
| 139 List<T> _internal; | |
| 140 | |
| 141 ObservableList([Observable parent = null]) | |
| 142 : super(parent), _internal = new List<T>(); | |
| 143 | |
| 144 T operator [](int index) => _internal[index]; | |
| 145 | |
| 146 void operator []=(int index, T value) { | |
| 147 recordListUpdate(index, value, _internal[index]); | |
| 148 _internal[index] = value; | |
| 149 } | |
| 150 | |
| 151 int get length() => _internal.length; | |
| 152 | |
| 153 void set length(int value) { | |
| 154 _internal.length = value; | |
| 155 recordGlobalChange(); | |
| 156 } | |
| 157 | |
| 158 void clear() { | |
| 159 _internal.clear(); | |
| 160 recordGlobalChange(); | |
| 161 } | |
| 162 | |
| 163 void sort(int compare(Object a, Object b)) { | |
| 164 _internal.sort(compare); | |
| 165 recordGlobalChange(); | |
| 166 } | |
| 167 | |
| 168 void add(T element) { | |
| 169 recordListInsert(length, element); | |
| 170 _internal.add(element); | |
| 171 } | |
| 172 | |
| 173 void addLast(T element) { | |
| 174 add(element); | |
| 175 } | |
| 176 | |
| 177 void addAll(Collection<T> elements) { | |
| 178 for (T element in elements) { | |
| 179 add(element); | |
| 180 } | |
| 181 } | |
| 182 | |
| 183 int push(T element) { | |
| 184 recordListInsert(length, element); | |
| 185 _internal.add(element); | |
| 186 return _internal.length; | |
| 187 } | |
| 188 | |
| 189 T last() => _internal.last(); | |
| 190 | |
| 191 T removeLast() { | |
| 192 final result = _internal.removeLast(); | |
| 193 recordListRemove(length, result); | |
| 194 return result; | |
| 195 } | |
| 196 | |
| 197 T removeAt(int index) { | |
| 198 int i = 0; | |
| 199 T found = null; | |
| 200 _internal = _internal.filter(bool _(element) { | |
| 201 if (i++ == index) { | |
| 202 found = element; | |
| 203 return false; | |
| 204 } | |
| 205 return true; | |
| 206 }); | |
| 207 if (found != null) { | |
| 208 recordListRemove(index, found); | |
| 209 } | |
| 210 return found; | |
| 211 } | |
| 212 | |
| 213 int indexOf(T element, [int start = 0]) { | |
| 214 return _internal.indexOf(element, start); | |
| 215 } | |
| 216 | |
| 217 int lastIndexOf(T element, [int start = null]) { | |
| 218 if (start === null) start = length - 1; | |
| 219 return _internal.lastIndexOf(element, start); | |
| 220 } | |
| 221 | |
| 222 bool removeFirstElement(T element) { | |
| 223 // the removeAt above will record the event. | |
| 224 return (removeAt(indexOf(element, 0)) != null); | |
| 225 } | |
| 226 | |
| 227 int removeAllElements(T element) { | |
| 228 int count = 0; | |
| 229 for (int i = 0; i < length; i++) { | |
| 230 if (_internal[i] == element) { | |
| 231 // the removeAt above will record the event. | |
| 232 removeAt(i); | |
| 233 // adjust index since remove shifted elements. | |
| 234 i--; | |
| 235 count++; | |
| 236 } | |
| 237 } | |
| 238 return count; | |
| 239 } | |
| 240 | |
| 241 void copyFrom(List<T> src, int srcStart, int dstStart, int count) { | |
| 242 Arrays.copy(src, srcStart, this, dstStart, count); | |
| 243 } | |
| 244 | |
| 245 void setRange(int start, int length, List from, [int startFrom = 0]) { | |
| 246 throw const NotImplementedException(); | |
| 247 } | |
| 248 | |
| 249 void removeRange(int start, int length) { | |
| 250 throw const NotImplementedException(); | |
| 251 } | |
| 252 | |
| 253 void insertRange(int start, int length, [initialValue = null]) { | |
| 254 throw const NotImplementedException(); | |
| 255 } | |
| 256 | |
| 257 List getRange(int start, int length) { | |
| 258 throw const NotImplementedException(); | |
| 259 } | |
| 260 | |
| 261 // Iterable<T>: | |
| 262 Iterator<T> iterator() => _internal.iterator(); | |
| 263 | |
| 264 // Collection<T>: | |
| 265 Collection<T> filter(bool f(T element)) => _internal.filter(f); | |
| 266 Collection map(f(T element)) => _internal.map(f); | |
| 267 bool every(bool f(T element)) => _internal.every(f); | |
| 268 bool some(bool f(T element)) => _internal.some(f); | |
| 269 void forEach(void f(T element)) { _internal.forEach(f); } | |
| 270 bool isEmpty() => length == 0; | |
| 271 } | |
| 272 | |
| 273 // TODO(jmesserly): is this too granular? Other similar systems make whole | |
| 274 // classes observable instead of individual fields. The memory cost of having | |
| 275 // every field effectively boxed, plus having a listeners list is likely too | |
| 276 // much. Also, making a value observable necessitates adding ".value" to lots | |
| 277 // of places, and constructing all fields with the verbose | |
| 278 // "new ObservableValue<DataType>(myValue)". | |
| 279 /** A wrapper around a single value whose change can be observed. */ | |
| 280 class ObservableValue<T> extends AbstractObservable { | |
| 281 ObservableValue(T value, [Observable parent = null]) | |
| 282 : super(parent), _value = value; | |
| 283 | |
| 284 T get value() => _value; | |
| 285 | |
| 286 void set value(T newValue) { | |
| 287 // Only fire on an actual change. | |
| 288 // TODO(terry): An object identity test === is needed. Each DataSource has | |
| 289 // its own operator == which does a value compare. Which | |
| 290 // equality check should be done? | |
| 291 if (newValue !== _value) { | |
| 292 final oldValue = _value; | |
| 293 _value = newValue; | |
| 294 recordPropertyUpdate("value", newValue, oldValue); | |
| 295 } | |
| 296 } | |
| 297 | |
| 298 T _value; | |
| 299 } | |
| OLD | NEW |