| Index: client/observable/observable.dart
|
| ===================================================================
|
| --- client/observable/observable.dart (revision 4144)
|
| +++ client/observable/observable.dart (working copy)
|
| @@ -1,299 +0,0 @@
|
| -// Copyright (c) 2011, 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('observable');
|
| -
|
| -#import('dart:coreimpl');
|
| -
|
| -#source('ChangeEvent.dart');
|
| -#source('EventBatch.dart');
|
| -
|
| -/**
|
| - * An object whose changes are tracked and who can issue events notifying how it
|
| - * has been changed.
|
| - */
|
| -interface Observable {
|
| - /** Returns a globally unique identifier for the object. */
|
| - // TODO(sigmund): remove once dart supports maps with arbitrary keys.
|
| - final int uid;
|
| -
|
| - /** Listeners on this model. */
|
| - final List<ChangeListener> listeners;
|
| -
|
| - /** The parent observable to notify when this child is changed. */
|
| - final Observable parent;
|
| -
|
| - /**
|
| - * Adds a listener for changes on this observable instance. Returns whether
|
| - * the listener was added successfully.
|
| - */
|
| - bool addChangeListener(ChangeListener listener);
|
| -
|
| - /**
|
| - * Removes a listener for changes on this observable instance. Returns whether
|
| - * the listener was removed successfully.
|
| - */
|
| - bool removeChangeListener(ChangeListener listener);
|
| -}
|
| -
|
| -
|
| -/** Common functionality for observable objects. */
|
| -class AbstractObservable implements Observable {
|
| -
|
| - /** Unique id to identify this model in an event batch. */
|
| - final int uid;
|
| -
|
| - /** The parent observable to notify when this child is changed. */
|
| - final Observable parent;
|
| -
|
| - /** Listeners on this model. */
|
| - List<ChangeListener> listeners;
|
| -
|
| - /** Whether this object is currently observed by listeners or propagators. */
|
| - bool get isObserved() {
|
| - for (Observable obj = this; obj != null; obj = obj.parent) {
|
| - if (listeners.length > 0) {
|
| - return true;
|
| - }
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - AbstractObservable([Observable this.parent = null])
|
| - : uid = EventBatch.genUid(),
|
| - listeners = new List<ChangeListener>();
|
| -
|
| - bool addChangeListener(ChangeListener listener) {
|
| - if (listeners.indexOf(listener, 0) == -1) {
|
| - listeners.add(listener);
|
| - return true;
|
| - }
|
| -
|
| - return false;
|
| - }
|
| -
|
| - bool removeChangeListener(ChangeListener listener) {
|
| - // TODO(rnystrom): This is awkward without List.remove(e).
|
| - if (listeners.indexOf(listener, 0) != -1) {
|
| - bool found = false;
|
| - listeners = listeners.filter((e) => found || !(found = (e == listener)));
|
| - return true;
|
| - } else {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - void recordPropertyUpdate(String propertyName, newValue, oldValue) {
|
| - recordEvent(new ChangeEvent.property(
|
| - this, propertyName, newValue, oldValue));
|
| - }
|
| -
|
| - void recordListUpdate(int index, newValue, oldValue) {
|
| - recordEvent(new ChangeEvent.list(
|
| - this, ChangeEvent.UPDATE, index, newValue, oldValue));
|
| - }
|
| -
|
| - void recordListInsert(int index, newValue) {
|
| - recordEvent(new ChangeEvent.list(
|
| - this, ChangeEvent.INSERT, index, newValue, null));
|
| - }
|
| -
|
| - void recordListRemove(int index, oldValue) {
|
| - recordEvent(new ChangeEvent.list(
|
| - this, ChangeEvent.REMOVE, index, null, oldValue));
|
| - }
|
| -
|
| - void recordGlobalChange() {
|
| - recordEvent(new ChangeEvent.global(this));
|
| - }
|
| -
|
| - void recordEvent(ChangeEvent event) {
|
| - // Bail if no one cares about the event.
|
| - if (!isObserved) {
|
| - return;
|
| - }
|
| -
|
| - if (EventBatch.current != null) {
|
| - // Already in a batch, so just add it.
|
| - assert (!EventBatch.current.sealed);
|
| - // TODO(sigmund): measure the performance implications of this indirection
|
| - // and consider whether caching the summary object in this instance helps.
|
| - var summary = EventBatch.current.getEvents(this);
|
| - summary.addEvent(event);
|
| - } else {
|
| - // Not in a batch, so create a one-off one.
|
| - // TODO(rnystrom): Needing to do ignore and (null) here is awkward.
|
| - EventBatch.wrap((ignore) { recordEvent(event); })(null);
|
| - }
|
| - }
|
| -}
|
| -
|
| -/** A growable list that fires events when it's modified. */
|
| -class ObservableList<T>
|
| - extends AbstractObservable
|
| - implements List<T>, Observable {
|
| -
|
| - /** Underlying list. */
|
| - // TODO(rnystrom): Make this final if we get list.remove().
|
| - List<T> _internal;
|
| -
|
| - ObservableList([Observable parent = null])
|
| - : super(parent), _internal = new List<T>();
|
| -
|
| - T operator [](int index) => _internal[index];
|
| -
|
| - void operator []=(int index, T value) {
|
| - recordListUpdate(index, value, _internal[index]);
|
| - _internal[index] = value;
|
| - }
|
| -
|
| - int get length() => _internal.length;
|
| -
|
| - void set length(int value) {
|
| - _internal.length = value;
|
| - recordGlobalChange();
|
| - }
|
| -
|
| - void clear() {
|
| - _internal.clear();
|
| - recordGlobalChange();
|
| - }
|
| -
|
| - void sort(int compare(Object a, Object b)) {
|
| - _internal.sort(compare);
|
| - recordGlobalChange();
|
| - }
|
| -
|
| - void add(T element) {
|
| - recordListInsert(length, element);
|
| - _internal.add(element);
|
| - }
|
| -
|
| - void addLast(T element) {
|
| - add(element);
|
| - }
|
| -
|
| - void addAll(Collection<T> elements) {
|
| - for (T element in elements) {
|
| - add(element);
|
| - }
|
| - }
|
| -
|
| - int push(T element) {
|
| - recordListInsert(length, element);
|
| - _internal.add(element);
|
| - return _internal.length;
|
| - }
|
| -
|
| - T last() => _internal.last();
|
| -
|
| - T removeLast() {
|
| - final result = _internal.removeLast();
|
| - recordListRemove(length, result);
|
| - return result;
|
| - }
|
| -
|
| - T removeAt(int index) {
|
| - int i = 0;
|
| - T found = null;
|
| - _internal = _internal.filter(bool _(element) {
|
| - if (i++ == index) {
|
| - found = element;
|
| - return false;
|
| - }
|
| - return true;
|
| - });
|
| - if (found != null) {
|
| - recordListRemove(index, found);
|
| - }
|
| - return found;
|
| - }
|
| -
|
| - int indexOf(T element, [int start = 0]) {
|
| - return _internal.indexOf(element, start);
|
| - }
|
| -
|
| - int lastIndexOf(T element, [int start = null]) {
|
| - if (start === null) start = length - 1;
|
| - return _internal.lastIndexOf(element, start);
|
| - }
|
| -
|
| - bool removeFirstElement(T element) {
|
| - // the removeAt above will record the event.
|
| - return (removeAt(indexOf(element, 0)) != null);
|
| - }
|
| -
|
| - int removeAllElements(T element) {
|
| - int count = 0;
|
| - for (int i = 0; i < length; i++) {
|
| - if (_internal[i] == element) {
|
| - // the removeAt above will record the event.
|
| - removeAt(i);
|
| - // adjust index since remove shifted elements.
|
| - i--;
|
| - count++;
|
| - }
|
| - }
|
| - return count;
|
| - }
|
| -
|
| - void copyFrom(List<T> src, int srcStart, int dstStart, int count) {
|
| - Arrays.copy(src, srcStart, this, dstStart, count);
|
| - }
|
| -
|
| - void setRange(int start, int length, List from, [int startFrom = 0]) {
|
| - throw const NotImplementedException();
|
| - }
|
| -
|
| - void removeRange(int start, int length) {
|
| - throw const NotImplementedException();
|
| - }
|
| -
|
| - void insertRange(int start, int length, [initialValue = null]) {
|
| - throw const NotImplementedException();
|
| - }
|
| -
|
| - List getRange(int start, int length) {
|
| - throw const NotImplementedException();
|
| - }
|
| -
|
| - // Iterable<T>:
|
| - Iterator<T> iterator() => _internal.iterator();
|
| -
|
| - // Collection<T>:
|
| - Collection<T> filter(bool f(T element)) => _internal.filter(f);
|
| - Collection map(f(T element)) => _internal.map(f);
|
| - bool every(bool f(T element)) => _internal.every(f);
|
| - bool some(bool f(T element)) => _internal.some(f);
|
| - void forEach(void f(T element)) { _internal.forEach(f); }
|
| - bool isEmpty() => length == 0;
|
| -}
|
| -
|
| -// TODO(jmesserly): is this too granular? Other similar systems make whole
|
| -// classes observable instead of individual fields. The memory cost of having
|
| -// every field effectively boxed, plus having a listeners list is likely too
|
| -// much. Also, making a value observable necessitates adding ".value" to lots
|
| -// of places, and constructing all fields with the verbose
|
| -// "new ObservableValue<DataType>(myValue)".
|
| -/** A wrapper around a single value whose change can be observed. */
|
| -class ObservableValue<T> extends AbstractObservable {
|
| - ObservableValue(T value, [Observable parent = null])
|
| - : super(parent), _value = value;
|
| -
|
| - T get value() => _value;
|
| -
|
| - void set value(T newValue) {
|
| - // Only fire on an actual change.
|
| - // TODO(terry): An object identity test === is needed. Each DataSource has
|
| - // its own operator == which does a value compare. Which
|
| - // equality check should be done?
|
| - if (newValue !== _value) {
|
| - final oldValue = _value;
|
| - _value = newValue;
|
| - recordPropertyUpdate("value", newValue, oldValue);
|
| - }
|
| - }
|
| -
|
| - T _value;
|
| -}
|
|
|