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 part of observe; |
6 | 6 |
7 /** | 7 /** |
8 * Represents an observable list of model values. If any items are added, | 8 * Represents an observable list of model values. If any items are added, |
9 * removed, or replaced, then observers that are listening to [changes] | 9 * removed, or replaced, then observers that are listening to [changes] |
10 * will be notified. | 10 * will be notified. |
11 */ | 11 */ |
12 // TODO(jmesserly): remove implements List<E> once we can extend ListBase<E> | 12 // TODO(jmesserly): remove implements List<E> once we can extend ListBase<E> |
Siggi Cherem (dart-lang)
2013/07/22 21:47:22
remove todo =)?
Jennifer Messerly
2013/07/22 22:38:39
Done.
| |
13 class ObservableList<E> extends _ListBaseWorkaround with ObservableMixin | 13 class ObservableList<E> extends ListBase<E> with ChangeNotifierMixin { |
14 implements List<E> { | 14 List<ListChangeRecord> _listRecords; |
15 List<ListChangeRecord> _records; | |
16 | 15 |
17 /** The inner [List<E>] with the actual storage. */ | 16 /** The inner [List<E>] with the actual storage. */ |
18 final List<E> _list; | 17 final List<E> _list; |
19 | 18 |
20 /** | 19 /** |
21 * Creates an observable list of the given [length]. | 20 * Creates an observable list of the given [length]. |
22 * | 21 * |
23 * If no [length] argument is supplied an extendable list of | 22 * If no [length] argument is supplied an extendable list of |
24 * length 0 is created. | 23 * length 0 is created. |
25 * | 24 * |
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
174 void _rangeCheck(int start, int end) { | 173 void _rangeCheck(int start, int end) { |
175 if (start < 0 || start > this.length) { | 174 if (start < 0 || start > this.length) { |
176 throw new RangeError.range(start, 0, this.length); | 175 throw new RangeError.range(start, 0, this.length); |
177 } | 176 } |
178 if (end < start || end > this.length) { | 177 if (end < start || end > this.length) { |
179 throw new RangeError.range(end, start, this.length); | 178 throw new RangeError.range(end, start, this.length); |
180 } | 179 } |
181 } | 180 } |
182 | 181 |
183 void _recordChange(ListChangeRecord record) { | 182 void _recordChange(ListChangeRecord record) { |
184 if (_records == null) { | 183 if (_listRecords == null) { |
185 _records = []; | 184 _listRecords = []; |
186 queueChangeRecords(_summarizeRecords); | 185 runAsync(deliverChanges); |
187 } | 186 } |
188 _records.add(record); | 187 _listRecords.add(record); |
188 } | |
189 | |
190 bool deliverChanges() { | |
191 if (_listRecords == null) return false; | |
192 _summarizeRecords(); | |
193 return super.deliverChanges(); | |
189 } | 194 } |
190 | 195 |
191 /** | 196 /** |
192 * We need to summarize change records. Consumers of these records want to | 197 * We need to summarize change records. Consumers of these records want to |
193 * apply the batch sequentially, and ensure that they can find inserted | 198 * apply the batch sequentially, and ensure that they can find inserted |
194 * items by looking at that position in the list. This property does not | 199 * items by looking at that position in the list. This property does not |
195 * hold in our record-as-you-go records. Consider: | 200 * hold in our record-as-you-go records. Consider: |
196 * | 201 * |
197 * var model = toObservable(['a', 'b']); | 202 * var model = toObservable(['a', 'b']); |
198 * model.removeAt(1); | 203 * model.removeAt(1); |
(...skipping 10 matching lines...) Expand all Loading... | |
209 * new list of changes records. This has the advantage of not depending | 214 * new list of changes records. This has the advantage of not depending |
210 * on the actual *values*, so we don't need to perform N^2 edit | 215 * on the actual *values*, so we don't need to perform N^2 edit |
211 */ | 216 */ |
212 // TODO(jmesserly): there's probably something smarter here, but this | 217 // TODO(jmesserly): there's probably something smarter here, but this |
213 // algorithm is pretty simple. It has complexity equivalent to the original | 218 // algorithm is pretty simple. It has complexity equivalent to the original |
214 // list modifications. | 219 // list modifications. |
215 // One simple idea: we can simply update the index map as we do the operations | 220 // One simple idea: we can simply update the index map as we do the operations |
216 // to the list, then produce the records at the end. | 221 // to the list, then produce the records at the end. |
217 void _summarizeRecords() { | 222 void _summarizeRecords() { |
218 int oldLength = length; | 223 int oldLength = length; |
219 for (var r in _records) { | 224 for (var r in _listRecords) { |
220 oldLength += r.removedCount - r.addedCount; | 225 oldLength += r.removedCount - r.addedCount; |
221 } | 226 } |
222 | 227 |
223 if (length != oldLength) { | 228 if (length != oldLength) { |
224 notifyPropertyChange(const Symbol('length'), oldLength, length); | 229 notifyPropertyChange(const Symbol('length'), oldLength, length); |
225 } | 230 } |
226 | 231 |
227 if (_records.length == 1) { | 232 if (_listRecords.length == 1) { |
228 notifyChange(_records[0]); | 233 notifyChange(_listRecords[0]); |
229 _records = null; | 234 _listRecords = null; |
230 return; | 235 return; |
231 } | 236 } |
232 | 237 |
233 var items = []; | 238 var items = []; |
234 for (int i = 0; i < oldLength; i++) items.add(i); | 239 for (int i = 0; i < oldLength; i++) items.add(i); |
235 for (var r in _records) { | 240 for (var r in _listRecords) { |
236 items.removeRange(r.index, r.index + r.removedCount); | 241 items.removeRange(r.index, r.index + r.removedCount); |
237 | 242 |
238 // Represent inserts with -1. | 243 // Represent inserts with -1. |
239 items.insertAll(r.index, new List.filled(r.addedCount, -1)); | 244 items.insertAll(r.index, new List.filled(r.addedCount, -1)); |
240 } | 245 } |
241 assert(items.length == length); | 246 assert(items.length == length); |
242 | 247 |
243 _records = null; | 248 _listRecords = null; |
244 | 249 |
245 int index = 0; | 250 int index = 0; |
246 int offset = 0; | 251 int offset = 0; |
247 while (index < items.length) { | 252 while (index < items.length) { |
248 // Skip unchanged items. | 253 // Skip unchanged items. |
249 while (index < items.length && items[index] == index + offset) { | 254 while (index < items.length && items[index] == index + offset) { |
250 index++; | 255 index++; |
251 } | 256 } |
252 | 257 |
253 // Find inserts | 258 // Find inserts |
(...skipping 13 matching lines...) Expand all Loading... | |
267 | 272 |
268 if (added > 0 || removed > 0) { | 273 if (added > 0 || removed > 0) { |
269 notifyChange(new ListChangeRecord(startIndex, addedCount: added, | 274 notifyChange(new ListChangeRecord(startIndex, addedCount: added, |
270 removedCount: removed)); | 275 removedCount: removed)); |
271 } | 276 } |
272 | 277 |
273 offset += removed - added; | 278 offset += removed - added; |
274 } | 279 } |
275 } | 280 } |
276 } | 281 } |
277 | |
278 // TODO(jmesserly): bogus type to workaround spurious VM bug with generic base | |
279 // class and mixins. | |
280 abstract class _ListBaseWorkaround extends ListBase<dynamic> {} | |
OLD | NEW |