blob: 0ce07a99fc3bde8f38ca7533088a4ca12c19d89b [file] [log] [blame]
// 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.
// @dart = 2.9
library observable;
part 'ChangeEvent.dart';
part 'EventBatch.dart';
/**
* An object whose changes are tracked and who can issue events notifying how it
* has been changed.
*/
abstract class Observable {
/** Returns a globally unique identifier for the object. */
// TODO(sigmund): remove once dart supports maps with arbitrary keys.
int get uid;
/** Listeners on this model. */
List<ChangeListener> get listeners;
/** The parent observable to notify when this child is changed. */
Observable get 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) {
var index = listeners.indexOf(listener, 0);
if (index != -1) {
listeners.removeAt(index);
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])
: _internal = new List<T>(),
super(parent);
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;
List<R> cast<R>() => _internal.cast<R>();
Iterable<R> whereType<R>() => _internal.whereType<R>();
List<T> operator +(List<T> other) => _internal + other;
Iterable<T> followedBy(Iterable<T> other) => _internal.followedBy(other);
int indexWhere(bool test(T element), [int start = 0]) =>
_internal.indexWhere(test, start);
int lastIndexWhere(bool test(T element), [int start]) =>
_internal.lastIndexWhere(test, start);
void set length(int value) {
_internal.length = value;
recordGlobalChange();
}
void clear() {
_internal.clear();
recordGlobalChange();
}
Iterable<T> get reversed => _internal.reversed;
void sort([int compare(T a, T b)]) {
//if (compare == null) compare = (u, v) => Comparable.compare(u, v);
_internal.sort(compare);
recordGlobalChange();
}
void add(T element) {
recordListInsert(length, element);
_internal.add(element);
}
void addAll(Iterable<T> elements) {
for (T element in elements) {
add(element);
}
}
int push(T element) {
recordListInsert(length, element);
_internal.add(element);
return _internal.length;
}
T get first => _internal.first;
void set first(T value) {
_internal.first = value;
}
T get last => _internal.last;
void set last(T value) {
_internal.last = value;
}
T get single => _internal.single;
void insert(int index, T element) {
_internal.insert(index, element);
recordListInsert(index, element);
}
void insertAll(int index, Iterable<T> iterable) {
throw new UnimplementedError();
}
void setAll(int index, Iterable<T> iterable) {
throw new UnimplementedError();
}
T removeLast() {
final result = _internal.removeLast();
recordListRemove(length, result);
return result;
}
T removeAt(int index) {
T result = _internal.removeAt(index);
recordListRemove(index, result);
return result;
}
int indexOf(Object element, [int start = 0]) {
return _internal.indexOf(element, start);
}
int lastIndexOf(Object element, [int start]) {
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) {
List dst = this;
if (srcStart == null) srcStart = 0;
if (dstStart == null) dstStart = 0;
if (srcStart < dstStart) {
for (int i = srcStart + count - 1, j = dstStart + count - 1;
i >= srcStart;
i--, j--) {
dst[j] = src[i];
}
} else {
for (int i = srcStart, j = dstStart; i < srcStart + count; i++, j++) {
dst[j] = src[i];
}
}
}
void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) {
throw new UnimplementedError();
}
void removeRange(int start, int end) {
throw new UnimplementedError();
}
void replaceRange(int start, int end, Iterable<T> iterable) {
throw new UnimplementedError();
}
void fillRange(int start, int end, [T fillValue]) {
throw new UnimplementedError();
}
List<T> sublist(int start, [int end]) {
throw new UnimplementedError();
}
Iterable<T> getRange(int start, int end) {
throw new UnimplementedError();
}
bool contains(Object element) {
throw new UnimplementedError();
}
T reduce(T combine(T previousValue, T element)) {
throw new UnimplementedError();
}
R fold<R>(R initialValue, R combine(R previousValue, T element)) {
throw new UnimplementedError();
}
// Iterable<T>:
Iterator<T> get iterator => _internal.iterator;
Iterable<T> where(bool f(T element)) => _internal.where(f);
Iterable<R> map<R>(R f(T element)) => _internal.map(f);
Iterable<R> expand<R>(Iterable<R> f(T element)) => _internal.expand(f);
List<T> skip(int count) => _internal.skip(count);
List<T> take(int count) => _internal.take(count);
bool every(bool f(T element)) => _internal.every(f);
bool any(bool f(T element)) => _internal.any(f);
void forEach(void f(T element)) {
_internal.forEach(f);
}
String join([String separator = ""]) => _internal.join(separator);
T firstWhere(bool test(T value), {T orElse()}) {
return _internal.firstWhere(test, orElse: orElse);
}
T lastWhere(bool test(T value), {T orElse()}) {
return _internal.lastWhere(test, orElse: orElse);
}
void shuffle([random]) => throw new UnimplementedError();
bool remove(Object element) => throw new UnimplementedError();
void removeWhere(bool test(T element)) => throw new UnimplementedError();
void retainWhere(bool test(T element)) => throw new UnimplementedError();
List<T> toList({bool growable: true}) => throw new UnimplementedError();
Set<T> toSet() => throw new UnimplementedError();
Iterable<T> takeWhile(bool test(T value)) => throw new UnimplementedError();
Iterable<T> skipWhile(bool test(T value)) => throw new UnimplementedError();
T singleWhere(bool test(T value), {T orElse()}) {
return _internal.singleWhere(test, orElse: orElse);
}
T elementAt(int index) {
return _internal.elementAt(index);
}
Map<int, T> asMap() {
return _internal.asMap();
}
bool get isEmpty => length == 0;
bool get isNotEmpty => !isEmpty;
}
// 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])
: _value = value,
super(parent);
T get value => _value;
void set value(T newValue) {
// Only fire on an actual change.
if (!identical(newValue, _value)) {
final oldValue = _value;
_value = newValue;
recordPropertyUpdate("value", newValue, oldValue);
}
}
T _value;
}