Skip to content

Commit

Permalink
Merged in 1.0.4 changes. Merge remote-tracking branch 'origin/master'…
Browse files Browse the repository at this point in the history
… into pull/38/tape-misc
  • Loading branch information
mweststrate committed Nov 11, 2015
2 parents e21ee5a + 5eea19d commit 5532789
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 28 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 1.0.4

* `map.size` is now a property instead of a function
* `map()` now accepts an array as entries to construct the new map
* introduced `isObservableObject`, `isObservableArray` and `isObservableMap`
* introduced `observe`, to observe observable arrays, objects and maps, similarly to Object.observe and Array.observe

# 1.0.3

* `extendObservable` now supports passing in multiple object properties
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,4 @@ Mobservable is inspired by Microsoft Excel and existing TFRP implementations lik
## Contributing

* Feel free to send pr requests.
* Use `npm start` to run the basic test suite, `npm test` for the test suite with coverage and `npm run perf` for the performance tests.
* Use `npm test` to run the basic test suite, `npm run converage` for the test suite with coverage and `npm run perf` for the performance tests.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mobservable",
"version": "1.0.3",
"version": "1.0.4",
"description": "Observable data. Reactive functions. Simple code.",
"main": "index.js",
"typings": "lib/index.d.ts",
Expand Down
32 changes: 31 additions & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import {isComputingView} from './dnode';
import {Lambda, IObservableArray, IObservableValue, IContextInfoStruct, IContextInfo} from './interfaces';
import {Lambda, IObservableArray, IObservableValue, IContextInfoStruct, IContextInfo, IArrayChange, IArraySplice, IObjectChange} from './interfaces';
import {isPlainObject, once} from './utils';
import {ObservableValue} from './observablevalue';
import {ObservableView, throwingViewSetter} from './observableview';
Expand Down Expand Up @@ -480,4 +480,34 @@ export function makeChildObservable(value, parentMode:ValueMode, context) {
export function assertUnwrapped(value, message) {
if (value instanceof AsReference || value instanceof AsStructure || value instanceof AsFlat)
throw new Error(`[mobservable] asStructure / asReference / asFlat cannot be used here. ${message}`);
}

export function isObservableObject(thing):boolean {
return thing && typeof thing === "object" && thing.$mobservable instanceof ObservableObject;
}

export function isObservableArray(thing):boolean {
return thing instanceof ObservableArray;
}

export function isObservableMap(thing):boolean {
return thing instanceof ObservableMap;
}

export function observe<T>(observableArray:IObservableArray<T>, listener:(change:IArrayChange<T>|IArraySplice<T>) => void): Lambda;
export function observe<T>(observableMap:ObservableMap<T>, listener:(change:IObjectChange<T, ObservableMap<T>>) => void): Lambda;
export function observe<T extends Object>(object:T, listener:(change:IObjectChange<any, T>) => void): Lambda;
export function observe(thing, listener):Lambda {
if (typeof thing === "function") {
console.error("[mobservable.observe] is deprecated in combination with a function, use 'mobservable.autorun' instead");
return autorun(thing);
} if (typeof listener !== "function")
throw new Error("[mobservable.observe] expected second argument to be a function");
if (isObservableArray(thing) || isObservableMap(thing))
return thing.observe(listener);
if (isObservableObject(thing))
return thing.$mobservable.observe(listener);
if (isPlainObject(thing))
return (<any>observable(thing)).$mobservable.observe(listener);
throw new Error("[mobservable.observe] first argument should be an observable array, observable map, observable object or plain object.");
}
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ export * from './interfaces';

export {
isObservable,
isObservableObject,
isObservableArray,
isObservableMap,
observable,
extendObservable,
asReference,
asFlat,
asStructure,
observe,
autorun,
autorunUntil,
autorunAsync,
Expand All @@ -25,7 +29,6 @@ export {
map,
observable as makeReactive,
extendObservable as extendReactive,
autorun as observe,
autorunUntil as observeUntil,
autorunAsync as observeAsync
} from './core';
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export interface IArraySplice<T> {
addedCount: number;
}

export interface IObjectChange<T, R> {
name: string;
object: R;
type: string;
oldValue?: T;
}

export interface IDependencyTree {
id: number;
name: string;
Expand Down
39 changes: 19 additions & 20 deletions src/observablemap.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import {ObservableValue} from './observablevalue';
import {ValueMode, observable, transaction, assertUnwrapped, getValueModeFromModifierFunc} from './core';
import {IObservableArray, Lambda} from './interfaces';
import {IObservableArray, Lambda, IObjectChange} from './interfaces';
import SimpleEventEmitter from './simpleeventemitter';
import {isComputingView} from './dnode';
import {ObservableArray} from './observablearray';
import {isPlainObject} from './utils';

export interface KeyValueMap<V> {
[key:string]: V
}

export type Entries<V> = [string, V][]

export type IObservableMapChange<T> = IObjectChange<T, ObservableMap<T>>;

export class ObservableMap<V> {
$mobservable = true;
private _data: { [key:string]: ObservableValue<V> } = {};
Expand All @@ -20,10 +25,12 @@ export class ObservableMap<V> {
private _valueMode: ValueMode;
private _events = new SimpleEventEmitter();

constructor(initialData?: KeyValueMap<V>, valueModeFunc?: Function) {
constructor(initialData?: Entries<V> | KeyValueMap<V>, valueModeFunc?: Function) {
this._valueMode = getValueModeFromModifierFunc(valueModeFunc);
if (initialData)
this.merge(initialData);
if (isPlainObject(initialData))
this.merge(<KeyValueMap<V>> initialData);
else if (Array.isArray(initialData))
initialData.forEach(([key, value]) => this.set(key, value));
}

_has(key: string): boolean {
Expand All @@ -44,7 +51,7 @@ export class ObservableMap<V> {
const oldValue = (<any>this._data[key])._value;
const changed = this._data[key].set(value);
if (changed) {
this._events.emit(<IObjectChange<V>>{
this._events.emit(<IObservableMapChange<V>>{
type: "update",
object: this,
name: key,
Expand All @@ -61,10 +68,10 @@ export class ObservableMap<V> {
this._updateHasMapEntry(key, true);
this._keys.push(key);
});
this._events.emit(<IObjectChange<V>>{
this._events.emit(<IObservableMapChange<V>>{
type: "add",
object: this,
name: key,
name: key
});
}
}
Expand All @@ -80,7 +87,7 @@ export class ObservableMap<V> {
observable.set(undefined);
this._data[key] = undefined;
});
this._events.emit(<IObjectChange<V>>{
this._events.emit(<IObservableMapChange<V>>{
type: "delete",
object: this,
name: key,
Expand All @@ -90,12 +97,11 @@ export class ObservableMap<V> {
}

_updateHasMapEntry(key: string, value: boolean): ObservableValue<boolean> {
// optimization; don't fill the hasMap if we are not observing, or remove entry if there are no observers anymore
let entry = this._hasMap[key];
if (entry) {
entry.set(value);
} else {
//if (value === false && !isComputingView())
// return; // optimization; don't fill the hasMap if we are not observing
entry = this._hasMap[key] = new ObservableValue(value, ValueMode.Reference, {
name: ".(has)" + key,
object: this
Expand All @@ -119,7 +125,7 @@ export class ObservableMap<V> {
return this.keys().map(this.get, this);
}

entries(): [string, V][] {
entries(): Entries<V> {
return this.keys().map(key => <[string, V]>[key, this.get(key)]);
}

Expand All @@ -144,7 +150,7 @@ export class ObservableMap<V> {
});
}

size(): number {
get size(): number {
return this._keys.length;
}

Expand Down Expand Up @@ -174,14 +180,7 @@ export class ObservableMap<V> {
* See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe
* for callback details
*/
observe(callback: (changes:IObjectChange<V>) => void): Lambda {
observe(callback: (changes:IObservableMapChange<V>) => void): Lambda {
return this._events.on(callback);
}
}

export interface IObjectChange<T> {
name: string;
object: ObservableMap<T>,
type: string;
oldValue?: T;
}
26 changes: 25 additions & 1 deletion src/observableobject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
*/
import {DataNode} from './dnode';
import {ValueMode, makeChildObservable, AsStructure} from './core';
import {IContextInfoStruct} from './interfaces';
import {IContextInfoStruct, IObjectChange, Lambda} from './interfaces';
import {ObservableView} from './observableview';
import {ObservableValue} from './observablevalue';
import SimpleEventEmitter from './simpleeventemitter';

// responsible for the administration of objects that have become reactive
export class ObservableObject {
values:{[key:string]:DataNode} = {};
private _events = new SimpleEventEmitter();

constructor(private target, private context:IContextInfoStruct, private mode: ValueMode) {
if (target.$mobservable)
Expand Down Expand Up @@ -66,8 +68,30 @@ export class ObservableObject {
return this.$mobservable ? this.$mobservable.values[propName].get() : undefined;
},
set: function(newValue) {
const oldValue = this.$mobservable.values[propName].get();
this.$mobservable.values[propName].set(newValue);
this.$mobservable._events.emit(<IObjectChange<any, any>>{
type: "update",
object: this,
name: propName,
oldValue
});
}
});

this._events.emit(<IObjectChange<any, any>>{
type: "add",
object: this.target,
name: propName
});
}

/**
* Observes this object. Triggers for the events 'add', 'update' and 'delete'.
* See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe
* for callback details
*/
observe(callback: (changes:IObjectChange<any, any>) => void): Lambda {
return this._events.on(callback);
}
}
12 changes: 9 additions & 3 deletions test/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test('map crud', function(t) {
t.equal(m.has("b"), false);
t.equal(m.get("a"), 1);
t.equal(m.get("b"), undefined);
t.equal(m.size(), 1);
t.equal(m.size, 1);

m.set("a", 2);
t.equal(m.has("a"), true);
Expand All @@ -29,14 +29,14 @@ test('map crud', function(t) {
t.deepEqual(m.entries(), [["a", 2], ["b", 3]]);
t.deepEqual(m.toJs(), { a: 2, b: 3});
t.deepEqual(m.toString(), "[mobservable.map { a: 2, b: 3 }]");
t.equal(m.size(), 2);
t.equal(m.size, 2);

m.clear();
t.deepEqual(m.keys(), []);
t.deepEqual(m.values(), []);
t.deepEqual(m.toJs(), { });
t.deepEqual(m.toString(), "[mobservable.map { }]");
t.equal(m.size(), 0);
t.equal(m.size, 0);

t.equal(m.has("a"), false);
t.equal(m.has("b"), false);
Expand Down Expand Up @@ -119,6 +119,12 @@ test('observe value', function(t) {
t.end();
})

test('initialize with entries', function(t) {
var a = map([["a", 1], ["b", 2]]);
t.deepEqual(a.toJs(), { a: 1, b: 2});
t.end();
})

test('observe collections', function(t) {
var x = map();
var keys, values, entries;
Expand Down
Loading

0 comments on commit 5532789

Please sign in to comment.