|
@@ -114,6 +114,252 @@ interface IObservableMap<T> extends IDisposable {
|
|
|
dispose(): void;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * A concrete implementation of IObservbleMap<T>.
|
|
|
+ */
|
|
|
+export
|
|
|
+class ObservableMap<T> implements IObservableMap<T> {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A signal emitted when the map has changed.
|
|
|
+ */
|
|
|
+ changed: ISignal<IObservableMap<T>, ObservableMap.IChangedArgs<T>>;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get whether this map can be linked to another.
|
|
|
+ * If so, the functions `link` and `unlink` will perform
|
|
|
+ * that. Otherwise, they are no-op functions.
|
|
|
+ *
|
|
|
+ * @returns `true` if the map may be linked to another,
|
|
|
+ * `false` otherwise.
|
|
|
+ */
|
|
|
+ readonly isLinkable: boolean = true;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Whether this map has been disposed.
|
|
|
+ */
|
|
|
+ get isDisposed(): boolean {
|
|
|
+ return this._map === null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Whether this map is linked to another.
|
|
|
+ */
|
|
|
+ get isLinked(): boolean {
|
|
|
+ return this._parent !== null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The number of key-value pairs in the map.
|
|
|
+ */
|
|
|
+ get size(): number {
|
|
|
+ if(this.isLinked) {
|
|
|
+ return this._parent.keys().length;
|
|
|
+ } else {
|
|
|
+ return this._map.size;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set a key-value pair in the map
|
|
|
+ *
|
|
|
+ * @param key - The key to set.
|
|
|
+ *
|
|
|
+ * @param value - The value for the key.
|
|
|
+ *
|
|
|
+ * @returns the old value for the key, or undefined
|
|
|
+ * if that did not exist.
|
|
|
+ */
|
|
|
+ set(key: string, value: T): T {
|
|
|
+ if(this.isLinked) {
|
|
|
+ return this._parent.set(key, value);
|
|
|
+ } else {
|
|
|
+ let oldVal = this._map.get(key);
|
|
|
+ this._map.set(key, value);
|
|
|
+ this.changed.emit({
|
|
|
+ type: oldVal ? 'change' : 'add',
|
|
|
+ key: key,
|
|
|
+ oldValue: oldVal,
|
|
|
+ newValue: value
|
|
|
+ });
|
|
|
+ return oldVal;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get a value for a given key.
|
|
|
+ *
|
|
|
+ * @param key - the key.
|
|
|
+ *
|
|
|
+ * @returns the value for that key.
|
|
|
+ */
|
|
|
+ get(key: string): T {
|
|
|
+ if(this.isLinked) {
|
|
|
+ return this._parent.get(key);
|
|
|
+ } else {
|
|
|
+ return this._map.get(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check whether the map has a key.
|
|
|
+ *
|
|
|
+ * @param key - the key to check.
|
|
|
+ *
|
|
|
+ * @returns `true` if the map has the key, `false` otherwise.
|
|
|
+ */
|
|
|
+ has(key: string): boolean {
|
|
|
+ if(this.isLinked) {
|
|
|
+ return this._parent.has(key);
|
|
|
+ } else {
|
|
|
+ return this._map.has(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get a list of the keys in the map.
|
|
|
+ *
|
|
|
+ * @returns - a list of keys.
|
|
|
+ */
|
|
|
+ keys(): string[] {
|
|
|
+ if(this.isLinked) {
|
|
|
+ return this._parent.keys();
|
|
|
+ } else {
|
|
|
+ let keyList: string[] = [];
|
|
|
+ this._map.forEach((v: T, k: string)=>{
|
|
|
+ keyList.push(k);
|
|
|
+ });
|
|
|
+ return keyList;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get a list of the values in the map.
|
|
|
+ *
|
|
|
+ * @returns - a list of values.
|
|
|
+ */
|
|
|
+ values(): T[] {
|
|
|
+ if(this.isLinked) {
|
|
|
+ return this._parent.values();
|
|
|
+ } else {
|
|
|
+ let valList: T[] = [];
|
|
|
+ this._map.forEach((v: T, k: string)=>{
|
|
|
+ valList.push(v);
|
|
|
+ });
|
|
|
+ return valList;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Remove a key from the map
|
|
|
+ *
|
|
|
+ * @param key - the key to remove.
|
|
|
+ *
|
|
|
+ * @returns the value of the given key,
|
|
|
+ * or undefined if that does not exist.
|
|
|
+ */
|
|
|
+ delete(key: string): T {
|
|
|
+ if(this.isLinked) {
|
|
|
+ return this._parent.delete(key);
|
|
|
+ } else {
|
|
|
+ let oldVal = this._map.get(key);
|
|
|
+ this._map.delete(key);
|
|
|
+ this.changed.emit({
|
|
|
+ type: 'remove',
|
|
|
+ key: key,
|
|
|
+ oldValue: oldVal,
|
|
|
+ newValue: undefined
|
|
|
+ });
|
|
|
+ return oldVal;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Link the map to another map.
|
|
|
+ * Any changes to either are mirrored in the other.
|
|
|
+ *
|
|
|
+ * @param map: the parent map.
|
|
|
+ */
|
|
|
+ link(map: IObservableMap<T>): void {
|
|
|
+ let keyList = map.keys();
|
|
|
+ let oldKeyList = this.keys();
|
|
|
+
|
|
|
+ //Remove values not in the parent map
|
|
|
+ for(let i = 0; i<oldKeyList.length; i++) {
|
|
|
+ if(!map.has(oldKeyList[i])) {
|
|
|
+ this.delete(oldKeyList[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //Insert new key-value pairs as necessary
|
|
|
+ for(let i=0; i<keyList.length; i++) {
|
|
|
+ let key = keyList[i];
|
|
|
+ if(this._map.get(key) !== map.get(key)) {
|
|
|
+ this.set(key, map.get(key));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //Now that we have mirrored the two maps,
|
|
|
+ //clear the local one and forward the signals
|
|
|
+ this._map.clear();
|
|
|
+ this._parent = map;
|
|
|
+ this._parent.changed.connect(this._forwardSignal, this);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Unlink the map from its parent map.
|
|
|
+ */
|
|
|
+ unlink(): void {
|
|
|
+ //Recreate the map locally
|
|
|
+ let keyList = this._parent.keys();
|
|
|
+ for(let i=0; i < keyList.length; i++) {
|
|
|
+ this._map.set(keyList[i], this._parent.get(keyList[i]));
|
|
|
+ }
|
|
|
+ this._parent = null;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set the ObservableMap to an empty map.
|
|
|
+ */
|
|
|
+ clear(): void {
|
|
|
+ if(this.isLinked) {
|
|
|
+ this._parent.clear();
|
|
|
+ } else {
|
|
|
+ let keyList = this.keys();
|
|
|
+ for(let i=0; i<keyList.length; i++) {
|
|
|
+ this.delete(keyList[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Dispose of the resources held by the map.
|
|
|
+ */
|
|
|
+ dispose(): void {
|
|
|
+ if(this._map === null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(this.isLinked) {
|
|
|
+ this.unlink();
|
|
|
+ }
|
|
|
+ this._map.clear();
|
|
|
+ this._map = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Catch a signal from the parent vector and pass it on.
|
|
|
+ */
|
|
|
+ private _forwardSignal(s: IObservableMap<T>,
|
|
|
+ c: ObservableMap.IChangedArgs<T>) {
|
|
|
+ this.changed.emit(c);
|
|
|
+ }
|
|
|
+
|
|
|
+ private _parent: IObservableMap<T> = null;
|
|
|
+ private _map: Map<string, T> = new Map<string, T>();
|
|
|
+}
|
|
|
|
|
|
/**
|
|
|
* The namespace for `ObservableMap` class statics.
|
|
@@ -166,3 +412,6 @@ namespace ObservableMap {
|
|
|
newValue: T;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// Define the signals for the `ObservableMap` class.
|
|
|
+defineSignal(ObservableMap.prototype, 'changed');
|