| 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);
|
| +}
|
|
|