Index: lib/observe/list.dart |
diff --git a/lib/observe/list.dart b/lib/observe/list.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b4f16e72b814c4e78710eba74ac2973c014eb70e |
--- /dev/null |
+++ b/lib/observe/list.dart |
@@ -0,0 +1,255 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library web_ui.observe.list; |
+ |
+import 'dart:collection'; |
+import 'dart:collection-dev'; |
+import 'package:web_ui/observe.dart'; |
+ |
+// TODO(jmesserly): this should extend the real list implementation. |
+// See http://dartbug.com/2600. The workaround was to copy+paste lots of code |
+// from the VM. |
+// TODO(jmesserly): also this shold extend Collection<E>, so we can delete |
+// copy+paste code. Fix once we have mixins. |
+/** |
+ * Represents an observable list of model values. If any items are added, |
+ * removed, or replaced, then observers that are registered with |
+ * [observe] will be notified. |
+ */ |
+class ObservableList<E> extends Observable implements List<E> { |
+ /** The inner [List<E>] with the actual storage. */ |
+ final List<E> _list; |
+ |
+ /** |
+ * Creates an observable list of the given [length]. |
+ * |
+ * If no [length] argument is supplied an extendable list of |
+ * length 0 is created. |
+ * |
+ * If a [length] argument is supplied, a fixed size list of that |
+ * length is created. |
+ */ |
+ ObservableList([int length = 0]) : _list = new List<E>(length); |
+ |
+ /** |
+ * Creates an observable list with the elements of [other]. The order in |
+ * the list will be the order provided by the iterator of [other]. |
+ */ |
+ factory ObservableList.from(Iterable<E> other) |
+ => new ObservableList<E>()..addAll(other); |
+ |
+ // TODO(jmesserly): This should be on List. |
+ // See http://code.google.com/p/dart/issues/detail?id=947 |
+ bool remove(E item) { |
+ int i = indexOf(item); |
+ if (i == -1) return false; |
+ removeAt(i); |
+ return true; |
+ } |
+ |
+ // TODO(jmesserly): This should be on List, to match removeAt. |
+ // See http://code.google.com/p/dart/issues/detail?id=5375 |
+ void insertAt(int index, E item) => insertRange(index, 1, item); |
+ |
+ E get first => this[0]; |
+ |
+ Iterator<E> get iterator => new ListIterator<E>(this); |
+ |
+ int get length { |
+ if (observeReads) notifyRead(ChangeRecord.FIELD, 'length'); |
+ return _list.length; |
+ } |
+ |
+ set length(int value) { |
+ int len = _list.length; |
+ if (len == value) return; |
+ |
+ // Produce notifications if needed |
+ if (hasObservers) { |
+ if (value < len) { |
+ // Remove items, then adjust length |
+ for (int i = len - 1; i >= value; i--) { |
+ notifyChange(ChangeRecord.INDEX, i, null, null); |
+ } |
+ notifyChange(ChangeRecord.FIELD, 'length', len, value); |
+ } else { |
+ // Adjust length then add items |
+ notifyChange(ChangeRecord.FIELD, 'length', len, value); |
+ for (int i = len; i < value; i++) { |
+ notifyChange(ChangeRecord.INDEX, i, null, null); |
+ } |
+ } |
+ } |
+ |
+ _list.length = value; |
+ } |
+ |
+ E operator [](int index) { |
+ if (observeReads) notifyRead(ChangeRecord.INDEX, index); |
+ return _list[index]; |
+ } |
+ |
+ operator []=(int index, E value) { |
+ var oldValue = _list[index]; |
+ if (hasObservers) notifyChange(ChangeRecord.INDEX, index, oldValue, value); |
+ _list[index] = value; |
+ } |
+ |
+ void add(E value) { |
+ int len = _list.length; |
+ if (hasObservers) { |
+ notifyChange(ChangeRecord.FIELD, 'length', len, len + 1); |
+ notifyChange(ChangeRecord.INDEX, len, null, value); |
+ } |
+ _list.add(value); |
+ } |
+ |
+ // --------------------------------------------------------------------------- |
+ // Note: below this comment, methods are either: |
+ // * redirect to Arrays |
+ // * copy+paste from VM GrowableObjectArray. |
+ // The general idea is to have these methods operate in terms of our primitive |
+ // methods above, so they correctly track reads/writes. |
+ // --------------------------------------------------------------------------- |
+ |
+ E removeLast() { |
+ var len = length - 1; |
+ var elem = this[len]; |
+ length = len; |
+ return elem; |
+ } |
+ |
+ int indexOf(E element, [int start = 0]) => |
+ Arrays.indexOf(this, element, start, length); |
+ |
+ int lastIndexOf(E element, [int start]) => |
+ Arrays.lastIndexOf(this, element, start); |
+ |
+ ObservableList<E> getRange(int start, int length) { |
+ if (length == 0) return []; |
+ Arrays.rangeCheck(this, start, length); |
+ List list = new ObservableList<E>(length); |
+ Arrays.copy(this, start, list, 0, length); |
+ return list; |
+ } |
+ |
+ bool get isEmpty => length == 0; |
+ |
+ E get last => this[length - 1]; |
+ |
+ void addLast(E value) => add(value); |
+ |
+ void sort([compare = Comparable.compare]) => |
+ IterableMixinWorkaround.sortList(this, compare); |
+ |
+ void clear() { |
+ this.length = 0; |
+ } |
+ |
+ E removeAt(int index) { |
+ if (index is! int) throw new ArgumentError(index); |
+ E result = this[index]; |
+ int newLength = this.length - 1; |
+ Arrays.copy(this, |
+ index + 1, |
+ this, |
+ index, |
+ newLength - index); |
+ this.length = newLength; |
+ return result; |
+ } |
+ |
+ void setRange(int start, int length, List<E> from, [int startFrom = 0]) { |
+ Arrays.copy(from, startFrom, this, start, length); |
+ } |
+ |
+ void removeRange(int start, int length) { |
+ if (length == 0) { |
+ return; |
+ } |
+ Arrays.rangeCheck(this, start, length); |
+ Arrays.copy(this, |
+ start + length, |
+ this, |
+ start, |
+ this.length - length - start); |
+ this.length = this.length - length; |
+ } |
+ |
+ void insertRange(int start, int length, [E initialValue]) { |
+ if (length == 0) { |
+ return; |
+ } |
+ if ((length < 0) || (length is! int)) { |
+ throw new ArgumentError("invalid length specified $length"); |
+ } |
+ if (start < 0 || start > this.length) { |
+ throw new RangeError.value(start); |
+ } |
+ var oldLength = this.length; |
+ this.length = oldLength + length; // Will expand if needed. |
+ Arrays.copy(this, |
+ start, |
+ this, |
+ start + length, |
+ oldLength - start); |
+ for (int i = start; i < start + length; i++) { |
+ this[i] = initialValue; |
+ } |
+ } |
+ |
+ String toString() => Collections.collectionToString(this); |
+ |
+ // --------------------------------------------------------------------------- |
+ // Note: below this comment, methods are copy+paste from Collection<E> and |
+ // Iterable<E>. Remove when we have mixins. |
+ // --------------------------------------------------------------------------- |
+ |
+ void addAll(Iterable<E> elements) { |
+ for (E element in elements) add(element); |
+ } |
+ |
+ List<E> get reversed => new ReversedListView<E>(this, 0, length); |
+ |
+ void removeAll(Iterable elements) => |
+ IterableMixinWorkaround.removeAll(this, elements); |
+ void retainAll(Iterable elements) => |
+ IterableMixinWorkaround.retainAll(this, elements); |
+ void removeMatching(bool test(E element)) => |
+ IterableMixinWorkaround.removeMatching(this, test); |
+ void retainMatching(bool test(E element)) => |
+ IterableMixinWorkaround.retainMatching(this, test); |
+ Iterable mappedBy(f(E element)) => |
+ IterableMixinWorkaround.mappedByList(this, f); |
+ Iterable<E> where(bool f(E element)) => |
+ IterableMixinWorkaround.where(this, f); |
+ bool contains(E element) => IterableMixinWorkaround.contains(this, element); |
+ void forEach(void f(E element)) => IterableMixinWorkaround.forEach(this, f); |
+ dynamic reduce(var initialValue, |
+ dynamic combine(var previousValue, E element)) => |
+ IterableMixinWorkaround.reduce(this, initialValue, combine); |
+ bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f); |
+ String join([String separator]) => |
+ IterableMixinWorkaround.join(this, separator); |
+ bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f); |
+ List<E> toList() => new List<E>.from(this); |
+ Set<E> toSet() => new Set<E>.from(this); |
+ E min([int compare(E a, E b)]) => IterableMixinWorkaround.min(this, compare); |
+ E max([int compare(E a, E b)]) => IterableMixinWorkaround.max(this, compare); |
+ Iterable<E> take(int n) => IterableMixinWorkaround.takeList(this, n); |
+ Iterable<E> takeWhile(bool test(E value)) |
+ => IterableMixinWorkaround.takeWhile(this, test); |
+ Iterable<E> skip(int n) => IterableMixinWorkaround.skipList(this, n); |
+ Iterable<E> skipWhile(bool test(E value)) => |
+ IterableMixinWorkaround.skipWhile(this, test); |
+ E get single => IterableMixinWorkaround.single(this); |
+ E firstMatching(bool test(E value), { E orElse() }) => |
+ IterableMixinWorkaround.firstMatching(this, test, orElse); |
+ E lastMatching(bool test(E value), {E orElse()}) => |
+ IterableMixinWorkaround.lastMatching(this, test, orElse); |
+ E singleMatching(bool test(E value)) => |
+ IterableMixinWorkaround.singleMatching(this, test); |
+ E elementAt(int index) => IterableMixinWorkaround.elementAt(this, index); |
+} |