mobservable
Advanced tools
Comparing version 1.0.0-alpha.4 to 1.0.0-alpha.5
@@ -0,2 +1,6 @@ | ||
# 1.0 | ||
* Introduces a second `strictMode` parameter to transaction. | ||
* dropped the default export of observable | ||
* Fixes issue where non-`strict` mode and `logLevel` where not always honored. | ||
@@ -3,0 +7,0 @@ # 0.7.0 |
@@ -66,4 +66,2 @@ (function webpackUniversalModuleDefinition(root, factory) { | ||
__export(__webpack_require__(11)); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.default = core.observable; | ||
var core_1 = __webpack_require__(1); | ||
@@ -76,20 +74,24 @@ exports.isObservable = core_1.isObservable; | ||
exports.asStructure = core_1.asStructure; | ||
exports.observe = core_1.observe; | ||
exports.observeUntil = core_1.observeUntil; | ||
exports.observeAsync = core_1.observeAsync; | ||
exports.autorun = core_1.autorun; | ||
exports.autorunUntil = core_1.autorunUntil; | ||
exports.autorunAsync = core_1.autorunAsync; | ||
exports.expr = core_1.expr; | ||
exports.transaction = core_1.transaction; | ||
exports.toJSON = core_1.toJSON; | ||
exports.logLevel = core_1.logLevel; | ||
exports.strict = core_1.strict; | ||
exports.isReactive = core_1.isObservable; | ||
exports.makeReactive = core_1.observable; | ||
exports.extendReactive = core_1.extendObservable; | ||
exports.observe = core_1.autorun; | ||
exports.observeUntil = core_1.autorunUntil; | ||
exports.observeAsync = core_1.autorunAsync; | ||
Object.defineProperties(module.exports, { | ||
strict: { | ||
enumerable: true, | ||
get: function () { return core.strict; }, | ||
set: function (v) { return core.strict = v; } | ||
get: core.getStrict, | ||
set: core.setStrict | ||
}, | ||
logLevel: { | ||
enumerable: true, | ||
get: function () { return core.logLevel; }, | ||
set: function (v) { return core.logLevel = v; } | ||
get: core.getLogLevel, | ||
set: core.setLogLevel | ||
} | ||
@@ -108,4 +110,4 @@ }); | ||
}; | ||
//# sourceMappingURL=index.js.map | ||
/***/ }, | ||
@@ -172,3 +174,3 @@ /* 1 */ | ||
exports.isObservable = isObservable; | ||
function observe(view, scope) { | ||
function autorun(view, scope) { | ||
var _a = getValueModeFromValue(view, ValueMode.Recursive), mode = _a[0], unwrappedView = _a[1]; | ||
@@ -183,3 +185,3 @@ var observable = new observableview_1.ObservableView(unwrappedView, scope, { | ||
}); | ||
if (exports.logLevel >= 2 && observable.observing.length === 0) | ||
if (logLevel >= 2 && observable.observing.length === 0) | ||
console.warn("[mobservable.observe] not a single observable was used inside the observing function. This observer is now a no-op."); | ||
@@ -189,5 +191,5 @@ disposer.$mobservable = observable; | ||
} | ||
exports.observe = observe; | ||
function observeUntil(predicate, effect, scope) { | ||
var disposer = observe(function () { | ||
exports.autorun = autorun; | ||
function autorunUntil(predicate, effect, scope) { | ||
var disposer = autorun(function () { | ||
if (predicate.call(scope)) { | ||
@@ -200,8 +202,8 @@ disposer(); | ||
} | ||
exports.observeUntil = observeUntil; | ||
function observeAsync(view, effect, delay, scope) { | ||
exports.autorunUntil = autorunUntil; | ||
function autorunAsync(view, effect, delay, scope) { | ||
if (delay === void 0) { delay = 1; } | ||
var latestValue = undefined; | ||
var timeoutHandle; | ||
var disposer = observe(function () { | ||
var disposer = autorun(function () { | ||
latestValue = view.call(scope); | ||
@@ -221,3 +223,3 @@ if (!timeoutHandle) { | ||
} | ||
exports.observeAsync = observeAsync; | ||
exports.autorunAsync = autorunAsync; | ||
function expr(expr, scope) { | ||
@@ -234,33 +236,51 @@ if (!dnode_1.isComputingView()) | ||
function observableDecorator(target, key, baseDescriptor) { | ||
var isDecoratingProperty = baseDescriptor && !baseDescriptor.hasOwnProperty("value"); | ||
var descriptor = baseDescriptor || {}; | ||
var baseValue = isDecoratingProperty ? descriptor.get : descriptor.value; | ||
// - In typescript, 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) | ||
// - In typescript, the baseDescriptor is empty for attributes without initial value | ||
// - In babel, the initial value is passed as the closure baseDiscriptor.initializer' | ||
var isDecoratingGetter = baseDescriptor && baseDescriptor.hasOwnProperty("get"); | ||
var descriptor = {}; | ||
var baseValue = undefined; | ||
if (baseDescriptor) { | ||
if (baseDescriptor.hasOwnProperty('get')) | ||
baseValue = baseDescriptor.get; | ||
else if (baseDescriptor.hasOwnProperty('value')) | ||
baseValue = baseDescriptor.value; | ||
else if (baseDescriptor.initializer) { | ||
baseValue = baseDescriptor.initializer(); | ||
if (typeof baseValue === "function") | ||
baseValue = asReference(baseValue); | ||
} | ||
} | ||
if (!target || typeof target !== "object") | ||
throw new Error("The @observable decorator can only be used on objects"); | ||
if (!isDecoratingProperty && typeof baseValue === "function") | ||
throw new Error("@observable functions are not supported. Use @observable on a getter function if you want to create a view, or wrap the value in 'asReference' if you want to store a value (found on member '" + key + "')."); | ||
if (isDecoratingProperty) { | ||
if (isDecoratingGetter) { | ||
if (typeof baseValue !== "function") | ||
throw new Error("@observable expects a getter function if used on a property (found on member '" + key + "')."); | ||
throw new Error("@observable expects a getter function if used on a property (in member: '" + key + "')."); | ||
if (descriptor.set) | ||
throw new Error("@observable properties cannot have a setter (found on member '" + key + "')."); | ||
throw new Error("@observable properties cannot have a setter (in member: '" + key + "')."); | ||
if (baseValue.length !== 0) | ||
throw new Error("@observable getter functions should not take arguments (found on member '" + key + "')."); | ||
throw new Error("@observable getter functions should not take arguments (in member: '" + key + "')."); | ||
} | ||
descriptor.configurable = true; | ||
descriptor.enumerable = true; | ||
delete descriptor.value; | ||
delete descriptor.writable; | ||
descriptor.get = function () { | ||
var baseStrict = strict; | ||
strict = false; | ||
observableobject_1.ObservableObject.asReactive(this, null, ValueMode.Recursive).set(key, baseValue); | ||
strict = baseStrict; | ||
return this[key]; | ||
}; | ||
descriptor.set = isDecoratingProperty | ||
? observableview_1.throwingViewSetter | ||
descriptor.set = isDecoratingGetter | ||
? observableview_1.throwingViewSetter(key) | ||
: function (value) { | ||
observableobject_1.ObservableObject.asReactive(this, null, ValueMode.Recursive).set(key, value); | ||
observableobject_1.ObservableObject.asReactive(this, null, ValueMode.Recursive).set(key, typeof value === "function" ? asReference(value) : value); | ||
}; | ||
if (!isDecoratingProperty) { | ||
if (!baseDescriptor) { | ||
Object.defineProperty(target, key, descriptor); | ||
} | ||
else { | ||
return descriptor; | ||
} | ||
} | ||
@@ -282,11 +302,27 @@ function toJSON(source) { | ||
exports.toJSON = toJSON; | ||
function transaction(action) { | ||
return scheduler_1.transaction(action); | ||
function transaction(action, strict) { | ||
return scheduler_1.transaction(action, strict); | ||
} | ||
exports.transaction = transaction; | ||
exports.logLevel = 1; | ||
exports.strict = true; | ||
var logLevel = 1; | ||
function getLogLevel() { | ||
return logLevel; | ||
} | ||
exports.getLogLevel = getLogLevel; | ||
function setLogLevel(newLogLevel) { | ||
logLevel = newLogLevel; | ||
} | ||
exports.setLogLevel = setLogLevel; | ||
var strict = true; | ||
function getStrict() { | ||
return strict; | ||
} | ||
exports.getStrict = getStrict; | ||
function setStrict(newStrict) { | ||
strict = newStrict; | ||
} | ||
exports.setStrict = setStrict; | ||
setTimeout(function () { | ||
if (exports.logLevel > 0) | ||
console.info("Welcome to mobservable. Current logLevel = " + exports.logLevel + ". Change mobservable.logLevel according to your needs: 0 = production, 1 = development, 2 = debugging. Strict mode is " + (exports.strict ? 'enabled' : 'disabled') + "."); | ||
if (logLevel > 0) | ||
console.info("Welcome to mobservable. Current logLevel = " + logLevel + ". Change mobservable.logLevel according to your needs: 0 = production, 1 = development, 2 = debugging. Strict mode is " + (strict ? 'enabled' : 'disabled') + "."); | ||
}, 1); | ||
@@ -411,2 +447,4 @@ (function (ValueType) { | ||
function assertUnwrapped(value, message) { | ||
if (logLevel === 0) | ||
return; | ||
if (value instanceof AsReference || value instanceof AsStructure || value instanceof AsFlat) | ||
@@ -416,4 +454,4 @@ throw new Error("[mobservable] asStructure / asReference / asFlat cannot be used here. " + message); | ||
exports.assertUnwrapped = assertUnwrapped; | ||
//# sourceMappingURL=core.js.map | ||
/***/ }, | ||
@@ -439,3 +477,3 @@ /* 2 */ | ||
function checkIfStateIsBeingModifiedDuringView(context) { | ||
if (isComputingView() && core_1.strict === true) { | ||
if (core_1.getLogLevel() > 0 && isComputingView() && core_1.getStrict() === true) { | ||
var ts = __mobservableViewStack; | ||
@@ -580,5 +618,14 @@ throw new Error("[mobservable] It is not allowed to change the state during the computation of a reactive view if 'mobservable.strict' mode is enabled:\nShould the data you are trying to modify actually be a view?\nView name: " + context.name + ".\nCurrent stack size is " + ts.length + ", active view: \"" + ts[ts.length - 1].toString() + "\"."); | ||
extras_1.reportTransition(this, "PENDING"); | ||
var stateDidChange = this.compute(); | ||
this.bindDependencies(); | ||
this.markReady(stateDidChange); | ||
var hasError = true; | ||
try { | ||
var stateDidChange = this.compute(); | ||
hasError = false; | ||
} | ||
finally { | ||
if (hasError) | ||
console.error("[mobservable.view '" + this.context.name + "'] There was an uncaught error during the computation of " + this.toString()); | ||
this.isComputing = false; | ||
this.bindDependencies(); | ||
this.markReady(stateDidChange); | ||
} | ||
}; | ||
@@ -595,3 +642,3 @@ ViewNode.prototype.compute = function () { | ||
__mobservableViewStack.length -= 1; | ||
if (this.observing.length === 0 && core_1.logLevel > 1 && !this.isDisposed) { | ||
if (this.observing.length === 0 && core_1.getLogLevel() > 1 && !this.isDisposed) { | ||
console.error("[mobservable] You have created a view function that doesn't observe any values, did you forget to make its dependencies observable?"); | ||
@@ -604,3 +651,3 @@ } | ||
var dependency = added[i]; | ||
if (dependency instanceof ViewNode && dependency.findCycle(this)) { | ||
if (core_1.getLogLevel() > 0 && dependency instanceof ViewNode && dependency.findCycle(this)) { | ||
this.hasCycle = true; | ||
@@ -648,4 +695,4 @@ this.observing.splice(this.observing.indexOf(added[i]), 1); | ||
var scheduler_1 = __webpack_require__(10); | ||
//# sourceMappingURL=dnode.js.map | ||
/***/ }, | ||
@@ -756,4 +803,4 @@ /* 3 */ | ||
exports.trackTransitions = trackTransitions; | ||
//# sourceMappingURL=extras.js.map | ||
/***/ }, | ||
@@ -818,4 +865,4 @@ /* 4 */ | ||
exports.ObservableObject = ObservableObject; | ||
//# sourceMappingURL=observableobject.js.map | ||
/***/ }, | ||
@@ -837,6 +884,7 @@ /* 5 */ | ||
var simpleeventemitter_1 = __webpack_require__(6); | ||
var core_1 = __webpack_require__(1); | ||
var utils_1 = __webpack_require__(7); | ||
function throwingViewSetter() { | ||
throw new Error("[mobservable.view '" + this.context.name + "'] View functions do not accept new values"); | ||
function throwingViewSetter(name) { | ||
return function () { | ||
throw new Error("[mobservable.view '" + name + "'] View functions do not accept new values"); | ||
}; | ||
} | ||
@@ -852,3 +900,2 @@ exports.throwingViewSetter = throwingViewSetter; | ||
this.isComputing = false; | ||
this.hasError = false; | ||
this.changeEvent = new simpleeventemitter_1.default(); | ||
@@ -874,32 +921,12 @@ } | ||
throw new Error("[mobservable.view '" + this.context.name + "'] Cycle detected"); | ||
if (this.hasError) { | ||
if (core_1.logLevel > 0) | ||
console.error("[mobservable.view '" + this.context.name + "'] Rethrowing caught exception to observer: " + this._value + (this._value.cause || '')); | ||
throw this._value; | ||
} | ||
return this._value; | ||
}; | ||
ObservableView.prototype.set = function () { | ||
throwingViewSetter.call(this); | ||
throwingViewSetter(this.context.name)(); | ||
}; | ||
ObservableView.prototype.compute = function () { | ||
var newValue; | ||
try { | ||
if (this.isComputing) | ||
throw new Error("[mobservable.view '" + this.context.name + "'] Cycle detected"); | ||
this.isComputing = true; | ||
newValue = this.func.call(this.scope); | ||
this.hasError = false; | ||
} | ||
catch (e) { | ||
this.hasError = true; | ||
console.error("[mobservable.view '" + this.context.name + "'] Caught error during computation: ", e, "View function:", this.func.toString()); | ||
console.trace(); | ||
if (e instanceof Error) | ||
newValue = e; | ||
else { | ||
newValue = new Error(("[mobservable.view '" + this.context.name + "'] Error during computation (see error.cause) in ") + this.func.toString()); | ||
newValue.cause = e; | ||
} | ||
} | ||
if (this.isComputing) | ||
throw new Error("[mobservable.view '" + this.context.name + "'] Cycle detected"); | ||
this.isComputing = true; | ||
var newValue = this.func.call(this.scope); | ||
this.isComputing = false; | ||
@@ -933,3 +960,3 @@ var changed = this.compareStructural ? !utils_1.deepEquals(newValue, this._value) : newValue !== this._value; | ||
get: function () { return _this.get(); }, | ||
set: throwingViewSetter | ||
set: throwingViewSetter(this.context.name) | ||
}; | ||
@@ -943,4 +970,4 @@ }; | ||
exports.ObservableView = ObservableView; | ||
//# sourceMappingURL=observableview.js.map | ||
/***/ }, | ||
@@ -993,4 +1020,4 @@ /* 6 */ | ||
exports.default = SimpleEventEmitter; | ||
//# sourceMappingURL=simpleeventemitter.js.map | ||
/***/ }, | ||
@@ -1107,4 +1134,4 @@ /* 7 */ | ||
exports.quickDiff = quickDiff; | ||
//# sourceMappingURL=utils.js.map | ||
/***/ }, | ||
@@ -1181,6 +1208,9 @@ /* 8 */ | ||
ObservableArray.prototype.updateLength = function (oldLength, delta) { | ||
if (delta < 0) | ||
if (delta < 0) { | ||
dnode_1.checkIfStateIsBeingModifiedDuringView(this.$mobservable.context); | ||
for (var i = oldLength + delta; i < oldLength; i++) | ||
delete this[i]; | ||
} | ||
else if (delta > 0) { | ||
dnode_1.checkIfStateIsBeingModifiedDuringView(this.$mobservable.context); | ||
if (oldLength + delta > OBSERVABLE_ARRAY_BUFFER_SIZE) | ||
@@ -1214,4 +1244,4 @@ reserveArrayBuffer(oldLength + delta); | ||
var lengthDelta = newItems.length - deleteCount; | ||
this.updateLength(length, lengthDelta); | ||
var res = (_a = this.$mobservable.values).splice.apply(_a, [index, deleteCount].concat(newItems)); | ||
this.updateLength(length, lengthDelta); | ||
this.notifySplice(index, res, newItems); | ||
@@ -1239,3 +1269,2 @@ return res; | ||
ObservableArray.prototype.notifyChanged = function () { | ||
dnode_1.checkIfStateIsBeingModifiedDuringView(this.$mobservable.context); | ||
this.$mobservable.markStale(); | ||
@@ -1364,8 +1393,9 @@ this.$mobservable.markReady(true); | ||
function createArrayBufferItem(index) { | ||
var prop = { | ||
enumerable: false, | ||
configurable: false, | ||
var prop = ENUMERABLE_PROPS[index] = { | ||
enumerable: true, | ||
configurable: true, | ||
set: function (value) { | ||
core_1.assertUnwrapped(value, "Modifiers cannot be used on array values. For non-reactive array values use makeReactive(asFlat(array))."); | ||
if (index < this.$mobservable.values.length) { | ||
dnode_1.checkIfStateIsBeingModifiedDuringView(this.$mobservable.context); | ||
var oldValue = this.$mobservable.values[index]; | ||
@@ -1381,6 +1411,6 @@ var changed = this.$mobservable.mode === core_1.ValueMode.Structure ? !utils_1.deepEquals(oldValue, value) : oldValue !== value; | ||
else | ||
throw new Error("[mobservable.array] Index out of bounds, " + index + " is larger than " + this.values.length); | ||
throw new Error("[mobservable.array] Index out of bounds, " + index + " is larger than " + this.$mobservable.values.length); | ||
}, | ||
get: function () { | ||
if (index < this.$mobservable.values.length) { | ||
if (this.$mobservable && index < this.$mobservable.values.length) { | ||
this.$mobservable.notifyObserved(); | ||
@@ -1392,6 +1422,8 @@ return this.$mobservable.values[index]; | ||
}; | ||
Object.defineProperty(ObservableArray.prototype, "" + index, prop); | ||
prop.enumerable = true; | ||
prop.configurable = true; | ||
ENUMERABLE_PROPS[index] = prop; | ||
Object.defineProperty(ObservableArray.prototype, "" + index, { | ||
enumerable: false, | ||
configurable: true, | ||
get: prop.get, | ||
set: prop.set | ||
}); | ||
} | ||
@@ -1404,4 +1436,4 @@ function reserveArrayBuffer(max) { | ||
reserveArrayBuffer(1000); | ||
//# sourceMappingURL=observablearray.js.map | ||
/***/ }, | ||
@@ -1472,8 +1504,9 @@ /* 9 */ | ||
exports.ObservableValue = ObservableValue; | ||
//# sourceMappingURL=observablevalue.js.map | ||
/***/ }, | ||
/* 10 */ | ||
/***/ function(module, exports) { | ||
/***/ function(module, exports, __webpack_require__) { | ||
var core = __webpack_require__(1); | ||
var inBatch = 0; | ||
@@ -1502,4 +1535,7 @@ var tasks = []; | ||
} | ||
function transaction(action) { | ||
function transaction(action, strict) { | ||
var preStrict = core.getStrict(); | ||
inBatch += 1; | ||
if (strict !== undefined) | ||
core.setStrict(strict); | ||
try { | ||
@@ -1514,7 +1550,8 @@ return action(); | ||
} | ||
core.setStrict(preStrict); | ||
} | ||
} | ||
exports.transaction = transaction; | ||
//# sourceMappingURL=scheduler.js.map | ||
/***/ }, | ||
@@ -1524,3 +1561,3 @@ /* 11 */ | ||
//# sourceMappingURL=interfaces.js.map | ||
@@ -1527,0 +1564,0 @@ /***/ } |
@@ -13,11 +13,13 @@ import { Lambda, IObservableArray, IObservableValue, IContextInfoStruct } from './interfaces'; | ||
export declare function isObservable(value: any): boolean; | ||
export declare function observe(view: Lambda, scope?: any): Lambda; | ||
export declare function observeUntil(predicate: () => boolean, effect: Lambda, scope?: any): Lambda; | ||
export declare function observeAsync<T>(view: () => T, effect: (latestValue: T) => void, delay?: number, scope?: any): Lambda; | ||
export declare function autorun(view: Lambda, scope?: any): Lambda; | ||
export declare function autorunUntil(predicate: () => boolean, effect: Lambda, scope?: any): Lambda; | ||
export declare function autorunAsync<T>(view: () => T, effect: (latestValue: T) => void, delay?: number, scope?: any): Lambda; | ||
export declare function expr<T>(expr: () => T, scope?: any): T; | ||
export declare function extendObservable<A extends Object, B extends Object>(target: A, properties: B, context?: IContextInfoStruct): A & B; | ||
export declare function toJSON(source: any): any; | ||
export declare function transaction<T>(action: () => T): T; | ||
export declare var logLevel: number; | ||
export declare var strict: boolean; | ||
export declare function transaction<T>(action: () => T, strict?: boolean): T; | ||
export declare function getLogLevel(): number; | ||
export declare function setLogLevel(newLogLevel: any): void; | ||
export declare function getStrict(): boolean; | ||
export declare function setStrict(newStrict: any): void; | ||
export declare enum ValueType { | ||
@@ -24,0 +26,0 @@ Reference = 0, |
@@ -1,8 +0,7 @@ | ||
import * as core from './core'; | ||
import { IDependencyTree, IObserverTree, ITransitionEvent, Lambda } from './interfaces'; | ||
import SimpleEventEmitter from './simpleeventemitter'; | ||
export * from './interfaces'; | ||
declare var _default: typeof core.observable; | ||
export default _default; | ||
export { isObservable, observable, extendObservable, asReference, asFlat, asStructure, observe, observeUntil, observeAsync, expr, transaction, toJSON, logLevel, strict } from './core'; | ||
export { isObservable, observable, extendObservable, asReference, asFlat, asStructure, autorun, autorunUntil, autorunAsync, expr, transaction, toJSON, isObservable as isReactive, observable as makeReactive, extendObservable as extendReactive, autorun as observe, autorunUntil as observeUntil, autorunAsync as observeAsync } from './core'; | ||
export declare var strict: boolean; | ||
export declare var logLevel: string; | ||
export declare const _: { | ||
@@ -9,0 +8,0 @@ isComputingView: () => boolean; |
import { ViewNode } from './dnode'; | ||
import SimpleEventEmitter from './simpleeventemitter'; | ||
import { IContextInfoStruct, Lambda } from './interfaces'; | ||
export declare function throwingViewSetter(): void; | ||
export declare function throwingViewSetter(name: any): Lambda; | ||
export declare class ObservableView<T> extends ViewNode { | ||
@@ -10,3 +10,2 @@ protected func: () => T; | ||
private isComputing; | ||
private hasError; | ||
protected _value: T; | ||
@@ -13,0 +12,0 @@ protected changeEvent: SimpleEventEmitter; |
import { Lambda } from './interfaces'; | ||
export declare function schedule(func: Lambda): void; | ||
export declare function transaction<T>(action: () => T): T; | ||
export declare function transaction<T>(action: () => T, strict?: boolean): T; |
{ | ||
"name": "mobservable", | ||
"version": "1.0.0-alpha.4", | ||
"description": "Observable data. Observing views.", | ||
"version": "1.0.0-alpha.5", | ||
"description": "Observable data. Reactive functions. No staleness.", | ||
"main": "dist/mobservable.js", | ||
@@ -12,3 +12,5 @@ "typings": "dist/typings/index", | ||
"build": "rm -rf dist && mkdir -p dist/typings && ./node_modules/typescript/bin/tsc && NODE_ENV=production ./node_modules/webpack/bin/webpack.js && cp -r .build/*.d.ts dist/typings", | ||
"buildtest": "cd test && ../node_modules/typescript/bin/tsc && cd ..", | ||
"buildtest": "npm run build-typescript-tests && npm run build-babel-tests", | ||
"build-typescript-tests": "./node_modules/typescript/bin/tsc -m commonjs -t es5 --experimentalDecorators --outDir test test/typescript/typescript-test.ts", | ||
"build-babel-tests": "./node_modules/babel/bin/babel.js --stage 0 test/babel/babel-test.js -o test/babel-test.js", | ||
"perf": "npm run build && ./node_modules/nodeunit/bin/nodeunit test/perf/*.js" | ||
@@ -27,2 +29,3 @@ }, | ||
"devDependencies": { | ||
"babel": "^5.8.23", | ||
"coveralls": "^2.11.4", | ||
@@ -29,0 +32,0 @@ "istanbul": "^0.3.21", |
@@ -5,3 +5,3 @@ # mobservable | ||
##### _Keeps views automatically in sync with state. Unobtrusively._ | ||
##### _Makes data observable. And functions reactive. Whut? Like Excel._ | ||
@@ -13,7 +13,8 @@ [![Build Status](https://travis-ci.org/mweststrate/mobservable.svg?branch=master)](https://travis-ci.org/mweststrate/mobservable) | ||
<br/> | ||
### New to Mobservable? Take the [five minute, interactive introduction](https://mweststrate.github.io/mobservable/getting-started.html) | ||
New to Mobservable? Take the [five minute, interactive introduction](https://mweststrate.github.io/mobservable/getting-started.html) | ||
## Introduction | ||
Mobservable is a library to create reactive state and views. Mobservable updates views automatically when the state changes, and thereby achieves [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control). This has major benefits for the simplicity, maintainability and performance of your code. This is the promise of Mobservable: | ||
Mobservable enables your data structures to become observable. | ||
Next to that it can make your functions reactive, so that they re-evaluate whenever relevant data is altered. This has major benefits for the simplicity, maintainability and performance of your code. This is the promise of Mobservable: | ||
* Write complex applications which unmatched simple code. | ||
@@ -31,5 +32,5 @@ * Enable unobtrusive state management: be free to use mutable objects, cyclic references, classes and real references to store state. | ||
```javascript | ||
var timerData = { | ||
var timerData = mobservable.observable({ | ||
secondsPassed: 0 | ||
}; | ||
}); | ||
@@ -40,7 +41,7 @@ setInterval(function() { | ||
var Timer = React.createClass({ | ||
var Timer = mobservable.observer(React.createClass({ | ||
render: function() { | ||
return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> ) | ||
} | ||
}); | ||
})); | ||
@@ -96,16 +97,2 @@ React.render(<Timer timerData={timerData} />, document.body); | ||
## What others are saying... | ||
> _Elegant! I love it!_ | ||
> ‐ Johan den Haan, CTO of Mendix | ||
> _We ported the book Notes and Kanban examples to Mobservable. Check out [the source](https://github.com/survivejs/mobservable-demo) to see how this worked out. Compared to the original I was definitely positively surprised. Mobservable seems like a good fit for these problems._ | ||
> ‐ Juho Vepsäläinen, author of "SurviveJS - Webpack and React" and jster.net curator | ||
> _Great job with Mobservable! Really gives current conventions and libraries a run for their money._ | ||
> ‐ Daniel Dunderfelt | ||
> _I was reluctant to abandon immutable data and the PureRenderMixin, but I no longer have any reservations. I can't think of any reason not to do things the simple, elegant way you have demonstrated._ | ||
> ‐David Schalk, fpcomplete.com | ||
## Resources | ||
@@ -132,5 +119,7 @@ | ||
Mobservable is inspired by Microsoft Excel and existing TFRP implementations like MeteorJS tracker, knockout and Vue.js. | ||
## Top level api | ||
For the full api, see the [API documentation](https://mweststrate.github.io/mobservable/refguide/api.html). | ||
For the full api, see the [API documentation](https://mweststrate.github.io/mobservable/refguide/observable.html). | ||
This is an overview of most important functions available in the `mobservable` namespace: | ||
@@ -151,20 +140,21 @@ | ||
## Runtime behavior | ||
* Reactive views always update synchronously (unless `transaction is used`) | ||
* Reactive views always update atomically, intermediate values will never be visible. | ||
* Reactive functions evaluate lazily and are not processed if they aren't observed. | ||
* Dependency detection is based on actual values to real-time minify the amount of dependencies. | ||
* Cycles are detected automatically. | ||
* Exceptions during computations are propagated to consumers. | ||
## Roadmap | ||
## What others are saying... | ||
* Write documentation, including how to organize projects | ||
* Write blog about inner workings | ||
* Introduce options for asynchronous views and structurally compare view results | ||
> _Elegant! I love it!_ | ||
> ‐ Johan den Haan, CTO of Mendix | ||
> _We ported the book Notes and Kanban examples to Mobservable. Check out [the source](https://github.com/survivejs/mobservable-demo) to see how this worked out. Compared to the original I was definitely positively surprised. Mobservable seems like a good fit for these problems._ | ||
> ‐ Juho Vepsäläinen, author of "SurviveJS - Webpack and React" and jster.net curator | ||
> _Great job with Mobservable! Really gives current conventions and libraries a run for their money._ | ||
> ‐ Daniel Dunderfelt | ||
> _I was reluctant to abandon immutable data and the PureRenderMixin, but I no longer have any reservations. I can't think of any reason not to do things the simple, elegant way you have demonstrated._ | ||
> ‐David Schalk, fpcomplete.com | ||
## Contributing | ||
* Feel free to send pr requests. | ||
* Use `npm start` to run the basic test suite, `npm test` for the test suite with coverage and `npm run perf` for the performance tests. | ||
* Use `npm start` to run the basic test suite, `npm test` for the test suite with coverage and `npm run perf` for the performance tests. |
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
86769
21
1828
7
154