| 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 library observe.src.observable_list; | 5 library observe.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 import 'package:observe/observe.dart'; |
| 10 import 'list_diff.dart' show projectListSplices, calcSplices; | 10 import 'list_diff.dart' show projectListSplices, calcSplices; |
| 11 | 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. | |
| 16 */ | |
| 17 class ObservableList<E> extends ListBase<E> with ChangeNotifier { | 15 class ObservableList<E> extends ListBase<E> with ChangeNotifier { |
| 18 List<ListChangeRecord> _listRecords; | 16 List<ListChangeRecord> _listRecords; |
| 19 | 17 |
| 20 StreamController _listChanges; | 18 StreamController _listChanges; |
| 21 | 19 |
| 22 /** The inner [List<E>] with the actual storage. */ | 20 /// The inner [List<E>] with the actual storage. |
| 23 final List<E> _list; | 21 final List<E> _list; |
| 24 | 22 |
| 25 /** | 23 /// Creates an observable list of the given [length]. |
| 26 * Creates an observable list of the given [length]. | 24 /// |
| 27 * | 25 /// If no [length] argument is supplied an extendable list of |
| 28 * If no [length] argument is supplied an extendable list of | 26 /// length 0 is created. |
| 29 * length 0 is created. | 27 /// |
| 30 * | 28 /// If a [length] argument is supplied, a fixed size list of that |
| 31 * If a [length] argument is supplied, a fixed size list of that | 29 /// length is created. |
| 32 * length is created. | |
| 33 */ | |
| 34 ObservableList([int length]) | 30 ObservableList([int length]) |
| 35 : _list = length != null ? new List<E>(length) : <E>[]; | 31 : _list = length != null ? new List<E>(length) : <E>[]; |
| 36 | 32 |
| 37 /** | 33 /// Creates an observable list with the elements of [other]. The order in |
| 38 * 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]. |
| 39 * the list will be the order provided by the iterator of [other]. | |
| 40 */ | |
| 41 factory ObservableList.from(Iterable<E> other) => | 35 factory ObservableList.from(Iterable<E> other) => |
| 42 new ObservableList<E>()..addAll(other); | 36 new ObservableList<E>()..addAll(other); |
| 43 | 37 |
| 44 /** | 38 /// The stream of summarized list changes, delivered asynchronously. |
| 45 * The stream of summarized list changes, delivered asynchronously. | 39 /// |
| 46 * | 40 /// Each list change record contains information about an individual mutation. |
| 47 * Each list change record contains information about an individual mutation. | 41 /// The records are projected so they can be applied sequentially. For |
| 48 * The records are projected so they can be applied sequentially. For example, | 42 /// example, this set of mutations: |
| 49 * this set of mutations: | 43 /// |
| 50 * | 44 /// var model = new ObservableList.from(['a', 'b']); |
| 51 * var model = new ObservableList.from(['a', 'b']); | 45 /// model.listChanges.listen((records) => records.forEach(print)); |
| 52 * model.listChanges.listen((records) => records.forEach(print)); | 46 /// model.removeAt(1); |
| 53 * model.removeAt(1); | 47 /// model.insertAll(0, ['c', 'd', 'e']); |
| 54 * model.insertAll(0, ['c', 'd', 'e']); | 48 /// model.removeRange(1, 3); |
| 55 * model.removeRange(1, 3); | 49 /// model.insert(1, 'f'); |
| 56 * model.insert(1, 'f'); | 50 /// |
| 57 * | 51 /// The change records will be summarized so they can be "played back", using |
| 58 * 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: |
| 59 * the final list positions to figure out which item was added: | 53 /// |
| 60 * | 54 /// #<ListChangeRecord index: 0, removed: [], addedCount: 2> |
| 61 * #<ListChangeRecord index: 0, removed: [], addedCount: 2> | 55 /// #<ListChangeRecord index: 3, removed: [b], addedCount: 0> |
| 62 * #<ListChangeRecord index: 3, removed: [b], addedCount: 0> | 56 /// |
| 63 * | 57 /// [deliverChanges] can be called to force synchronous delivery. |
| 64 * [deliverChanges] can be called to force synchronous delivery. | |
| 65 */ | |
| 66 Stream<List<ListChangeRecord>> get listChanges { | 58 Stream<List<ListChangeRecord>> get listChanges { |
| 67 if (_listChanges == null) { | 59 if (_listChanges == null) { |
| 68 // TODO(jmesserly): split observed/unobserved notions? | 60 // TODO(jmesserly): split observed/unobserved notions? |
| 69 _listChanges = new StreamController.broadcast(sync: true, | 61 _listChanges = new StreamController.broadcast(sync: true, |
| 70 onCancel: () { _listChanges = null; }); | 62 onCancel: () { _listChanges = null; }); |
| 71 } | 63 } |
| 72 return _listChanges.stream; | 64 return _listChanges.stream; |
| 73 } | 65 } |
| 74 | 66 |
| 75 bool get _hasListObservers => | 67 bool get _hasListObservers => |
| (...skipping 189 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 265 var records = projectListSplices(this, _listRecords); | 257 var records = projectListSplices(this, _listRecords); |
| 266 _listRecords = null; | 258 _listRecords = null; |
| 267 | 259 |
| 268 if (_hasListObservers && !records.isEmpty) { | 260 if (_hasListObservers && !records.isEmpty) { |
| 269 _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records)); | 261 _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records)); |
| 270 return true; | 262 return true; |
| 271 } | 263 } |
| 272 return false; | 264 return false; |
| 273 } | 265 } |
| 274 | 266 |
| 275 /** | 267 /// Calculates the changes to the list, if lacking individual splice mutation |
| 276 * Calculates the changes to the list, if lacking individual splice mutation | 268 /// information. |
| 277 * information. | 269 /// |
| 278 * | 270 /// This is not needed for change records produced by [ObservableList] itself, |
| 279 * This is not needed for change records produced by [ObservableList] itself, | 271 /// but it can be used if the list instance was replaced by another list. |
| 280 * but it can be used if the list instance was replaced by another list. | 272 /// |
| 281 * | 273 /// The minimal set of splices can be synthesized given the previous state and |
| 282 * The minimal set of splices can be synthesized given the previous state and | 274 /// final state of a list. The basic approach is to calculate the edit |
| 283 * final state of a list. The basic approach is to calculate the edit distance | 275 /// distance matrix and choose the shortest path through it. |
| 284 * matrix and choose the shortest path through it. | 276 /// |
| 285 * | 277 /// Complexity is `O(l * p)` where `l` is the length of the current list and |
| 286 * Complexity is `O(l * p)` where `l` is the length of the current list and | 278 /// `p` is the length of the old list. |
| 287 * `p` is the length of the old list. | |
| 288 */ | |
| 289 static List<ListChangeRecord> calculateChangeRecords( | 279 static List<ListChangeRecord> calculateChangeRecords( |
| 290 List<Object> oldValue, List<Object> newValue) => | 280 List<Object> oldValue, List<Object> newValue) => |
| 291 calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length); | 281 calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length); |
| 292 | 282 |
| 293 /** | 283 /// Updates the [previous] list using the change [records]. For added items, |
| 294 * Updates the [previous] list using the change [records]. For added items, | 284 /// the [current] list is used to find the current value. |
| 295 * the [current] list is used to find the current value. | |
| 296 */ | |
| 297 static void applyChangeRecords(List<Object> previous, List<Object> current, | 285 static void applyChangeRecords(List<Object> previous, List<Object> current, |
| 298 List<ListChangeRecord> changeRecords) { | 286 List<ListChangeRecord> changeRecords) { |
| 299 | 287 |
| 300 if (identical(previous, current)) { | 288 if (identical(previous, current)) { |
| 301 throw new ArgumentError("can't use same list for previous and current"); | 289 throw new ArgumentError("can't use same list for previous and current"); |
| 302 } | 290 } |
| 303 | 291 |
| 304 for (var change in changeRecords) { | 292 for (var change in changeRecords) { |
| 305 int addEnd = change.index + change.addedCount; | 293 int addEnd = change.index + change.addedCount; |
| 306 int removeEnd = change.index + change.removed.length; | 294 int removeEnd = change.index + change.removed.length; |
| 307 | 295 |
| 308 var addedItems = current.getRange(change.index, addEnd); | 296 var addedItems = current.getRange(change.index, addEnd); |
| 309 previous.replaceRange(change.index, removeEnd, addedItems); | 297 previous.replaceRange(change.index, removeEnd, addedItems); |
| 310 } | 298 } |
| 311 } | 299 } |
| 312 } | 300 } |
| OLD | NEW |