mobservable
Advanced tools
Comparing version 0.2.1 to 0.2.2
@@ -0,1 +1,2 @@ | ||
/// <reference path="./typings/node-0.10.d.ts" /> | ||
var __extends = this.__extends || function (d, b) { | ||
@@ -24,5 +25,3 @@ for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; | ||
propFunc.prop = prop; | ||
propFunc.toString = function () { | ||
return prop.toString(); | ||
}; | ||
propFunc.toString = function () { return prop.toString(); }; | ||
return propFunc; | ||
@@ -51,2 +50,21 @@ } | ||
}; | ||
mobservableStatic.observeProperty = function observeProperty(object, key, listener) { | ||
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, true); | ||
else if (currentValue.prop && currentValue.prop instanceof ObservableValue) | ||
return currentValue.prop.observe(listener, true); | ||
var observer = new ComputedObservable(function () { return object[key]; }, object); | ||
var disposer = observer.observe(listener, true); | ||
if (!observer.dependencyState.observing.length) | ||
warn("mobservable.observeProperty: property '" + key + "' of '" + object + " doesn't seem to be observable. Did you define it as observable?"); | ||
return function () { | ||
disposer(); | ||
observer.dependencyState.dispose(); | ||
}; | ||
}; | ||
mobservableStatic.array = function array(values) { | ||
@@ -64,2 +82,38 @@ return new ObservableArray(values); | ||
}; | ||
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 () { | ||
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, null); | ||
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(object, name, initialValue) { | ||
@@ -158,2 +212,3 @@ var _property = mobservableStatic.value(initialValue, object); | ||
ComputedObservable.prototype.set = function (_) { | ||
// TODO: generic setter exception | ||
throw new Error("ComputedObservable cannot retrieve a new value!"); | ||
@@ -323,3 +378,3 @@ }; | ||
ObservableArray.prototype.pop = function () { | ||
return this.splice(this._values.length, 1)[0]; | ||
return this.splice(this._values.length - 1, 1)[0]; | ||
}; | ||
@@ -337,8 +392,4 @@ ObservableArray.prototype.shift = function () { | ||
}; | ||
ObservableArray.prototype.toString = function () { | ||
return this.wrapReadFunction("toString", arguments); | ||
}; | ||
ObservableArray.prototype.toLocaleString = function () { | ||
return this.wrapReadFunction("toLocaleString", arguments); | ||
}; | ||
ObservableArray.prototype.toString = function () { return this.wrapReadFunction("toString", arguments); }; | ||
ObservableArray.prototype.toLocaleString = function () { return this.wrapReadFunction("toLocaleString", arguments); }; | ||
ObservableArray.prototype.concat = function () { | ||
@@ -351,41 +402,15 @@ var items = []; | ||
}; | ||
ObservableArray.prototype.join = function (separator) { | ||
return this.wrapReadFunction("join", arguments); | ||
}; | ||
ObservableArray.prototype.reverse = function () { | ||
return this.wrapReadFunction("reverse", arguments); | ||
}; | ||
ObservableArray.prototype.slice = function (start, end) { | ||
return this.wrapReadFunction("slice", arguments); | ||
}; | ||
ObservableArray.prototype.sort = function (compareFn) { | ||
return this.wrapReadFunction("sort", 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.join = function (separator) { return this.wrapReadFunction("join", arguments); }; | ||
ObservableArray.prototype.reverse = function () { return this.wrapReadFunction("reverse", arguments); }; | ||
ObservableArray.prototype.slice = function (start, end) { return this.wrapReadFunction("slice", arguments); }; | ||
ObservableArray.prototype.sort = function (compareFn) { return this.wrapReadFunction("sort", 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, args) { | ||
@@ -411,3 +436,3 @@ var baseFunc = Array.prototype[funcName]; | ||
this.isComputed = isComputed; | ||
this.state = 2 /* READY */; | ||
this.state = DNodeState.READY; | ||
this.isSleeping = true; | ||
@@ -455,13 +480,13 @@ this.hasCycle = false; | ||
DNode.prototype.markStale = function () { | ||
if (this.state === 1 /* PENDING */) | ||
if (this.state === DNodeState.PENDING) | ||
return; | ||
if (this.state === 0 /* STALE */) | ||
if (this.state === DNodeState.STALE) | ||
return; | ||
this.state = 0 /* STALE */; | ||
this.state = DNodeState.STALE; | ||
this.notifyObservers(); | ||
}; | ||
DNode.prototype.markReady = function (didTheValueActuallyChange) { | ||
if (this.state === 2 /* READY */) | ||
if (this.state === DNodeState.READY) | ||
return; | ||
this.state = 2 /* READY */; | ||
this.state = DNodeState.READY; | ||
this.notifyObservers(didTheValueActuallyChange); | ||
@@ -479,3 +504,3 @@ Scheduler.scheduleReady(); | ||
for (var i = 0; i < l; i++) | ||
if (obs[i].state !== 2 /* READY */) | ||
if (obs[i].state !== DNodeState.READY) | ||
return false; | ||
@@ -495,3 +520,3 @@ return true; | ||
this.isSleeping = false; | ||
this.state = 1 /* PENDING */; | ||
this.state = DNodeState.PENDING; | ||
this.computeNextValue(); | ||
@@ -503,7 +528,7 @@ } | ||
switch (this.state) { | ||
case 0 /* STALE */: | ||
if (observable.state === 2 /* READY */ && didTheValueActuallyChange) | ||
case DNodeState.STALE: | ||
if (observable.state === DNodeState.READY && didTheValueActuallyChange) | ||
this.dependencyChangeCount += 1; | ||
if (observable.state === 2 /* READY */ && this.areAllDependenciesAreStable()) { | ||
this.state = 1 /* PENDING */; | ||
if (observable.state === DNodeState.READY && this.areAllDependenciesAreStable()) { | ||
this.state = DNodeState.PENDING; | ||
Scheduler.schedule(function () { | ||
@@ -518,6 +543,6 @@ if (_this.dependencyChangeCount > 0) | ||
break; | ||
case 1 /* PENDING */: | ||
case DNodeState.PENDING: | ||
break; | ||
case 2 /* READY */: | ||
if (observable.state === 0 /* STALE */) | ||
case DNodeState.READY: | ||
if (observable.state === DNodeState.STALE) | ||
this.markStale(); | ||
@@ -524,0 +549,0 @@ break; |
@@ -15,5 +15,11 @@ module.exports = function(grunt) { | ||
src: ["mobservable.ts"] | ||
}, | ||
buildtypescripttest: { | ||
options: { | ||
compiler: './node_modules/typescript/bin/tsc' | ||
}, | ||
src: ["test/typescript-test.ts"] | ||
} | ||
}, | ||
nodeunit: { | ||
nodeunit: { | ||
options: { | ||
@@ -29,3 +35,3 @@ reporter: 'default' | ||
grunt.loadNpmTasks('grunt-contrib-nodeunit'); | ||
grunt.registerTask("publish", "Publish to npm", function() { | ||
@@ -36,5 +42,5 @@ require("./publish.js"); | ||
grunt.registerTask("build", ["ts:builddist"]); | ||
grunt.registerTask("test", ["ts:buildlocal", "nodeunit:all"]); | ||
grunt.registerTask("test", ["ts:buildlocal","ts:buildtypescripttest", "nodeunit:all"]); | ||
grunt.registerTask("perf", ["ts:buildlocal", "nodeunit:perf"]); | ||
} | ||
}; |
@@ -8,8 +8,13 @@ declare module "mobservable" { | ||
(value: T): S; | ||
subscribe(callback: (newValue: T, oldValue: T) => void): Lambda; | ||
observe(callback: (newValue: T, oldValue: T) => void, fireImmediately:boolean): Lambda; | ||
} | ||
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 array<T>(values?:T[]): IObservableArray<T>; | ||
export function observeProperty(object:Object, key:string, listener:Function):Lambda; | ||
// annotation | ||
export function observable(target:Object, key:string); | ||
export function batch(action:Lambda); | ||
@@ -26,3 +31,3 @@ export function onReady(listener:Lambda):Lambda; | ||
spliceWithArray(index:number, deleteCount?:number, newItems?:T[]):T[]; | ||
observe(listener:()=>void, fireImmediately:boolean):Lambda; | ||
observe(listener:()=>void, fireImmediately?:boolean):Lambda; | ||
clear(): T[]; | ||
@@ -29,0 +34,0 @@ replace(newItems:T[]); |
@@ -18,3 +18,3 @@ /// <reference path="./typings/node-0.10.d.ts" /> | ||
(value:T):S; | ||
observe(callback:(newValue:T, oldValue:T)=>void):Lambda; | ||
observe(callback:(newValue:T, oldValue:T)=>void, fireImmediately?:boolean):Lambda; | ||
} | ||
@@ -25,5 +25,10 @@ | ||
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]; | ||
array<T>(values?:T[]): ObservableArray<T>; | ||
observeProperty(object:Object, key:string, listener:Function):Lambda; | ||
observable(target:Object, key:string); // annotation | ||
batch(action:Lambda); | ||
@@ -84,2 +89,28 @@ onReady(listener:Lambda):Lambda; | ||
mobservableStatic.observeProperty = function observeProperty(object:Object, key:string, listener:(...args:any[])=>void):Lambda { | ||
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]; | ||
// ObservableValue, ComputedObservable or ObservableArray? | ||
if (currentValue instanceof ObservableValue || currentValue instanceof ObservableArray) | ||
return currentValue.observe(listener, true); | ||
// IObservable? | ||
else if (currentValue.prop && currentValue.prop instanceof ObservableValue) | ||
return currentValue.prop.observe(listener, true); | ||
var observer = new ComputedObservable(() => object[key], object); | ||
var disposer = observer.observe(listener, true); | ||
if (!(<any>(<any>observer).dependencyState).observing.length) | ||
warn(`mobservable.observeProperty: property '${key}' of '${object} doesn't seem to be observable. Did you define it as observable?`); | ||
return () => { | ||
disposer(); | ||
(<any>observer).dependencyState.dispose(); // clean up | ||
}; | ||
} | ||
mobservableStatic.array = function array<T>(values?:T[]): ObservableArray<T> { | ||
@@ -101,2 +132,44 @@ return new ObservableArray(values); | ||
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 () { | ||
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, null); | ||
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) { | ||
@@ -127,2 +200,3 @@ var _property = mobservableStatic.value(initialValue, object); | ||
} | ||
class ObservableValue<T,S> { | ||
@@ -200,3 +274,3 @@ protected events = new events.EventEmitter(); | ||
} else if (this.dependencyState.isSleeping) { | ||
this.compute(); // <- doesn't detect cycles! | ||
this.compute(); | ||
} else { | ||
@@ -214,2 +288,4 @@ // we are already up to date, somebody is just inspecting our current value | ||
set(_:U):S { | ||
// TODO: generic setter exception | ||
throw new Error("ComputedObservable cannot retrieve a new value!"); | ||
@@ -384,3 +460,3 @@ } | ||
if (fireImmediately) | ||
listener(); | ||
listener(); // TODO: pass in splice data | ||
@@ -425,3 +501,3 @@ this.events.addListener('change', listener); | ||
pop(): T { | ||
return this.splice(this._values.length, 1)[0]; | ||
return this.splice(this._values.length - 1, 1)[0]; | ||
} | ||
@@ -713,3 +789,3 @@ shift(): T { | ||
this.isDisposed = true; | ||
// Do something with the observers, notify some state like KILLED? TODO: => set 'undefined' | ||
// TODO: if there are observers, throw warning! | ||
} | ||
@@ -716,0 +792,0 @@ } |
{ | ||
"name": "mobservable", | ||
"version": "0.2.1", | ||
"version": "0.2.2", | ||
"description": "Changes are coming! Small library for creating observable properties en functions", | ||
@@ -26,4 +26,4 @@ "main": "dist/mobservable.js", | ||
"nscript": "^0.1.5", | ||
"typescript": "^1.4.1" | ||
"typescript": "^1.5.0-alpha" | ||
} | ||
} |
@@ -90,4 +90,19 @@ # MOBservable | ||
TODO | ||
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. | ||
```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); | ||
} | ||
} | ||
``` | ||
## mobservable.defineObservableProperty(object, name, value) | ||
@@ -162,2 +177,15 @@ | ||
## mobservable.observeProperty | ||
`function observeProperty(object:Object, key:string, listener:Function):Lambda` | ||
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(); | ||
mobservable.observeProperty(order, 'total', (newPrice) => console.log("New price: " + newPrice)); | ||
## mobservable.watch(func, onInvalidate) | ||
@@ -164,0 +192,0 @@ |
@@ -6,5 +6,5 @@ | ||
* ~~error tests~~ | ||
* typescript tets | ||
* ~~typescript tests~~ | ||
* ~~rename defineProperty to defineObservableProperty~~ | ||
* introduce 1.5 decorator. w00t! https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#decorators | ||
* ~~introduce 1.5 decorator. w00t! https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#decorators~~ | ||
* ~~introduce initializeProperties~~ | ||
@@ -34,2 +34,2 @@ * implement and test observe() methods | ||
- collapse stale / ready notifications whenever possible | ||
- heuristic to make computables non-lazy if used frequently | ||
- heuristic to make computables non-lazy if used frequently (something like, in computable, if (this.lazyReads > this.computesWithoutObservers) then never-go-to-sleep) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
133450
2833
259