mobservable
Advanced tools
Comparing version 0.6.0 to 0.6.1
@@ -61,3 +61,3 @@ /** | ||
f.toString = function () { | ||
return self.toString(); | ||
return "" + self.value; | ||
}; | ||
@@ -64,0 +64,0 @@ _.markReactive(f); |
@@ -1,1 +0,1 @@ | ||
var mobservable;!function(a){var b;!function(b){var c=function(){function c(a,c){this.value=a,this.recurse=c,this.changeEvent=new b.SimpleEventEmitter,this.dependencyState=new b.DNode(this),this._value=this.makeReferenceValueReactive(a)}return c.prototype.makeReferenceValueReactive=function(c){return this.recurse&&(Array.isArray(c)||b.isPlainObject(c))?a.makeReactive(c):c},c.prototype.set=function(a){if(a!==this._value){var b=this._value;this.dependencyState.markStale(),this._value=this.makeReferenceValueReactive(a),this.dependencyState.markReady(!0),this.changeEvent.emit(this._value,b)}},c.prototype.get=function(){return this.dependencyState.notifyObserved(),this._value},c.prototype.observe=function(a,c){var d=this;void 0===c&&(c=!1),this.dependencyState.setRefCount(1),c&&a(this.get(),void 0);var e=this.changeEvent.on(a);return b.once(function(){d.dependencyState.setRefCount(-1),e()})},c.prototype.createGetterSetter=function(){var a=this,c=function(b){return arguments.length>0?void a.set(b):a.get()};return c.impl=this,c.observe=function(b,c){return a.observe(b,c)},c.toString=function(){return a.toString()},b.markReactive(c),c},c.prototype.toString=function(){return"Observable["+this._value+"]"},c}();b.ObservableValue=c}(b=a._||(a._={}))}(mobservable||(mobservable={}));var __extends=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},mobservable;!function(a){var b;!function(b){var c=function(c){function d(a,b){if(c.call(this,void 0,!1),this.func=a,this.scope=b,this.isComputing=!1,this.hasError=!1,"function"!=typeof a)throw new Error("ComputedObservable requires a function")}return __extends(d,c),d.prototype.get=function(){if(this.isComputing)throw new Error("Cycle detected");var c=this.dependencyState;if(c.isSleeping?b.DNode.trackingStack.length>0?(c.wakeUp(),c.notifyObserved()):this.compute():c.notifyObserved(),c.hasCycle)throw new Error("Cycle detected");if(this.hasError)throw a.debugLevel&&(console.trace(),b.warn(this+": rethrowing caught exception to observer: "+this._value+(this._value.cause||""))),this._value;return this._value},d.prototype.set=function(a){throw new Error(this.toString()+": A computed observable does not accept new values!")},d.prototype.compute=function(){var a;try{if(this.isComputing)throw new Error("Cycle detected");this.isComputing=!0,a=this.func.call(this.scope),this.hasError=!1}catch(b){this.hasError=!0,console.error(this+"Caught error during computation: ",b),b instanceof Error?a=b:(a=new Error("MobservableComputationError"),a.cause=b)}if(this.isComputing=!1,a!==this._value){var c=this._value;return this._value=a,this.changeEvent.emit(a,c),!0}return!1},d.prototype.toString=function(){return"ComputedObservable["+this.func.toString()+"]"},d}(b.ObservableValue);b.ComputedObservable=c}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(b){function c(){return e.trackingStack.length}!function(a){a[a.STALE=0]="STALE",a[a.PENDING=1]="PENDING",a[a.READY=2]="READY"}(b.DNodeState||(b.DNodeState={}));var d=b.DNodeState,e=function(){function c(a){this.owner=a,this.state=d.READY,this.isSleeping=!0,this.hasCycle=!1,this.observing=[],this.prevObserving=null,this.observers=[],this.dependencyChangeCount=0,this.dependencyStaleCount=0,this.isDisposed=!1,this.externalRefenceCount=0,this.isComputed=void 0!==a.compute}return c.prototype.setRefCount=function(a){var b=this.externalRefenceCount+=a;0===b?this.tryToSleep():b===a&&this.wakeUp()},c.prototype.addObserver=function(a){this.observers[this.observers.length]=a},c.prototype.removeObserver=function(a){var b=this.observers,c=b.indexOf(a);-1!==c&&(b.splice(c,1),0===b.length&&this.tryToSleep())},c.prototype.markStale=function(){this.state===d.READY&&(this.state=d.STALE,this.notifyObservers())},c.prototype.markReady=function(a){this.state!==d.READY&&(this.state=d.READY,this.notifyObservers(a))},c.prototype.notifyObservers=function(a){void 0===a&&(a=!1);for(var b=this.observers.slice(),c=b.length,d=0;c>d;d++)b[d].notifyStateChange(this,a)},c.prototype.tryToSleep=function(){if(!this.isSleeping&&this.isComputed&&0===this.observers.length&&0===this.externalRefenceCount){for(var a=0,b=this.observing.length;b>a;a++)this.observing[a].removeObserver(this);this.observing=[],this.isSleeping=!0}},c.prototype.wakeUp=function(){this.isSleeping&&this.isComputed&&(this.isSleeping=!1,this.state=d.PENDING,this.computeNextState())},c.prototype.notifyStateChange=function(a,c){var e=this;a.state===d.STALE?1===++this.dependencyStaleCount&&this.markStale():(c&&(this.dependencyChangeCount+=1),0===--this.dependencyStaleCount&&(this.state=d.PENDING,b.Scheduler.schedule(function(){e.dependencyChangeCount>0?e.computeNextState():e.markReady(!1),e.dependencyChangeCount=0})))},c.prototype.computeNextState=function(){this.trackDependencies();var a=this.owner.compute();this.bindDependencies(),this.markReady(a)},c.prototype.trackDependencies=function(){this.prevObserving=this.observing,c.trackingStack[c.trackingStack.length]=[]},c.prototype.bindDependencies=function(){this.observing=c.trackingStack.pop(),this.isComputed&&0===this.observing.length&&a.debugLevel>1&&!this.isDisposed&&(console.trace(),b.warn("You have created a function that doesn't observe any values, did you forget to make its dependencies observable?"));var d=b.quickDiff(this.observing,this.prevObserving),e=d[0],f=d[1];this.prevObserving=null;for(var g=0,h=f.length;h>g;g++)f[g].removeObserver(this);this.hasCycle=!1;for(var g=0,h=e.length;h>g;g++)this.isComputed&&e[g].findCycle(this)?(this.hasCycle=!0,this.observing.splice(this.observing.indexOf(e[g]),1),e[g].hasCycle=!0):e[g].addObserver(this)},c.prototype.notifyObserved=function(){var a=c.trackingStack,b=a.length;if(b>0){var d=a[b-1],e=d.length;d[e-1]!==this&&d[e-2]!==this&&(d[e]=this)}},c.prototype.findCycle=function(a){var b=this.observing;if(-1!==b.indexOf(a))return!0;for(var c=b.length,d=0;c>d;d++)if(b[d].findCycle(a))return!0;return!1},c.prototype.dispose=function(){if(this.observers.length)throw new Error("Cannot dispose DNode; it is still being observed");if(this.observing)for(var a=this.observing.length,b=0;a>b;b++)this.observing[b].removeObserver(this);this.observing=null,this.isDisposed=!0},c.trackingStack=[],c}();b.DNode=e,b.stackDepth=c}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){function b(a,b){if(e(a))return a;b=b||{},a instanceof m.AsReference&&(a=a.value,b.as="reference");var d=b.recurse!==!1,f="reference"===b.as?l.Reference:c(a);switch(f){case l.Reference:case l.ComplexObject:return m.makeReactiveReference(a,!1);case l.ComplexFunction:throw new Error("[mobservable:error] Creating reactive functions from functions with multiple arguments is currently not supported, see https://github.com/mweststrate/mobservable/issues/12");case l.ViewFunction:return new m.ComputedObservable(a,b.scope).createGetterSetter();case l.Array:return new m.ObservableArray(a,d);case l.PlainObject:return m.extendReactive({},a,d)}throw"Illegal State"}function c(a){return null===a||void 0===a?l.Reference:"function"==typeof a?a.length?l.ComplexFunction:l.ViewFunction:Array.isArray(a)||a instanceof m.ObservableArray?l.Array:"object"==typeof a?m.isPlainObject(a)?l.PlainObject:l.ComplexObject:l.Reference}function d(a){return new m.AsReference(a)}function e(a){if(null===a||void 0===a)return!1;switch(typeof a){case"array":case"object":case"function":return a.__isReactive===!0}return!1}function f(a,b){var c=new m.ComputedObservable(a,b),d=c.observe(m.noop);return 0===c.dependencyState.observing.length&&m.warn("mobservable.sideEffect: not a single observable was used inside the side-effect function. Side-effect would be a no-op."),d}function g(a,b){m.extendReactive(a,b,!0)}function h(a,b,c){var d=c?c.value:null;"function"==typeof d?(delete c.value,delete c.writable,c.configurable=!0,c.get=function(){var a=this.key=new m.ComputedObservable(d,this).createGetterSetter();return a},c.set=function(){throw console.trace(),new Error("It is not allowed to reassign observable functions")}):Object.defineProperty(a,b,{configurable:!0,enumberable:!0,get:function(){return m.defineReactiveProperty(this,b,void 0,!0),this[b]},set:function(a){m.defineReactiveProperty(this,b,a,!0)}})}function i(a){if(!a)return a;if(Array.isArray(a)||a instanceof m.ObservableArray)return a.map(i);if("object"==typeof a){var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=i(a[c]));return b}return a}function j(a){return m.Scheduler.batch(a)}function k(a,b){var c=new m.WatchedExpression(a,b);return[c.value,function(){return c.dispose()}]}var l;!function(a){a[a.Reference=0]="Reference",a[a.PlainObject=1]="PlainObject",a[a.ComplexObject=2]="ComplexObject",a[a.Array=3]="Array",a[a.ViewFunction=4]="ViewFunction",a[a.ComplexFunction=5]="ComplexFunction"}(l||(l={})),a.makeReactive=b,a.asReference=d,a.isReactive=e,a.sideEffect=f,a.extendReactive=g,a.observable=h,a.toJson=i,a.transaction=j,a.observeUntilInvalid=k,a.debugLevel=0;var m;!function(a){function b(a,b,c){h(a);for(var e in b)d(a,e,b[e],c);return a}function d(b,d,e,f){var h;e instanceof i?(e=e.value,h=l.Reference,f=!1):h=c(e);var j;switch(h){case l.Reference:case l.ComplexObject:j=g(e,!1);break;case l.ViewFunction:j=new a.ComputedObservable(e,b).createGetterSetter();break;case l.ComplexFunction:a.warn("Storing reactive functions in objects is not supported yet, please use flag 'recurse:false' or wrap the function in 'asReference'"),j=g(e,!1);case l.Array:case l.PlainObject:j=g(e,f)}return Object.defineProperty(b,d,{get:j,set:j,enumerable:!0,configurable:!1}),b}function f(b){if(e(b))return b;if(b instanceof i)return b=b.value;switch(c(b)){case l.Reference:case l.ComplexObject:return b;case l.ViewFunction:case l.ComplexFunction:return a.warn("Storing reactive functions in arrays is not supported, please use flag 'recurse:false' or wrap the function in 'asReference'"),b;case l.Array:return new a.ObservableArray(b,!0);case l.PlainObject:return a.extendReactive({},b,!0)}throw"Illegal State"}function g(b,c){return new a.ObservableValue(b,c).createGetterSetter()}function h(a){Object.defineProperty(a,"__isReactive",{enumerable:!1,value:!0})}a.extendReactive=b,a.defineReactiveProperty=d,a.makeReactiveArrayItem=f,a.makeReactiveReference=g,a.markReactive=h;var i=function(){function a(a){this.value=a}return a}();a.AsReference=i}(m=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(b){function c(a){var b={enumerable:!1,configurable:!1,set:function(b){if(a<this._values.length){var c=this._values[a];c!==b&&(this._values[a]=b,this.notifyChildUpdate(a,c))}else{if(a!==this._values.length)throw new Error("ObservableArray: Index out of bounds, "+a+" is larger than "+this.values.length);this.push(b)}},get:function(){return a<this._values.length?(this.dependencyState.notifyObserved(),this._values[a]):void 0}};Object.defineProperty(f.prototype,""+a,b),b.enumerable=!0,b.configurable=!0,h[a]=b}function d(a){for(var b=g;a>b;b++)c(b);g=a}var e=function(){function a(){}return a}();e.prototype=[];var f=function(c){function e(a,d){c.call(this),b.markReactive(this),Object.defineProperties(this,{recurse:{enumerable:!1,value:d},dependencyState:{enumerable:!1,value:new b.DNode(this)},_values:{enumerable:!1,value:a?d?a.map(b.makeReactiveArrayItem):a.slice():[]},changeEvent:{enumerable:!1,value:new b.SimpleEventEmitter}}),a&&a.length&&this.updateLength(0,a.length)}return __extends(e,c),Object.defineProperty(e.prototype,"length",{get:function(){return this.dependencyState.notifyObserved(),this._values.length},set:function(a){if("number"!=typeof a||0>a)throw new Error("Out of range: "+a);var b=this._values.length;a!==b&&(a>b?this.spliceWithArray(b,0,new Array(a-b)):this.spliceWithArray(a,b-a))},enumerable:!0,configurable:!0}),e.prototype.updateLength=function(a,b){if(0>b)for(var c=a+b;a>c;c++)delete this[c];else if(b>0){a+b>g&&d(a+b);for(var c=a,e=a+b;e>c;c++)Object.defineProperty(this,""+c,h[c])}},e.prototype.spliceWithArray=function(a,c,d){var e=this._values.length;if(!(void 0!==d&&0!==d.length||0!==c&&0!==e))return[];void 0===a?a=0:a>e?a=e:0>a&&(a=Math.max(0,e+a)),c=1===arguments.length?e-a:void 0===c||null===c?0:Math.max(0,Math.min(c,e-a)),void 0===d?d=[]:this.recurse&&(d=d.map(b.makeReactiveArrayItem));var f=d.length-c,g=(h=this._values).splice.apply(h,[a,c].concat(d));return this.updateLength(e,f),this.notifySplice(a,g,d),g;var h},e.prototype.notifyChildUpdate=function(a,b){this.notifyChanged(),this.changeEvent.emit({object:this,type:"update",index:a,oldValue:b})},e.prototype.notifySplice=function(a,b,c){(0!==b.length||0!==c.length)&&(this.notifyChanged(),this.changeEvent.emit({object:this,type:"splice",index:a,addedCount:c.length,removed:b}))},e.prototype.notifyChanged=function(){this.dependencyState.markStale(),this.dependencyState.markReady(!0)},e.prototype.observe=function(a,b){return void 0===b&&(b=!1),b&&a({object:this,type:"splice",index:0,addedCount:this._values.length,removed:[]}),this.changeEvent.on(a)},e.prototype.clear=function(){return this.splice(0)},e.prototype.replace=function(a){return this.spliceWithArray(0,this._values.length,a)},e.prototype.values=function(){return this.dependencyState.notifyObserved(),this._values.slice()},e.prototype.toJSON=function(){return this.dependencyState.notifyObserved(),this._values.slice()},e.prototype.clone=function(){return this.dependencyState.notifyObserved(),new e(this._values,this.recurse)},e.prototype.find=function(a,b,c){void 0===c&&(c=0),this.dependencyState.notifyObserved();for(var d=this._values,e=d.length,f=c;e>f;f++)if(a.call(b,d[f],f,this))return d[f];return null},e.prototype.splice=function(a,b){for(var c=[],d=2;d<arguments.length;d++)c[d-2]=arguments[d];switch(this.sideEffectWarning("splice"),arguments.length){case 0:return[];case 1:return this.spliceWithArray(a);case 2:return this.spliceWithArray(a,b)}return this.spliceWithArray(a,b,c)},e.prototype.push=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];return this.sideEffectWarning("push"),this.spliceWithArray(this._values.length,0,a),this._values.length},e.prototype.pop=function(){return this.sideEffectWarning("pop"),this.splice(Math.max(this._values.length-1,0),1)[0]},e.prototype.shift=function(){return this.sideEffectWarning("shift"),this.splice(0,1)[0]},e.prototype.unshift=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];return this.sideEffectWarning("unshift"),this.spliceWithArray(0,0,a),this._values.length},e.prototype.reverse=function(){return this.sideEffectWarning("reverse"),this.replace(this._values.reverse())},e.prototype.sort=function(a){return this.sideEffectWarning("sort"),this.replace(this._values.sort.apply(this._values,arguments))},e.prototype.remove=function(a){this.sideEffectWarning("remove");var b=this._values.indexOf(a);return b>-1?(this.splice(b,1),!0):!1},e.prototype.toString=function(){return this.wrapReadFunction("toString",arguments)},e.prototype.toLocaleString=function(){return this.wrapReadFunction("toLocaleString",arguments)},e.prototype.concat=function(){return this.wrapReadFunction("concat",arguments)},e.prototype.join=function(a){return this.wrapReadFunction("join",arguments)},e.prototype.slice=function(a,b){return this.wrapReadFunction("slice",arguments)},e.prototype.indexOf=function(a,b){return this.wrapReadFunction("indexOf",arguments)},e.prototype.lastIndexOf=function(a,b){return this.wrapReadFunction("lastIndexOf",arguments)},e.prototype.every=function(a,b){return this.wrapReadFunction("every",arguments)},e.prototype.some=function(a,b){return this.wrapReadFunction("some",arguments)},e.prototype.forEach=function(a,b){return this.wrapReadFunction("forEach",arguments)},e.prototype.map=function(a,b){return this.wrapReadFunction("map",arguments)},e.prototype.filter=function(a,b){return this.wrapReadFunction("filter",arguments)},e.prototype.reduce=function(a,b){return this.wrapReadFunction("reduce",arguments)},e.prototype.reduceRight=function(a,b){return this.wrapReadFunction("reduceRight",arguments)},e.prototype.wrapReadFunction=function(a,b){var c=Array.prototype[a];return(e.prototype[a]=function(){return this.dependencyState.notifyObserved(),c.apply(this._values,arguments)}).apply(this,b)},e.prototype.sideEffectWarning=function(c){a.debugLevel>0&&b.DNode.trackingStack.length>0&&b.warn("[Mobservable.Array] The method array."+c+" should probably not be used inside observable functions since it has side-effects")},e}(e);b.ObservableArray=f;var g=0,h=[];d(1e3)}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){function b(b){var c=b.prototype.componentWillMount,d=b.prototype.componentWillUnmount;return b.prototype.componentWillMount=function(){a.reactiveMixin.componentWillMount.apply(this,arguments),c&&c.apply(this,arguments)},b.prototype.componentWillUnmount=function(){a.reactiveMixin.componentWillUnmount.apply(this,arguments),d&&d.apply(this,arguments)},b.prototype.shouldComponentUpdate||(b.prototype.shouldComponentUpdate=a.reactiveMixin.shouldComponentUpdate),b}a.reactiveMixin={componentWillMount:function(){var b=this.render;this.render=function(){var c=this;this._watchDisposer&&this._watchDisposer();var d=a.observeUntilInvalid(function(){return b.call(c)},function(){c.forceUpdate()}),e=d[0],f=d[1];return this._watchDisposer=f,e}},componentWillUnmount:function(){this._watchDisposer&&this._watchDisposer()},shouldComponentUpdate:function(a,b){if(this.state!==b)return!0;var c,d=Object.keys(this.props);if(d.length!==Object.keys(a).length)return!0;for(var e=d.length-1;c=d[e];e--)if(a[c]!==this.props[c])return!0;return!1}},a.reactiveComponent=b}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(a){var b=function(){function a(){}return a.schedule=function(b){a.inBatch<1?b():a.tasks[a.tasks.length]=b},a.runPostBatchActions=function(){for(var b=0;a.tasks.length;)try{for(;b<a.tasks.length;b++)a.tasks[b]();a.tasks=[]}catch(c){console.error("Failed to run scheduled action, the action has been dropped from the queue: "+c,c),a.tasks.splice(0,b+1)}},a.batch=function(b){a.inBatch+=1;try{return b()}finally{0===--a.inBatch&&(a.inBatch+=1,a.runPostBatchActions(),a.inBatch-=1)}},a.inBatch=0,a.tasks=[],a}();a.Scheduler=b}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(a){var b=function(){function b(){this.listeners=[]}return b.prototype.emit=function(){var a=this.listeners.slice(),b=a.length;switch(arguments.length){case 0:for(var c=0;b>c;c++)a[c]();break;case 1:for(var d=arguments[0],c=0;b>c;c++)a[c](d);break;default:for(var c=0;b>c;c++)a[c].apply(null,arguments)}},b.prototype.on=function(b){var c=this;return this.listeners.push(b),a.once(function(){var a=c.listeners.indexOf(b);-1!==a&&c.listeners.splice(a,1)})},b.prototype.once=function(a){var b=this.on(function(){b(),a.apply(this,arguments)});return b},b}();a.SimpleEventEmitter=b}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(a){function b(a){console&&console.warn("[mobservable:warning] "+a)}function c(a){var b=!1;return function(){return b?void 0:(b=!0,a.apply(this,arguments))}}function d(){}function e(a){return null!==a&&"object"==typeof a&&Object.getPrototypeOf(a)===Object.prototype}function f(a,b){if(!b||!b.length)return[a,[]];if(!a||!a.length)return[[],b];for(var c=[],d=[],e=0,f=0,g=a.length,h=!1,i=0,j=0,k=b.length,l=!1,m=!1;!m&&!h;){if(!l){if(g>e&&k>i&&a[e]===b[i]){if(e++,i++,e===g&&i===k)return[c,d];continue}f=e,j=i,l=!0}j+=1,f+=1,j>=k&&(m=!0),f>=g&&(h=!0),h||a[f]!==b[i]?m||b[j]!==a[e]||(d.push.apply(d,b.slice(i,j)),i=j+1,e++,l=!1):(c.push.apply(c,a.slice(e,f)),e=f+1,i++,l=!1)}return c.push.apply(c,a.slice(e)),d.push.apply(d,b.slice(i)),[c,d]}a.warn=b,a.once=c,a.noop=d,a.isPlainObject=e,a.quickDiff=f}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(a){var b=function(){function b(b,c){this.expr=b,this.onInvalidate=c,this.dependencyState=new a.DNode(this),this.didEvaluate=!1,this.dependencyState.computeNextState()}return b.prototype.compute=function(){return this.didEvaluate?(this.dispose(),this.onInvalidate()):(this.didEvaluate=!0,this.value=this.expr()),!1},b.prototype.dispose=function(){this.dependencyState.dispose()},b}();a.WatchedExpression=b}(b=a._||(a._={}))}(mobservable||(mobservable={}));var forCompilerVerificationOnly=mobservable;!function(a,b){"function"==typeof define&&define.amd?define("mobservable",[],function(){return b()}):"object"==typeof exports?module.exports=b():a.mobservable=b()}(this,function(){var a=mobservable.makeReactive;for(var b in mobservable)a[b]=mobservable[b];return a}); | ||
var mobservable;!function(a){var b;!function(b){var c=function(){function c(a,c){this.value=a,this.recurse=c,this.changeEvent=new b.SimpleEventEmitter,this.dependencyState=new b.DNode(this),this._value=this.makeReferenceValueReactive(a)}return c.prototype.makeReferenceValueReactive=function(c){return this.recurse&&(Array.isArray(c)||b.isPlainObject(c))?a.makeReactive(c):c},c.prototype.set=function(a){if(a!==this._value){var b=this._value;this.dependencyState.markStale(),this._value=this.makeReferenceValueReactive(a),this.dependencyState.markReady(!0),this.changeEvent.emit(this._value,b)}},c.prototype.get=function(){return this.dependencyState.notifyObserved(),this._value},c.prototype.observe=function(a,c){var d=this;void 0===c&&(c=!1),this.dependencyState.setRefCount(1),c&&a(this.get(),void 0);var e=this.changeEvent.on(a);return b.once(function(){d.dependencyState.setRefCount(-1),e()})},c.prototype.createGetterSetter=function(){var a=this,c=function(b){return arguments.length>0?void a.set(b):a.get()};return c.impl=this,c.observe=function(b,c){return a.observe(b,c)},c.toString=function(){return""+a.value},b.markReactive(c),c},c.prototype.toString=function(){return"Observable["+this._value+"]"},c}();b.ObservableValue=c}(b=a._||(a._={}))}(mobservable||(mobservable={}));var __extends=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},mobservable;!function(a){var b;!function(b){var c=function(c){function d(a,b){if(c.call(this,void 0,!1),this.func=a,this.scope=b,this.isComputing=!1,this.hasError=!1,"function"!=typeof a)throw new Error("ComputedObservable requires a function")}return __extends(d,c),d.prototype.get=function(){if(this.isComputing)throw new Error("Cycle detected");var c=this.dependencyState;if(c.isSleeping?b.DNode.trackingStack.length>0?(c.wakeUp(),c.notifyObserved()):this.compute():c.notifyObserved(),c.hasCycle)throw new Error("Cycle detected");if(this.hasError)throw a.debugLevel&&(console.trace(),b.warn(this+": rethrowing caught exception to observer: "+this._value+(this._value.cause||""))),this._value;return this._value},d.prototype.set=function(a){throw new Error(this.toString()+": A computed observable does not accept new values!")},d.prototype.compute=function(){var a;try{if(this.isComputing)throw new Error("Cycle detected");this.isComputing=!0,a=this.func.call(this.scope),this.hasError=!1}catch(b){this.hasError=!0,console.error(this+"Caught error during computation: ",b),b instanceof Error?a=b:(a=new Error("MobservableComputationError"),a.cause=b)}if(this.isComputing=!1,a!==this._value){var c=this._value;return this._value=a,this.changeEvent.emit(a,c),!0}return!1},d.prototype.toString=function(){return"ComputedObservable["+this.func.toString()+"]"},d}(b.ObservableValue);b.ComputedObservable=c}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(b){function c(){return e.trackingStack.length}!function(a){a[a.STALE=0]="STALE",a[a.PENDING=1]="PENDING",a[a.READY=2]="READY"}(b.DNodeState||(b.DNodeState={}));var d=b.DNodeState,e=function(){function c(a){this.owner=a,this.state=d.READY,this.isSleeping=!0,this.hasCycle=!1,this.observing=[],this.prevObserving=null,this.observers=[],this.dependencyChangeCount=0,this.dependencyStaleCount=0,this.isDisposed=!1,this.externalRefenceCount=0,this.isComputed=void 0!==a.compute}return c.prototype.setRefCount=function(a){var b=this.externalRefenceCount+=a;0===b?this.tryToSleep():b===a&&this.wakeUp()},c.prototype.addObserver=function(a){this.observers[this.observers.length]=a},c.prototype.removeObserver=function(a){var b=this.observers,c=b.indexOf(a);-1!==c&&(b.splice(c,1),0===b.length&&this.tryToSleep())},c.prototype.markStale=function(){this.state===d.READY&&(this.state=d.STALE,this.notifyObservers())},c.prototype.markReady=function(a){this.state!==d.READY&&(this.state=d.READY,this.notifyObservers(a))},c.prototype.notifyObservers=function(a){void 0===a&&(a=!1);for(var b=this.observers.slice(),c=b.length,d=0;c>d;d++)b[d].notifyStateChange(this,a)},c.prototype.tryToSleep=function(){if(!this.isSleeping&&this.isComputed&&0===this.observers.length&&0===this.externalRefenceCount){for(var a=0,b=this.observing.length;b>a;a++)this.observing[a].removeObserver(this);this.observing=[],this.isSleeping=!0}},c.prototype.wakeUp=function(){this.isSleeping&&this.isComputed&&(this.isSleeping=!1,this.state=d.PENDING,this.computeNextState())},c.prototype.notifyStateChange=function(a,c){var e=this;a.state===d.STALE?1===++this.dependencyStaleCount&&this.markStale():(c&&(this.dependencyChangeCount+=1),0===--this.dependencyStaleCount&&(this.state=d.PENDING,b.Scheduler.schedule(function(){e.dependencyChangeCount>0?e.computeNextState():e.markReady(!1),e.dependencyChangeCount=0})))},c.prototype.computeNextState=function(){this.trackDependencies();var a=this.owner.compute();this.bindDependencies(),this.markReady(a)},c.prototype.trackDependencies=function(){this.prevObserving=this.observing,c.trackingStack[c.trackingStack.length]=[]},c.prototype.bindDependencies=function(){this.observing=c.trackingStack.pop(),this.isComputed&&0===this.observing.length&&a.debugLevel>1&&!this.isDisposed&&(console.trace(),b.warn("You have created a function that doesn't observe any values, did you forget to make its dependencies observable?"));var d=b.quickDiff(this.observing,this.prevObserving),e=d[0],f=d[1];this.prevObserving=null;for(var g=0,h=f.length;h>g;g++)f[g].removeObserver(this);this.hasCycle=!1;for(var g=0,h=e.length;h>g;g++)this.isComputed&&e[g].findCycle(this)?(this.hasCycle=!0,this.observing.splice(this.observing.indexOf(e[g]),1),e[g].hasCycle=!0):e[g].addObserver(this)},c.prototype.notifyObserved=function(){var a=c.trackingStack,b=a.length;if(b>0){var d=a[b-1],e=d.length;d[e-1]!==this&&d[e-2]!==this&&(d[e]=this)}},c.prototype.findCycle=function(a){var b=this.observing;if(-1!==b.indexOf(a))return!0;for(var c=b.length,d=0;c>d;d++)if(b[d].findCycle(a))return!0;return!1},c.prototype.dispose=function(){if(this.observers.length)throw new Error("Cannot dispose DNode; it is still being observed");if(this.observing)for(var a=this.observing.length,b=0;a>b;b++)this.observing[b].removeObserver(this);this.observing=null,this.isDisposed=!0},c.trackingStack=[],c}();b.DNode=e,b.stackDepth=c}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){function b(a,b){if(e(a))return a;b=b||{},a instanceof m.AsReference&&(a=a.value,b.as="reference");var d=b.recurse!==!1,f="reference"===b.as?l.Reference:c(a);switch(f){case l.Reference:case l.ComplexObject:return m.makeReactiveReference(a,!1);case l.ComplexFunction:throw new Error("[mobservable:error] Creating reactive functions from functions with multiple arguments is currently not supported, see https://github.com/mweststrate/mobservable/issues/12");case l.ViewFunction:return new m.ComputedObservable(a,b.scope).createGetterSetter();case l.Array:return new m.ObservableArray(a,d);case l.PlainObject:return m.extendReactive({},a,d)}throw"Illegal State"}function c(a){return null===a||void 0===a?l.Reference:"function"==typeof a?a.length?l.ComplexFunction:l.ViewFunction:Array.isArray(a)||a instanceof m.ObservableArray?l.Array:"object"==typeof a?m.isPlainObject(a)?l.PlainObject:l.ComplexObject:l.Reference}function d(a){return new m.AsReference(a)}function e(a){if(null===a||void 0===a)return!1;switch(typeof a){case"array":case"object":case"function":return a.__isReactive===!0}return!1}function f(a,b){var c=new m.ComputedObservable(a,b),d=c.observe(m.noop);return 0===c.dependencyState.observing.length&&m.warn("mobservable.sideEffect: not a single observable was used inside the side-effect function. Side-effect would be a no-op."),d}function g(a,b){m.extendReactive(a,b,!0)}function h(a,b,c){var d=c?c.value:null;"function"==typeof d?(delete c.value,delete c.writable,c.configurable=!0,c.get=function(){var a=this.key=new m.ComputedObservable(d,this).createGetterSetter();return a},c.set=function(){throw console.trace(),new Error("It is not allowed to reassign observable functions")}):Object.defineProperty(a,b,{configurable:!0,enumberable:!0,get:function(){return m.defineReactiveProperty(this,b,void 0,!0),this[b]},set:function(a){m.defineReactiveProperty(this,b,a,!0)}})}function i(a){if(!a)return a;if(Array.isArray(a)||a instanceof m.ObservableArray)return a.map(i);if("object"==typeof a){var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=i(a[c]));return b}return a}function j(a){return m.Scheduler.batch(a)}function k(a,b){var c=new m.WatchedExpression(a,b);return[c.value,function(){return c.dispose()}]}var l;!function(a){a[a.Reference=0]="Reference",a[a.PlainObject=1]="PlainObject",a[a.ComplexObject=2]="ComplexObject",a[a.Array=3]="Array",a[a.ViewFunction=4]="ViewFunction",a[a.ComplexFunction=5]="ComplexFunction"}(l||(l={})),a.makeReactive=b,a.asReference=d,a.isReactive=e,a.sideEffect=f,a.extendReactive=g,a.observable=h,a.toJson=i,a.transaction=j,a.observeUntilInvalid=k,a.debugLevel=0;var m;!function(a){function b(a,b,c){h(a);for(var e in b)d(a,e,b[e],c);return a}function d(b,d,e,f){var h;e instanceof i?(e=e.value,h=l.Reference,f=!1):h=c(e);var j;switch(h){case l.Reference:case l.ComplexObject:j=g(e,!1);break;case l.ViewFunction:j=new a.ComputedObservable(e,b).createGetterSetter();break;case l.ComplexFunction:a.warn("Storing reactive functions in objects is not supported yet, please use flag 'recurse:false' or wrap the function in 'asReference'"),j=g(e,!1);case l.Array:case l.PlainObject:j=g(e,f)}return Object.defineProperty(b,d,{get:j,set:j,enumerable:!0,configurable:!1}),b}function f(b){if(e(b))return b;if(b instanceof i)return b=b.value;switch(c(b)){case l.Reference:case l.ComplexObject:return b;case l.ViewFunction:case l.ComplexFunction:return a.warn("Storing reactive functions in arrays is not supported, please use flag 'recurse:false' or wrap the function in 'asReference'"),b;case l.Array:return new a.ObservableArray(b,!0);case l.PlainObject:return a.extendReactive({},b,!0)}throw"Illegal State"}function g(b,c){return new a.ObservableValue(b,c).createGetterSetter()}function h(a){Object.defineProperty(a,"__isReactive",{enumerable:!1,value:!0})}a.extendReactive=b,a.defineReactiveProperty=d,a.makeReactiveArrayItem=f,a.makeReactiveReference=g,a.markReactive=h;var i=function(){function a(a){this.value=a}return a}();a.AsReference=i}(m=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(b){function c(a){var b={enumerable:!1,configurable:!1,set:function(b){if(a<this._values.length){var c=this._values[a];c!==b&&(this._values[a]=b,this.notifyChildUpdate(a,c))}else{if(a!==this._values.length)throw new Error("ObservableArray: Index out of bounds, "+a+" is larger than "+this.values.length);this.push(b)}},get:function(){return a<this._values.length?(this.dependencyState.notifyObserved(),this._values[a]):void 0}};Object.defineProperty(f.prototype,""+a,b),b.enumerable=!0,b.configurable=!0,h[a]=b}function d(a){for(var b=g;a>b;b++)c(b);g=a}var e=function(){function a(){}return a}();e.prototype=[];var f=function(c){function e(a,d){c.call(this),b.markReactive(this),Object.defineProperties(this,{recurse:{enumerable:!1,value:d},dependencyState:{enumerable:!1,value:new b.DNode(this)},_values:{enumerable:!1,value:a?d?a.map(b.makeReactiveArrayItem):a.slice():[]},changeEvent:{enumerable:!1,value:new b.SimpleEventEmitter}}),a&&a.length&&this.updateLength(0,a.length)}return __extends(e,c),Object.defineProperty(e.prototype,"length",{get:function(){return this.dependencyState.notifyObserved(),this._values.length},set:function(a){if("number"!=typeof a||0>a)throw new Error("Out of range: "+a);var b=this._values.length;a!==b&&(a>b?this.spliceWithArray(b,0,new Array(a-b)):this.spliceWithArray(a,b-a))},enumerable:!0,configurable:!0}),e.prototype.updateLength=function(a,b){if(0>b)for(var c=a+b;a>c;c++)delete this[c];else if(b>0){a+b>g&&d(a+b);for(var c=a,e=a+b;e>c;c++)Object.defineProperty(this,""+c,h[c])}},e.prototype.spliceWithArray=function(a,c,d){var e=this._values.length;if(!(void 0!==d&&0!==d.length||0!==c&&0!==e))return[];void 0===a?a=0:a>e?a=e:0>a&&(a=Math.max(0,e+a)),c=1===arguments.length?e-a:void 0===c||null===c?0:Math.max(0,Math.min(c,e-a)),void 0===d?d=[]:this.recurse&&(d=d.map(b.makeReactiveArrayItem));var f=d.length-c,g=(h=this._values).splice.apply(h,[a,c].concat(d));return this.updateLength(e,f),this.notifySplice(a,g,d),g;var h},e.prototype.notifyChildUpdate=function(a,b){this.notifyChanged(),this.changeEvent.emit({object:this,type:"update",index:a,oldValue:b})},e.prototype.notifySplice=function(a,b,c){(0!==b.length||0!==c.length)&&(this.notifyChanged(),this.changeEvent.emit({object:this,type:"splice",index:a,addedCount:c.length,removed:b}))},e.prototype.notifyChanged=function(){this.dependencyState.markStale(),this.dependencyState.markReady(!0)},e.prototype.observe=function(a,b){return void 0===b&&(b=!1),b&&a({object:this,type:"splice",index:0,addedCount:this._values.length,removed:[]}),this.changeEvent.on(a)},e.prototype.clear=function(){return this.splice(0)},e.prototype.replace=function(a){return this.spliceWithArray(0,this._values.length,a)},e.prototype.values=function(){return this.dependencyState.notifyObserved(),this._values.slice()},e.prototype.toJSON=function(){return this.dependencyState.notifyObserved(),this._values.slice()},e.prototype.clone=function(){return this.dependencyState.notifyObserved(),new e(this._values,this.recurse)},e.prototype.find=function(a,b,c){void 0===c&&(c=0),this.dependencyState.notifyObserved();for(var d=this._values,e=d.length,f=c;e>f;f++)if(a.call(b,d[f],f,this))return d[f];return null},e.prototype.splice=function(a,b){for(var c=[],d=2;d<arguments.length;d++)c[d-2]=arguments[d];switch(this.sideEffectWarning("splice"),arguments.length){case 0:return[];case 1:return this.spliceWithArray(a);case 2:return this.spliceWithArray(a,b)}return this.spliceWithArray(a,b,c)},e.prototype.push=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];return this.sideEffectWarning("push"),this.spliceWithArray(this._values.length,0,a),this._values.length},e.prototype.pop=function(){return this.sideEffectWarning("pop"),this.splice(Math.max(this._values.length-1,0),1)[0]},e.prototype.shift=function(){return this.sideEffectWarning("shift"),this.splice(0,1)[0]},e.prototype.unshift=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];return this.sideEffectWarning("unshift"),this.spliceWithArray(0,0,a),this._values.length},e.prototype.reverse=function(){return this.sideEffectWarning("reverse"),this.replace(this._values.reverse())},e.prototype.sort=function(a){return this.sideEffectWarning("sort"),this.replace(this._values.sort.apply(this._values,arguments))},e.prototype.remove=function(a){this.sideEffectWarning("remove");var b=this._values.indexOf(a);return b>-1?(this.splice(b,1),!0):!1},e.prototype.toString=function(){return this.wrapReadFunction("toString",arguments)},e.prototype.toLocaleString=function(){return this.wrapReadFunction("toLocaleString",arguments)},e.prototype.concat=function(){return this.wrapReadFunction("concat",arguments)},e.prototype.join=function(a){return this.wrapReadFunction("join",arguments)},e.prototype.slice=function(a,b){return this.wrapReadFunction("slice",arguments)},e.prototype.indexOf=function(a,b){return this.wrapReadFunction("indexOf",arguments)},e.prototype.lastIndexOf=function(a,b){return this.wrapReadFunction("lastIndexOf",arguments)},e.prototype.every=function(a,b){return this.wrapReadFunction("every",arguments)},e.prototype.some=function(a,b){return this.wrapReadFunction("some",arguments)},e.prototype.forEach=function(a,b){return this.wrapReadFunction("forEach",arguments)},e.prototype.map=function(a,b){return this.wrapReadFunction("map",arguments)},e.prototype.filter=function(a,b){return this.wrapReadFunction("filter",arguments)},e.prototype.reduce=function(a,b){return this.wrapReadFunction("reduce",arguments)},e.prototype.reduceRight=function(a,b){return this.wrapReadFunction("reduceRight",arguments)},e.prototype.wrapReadFunction=function(a,b){var c=Array.prototype[a];return(e.prototype[a]=function(){return this.dependencyState.notifyObserved(),c.apply(this._values,arguments)}).apply(this,b)},e.prototype.sideEffectWarning=function(c){a.debugLevel>0&&b.DNode.trackingStack.length>0&&b.warn("[Mobservable.Array] The method array."+c+" should probably not be used inside observable functions since it has side-effects")},e}(e);b.ObservableArray=f;var g=0,h=[];d(1e3)}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){function b(b){var c=b.prototype.componentWillMount,d=b.prototype.componentWillUnmount;return b.prototype.componentWillMount=function(){a.reactiveMixin.componentWillMount.apply(this,arguments),c&&c.apply(this,arguments)},b.prototype.componentWillUnmount=function(){a.reactiveMixin.componentWillUnmount.apply(this,arguments),d&&d.apply(this,arguments)},b.prototype.shouldComponentUpdate||(b.prototype.shouldComponentUpdate=a.reactiveMixin.shouldComponentUpdate),b}a.reactiveMixin={componentWillMount:function(){var b=this.render;this.render=function(){var c=this;this._watchDisposer&&this._watchDisposer();var d=a.observeUntilInvalid(function(){return b.call(c)},function(){c.forceUpdate()}),e=d[0],f=d[1];return this._watchDisposer=f,e}},componentWillUnmount:function(){this._watchDisposer&&this._watchDisposer()},shouldComponentUpdate:function(a,b){if(this.state!==b)return!0;var c,d=Object.keys(this.props);if(d.length!==Object.keys(a).length)return!0;for(var e=d.length-1;c=d[e];e--)if(a[c]!==this.props[c])return!0;return!1}},a.reactiveComponent=b}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(a){var b=function(){function a(){}return a.schedule=function(b){a.inBatch<1?b():a.tasks[a.tasks.length]=b},a.runPostBatchActions=function(){for(var b=0;a.tasks.length;)try{for(;b<a.tasks.length;b++)a.tasks[b]();a.tasks=[]}catch(c){console.error("Failed to run scheduled action, the action has been dropped from the queue: "+c,c),a.tasks.splice(0,b+1)}},a.batch=function(b){a.inBatch+=1;try{return b()}finally{0===--a.inBatch&&(a.inBatch+=1,a.runPostBatchActions(),a.inBatch-=1)}},a.inBatch=0,a.tasks=[],a}();a.Scheduler=b}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(a){var b=function(){function b(){this.listeners=[]}return b.prototype.emit=function(){var a=this.listeners.slice(),b=a.length;switch(arguments.length){case 0:for(var c=0;b>c;c++)a[c]();break;case 1:for(var d=arguments[0],c=0;b>c;c++)a[c](d);break;default:for(var c=0;b>c;c++)a[c].apply(null,arguments)}},b.prototype.on=function(b){var c=this;return this.listeners.push(b),a.once(function(){var a=c.listeners.indexOf(b);-1!==a&&c.listeners.splice(a,1)})},b.prototype.once=function(a){var b=this.on(function(){b(),a.apply(this,arguments)});return b},b}();a.SimpleEventEmitter=b}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(a){function b(a){console&&console.warn("[mobservable:warning] "+a)}function c(a){var b=!1;return function(){return b?void 0:(b=!0,a.apply(this,arguments))}}function d(){}function e(a){return null!==a&&"object"==typeof a&&Object.getPrototypeOf(a)===Object.prototype}function f(a,b){if(!b||!b.length)return[a,[]];if(!a||!a.length)return[[],b];for(var c=[],d=[],e=0,f=0,g=a.length,h=!1,i=0,j=0,k=b.length,l=!1,m=!1;!m&&!h;){if(!l){if(g>e&&k>i&&a[e]===b[i]){if(e++,i++,e===g&&i===k)return[c,d];continue}f=e,j=i,l=!0}j+=1,f+=1,j>=k&&(m=!0),f>=g&&(h=!0),h||a[f]!==b[i]?m||b[j]!==a[e]||(d.push.apply(d,b.slice(i,j)),i=j+1,e++,l=!1):(c.push.apply(c,a.slice(e,f)),e=f+1,i++,l=!1)}return c.push.apply(c,a.slice(e)),d.push.apply(d,b.slice(i)),[c,d]}a.warn=b,a.once=c,a.noop=d,a.isPlainObject=e,a.quickDiff=f}(b=a._||(a._={}))}(mobservable||(mobservable={}));var mobservable;!function(a){var b;!function(a){var b=function(){function b(b,c){this.expr=b,this.onInvalidate=c,this.dependencyState=new a.DNode(this),this.didEvaluate=!1,this.dependencyState.computeNextState()}return b.prototype.compute=function(){return this.didEvaluate?(this.dispose(),this.onInvalidate()):(this.didEvaluate=!0,this.value=this.expr()),!1},b.prototype.dispose=function(){this.dependencyState.dispose()},b}();a.WatchedExpression=b}(b=a._||(a._={}))}(mobservable||(mobservable={}));var forCompilerVerificationOnly=mobservable;!function(a,b){"function"==typeof define&&define.amd?define("mobservable",[],function(){return b()}):"object"==typeof exports?module.exports=b():a.mobservable=b()}(this,function(){var a=mobservable.makeReactive;for(var b in mobservable)a[b]=mobservable[b];return a}); |
372
docs/api.md
# API Documentation | ||
## mobservable top level api | ||
Mobservable divides your application into three different concepts: | ||
1. State | ||
2. Views on your state | ||
3. State management | ||
_State_ is is all the factual information that lives inside your application. | ||
This might be the profile of the user that is logged in, the tasks he needs to manage, or the fact that the sidebar currently collapsed. | ||
With Mobservable you can make your state reactive. This means that all derived views based on your state are updated automatically. | ||
The first section of this api documentation describes how to [make data reactive](#making-state-reactive). | ||
_Views_ are all pieces of information that can be derived from the _State_ or its mutations. | ||
For example; the amount of unfinished tasks, the user interface and the data mutations that need to be synced with the server. | ||
Those are all forms of views. | ||
The second section describes how to [react to data changes](#reacting-to-state-changes). | ||
Finally your application has actions that _change state_. | ||
Mobservable does not dictate how to change your state. | ||
Instead of that, Mobservable tries to be as unobtrusive as possible. | ||
You can use mutable objects and arrays, real references, classes and cyclic data structures to store your state. | ||
With Mobservable you are free to mutate that state in any way you think is the best. | ||
Different examples of storing state in ES5, ES6, or TypeScript, using plain objects, constructor functions or classes can be found in the [syntax documentation](syntax.md). | ||
The third section describes some [utility functions](#utility-functions) that might come in convenient. | ||
## Making state reactive | ||
### makeReactive(data, options) | ||
`makeReactive` is the swiss knife of `mobservable`. It converts `data` to something similar, but reactive, based on the type of `data`. | ||
The following types are distinguished: | ||
`makeReactive` is the swiss knife of `mobservable`. It converts `data` to something reactive. | ||
The following types are distinguished, details are described below. | ||
* `Primitive`: boolean, string, number, null, undefined, date | ||
* `PlainObject`: raw javascript objects that was not created using a constructor function | ||
* `ComplexObject`: raw javascript object that was created using a constructor function | ||
* `Array` | ||
* `ViewFuncion`: function that takes no arguments but produces a value | ||
* `ComplexFunction`: function that takes one or more arguments and might produce a value | ||
* `Primitive`: Any boolean, string, number, null, undefined, date, or regex. | ||
* `PlainObject`: Any raw javascript object that wasn't created using a constructor function | ||
* `ComplexObject`: A javascript object that was created by a constructor function (using the `new` keyword, with the sole exception of `new Object`). | ||
* `Array`: A javascript array; anything which `Array.isArray` yields true. | ||
* `ViewFuncion`: A function that takes no arguments but produces a value based on its scope / closure. | ||
* `ComplexFunction`: A function that takes one or more arguments and might produce a value | ||
If `data` is a primitive value, _complex_ object or function, a _[reactive getter/setter](#reactive-getter-setter)_ will be returned. | ||
#### Primitive values | ||
If `data` is a primitive value, _complex_ object or function, a getter/setter function is returned: | ||
a function that returns its current value if invoked without arguments, or updates its current value if invoked with exactly one argument. | ||
If updated, it will notify all its _observers_. | ||
For plain objects, a plain object with reactive properties will be returned. View functions inside the object will become reactive properties of the object. Their `this` will be bound to the object. | ||
New observers can be registered by invoking the _.observe(callback, invokeImmediately=false)_ method. | ||
If the second parameter passed to `.observe` is true, the callback will be invoked with the current value immediately. | ||
Otherwise the callback will be invoked on the first change. | ||
Note that you might never use `.observe` yourself; as creating new [reactive functions or side-effects](#reacting-to-state-changes) is a more high-level approach to observe one or more values. | ||
For arrays an [reactive array](#reactive-array) will be returned. | ||
In practice you will hardly use `makeReactive` for primitives, as most primitive values will belong to some object. | ||
Reactiveness is an contagious thing; all values that are part of `data`, or will be in same future time, | ||
will be made reactive as well. | ||
Except for non-plain objects, multi-argument functions or any value that is wrapped in `asReference`. | ||
```javascript | ||
var temperature = makeReactive(25); | ||
temperature.observe(function(newTemperature, oldTemperature) { | ||
console.log('Temperature changed from ', oldTemperature, ' to ', newTemperature); | ||
}); | ||
temperature(30); | ||
// prints: 'Temperature changed from 25 to 30' | ||
console.log(temperature()); | ||
// prints: '30'. | ||
``` | ||
#### Plain objects | ||
If `makeReactive` is invoked on a plain objects, a new plain object with reactive properties based on the original properties will be returned. | ||
`makeReactive` will recurse into all property values of the original object. | ||
Any values that will be assigned to these properties in the future, will be made reactive as well if needed. | ||
View functions inside the object will become reactive properties of the object (see the next section for more info about reactive functions). | ||
Their `this` will be bound to the object automatically. | ||
Properties that will be added to the reactive object later on won't become reactive automatically. | ||
This makes it easy to extend objects with, for example, functions that are not reactive themselves but instead mutate the object. | ||
If you want to add a new reactive property to an existing object, just use `extendReactive`. | ||
Example: | ||
```javascript | ||
var orderLine = makeReactive({ | ||
price: 10, | ||
amount: 1, | ||
total: function() { | ||
return this.price * this.amount; | ||
} | ||
}); | ||
// sideEffect is explained below, | ||
mobservable.sideEffect(function() { | ||
console.log(orderline.total); | ||
}); | ||
// prints: 10 | ||
orderLine.amount = 3; | ||
// prints: 30 | ||
``` | ||
The recommended way to create reactive objects is to create a constructor function and use `extendReactive(this, properties)` inside the constructor; | ||
this keeps the responsibility of making an object inside the object and makes it impossible to accidentally use a non-reactive version of the object. | ||
However, some prefer to not use constructor functions at all in javascript applications. | ||
So Mobservable will work just as fine when using `makeReactive(plainObject)`. | ||
#### Complex objects | ||
Passing non-plain objects to `makeReactive` will result in a reactive reference to the object, similar to creating reactive primitive values. | ||
The constructor of such objects is considered responsible for creating reactive properties if needed. | ||
#### Arrays | ||
For arrays a new, reactive array will be returned. | ||
Like with plain objects, reactiveness is a contagious thing; all values of the array, | ||
now or in the future, will be made reactive as well if needed. | ||
Arrays created using `makeReactive` provide a thin abstraction over native arrays. | ||
The most notable difference between built-in arrays is that reactive arrays cannot be sparse; | ||
values assigned to an index larger than `length` are considered to be out-of-bounds and will not become reactive. | ||
Furthermore, `Array.isArray(reactiveArray)` and `typeof reactiveArray === "array"` will yield `false` for reactive arrays, | ||
but `reactiveArray instanceof Array` will return `true`. | ||
This has consequences when passing arrays to external methods or built-in functions, like `array.concat`, as they might not handle reactive arrays correctly. | ||
(This might improve in the future). | ||
***To avoid issues with other libraries, just make defensive copies before passing reactive arrays to external libraries using `array.slice()`.*** | ||
Reactive arrays support all available ES5 array methods. Besides those, the following methods are available as well: | ||
* `observe(listener, fireImmediately? = false)` Listen to changes in this array. The callback will receive arguments that express an array splice or array change, conforming to [ES7 proposal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/observe). It returns a disposer function to stop the listener. | ||
* `clear()` Remove all current entries from the array. | ||
* `replace(newItems)` Replaces all existing entries in the array with new ones. | ||
* `clone()` Create a new observable array containing the same values. | ||
* `find(predicate: (item, index, array) => boolean, thisArg?, fromIndex?)` Find implementation, basically the same as the ES7 Array.find proposal, but with added `fromIndex` parameter. | ||
* `remove(value)` Remove a single item by value from the array. Returns true if the item was found and removed. | ||
#### Functions | ||
Those are explained in the next [section](#reacting-to-state-changes). | ||
#### Further notes on `makeReactive`. | ||
`makeReactive` will not recurse into non-plain objects, multi-argument functions and any value that is wrapped in `asReference`. | ||
`makeReactive` will not recurse into objects that already have been processed by `makeReactive` or `extendReactive`. | ||
The second `options` parameter object is optional but can define using following flags: | ||
The `options` object is optional but can define the following flags: | ||
* `as` specifies what kind of reactive object should be made. Defaults to `"auto"`. Other valid values are `"reference"`, `"struct"` (in a later version see #8). | ||
@@ -36,41 +152,40 @@ * `scope` defined the `this` of reactive functions. Will be set automatically in most cases | ||
`makeReactive` is the default export of the `mobservable` module, so `mobservable(value) === mobservable.makeReactive(value)`. | ||
More flags will be made available in the feature. | ||
```javascript | ||
var todoStore = mobservable.makeReactive({ | ||
todos: [ | ||
{ | ||
title: 'Find a clean mug', | ||
completed: true | ||
}, | ||
{ | ||
title: 'Make coffee', | ||
completed: false | ||
} | ||
], | ||
completedCount: function() { | ||
return this.todos.filter((todo) => todo.completed).length; | ||
}, | ||
pending: 0 | ||
}); | ||
``` | ||
`makeReactive` is the default export of the `mobservable` module, so you can use `mobservable(data, opts)` as a shorthand. | ||
### extendReactive(target, properties) | ||
Creates reactive `properties` on the given `target` object. Works similar to `makeReactive`, but extends existing objects. | ||
`extendReactive` works similarly to `makeReactive`, but it extends an existing object instead of creating a new one. Similar to `Object.assign` or `jQuery.extend`. | ||
This is especially useful inside constructor functions or to extend existing (possibly already reactive) objects. | ||
### isReactive(value) | ||
In general, it is better to use `extendReactive(target, { property : value })` than `target.property = makeReactive(value)`. | ||
The difference is that in the later only creates a reactive value, while `extendReactive` will make the property itself reactive as well, | ||
so that you can safely assign new values to it later on. | ||
Returns true if the given value was created or extended by mobservable. | ||
### asReference(value) | ||
See `makeReactive`, the given value will not be converted to a reactive structure if its added to another reactive structure. The reference to it will be observable nonetheless. | ||
See `makeReactive`, the given value will not be converted to a reactive structure if it is added to another reactive structure. | ||
The reference to it will be observable nonetheless. | ||
In the following example the properties of the dimensions object itself won't be reactive, but assigning a new object value to the `image.dimension` will be picked up: | ||
```javascript | ||
var image = makeReactive({ | ||
src: "/some/path", | ||
dimension: asReference({ | ||
width: 100, | ||
height: 200 | ||
}) | ||
}); | ||
``` | ||
### observable | ||
Decorator (or annotation) that can be used on ES6 or TypeScript properties to make them reactive. | ||
It can be used on functions as well for reactive derived data, but for consistency it is recommended to assign it to a getter in that case. | ||
It can be used on functions as well if they should be reactive, but for type consistency it is recommended to use a getter function in such cases. | ||
Note that in ES6 the annotation can only be used on getter functions, as ES6 doesn't support property initializers in class declarations. | ||
See also the [syntax section](syntax.md) to see how `@observable` can be combined with different flavors of javascript code. | ||
```javascript | ||
@@ -101,11 +216,58 @@ /// <reference path="./node_modules/mobservable/dist/mobservable.d.ts"/> | ||
### sideEffect(function) | ||
## Reacting to state changes | ||
Makes `function` reactive. The difference with `makeReactive(function)` is that in cases where `sideEffect` is used, `function` will always be | ||
triggered when one of its dependencies changes, whereas `makeReactive(function)` creates reactive functions that only re-evaluate if it has | ||
observers on its own. `sideEffect` return a functions that cancels its effect. | ||
### makeReactive(function, options) | ||
`sideEffect` is very useful if you need to bridge from reactive to imperative code, for example: | ||
Responding to changes in your state is simply the matter of passing a `function` that takes no parameters to `makeReactive`. | ||
Mobservable will track which reactive objects, array and other reactive functions are used by the provided function. | ||
Mobservable will call `function` again when any of those values have changed. | ||
This will happen in such a way one can never observe a stale output of `function`; updates are pushed synchronously. | ||
Invocations of `function` will only happen when none of its dependencies is stale, so that updates are atomic. | ||
This is a major difference with many other reactive frameworks. | ||
This sounds complicated and expensive but in practice you won't notice any performance overhead in any reasonable scenario. | ||
Reactive functions evaluate lazily; if nobody is observing the reactive function it will never evaluate. | ||
For non-lazy reactive functions see `sideEffect`. | ||
Invoking `makeReactive` directly on a function will result in a _getter function_, similar to invoking `makeReactive` on primitive values. | ||
If `makeReactive` encounters a function inside an object passed through it, | ||
it will introduce a new property on that object, that uses the function as getter function for that property. | ||
The optional `options` parameter is an object. | ||
The `scope` property of that object can be set to define the `this` value that will be used inside the reactive function. | ||
```javascript | ||
var greeter = makeReactive({ | ||
who: "world", | ||
greeting: function() { | ||
return "Hello, " + this.who + "!!!"; | ||
} | ||
}); | ||
var upperCaseGreeter = makeReactive(function() { | ||
return greeter.greeting; // greeting has become an reactive property | ||
}); | ||
var disposer = upperCaseGreeter.observe(function(newGreeting) { | ||
console.log(newGreeting) | ||
}); | ||
greeter.who = "Universe"; | ||
// Prints: 'HELLO, UNIVERSE!!!' | ||
disposer(); // stop observing | ||
console.log(greeter.greeting); // prints the latest version of the reactive, derived property | ||
console.log(upperCaseGreeter()); // prints the latest version of the reactive function | ||
``` | ||
### sideEffect(function) | ||
`sideEffect` can be used in those cases where you want to create a reactive function that will never have observers itself. | ||
This is usually the case when you need to bridge from reactive to imperative code, for example for logging, persistence or UI-updating code. | ||
When `sideEffect` is used, `function` will always be | ||
triggered when one of its dependencies changes. | ||
(In contrast, `makeReactive(function)` creates functions that only re-evaluate if it has | ||
observers on its own, otherwise its value is considered to be irrelevant). | ||
```javascript | ||
var numbers = makeReactive([1,2,3]); | ||
@@ -122,5 +284,54 @@ var sum = makeReactive(() => numbers.reduce((a, b) => a + b, 0); | ||
// won't print anything, nor is `sum` re-evaluated | ||
``` | ||
Fun fact: `sideEffect(func)` is actually an alias for `makeReactive(func).observe(function() { /* noop */ });`. | ||
### reactiveComponent(component) | ||
It turns a ReactJS component into a reactive one. | ||
Making a component reactive means that it will automatically observe any reactive data it uses. | ||
It is quite similar to `@connect` as found in several flux libraries, yet there are two important differences. | ||
With `@reactiveComponent` you don't need to specify which store / data should be observed in order to re-render at the appropriate time. | ||
Secondly, reactive components provide far more fine grained update semantics: Reactive components won't be observing a complete store or data tree, but only that data that is actually used during the rendering of the component. This might be a complete list, but also a single object or even a single property. | ||
The consequence of this is that components won't re-render unless some data that is actually used in the rendering has changed. Large applications really benefit from this in terms of performance. | ||
Rule of thumb is to use `reactiveComponent` on every component in your application that is specific for your application. | ||
Its overhead is neglectable and it makes sure that whenever you start using reactive data the component will respond to it. | ||
One exception are general purposes components that are not specific for your app. As these probably don't depend on the actual state of your application. | ||
For that reason it doesn't make sense to add `reactiveComponent` to them (unless their own state is expressed using reactive data structures as well). | ||
The `reactiveComponent` function / decorator supports both components that are constructed using `React.createClass` or using ES6 classes that extend `React.Component`. `reactiveComponent` is also available as mixin: `mobservable.reactiveMixin`. | ||
`reactiveComponent` also prevents re-renderings when the *props* of the component have only shallowly changed, which makes a lot of sense if the data passed into the component is reactive. | ||
This behavior is similar to [React PureRender mixin](https://facebook.github.io/react/docs/pure-render-mixin.html), except that *state* changes are still always processed. | ||
If a component provides its own `shouldComponentUpdate`, that one takes precedence. | ||
_Note: when `reactiveComponent` needs to be combined with other decorators or higher-order-components, make sure that `reactiveComponent` is the most inner (first applied) decorator; | ||
otherwise it might do nothing at all._ | ||
**ES6 class + decorator** | ||
```javascript | ||
@reactiveComponent class MyCompoment extends React.Component { | ||
/* .. */ | ||
} | ||
``` | ||
**ES6 class + function call** | ||
```javascript | ||
reactiveComponent(class MyCompoment extends React.Component { | ||
/* .. */ | ||
}); | ||
``` | ||
**ES5 + React.createClass** | ||
```javascript | ||
reactiveComponent(React.createClass({ | ||
/* .. */ | ||
})) | ||
``` | ||
Note: `reactiveComponent` is actually the only ReactJS specific thing in mobservable and should be easily portable to other frameworks. | ||
`reactiveComponent` might move to its own package in the future. | ||
### observeUntilInvalid(functionToObserve, onInvalidate) | ||
@@ -134,9 +345,20 @@ | ||
`observeUntilInvalid` 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, e.g. in the `render` method of a React component. | ||
`observeUntilInvalid` 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. | ||
It is for example used to implement `reactiveCompoenent`, which, in pseudo-code, does just: `component.render = () => observeUntilInvalid(originalRender, this.forceUpdate)[0]`; | ||
## Utility functions | ||
### isReactive(value) | ||
Returns true if the given value was created or extended by mobservable. Note: this function cannot be used to tell whether a property is reactive; it will determine the reactiveness of its actual value. | ||
### toJson(value) | ||
Converts a non-cyclic tree of observable objects into a JSON structure that is not observable. It is kind of the inverse of `mobservable.makeReactive` | ||
### transaction(workerFunction) | ||
Transaction 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, e.g. while refreshing data from the backend. | ||
In practice, you wil probably never need `.transaction`, since observables typically update wickedly fast. | ||
This is useful if you want to apply a bunch of different updates throughout your model before needing the updated computed values, e.g. while refreshing data from the back-end. | ||
In practice, you will probably never need `.transaction`, since observables typically update wickedly fast. | ||
@@ -162,51 +384,1 @@ ```javascript | ||
``` | ||
### toJson(value) | ||
Converts a non-cyclic tree of observable objects into a JSON structure that is not observable. It is kind of the inverse of `mobservable.makeReactive` | ||
### reactiveComponent(component) | ||
Turns a reactiveComponent into a reactive one. Supports both components that are contructed using `React.createClass` or using ES6 classes that extend `React.Component`. | ||
See `reactiveMixin`. | ||
### reactiveMixin | ||
The observer mixin can be used in ReactJS components. | ||
This mixin basically turns the `.render` function of the component into an observable function, and makes sure that the component itself becomes an observer of that function, that the component is re-rendered each time an observable has changed. | ||
This mixin also prevents re-renderings when the *props* of the component have only shallowly changed. | ||
(This is similar to [React PureRender mixin](https://facebook.github.io/react/docs/pure-render-mixin.html), except that *state* changes are still always processed). | ||
This allows for React apps that perform well in apps with large amount of complex data, while avoiding the need to manage a lot of subscriptions. | ||
## reactive array | ||
The arrays created using `makeReactive` provide thin abstraction over native arrays and to add reactive entries in the array. | ||
The most notable difference between built-in arrays is that reactive arrays cannot be sparse, i.e. values assigned to an index larger than `length` are considered out-of-bounds and not observed. | ||
Furthermore, `Array.isArray(reactiveArray)` and `typeof reactiveArray === "array"` will yield `false` for reactive arrays, but `reactiveArray instanceof Array` will return `true`. | ||
Reactive arrays implement all the ES5 array methods. Besides those, the following methods are available as well: | ||
* `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, conforming to [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, non-observable 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. | ||
## reactive getter/setter | ||
A reactive getter/setter is a function that wraps a primitive reactive value. If it is invoked without arguments, it returns the current value. | ||
If it is invoked with a value, the current value will be updated with that value. | ||
Futher it exposes an `observe` function which can be used to attach a listener to the getter/setter function to be notified of any future updates. | ||
It's full interface is: | ||
```typescript | ||
interface IObservableValue<T> { | ||
(): T; | ||
(value: T); | ||
observe(callback: (newValue: T, oldValue: T)=>void, fireImmediately?: boolean): () => void /* disposer */; | ||
} | ||
``` |
@@ -1,1 +0,9 @@ | ||
Coming soon. | ||
Coming soon. | ||
Developer tools | ||
State recording and hydration | ||
Dependency tree printing | ||
Faq: how does it keep my memory sane? why are there so few subscription cleanups |
{ | ||
"name": "mobservable", | ||
"version": "0.6.0", | ||
"description": "Unobtrusive reactive library that keeps views automatically in sync with data.", | ||
"version": "0.6.1", | ||
"description": "Keeps views automatically in sync with state. Unobtrusively.", | ||
"main": "dist/mobservable.js", | ||
@@ -6,0 +6,0 @@ "scripts": { |
180
README.md
@@ -6,3 +6,3 @@ # mobservable | ||
##### _Unobtrusive reactive library that keeps views automatically in sync with data._ | ||
##### _Keeps views automatically in sync with state. Unobtrusively._ | ||
@@ -13,24 +13,20 @@ [![Build Status](https://travis-ci.org/mweststrate/mobservable.svg?branch=master)](https://travis-ci.org/mweststrate/mobservable) | ||
[API documentation](https://github.com/mweststrate/mobservable/blob/master/docs/api.md) - [Typings](https://github.com/mweststrate/mobservable/blob/master/dist/mobservable.d.ts) | ||
## Philosophy | ||
##### <center>A [Five minute, interactive introduction](https://mweststrate.github.io/mobservable/getting-started.html) to Mobservable and React</center> | ||
Mobservable is light-weight standalone library to create reactive primitives, functions, arrays and objects. | ||
The goal of mobservable is simple: | ||
1. Write simple views. Views should be subscription free. | ||
2. Write simple controllers and stores. Change data without thinking about how this should be reflected in views. | ||
3. Allow flexible model design, be able to use objects, arrays, classes, real references, and cyclic data structures in your app. | ||
4. Performance: find the absolute minimum amount of changes that are needed to update views. | ||
5. Views should be updated atomically and sychronously without showing stale or intermediate values. | ||
[API documentation](https://github.com/mweststrate/mobservable/blob/master/docs/api.md) - [Tips & Tricks](https://github.com/mweststrate/mobservable/blob/master/docs/syntax.md) - [ES5, ES6, TypeScript syntax examples](https://github.com/mweststrate/mobservable/blob/master/docs/api.md) - [TypeScript Typings](https://github.com/mweststrate/mobservable/blob/master/dist/mobservable.d.ts) | ||
Mobservable is born as part of an enterprise scale visual editor, | ||
which needs high performance rendering and covers over 400 different domain concepts. | ||
So the best performance and the simplest possible controller and view code are both of the utmost importance. | ||
See [this blog](https://www.mendix.com/tech-blog/making-react-reactive-pursuit-high-performing-easily-maintainable-react-apps/) for more details about that journey. | ||
Mobservable applies reactive programming behind the scenes and is inspired by MVVM frameworks like knockout and ember, yet less obtrusive to use. | ||
## 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: | ||
* Write complex applications which unmatched simple code. | ||
* Enable unobtrusive state management: be free to use mutable objects, cyclic references, classes and real references to store state. | ||
* Write declarative views that track their own dependencies. No subscriptions, cursors or other redundant declarations to manage. | ||
* Build [high performing](mendix.com/tech-blog/making-react-reactive-pursuit-high-performing-easily-maintainable-react-apps/) React applications without Flux or Immutable data structures. | ||
* Predictable behavior: all views are updated synchronously and atomically. | ||
## The essentials | ||
Mobservable can be summarized in two functions that will fundamentally simplify the way you write Reactjs applications. Lets take a look at this really really simple timer application: | ||
Mobservable can be summarized in two functions that will fundamentally simplify the way you write React applications. Lets take a look at this really really simple timer application: | ||
@@ -43,3 +39,3 @@ ```javascript | ||
setInterval(function() { | ||
this.timerData.secondsPassed++; | ||
timerData.secondsPassed++; | ||
}, 1000); | ||
@@ -56,5 +52,7 @@ | ||
So what will this app do? It does nothing! The timer increases every second, but the UI never responds to that. After the interval updates the timer we should force the UI to update. | ||
But that is the kind of dependency we want to avoid in our code. So let's apply two simple functions of mobservable instead to fix this issue: | ||
So what will this app do? It does nothing! The timer increases every second, but the will UI never update. To fix that, we should force the UI to refresh somehow upon each interval. | ||
But that is the kind of dependency we should avoid in our code. We shouldn't have to _pull_ data from our state to update the UI. Instead, the data structures should be in control and call the UI when it needs an update. The state should be _pushed_ throughout our application. This is called inversion of control. | ||
We can apply two simple functions of Mobservable to achieve this. | ||
### mobservable.makeReactive | ||
@@ -80,7 +78,7 @@ | ||
Thats all folks! Its as simple as that. The `Timer` will now automatically update each time `timerData.secondsPassed` is altered. | ||
Its as simple as that. The `Timer` will now automatically update each time `timerData.secondsPassed` is altered. | ||
The actual interesting thing about these changes are the things that are *not* in the code: | ||
* The `setInterval` method didn't alter. It still threads `timerData` as a plain JS object. | ||
* There is no state. Timer is still a dump component. | ||
* The `setInterval` method didn't alter. It still treats `timerData` as a plain JS object. | ||
* There is no state. Timer is still a dumb component. | ||
* There is no magic context being passed through components. | ||
@@ -99,110 +97,15 @@ * There are no subscriptions of any kind that need to be managed. | ||
## A Todo application | ||
The following simple todo application can be found up & running on https://mweststrate.github.io/mobservable. A full TodoMVC implementation can be found [here](https://github.com/mweststrate/todomvc/tree/master/examples/react-mobservable) | ||
Note how the array, function and primitive of `todoStore` will all become reactive. There are just three calls to `mobservable` and all the components are kept in sync with the `todoStore`. | ||
```javascript | ||
var todoStore = mobservable.makeReactive({ | ||
todos: [ | ||
{ | ||
title: 'Find a clean mug', | ||
completed: true | ||
}, | ||
{ | ||
title: 'Make coffee', | ||
completed: false | ||
} | ||
], | ||
completedCount: function() { | ||
return this.todos.filter((todo) => todo.completed).length; | ||
}, | ||
pending: 0 | ||
}); | ||
todoStore.addTodo = function(title) { | ||
this.todos.push({ | ||
title: title, | ||
completed: false | ||
}); | ||
}; | ||
todoStore.removeTodo = function(todo) { | ||
this.todos.splice(this.todos.indexOf(todo), 1); | ||
}; | ||
todoStore.loadTodosAsync = function() { | ||
this.pending++; | ||
setTimeout(function() { | ||
this.addTodo('Asynchronously created todo'); | ||
this.pending--; | ||
}.bind(this), 2000); | ||
}; | ||
var TodoList = mobservable.reactiveComponent(React.createClass({ | ||
render: function() { | ||
var store = this.props.store; | ||
return (<div> | ||
<ul> | ||
{ store.todos.map((todo, idx) => | ||
(<TodoView store={ store } todo={ todo } key={ idx } />) | ||
) } | ||
{ store.pending ? (<li>Loading more items...</li>) : null } | ||
</ul> | ||
<hr/> | ||
Completed { store.completedCount } of { store.todos.length } items.<br/> | ||
<button onClick={ this.onNewTodo }>New Todo</button> | ||
<button onClick={ this.loadMore }>Load more...</button> | ||
</div>); | ||
}, | ||
onNewTodo: function() { | ||
this.props.store.addTodo(prompt('Enter a new todo:', 'Try mobservable at home!')); | ||
}, | ||
loadMore: function() { | ||
this.props.store.loadTodosAsync(); | ||
} | ||
})); | ||
var TodoView = mobservable.reactiveComponent(React.createClass({ | ||
render: function() { | ||
var todo = this.props.todo; | ||
return (<li> | ||
<input type='checkbox' checked={ todo.completed } onChange={ this.onToggleCompleted } /> | ||
{todo.title}{' '} | ||
<a href='#' onClick={ this.onEdit }>[edit]</a> | ||
<a href='#' onClick={ this.onRemove }>[remove]</a> | ||
</li>); | ||
}, | ||
onToggleCompleted: function() { | ||
this.props.todo.completed = !this.props.todo.completed; | ||
}, | ||
onEdit: function(e) { | ||
e.preventDefault(); | ||
this.props.todo.title = prompt('Todo:', this.props.todo.title); | ||
}, | ||
onRemove: function(e) { | ||
e.preventDefault(); | ||
this.props.store.removeTodo(this.props.todo); | ||
} | ||
})); | ||
React.render(<TodoList store={todoStore} />, document.getElementById('approot')); | ||
``` | ||
## Getting started | ||
Either: | ||
* [Edit](https://mweststrate.github.io/mobservable/getting-started.html#demo) a simple ToDo application online. | ||
* `npm install mobservable --save` | ||
* clone the boilerplate repository containing the above example from: https://github.com/mweststrate/react-mobservable-boilerplate | ||
* or fork this [JSFiddle](https://jsfiddle.net/mweststrate/wgbe4guu/) | ||
* Clone the boilerplate repository containing the above example from: https://github.com/mweststrate/react-mobservable-boilerplate. | ||
* Or fork this [JSFiddle](https://jsfiddle.net/mweststrate/wgbe4guu/). | ||
## Examples | ||
* The [ports of the _Notes_ and _Kanban_ examples](https://github.com/survivejs/mobservable-demo) from the book "SurviveJS - Webpack and React" to mobservable. | ||
* A simple webshop using [React + mobservable](https://jsfiddle.net/mweststrate/46vL0phw) or [JQuery + mobservable](http://jsfiddle.net/mweststrate/vxn7qgdw). | ||
* [Simple timer](https://jsfiddle.net/mweststrate/wgbe4guu/) | ||
* [Simple timer](https://jsfiddle.net/mweststrate/wgbe4guu/) application in JSFiddle. | ||
* [TodoMVC](https://rawgit.com/mweststrate/todomvc/immutable-to-observable/examples/react-mobservable/index.html#/), based on the ReactJS TodoMVC. | ||
@@ -212,2 +115,3 @@ | ||
* [Five minute interactive introducton](https://mweststrate.github.io/mobservable/getting-started.html) to Mobservable and React | ||
* [Making React reactive: the pursuit of high performing, easily maintainable React apps](https://www.mendix.com/tech-blog/making-react-reactive-pursuit-high-performing-easily-maintainable-react-apps/) | ||
@@ -227,9 +131,27 @@ * [Pure rendering in the light of time and state](https://medium.com/@mweststrate/pure-rendering-in-the-light-of-time-and-state-4b537d8d40b1) | ||
## Top level api | ||
For the full api, see the [API documentation](https://github.com/mweststrate/mobservable/blob/master/docs/api.md). | ||
This is an overview of most important functions available in the `mobservable` namespace: | ||
**makeReactive(value, options?)** | ||
Turns a value into a reactive array, object, function, value or a reactive reference to a value. | ||
**reactiveComponent(reactJsComponent)** | ||
Turns a ReactJS component into a reactive one, that automatically re-renders if any reactive data that it uses is changed. | ||
**extendReactive(target, properties)** | ||
Extends an existing object with reactive properties. | ||
**sideEffect(function)** | ||
Similar to `makeReactive(function)`. Exception the created reactive function will not be lazy, so that it is executed even when it has no observers on its own. | ||
Useful to bridge reactive code to imperative code. | ||
## FAQ | ||
**Is mobservable a framework?** | ||
##### Is mobservable a framework? | ||
Mobservabe is *not* a framework. It does not tell you how to structure your code, where to store state or how to process events. Yet it might free you from frameworks that poses all kinds of restrictions on your code in the name of performance. | ||
**Can I combine flux with mobservable?** | ||
##### Can I combine flux with mobservable? | ||
@@ -241,3 +163,3 @@ Flux implementations that do not work on the assumption that the data in their stores is immutable should work well with mobservable. | ||
**Can I use mobservable together with framework X?** | ||
##### Can I use mobservable together with framework X? | ||
@@ -248,1 +170,9 @@ Probably. | ||
Mobservable works just as well server side, and is already combined with JQuery (see this [Fiddle](http://jsfiddle.net/mweststrate/vxn7qgdw)) and [Deku](https://gist.github.com/mattmccray/d8740ea97013c7505a9b). | ||
##### Can I record states and re-hydrate them? | ||
Yes, some examples are coming shortly! | ||
##### Can you tell me how it works? | ||
Sure, join the reactiflux channel our checkout [dnode.ts](dnode.ts). Or, submit an issue to motivate me to make some nice drawings :). |
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
104888
10
169