Socket
Socket
Sign inDemoInstall

mobservable

Package Overview
Dependencies
Maintainers
1
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mobservable - npm Package Compare versions

Comparing version 0.3.3 to 0.4.0

dist/mobservable.min.js

1518

dist/mobservable.js

@@ -1,6 +0,1 @@

/**
* MOBservable
* (c) 2015 - Michel Weststrate
* https://github.com/mweststrate/mobservable
*/
var __extends = this.__extends || function (d, b) {

@@ -12,772 +7,829 @@ for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];

};
function createObservable(value, scope) {
var prop = null;
if (Array.isArray && Array.isArray(value) && mobservableStatic.debugLevel)
warn("mobservable.value() was invoked with an array. Probably you want to create an mobservable.array() instead of observing a reference to an array?");
if (typeof value === "function")
prop = new ComputedObservable(value, scope);
else
prop = new ObservableValue(value, scope);
var propFunc = function (value) {
if (arguments.length > 0)
return prop.set(value);
else
return prop.get();
var mobservable;
(function (mobservable) {
function createObservable(value, scope) {
if (Array.isArray(value))
return new ObservableArray(value);
if (typeof value === "function")
return mobservable.mobservableStatic.computed(value, scope);
return mobservable.mobservableStatic.primitive(value);
}
mobservable.mobservableStatic = function (value, scope) {
return createObservable(value, scope);
};
propFunc.observe = prop.observe.bind(prop);
propFunc.prop = prop;
propFunc.toString = function () { return prop.toString(); };
return propFunc;
}
var mobservableStatic = function (value, scope) {
return createObservable(value, scope);
};
mobservableStatic.value = createObservable;
mobservableStatic.debugLevel = 0;
mobservableStatic.watch = function watch(func, onInvalidate) {
var dnode = new DNode(true);
var retVal;
dnode.nextState = function () {
retVal = func();
dnode.nextState = function () {
dnode.dispose();
onInvalidate();
return false;
};
return false;
mobservable.mobservableStatic.value = createObservable;
mobservable.mobservableStatic.primitive = mobservable.mobservableStatic.reference = function (value) {
return new ObservableValue(value).createGetterSetter();
};
dnode.computeNextState();
return [retVal, function () { return dnode.dispose(); }];
};
mobservableStatic.observeProperty = function observeProperty(object, key, listener, invokeImmediately) {
if (invokeImmediately === void 0) { invokeImmediately = false; }
if (!object || !key || object[key] === undefined)
throw new Error("Object '" + object + "' has no key '" + key + "'.");
if (!listener || typeof listener !== "function")
throw new Error("Third argument to mobservable.observeProperty should be a function");
var currentValue = object[key];
if (currentValue instanceof ObservableValue || currentValue instanceof ObservableArray)
return currentValue.observe(listener, invokeImmediately);
else if (currentValue.prop && currentValue.prop instanceof ObservableValue)
return currentValue.prop.observe(listener, invokeImmediately);
var observer = new ComputedObservable((function () { return object[key]; }), object);
var disposer = observer.observe(listener, invokeImmediately);
if (mobservableStatic.debugLevel && observer.dependencyState.observing.length === 0)
warn("mobservable.observeProperty: property '" + key + "' of '" + object + " doesn't seem to be observable. Did you define it as observable?");
return once(function () {
disposer();
observer.dependencyState.dispose();
});
};
mobservableStatic.array = function array(values) {
return new ObservableArray(values);
};
mobservableStatic.toJSON = function toJSON(value) {
if (value instanceof ObservableArray)
return value.values();
return value;
};
mobservableStatic.batch = function batch(action) {
return Scheduler.batch(action);
};
mobservableStatic.onReady = function onReady(listener) {
return Scheduler.onReady(listener);
};
mobservableStatic.onceReady = function onceReady(listener) {
Scheduler.onceReady(listener);
};
mobservableStatic.observable = function observable(target, key, descriptor) {
var baseValue = descriptor ? descriptor.value : null;
if (typeof baseValue === "function") {
delete descriptor.value;
delete descriptor.writable;
descriptor.get = function () {
mobservableStatic.defineObservableProperty(this, key, baseValue);
return this[key];
};
descriptor.set = function () {
console.trace();
throw new Error("It is not allowed to reassign observable functions");
};
}
else {
Object.defineProperty(target, key, {
configurable: true, enumberable: true,
get: function () {
mobservableStatic.defineObservableProperty(this, key, undefined);
mobservable.mobservableStatic.computed = function (func, scope) {
return new ComputedObservable(func, scope).createGetterSetter();
};
mobservable.mobservableStatic.array = function array(values) {
return new ObservableArray(values);
};
mobservable.mobservableStatic.props = function props(target, props, value) {
switch (arguments.length) {
case 0:
throw new Error("Not enough arguments");
case 1:
return mobservable.mobservableStatic.props(target, target);
case 2:
for (var key in props)
mobservable.mobservableStatic.props(target, key, props[key]);
break;
case 3:
var isArray = Array.isArray(value);
var observable = mobservable.mobservableStatic.value(value, target);
Object.defineProperty(target, props, {
get: isArray
? function () { return observable; }
: observable,
set: isArray
? function (newValue) { observable.replace(newValue); }
: observable,
enumerable: true,
configurable: false
});
break;
}
return target;
};
mobservable.mobservableStatic.observable = function observable(target, key, descriptor) {
var baseValue = descriptor ? descriptor.value : null;
if (typeof baseValue === "function") {
delete descriptor.value;
delete descriptor.writable;
descriptor.get = function () {
mobservable.mobservableStatic.props(this, key, baseValue);
return this[key];
},
set: function (value) {
if (Array.isArray(value)) {
var ar = new ObservableArray(value);
Object.defineProperty(this, key, {
value: ar,
writeable: false,
configurable: false,
enumberable: true
});
};
descriptor.set = function () {
console.trace();
throw new Error("It is not allowed to reassign observable functions");
};
}
else {
Object.defineProperty(target, key, {
configurable: false, enumberable: true,
get: function () {
mobservable.mobservableStatic.props(this, key, undefined);
return this[key];
},
set: function (value) {
mobservable.mobservableStatic.props(this, key, value);
}
else
mobservableStatic.defineObservableProperty(this, key, value);
});
}
};
mobservable.mobservableStatic.toPlainValue = function toPlainValue(value) {
if (value) {
if (value instanceof Array)
return value.slice();
else if (value instanceof ObservableValue)
return value.get();
else if (typeof value === "function" && value.impl) {
if (value.impl instanceof ObservableValue)
return value();
else if (value.impl instanceof ObservableArray)
return value().slice();
}
});
}
};
mobservableStatic.defineObservableProperty = function defineObservableProperty(object, name, initialValue) {
var _property = mobservableStatic.value(initialValue, object);
definePropertyForObservable(object, name, _property);
};
mobservableStatic.initializeObservableProperties = function initializeObservableProperties(object) {
for (var key in object)
if (object.hasOwnProperty(key)) {
if (object[key] && object[key].prop && object[key].prop instanceof ObservableValue)
definePropertyForObservable(object, key, object[key]);
else if (typeof value === "object") {
var res = {};
for (var key in value)
res[key] = toPlainValue(value[key]);
return res;
}
}
};
function definePropertyForObservable(object, name, observable) {
Object.defineProperty(object, name, {
get: function () {
return observable();
},
set: function (value) {
observable(value);
},
enumerable: true,
configurable: true
});
}
var ObservableValue = (function () {
function ObservableValue(_value, scope) {
this._value = _value;
this.scope = scope;
this.changeEvent = new SimpleEventEmitter();
this.dependencyState = new DNode(false);
}
ObservableValue.prototype.set = function (value) {
if (value !== this._value) {
var oldValue = this._value;
this.dependencyState.markStale();
this._value = value;
this.dependencyState.markReady(true);
this.changeEvent.emit(value, oldValue);
}
return this.scope;
return value;
};
ObservableValue.prototype.get = function () {
this.dependencyState.notifyObserved();
return this._value;
};
ObservableValue.prototype.observe = function (listener, fireImmediately) {
var _this = this;
if (fireImmediately === void 0) { fireImmediately = false; }
this.dependencyState.setRefCount(+1);
if (fireImmediately)
listener(this.get(), undefined);
var disposer = this.changeEvent.on(listener);
mobservable.mobservableStatic.observeProperty = function observeProperty(object, key, listener, invokeImmediately) {
if (invokeImmediately === void 0) { invokeImmediately = false; }
if (!object || !key || object[key] === undefined)
throw new Error("Object '" + object + "' has no property '" + key + "'.");
if (!listener || typeof listener !== "function")
throw new Error("Third argument to mobservable.observeProperty should be a function");
var currentValue = object[key];
if (currentValue instanceof ObservableValue || currentValue instanceof ObservableArray)
return currentValue.observe(listener, invokeImmediately);
else if (currentValue.impl && (currentValue.impl instanceof ObservableValue || currentValue instanceof ObservableArray))
return currentValue.impl.observe(listener, invokeImmediately);
var observer = new ComputedObservable((function () { return object[key]; }), object);
var disposer = observer.observe(listener, invokeImmediately);
if (mobservable.mobservableStatic.debugLevel && observer.dependencyState.observing.length === 0)
warn("mobservable.observeProperty: property '" + key + "' of '" + object + " doesn't seem to be observable. Did you define it as observable?");
return once(function () {
_this.dependencyState.setRefCount(-1);
disposer();
observer.dependencyState.dispose();
});
};
ObservableValue.prototype.toString = function () {
return "Observable[" + this._value + "]";
mobservable.mobservableStatic.watch = function watch(func, onInvalidate) {
var watch = new WatchedExpression(func, onInvalidate);
return [watch.value, function () { return watch.dispose(); }];
};
return ObservableValue;
})();
var ComputedObservable = (function (_super) {
__extends(ComputedObservable, _super);
function ComputedObservable(func, scope) {
_super.call(this, undefined, scope);
this.func = func;
this.isComputing = false;
this.hasError = false;
if (!func)
throw new Error("ComputedObservable requires a function");
this.dependencyState.isComputed = true;
this.dependencyState.nextState = this.compute.bind(this);
}
ComputedObservable.prototype.get = function () {
if (this.isComputing)
throw new Error("Cycle detected");
var state = this.dependencyState;
if (state.isSleeping) {
if (DNode.trackingStack.length > 0) {
state.wakeUp();
state.notifyObserved();
}
else {
this.compute();
}
mobservable.mobservableStatic.batch = function batch(action) {
return Scheduler.batch(action);
};
mobservable.mobservableStatic.debugLevel = 0;
var ObservableValue = (function () {
function ObservableValue(_value) {
this._value = _value;
this.changeEvent = new SimpleEventEmitter();
this.dependencyState = new DNode(this);
}
else {
state.notifyObserved();
}
if (state.hasCycle)
throw new Error("Cycle detected");
if (this.hasError) {
if (mobservableStatic.debugLevel) {
console.trace();
warn(this + ": rethrowing caught exception to observer: " + this._value + (this._value.cause || ''));
ObservableValue.prototype.set = function (value) {
if (value !== this._value) {
var oldValue = this._value;
this.dependencyState.markStale();
this._value = value;
this.dependencyState.markReady(true);
this.changeEvent.emit(value, oldValue);
}
throw this._value;
};
ObservableValue.prototype.get = function () {
this.dependencyState.notifyObserved();
return this._value;
};
ObservableValue.prototype.observe = function (listener, fireImmediately) {
var _this = this;
if (fireImmediately === void 0) { fireImmediately = false; }
this.dependencyState.setRefCount(+1);
if (fireImmediately)
listener(this.get(), undefined);
var disposer = this.changeEvent.on(listener);
return once(function () {
_this.dependencyState.setRefCount(-1);
disposer();
});
};
ObservableValue.prototype.createGetterSetter = function () {
var _this = this;
var self = this;
var f = function (value) {
if (arguments.length > 0)
self.set(value);
else
return self.get();
};
f.observe = function (listener, fire) { return _this.observe(listener, fire); };
f.impl = this;
f.toString = function () { return _this.toString(); };
return f;
};
ObservableValue.prototype.toString = function () {
return "Observable[" + this._value + "]";
};
return ObservableValue;
})();
var ComputedObservable = (function (_super) {
__extends(ComputedObservable, _super);
function ComputedObservable(func, scope) {
_super.call(this, undefined);
this.func = func;
this.scope = scope;
this.isComputing = false;
this.hasError = false;
if (typeof func !== "function")
throw new Error("ComputedObservable requires a function");
}
return this._value;
};
ComputedObservable.prototype.set = function (_) {
throw new Error(this.toString() + ": A computed observable does not accept new values!");
};
ComputedObservable.prototype.compute = function () {
var newValue;
try {
ComputedObservable.prototype.get = function () {
if (this.isComputing)
throw new Error("Cycle detected");
this.isComputing = true;
newValue = this.func.call(this.scope);
this.hasError = false;
var state = this.dependencyState;
if (state.isSleeping) {
if (DNode.trackingStack.length > 0) {
state.wakeUp();
state.notifyObserved();
}
else {
this.compute();
}
}
else {
state.notifyObserved();
}
if (state.hasCycle)
throw new Error("Cycle detected");
if (this.hasError) {
if (mobservable.mobservableStatic.debugLevel) {
console.trace();
warn(this + ": rethrowing caught exception to observer: " + this._value + (this._value.cause || ''));
}
throw this._value;
}
return this._value;
};
ComputedObservable.prototype.set = function (_) {
throw new Error(this.toString() + ": A computed observable does not accept new values!");
};
ComputedObservable.prototype.compute = function () {
var newValue;
try {
if (this.isComputing)
throw new Error("Cycle detected");
this.isComputing = true;
newValue = this.func.call(this.scope);
this.hasError = false;
}
catch (e) {
this.hasError = true;
console.error(this + "Caught error during computation: ", e);
if (e instanceof Error)
newValue = e;
else {
newValue = new Error("MobservableComputationError");
newValue.cause = e;
}
}
this.isComputing = false;
if (newValue !== this._value) {
var oldValue = this._value;
this._value = newValue;
this.changeEvent.emit(newValue, oldValue);
return true;
}
return false;
};
ComputedObservable.prototype.toString = function () {
return "ComputedObservable[" + this.func.toString() + "]";
};
return ComputedObservable;
})(ObservableValue);
var WatchedExpression = (function () {
function WatchedExpression(expr, onInvalidate) {
this.expr = expr;
this.onInvalidate = onInvalidate;
this.dependencyState = new DNode(this);
this.didEvaluate = false;
this.dependencyState.computeNextState();
}
catch (e) {
this.hasError = true;
console.error(this + "Caught error during computation: ", e);
if (e instanceof Error)
newValue = e;
WatchedExpression.prototype.compute = function () {
if (!this.didEvaluate) {
this.didEvaluate = true;
this.value = this.expr();
}
else {
newValue = new Error("MobservableComputationError");
newValue.cause = e;
this.dispose();
this.onInvalidate();
}
return false;
};
WatchedExpression.prototype.dispose = function () {
this.dependencyState.dispose();
};
return WatchedExpression;
})();
var DNodeState;
(function (DNodeState) {
DNodeState[DNodeState["STALE"] = 0] = "STALE";
DNodeState[DNodeState["PENDING"] = 1] = "PENDING";
DNodeState[DNodeState["READY"] = 2] = "READY";
})(DNodeState || (DNodeState = {}));
;
var DNode = (function () {
function DNode(owner) {
this.owner = owner;
this.state = DNodeState.READY;
this.isSleeping = true;
this.hasCycle = false;
this.observing = [];
this.prevObserving = null;
this.observers = [];
this.dependencyChangeCount = 0;
this.dependencyStaleCount = 0;
this.isDisposed = false;
this.externalRefenceCount = 0;
this.isComputed = owner.compute !== undefined;
}
this.isComputing = false;
if (newValue !== this._value) {
var oldValue = this._value;
this._value = newValue;
this.changeEvent.emit(newValue, oldValue);
return true;
}
return false;
};
ComputedObservable.prototype.toString = function () {
return "ComputedObservable[" + this.func.toString() + "]";
};
return ComputedObservable;
})(ObservableValue);
var DNodeState;
(function (DNodeState) {
DNodeState[DNodeState["STALE"] = 0] = "STALE";
DNodeState[DNodeState["PENDING"] = 1] = "PENDING";
DNodeState[DNodeState["READY"] = 2] = "READY";
})(DNodeState || (DNodeState = {}));
;
var DNode = (function () {
function DNode(isComputed) {
this.isComputed = isComputed;
this.state = DNodeState.READY;
this.isSleeping = true;
this.hasCycle = false;
this.observing = [];
this.prevObserving = null;
this.observers = [];
this.dependencyChangeCount = 0;
this.dependencyStaleCount = 0;
this.isDisposed = false;
this.externalRefenceCount = 0;
}
DNode.prototype.setRefCount = function (delta) {
var rc = this.externalRefenceCount += delta;
if (rc === 0)
this.tryToSleep();
else if (rc === delta)
this.wakeUp();
};
DNode.prototype.addObserver = function (node) {
this.observers[this.observers.length] = node;
};
DNode.prototype.removeObserver = function (node) {
var obs = this.observers, idx = obs.indexOf(node);
if (idx !== -1) {
obs.splice(idx, 1);
if (obs.length === 0)
;
DNode.prototype.setRefCount = function (delta) {
var rc = this.externalRefenceCount += delta;
if (rc === 0)
this.tryToSleep();
}
};
DNode.prototype.markStale = function () {
if (this.state !== DNodeState.READY)
return;
this.state = DNodeState.STALE;
this.notifyObservers();
};
DNode.prototype.markReady = function (stateDidActuallyChange) {
if (this.state === DNodeState.READY)
return;
this.state = DNodeState.READY;
this.notifyObservers(stateDidActuallyChange);
if (this.observers.length === 0)
Scheduler.scheduleReady();
};
DNode.prototype.notifyObservers = function (stateDidActuallyChange) {
if (stateDidActuallyChange === void 0) { stateDidActuallyChange = false; }
var os = this.observers.slice();
for (var l = os.length, i = 0; i < l; i++)
os[i].notifyStateChange(this, stateDidActuallyChange);
};
DNode.prototype.tryToSleep = function () {
if (this.isComputed && this.observers.length === 0 && this.externalRefenceCount === 0 && !this.isSleeping) {
for (var i = 0, l = this.observing.length; i < l; i++)
this.observing[i].removeObserver(this);
this.observing = [];
this.isSleeping = true;
}
};
DNode.prototype.wakeUp = function () {
if (this.isSleeping && this.isComputed) {
this.isSleeping = false;
this.state = DNodeState.PENDING;
this.computeNextState();
}
};
DNode.prototype.notifyStateChange = function (observable, stateDidActuallyChange) {
var _this = this;
if (observable.state === DNodeState.STALE) {
if (++this.dependencyStaleCount === 1)
this.markStale();
}
else {
if (stateDidActuallyChange)
this.dependencyChangeCount += 1;
if (--this.dependencyStaleCount === 0) {
else if (rc === delta)
this.wakeUp();
};
DNode.prototype.addObserver = function (node) {
this.observers[this.observers.length] = node;
};
DNode.prototype.removeObserver = function (node) {
var obs = this.observers, idx = obs.indexOf(node);
if (idx !== -1) {
obs.splice(idx, 1);
if (obs.length === 0)
this.tryToSleep();
}
};
DNode.prototype.markStale = function () {
if (this.state !== DNodeState.READY)
return;
this.state = DNodeState.STALE;
this.notifyObservers();
};
DNode.prototype.markReady = function (stateDidActuallyChange) {
if (this.state === DNodeState.READY)
return;
this.state = DNodeState.READY;
this.notifyObservers(stateDidActuallyChange);
};
DNode.prototype.notifyObservers = function (stateDidActuallyChange) {
if (stateDidActuallyChange === void 0) { stateDidActuallyChange = false; }
var os = this.observers.slice();
for (var l = os.length, i = 0; i < l; i++)
os[i].notifyStateChange(this, stateDidActuallyChange);
};
DNode.prototype.tryToSleep = function () {
if (!this.isSleeping && this.isComputed && this.observers.length === 0 && this.externalRefenceCount === 0) {
for (var i = 0, l = this.observing.length; i < l; i++)
this.observing[i].removeObserver(this);
this.observing = [];
this.isSleeping = true;
}
};
DNode.prototype.wakeUp = function () {
if (this.isSleeping && this.isComputed) {
this.isSleeping = false;
this.state = DNodeState.PENDING;
Scheduler.schedule(function () {
if (_this.dependencyChangeCount > 0)
_this.computeNextState();
else
_this.markReady(false);
_this.dependencyChangeCount = 0;
});
this.computeNextState();
}
}
};
DNode.prototype.computeNextState = function () {
this.trackDependencies();
var stateDidChange = this.nextState();
this.bindDependencies();
this.markReady(stateDidChange);
};
DNode.prototype.nextState = function () {
return false;
};
DNode.prototype.trackDependencies = function () {
this.prevObserving = this.observing;
DNode.trackingStack[DNode.trackingStack.length] = [];
};
DNode.prototype.bindDependencies = function () {
this.observing = DNode.trackingStack.pop();
if (this.isComputed && this.observing.length === 0 && mobservableStatic.debugLevel > 1 && !this.isDisposed) {
console.trace();
warn("You have created a function that doesn't observe any values, did you forget to make its dependencies observable?");
}
var _a = quickDiff(this.observing, this.prevObserving), added = _a[0], removed = _a[1];
this.prevObserving = null;
for (var i = 0, l = removed.length; i < l; i++)
removed[i].removeObserver(this);
this.hasCycle = false;
for (var i = 0, l = added.length; i < l; i++) {
if (this.isComputed && added[i].findCycle(this)) {
this.hasCycle = true;
this.observing.splice(this.observing.indexOf(added[i]), 1);
added[i].hasCycle = true;
};
DNode.prototype.notifyStateChange = function (observable, stateDidActuallyChange) {
var _this = this;
if (observable.state === DNodeState.STALE) {
if (++this.dependencyStaleCount === 1)
this.markStale();
}
else {
added[i].addObserver(this);
if (stateDidActuallyChange)
this.dependencyChangeCount += 1;
if (--this.dependencyStaleCount === 0) {
this.state = DNodeState.PENDING;
Scheduler.schedule(function () {
if (_this.dependencyChangeCount > 0)
_this.computeNextState();
else
_this.markReady(false);
_this.dependencyChangeCount = 0;
});
}
}
};
DNode.prototype.computeNextState = function () {
this.trackDependencies();
var stateDidChange = this.owner.compute();
this.bindDependencies();
this.markReady(stateDidChange);
};
DNode.prototype.trackDependencies = function () {
this.prevObserving = this.observing;
DNode.trackingStack[DNode.trackingStack.length] = [];
};
DNode.prototype.bindDependencies = function () {
this.observing = DNode.trackingStack.pop();
if (this.isComputed && this.observing.length === 0 && mobservable.mobservableStatic.debugLevel > 1 && !this.isDisposed) {
console.trace();
warn("You have created a function that doesn't observe any values, did you forget to make its dependencies observable?");
}
var _a = quickDiff(this.observing, this.prevObserving), added = _a[0], removed = _a[1];
this.prevObserving = null;
for (var i = 0, l = removed.length; i < l; i++)
removed[i].removeObserver(this);
this.hasCycle = false;
for (var i = 0, l = added.length; i < l; i++) {
if (this.isComputed && added[i].findCycle(this)) {
this.hasCycle = true;
this.observing.splice(this.observing.indexOf(added[i]), 1);
added[i].hasCycle = true;
}
else {
added[i].addObserver(this);
}
}
};
DNode.prototype.notifyObserved = function () {
var ts = DNode.trackingStack, l = ts.length;
if (l > 0) {
var cs = ts[l - 1], csl = cs.length;
if (cs[csl - 1] !== this && cs[csl - 2] !== this)
cs[csl] = this;
}
};
DNode.prototype.findCycle = function (node) {
var obs = this.observing;
if (obs.indexOf(node) !== -1)
return true;
for (var l = obs.length, i = 0; i < l; i++)
if (obs[i].findCycle(node))
return true;
return false;
};
DNode.prototype.dispose = function () {
if (this.observers.length)
throw new Error("Cannot dispose DNode; it is still being observed");
for (var l = this.observing.length, i = 0; i < l; i++)
this.observing[i].removeObserver(this);
this.observing = [];
this.isDisposed = true;
};
DNode.trackingStack = [];
return DNode;
})();
var StubArray = (function () {
function StubArray() {
}
};
DNode.prototype.notifyObserved = function () {
var ts = DNode.trackingStack, l = ts.length;
if (l > 0) {
var cs = ts[l - 1], csl = cs.length;
if (cs[csl - 1] !== this && cs[csl - 2] !== this)
cs[csl] = this;
return StubArray;
})();
StubArray.prototype = [];
var ObservableArray = (function (_super) {
__extends(ObservableArray, _super);
function ObservableArray(initialValues) {
_super.call(this);
Object.defineProperties(this, {
"dependencyState": { enumerable: false, value: new DNode(this) },
"_values": { enumerable: false, value: initialValues ? initialValues.slice() : [] },
"changeEvent": { enumerable: false, value: new SimpleEventEmitter() }
});
if (initialValues && initialValues.length)
this.updateLength(0, initialValues.length);
}
};
DNode.prototype.findCycle = function (node) {
var obs = this.observing;
if (obs.indexOf(node) !== -1)
return true;
for (var l = obs.length, i = 0; i < l; i++)
if (obs[i].findCycle(node))
return true;
return false;
};
DNode.prototype.dispose = function () {
if (this.observers.length)
throw new Error("Cannot dispose DNode; it is still being observed");
for (var l = this.observing.length, i = 0; i < l; i++)
this.observing[i].removeObserver(this);
this.observing = [];
this.isDisposed = true;
};
DNode.trackingStack = [];
return DNode;
})();
var ObservableArray = (function () {
function ObservableArray(initialValues) {
Object.defineProperties(this, {
"dependencyState": { enumerable: false, value: new DNode(false) },
"_values": { enumerable: false, value: initialValues ? initialValues.slice() : [] },
"changeEvent": { enumerable: false, value: new SimpleEventEmitter() },
Object.defineProperty(ObservableArray.prototype, "length", {
get: function () {
this.dependencyState.notifyObserved();
return this._values.length;
},
set: function (newLength) {
if (typeof newLength !== "number" || newLength < 0)
throw new Error("Out of range: " + newLength);
var currentLength = this._values.length;
if (newLength === currentLength)
return;
else if (newLength > currentLength)
this.spliceWithArray(currentLength, 0, new Array(newLength - currentLength));
else
this.spliceWithArray(newLength, currentLength - newLength);
},
enumerable: true,
configurable: true
});
if (initialValues && initialValues.length)
this.updateLength(0, initialValues.length);
}
Object.defineProperty(ObservableArray.prototype, "length", {
get: function () {
ObservableArray.prototype.updateLength = function (oldLength, delta) {
if (delta < 0)
for (var i = oldLength + delta; i < oldLength; i++)
delete this[i];
else if (delta > 0) {
if (oldLength + delta > ObservableArray.OBSERVABLE_ARRAY_BUFFER_SIZE)
ObservableArray.reserveArrayBuffer(oldLength + delta);
for (var i = oldLength, end = oldLength + delta; i < end; i++)
Object.defineProperty(this, "" + i, ObservableArray.ENUMERABLE_PROPS[i]);
}
};
ObservableArray.prototype.spliceWithArray = function (index, deleteCount, newItems) {
var length = this._values.length;
if ((newItems === undefined || newItems.length === 0) && (deleteCount === 0 || length === 0))
return [];
if (index === undefined)
index = 0;
else if (index > length)
index = length;
else if (index < 0)
index = Math.max(0, length + index);
if (arguments.length === 1)
deleteCount = length - index;
else if (deleteCount === undefined || deleteCount === null)
deleteCount = 0;
else
deleteCount = Math.max(0, Math.min(deleteCount, length - index));
if (newItems === undefined)
newItems = [];
var lengthDelta = newItems.length - deleteCount;
var res = (_a = this._values).splice.apply(_a, [index, deleteCount].concat(newItems));
this.updateLength(length, lengthDelta);
this.notifySplice(index, res, newItems);
return res;
var _a;
};
ObservableArray.prototype.notifyChildUpdate = function (index, oldValue) {
this.notifyChanged();
this.changeEvent.emit({ object: this, type: 'update', index: index, oldValue: oldValue });
};
ObservableArray.prototype.notifySplice = function (index, deleted, added) {
if (deleted.length === 0 && added.length === 0)
return;
this.notifyChanged();
this.changeEvent.emit({ object: this, type: 'splice', index: index, addedCount: added.length, removed: deleted });
};
ObservableArray.prototype.notifyChanged = function () {
this.dependencyState.markStale();
this.dependencyState.markReady(true);
};
ObservableArray.prototype.observe = function (listener, fireImmediately) {
if (fireImmediately === void 0) { fireImmediately = false; }
if (fireImmediately)
listener({ object: this, type: 'splice', index: 0, addedCount: this._values.length, removed: [] });
return this.changeEvent.on(listener);
};
ObservableArray.prototype.clear = function () {
return this.splice(0);
};
ObservableArray.prototype.replace = function (newItems) {
return this.spliceWithArray(0, this._values.length, newItems);
};
ObservableArray.prototype.values = function () {
this.dependencyState.notifyObserved();
return this._values.slice();
};
ObservableArray.prototype.toJSON = function () {
this.dependencyState.notifyObserved();
return this._values.slice();
};
ObservableArray.prototype.clone = function () {
this.dependencyState.notifyObserved();
return new ObservableArray(this._values);
};
ObservableArray.prototype.find = function (predicate, thisArg, fromIndex) {
if (fromIndex === void 0) { fromIndex = 0; }
this.dependencyState.notifyObserved();
var items = this._values, l = items.length;
for (var i = fromIndex; i < l; i++)
if (predicate.call(thisArg, items[i], i, this))
return items[i];
return null;
};
ObservableArray.prototype.splice = function (index, deleteCount) {
var newItems = [];
for (var _i = 2; _i < arguments.length; _i++) {
newItems[_i - 2] = arguments[_i];
}
this.sideEffectWarning("splice");
switch (arguments.length) {
case 0:
return [];
case 1:
return this.spliceWithArray(index);
case 2:
return this.spliceWithArray(index, deleteCount);
}
return this.spliceWithArray(index, deleteCount, newItems);
};
ObservableArray.prototype.push = function () {
var items = [];
for (var _i = 0; _i < arguments.length; _i++) {
items[_i - 0] = arguments[_i];
}
this.sideEffectWarning("push");
this.spliceWithArray(this._values.length, 0, items);
return this._values.length;
},
set: function (newLength) {
if (typeof newLength !== "number" || newLength < 0)
throw new Error("Out of range: " + newLength);
var currentLength = this._values.length;
if (newLength === currentLength)
return;
else if (newLength > currentLength)
this.spliceWithArray(currentLength, 0, new Array(newLength - currentLength));
else
this.spliceWithArray(newLength, currentLength - newLength);
},
enumerable: true,
configurable: true
});
ObservableArray.prototype.updateLength = function (oldLength, delta) {
if (delta < 0)
for (var i = oldLength + delta; i < oldLength; i++)
delete this[i];
else if (delta > 0) {
if (oldLength + delta > ObservableArray.OBSERVABLE_ARRAY_BUFFER_SIZE)
ObservableArray.reserveArrayBuffer(oldLength + delta);
for (var i = oldLength, end = oldLength + delta; i < end; i++)
Object.defineProperty(this, "" + i, ObservableArray.ENUMERABLE_PROPS[i]);
};
ObservableArray.prototype.pop = function () {
this.sideEffectWarning("pop");
return this.splice(Math.max(this._values.length - 1, 0), 1)[0];
};
ObservableArray.prototype.shift = function () {
this.sideEffectWarning("shift");
return this.splice(0, 1)[0];
};
ObservableArray.prototype.unshift = function () {
var items = [];
for (var _i = 0; _i < arguments.length; _i++) {
items[_i - 0] = arguments[_i];
}
this.sideEffectWarning("unshift");
this.spliceWithArray(0, 0, items);
return this._values.length;
};
ObservableArray.prototype.reverse = function () {
this.sideEffectWarning("reverse");
return this.replace(this._values.reverse());
};
ObservableArray.prototype.sort = function (compareFn) {
this.sideEffectWarning("sort");
return this.replace(this._values.sort.apply(this._values, arguments));
};
ObservableArray.prototype.remove = function (value) {
this.sideEffectWarning("remove");
var idx = this._values.indexOf(value);
if (idx > -1) {
this.splice(idx, 1);
return true;
}
return false;
};
ObservableArray.prototype.toString = function () { return this.wrapReadFunction("toString", arguments); };
ObservableArray.prototype.toLocaleString = function () { return this.wrapReadFunction("toLocaleString", arguments); };
ObservableArray.prototype.concat = function () { return this.wrapReadFunction("concat", arguments); };
ObservableArray.prototype.join = function (separator) { return this.wrapReadFunction("join", arguments); };
ObservableArray.prototype.slice = function (start, end) { return this.wrapReadFunction("slice", arguments); };
ObservableArray.prototype.indexOf = function (searchElement, fromIndex) { return this.wrapReadFunction("indexOf", arguments); };
ObservableArray.prototype.lastIndexOf = function (searchElement, fromIndex) { return this.wrapReadFunction("lastIndexOf", arguments); };
ObservableArray.prototype.every = function (callbackfn, thisArg) { return this.wrapReadFunction("every", arguments); };
ObservableArray.prototype.some = function (callbackfn, thisArg) { return this.wrapReadFunction("some", arguments); };
ObservableArray.prototype.forEach = function (callbackfn, thisArg) { return this.wrapReadFunction("forEach", arguments); };
ObservableArray.prototype.map = function (callbackfn, thisArg) { return this.wrapReadFunction("map", arguments); };
ObservableArray.prototype.filter = function (callbackfn, thisArg) { return this.wrapReadFunction("filter", arguments); };
ObservableArray.prototype.reduce = function (callbackfn, initialValue) { return this.wrapReadFunction("reduce", arguments); };
ObservableArray.prototype.reduceRight = function (callbackfn, initialValue) { return this.wrapReadFunction("reduceRight", arguments); };
ObservableArray.prototype.wrapReadFunction = function (funcName, initialArgs) {
var baseFunc = Array.prototype[funcName];
return (ObservableArray.prototype[funcName] = function () {
this.dependencyState.notifyObserved();
return baseFunc.apply(this._values, arguments);
}).apply(this, initialArgs);
};
ObservableArray.prototype.sideEffectWarning = function (funcName) {
if (DNode.trackingStack.length > 0)
warn("[Mobservable.Array] The method array." + funcName + " should not be used inside observable functions since it has side-effects");
};
ObservableArray.createArrayBufferItem = function (index) {
var prop = {
enumerable: false,
configurable: false,
set: function (value) {
if (index < this._values.length) {
var oldValue = this._values[index];
if (oldValue !== value) {
this._values[index] = value;
this.notifyChildUpdate(index, oldValue);
}
}
else if (index === this._values.length)
this.push(value);
else
throw new Error("ObservableArray: Index out of bounds, " + index + " is larger than " + this.values.length);
},
get: function () {
if (index < this._values.length) {
this.dependencyState.notifyObserved();
return this._values[index];
}
return undefined;
}
};
Object.defineProperty(ObservableArray.prototype, "" + index, prop);
prop.enumerable = true;
prop.configurable = true;
ObservableArray.ENUMERABLE_PROPS[index] = prop;
};
ObservableArray.reserveArrayBuffer = function (max) {
for (var index = ObservableArray.OBSERVABLE_ARRAY_BUFFER_SIZE; index <= max; index++)
ObservableArray.createArrayBufferItem(index);
ObservableArray.OBSERVABLE_ARRAY_BUFFER_SIZE = max;
};
ObservableArray.OBSERVABLE_ARRAY_BUFFER_SIZE = 0;
ObservableArray.ENUMERABLE_PROPS = [];
return ObservableArray;
})(StubArray);
ObservableArray.reserveArrayBuffer(1000);
var SimpleEventEmitter = (function () {
function SimpleEventEmitter() {
this.listeners = [];
}
};
ObservableArray.prototype.spliceWithArray = function (index, deleteCount, newItems) {
var length = this._values.length;
if ((newItems === undefined || newItems.length === 0) && (deleteCount === 0 || length === 0))
return [];
if (index === undefined)
index = 0;
else if (index > length)
index = length;
else if (index < 0)
index = Math.max(0, length + index);
if (arguments.length === 1)
deleteCount = length - index;
else if (deleteCount === undefined || deleteCount === null)
deleteCount = 0;
else
deleteCount = Math.max(0, Math.min(deleteCount, length - index));
if (newItems === undefined)
newItems = [];
var lengthDelta = newItems.length - deleteCount;
var res = (_a = this._values).splice.apply(_a, [index, deleteCount].concat(newItems));
this.updateLength(length, lengthDelta);
this.notifySplice(index, res, newItems);
return res;
var _a;
};
ObservableArray.prototype.notifyChildUpdate = function (index, oldValue) {
this.notifyChanged();
this.changeEvent.emit({ object: this, type: 'update', index: index, oldValue: oldValue });
};
ObservableArray.prototype.notifySplice = function (index, deleted, added) {
if (deleted.length === 0 && added.length === 0)
return;
this.notifyChanged();
this.changeEvent.emit({ object: this, type: 'splice', index: index, addedCount: added.length, removed: deleted });
};
ObservableArray.prototype.notifyChanged = function () {
this.dependencyState.markStale();
this.dependencyState.markReady(true);
};
ObservableArray.prototype.observe = function (listener, fireImmediately) {
if (fireImmediately === void 0) { fireImmediately = false; }
if (fireImmediately)
listener({ object: this, type: 'splice', index: 0, addedCount: this._values.length, removed: [] });
return this.changeEvent.on(listener);
};
ObservableArray.prototype.clear = function () {
return this.splice(0);
};
ObservableArray.prototype.replace = function (newItems) {
return this.spliceWithArray(0, this._values.length, newItems);
};
ObservableArray.prototype.values = function () {
this.dependencyState.notifyObserved();
return this._values.slice();
};
ObservableArray.prototype.toJSON = function () {
this.dependencyState.notifyObserved();
return this._values.slice();
};
ObservableArray.prototype.clone = function () {
this.dependencyState.notifyObserved();
return new ObservableArray(this._values);
};
ObservableArray.prototype.splice = function (index, deleteCount) {
var newItems = [];
for (var _i = 2; _i < arguments.length; _i++) {
newItems[_i - 2] = arguments[_i];
SimpleEventEmitter.prototype.emit = function () {
var listeners = this.listeners.slice();
var l = listeners.length;
switch (arguments.length) {
case 0:
for (var i = 0; i < l; i++)
listeners[i]();
break;
case 1:
var data = arguments[0];
for (var i = 0; i < l; i++)
listeners[i](data);
break;
default:
for (var i = 0; i < l; i++)
listeners[i].apply(null, arguments);
}
};
SimpleEventEmitter.prototype.on = function (listener) {
var _this = this;
this.listeners.push(listener);
return once(function () {
var idx = _this.listeners.indexOf(listener);
if (idx !== -1)
_this.listeners.splice(idx, 1);
});
};
SimpleEventEmitter.prototype.once = function (listener) {
var subscription = this.on(function () {
subscription();
listener.apply(this, arguments);
});
return subscription;
};
return SimpleEventEmitter;
})();
mobservable.mobservableStatic.SimpleEventEmitter = SimpleEventEmitter;
var Scheduler = (function () {
function Scheduler() {
}
switch (arguments.length) {
case 0:
return [];
case 1:
return this.spliceWithArray(index);
case 2:
return this.spliceWithArray(index, deleteCount);
}
return this.spliceWithArray(index, deleteCount, newItems);
};
ObservableArray.prototype.push = function () {
var items = [];
for (var _i = 0; _i < arguments.length; _i++) {
items[_i - 0] = arguments[_i];
}
this.spliceWithArray(this._values.length, 0, items);
return this._values.length;
};
ObservableArray.prototype.pop = function () {
return this.splice(Math.max(this._values.length - 1, 0), 1)[0];
};
ObservableArray.prototype.shift = function () {
return this.splice(0, 1)[0];
};
ObservableArray.prototype.unshift = function () {
var items = [];
for (var _i = 0; _i < arguments.length; _i++) {
items[_i - 0] = arguments[_i];
}
this.spliceWithArray(0, 0, items);
return this._values.length;
};
ObservableArray.prototype.reverse = function () {
return this.replace(this._values.reverse());
};
ObservableArray.prototype.sort = function (compareFn) {
return this.replace(this._values.sort.apply(this._values, arguments));
};
ObservableArray.prototype.toString = function () { return this.wrapReadFunction("toString", arguments); };
ObservableArray.prototype.toLocaleString = function () { return this.wrapReadFunction("toLocaleString", arguments); };
ObservableArray.prototype.concat = function () { return this.wrapReadFunction("concat", arguments); };
ObservableArray.prototype.join = function (separator) { return this.wrapReadFunction("join", arguments); };
ObservableArray.prototype.slice = function (start, end) { return this.wrapReadFunction("slice", arguments); };
ObservableArray.prototype.indexOf = function (searchElement, fromIndex) { return this.wrapReadFunction("indexOf", arguments); };
ObservableArray.prototype.lastIndexOf = function (searchElement, fromIndex) { return this.wrapReadFunction("lastIndexOf", arguments); };
ObservableArray.prototype.every = function (callbackfn, thisArg) { return this.wrapReadFunction("every", arguments); };
ObservableArray.prototype.some = function (callbackfn, thisArg) { return this.wrapReadFunction("some", arguments); };
ObservableArray.prototype.forEach = function (callbackfn, thisArg) { return this.wrapReadFunction("forEach", arguments); };
ObservableArray.prototype.map = function (callbackfn, thisArg) { return this.wrapReadFunction("map", arguments); };
ObservableArray.prototype.filter = function (callbackfn, thisArg) { return this.wrapReadFunction("filter", arguments); };
ObservableArray.prototype.reduce = function (callbackfn, initialValue) { return this.wrapReadFunction("reduce", arguments); };
ObservableArray.prototype.reduceRight = function (callbackfn, initialValue) { return this.wrapReadFunction("reduceRight", arguments); };
ObservableArray.prototype.wrapReadFunction = function (funcName, initialArgs) {
var baseFunc = Array.prototype[funcName];
return (ObservableArray.prototype[funcName] = function () {
this.dependencyState.notifyObserved();
return baseFunc.apply(this._values, arguments);
}).apply(this, initialArgs);
};
ObservableArray.createArrayBufferItem = function (index) {
var prop = {
enumerable: false,
configurable: true,
set: function (value) {
if (index < this._values.length) {
var oldValue = this._values[index];
if (oldValue !== value) {
this._values[index] = value;
this.notifyChildUpdate(index, oldValue);
}
Scheduler.schedule = function (func) {
if (Scheduler.inBatch < 1)
func();
else
Scheduler.tasks[Scheduler.tasks.length] = func;
};
Scheduler.runPostBatchActions = function () {
var i = 0;
while (Scheduler.tasks.length) {
try {
for (; i < Scheduler.tasks.length; i++)
Scheduler.tasks[i]();
Scheduler.tasks = [];
}
else if (index === this._values.length)
this.push(value);
else
throw new Error("ObservableArray: Index out of bounds, " + index + " is larger than " + this.values.length);
},
get: function () {
if (index < this._values.length) {
this.dependencyState.notifyObserved();
return this._values[index];
catch (e) {
console.error("Failed to run scheduled action, the action has been dropped from the queue: " + e, e);
Scheduler.tasks.splice(0, i + 1);
}
return undefined;
}
};
Object.defineProperty(ObservableArray.prototype, "" + index, prop);
prop.enumerable = true;
ObservableArray.ENUMERABLE_PROPS[index] = prop;
};
ObservableArray.reserveArrayBuffer = function (max) {
for (var index = ObservableArray.OBSERVABLE_ARRAY_BUFFER_SIZE; index <= max; index++)
ObservableArray.createArrayBufferItem(index);
ObservableArray.OBSERVABLE_ARRAY_BUFFER_SIZE = max;
};
ObservableArray.OBSERVABLE_ARRAY_BUFFER_SIZE = 0;
ObservableArray.ENUMERABLE_PROPS = [];
return ObservableArray;
})();
ObservableArray.reserveArrayBuffer(1000);
var SimpleEventEmitter = (function () {
function SimpleEventEmitter() {
this.listeners = [];
}
SimpleEventEmitter.prototype.emit = function () {
var listeners = this.listeners.slice();
var l = listeners.length;
switch (arguments.length) {
case 0:
for (var i = 0; i < l; i++)
listeners[i]();
break;
case 1:
var data = arguments[0];
for (var i = 0; i < l; i++)
listeners[i](data);
break;
default:
for (var i = 0; i < l; i++)
listeners[i].apply(null, arguments);
}
};
SimpleEventEmitter.prototype.on = function (listener) {
var _this = this;
this.listeners.push(listener);
return once(function () {
var idx = _this.listeners.indexOf(listener);
if (idx !== -1)
_this.listeners.splice(idx, 1);
});
};
SimpleEventEmitter.prototype.once = function (listener) {
var subscription = this.on(function () {
subscription();
listener.apply(this, arguments);
});
return subscription;
};
return SimpleEventEmitter;
})();
mobservableStatic.SimpleEventEmitter = SimpleEventEmitter;
var Scheduler = (function () {
function Scheduler() {
}
Scheduler.schedule = function (func) {
if (Scheduler.inBatch < 1)
func();
else
Scheduler.tasks[Scheduler.tasks.length] = func;
};
Scheduler.runPostBatchActions = function () {
var i = 0;
try {
for (; i < Scheduler.tasks.length; i++)
Scheduler.tasks[i]();
Scheduler.tasks = [];
}
catch (e) {
console.error("Failed to run scheduled action, the action has been dropped from the queue: " + e, e);
Scheduler.tasks.splice(0, i + 1);
setTimeout(Scheduler.runPostBatchActions, 1);
throw e;
}
};
Scheduler.batch = function (action) {
Scheduler.inBatch += 1;
try {
return action();
}
finally {
if (--Scheduler.inBatch === 0) {
Scheduler.runPostBatchActions();
Scheduler.scheduleReady();
Scheduler.batch = function (action) {
Scheduler.inBatch += 1;
try {
return action();
}
}
};
Scheduler.scheduleReady = function () {
if (!Scheduler.pendingReady) {
Scheduler.pendingReady = true;
setTimeout(function () {
Scheduler.pendingReady = false;
Scheduler.readyEvent.emit();
}, 1);
}
};
Scheduler.onReady = function (listener) {
return Scheduler.readyEvent.on(listener);
};
Scheduler.onceReady = function (listener) {
return Scheduler.readyEvent.once(listener);
};
Scheduler.pendingReady = false;
Scheduler.readyEvent = new SimpleEventEmitter();
Scheduler.inBatch = 0;
Scheduler.tasks = [];
return Scheduler;
})();
function quickDiff(current, base) {
if (!base.length)
return [current, []];
if (!current.length)
return [[], base];
var added = [];
var removed = [];
var currentIndex = 0, currentSearch = 0, currentLength = current.length, currentExhausted = false, baseIndex = 0, baseSearch = 0, baseLength = base.length, isSearching = false, baseExhausted = false;
while (!baseExhausted && !currentExhausted) {
if (!isSearching) {
if (currentIndex < currentLength && baseIndex < baseLength && current[currentIndex] === base[baseIndex]) {
currentIndex++;
finally {
if (--Scheduler.inBatch === 0) {
Scheduler.inBatch += 1;
Scheduler.runPostBatchActions();
Scheduler.inBatch -= 1;
}
}
};
Scheduler.inBatch = 0;
Scheduler.tasks = [];
return Scheduler;
})();
function quickDiff(current, base) {
if (!base.length)
return [current, []];
if (!current.length)
return [[], base];
var added = [];
var removed = [];
var currentIndex = 0, currentSearch = 0, currentLength = current.length, currentExhausted = false, baseIndex = 0, baseSearch = 0, baseLength = base.length, isSearching = false, baseExhausted = false;
while (!baseExhausted && !currentExhausted) {
if (!isSearching) {
if (currentIndex < currentLength && baseIndex < baseLength && current[currentIndex] === base[baseIndex]) {
currentIndex++;
baseIndex++;
if (currentIndex === currentLength && baseIndex === baseLength)
return [added, removed];
continue;
}
currentSearch = currentIndex;
baseSearch = baseIndex;
isSearching = true;
}
baseSearch += 1;
currentSearch += 1;
if (baseSearch >= baseLength)
baseExhausted = true;
if (currentSearch >= currentLength)
currentExhausted = true;
if (!currentExhausted && current[currentSearch] === base[baseIndex]) {
added.push.apply(added, current.slice(currentIndex, currentSearch));
currentIndex = currentSearch + 1;
baseIndex++;
if (currentIndex === currentLength && baseIndex === baseLength)
return [added, removed];
continue;
isSearching = false;
}
currentSearch = currentIndex;
baseSearch = baseIndex;
isSearching = true;
else if (!baseExhausted && base[baseSearch] === current[currentIndex]) {
removed.push.apply(removed, base.slice(baseIndex, baseSearch));
baseIndex = baseSearch + 1;
currentIndex++;
isSearching = false;
}
}
baseSearch += 1;
currentSearch += 1;
if (baseSearch >= baseLength)
baseExhausted = true;
if (currentSearch >= currentLength)
currentExhausted = true;
if (!currentExhausted && current[currentSearch] === base[baseIndex]) {
added.push.apply(added, current.slice(currentIndex, currentSearch));
currentIndex = currentSearch + 1;
baseIndex++;
isSearching = false;
}
else if (!baseExhausted && base[baseSearch] === current[currentIndex]) {
removed.push.apply(removed, base.slice(baseIndex, baseSearch));
baseIndex = baseSearch + 1;
currentIndex++;
isSearching = false;
}
added.push.apply(added, current.slice(currentIndex));
removed.push.apply(removed, base.slice(baseIndex));
return [added, removed];
}
added.push.apply(added, current.slice(currentIndex));
removed.push.apply(removed, base.slice(baseIndex));
return [added, removed];
}
mobservableStatic.quickDiff = quickDiff;
mobservableStatic.stackDepth = function () { return DNode.trackingStack.length; };
function warn(message) {
if (console)
console.warn("[WARNING:mobservable] " + message);
}
function once(func) {
var invoked = false;
return function () {
if (invoked)
return;
invoked = true;
return func.apply(this, arguments);
};
}
module.exports = mobservableStatic;
//# sourceMappingURL=mobservable.js.map
mobservable.mobservableStatic.quickDiff = quickDiff;
mobservable.mobservableStatic.stackDepth = function () { return DNode.trackingStack.length; };
function warn(message) {
if (console)
console.warn("[WARNING:mobservable] " + message);
}
function once(func) {
var invoked = false;
return function () {
if (invoked)
return;
invoked = true;
return func.apply(this, arguments);
};
}
})(mobservable || (mobservable = {}));
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define('mobservable', [], function () {
return (factory());
});
}
else if (typeof exports === 'object') {
module.exports = factory();
}
else {
root['mobservable'] = factory();
}
}(this, function () {
return mobservable.mobservableStatic;
}));

@@ -0,36 +1,26 @@

var fs = require('fs');
var mkdirp = require('mkdirp');
var path = require('path');
module.exports = function(grunt) {
var tsc = "node " + __dirname + "/node_modules/typescript/bin/tsc.js".replace(/\//g, path.sep);
console.log("Compiling with: " + tsc);
grunt.initConfig({
ts: {
options: {
module: 'commonjs',
target: 'es5'
},
builddist : {
src: ["mobservable.ts"],
outDir: "dist/",
comments: false,
},
buildlocal : {
src: ["mobservable.ts"]
},
buildtypescripttest: {
options: {
compiler: './node_modules/typescript/bin/tsc'
},
src: ["test/typescript-test.ts"]
}
},
nodeunit: {
options: {
reporter: 'default'
},
options: { reporter: 'default' },
all: ['test/*.js'],
perf: ['test/performance.js']
perf: ['test/perf/*.js']
},
exec: {
cover: "mkdir -p dist/test && cp -rf test/* dist/test && istanbul cover nodeunit dist/test/"
cover: "istanbul cover nodeunit test/",
buildtypescripttest: {
cmd: tsc + " typescript-test.ts -m commonjs -t es5",
cwd: "test/"
},
buildlocal: tsc + " mobservable.ts -t es5 --sourceMap",
builddist: tsc + " mobservable.ts -t es5 --removeComments -out dist/mobservable.js"
},
coveralls: {
options: {
// LCOV coverage file relevant to every target
// LCOV coverage file relevant to every target
force: false

@@ -41,18 +31,41 @@ },

}
}
},
uglify: {
dist: {
files: {
'dist/mobservable.min.js': ['dist/mobservable.js']
}
}
}
});
grunt.loadNpmTasks("grunt-ts");
grunt.loadNpmTasks('grunt-contrib-nodeunit');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-coveralls');
grunt.loadNpmTasks('grunt-exec');
grunt.registerTask("buildDts", "Build .d.ts file", function() {
var moduleDeclaration = '\n\ndeclare module "mobservable" {\n\tvar m : IMObservableStatic;\n\texport = m;\n}';
var ts = fs.readFileSync('mobservable.ts','utf8');
var headerEndIndex = ts.indexOf("/* END OF DECLARATION */");
if (headerEndIndex === -1)
throw "Failed to find end of declaration in mobservable.ts";
fs.writeFileSync('mobservable.d.ts', "/** GENERATED FILE */\n" + ts.substr(0, headerEndIndex) + moduleDeclaration, 'utf8');
});
grunt.registerTask("preparetest", "Create node module in test folder", function(sourceDir) {
mkdirp.sync("test/node_modules/mobservable");
fs.writeFileSync("test/node_modules/mobservable/mobservable.d.ts", fs.readFileSync("mobservable.d.ts","utf8"),"utf8");
fs.writeFileSync("test/node_modules/mobservable/index.js", "module.exports=require('../../../" + sourceDir + "/mobservable.js');","utf8");
});
grunt.registerTask("publish", "Publish to npm", function() {
require("./publish.js");
});
grunt.registerTask("default", ["ts:buildlocal"]);
grunt.registerTask("build", ["ts:builddist"]);
grunt.registerTask("cover", ["ts:buildlocal", "exec:cover", "coveralls:default"]);
grunt.registerTask("test", ["ts:buildlocal","ts:buildtypescripttest", "nodeunit:all"]);
grunt.registerTask("perf", ["ts:buildlocal", "nodeunit:perf"]);
grunt.registerTask("default", ["buildlocal"]);
grunt.registerTask("builddist", ["exec:builddist","buildDts","uglify:dist"]);
grunt.registerTask("buildlocal", ["exec:buildlocal", "buildDts"]);
grunt.registerTask("cover", ["builddist", "preparetest:dist", "exec:cover", "coveralls:default"]);
grunt.registerTask("test", ["buildlocal", "preparetest:", "exec:buildtypescripttest", "nodeunit:all"]);
grunt.registerTask("perf", ["buildlocal", "preparetest:", "nodeunit:perf"]);
};

@@ -1,61 +0,92 @@

declare module "mobservable" {
interface Lambda {
(): void;
}
interface IObservableValue<T, S> {
(): T;
(value: T): S;
observe(callback: (newValue: T, oldValue: T) => void, fireImmediately:boolean): Lambda;
}
/** GENERATED FILE */
/**
* MOBservable
* (c) 2015 - Michel Weststrate
* https://github.com/mweststrate/mobservable
*/
interface IMObservableStatic {
// ways of creating observables.
<T>(value?:T[]):IObservableArray<T>;
<T>(value?:T|{():T}, scope?:Object):IObservableValue<T>;
value<T>(value?:T[]):IObservableArray<T>;
value<T>(value?:T|{():T}, scope?:Object):IObservableValue<T>;
array<T>(values?:T[]):IObservableArray<T>;
primitive<T>(value?:T):IObservableValue<T>;
reference<T>(value?:T):IObservableValue<T>;
computed<T>(value:()=>T,scope?):IObservableValue<T>;
export function array<T>(values?:T[]): IObservableArray<T>;
export function value<T,S>(value?:T|{():T}, scope?:S):IObservableValue<T,S>;
export function watch<T>(func:()=>T, onInvalidate:Lambda):[T,Lambda];
export function observeProperty(object:Object, key:string, listener:Function, invokeImmediately?:boolean):Lambda;
// create observable properties
props(object:Object, name:string, initalValue: any);
props(object:Object, props:Object);
props(object:Object);
observable(target:Object, key:string); // annotation
export function toJSON<T>(any:T):T;
// annotation
export function observable(target:Object, key:string);
// observables to not observables
toPlainValue<T>(any:T):T;
export function batch<T>(action:()=>T):T;
export function onReady(listener:Lambda):Lambda;
export function onceReady(listener:Lambda);
export function defineObservableProperty<T>(object:Object, name:string, initialValue?:T);
export function initializeObservableProperties(object:Object);
// observe observables
observeProperty(object:Object, key:string, listener:Function, invokeImmediately?:boolean):Lambda;
watch<T>(func:()=>T, onInvalidate:Lambda):[T,Lambda];
// change a lot of observables at once
batch<T>(action:()=>T):T;
export var SimpleEventEmitter: new() => ISimpleEventEmitter;
// Utils
debugLevel: number;
SimpleEventEmitter: new()=> ISimpleEventEmitter;
}
interface IObservableArray<T> extends Array<T> {
[n: number]: T;
length: number;
interface Lambda {
():void;
}
spliceWithArray(index:number, deleteCount?:number, newItems?:T[]):T[];
observe(listener:(changeData:IArrayChange<T>|IArraySplice<T>)=>void, fireImmediately?:boolean):Lambda;
clear(): T[];
replace(newItems:T[]);
values(): T[];
clone(): IObservableArray<T>;
}
interface IArrayChange<T> {
type: string; // Always: 'update'
object: IObservableArray<T>;
index: number;
oldValue: T;
}
interface IArraySplice<T> {
type: string; // Always: 'splice'
object: IObservableArray<T>;
index: number;
removed: T[];
addedCount: number;
}
interface IObservable {
observe(callback:(...args:any[])=>void, fireImmediately?:boolean):Lambda;
}
interface ISimpleEventEmitter {
emit(...data:any[]):void;
on(listener:(...data:any[])=>void):Lambda;
once(listener:(...data:any[])=>void):Lambda;
}
interface IObservableValue<T> extends IObservable {
():T;
(value:T);
observe(callback:(newValue:T, oldValue:T)=>void, fireImmediately?:boolean):Lambda;
}
interface IObservableArray<T> extends IObservable, Array<T> {
spliceWithArray(index:number, deleteCount?:number, newItems?:T[]):T[];
observe(listener:(changeData:IArrayChange<T>|IArraySplice<T>)=>void, fireImmediately?:boolean):Lambda;
clear(): T[];
replace(newItems:T[]);
values(): T[];
clone(): IObservableArray<T>;
find(predicate:(item:T,index:number,array:IObservableArray<T>)=>boolean,thisArg?,fromIndex?:number):T;
remove(value:T):boolean;
}
interface IArrayChange<T> {
type: string; // Always: 'update'
object: IObservableArray<T>;
index: number;
oldValue: T;
}
interface IArraySplice<T> {
type: string; // Always: 'splice'
object: IObservableArray<T>;
index: number;
removed: T[];
addedCount: number;
}
interface ISimpleEventEmitter {
emit(...data:any[]):void;
on(listener:(...data:any[])=>void):Lambda;
once(listener:(...data:any[])=>void):Lambda;
}
declare module "mobservable" {
var m : IMObservableStatic;
export = m;
}

@@ -6,3 +6,36 @@ /**

*/
interface IMObservableStatic {
// ways of creating observables.
<T>(value?:T[]):IObservableArray<T>;
<T>(value?:T|{():T}, scope?:Object):IObservableValue<T>;
value<T>(value?:T[]):IObservableArray<T>;
value<T>(value?:T|{():T}, scope?:Object):IObservableValue<T>;
array<T>(values?:T[]):IObservableArray<T>;
primitive<T>(value?:T):IObservableValue<T>;
reference<T>(value?:T):IObservableValue<T>;
computed<T>(value:()=>T,scope?):IObservableValue<T>;
// create observable properties
props(object:Object, name:string, initalValue: any);
props(object:Object, props:Object);
props(object:Object);
observable(target:Object, key:string); // annotation
// observables to not observables
toPlainValue<T>(any:T):T;
// observe observables
observeProperty(object:Object, key:string, listener:Function, invokeImmediately?:boolean):Lambda;
watch<T>(func:()=>T, onInvalidate:Lambda):[T,Lambda];
// change a lot of observables at once
batch<T>(action:()=>T):T;
// Utils
debugLevel: number;
SimpleEventEmitter: new()=> ISimpleEventEmitter;
}
interface Lambda {

@@ -12,102 +45,171 @@ ():void;

interface IObservableValue<T,S> {
interface IObservable {
observe(callback:(...args:any[])=>void, fireImmediately?:boolean):Lambda;
}
interface IObservableValue<T> extends IObservable {
():T;
(value:T):S;
(value:T);
observe(callback:(newValue:T, oldValue:T)=>void, fireImmediately?:boolean):Lambda;
}
interface MobservableStatic {
// shorthand for .value()
<T,S>(value?:T|{():T}, scope?:S):IObservableValue<T,S>;
interface IObservableArray<T> extends IObservable, Array<T> {
spliceWithArray(index:number, deleteCount?:number, newItems?:T[]):T[];
observe(listener:(changeData:IArrayChange<T>|IArraySplice<T>)=>void, fireImmediately?:boolean):Lambda;
clear(): T[];
replace(newItems:T[]);
values(): T[];
clone(): IObservableArray<T>;
find(predicate:(item:T,index:number,array:IObservableArray<T>)=>boolean,thisArg?,fromIndex?:number):T;
remove(value:T):boolean;
}
// core functinos
array<T>(values?:T[]): ObservableArray<T>;
value<T,S>(value?:T|{():T}, scope?:S):IObservableValue<T,S>;
watch<T>(func:()=>T, onInvalidate:Lambda):[T,Lambda];
toJSON<T>(any:T):T;
// property definition
observable(target:Object, key:string); // annotation
defineObservableProperty<T>(object:Object, name:string, initialValue?:T);
initializeObservableProperties(object:Object);
observeProperty(object:Object, key:string, listener:Function, invokeImmediately?:boolean):Lambda;
interface IArrayChange<T> {
type: string; // Always: 'update'
object: IObservableArray<T>;
index: number;
oldValue: T;
}
// batching
batch<T>(action:()=>T):T;
onReady(listener:Lambda):Lambda;
onceReady(listener:Lambda);
interface IArraySplice<T> {
type: string; // Always: 'splice'
object: IObservableArray<T>;
index: number;
removed: T[];
addedCount: number;
}
// Utils
SimpleEventEmitter: new()=> SimpleEventEmitter;
debugLevel: number;
interface ISimpleEventEmitter {
emit(...data:any[]):void;
on(listener:(...data:any[])=>void):Lambda;
once(listener:(...data:any[])=>void):Lambda;
}
/**
Creates an observable from either a value or a function.
If a scope is provided, the function will be always executed usign the provided scope.
Returns a new IObservable, that is, a functon that can be used to set a new value or get the current values
(the latter if no arguments are provided)
*/
function createObservable<T,S>(value?:T|{():T}, scope?:S):IObservableValue<T,S> {
var prop:ObservableValue<T,S> = null;
/* END OF DECLARATION */
if (Array.isArray && Array.isArray(value) && mobservableStatic.debugLevel)
warn("mobservable.value() was invoked with an array. Probably you want to create an mobservable.array() instead of observing a reference to an array?");
module mobservable { // wrap in module for UMD export, see end of the file
function createObservable<T>(value:T[]):IObservableArray<T>;
function createObservable<T>(value?:T|{():T}, scope?:Object):IObservableValue<T>;
function createObservable(value?, scope?:Object):any {
if (Array.isArray(value))
return new ObservableArray(value);
if (typeof value === "function")
prop = new ComputedObservable(<()=>T>value, scope);
else
prop = new ObservableValue(<T>value, scope);
var propFunc = function(value?:T):T|S {
if (arguments.length > 0)
return <S> prop.set(value);
else
return <T> prop.get();
};
(<any>propFunc).observe = prop.observe.bind(prop);
(<any>propFunc).prop = prop;
(<any>propFunc).toString = function() { return prop.toString(); };
return <IObservableValue<T,S>> propFunc;
return mobservableStatic.computed(value, scope);
return mobservableStatic.primitive(value);
}
/**
@see mobservableStatic.value
*/
var mobservableStatic:MobservableStatic = <MobservableStatic> function<T,S>(value?:T|{():T}, scope?:S):IObservableValue<T,S> {
export var mobservableStatic:IMObservableStatic = <IMObservableStatic> function(value, scope?) {
return createObservable(value,scope);
};
/**
@see createObservable
*/
mobservableStatic.value = createObservable;
mobservableStatic.primitive = mobservableStatic.reference = function(value?) {
return new ObservableValue(value).createGetterSetter();
}
mobservableStatic.computed = function<T>(func:()=>void, scope?) {
return new ComputedObservable(func, scope).createGetterSetter();
}
mobservableStatic.array = function array<T>(values?:T[]): ObservableArray<T> {
return new ObservableArray(values);
}
mobservableStatic.props = function props(target, props?, value?) {
switch(arguments.length) {
case 0:
throw new Error("Not enough arguments");
case 1:
return mobservableStatic.props(target, target); // mix target properties into itself
case 2:
for(var key in props)
mobservableStatic.props(target, key, props[key]);
break;
case 3:
var isArray = Array.isArray(value);
var observable = mobservableStatic.value(value, target);
Object.defineProperty(target, props, {
get: isArray
? function() { return observable; }
: observable,
set: isArray
? function(newValue) { (<IObservableArray<any>><any>observable).replace(newValue) }
: observable,
enumerable: true,
configurable: false
});
break;
}
return target;
}
/**
DebugLevel: level 0: warnings only, level 1 or higher, prints a lot of messages.
*/
mobservableStatic.debugLevel = 0;
* Use this annotation to wrap properties of an object in an observable, for example:
* class OrderLine {
* @observable amount = 3;
* @observable price = 2;
* @observable total() {
* return this.amount * this.price;
* }
* }
*/
mobservableStatic.observable = function observable(target:Object, key:string, descriptor?) {
var baseValue = descriptor ? descriptor.value : null;
// observable annotations are invoked on the prototype, not on actual instances,
// so upon invocation, determine the 'this' instance, and define a property on the
// instance as well (that hides the propotype property)
if (typeof baseValue === "function") {
delete descriptor.value;
delete descriptor.writable;
descriptor.get = function() {
mobservableStatic.props(this, key, baseValue);
return this[key];
}
descriptor.set = function () {
console.trace();
throw new Error("It is not allowed to reassign observable functions");
}
} else {
Object.defineProperty(target, key, {
configurable: false, enumberable:true,
get: function() {
mobservableStatic.props(this, key, undefined);
return this[key];
},
set: function(value) {
mobservableStatic.props(this, key, value);
}
});
}
}
/**
Evaluates func and return its results. Watch tracks all observables that are used by 'func'
and invokes 'onValidate' whenever func *should* update.
Returns a tuplde [return value of func, disposer]. The disposer can be used to abort the watch early.
*/
mobservableStatic.watch = function watch<T>(func:()=>T, onInvalidate:Lambda):[T,Lambda] {
var dnode = new DNode(true);
var retVal:T;
dnode.nextState = function() {
retVal = func();
dnode.nextState = function() {
dnode.dispose();
onInvalidate();
return false;
* Inverse function of `props` and `array`, given an (observable) array, returns a plain,
* non observable version. (non recursive), or given an object with observable properties, returns a clone
* object with plain properties.
*
* Any other value will be returned as is.
*/
mobservableStatic.toPlainValue = function toPlainValue(value:any):any {
if (value) {
if (value instanceof Array)
return value.slice();
else if (value instanceof ObservableValue)
return value.get();
else if (typeof value === "function" && value.impl) {
if (value.impl instanceof ObservableValue)
return value()
else if (value.impl instanceof ObservableArray)
return value().slice();
}
else if (typeof value === "object") {
var res = {};
for (var key in value)
res[key] = toPlainValue(value[key]);
return res;
}
return false;
}
dnode.computeNextState();
return [retVal, () => dnode.dispose()];
return value;
}

@@ -119,6 +221,6 @@

(Since properties do not expose an .observe method themselves).
*/
*/
mobservableStatic.observeProperty = function observeProperty(object:Object, key:string, listener:(...args:any[])=>void, invokeImmediately = false):Lambda {
if (!object || !key || object[key] === undefined)
throw new Error(`Object '${object}' has no key '${key}'.`);
throw new Error(`Object '${object}' has no property '${key}'.`);
if (!listener || typeof listener !== "function")

@@ -133,4 +235,4 @@ throw new Error("Third argument to mobservable.observeProperty should be a function");

// IObservable? -> attach observer
else if (currentValue.prop && currentValue.prop instanceof ObservableValue)
return currentValue.prop.observe(listener, invokeImmediately);
else if (currentValue.impl && (currentValue.impl instanceof ObservableValue || currentValue instanceof ObservableArray))
return currentValue.impl.observe(listener, invokeImmediately);

@@ -150,13 +252,12 @@ // wrap with observable function

mobservableStatic.array = function array<T>(values?:T[]): ObservableArray<T> {
return new ObservableArray(values);
/**
Evaluates func and return its results. Watch tracks all observables that are used by 'func'
and invokes 'onValidate' whenever func *should* update.
Returns a tuplde [return value of func, disposer]. The disposer can be used to abort the watch early.
*/
mobservableStatic.watch = function watch<T>(func:()=>T, onInvalidate:Lambda):[T,Lambda] {
var watch = new WatchedExpression(func, onInvalidate);
return [watch.value, () => watch.dispose()];
}
mobservableStatic.toJSON = function toJSON<T>(value:T):T {
if (value instanceof ObservableArray)
return (<any>value).values();
// later on, more cases like objects and such
return value;
}
mobservableStatic.batch = function batch<T>(action:()=>T):T {

@@ -166,85 +267,12 @@ return Scheduler.batch(action);

mobservableStatic.onReady = function onReady(listener:Lambda):Lambda {
return Scheduler.onReady(listener);
}
mobservableStatic.debugLevel = 0;
mobservableStatic.onceReady = function onceReady(listener:Lambda) {
Scheduler.onceReady(listener);
}
mobservableStatic.observable = function observable(target:Object, key:string, descriptor?) {
var baseValue = descriptor ? descriptor.value : null;
// observable annotations are invoked on the prototype, not on actual instances,
// so upon invocation, determine the 'this' instance, and define a property on the
// instance as well (that hides the propotype property)
if (typeof baseValue === "function") {
delete descriptor.value;
delete descriptor.writable;
descriptor.get = function() {
mobservableStatic.defineObservableProperty(this, key, baseValue);
return this[key];
}
descriptor.set = function () {
console.trace();
throw new Error("It is not allowed to reassign observable functions");
}
} else {
Object.defineProperty(target, key, {
configurable: true, enumberable:true,
get: function() {
mobservableStatic.defineObservableProperty(this, key, undefined);
return this[key];
},
set: function(value) {
if (Array.isArray(value)) {
var ar = new ObservableArray(value);
Object.defineProperty(this, key, {
value: ar,
writeable: false,
configurable: false,
enumberable: true
});
}
else
mobservableStatic.defineObservableProperty(this, key, value);
}
});
}
}
mobservableStatic.defineObservableProperty = function defineObservableProperty<T>(object:Object, name:string, initialValue?:T) {
var _property = mobservableStatic.value(initialValue, object);
definePropertyForObservable(object, name, _property);
}
mobservableStatic.initializeObservableProperties = function initializeObservableProperties(object:Object) {
for(var key in object) if (object.hasOwnProperty(key)) {
if (object[key] && object[key].prop && object[key].prop instanceof ObservableValue)
definePropertyForObservable(object, key, <IObservableValue<any,any>> object[key])
}
}
function definePropertyForObservable(object:Object, name:string, observable:IObservableValue<any,any>) {
Object.defineProperty(object, name, {
get: function() {
return observable();
},
set: function(value) {
observable(value);
},
enumerable: true,
configurable: true
});
}
class ObservableValue<T,S> {
class ObservableValue<T> {
protected changeEvent = new SimpleEventEmitter();
protected dependencyState:DNode = new DNode(false);
protected dependencyState:DNode = new DNode(this);
constructor(protected _value?:T, protected scope?:S){
constructor(protected _value?:T){
}
set(value:T):S {
set(value:T) {
if (value !== this._value) {

@@ -257,3 +285,2 @@ var oldValue = this._value;

}
return this.scope;
}

@@ -276,2 +303,16 @@

}
createGetterSetter():IObservableValue<T> {
var self = this;
var f:any = function(value?) {
if (arguments.length > 0)
self.set(value);
else
return self.get();
};
f.observe = (listener, fire) => this.observe(listener, fire);
f.impl = this;
f.toString = () => this.toString();
return f;
}

@@ -283,12 +324,10 @@ toString() {

class ComputedObservable<U,S> extends ObservableValue<U,S> {
class ComputedObservable<U> extends ObservableValue<U> {
private isComputing = false;
private hasError = false;
constructor(protected func:()=>U, scope:S) {
super(undefined, scope);
if (!func)
constructor(protected func:()=>U, private scope?:Object) {
super(undefined);
if (typeof func !== "function")
throw new Error("ComputedObservable requires a function");
this.dependencyState.isComputed = true;
this.dependencyState.nextState = this.compute.bind(this);
}

@@ -299,3 +338,3 @@

throw new Error("Cycle detected");
var state = this.dependencyState;
var state = this.dependencyState;
if (state.isSleeping) {

@@ -327,3 +366,3 @@ if (DNode.trackingStack.length > 0) {

set(_:U):S {
set(_:U) {
throw new Error(this.toString() + ": A computed observable does not accept new values!");

@@ -366,2 +405,31 @@ }

/**
* given an expression, evaluate it once and track its dependencies.
* Whenever the expression *should* re-evaluate, the onInvalidate event should fire
*/
class WatchedExpression<T> {
private dependencyState = new DNode(this);
private didEvaluate = false;
public value:T;
constructor(private expr:()=>T, private onInvalidate:()=>void){
this.dependencyState.computeNextState();
}
compute() {
if (!this.didEvaluate) {
this.didEvaluate = true;
this.value = this.expr();
} else {
this.dispose();
this.onInvalidate();
}
return false;
}
dispose() {
this.dependencyState.dispose();
}
}
enum DNodeState {

@@ -392,5 +460,6 @@ STALE, // One or more depencies have changed but their values are not yet known, current value is stale

private externalRefenceCount = 0; // nr of 'things' that depend on us, excluding other DNode's. If > 0, this node will not go to sleep
public isComputed:boolean;; // isComputed indicates that this node can depend on others, and should update when dependencies change
constructor(public isComputed:boolean) {
// isComputed indicates that this node can depend on others.
constructor(private owner:{compute?:()=>boolean}) {
this.isComputed = owner.compute !== undefined;
}

@@ -431,4 +500,2 @@

this.notifyObservers(stateDidActuallyChange);
if (this.observers.length === 0) // otherwise, let one of the observers do that :)
Scheduler.scheduleReady();
}

@@ -443,3 +510,3 @@

tryToSleep() {
if (this.isComputed && this.observers.length === 0 && this.externalRefenceCount === 0 && !this.isSleeping) {
if (!this.isSleeping && this.isComputed && this.observers.length === 0 && this.externalRefenceCount === 0) {
for (var i = 0, l = this.observing.length; i < l; i++)

@@ -460,2 +527,3 @@ this.observing[i].removeObserver(this);

// the state of something we are observing has changed..
notifyStateChange(observable:DNode, stateDidActuallyChange:boolean) {

@@ -465,6 +533,6 @@ if (observable.state === DNodeState.STALE) {

this.markStale();
} else { // ready
} else { // not stale, thus ready since pending states are not propagated
if (stateDidActuallyChange)
this.dependencyChangeCount += 1;
if (--this.dependencyStaleCount === 0) {
if (--this.dependencyStaleCount === 0) { // all dependencies are ready
this.state = DNodeState.PENDING;

@@ -486,3 +554,3 @@ Scheduler.schedule(() => {

this.trackDependencies();
var stateDidChange = this.nextState();
var stateDidChange = this.owner.compute();
this.bindDependencies();

@@ -492,6 +560,2 @@ this.markReady(stateDidChange);

nextState():boolean {
return false; // false == unchanged
}
private trackDependencies() {

@@ -561,3 +625,8 @@ this.prevObserving = this.observing;

class ObservableArray<T> implements Array<T> {
// Workaround to make sure ObservableArray extends Array
class StubArray {
}
StubArray.prototype = [];
class ObservableArray<T> extends StubArray implements IObservableArray<T> {
[n: number]: T;

@@ -570,7 +639,8 @@

constructor(initialValues?:T[]) {
super();
// make for .. in / Object.keys behave like an array, so hide the other properties
Object.defineProperties(this, {
"dependencyState" : { enumerable: false, value: new DNode(false) },
"dependencyState" : { enumerable: false, value: new DNode(this) },
"_values" : { enumerable: false, value: initialValues ? initialValues.slice() : [] },
"changeEvent" : { enumerable: false, value: new SimpleEventEmitter() },
"changeEvent" : { enumerable: false, value: new SimpleEventEmitter() }
});

@@ -645,3 +715,3 @@ if (initialValues && initialValues.length)

// conform: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/observe
this.changeEvent.emit({ object: this, type: 'update', index: index, oldValue: oldValue});
this.changeEvent.emit(<IArrayChange<T>>{ object: this, type: 'update', index: index, oldValue: oldValue});
}

@@ -654,3 +724,3 @@

// conform: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/observe
this.changeEvent.emit({ object: this, type: 'splice', index: index, addedCount: added.length, removed: deleted});
this.changeEvent.emit(<IArraySplice<T>>{ object: this, type: 'splice', index: index, addedCount: added.length, removed: deleted});
}

@@ -663,5 +733,5 @@

observe(listener:(data)=>void, fireImmediately=false):Lambda {
observe(listener:(changeData:IArrayChange<T>|IArraySplice<T>)=>void, fireImmediately=false):Lambda {
if (fireImmediately)
listener({ object: this, type: 'splice', index: 0, addedCount: this._values.length, removed: []});
listener(<IArraySplice<T>>{ object: this, type: 'splice', index: 0, addedCount: this._values.length, removed: []});
return this.changeEvent.on(listener);

@@ -693,6 +763,20 @@ }

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
find(predicate:(item:T,index:number,array:ObservableArray<T>)=>boolean, thisArg?, fromIndex=0):T {
this.dependencyState.notifyObserved();
var items = this._values, l = items.length;
for(var i = fromIndex; i < l; i++)
if(predicate.call(thisArg, items[i], i, this))
return items[i];
return null;
}
/*
functions that do alter the internal structure of the array, from lib.es6.d.ts
functions that do alter the internal structure of the array, (based on lib.es6.d.ts)
since these functions alter the inner structure of the array, the have side effects.
Because the have side effects, they should not be used in computed function,
and for that reason the do not call dependencyState.notifyObserved
*/
splice(index:number, deleteCount?:number, ...newItems:T[]):T[] {
this.sideEffectWarning("splice");
switch(arguments.length) {

@@ -710,2 +794,3 @@ case 0:

push(...items: T[]): number {
this.sideEffectWarning("push");
this.spliceWithArray(this._values.length, 0, items);

@@ -716,2 +801,3 @@ return this._values.length;

pop(): T {
this.sideEffectWarning("pop");
return this.splice(Math.max(this._values.length - 1, 0), 1)[0];

@@ -721,2 +807,3 @@ }

shift(): T {
this.sideEffectWarning("shift");
return this.splice(0, 1)[0]

@@ -726,2 +813,3 @@ }

unshift(...items: T[]): number {
this.sideEffectWarning("unshift");
this.spliceWithArray(0, 0, items);

@@ -732,2 +820,3 @@ return this._values.length;

reverse():T[] {
this.sideEffectWarning("reverse");
return this.replace(this._values.reverse());

@@ -737,4 +826,16 @@ }

sort(compareFn?: (a: T, b: T) => number): T[] {
this.sideEffectWarning("sort");
return this.replace(this._values.sort.apply(this._values, arguments));
}
remove(value:T):boolean {
this.sideEffectWarning("remove");
var idx = this._values.indexOf(value);
if (idx > -1) {
this.splice(idx, 1);
return true;
}
return false;
}
/*

@@ -768,2 +869,7 @@ functions that do not alter the array, from lib.es6.d.ts

private sideEffectWarning(funcName:string) {
if (DNode.trackingStack.length > 0)
warn(`[Mobservable.Array] The method array.${funcName} should not be used inside observable functions since it has side-effects`);
}
static OBSERVABLE_ARRAY_BUFFER_SIZE = 0;

@@ -775,3 +881,3 @@ static ENUMERABLE_PROPS = [];

enumerable: false,
configurable: true,
configurable: false,
set: function(value) {

@@ -800,2 +906,3 @@ if (index < this._values.length) {

prop.enumerable = true;
prop.configurable = true;
ObservableArray.ENUMERABLE_PROPS[index] = prop;

@@ -812,3 +919,3 @@ }

class SimpleEventEmitter {
class SimpleEventEmitter implements ISimpleEventEmitter {
listeners:{(data?):void}[] = [];

@@ -856,4 +963,2 @@

class Scheduler {
private static pendingReady = false;
private static readyEvent = new SimpleEventEmitter();
private static inBatch = 0;

@@ -871,12 +976,12 @@ private static tasks:{():void}[] = [];

var i = 0;
try {
for(; i < Scheduler.tasks.length; i++)
Scheduler.tasks[i]();
Scheduler.tasks = [];
} catch (e) {
console.error("Failed to run scheduled action, the action has been dropped from the queue: " + e, e);
// drop already executed tasks, including the failing one, and retry in the future
Scheduler.tasks.splice(0, i + 1);
setTimeout(Scheduler.runPostBatchActions, 1);
throw e; // rethrow
while(Scheduler.tasks.length) {
try { // try outside loop; much cheaper
for(; i < Scheduler.tasks.length; i++)
Scheduler.tasks[i]();
Scheduler.tasks = [];
} catch (e) {
console.error("Failed to run scheduled action, the action has been dropped from the queue: " + e, e);
// drop already executed tasks, including the failing one, and continue with other actions, to keep state as stable as possible
Scheduler.tasks.splice(0, i + 1);
}
}

@@ -892,25 +997,9 @@ }

if (--Scheduler.inBatch === 0) {
// make sure follow up actions are processed in batch after the current queue
Scheduler.inBatch += 1;
Scheduler.runPostBatchActions();
Scheduler.scheduleReady();
Scheduler.inBatch -= 1;
}
}
}
static scheduleReady() {
if (!Scheduler.pendingReady) {
Scheduler.pendingReady = true;
setTimeout(() => {
Scheduler.pendingReady = false;
Scheduler.readyEvent.emit();
}, 1);
}
}
static onReady(listener:Lambda) {
return Scheduler.readyEvent.on(listener);
}
static onceReady(listener:Lambda) {
return Scheduler.readyEvent.once(listener);
}
}

@@ -1011,2 +1100,24 @@

export = mobservableStatic;
} // end of module
/* typescript does not support UMD modules yet, lets do it ourselves... */
declare var define;
declare var exports;
declare var module;
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD.
define('mobservable', [], function () {
return (factory());
});
} else if (typeof exports === 'object') {
// CommonJS like
module.exports = factory();
} else {
// register global
root['mobservable'] = factory();
}
}(this, function () {
return mobservable.mobservableStatic;
}));
{
"name": "mobservable",
"version": "0.3.3",
"description": "Changes are coming! Small library for creating observable properties en functions",
"version": "0.4.0",
"description": "Changes are coming! Small library for creating observable properties, arrays and functions",
"main": "dist/mobservable.js",
"scripts": {
"test": "grunt cover",
"prepublish": "grunt build"
"prepublish": "grunt builddist"
},

@@ -24,7 +24,22 @@ "repository": {

"grunt-coveralls": "^1.0.0",
"grunt-ts": "^4.0.1",
"grunt-exec": "^0.4.6",
"grunt-contrib-uglify": "^0.9.1",
"mkdirp": "^0.5.1",
"nodeunit-browser-tap": "^0.1.0",
"nscript": "^0.1.5",
"typescript": "^1.5.0-alpha"
"typescript": "^1.5.0-beta"
},
"testling": {
"files": "test/browser/test.js",
"browsers": [
"ie/6..latest",
"chrome/22..latest",
"firefox/16..latest",
"safari/latest",
"opera/11.0..latest",
"iphone/6",
"ipad/6",
"android-browser/latest"
]
}
}
# MOBservable
*Changes are coming!*
MOBservable is light-weight stand-alone observable implementation, based on the ideas of observables in bigger frameworks like `knockout`, `ember`, but this time without 'strings attached'. MOBservables allows you to observe primitive values, references, functions and arrays.
[![Build Status](https://travis-ci.org/mweststrate/MOBservable.svg?branch=master)](https://travis-ci.org/mweststrate/MOBservable)

@@ -12,281 +8,477 @@ [![Coverage Status](https://coveralls.io/repos/mweststrate/MOBservable/badge.svg?branch=master)](https://coveralls.io/r/mweststrate/MOBservable)

[Typescript typings](https://github.com/mweststrate/MOBservable/blob/master/mobservable.d.ts)
Installation: `npm install mobservable --save`
# Observable values
MOBservable is light-weight stand-alone observable implementation, that helps you to create reactive data structures, based on the ideas of observables in bigger frameworks like `knockout`, `ember`, but this time without 'strings attached'.
MOBservables allows you to observe primitive values, references, functions and arrays and makes sure that all changes in your data are propagated automatically, atomically and synchronously.
The `mobservable.value(valueToObserve)` method (or just its shorthand: `mobservable(valueToObserve)`) takes a value or function and creates an observable value from it. A quick example:
# Examples
```typescript
/// <reference path='./node_modules/mobservable/mobservable.d.ts'/>
import mobservable = require('mobservable');
[Fiddle demo: MOBservable + JQuery](http://jsfiddle.net/mweststrate/vxn7qgdw)
var vat = mobservable.value(0.20);
## Example: Observable values and functions
var order = {};
order.price = mobservable.value(10),
order.priceWithVat = mobservable.value(() => order.price() * (1 + vat()));
The core of `MOBservable` consists of observable values, functions that automatically recompute when an observed value changes,
and the possibility to listen to changing values and updated computations.
order.priceWithVat.observe((price) => console.log("New price: " + price));
```javascript
var mobservable = require('mobservable');
order.price(20);
// Prints: New price: 24
vat(0.10);
// Prints: New price: 22
var nrOfCatz = mobservable(3);
var nrOfDogs = mobservable(8);
// Create a function that automatically observes values:
var nrOfAnimals = mobservable(function() {
// calling an mobservable without arguments acts as getter
return nrOfCatz() * nrOfDogs();
});
// Print a message whenever the observable changes:
nrOfAnimals.observe(function(amount) {
console.log("Total: " + amount);
}, true);
// -> Prints: "Total: 11"
// calling an mobservable with a value acts as setter,
// ...and automatically updates all computations in which it was used
nrOfCatz(34);
// -> Prints: "Total: 42"
```
## mobservable.value(value, scope?):IObservableValue
## Example: Observable objects & properties
Constructs a new observable value. The value can be everything that is not a function, or a function that takes no arguments and returns a value. In the body of the function, references to other properties will be tracked, and on change, the function will be re-evaluated. The returned value is an `IProperty` function/object. Passing an array or object into the `value` method will only observe the reference, not the contents of the objects itself. To observe the contents of an array, use `mobservable.array`, to observe the contents of an object, just make sure its (relevant) properties are observable values themselves.
By using `.props`, it is possible to create observable values and functions that can be assigned or read as normal properties.
The method optionally accepts a scope parameter, which will be returned by the setter for chaining, and which will be used as scope for calculated properties, for example:
```javascript
var value = mobservable.value;
var mobservable = require('mobservable');
function OrderLine(price, amount) {
this.price = value(price);
this.amount = value(amount);
this.total = value(function() {
return this.price() * this.amount();
}, this)
var Person = function(firstName, lastName) {
// define the observable properties firstName, lastName and fullName on 'this'.
mobservable.props(this, {
firstName: firstName,
lastName: lastName,
fullName: function() {
return this.firsName + " " + this.lastName;
}
});
}
var jane = new Person("Jane","Dôh");
// (computed) properties can be accessed like any other property:
console.log(jan.fullName);
// prints: "Jan Dôh"
// properties can be observed as well:
mobsevable.observeProperty(jane, "fullName", console.log);
// values can be assigned directly to observable properties
jane.lastName = "Do";
// prints: "Jane Do"
```
**Note: `mobservable.value` versus `mobservable.array`**
Do *not* confuse `mobservable.value([])` (or `mobservable([])`) with `mobservable.array([])`, the first creates an observable reference to an array, but does not observe its contents. The later observes the contents from the array you pass into it.
## Example: Observable arrays
## mobservable.array(initialValues?):ObservableArray
`mobservable` provides an observable array implementation, which is fully ES5 compliant,
but which will notify dependent computations upon each change.
**Note: ES5 environments only**
```javascript
import mobservable = require('mobservable');
Constructs an array like, observable structure. An observable array is a thin abstraction over native arrays that adds observable properties. The only noticable difference between built-in arrays is that these arrays cannot be sparse, that is, values assigned to an index larger than `length` are not oberved (nor any other property that is assigned to a non-numeric index). In practice, this should harldy be an issue. Example:
// create an array, that works by all means as a normal array, except that it is observable!
var someNumbers = mobservable.value([1,2,3]);
```javascript
var numbers = mobservable.array([1,2,3]);
// a naive function that sums all the values
var sum = mobservable.value(function() {
return numbers.reduce(function(a, b) { return a + b }, 0);
for(var s = 0, i = 0; i < someNumbers.length; i++)
s += someNumbers[i];
return s;
});
sum.observe(function(s) { console.log(s); });
sum.observe(console.log);
numbers[3] = 4;
// prints 10
numbers.push(5,6);
// prints 21
numbers.unshift(10)
// prints 31
someNumbers.push(4);
// Prints: 10
someNumbers[2] = 0;
// Prints: 7
someNumbers[someNumbers.length] = 5;
// Prints: 12
```
**Note: do not reassign a array variables!**
## Example: TypeScript classes and annotations
In general you should never (need to) reassign variables that hold an observable array, instead, use the `replace` method on the array. If you reassign a variable that holds an observable array, the reassignment won't be visible to any of it observers; they will still be observing the original array:
For typescript users, `mobservable` ships with module typings and an `@observable` annotation with which class members can be marked as observable.
```javascript
var numbers = mobservable.array([1,2]);
// .. stuff that depends on numbers
// bad:
var numbers = mobservable.array([1,2,3]);
// good:
numbers.replace([1,2,3]);
```
```typescript
/// <reference path="./node_modules/mobservable/mobservable.d.ts"/>
import mobservable = require('mobservable');
var observable = mobservable.observable;
## mobservable.Observable annotation
class Order {
@observable orderLines: OrderLine[] = [];
@observable total() {
return this.orderLines.reduce((sum, orderLine) => sum + orderLine.total, 0)
}
}
**Note: ES5, TypeScript 1.5+ environments only**
class OrderLine {
@observable price:number = 0;
@observable amount:number = 1;
Marks a property or method as observable. This annotations basically wraps `mobservable.defineObservableProperty`. If the annotations is used in combination with an array property, an observable array will be created.
constructor(price) {
this.price = price;
}
```typescript
var observable = require('mobservable').observable;
class Order {
@observable price:number = 3;
@observable amount:number = 2;
@observable orders = [];
@observable total() {
return this.amount * this.price * (1 + orders.length);
return "Total: " + this.price * this.amount;
}
}
}
var order1 = new Order();
order1.total.observe(console.log);
order1.orderLines.push(new OrderLine(7));
// Prints: Total: 7
order1.orderLines.push(new OrderLine(12));
// Prints: Total: 12
order1.orderLines[0].amount = 3;
// Prints: Total: 33
```
## mobservable.defineObservableProperty(object, name, value)
# Processing observables
**Note: ES5 environments only**
Observable values, arrays and functions created by `mobservable` possess the following characteristics:
Defines a property using ES5 getters and setters. This is useful in constructor functions, and allows for direct assignment / reading from observables:
* _synchronous_. All updates are processed synchronously, that is, the pseudo expressions `a = 3; b -> a * 2; a = 4; print(b); ` will always print `4`; `b` will never yield a stale value (unless `batch` is used).
* _atomic_. All computed values will postpone updates until all inputs are settled, to make sure no intermediate values are visible. That is, the expression `a = 3; b -> a * 2; c -> a * b; a = 4; print(c)` will always print `36` and no intermediate values like `24`.
* _real time dependency detection_. Computed values only depend on values actually used in the last computation, for example in this `a -> b > 5 ? c : b` the variable `c` will only cause a re-evaluation of a if `b` > 5.
* _lazy_. Computed values will only be evaluated if they are actually being observed. So make sure computed functions are pure and side effect free; the library might not evaluate the expression as often as you thought it would.
* _cycle detection_. Cycles in computes, like in `a -> 2 * b; b -> 2 * a;` will be deteced automatically.
* _error handling_. Exceptions that are raised during computations are propagated to consumers.
```javascript
var vat = mobservable.value(0.2);
# API
var Order = function() {
mobservable.defineObservableProperty(this, 'price', 20);
mobservable.defineObservableProperty(this, 'amount', 2);
mobservable.defineObservableProperty(this, 'total', function() {
return (1+vat()) * this.price * this.amount; // price and amount are now properties!
});
};
[Typescript typings](https://github.com/mweststrate/MOBservable/blob/master/mobservable.d.ts)
var order = new Order();
order.price = 10;
order.amount = 3;
// order.total now equals 36
## Creating observables
### mobservable
Shorthand for `mobservable.value`
### mobservable.value
`mobservable.value<T>(value? : T[], scope? : Object) : IObservableArray<T>`
`mobservable.value<T>(value? : T|()=>T, scope? : Object) : IObservableValue<T>`
Function that creates an observable given a `value`.
Depending on the type of the function, this function invokes `mobservable.array`, `mobservable.computed` or `mobservable.primitive`.
See the examples above for usage patterns. The `scope` is only meaningful if a function is passed into this method.
### mobservable.primitive
`mobservable.primitive<T>(value? : T) : IObservableValue<T>`
Creates a new observable, initialzed with the given `value` that can change over time.
The returned observable is a function, that without arguments acts as getter, and with arguments as setter.
Furthermore its value can be observed using the `.observe` method, see `IObservableValue.observe`.
Example:
```
var vat = mobservable.primitive(3);
console.log(vat()); // prints '3'
vat.observe(console.log); // register an observer
vat(4); // updates value, also notifies all observers, thus prints '4'
```
In typescript < 1.5, it might be more convenient for the typesystem to directly define getters and setters instead of using `mobservable.defineProperty` (or, use `mobservable.initializeObservableProperties`):
```typescript
class Order {
_price = new mobservable.value(20, this);
get price() {
return this._price();
}
set price(value) {
this._price(value);
}
}
### mobservable.reference
`mobservable.reference<T>(value? : T) : IObservableValue<T>`
Synonym for `mobservable.primitive`, since the equality of primitives is determined in the same way as references, namely by strict equality.
(from version 0.6, see `mobservable.struct` if values need to be compared structuraly by using deep equality).
### mobservable.computed
`mobservable.computed<T>(expr : () => T, scope?) : IObservableValue<T>`
`computed` turns a function into an observable value.
The provided `expr` should not have any arguments, but instead really on other observables that are in scope to determine its value.
The latest value returned by `expr` determines the value of the observable. When one of the observables used in `expr` changes, `computed` will make sure that the function gets re-evaluated, and all updates are propogated to the children.
```javascript
var amount = mobservable(3);
var price = mobservable(2);
var total = mobservable.computed(function() {
return amount() * price();
});
console.log(total()); // gets the current value, prints '6'
total.observe(console.log); // attach listener
amount(4); // update amount, total gets re-evaluated automatically and will print '8'
amount(4); // update amount, but total will not be re-evaluated since the value didn't change
```
## mobservable.initializeObservableProperties(object)
The optional `scope` parameter defines `this` context during the evaluation of `expr`.
`computed` will try to reduce the amount of re-evaluates of `expr` as much as possible. For that reason the function *should* be pure, that is:
* The result of `expr` should only be defined in terms of other observables, and not depend on any other state.
* Your code shouldn't rely on any side-effects, triggered by `expr`; `expr` should be side-effect free.
* The result of `expr` should always be the same if none of the observed observables did change.
It is not allowed for `expr` to have an (implicit) dependency on its own value.
It is allowed to throw exceptions in an observed function. The thrown exceptions might only be detected late.
The exception will be rethrown if somebody inspects the current value, and will be passed as first callback argument
to all the listeners.
### mobservable.array
`mobservable.array<T>(values? : T[]) : IObservableArray<T>`
**Note: ES5 environments only**
Converts all observables of the given object into property accessors. For example:
Constructs an array like, observable structure. An observable array is a thin abstraction over native arrays that adds observable properties.
The most notable difference between built-in arrays is that these arrays cannot be sparse, that is, values assigned to an index larger than `length` are considered out-of-bounds and not oberved (nor any other property that is assigned to a non-numeric index).
Furthermore, `Array.isArray(observableArray)` and `typeof observableArray === "array"` will yield `false` for observable arrays, but `observableArray instanceof Array` will return `true`.
```javascript
var Order = function() {
this.price = value(20);
this.amount = value(2);
this.nonsense = 3;
this.total = value(function() {
return (1+vat()) * this.price * this.amount; // price and amount are now properties!
}, this);
mobservable.initializeObservableProperties(this);
};
var numbers = mobservable.array([1,2,3]);
var sum = mobservable.value(function() {
return numbers.reduce(function(a, b) { return a + b }, 0);
});
sum.observe(function(s) { console.log(s); });
var order = new Order();
console.log(order.total); // prints 36
numbers[3] = 4;
// prints 10
numbers.push(5,6);
// prints 21
numbers.unshift(10);
// prints 31
```
Or in typescript pre 1.5, where annotations are not yet supported:
Observable arrays implement all the ES5 array methods. Besides those, the following methods are available as well:
```typescript
class Order {
price:number = <number><any>new mobservable.value(20, this);
constructor() {
mobservable.initializeObservableProperties(this);
}
}
```
* `observe(listener:(changeData:IArrayChange<T>|IArraySplice<T>)=>void, fireImmediately?:boolean):Lambda` Listen to changes in this array. The callback will receive arguments that express an array splice or array change, conform the [ES7 proposal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/observe)
* `clear(): T[]` Remove all current entries from the array
* `replace(newItems:T[])` Replaces all existing entries in the array with new ones.
* `values(): T[]` Returns a shallow clone of the array, similar to `.slice`
* `clone(): IObservableArray<T>` Create a new observable array containing the same values
* `find(predicate:(item:T,index:number,array:IObservableArray<T>)=>boolean,thisArg?,fromIndex?:number):T` Find implementation, basically the same as the ES7 Array.find proposal, but with added `fromIndex` parameter.
* `remove(value:T):boolean` Remove a single item by value from the array. Returns true if the item was found and removed.
## mobservable.observeProperty
`function observeProperty(object:Object, key:string, listener:Function, fireImmediately=false):Lambda`
### mobservable.props
Observes the observable property `key` of `object`. This is useful if you want to observe properties created using the `observable` annotation or the `defineObservableProperty` method, since for those properties their own `observe` method is not publicly available.
```typescript
class Order {
@observable total = () => this.price * this.amount;
}
var order = new Order();
props(target:Object, name:string, initialValue: any):Object;
props(target:Object, props:Object):Object;
props(target:Object):Object;
```
**Note: ES5 environments only**
mobservable.observeProperty(order, 'total', (newPrice) => console.log("New price: " + newPrice));
Creates observable properties on the given `target` object. This function uses `mobservable.value` internally to create observables.
Creating properties has as advantage that they are more convenient to use. See also [props or variables](#value_versus_props).
The original `target`, with the added properties, is returned by this function. Functions used to created computed observables will automatically
be bound to the correct `this`.
```javascript
var order = {};
mobservable.props(order, {
amount: 3,
price: 5,
total: function() {
return this.amount * this.price; // note that no setters are needed
}
});
order.amount = 4;
console.log(order.total); // Prints '20'
```
## mobservable.watch(func, onInvalidate)
Note that observables created by `mobservable.props` do not expose an `.observe` method, to observe properties, see [`mobservable.observeProperty`](#mobservable_observeproperty)
`watch` invokes `func` and returns a tuple consisting of the return value of `func` and an unsubscriber. `watch` will track which observables `func` was observing, but it will *not* recalculate `func` if necessary, instead, it will fire the `onInvalidate` callback to notify that the output of `func` can no longer be trusted.
Other forms in which this function can be called:
```javascript
mobservable.props(order, "price", 3); // equivalent to mobservable.props(order, { price: 3 });
var order = mobservable.props({ price: 3}); // uses the original object as target, that is, all values in it are replaced by their observable counterparts
```
The `onInvalidate` function will be called only once, after that, the watch has finished. To abort a watch, use the returned unsubscriber.
### mobservable.observable annotation
`Watch` is useful in functions where you want to have a function that responds to change, but where the function is actually invoked as side effect or part of a bigger change flow or where unnecessary recalculations of `func` or either pointless or expensive, for example in React component render methods
**Note: ES5, TypeScript 1.5+ environments only**
## mobservable.batch(workerFunction)
Typescript 1.5 introduces annotations. The `mobservable.observable` annotation can be used to mark class properties and functions as observable.
This annotations basically wraps `mobservable.props`. Example:
Batch postpones the updates of computed properties until the (synchronous) `workerFunction` has completed. This is useful if you want to apply a bunch of different updates throughout your model before needing the updated computed values, for example while refreshing a value from the database.
```typescript
/// <reference path='./node_modules/mobservable/mobservable.d.ts'/>
var observable = require('mobservable').observable;
## mobservable.onReady(listener) / mobservable.onceReady(listener)
class Order {
@observable price:number = 3;
@observable amount:number = 2;
@observable orders = [];
The listener is invoked each time the complete model has become stable. The listener is always invoked asynchronously, so that even without `batch` the listener is only invoked after a bunch of changes have been applied
@observable total() {
return this.amount * this.price * (1 + orders.length);
}
}
```
## Observing changes
`onReady` returns a function with wich the listener can be unsubscribed from future events
### mobservable.observeProperty
`mobservable.observeProperty(object : Object, key : string, listener : Function, invokeImmediately : boolean = false) : Function`
## `IObservableValue` objects
Observes the observable property `key` of `object`. This is useful if you want to observe properties created using the `observable` annotation or the `props` method,
since for those properties their own `observe` method is not publicly available.
### IObservableValue()
```javascript
function OrderLine(price) {
mobservable.props(this, {
price: price,
amount: 2,
total: function() {
return this.price * this.amount;
}
});
}
If an IObservableValue object is called without arguments, the current value of the observer is returned
var orderLine = new OrderLine(5);
mobservable.observeProperty(order, 'total', console.log, true); // Prints: '10'
```
### IObservableValue(newValue)
### mobservable.watch
`mobservable.watch<T>(func: () => T, onInvalidate : Function) : [T, Function];`
If an IObservable object is called with arguments, the current value is updated. All current observers will be updated as well.
`watch` is quite similar to `mobservable.computed`, but instead of re-evaluating `func` when one of its dependencies has changed, the `onInvalidate` function is triggered.
So `func` will be evaluated only once, and as soon as its value has become stale, the `onInvalidate` callback is triggered.
`watch` returns a tuple consisting of the initial return value of `func` and an unsubscriber to be able to abort the watch.
The `onInvalidate` function will be called only once, after that, the watch has finished.
### IObservableValue.observe(listener,fireImmediately=false)
`watch` is useful in functions where you want to have a function that responds to change,
but where the function is actually invoked as side effect or as part of a bigger change flow or where unnecessary recalculations of `func` or either pointless or expensive,
for example in the `render` method of a React component.
Registers a new listener to change events. Listener should be a function, its first argument will be the new value, and second argument the old value.
### mobservable.batch
Returns a function that upon invocation unsubscribes the listener from the property.
`mobservable.batch<T>(workerFunction : ()=>T):T`
## `ObservableArray`
Batch postpones the updates of computed properties until the (synchronous) `workerFunction` has completed.
This is useful if you want to apply a bunch of different updates throughout your model before needing the updated computed values,
for example while refreshing a data from the database.
An `ObservableArray` is an array-like structure with all the typical behavior of arrays, so you can freely assign new values to (non-sparse) indexes, alter the length, call array functions like `map`, `filter`, `shift` etc. etc. All the ES5 features are in there. Additionally available methods:
```javascript
var amount = mobservable(3);
var price = mobservable(2.5);
var total = mobservable(function() {
return amount() * price();
});
total.observe(console.log);
### ObservableArray.clear()
// without batch:
amount(2); // Prints 5
price(3); // Prints 6
Removes all elements from the array and returns the removed elements. Shorthand for `ObservableArray.splice(0)`
// with batch:
mobservable.batch(function() {
amount(3);
price(4);
});
// Prints 12, after completing the batch
```
### ObservableArray.replace(newItemsArray)
## Utilities
Replaces all the items in the array with `newItemsArray`, and returns the old items.
### mobservable.toPlainValue
`mobservable.toPlainValue<T>(any:T):T;`
### ObservableArray.spliceWithArray(index, deleteCount, newItemsArray)
Converts a (possibly) observable value into a non-observablue value.
For non-primitive values, this function will always return a shallow copy.
Similar to `Array.splice`, but instead of accepting a variable amount of arguments, the third argument should be an array containing the new arguments.
### mobservable.debugLevel
### ObservableArray.observe(callback)
Numeric property, setting this to value to '1' or higher will cause additional debug information to be printed.
Register a callback that will be triggered every time the array is altered. A method to unregister the callback is returned.
### mobservable.SimpleEventEmitter
Utility class for managing an event. Its methods are:
The events that are being fired adhere to the ES7 specs for Array.observe. The event data will be either a `splice` or `update` event, examples:
* `emit(...data : any[])`. Invokes all registered listeners with the given arguments
* `on(listener:(...data : any[]) => void) : () => void`. Registers a new callback that will be invoked on each `emit`. Returns a method that can be used to unsubscribe the listener.
* `once(listener:(...data : any[]) => void) : () => void`. Similar to `.on`, but automatically removes the listener after one invocation.
```javascript
{ object: <array>, type: "update", index: 2, oldValue: 4 },
{ object: <array>, type: "splice", index: 1, addedCount: 2, removed: [4,1] },
```
# Tips & tricks
### ObservableArray.values()
## Use local variables in computations
Returns all the values of this ObservableArray as native, non-observable, javascript array. The returned array is a shallow copy.
Each time an observable value is read, there is a small performance overhead to keep the dependency tree of computations up to date.
Although this might not be noticable in practice, if you want to squeeze the last bit of performance out of the library;
use local variables as much as possible to reduce the amount of observable reads.
This also holds for array entries and object properties created using `mobservable.props`.
## mobservable.SimpleEventEmitter
```javascript
var firstName = mobservable('John');
var lastName = mobservable('Do');
Class that implements a simple event system.
// Ok:
var fullName = mobservable(function() {
if (firstName())
return lastName() + ", " + firstName(); // another read of firstName..
return lastName();
}
### SimpleEventEmitter.emit
// Faster:
var fullName = mobservable(function() {
var first = firstName(), last = lastName();
if (first)
return last+ ", " + first;
return last;
}
```
`emit(...data:any[]):void;`
## Use native array methods
Fires the event represented by this SimpleEventEmitter. All arguments passed to `emit` are passed to the listeners.
For performance, use built-in array methods as much as possible;
a classic array for loop is registered as multiple reads, while a function call is registered as a single read.
Alternatively, slicing the array before using it will also result in a single read.
### SimpleEventEmitter.on
```javascript
var numbers = mobservable([1,2,3]);
`on(listener:(...data:any[])=>void):Lambda;`
// Ok:
var sum1 = mobservable(function() {
var s = 0;
for(var i = 0; i < numbers.length; i++) // observable read
s += numbers[i]; // observable reads
return s;
});
Subscribes a new event listener to this event emitter. The returned function can be used to unsubscribe.
// Faster:
var sum2 = mobservable(function() {
var s = 0, localNumbers = numbers.slice(); // observable read
for(var i = 0; i < localNumbers.length; i++)
s += localNumbers[i];
return s;
});
### SimpleEventEmitter.once
// Also fast:
var sum2 = mobservable(function() {
return numbers.reduce(function(a, b) { // single observable read
return a + b;
}, 0);
});
```
## `.value` versus `.props`
`once(listener:(...data:any[])=>void):Lambda;`
Using `mobservable.value` or `mobservable.props` to create observables inside objects might be a matter of taste.
Here is a small comparison list between the two approaches.
Similar to `on`, but the listener is fired only one time and disposed after that.
| .value | .props |
| ---- | ---|
| ES3 complient | requires ES 5 |
| explicit getter/setter functions: `obj.amount(2)` | object properties with implicit getter/setter: `obj.amount = 2 ` |
| easy to make mistakes; e.g. `obj.amount = 3` instead of `obj.amount(3)`, or `7 * obj.amount` instead of `7 * obj.amount()` wilt both not achieve the intended behavior | Use property reads / assignments |
| easy to observe: `obj.amount.observe(listener)` | `mobservable.observeProperty(obj,'amount',listener)` |
# Using mobservable with Typescript
## `.reference` versus `.array`
Use the following import statement to have strongly typed mobservables in typescript:
Do *not* confuse `mobservable.primitive([])` (or `mobservable([])`) with `mobservable.array([])`,
the first creates an observable reference to an array, but does not observe its contents.
The later observes the contents from the array you pass into it.
```typescript
/// <reference path='./node_modules/mobservable/mobservable.d.ts'/>
import mobservable = require('mobservable');
```
Note that the `mobservable(value)` shorthand is not available in typescript, due to limitations in the combination of require statements and .d.ts references. use `mobservable.value(value)` instead.
* ~~lazy tests~~
* ~~fix warning in test~~
* ~~fix lazy cycles~~
* ~~error tests~~
* ~~typescript tests~~
* ~~rename defineProperty to defineObservableProperty~~
* ~~introduce 1.5 decorator. w00t! https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#decorators~~
* ~~introduce initializeProperties~~
* ~~implement and test observe() methods~~
* ~~toJSON~~
* ~~layout elses, rename properties.js -> observables.js~~
* ~~tabs to spaces everywhere~~
* ~~remove memwatch, make tests smaller?~~
* ~~drop event emitter, to make lib smaller and stand alone? https://github.com/joyent/node/blob/master/lib/events.js, note: clone listeners before invoking, note: document~~
* ~~array.observe conform~~
* ~~badges for build, coverage, npm~~
* ~~process remaining optimizations / todo's, document code~~
* coverage tests
* minified version
* ~~use typescript 1.5 spread operator~~
* ~~use console.trace() in logging where applicable~~
* add 'name' as parameter to observable.value, automatically set it when defining properties, use it in warnings / toString
* IReactiveValue interface
* IObservable interface
* browser support test https://ci.testling.com/
* ~~use destructurings (for example quickdiff)~~
* examples
* introduce extend / properties / defineProperties
* describe properties in readme:

@@ -39,34 +11,63 @@ - synchronous updates

* optimizations
- ~~pass compute function into DNode in computed observable~~
- computable's without observers should stay 'awake' after a computation (e.g. being inspected by .get),
but then go to sleep if one of its dependencies has changed, so that subsequent reads on a computable are cheap even if there are no observers
- ~~node.addObserver check if new observer doesn't equal the prevous one~~
- check if somewhere an array is filled that could be preallocate
- ~~array: recycle properties (or not)~~
- ~~look into defineProperties / making property creating faster (especially arrays)~~
- ~~count stale dependencies, instead of looping each time whether all dependencies are ready again.~~
- collapse stale / ready notifications whenever possible
- ~~find unmodifyable empty lists / objects and put in var~~
- ~~heuristic to make computables non-lazy if used frequently (something like, in computable, if (this.lazyReads > this.computesWithoutObservers) then never-go-to-sleep)~~
- combine multiple adds to array during batch into single splice
- verify that combine multiple assignments to property during batch are combined into singleupdate
- verify that multiple computations schedulings during batch result in single computation
* ~~make sure array properties are read only~~
- go to sleep if there are no observers and a 'stale' notification comes in
- notifyStateChange: avoid or remove scheduler.schedule, or strip closure by passing the DNode in
- do not wrap around .value in .props, to use save some closures
- create dnode.observers / observing lazy, to save memory?
0.4
* License
* implement array.sort & reverse properly, they do change the array
* drop mobservable.onReady / onceReady?
* clean up / clarify properties / annotations code
* drop initializeObservableProperty/ies
* introduce .props(target, prop, value), .props(props), .props(target, ...props)
* use disposable like RxJs?
* ~~License~~
* ~~implement array.sort & reverse properly, they do change the array~~
* ~~drop mobservable.onReady / onceReady?~~
* ~~clean up / clarify properties / annotations code~~
* ~~drop initializeObservableProperties -> turnObservablesIntoProperties ~~
* ~~introduce .props(target, prop, value), .props(props), .props(target, ...props)~~
* ~~use disposable like RxJs? -> NO ~~
* ~~props should create real observable array~~
* ~~value for array, + set -> replace~~
* ~~check nested watches! inner watch should not reevaluate outer watch, introduce DNode.unobserved that swaps out trackingstack?~~
* ~~rename .prop. -> .impl.~~
* ~~replace instanceof observableValue checks with isObservable, isWrappedObservable~~
* ~~introduce createGetterSetter on all implementations~~
* ~~make properties non-configurable~~
* ~~helpers .variable, computed, array~~
* ~~introduce IGetter / ISetter interfaces, or create getter setter for array~~
* ~~perf tests~~
* ~~test .value etc~~
* ~~removed .bind, it is slow!~~
* ~~watchClass~~
* ~~move out scheduler check stuff..~~
* ~~check batching~~
* ~~check closure usages for scheduler.schedule~~
* ~~no source map for dist!~~
* fiddle demo
* update apidocs
0.5
* ~~browser based tests~~
* ~~fix instanceof array~~
* ~~implement + test toPlainValue~~
* ~~make browser / amd / umd build https://github.com/bebraw/grunt-umd https://github.com/umdjs/umd~~
* ~~minify~~
* react mixin, https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750
* react fiddle demo
* mobservable.struct (ALWAYS compare deep equal, use defensive copy so that changes in object are detected)
* mobservable.computedStruct
Later
* coverage tests
* minified version
* add 'tap' function (that doesn't register as new observer)
* add 'name' as parameter to observable.value, automatically set it when defining properties, use it in warnings / toString
* browser support test https://ci.testling.com/
* Introduce mobservable.object(data) that creates observable properties and an observe method.
* react components
* should arrays trigger a notify observed for methods like reverse, sort, values, json etc? -> they change the internal structure, so that seems to be weird
0.5
* nested watcher test
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc