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

Side by Side Diff: packages/observable/lib/src/observable_list.dart

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 4 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
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2016, 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_list; 5 library observable.src.observable_list;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection' show ListBase, UnmodifiableListView; 8 import 'dart:collection' show ListBase, UnmodifiableListView;
9 import 'package:observe/observe.dart'; 9
10 import 'list_diff.dart' show projectListSplices, calcSplices; 10 import 'list_diff.dart' show ListChangeRecord, projectListSplices, calcSplices;
11 import 'observable.dart' show Observable;
11 12
12 /// Represents an observable list of model values. If any items are added, 13 /// Represents an observable list of model values. If any items are added,
13 /// removed, or replaced, then observers that are listening to [changes] 14 /// removed, or replaced, then observers that are listening to [changes]
14 /// will be notified. 15 /// will be notified.
15 class ObservableList<E> extends ListBase<E> with ChangeNotifier { 16 class ObservableList<E> extends ListBase<E> with Observable {
16 List<ListChangeRecord> _listRecords; 17 List<ListChangeRecord> _listRecords;
17 18
18 StreamController _listChanges; 19 StreamController<List<ListChangeRecord>> _listChanges;
19 20
20 /// The inner [List<E>] with the actual storage. 21 /// The inner [List<E>] with the actual storage.
21 final List<E> _list; 22 final List<E> _list;
22 23
23 /// Creates an observable list of the given [length]. 24 /// Creates an observable list of the given [length].
24 /// 25 ///
25 /// If no [length] argument is supplied an extendable list of 26 /// If no [length] argument is supplied an extendable list of
26 /// length 0 is created. 27 /// length 0 is created.
27 /// 28 ///
28 /// If a [length] argument is supplied, a fixed size list of that 29 /// If a [length] argument is supplied, a fixed size list of that
29 /// length is created. 30 /// length is created.
30 ObservableList([int length]) 31 ObservableList([int length])
31 : _list = length != null ? new List<E>(length) : <E>[]; 32 : _list = length != null ? new List<E>(length) : <E>[];
32 33
34 /// Creates an observable list of the given [length].
35 ///
36 /// This constructor exists to work around an issue in the VM whereby
37 /// classes that derive from [ObservableList] and mixin other classes
38 /// require a default generative constructor in the super class that
39 /// does not take optional arguments.
40 ObservableList.withLength(int length) : this(length);
41
33 /// Creates an observable list with the elements of [other]. The order in 42 /// Creates an observable list with the elements of [other]. The order in
34 /// the list will be the order provided by the iterator of [other]. 43 /// the list will be the order provided by the iterator of [other].
35 factory ObservableList.from(Iterable<E> other) => 44 factory ObservableList.from(Iterable<E> other) =>
36 new ObservableList<E>()..addAll(other); 45 new ObservableList<E>()..addAll(other);
37 46
38 /// The stream of summarized list changes, delivered asynchronously. 47 /// The stream of summarized list changes, delivered asynchronously.
39 /// 48 ///
40 /// Each list change record contains information about an individual mutation. 49 /// Each list change record contains information about an individual mutation.
41 /// The records are projected so they can be applied sequentially. For 50 /// The records are projected so they can be applied sequentially. For
42 /// example, this set of mutations: 51 /// example, this set of mutations:
43 /// 52 ///
44 /// var model = new ObservableList.from(['a', 'b']); 53 /// var model = new ObservableList.from(['a', 'b']);
45 /// model.listChanges.listen((records) => records.forEach(print)); 54 /// model.listChanges.listen((records) => records.forEach(print));
46 /// model.removeAt(1); 55 /// model.removeAt(1);
47 /// model.insertAll(0, ['c', 'd', 'e']); 56 /// model.insertAll(0, ['c', 'd', 'e']);
48 /// model.removeRange(1, 3); 57 /// model.removeRange(1, 3);
49 /// model.insert(1, 'f'); 58 /// model.insert(1, 'f');
50 /// 59 ///
51 /// The change records will be summarized so they can be "played back", using 60 /// The change records will be summarized so they can be "played back", using
52 /// the final list positions to figure out which item was added: 61 /// the final list positions to figure out which item was added:
53 /// 62 ///
54 /// #<ListChangeRecord index: 0, removed: [], addedCount: 2> 63 /// #<ListChangeRecord index: 0, removed: [], addedCount: 2>
55 /// #<ListChangeRecord index: 3, removed: [b], addedCount: 0> 64 /// #<ListChangeRecord index: 3, removed: [b], addedCount: 0>
56 /// 65 ///
57 /// [deliverChanges] can be called to force synchronous delivery. 66 /// [deliverChanges] can be called to force synchronous delivery.
58 Stream<List<ListChangeRecord>> get listChanges { 67 Stream<List<ListChangeRecord>> get listChanges {
59 if (_listChanges == null) { 68 if (_listChanges == null) {
60 // TODO(jmesserly): split observed/unobserved notions? 69 // TODO(jmesserly): split observed/unobserved notions?
61 _listChanges = new StreamController.broadcast(sync: true, 70 _listChanges = new StreamController.broadcast(
62 onCancel: () { _listChanges = null; }); 71 sync: true,
72 onCancel: () {
73 _listChanges = null;
74 },
75 );
63 } 76 }
64 return _listChanges.stream; 77 return _listChanges.stream;
65 } 78 }
66 79
67 bool get hasListObservers => 80 bool get hasListObservers => _listChanges != null && _listChanges.hasListener;
68 _listChanges != null && _listChanges.hasListener;
69 81
70 @reflectable int get length => _list.length; 82 int get length => _list.length;
71 83
72 @reflectable set length(int value) { 84 set length(int value) {
73 int len = _list.length; 85 int len = _list.length;
74 if (len == value) return; 86 if (len == value) return;
75 87
76 // Produce notifications if needed 88 // Produce notifications if needed
77 _notifyChangeLength(len, value); 89 _notifyChangeLength(len, value);
78 if (hasListObservers) { 90 if (hasListObservers) {
79 if (value < len) { 91 if (value < len) {
80 _recordChange(new ListChangeRecord(this, value, 92 _notifyListChange(value, removed: _list.getRange(value, len).toList());
81 removed: _list.getRange(value, len).toList()));
82 } else { 93 } else {
83 _recordChange(new ListChangeRecord(this, len, addedCount: value - len)); 94 _notifyListChange(len, addedCount: value - len);
84 } 95 }
85 } 96 }
86 97
87 _list.length = value; 98 _list.length = value;
88 } 99 }
89 100
90 @reflectable E operator [](int index) => _list[index]; 101 E operator [](int index) => _list[index];
91 102
92 @reflectable void operator []=(int index, E value) { 103 void operator []=(int index, E value) {
93 var oldValue = _list[index]; 104 E oldValue = _list[index];
94 if (hasListObservers && oldValue != value) { 105 if (hasListObservers && oldValue != value) {
95 _recordChange(new ListChangeRecord(this, index, addedCount: 1, 106 _notifyListChange(index, addedCount: 1, removed: [oldValue]);
96 removed: [oldValue]));
97 } 107 }
98 _list[index] = value; 108 _list[index] = value;
99 } 109 }
100 110
101 // Forwarders so we can reflect on the properties. 111 // Forwarders so we can reflect on the properties.
102 @reflectable bool get isEmpty => super.isEmpty; 112 bool get isEmpty => super.isEmpty;
103 @reflectable bool get isNotEmpty => super.isNotEmpty; 113 bool get isNotEmpty => super.isNotEmpty;
104 114
105 // TODO(jmesserly): should we support first/last/single? They're kind of 115 // TODO(jmesserly): should we support first/last/single? They're kind of
106 // dangerous to use in a path because they throw exceptions. Also we'd need 116 // dangerous to use in a path because they throw exceptions. Also we'd need
107 // to produce property change notifications which seems to conflict with our 117 // to produce property change notifications which seems to conflict with our
108 // existing list notifications. 118 // existing list notifications.
109 119
110 // The following methods are here so that we can provide nice change events. 120 // The following methods are here so that we can provide nice change events.
111 121
112 void setAll(int index, Iterable<E> iterable) { 122 void setAll(int index, Iterable<E> iterable) {
113 if (iterable is! List && iterable is! Set) { 123 if (iterable is! List && iterable is! Set) {
114 iterable = iterable.toList(); 124 iterable = iterable.toList();
115 } 125 }
116 var len = iterable.length; 126 int length = iterable.length;
117 if (hasListObservers && len > 0) { 127 if (hasListObservers && length > 0) {
118 _recordChange(new ListChangeRecord(this, index, addedCount: len, 128 _notifyListChange(index,
119 removed: _list.getRange(index, len).toList())); 129 addedCount: length, removed: _list.sublist(index, length));
120 } 130 }
121 _list.setAll(index, iterable); 131 _list.setAll(index, iterable);
122 } 132 }
123 133
124 void add(E value) { 134 void add(E value) {
125 int len = _list.length; 135 int len = _list.length;
126 _notifyChangeLength(len, len + 1); 136 _notifyChangeLength(len, len + 1);
127 if (hasListObservers) { 137 if (hasListObservers) {
128 _recordChange(new ListChangeRecord(this, len, addedCount: 1)); 138 _notifyListChange(len, addedCount: 1);
129 } 139 }
130 140
131 _list.add(value); 141 _list.add(value);
132 } 142 }
133 143
134 void addAll(Iterable<E> iterable) { 144 void addAll(Iterable<E> iterable) {
135 int len = _list.length; 145 int len = _list.length;
136 _list.addAll(iterable); 146 _list.addAll(iterable);
137 147
138 _notifyChangeLength(len, _list.length); 148 _notifyChangeLength(len, _list.length);
139 149
140 int added = _list.length - len; 150 int added = _list.length - len;
141 if (hasListObservers && added > 0) { 151 if (hasListObservers && added > 0) {
142 _recordChange(new ListChangeRecord(this, len, addedCount: added)); 152 _notifyListChange(len, addedCount: added);
143 } 153 }
144 } 154 }
145 155
146 bool remove(Object element) { 156 bool remove(Object element) {
147 for (int i = 0; i < this.length; i++) { 157 for (int i = 0; i < this.length; i++) {
148 if (this[i] == element) { 158 if (this[i] == element) {
149 removeRange(i, i + 1); 159 removeRange(i, i + 1);
150 return true; 160 return true;
151 } 161 }
152 } 162 }
153 return false; 163 return false;
154 } 164 }
155 165
156 void removeRange(int start, int end) { 166 void removeRange(int start, int end) {
157 _rangeCheck(start, end); 167 _rangeCheck(start, end);
158 int rangeLength = end - start; 168 int rangeLength = end - start;
159 int len = _list.length; 169 int len = _list.length;
160 170
161 _notifyChangeLength(len, len - rangeLength); 171 _notifyChangeLength(len, len - rangeLength);
162 if (hasListObservers && rangeLength > 0) { 172 if (hasListObservers && rangeLength > 0) {
163 _recordChange(new ListChangeRecord(this, start, 173 _notifyListChange(start, removed: _list.getRange(start, end).toList());
164 removed: _list.getRange(start, end).toList()));
165 } 174 }
166 175
167 _list.removeRange(start, end); 176 _list.removeRange(start, end);
168 } 177 }
169 178
170 void insertAll(int index, Iterable<E> iterable) { 179 void insertAll(int index, Iterable<E> iterable) {
171 if (index < 0 || index > length) { 180 if (index < 0 || index > length) {
172 throw new RangeError.range(index, 0, length); 181 throw new RangeError.range(index, 0, length);
173 } 182 }
174 // TODO(floitsch): we can probably detect more cases. 183 // TODO(floitsch): we can probably detect more cases.
175 if (iterable is! List && iterable is! Set) { 184 if (iterable is! List && iterable is! Set) {
176 iterable = iterable.toList(); 185 iterable = iterable.toList();
177 } 186 }
178 int insertionLength = iterable.length; 187 int insertionLength = iterable.length;
179 // There might be errors after the length change, in which case the list 188 // There might be errors after the length change, in which case the list
180 // will end up being modified but the operation not complete. Unless we 189 // will end up being modified but the operation not complete. Unless we
181 // always go through a "toList" we can't really avoid that. 190 // always go through a "toList" we can't really avoid that.
182 int len = _list.length; 191 int len = _list.length;
183 _list.length += insertionLength; 192 _list.length += insertionLength;
184 193
185 _list.setRange(index + insertionLength, this.length, this, index); 194 _list.setRange(index + insertionLength, this.length, this, index);
186 _list.setAll(index, iterable); 195 _list.setAll(index, iterable);
187 196
188 _notifyChangeLength(len, _list.length); 197 _notifyChangeLength(len, _list.length);
189 198
190 if (hasListObservers && insertionLength > 0) { 199 if (hasListObservers && insertionLength > 0) {
191 _recordChange(new ListChangeRecord(this, index, 200 _notifyListChange(index, addedCount: insertionLength);
192 addedCount: insertionLength));
193 } 201 }
194 } 202 }
195 203
196 void insert(int index, E element) { 204 void insert(int index, E element) {
197 if (index < 0 || index > length) { 205 if (index < 0 || index > length) {
198 throw new RangeError.range(index, 0, length); 206 throw new RangeError.range(index, 0, length);
199 } 207 }
200 if (index == length) { 208 if (index == length) {
201 add(element); 209 add(element);
202 return; 210 return;
203 } 211 }
204 // We are modifying the length just below the is-check. Without the check 212 // We are modifying the length just below the is-check. Without the check
205 // Array.copy could throw an exception, leaving the list in a bad state 213 // Array.copy could throw an exception, leaving the list in a bad state
206 // (with a length that has been increased, but without a new element). 214 // (with a length that has been increased, but without a new element).
207 if (index is! int) throw new ArgumentError(index); 215 if (index is! int) throw new ArgumentError(index);
208 _list.length++; 216 _list.length++;
209 _list.setRange(index + 1, length, this, index); 217 _list.setRange(index + 1, length, this, index);
210 218
211 _notifyChangeLength(_list.length - 1, _list.length); 219 _notifyChangeLength(_list.length - 1, _list.length);
212 if (hasListObservers) { 220 if (hasListObservers) {
213 _recordChange(new ListChangeRecord(this, index, addedCount: 1)); 221 _notifyListChange(index, addedCount: 1);
214 } 222 }
215 _list[index] = element; 223 _list[index] = element;
216 } 224 }
217 225
218
219 E removeAt(int index) { 226 E removeAt(int index) {
220 E result = this[index]; 227 E result = this[index];
221 removeRange(index, index + 1); 228 removeRange(index, index + 1);
222 return result; 229 return result;
223 } 230 }
224 231
225 void _rangeCheck(int start, int end) { 232 void _rangeCheck(int start, int end) {
226 if (start < 0 || start > this.length) { 233 if (start < 0 || start > this.length) {
227 throw new RangeError.range(start, 0, this.length); 234 throw new RangeError.range(start, 0, this.length);
228 } 235 }
229 if (end < start || end > this.length) { 236 if (end < start || end > this.length) {
230 throw new RangeError.range(end, start, this.length); 237 throw new RangeError.range(end, start, this.length);
231 } 238 }
232 } 239 }
233 240
234 void _recordChange(ListChangeRecord record) { 241 void _notifyListChange(int index, {List removed, int addedCount}) {
235 if (!hasListObservers) return; 242 if (!hasListObservers) return;
236
237 if (_listRecords == null) { 243 if (_listRecords == null) {
238 _listRecords = []; 244 _listRecords = [];
239 scheduleMicrotask(deliverListChanges); 245 scheduleMicrotask(deliverListChanges);
240 } 246 }
241 _listRecords.add(record); 247 _listRecords.add(new ListChangeRecord(this, index,
248 removed: removed, addedCount: addedCount));
242 } 249 }
243 250
244 void _notifyChangeLength(int oldValue, int newValue) { 251 void _notifyChangeLength(int oldValue, int newValue) {
245 notifyPropertyChange(#length, oldValue, newValue); 252 notifyPropertyChange(#length, oldValue, newValue);
246 notifyPropertyChange(#isEmpty, oldValue == 0, newValue == 0); 253 notifyPropertyChange(#isEmpty, oldValue == 0, newValue == 0);
247 notifyPropertyChange(#isNotEmpty, oldValue != 0, newValue != 0); 254 notifyPropertyChange(#isNotEmpty, oldValue != 0, newValue != 0);
248 } 255 }
249 256
250 /// Deprecated. Name had a typo, use [discardListChanges] instead.
251 @deprecated
252 void discardListChages() => discardListChanges();
253
254 void discardListChanges() { 257 void discardListChanges() {
255 // Leave _listRecords set so we don't schedule another delivery. 258 // Leave _listRecords set so we don't schedule another delivery.
256 if (_listRecords != null) _listRecords = []; 259 if (_listRecords != null) _listRecords = [];
257 } 260 }
258 261
259 bool deliverListChanges() { 262 bool deliverListChanges() {
260 if (_listRecords == null) return false; 263 if (_listRecords == null) return false;
261 var records = projectListSplices(this, _listRecords); 264 List<ListChangeRecord> records = projectListSplices(this, _listRecords);
262 _listRecords = null; 265 _listRecords = null;
263 266
264 if (hasListObservers && !records.isEmpty) { 267 if (hasListObservers && records.isNotEmpty) {
265 _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records)); 268 _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records));
266 return true; 269 return true;
267 } 270 }
268 return false; 271 return false;
269 } 272 }
270 273
271 /// Calculates the changes to the list, if lacking individual splice mutation 274 /// Calculates the changes to the list, if lacking individual splice mutation
272 /// information. 275 /// information.
273 /// 276 ///
274 /// This is not needed for change records produced by [ObservableList] itself, 277 /// This is not needed for change records produced by [ObservableList] itself,
275 /// but it can be used if the list instance was replaced by another list. 278 /// but it can be used if the list instance was replaced by another list.
276 /// 279 ///
277 /// The minimal set of splices can be synthesized given the previous state and 280 /// The minimal set of splices can be synthesized given the previous state and
278 /// final state of a list. The basic approach is to calculate the edit 281 /// final state of a list. The basic approach is to calculate the edit
279 /// distance matrix and choose the shortest path through it. 282 /// distance matrix and choose the shortest path through it.
280 /// 283 ///
281 /// Complexity is `O(l * p)` where `l` is the length of the current list and 284 /// Complexity is `O(l * p)` where `l` is the length of the current list and
282 /// `p` is the length of the old list. 285 /// `p` is the length of the old list.
283 static List<ListChangeRecord> calculateChangeRecords( 286 static List<ListChangeRecord> calculateChangeRecords(
284 List<Object> oldValue, List<Object> newValue) => 287 List<Object> oldValue, List<Object> newValue) =>
285 calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length); 288 calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length);
286 289
287 /// Updates the [previous] list using the change [records]. For added items, 290 /// Updates the [previous] list using the [changeRecords]. For added items,
288 /// the [current] list is used to find the current value. 291 /// the [current] list is used to find the current value.
289 static void applyChangeRecords(List<Object> previous, List<Object> current, 292 static void applyChangeRecords(List<Object> previous, List<Object> current,
290 List<ListChangeRecord> changeRecords) { 293 List<ListChangeRecord> changeRecords) {
291
292 if (identical(previous, current)) { 294 if (identical(previous, current)) {
293 throw new ArgumentError("can't use same list for previous and current"); 295 throw new ArgumentError("can't use same list for previous and current");
294 } 296 }
295 297
296 for (var change in changeRecords) { 298 for (ListChangeRecord change in changeRecords) {
297 int addEnd = change.index + change.addedCount; 299 int addEnd = change.index + change.addedCount;
298 int removeEnd = change.index + change.removed.length; 300 int removeEnd = change.index + change.removed.length;
299 301
300 var addedItems = current.getRange(change.index, addEnd); 302 Iterable addedItems = current.getRange(change.index, addEnd);
301 previous.replaceRange(change.index, removeEnd, addedItems); 303 previous.replaceRange(change.index, removeEnd, addedItems);
302 } 304 }
303 } 305 }
304 } 306 }
OLDNEW
« no previous file with comments | « packages/observable/lib/src/observable.dart ('k') | packages/observable/lib/src/observable_map.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698