OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 library web_ui.observe.list; |
| 6 |
| 7 import 'dart:collection'; |
| 8 import 'dart:collection-dev'; |
| 9 import 'package:web_ui/observe.dart'; |
| 10 |
| 11 // TODO(jmesserly): this should extend the real list implementation. |
| 12 // See http://dartbug.com/2600. The workaround was to copy+paste lots of code |
| 13 // from the VM. |
| 14 // TODO(jmesserly): also this shold extend Collection<E>, so we can delete |
| 15 // copy+paste code. Fix once we have mixins. |
| 16 /** |
| 17 * Represents an observable list of model values. If any items are added, |
| 18 * removed, or replaced, then observers that are registered with |
| 19 * [observe] will be notified. |
| 20 */ |
| 21 class ObservableList<E> extends Observable implements List<E> { |
| 22 /** The inner [List<E>] with the actual storage. */ |
| 23 final List<E> _list; |
| 24 |
| 25 /** |
| 26 * Creates an observable list of the given [length]. |
| 27 * |
| 28 * If no [length] argument is supplied an extendable list of |
| 29 * length 0 is created. |
| 30 * |
| 31 * If a [length] argument is supplied, a fixed size list of that |
| 32 * length is created. |
| 33 */ |
| 34 ObservableList([int length = 0]) : _list = new List<E>(length); |
| 35 |
| 36 /** |
| 37 * Creates an observable list with the elements of [other]. The order in |
| 38 * the list will be the order provided by the iterator of [other]. |
| 39 */ |
| 40 factory ObservableList.from(Iterable<E> other) |
| 41 => new ObservableList<E>()..addAll(other); |
| 42 |
| 43 // TODO(jmesserly): This should be on List. |
| 44 // See http://code.google.com/p/dart/issues/detail?id=947 |
| 45 bool remove(E item) { |
| 46 int i = indexOf(item); |
| 47 if (i == -1) return false; |
| 48 removeAt(i); |
| 49 return true; |
| 50 } |
| 51 |
| 52 // TODO(jmesserly): This should be on List, to match removeAt. |
| 53 // See http://code.google.com/p/dart/issues/detail?id=5375 |
| 54 void insertAt(int index, E item) => insertRange(index, 1, item); |
| 55 |
| 56 E get first => this[0]; |
| 57 |
| 58 Iterator<E> get iterator => new ListIterator<E>(this); |
| 59 |
| 60 int get length { |
| 61 if (observeReads) notifyRead(ChangeRecord.FIELD, 'length'); |
| 62 return _list.length; |
| 63 } |
| 64 |
| 65 set length(int value) { |
| 66 int len = _list.length; |
| 67 if (len == value) return; |
| 68 |
| 69 // Produce notifications if needed |
| 70 if (hasObservers) { |
| 71 if (value < len) { |
| 72 // Remove items, then adjust length |
| 73 for (int i = len - 1; i >= value; i--) { |
| 74 notifyChange(ChangeRecord.INDEX, i, null, null); |
| 75 } |
| 76 notifyChange(ChangeRecord.FIELD, 'length', len, value); |
| 77 } else { |
| 78 // Adjust length then add items |
| 79 notifyChange(ChangeRecord.FIELD, 'length', len, value); |
| 80 for (int i = len; i < value; i++) { |
| 81 notifyChange(ChangeRecord.INDEX, i, null, null); |
| 82 } |
| 83 } |
| 84 } |
| 85 |
| 86 _list.length = value; |
| 87 } |
| 88 |
| 89 E operator [](int index) { |
| 90 if (observeReads) notifyRead(ChangeRecord.INDEX, index); |
| 91 return _list[index]; |
| 92 } |
| 93 |
| 94 operator []=(int index, E value) { |
| 95 var oldValue = _list[index]; |
| 96 if (hasObservers) notifyChange(ChangeRecord.INDEX, index, oldValue, value); |
| 97 _list[index] = value; |
| 98 } |
| 99 |
| 100 void add(E value) { |
| 101 int len = _list.length; |
| 102 if (hasObservers) { |
| 103 notifyChange(ChangeRecord.FIELD, 'length', len, len + 1); |
| 104 notifyChange(ChangeRecord.INDEX, len, null, value); |
| 105 } |
| 106 _list.add(value); |
| 107 } |
| 108 |
| 109 // --------------------------------------------------------------------------- |
| 110 // Note: below this comment, methods are either: |
| 111 // * redirect to Arrays |
| 112 // * copy+paste from VM GrowableObjectArray. |
| 113 // The general idea is to have these methods operate in terms of our primitive |
| 114 // methods above, so they correctly track reads/writes. |
| 115 // --------------------------------------------------------------------------- |
| 116 |
| 117 E removeLast() { |
| 118 var len = length - 1; |
| 119 var elem = this[len]; |
| 120 length = len; |
| 121 return elem; |
| 122 } |
| 123 |
| 124 int indexOf(E element, [int start = 0]) => |
| 125 Arrays.indexOf(this, element, start, length); |
| 126 |
| 127 int lastIndexOf(E element, [int start]) => |
| 128 Arrays.lastIndexOf(this, element, start); |
| 129 |
| 130 ObservableList<E> getRange(int start, int length) { |
| 131 if (length == 0) return []; |
| 132 Arrays.rangeCheck(this, start, length); |
| 133 List list = new ObservableList<E>(length); |
| 134 Arrays.copy(this, start, list, 0, length); |
| 135 return list; |
| 136 } |
| 137 |
| 138 bool get isEmpty => length == 0; |
| 139 |
| 140 E get last => this[length - 1]; |
| 141 |
| 142 void addLast(E value) => add(value); |
| 143 |
| 144 void sort([compare = Comparable.compare]) => |
| 145 IterableMixinWorkaround.sortList(this, compare); |
| 146 |
| 147 void clear() { |
| 148 this.length = 0; |
| 149 } |
| 150 |
| 151 E removeAt(int index) { |
| 152 if (index is! int) throw new ArgumentError(index); |
| 153 E result = this[index]; |
| 154 int newLength = this.length - 1; |
| 155 Arrays.copy(this, |
| 156 index + 1, |
| 157 this, |
| 158 index, |
| 159 newLength - index); |
| 160 this.length = newLength; |
| 161 return result; |
| 162 } |
| 163 |
| 164 void setRange(int start, int length, List<E> from, [int startFrom = 0]) { |
| 165 Arrays.copy(from, startFrom, this, start, length); |
| 166 } |
| 167 |
| 168 void removeRange(int start, int length) { |
| 169 if (length == 0) { |
| 170 return; |
| 171 } |
| 172 Arrays.rangeCheck(this, start, length); |
| 173 Arrays.copy(this, |
| 174 start + length, |
| 175 this, |
| 176 start, |
| 177 this.length - length - start); |
| 178 this.length = this.length - length; |
| 179 } |
| 180 |
| 181 void insertRange(int start, int length, [E initialValue]) { |
| 182 if (length == 0) { |
| 183 return; |
| 184 } |
| 185 if ((length < 0) || (length is! int)) { |
| 186 throw new ArgumentError("invalid length specified $length"); |
| 187 } |
| 188 if (start < 0 || start > this.length) { |
| 189 throw new RangeError.value(start); |
| 190 } |
| 191 var oldLength = this.length; |
| 192 this.length = oldLength + length; // Will expand if needed. |
| 193 Arrays.copy(this, |
| 194 start, |
| 195 this, |
| 196 start + length, |
| 197 oldLength - start); |
| 198 for (int i = start; i < start + length; i++) { |
| 199 this[i] = initialValue; |
| 200 } |
| 201 } |
| 202 |
| 203 String toString() => Collections.collectionToString(this); |
| 204 |
| 205 // --------------------------------------------------------------------------- |
| 206 // Note: below this comment, methods are copy+paste from Collection<E> and |
| 207 // Iterable<E>. Remove when we have mixins. |
| 208 // --------------------------------------------------------------------------- |
| 209 |
| 210 void addAll(Iterable<E> elements) { |
| 211 for (E element in elements) add(element); |
| 212 } |
| 213 |
| 214 List<E> get reversed => new ReversedListView<E>(this, 0, length); |
| 215 |
| 216 void removeAll(Iterable elements) => |
| 217 IterableMixinWorkaround.removeAll(this, elements); |
| 218 void retainAll(Iterable elements) => |
| 219 IterableMixinWorkaround.retainAll(this, elements); |
| 220 void removeMatching(bool test(E element)) => |
| 221 IterableMixinWorkaround.removeMatching(this, test); |
| 222 void retainMatching(bool test(E element)) => |
| 223 IterableMixinWorkaround.retainMatching(this, test); |
| 224 Iterable mappedBy(f(E element)) => |
| 225 IterableMixinWorkaround.mappedByList(this, f); |
| 226 Iterable<E> where(bool f(E element)) => |
| 227 IterableMixinWorkaround.where(this, f); |
| 228 bool contains(E element) => IterableMixinWorkaround.contains(this, element); |
| 229 void forEach(void f(E element)) => IterableMixinWorkaround.forEach(this, f); |
| 230 dynamic reduce(var initialValue, |
| 231 dynamic combine(var previousValue, E element)) => |
| 232 IterableMixinWorkaround.reduce(this, initialValue, combine); |
| 233 bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f); |
| 234 String join([String separator]) => |
| 235 IterableMixinWorkaround.join(this, separator); |
| 236 bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f); |
| 237 List<E> toList() => new List<E>.from(this); |
| 238 Set<E> toSet() => new Set<E>.from(this); |
| 239 E min([int compare(E a, E b)]) => IterableMixinWorkaround.min(this, compare); |
| 240 E max([int compare(E a, E b)]) => IterableMixinWorkaround.max(this, compare); |
| 241 Iterable<E> take(int n) => IterableMixinWorkaround.takeList(this, n); |
| 242 Iterable<E> takeWhile(bool test(E value)) |
| 243 => IterableMixinWorkaround.takeWhile(this, test); |
| 244 Iterable<E> skip(int n) => IterableMixinWorkaround.skipList(this, n); |
| 245 Iterable<E> skipWhile(bool test(E value)) => |
| 246 IterableMixinWorkaround.skipWhile(this, test); |
| 247 E get single => IterableMixinWorkaround.single(this); |
| 248 E firstMatching(bool test(E value), { E orElse() }) => |
| 249 IterableMixinWorkaround.firstMatching(this, test, orElse); |
| 250 E lastMatching(bool test(E value), {E orElse()}) => |
| 251 IterableMixinWorkaround.lastMatching(this, test, orElse); |
| 252 E singleMatching(bool test(E value)) => |
| 253 IterableMixinWorkaround.singleMatching(this, test); |
| 254 E elementAt(int index) => IterableMixinWorkaround.elementAt(this, index); |
| 255 } |
OLD | NEW |