| 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> |
| 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 |