mobservable
Advanced tools
Comparing version 0.4.3 to 0.5.0
@@ -31,2 +31,7 @@ /** | ||
}; | ||
mobservable.mobservableStatic.expr = function (expr, scope) { | ||
if (DNode.trackingStack.length === 0) | ||
throw new Error("mobservable.expr can only be used inside a computed observable. Probably mobservable.computed should be used instead of .expr"); | ||
return new ComputedObservable(expr, scope).get(); | ||
}; | ||
mobservable.mobservableStatic.array = function array(values) { | ||
@@ -68,4 +73,4 @@ return new ObservableArray(values); | ||
descriptor.get = function () { | ||
mobservable.mobservableStatic.props(this, key, baseValue); | ||
return this[key]; | ||
var observable = this.key = mobservable.mobservableStatic.computed(baseValue, this); | ||
return observable; | ||
}; | ||
@@ -776,3 +781,6 @@ descriptor.set = function () { | ||
var _a = mobservable.mobservableStatic.watch(function () { return baseRender.call(_this); }, function () { | ||
_this.forceUpdate(); | ||
if (_this.isMounted()) | ||
_this.forceUpdate(); | ||
else if (mobservable.mobservableStatic.debugLevel) | ||
warn("Rendering was triggered for unmounted component. Please check the lifecycle of the components"); | ||
}), rendering = _a[0], disposer = _a[1]; | ||
@@ -786,4 +794,30 @@ this._watchDisposer = disposer; | ||
this._watchDisposer(); | ||
}, | ||
shouldComponentUpdate: function (nextProps, nextState) { | ||
if (this.state !== nextState) | ||
return true; | ||
var keys = Object.keys(this.props); | ||
var key; | ||
if (keys.length !== Object.keys(nextProps).length) | ||
return true; | ||
for (var i = keys.length - 1; i >= 0, key = keys[i]; i--) | ||
if (nextProps[key] !== this.props[key]) | ||
return true; | ||
return false; | ||
} | ||
}; | ||
mobservable.mobservableStatic.ObservingComponent = function (componentClass) { | ||
var baseMount = componentClass.componentWillMount; | ||
var baseUnmount = componentClass.componentWillUnmount; | ||
componentClass.prototype.componentWillMount = function () { | ||
mobservable.mobservableStatic.ObserverMixin.componentWillMount.apply(this, arguments); | ||
return baseMount && baseMount.apply(this, arguments); | ||
}; | ||
componentClass.prototype.componentWillUnmount = function () { | ||
mobservable.mobservableStatic.ObserverMixin.componentWillUnmount.apply(this, arguments); | ||
return baseUnmount && baseUnmount.apply(this, arguments); | ||
}; | ||
componentClass.prototype.shouldComponentUpdate = mobservable.mobservableStatic.ObserverMixin.shouldComponentUpdate; | ||
return componentClass; | ||
}; | ||
function quickDiff(current, base) { | ||
@@ -790,0 +824,0 @@ if (!base.length) |
@@ -1,1 +0,1 @@ | ||
var __extends=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){function b(b,c){return Array.isArray(b)?new l(b):"function"==typeof b?a.mobservableStatic.computed(b,c):a.mobservableStatic.primitive(b)}function c(a,b){if(!b.length)return[a,[]];if(!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]}function d(a){console&&console.warn("[WARNING:mobservable] "+a)}function e(a){var b=!1;return function(){return b?void 0:(b=!0,a.apply(this,arguments))}}a.mobservableStatic=function(a,c){return b(a,c)},a.mobservableStatic.value=b,a.mobservableStatic.primitive=a.mobservableStatic.reference=function(a){return new g(a).createGetterSetter()},a.mobservableStatic.computed=function(a,b){return new h(a,b).createGetterSetter()},a.mobservableStatic.array=function(a){return new l(a)},a.mobservableStatic.props=function o(b,o,c){switch(arguments.length){case 0:throw new Error("Not enough arguments");case 1:return a.mobservableStatic.props(b,b);case 2:for(var d in o)a.mobservableStatic.props(b,d,o[d]);break;case 3:var e=Array.isArray(c),f=a.mobservableStatic.value(c,b);Object.defineProperty(b,o,{get:e?function(){return f}:f,set:e?function(a){f.replace(a)}:f,enumerable:!0,configurable:!1})}return b},a.mobservableStatic.observable=function(b,c,d){var e=d?d.value:null;"function"==typeof e?(delete d.value,delete d.writable,d.get=function(){return a.mobservableStatic.props(this,c,e),this[c]},d.set=function(){throw console.trace(),new Error("It is not allowed to reassign observable functions")}):Object.defineProperty(b,c,{configurable:!1,enumberable:!0,get:function(){return a.mobservableStatic.props(this,c,void 0),this[c]},set:function(b){a.mobservableStatic.props(this,c,b)}})},a.mobservableStatic.toPlainValue=function p(a){if(a){if(a instanceof Array)return a.slice();if(a instanceof g)return a.get();if("function"==typeof a&&a.impl){if(a.impl instanceof g)return a();if(a.impl instanceof l)return a().slice()}else if("object"==typeof a){var b={};for(var c in a)b[c]=p(a[c]);return b}}return a},a.mobservableStatic.observeProperty=function(b,c,f,i){if(void 0===i&&(i=!1),!b||!c||void 0===b[c])throw new Error("Object '"+b+"' has no property '"+c+"'.");if(!f||"function"!=typeof f)throw new Error("Third argument to mobservable.observeProperty should be a function");var j=b[c];if(j instanceof g||j instanceof l)return j.observe(f,i);if(j.impl&&(j.impl instanceof g||j instanceof l))return j.impl.observe(f,i);var k=new h(function(){return b[c]},b),m=k.observe(f,i);return a.mobservableStatic.debugLevel&&0===k.dependencyState.observing.length&&d("mobservable.observeProperty: property '"+c+"' of '"+b+" doesn't seem to be observable. Did you define it as observable?"),e(function(){m(),k.dependencyState.dispose()})},a.mobservableStatic.watch=function q(a,b){var q=new i(a,b);return[q.value,function(){return q.dispose()}]},a.mobservableStatic.batch=function(a){return n.batch(a)},a.mobservableStatic.debugLevel=0;var f,g=function(){function a(a){this._value=a,this.changeEvent=new m,this.dependencyState=new j(this)}return a.prototype.set=function(a){if(a!==this._value){var b=this._value;this.dependencyState.markStale(),this._value=a,this.dependencyState.markReady(!0),this.changeEvent.emit(a,b)}},a.prototype.get=function(){return this.dependencyState.notifyObserved(),this._value},a.prototype.observe=function(a,b){var c=this;void 0===b&&(b=!1),this.dependencyState.setRefCount(1),b&&a(this.get(),void 0);var d=this.changeEvent.on(a);return e(function(){c.dependencyState.setRefCount(-1),d()})},a.prototype.createGetterSetter=function(){var a=this,b=this,c=function(a){return arguments.length>0?void b.set(a):b.get()};return c.observe=function(b,c){return a.observe(b,c)},c.impl=this,c.toString=function(){return a.toString()},c},a.prototype.toString=function(){return"Observable["+this._value+"]"},a}(),h=function(b){function c(a,c){if(b.call(this,void 0),this.func=a,this.scope=c,this.isComputing=!1,this.hasError=!1,"function"!=typeof a)throw new Error("ComputedObservable requires a function")}return __extends(c,b),c.prototype.get=function(){if(this.isComputing)throw new Error("Cycle detected");var b=this.dependencyState;if(b.isSleeping?j.trackingStack.length>0?(b.wakeUp(),b.notifyObserved()):this.compute():b.notifyObserved(),b.hasCycle)throw new Error("Cycle detected");if(this.hasError)throw a.mobservableStatic.debugLevel&&(console.trace(),d(this+": rethrowing caught exception to observer: "+this._value+(this._value.cause||""))),this._value;return this._value},c.prototype.set=function(a){throw new Error(this.toString()+": A computed observable does not accept new values!")},c.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},c.prototype.toString=function(){return"ComputedObservable["+this.func.toString()+"]"},c}(g),i=function(){function a(a,b){this.expr=a,this.onInvalidate=b,this.dependencyState=new j(this),this.didEvaluate=!1,this.dependencyState.computeNextState()}return a.prototype.compute=function(){return this.didEvaluate?(this.dispose(),this.onInvalidate()):(this.didEvaluate=!0,this.value=this.expr()),!1},a.prototype.dispose=function(){this.dependencyState.dispose()},a}();!function(a){a[a.STALE=0]="STALE",a[a.PENDING=1]="PENDING",a[a.READY=2]="READY"}(f||(f={}));var j=function(){function b(a){this.owner=a,this.state=f.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 b.prototype.setRefCount=function(a){var b=this.externalRefenceCount+=a;0===b?this.tryToSleep():b===a&&this.wakeUp()},b.prototype.addObserver=function(a){this.observers[this.observers.length]=a},b.prototype.removeObserver=function(a){var b=this.observers,c=b.indexOf(a);-1!==c&&(b.splice(c,1),0===b.length&&this.tryToSleep())},b.prototype.markStale=function(){this.state===f.READY&&(this.state=f.STALE,this.notifyObservers())},b.prototype.markReady=function(a){this.state!==f.READY&&(this.state=f.READY,this.notifyObservers(a))},b.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)},b.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}},b.prototype.wakeUp=function(){this.isSleeping&&this.isComputed&&(this.isSleeping=!1,this.state=f.PENDING,this.computeNextState())},b.prototype.notifyStateChange=function(a,b){var c=this;a.state===f.STALE?1===++this.dependencyStaleCount&&this.markStale():(b&&(this.dependencyChangeCount+=1),0===--this.dependencyStaleCount&&(this.state=f.PENDING,n.schedule(function(){c.dependencyChangeCount>0?c.computeNextState():c.markReady(!1),c.dependencyChangeCount=0})))},b.prototype.computeNextState=function(){this.trackDependencies();var a=this.owner.compute();this.bindDependencies(),this.markReady(a)},b.prototype.trackDependencies=function(){this.prevObserving=this.observing,b.trackingStack[b.trackingStack.length]=[]},b.prototype.bindDependencies=function(){this.observing=b.trackingStack.pop(),this.isComputed&&0===this.observing.length&&a.mobservableStatic.debugLevel>1&&!this.isDisposed&&(console.trace(),d("You have created a function that doesn't observe any values, did you forget to make its dependencies observable?"));var e=c(this.observing,this.prevObserving),f=e[0],g=e[1];this.prevObserving=null;for(var h=0,i=g.length;i>h;h++)g[h].removeObserver(this);this.hasCycle=!1;for(var h=0,i=f.length;i>h;h++)this.isComputed&&f[h].findCycle(this)?(this.hasCycle=!0,this.observing.splice(this.observing.indexOf(f[h]),1),f[h].hasCycle=!0):f[h].addObserver(this)},b.prototype.notifyObserved=function(){var a=b.trackingStack,c=a.length;if(c>0){var d=a[c-1],e=d.length;d[e-1]!==this&&d[e-2]!==this&&(d[e]=this)}},b.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},b.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},b.trackingStack=[],b}(),k=function(){function a(){}return a}();k.prototype=[];var l=function(b){function c(a){b.call(this),Object.defineProperties(this,{dependencyState:{enumerable:!1,value:new j(this)},_values:{enumerable:!1,value:a?a.slice():[]},changeEvent:{enumerable:!1,value:new m}}),a&&a.length&&this.updateLength(0,a.length)}return __extends(c,b),Object.defineProperty(c.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}),c.prototype.updateLength=function(a,b){if(0>b)for(var d=a+b;a>d;d++)delete this[d];else if(b>0){a+b>c.OBSERVABLE_ARRAY_BUFFER_SIZE&&c.reserveArrayBuffer(a+b);for(var d=a,e=a+b;e>d;d++)Object.defineProperty(this,""+d,c.ENUMERABLE_PROPS[d])}},c.prototype.spliceWithArray=function(a,b,c){var d=this._values.length;if(!(void 0!==c&&0!==c.length||0!==b&&0!==d))return[];void 0===a?a=0:a>d?a=d:0>a&&(a=Math.max(0,d+a)),b=1===arguments.length?d-a:void 0===b||null===b?0:Math.max(0,Math.min(b,d-a)),void 0===c&&(c=[]);var e=c.length-b,f=(g=this._values).splice.apply(g,[a,b].concat(c));return this.updateLength(d,e),this.notifySplice(a,f,c),f;var g},c.prototype.notifyChildUpdate=function(a,b){this.notifyChanged(),this.changeEvent.emit({object:this,type:"update",index:a,oldValue:b})},c.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}))},c.prototype.notifyChanged=function(){this.dependencyState.markStale(),this.dependencyState.markReady(!0)},c.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)},c.prototype.clear=function(){return this.splice(0)},c.prototype.replace=function(a){return this.spliceWithArray(0,this._values.length,a)},c.prototype.values=function(){return this.dependencyState.notifyObserved(),this._values.slice()},c.prototype.toJSON=function(){return this.dependencyState.notifyObserved(),this._values.slice()},c.prototype.clone=function(){return this.dependencyState.notifyObserved(),new c(this._values)},c.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},c.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)},c.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},c.prototype.pop=function(){return this.sideEffectWarning("pop"),this.splice(Math.max(this._values.length-1,0),1)[0]},c.prototype.shift=function(){return this.sideEffectWarning("shift"),this.splice(0,1)[0]},c.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},c.prototype.reverse=function(){return this.sideEffectWarning("reverse"),this.replace(this._values.reverse())},c.prototype.sort=function(a){return this.sideEffectWarning("sort"),this.replace(this._values.sort.apply(this._values,arguments))},c.prototype.remove=function(a){this.sideEffectWarning("remove");var b=this._values.indexOf(a);return b>-1?(this.splice(b,1),!0):!1},c.prototype.toString=function(){return this.wrapReadFunction("toString",arguments)},c.prototype.toLocaleString=function(){return this.wrapReadFunction("toLocaleString",arguments)},c.prototype.concat=function(){return this.wrapReadFunction("concat",arguments)},c.prototype.join=function(a){return this.wrapReadFunction("join",arguments)},c.prototype.slice=function(a,b){return this.wrapReadFunction("slice",arguments)},c.prototype.indexOf=function(a,b){return this.wrapReadFunction("indexOf",arguments)},c.prototype.lastIndexOf=function(a,b){return this.wrapReadFunction("lastIndexOf",arguments)},c.prototype.every=function(a,b){return this.wrapReadFunction("every",arguments)},c.prototype.some=function(a,b){return this.wrapReadFunction("some",arguments)},c.prototype.forEach=function(a,b){return this.wrapReadFunction("forEach",arguments)},c.prototype.map=function(a,b){return this.wrapReadFunction("map",arguments)},c.prototype.filter=function(a,b){return this.wrapReadFunction("filter",arguments)},c.prototype.reduce=function(a,b){return this.wrapReadFunction("reduce",arguments)},c.prototype.reduceRight=function(a,b){return this.wrapReadFunction("reduceRight",arguments)},c.prototype.wrapReadFunction=function(a,b){var d=Array.prototype[a];return(c.prototype[a]=function(){return this.dependencyState.notifyObserved(),d.apply(this._values,arguments)}).apply(this,b)},c.prototype.sideEffectWarning=function(b){a.mobservableStatic.debugLevel>0&&j.trackingStack.length>0&&d("[Mobservable.Array] The method array."+b+" should probably not be used inside observable functions since it has side-effects")},c.createArrayBufferItem=function(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(c.prototype,""+a,b),b.enumerable=!0,b.configurable=!0,c.ENUMERABLE_PROPS[a]=b},c.reserveArrayBuffer=function(a){for(var b=c.OBSERVABLE_ARRAY_BUFFER_SIZE;a>b;b++)c.createArrayBufferItem(b);c.OBSERVABLE_ARRAY_BUFFER_SIZE=a},c.OBSERVABLE_ARRAY_BUFFER_SIZE=0,c.ENUMERABLE_PROPS=[],c}(k);l.reserveArrayBuffer(1e3);var m=function(){function a(){this.listeners=[]}return a.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)}},a.prototype.on=function(a){var b=this;return this.listeners.push(a),e(function(){var c=b.listeners.indexOf(a);-1!==c&&b.listeners.splice(c,1)})},a.prototype.once=function(a){var b=this.on(function(){b(),a.apply(this,arguments)});return b},a}();a.mobservableStatic.SimpleEventEmitter=m;var n=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.mobservableStatic.ObserverMixin={componentWillMount:function(){var b=this.render;this.render=function(){var c=this;this._watchDisposer&&this._watchDisposer();var d=a.mobservableStatic.watch(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()}},a.mobservableStatic.quickDiff=c,a.mobservableStatic.stackDepth=function(){return j.trackingStack.length}}(mobservable||(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(){return mobservable.mobservableStatic}); | ||
var __extends=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){function b(b,c){return Array.isArray(b)?new l(b):"function"==typeof b?a.mobservableStatic.computed(b,c):a.mobservableStatic.primitive(b)}function c(a,b){if(!b.length)return[a,[]];if(!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]}function d(a){console&&console.warn("[WARNING:mobservable] "+a)}function e(a){var b=!1;return function(){return b?void 0:(b=!0,a.apply(this,arguments))}}a.mobservableStatic=function(a,c){return b(a,c)},a.mobservableStatic.value=b,a.mobservableStatic.primitive=a.mobservableStatic.reference=function(a){return new g(a).createGetterSetter()},a.mobservableStatic.computed=function(a,b){return new h(a,b).createGetterSetter()},a.mobservableStatic.expr=function(a,b){if(0===j.trackingStack.length)throw new Error("mobservable.expr can only be used inside a computed observable. Probably mobservable.computed should be used instead of .expr");return new h(a,b).get()},a.mobservableStatic.array=function(a){return new l(a)},a.mobservableStatic.props=function o(b,o,c){switch(arguments.length){case 0:throw new Error("Not enough arguments");case 1:return a.mobservableStatic.props(b,b);case 2:for(var d in o)a.mobservableStatic.props(b,d,o[d]);break;case 3:var e=Array.isArray(c),f=a.mobservableStatic.value(c,b);Object.defineProperty(b,o,{get:e?function(){return f}:f,set:e?function(a){f.replace(a)}:f,enumerable:!0,configurable:!1})}return b},a.mobservableStatic.observable=function(b,c,d){var e=d?d.value:null;"function"==typeof e?(delete d.value,delete d.writable,d.get=function(){var b=this.key=a.mobservableStatic.computed(e,this);return b},d.set=function(){throw console.trace(),new Error("It is not allowed to reassign observable functions")}):Object.defineProperty(b,c,{configurable:!1,enumberable:!0,get:function(){return a.mobservableStatic.props(this,c,void 0),this[c]},set:function(b){a.mobservableStatic.props(this,c,b)}})},a.mobservableStatic.toPlainValue=function p(a){if(a){if(a instanceof Array)return a.slice();if(a instanceof g)return a.get();if("function"==typeof a&&a.impl){if(a.impl instanceof g)return a();if(a.impl instanceof l)return a().slice()}else if("object"==typeof a){var b={};for(var c in a)b[c]=p(a[c]);return b}}return a},a.mobservableStatic.observeProperty=function(b,c,f,i){if(void 0===i&&(i=!1),!b||!c||void 0===b[c])throw new Error("Object '"+b+"' has no property '"+c+"'.");if(!f||"function"!=typeof f)throw new Error("Third argument to mobservable.observeProperty should be a function");var j=b[c];if(j instanceof g||j instanceof l)return j.observe(f,i);if(j.impl&&(j.impl instanceof g||j instanceof l))return j.impl.observe(f,i);var k=new h(function(){return b[c]},b),m=k.observe(f,i);return a.mobservableStatic.debugLevel&&0===k.dependencyState.observing.length&&d("mobservable.observeProperty: property '"+c+"' of '"+b+" doesn't seem to be observable. Did you define it as observable?"),e(function(){m(),k.dependencyState.dispose()})},a.mobservableStatic.watch=function q(a,b){var q=new i(a,b);return[q.value,function(){return q.dispose()}]},a.mobservableStatic.batch=function(a){return n.batch(a)},a.mobservableStatic.debugLevel=0;var f,g=function(){function a(a){this._value=a,this.changeEvent=new m,this.dependencyState=new j(this)}return a.prototype.set=function(a){if(a!==this._value){var b=this._value;this.dependencyState.markStale(),this._value=a,this.dependencyState.markReady(!0),this.changeEvent.emit(a,b)}},a.prototype.get=function(){return this.dependencyState.notifyObserved(),this._value},a.prototype.observe=function(a,b){var c=this;void 0===b&&(b=!1),this.dependencyState.setRefCount(1),b&&a(this.get(),void 0);var d=this.changeEvent.on(a);return e(function(){c.dependencyState.setRefCount(-1),d()})},a.prototype.createGetterSetter=function(){var a=this,b=this,c=function(a){return arguments.length>0?void b.set(a):b.get()};return c.observe=function(b,c){return a.observe(b,c)},c.impl=this,c.toString=function(){return a.toString()},c},a.prototype.toString=function(){return"Observable["+this._value+"]"},a}(),h=function(b){function c(a,c){if(b.call(this,void 0),this.func=a,this.scope=c,this.isComputing=!1,this.hasError=!1,"function"!=typeof a)throw new Error("ComputedObservable requires a function")}return __extends(c,b),c.prototype.get=function(){if(this.isComputing)throw new Error("Cycle detected");var b=this.dependencyState;if(b.isSleeping?j.trackingStack.length>0?(b.wakeUp(),b.notifyObserved()):this.compute():b.notifyObserved(),b.hasCycle)throw new Error("Cycle detected");if(this.hasError)throw a.mobservableStatic.debugLevel&&(console.trace(),d(this+": rethrowing caught exception to observer: "+this._value+(this._value.cause||""))),this._value;return this._value},c.prototype.set=function(a){throw new Error(this.toString()+": A computed observable does not accept new values!")},c.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},c.prototype.toString=function(){return"ComputedObservable["+this.func.toString()+"]"},c}(g),i=function(){function a(a,b){this.expr=a,this.onInvalidate=b,this.dependencyState=new j(this),this.didEvaluate=!1,this.dependencyState.computeNextState()}return a.prototype.compute=function(){return this.didEvaluate?(this.dispose(),this.onInvalidate()):(this.didEvaluate=!0,this.value=this.expr()),!1},a.prototype.dispose=function(){this.dependencyState.dispose()},a}();!function(a){a[a.STALE=0]="STALE",a[a.PENDING=1]="PENDING",a[a.READY=2]="READY"}(f||(f={}));var j=function(){function b(a){this.owner=a,this.state=f.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 b.prototype.setRefCount=function(a){var b=this.externalRefenceCount+=a;0===b?this.tryToSleep():b===a&&this.wakeUp()},b.prototype.addObserver=function(a){this.observers[this.observers.length]=a},b.prototype.removeObserver=function(a){var b=this.observers,c=b.indexOf(a);-1!==c&&(b.splice(c,1),0===b.length&&this.tryToSleep())},b.prototype.markStale=function(){this.state===f.READY&&(this.state=f.STALE,this.notifyObservers())},b.prototype.markReady=function(a){this.state!==f.READY&&(this.state=f.READY,this.notifyObservers(a))},b.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)},b.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}},b.prototype.wakeUp=function(){this.isSleeping&&this.isComputed&&(this.isSleeping=!1,this.state=f.PENDING,this.computeNextState())},b.prototype.notifyStateChange=function(a,b){var c=this;a.state===f.STALE?1===++this.dependencyStaleCount&&this.markStale():(b&&(this.dependencyChangeCount+=1),0===--this.dependencyStaleCount&&(this.state=f.PENDING,n.schedule(function(){c.dependencyChangeCount>0?c.computeNextState():c.markReady(!1),c.dependencyChangeCount=0})))},b.prototype.computeNextState=function(){this.trackDependencies();var a=this.owner.compute();this.bindDependencies(),this.markReady(a)},b.prototype.trackDependencies=function(){this.prevObserving=this.observing,b.trackingStack[b.trackingStack.length]=[]},b.prototype.bindDependencies=function(){this.observing=b.trackingStack.pop(),this.isComputed&&0===this.observing.length&&a.mobservableStatic.debugLevel>1&&!this.isDisposed&&(console.trace(),d("You have created a function that doesn't observe any values, did you forget to make its dependencies observable?"));var e=c(this.observing,this.prevObserving),f=e[0],g=e[1];this.prevObserving=null;for(var h=0,i=g.length;i>h;h++)g[h].removeObserver(this);this.hasCycle=!1;for(var h=0,i=f.length;i>h;h++)this.isComputed&&f[h].findCycle(this)?(this.hasCycle=!0,this.observing.splice(this.observing.indexOf(f[h]),1),f[h].hasCycle=!0):f[h].addObserver(this)},b.prototype.notifyObserved=function(){var a=b.trackingStack,c=a.length;if(c>0){var d=a[c-1],e=d.length;d[e-1]!==this&&d[e-2]!==this&&(d[e]=this)}},b.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},b.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},b.trackingStack=[],b}(),k=function(){function a(){}return a}();k.prototype=[];var l=function(b){function c(a){b.call(this),Object.defineProperties(this,{dependencyState:{enumerable:!1,value:new j(this)},_values:{enumerable:!1,value:a?a.slice():[]},changeEvent:{enumerable:!1,value:new m}}),a&&a.length&&this.updateLength(0,a.length)}return __extends(c,b),Object.defineProperty(c.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}),c.prototype.updateLength=function(a,b){if(0>b)for(var d=a+b;a>d;d++)delete this[d];else if(b>0){a+b>c.OBSERVABLE_ARRAY_BUFFER_SIZE&&c.reserveArrayBuffer(a+b);for(var d=a,e=a+b;e>d;d++)Object.defineProperty(this,""+d,c.ENUMERABLE_PROPS[d])}},c.prototype.spliceWithArray=function(a,b,c){var d=this._values.length;if(!(void 0!==c&&0!==c.length||0!==b&&0!==d))return[];void 0===a?a=0:a>d?a=d:0>a&&(a=Math.max(0,d+a)),b=1===arguments.length?d-a:void 0===b||null===b?0:Math.max(0,Math.min(b,d-a)),void 0===c&&(c=[]);var e=c.length-b,f=(g=this._values).splice.apply(g,[a,b].concat(c));return this.updateLength(d,e),this.notifySplice(a,f,c),f;var g},c.prototype.notifyChildUpdate=function(a,b){this.notifyChanged(),this.changeEvent.emit({object:this,type:"update",index:a,oldValue:b})},c.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}))},c.prototype.notifyChanged=function(){this.dependencyState.markStale(),this.dependencyState.markReady(!0)},c.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)},c.prototype.clear=function(){return this.splice(0)},c.prototype.replace=function(a){return this.spliceWithArray(0,this._values.length,a)},c.prototype.values=function(){return this.dependencyState.notifyObserved(),this._values.slice()},c.prototype.toJSON=function(){return this.dependencyState.notifyObserved(),this._values.slice()},c.prototype.clone=function(){return this.dependencyState.notifyObserved(),new c(this._values)},c.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},c.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)},c.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},c.prototype.pop=function(){return this.sideEffectWarning("pop"),this.splice(Math.max(this._values.length-1,0),1)[0]},c.prototype.shift=function(){return this.sideEffectWarning("shift"),this.splice(0,1)[0]},c.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},c.prototype.reverse=function(){return this.sideEffectWarning("reverse"),this.replace(this._values.reverse())},c.prototype.sort=function(a){return this.sideEffectWarning("sort"),this.replace(this._values.sort.apply(this._values,arguments))},c.prototype.remove=function(a){this.sideEffectWarning("remove");var b=this._values.indexOf(a);return b>-1?(this.splice(b,1),!0):!1},c.prototype.toString=function(){return this.wrapReadFunction("toString",arguments)},c.prototype.toLocaleString=function(){return this.wrapReadFunction("toLocaleString",arguments)},c.prototype.concat=function(){return this.wrapReadFunction("concat",arguments)},c.prototype.join=function(a){return this.wrapReadFunction("join",arguments)},c.prototype.slice=function(a,b){return this.wrapReadFunction("slice",arguments)},c.prototype.indexOf=function(a,b){return this.wrapReadFunction("indexOf",arguments)},c.prototype.lastIndexOf=function(a,b){return this.wrapReadFunction("lastIndexOf",arguments)},c.prototype.every=function(a,b){return this.wrapReadFunction("every",arguments)},c.prototype.some=function(a,b){return this.wrapReadFunction("some",arguments)},c.prototype.forEach=function(a,b){return this.wrapReadFunction("forEach",arguments)},c.prototype.map=function(a,b){return this.wrapReadFunction("map",arguments)},c.prototype.filter=function(a,b){return this.wrapReadFunction("filter",arguments)},c.prototype.reduce=function(a,b){return this.wrapReadFunction("reduce",arguments)},c.prototype.reduceRight=function(a,b){return this.wrapReadFunction("reduceRight",arguments)},c.prototype.wrapReadFunction=function(a,b){var d=Array.prototype[a];return(c.prototype[a]=function(){return this.dependencyState.notifyObserved(),d.apply(this._values,arguments)}).apply(this,b)},c.prototype.sideEffectWarning=function(b){a.mobservableStatic.debugLevel>0&&j.trackingStack.length>0&&d("[Mobservable.Array] The method array."+b+" should probably not be used inside observable functions since it has side-effects")},c.createArrayBufferItem=function(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(c.prototype,""+a,b),b.enumerable=!0,b.configurable=!0,c.ENUMERABLE_PROPS[a]=b},c.reserveArrayBuffer=function(a){for(var b=c.OBSERVABLE_ARRAY_BUFFER_SIZE;a>b;b++)c.createArrayBufferItem(b);c.OBSERVABLE_ARRAY_BUFFER_SIZE=a},c.OBSERVABLE_ARRAY_BUFFER_SIZE=0,c.ENUMERABLE_PROPS=[],c}(k);l.reserveArrayBuffer(1e3);var m=function(){function a(){this.listeners=[]}return a.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)}},a.prototype.on=function(a){var b=this;return this.listeners.push(a),e(function(){var c=b.listeners.indexOf(a);-1!==c&&b.listeners.splice(c,1)})},a.prototype.once=function(a){var b=this.on(function(){b(),a.apply(this,arguments)});return b},a}();a.mobservableStatic.SimpleEventEmitter=m;var n=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.mobservableStatic.ObserverMixin={componentWillMount:function(){var b=this.render;this.render=function(){var c=this;this._watchDisposer&&this._watchDisposer();var e=a.mobservableStatic.watch(function(){return b.call(c)},function(){c.isMounted()?c.forceUpdate():a.mobservableStatic.debugLevel&&d("Rendering was triggered for unmounted component. Please check the lifecycle of the components")}),f=e[0],g=e[1];return this._watchDisposer=g,f}},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.mobservableStatic.ObservingComponent=function(b){var c=b.componentWillMount,d=b.componentWillUnmount;return b.prototype.componentWillMount=function(){return a.mobservableStatic.ObserverMixin.componentWillMount.apply(this,arguments),c&&c.apply(this,arguments)},b.prototype.componentWillUnmount=function(){return a.mobservableStatic.ObserverMixin.componentWillUnmount.apply(this,arguments),d&&d.apply(this,arguments)},b.prototype.shouldComponentUpdate=a.mobservableStatic.ObserverMixin.shouldComponentUpdate,b},a.mobservableStatic.quickDiff=c,a.mobservableStatic.stackDepth=function(){return j.trackingStack.length}}(mobservable||(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(){return mobservable.mobservableStatic}); |
@@ -19,3 +19,4 @@ /** GENERATED FILE */ | ||
reference<T>(value?:T):Mobservable.IObservableValue<T>; | ||
computed<T>(value:()=>T,scope?):Mobservable.IObservableValue<T>; | ||
computed<T>(func:()=>T,scope?):Mobservable.IObservableValue<T>; | ||
expr<T>(expr:()=>T,scope?):T; | ||
@@ -45,3 +46,5 @@ // create observable properties | ||
componentWillUnmount(); | ||
} | ||
shouldComponentUpdate(nextProps, nextState); | ||
}; | ||
ObservingComponent<T>(componentClass:T):T; | ||
} | ||
@@ -48,0 +51,0 @@ |
@@ -18,3 +18,4 @@ /** | ||
reference<T>(value?:T):Mobservable.IObservableValue<T>; | ||
computed<T>(value:()=>T,scope?):Mobservable.IObservableValue<T>; | ||
computed<T>(func:()=>T,scope?):Mobservable.IObservableValue<T>; | ||
expr<T>(expr:()=>T,scope?):T; | ||
@@ -44,3 +45,5 @@ // create observable properties | ||
componentWillUnmount(); | ||
} | ||
shouldComponentUpdate(nextProps, nextState); | ||
}; | ||
ObservingComponent<T>(componentClass:T):T; | ||
} | ||
@@ -127,2 +130,8 @@ | ||
mobservableStatic.expr = function<T>(expr:()=>void, scope?) { | ||
if (DNode.trackingStack.length === 0) | ||
throw new Error("mobservable.expr can only be used inside a computed observable. Probably mobservable.computed should be used instead of .expr"); | ||
return new ComputedObservable(expr, scope).get(); | ||
} | ||
mobservableStatic.array = function array<T>(values?:T[]): ObservableArray<T> { | ||
@@ -179,9 +188,9 @@ return new ObservableArray(values); | ||
descriptor.get = function() { | ||
mobservableStatic.props(this, key, baseValue); | ||
return this[key]; | ||
} | ||
var observable = this.key = mobservableStatic.computed(baseValue, this); | ||
return observable; | ||
}; | ||
descriptor.set = function () { | ||
console.trace(); | ||
throw new Error("It is not allowed to reassign observable functions"); | ||
} | ||
}; | ||
} else { | ||
@@ -473,3 +482,3 @@ Object.defineProperty(target, key, { | ||
this.tryToSleep(); | ||
else if (rc === delta) | ||
else if (rc === delta) // a.k.a. rc was zero. | ||
this.wakeUp(); | ||
@@ -992,3 +1001,6 @@ } | ||
var[rendering, disposer] = mobservableStatic.watch(() => baseRender.call(this), () => { | ||
this.forceUpdate(); | ||
if (this.isMounted()) | ||
this.forceUpdate(); | ||
else if (mobservableStatic.debugLevel) | ||
warn("Rendering was triggered for unmounted component. Please check the lifecycle of the components"); | ||
}); | ||
@@ -1003,5 +1015,35 @@ this._watchDisposer = disposer; | ||
this._watchDisposer(); | ||
}, | ||
shouldComponentUpdate: function(nextProps, nextState) { | ||
// update on any state changes (as is the default) | ||
if (this.state !== nextState) | ||
return true; | ||
// update if props are shallowly not equal, inspired by PureRenderMixin | ||
var keys = Object.keys(this.props); | ||
var key; | ||
if (keys.length !== Object.keys(nextProps).length) | ||
return true; | ||
for(var i = keys.length -1; i >= 0, key = keys[i]; i--) | ||
if (nextProps[key] !== this.props[key]) | ||
return true; | ||
return false; | ||
} | ||
} | ||
mobservableStatic.ObservingComponent = function(componentClass) { | ||
var baseMount = componentClass.componentWillMount; | ||
var baseUnmount = componentClass.componentWillUnmount; | ||
componentClass.prototype.componentWillMount = function() { | ||
mobservableStatic.ObserverMixin.componentWillMount.apply(this, arguments); | ||
return baseMount && baseMount.apply(this, arguments); | ||
}; | ||
componentClass.prototype.componentWillUnmount = function() { | ||
mobservableStatic.ObserverMixin.componentWillUnmount.apply(this, arguments); | ||
return baseUnmount && baseUnmount.apply(this, arguments); | ||
}; | ||
componentClass.prototype.shouldComponentUpdate = mobservableStatic.ObserverMixin.shouldComponentUpdate; | ||
return componentClass; | ||
}; | ||
/** | ||
@@ -1008,0 +1050,0 @@ * Given a new and an old list, tries to determine which items are added or removed |
{ | ||
"name": "mobservable", | ||
"version": "0.4.3", | ||
"version": "0.5.0", | ||
"description": "Changes are coming! Small library for creating observable properties, arrays and functions", | ||
@@ -28,3 +28,2 @@ "main": "dist/mobservable.js", | ||
"nodeunit-browser-tap": "^0.1.0", | ||
"nscript": "^0.1.5", | ||
"typescript": "^1.5.0-beta" | ||
@@ -31,0 +30,0 @@ }, |
239
README.md
@@ -8,12 +8,23 @@ # MOBservable | ||
MOBservable is light-weight stand-alone observable implementation, that helps you to create reactive data structures, based on the ideas of observables in bigger frameworks like `knockout`, `ember`, but this time without 'strings attached'. | ||
MOBservables allows you to observe primitive values, references, functions and arrays and makes sure that all changes in your data are propagated automatically, atomically and synchronously. | ||
MOBservable is light-weight stand-alone library to create reactive primitives, functions, arrays and objects. | ||
[Blog post: combining React with MOBservable](https://www.mendix.com/tech-blog/making-react-reactive-pursuit-high-performing-easily-maintainable-react-apps/) | ||
Its goal is to make developers happy and productive, by removing boilerplate work such as invalidating derived data or managing event listeners. | ||
It makes sure data changes are automatically, atomically and synchronously propagated through your app without being obtrusive. | ||
MOBservable runs in any ES5 environment but features also some React addons. | ||
It is higly efficient and shines when managing large amounts of complex, cyclic, nested or computed data. | ||
* [Blog post: combining React with MOBservable](https://www.mendix.com/tech-blog/making-react-reactive-pursuit-high-performing-easily-maintainable-react-apps/) | ||
* [Examples](#examples) | ||
* [Design principles](#design-principles) | ||
* [API documentation](#api-documentation) | ||
* [Advanced Tips & Tricks](#advanced-tips--tricks) | ||
# Examples | ||
[Fiddle demo: MOBservable + JQuery](http://jsfiddle.net/mweststrate/vxn7qgdw) | ||
[Fiddle demo: MOBservable + React](https://jsfiddle.net/mweststrate/46vL0phw) | ||
* [Fiddle demo: MOBservable + React: simple timer](https://jsfiddle.net/mweststrate/wgbe4guu/) | ||
* [Fiddle demo: MOBservable + React: shop](https://jsfiddle.net/mweststrate/46vL0phw) | ||
* [Fiddle demo: MOBservable + JQuery: shop](http://jsfiddle.net/mweststrate/vxn7qgdw) | ||
The source of all demo's can also be found in the [example](/example) folder. | ||
## Example: Observable values and functions | ||
@@ -69,3 +80,3 @@ | ||
// (computed) properties can be accessed like any other property: | ||
console.log(jan.fullName); | ||
console.log(jane.fullName); | ||
// prints: "Jan Dôh" | ||
@@ -83,3 +94,3 @@ | ||
`mobservable` provides an observable array implementation, which is fully ES5 compliant, | ||
`mobservable` provides an observable array implementation (as ES7 polyfill), which is fully ES5 compliant, | ||
but which will notify dependent computations upon each change. | ||
@@ -149,34 +160,71 @@ | ||
## Example: ObserverMixin for react components | ||
## Example: ObservingComponent for react components | ||
MOBservable ships with a mixin that can be used to subscribe React components to observables automatically, so that model changes are processed transparently. | ||
The full JSX example can be found in this [fjsiddle]() | ||
MOBservable ships with a mixin and class decorator that can be used to subscribe React components to observables automatically. | ||
The full JSX example can be found in this [fjsiddle](https://jsfiddle.net/mweststrate/wgbe4guu/) | ||
```javascript | ||
function Article(name, price) { | ||
mobservable.props(this, { | ||
name: name, | ||
price: price | ||
}); | ||
var store = {}; | ||
// add observable properties to the store | ||
mobservable.props(store, { | ||
timer: 0 // this could be an array, object, function as well.. | ||
}); | ||
// of course, this could be put flux-style in dispatchable actions, but this is just to demo Model -> View | ||
function resetTimer() { | ||
store.timer = 0; | ||
} | ||
var ArticleView = React.createClass({ | ||
mixins: [mobservable.ObserverMixin], | ||
setInterval(function() { | ||
store.timer += 1; | ||
}, 1000); | ||
// This component is actually an observer of all store properties that are accessed during the last rendering | ||
// so there is no need to declare any data use, nor is there (seemingly) any state in this component | ||
// the combination of mobservable.props and ObservingComponent does all the magic for us. | ||
// UI updates are nowhere forced, but all views (un)subscribe to their data automatically | ||
var TimerView = mobservable.ObservingComponent(React.createClass({ | ||
render: function() { | ||
return (<li> | ||
<span>{this.props.article.name}</span> | ||
<span className="price">{this.props.article.price}</span> | ||
</li>); | ||
return (<span>Seconds passed: {this.props.store.timer}</span>); | ||
} | ||
})); | ||
var TimerApp = React.createClass({ | ||
render: function() { | ||
var now = new Date(); // just to demonstrate that TimerView updates independently of TimerApp | ||
return (<div> | ||
<div>Started rendering at: {now.toString()}</div> | ||
<TimerView {...this.props} /> | ||
<br/><button onClick={resetTimer}>Reset timer</button> | ||
</div>); | ||
} | ||
}); | ||
var book = new Article("Orthodoxy, G.K. Chesterton", 19.95); | ||
React.render(<ArticleView article={book} />, document.body); | ||
book.price = 15.95; // Triggers automatically a re-render of the ArticleView | ||
// pass in the store to the component tree (you could also access it directly through global vars, whatever suits your style) | ||
React.render(<TimerApp store={store} />, document.body); | ||
``` | ||
# Processing observables | ||
# Design principles | ||
## Principles | ||
MOBservable is designed with the following principles in mind. | ||
- The Model, View (and Contorller) of an app should be separated. | ||
Views should be loosely coupled to the UI, so that UI refactorings do not require changes of the data model. | ||
It should be possible to describe views on the data model as naturally as possible, as-if data does not change over time. | ||
- Derived data should be re-calculated automatically and efficiently. | ||
It is the responsibility of MOBservable to prevent that views ever become stale. | ||
- MOBservable is unobtrusive and doesnt place any constraints on how you build or work with data structures. | ||
Inheritance, classes, cyclic data structures, or instance methods...? The library does not pose any restrictions on your data. | ||
- Data should be mutable as this is close to the natural mental model of most kinds of data. | ||
<small>(despite some nice properties of immutable data, mutable data is easier to inspect, read, grok and especially more natural to program against. | ||
`markRead(email) { email.isRead = true; }` is more convenient to write than `markRead(email) { return { ...email, isRead : true }; }` or `markRead(email) { model.set('email', 'isRead', true); }`. | ||
Especially when email is somewhere deep in your model tree)</small> | ||
- Subscriptions should be a breeze to manage, and managed automatically wherever possible. | ||
- MOBservable is only about the model data, not about querying, back-end communication etc (although observers are really useful there as well). | ||
## Behavior | ||
Observable values, arrays and functions created by `mobservable` possess the following characteristics: | ||
@@ -187,7 +235,7 @@ | ||
* _real time dependency detection_. Computed values only depend on values actually used in the last computation, for example in this `a -> b > 5 ? c : b` the variable `c` will only cause a re-evaluation of a if `b` > 5. | ||
* _lazy_. Computed values will only be evaluated if they are actually being observed. So make sure computed functions are pure and side effect free; the library might not evaluate the expression as often as you thought it would. | ||
* _lazy_. Computed values will only be evaluated if they are actually being observed. So make sure computed functions are pure and side effect free; the library might not evaluate expressions as often as you thought it would. | ||
* _cycle detection_. Cycles in computes, like in `a -> 2 * b; b -> 2 * a;` will be deteced automatically. | ||
* _error handling_. Exceptions that are raised during computations are propagated to consumers. | ||
# API | ||
# API Documentation | ||
@@ -228,3 +276,2 @@ [Typescript typings](https://github.com/mweststrate/MOBservable/blob/master/mobservable.d.ts) | ||
### mobservable.reference | ||
@@ -269,11 +316,23 @@ | ||
### mobservable.expr | ||
`mobservable.expr<T>(expr:()=>T,scope?):T;` | ||
This function is simply sugar for `mobservable.computed(expr, scope)();`. | ||
`expr` can be used to split up and improve the performance of expensive computations, | ||
as described in this [section](#use-nested-observables-in-expensive-computations). | ||
### mobservable.array | ||
`mobservable.array<T>(values? : T[]) : IObservableArray<T>` | ||
**Note: ES5 environments only** | ||
Constructs an array like, observable structure. An observable array is a thin abstraction over native arrays that adds observable properties. | ||
The most notable difference between built-in arrays is that these arrays cannot be sparse, that is, values assigned to an index larger than `length` are considered out-of-bounds and not oberved (nor any other property that is assigned to a non-numeric index). | ||
Constructs an array like, observable structure. An observable array is a thin abstraction over native arrays and adds observable properties. | ||
The most notable difference between built-in arrays is that these arrays cannot be sparse, that is, | ||
values assigned to an index larger than `length` are considered out-of-bounds and not oberved | ||
(nor any other property that is assigned to a non-numeric pr negative index). | ||
Furthermore, `Array.isArray(observableArray)` and `typeof observableArray === "array"` will yield `false` for observable arrays, but `observableArray instanceof Array` will return `true`. | ||
Furthermore, `Array.isArray(observableArray)` and `typeof observableArray === "array"` will yield `false` for observable arrays, | ||
but `observableArray instanceof Array` will return `true`. | ||
@@ -300,3 +359,3 @@ ```javascript | ||
* `replace(newItems:T[])` Replaces all existing entries in the array with new ones. | ||
* `values(): T[]` Returns a shallow clone of the array, similar to `.slice` | ||
* `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 | ||
@@ -316,3 +375,3 @@ * `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. | ||
Creates observable properties on the given `target` object. This function uses `mobservable.value` internally to create observables. | ||
Creating properties has as advantage that they are more convenient to use. See also [props or variables](#value_versus_props). | ||
Creating properties has as advantage that they are more convenient to use. See also [value versus props](#value-versus-props). | ||
The original `target`, with the added properties, is returned by this function. Functions used to created computed observables will automatically | ||
@@ -334,3 +393,4 @@ be bound to the correct `this`. | ||
Note that observables created by `mobservable.props` do not expose an `.observe` method, to observe properties, see [`mobservable.observeProperty`](#mobservable_observeproperty) | ||
Note that observables created by `mobservable.props` do not expose an `.observe` method, | ||
to observe properties, see [`mobservable.observeProperty`](#mobservableobserveproperty) | ||
@@ -405,3 +465,3 @@ Other forms in which this function can be called: | ||
This is useful if you want to apply a bunch of different updates throughout your model before needing the updated computed values, | ||
for example while refreshing a data from the database. | ||
for example while refreshing a data from the database. In practice, you wil probably never need `.batch`, since observables usually update wickedly fast. | ||
@@ -441,9 +501,23 @@ ```javascript | ||
so that the component is re-rendered each time an observable has changed. | ||
In general, this mixin combines very well with the [React PureRender mixin](https://facebook.github.io/react/docs/pure-render-mixin.html) if observable objects or arrays are passed into the component. | ||
This mixin also prevents re-renderings when the *props* of the component have only shallowly changed | ||
(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. | ||
See the [above example](#example_observermixin_for_react_components) or the [JSFiddle demo: MOBservable + React](https://jsfiddle.net/mweststrate/46vL0phw) | ||
See the [above example](#example_observingcomponent_for_react_components) or the [JSFiddle demo: MOBservable + React](https://jsfiddle.net/mweststrate/46vL0phw) | ||
For an extensive explanation, read [combing React with MOBservable](https://www.mendix.com/tech-blog/making-react-reactive-pursuit-high-performing-easily-maintainable-react-apps/) | ||
### mobservable.ObservingComponent | ||
`mobservable.ObservingComponent(clazz:ReactComponentClass):ReactComponentClass` | ||
If you want to create a React component based on ES6 where mixins are not supported, | ||
you can use the `ObservingComponent` function to wrap around your React `createClass` call (instead of using the mixin `ObserverMixin`): | ||
```javascript | ||
// TODO: change to class | ||
var myComponent = mobservable.ObservingComponent(React.createClass({ | ||
// widget specification without mixins | ||
}); | ||
``` | ||
### mobservable.debugLevel | ||
@@ -454,4 +528,5 @@ | ||
### mobservable.SimpleEventEmitter | ||
Utility class for managing an event. Its methods are: | ||
Utility class for managing an event. Its instance methods are: | ||
* `new mobservable.SimpleEventEmitter()`. Creates a new `SimpleEventEmitter` | ||
* `emit(...data : any[])`. Invokes all registered listeners with the given arguments | ||
@@ -461,4 +536,8 @@ * `on(listener:(...data : any[]) => void) : () => void`. Registers a new callback that will be invoked on each `emit`. Returns a method that can be used to unsubscribe the listener. | ||
# Tips & tricks | ||
# Advanced Tips & Tricks | ||
## How to create lazy values? | ||
All computed values are lazy and only evaluated upon first observation (or when their value is explicitly getted) | ||
## Use local variables in computations | ||
@@ -491,2 +570,49 @@ | ||
## Use nested observables in expensive computations | ||
It is perfectly fine to create computed observables inside computed observables. | ||
This is a useful pattern if you have an expensive computation that depends on a condition check that is fired often, but not changed often. | ||
For example when your computation contains a cheap treshold check, or when your UI renderig depends on the some selection of the user. | ||
For example: | ||
```javascript | ||
var person; // ... | ||
var total = mobservable(function() { | ||
if (person.age === 42) | ||
doSomeExpensiveComputation(); | ||
else | ||
doSomeOtherExpensiveComputation(); | ||
}); | ||
``` | ||
In the example above, every single time the person's `age` changes, `total` is computed by invoking some expensive computations. | ||
However, if the expression `page.age === 42` was put in a separate observable, | ||
computing the `total` itself could be avoided in many cases because a recomputation would only occur if the value of the complete expression changes. | ||
Yet, you might not want to create separate stand-alone observables for these expressions, | ||
because you don't have a nice place to put them or because it would make the readability of the code worse. | ||
In such cases you can also create an inline observable. | ||
In the following example, the total is only recalculated if the age changes to, or from, 42. Which means that for most other ages, | ||
recomputing the expensive computations can be avoided. | ||
```javascript | ||
var person; // ... | ||
var total = mobservable(function() { | ||
var ageEquals42 = mobservable(function() { return person.age === 42 })(); // create observable and invoke getter | ||
if (ageEquals42) | ||
doSomeExpensiveComputation(); | ||
else | ||
doSomeOtherExpensiveComputation(); | ||
}); | ||
``` | ||
Note that the dangling `()` after the expression is meant to invoke the getter of the just created observable to obtain its value. | ||
For convenience the same statement can also be rewritten using the [expr](#mobservableexpr) function: | ||
```javascript | ||
// ... | ||
var ageEquals42 = mobservable.expr(function() { return person.age === 42 }); | ||
// ... | ||
``` | ||
## Use native array methods | ||
@@ -517,3 +643,3 @@ | ||
// Also fast: | ||
// Faster: | ||
var sum2 = mobservable(function() { | ||
@@ -527,17 +653,22 @@ return numbers.reduce(function(a, b) { // single observable read | ||
Using `mobservable.value` or `mobservable.props` to create observables inside objects might be a matter of taste. | ||
The difference between `obj.amount = mobservable.value(3)` and `mobservable.props(obj, { value: 3 })` | ||
to create observable values inside an object might seem to be a matter of taste. | ||
Here is a small comparison list between the two approaches. | ||
| .value | .props | | ||
| ---- | ---| | ||
| ES3 complient | requires ES 5 | | ||
| explicit getter/setter functions: `obj.amount(2)` | object properties with implicit getter/setter: `obj.amount = 2 ` | | ||
| easy to make mistakes; e.g. `obj.amount = 3` instead of `obj.amount(3)`, or `7 * obj.amount` instead of `7 * obj.amount()` wilt both not achieve the intended behavior | Use property reads / assignments | | ||
| easy to observe: `obj.amount.observe(listener)` | `mobservable.observeProperty(obj,'amount',listener)` | | ||
**.value** | ||
* ES3 compliant | ||
* explicit getter/setter functions: `obj.amount(2)` | ||
* easy to make mistakes in assignments; e.g. `obj.amount = 3` instead of `obj.amount(3)`, or `7 * obj.amount` instead of `7 * obj.amount()` | ||
* easy to manually observe: `obj.amount.observe(listener)` | ||
**.props** | ||
* Requires ES5 | ||
* object properties with implicit getter/setter: `obj.amount = 2` | ||
* more natural to write / read values, syntactically you won't notice they are observable | ||
* harder to manually observe: `mobservable.observeProperty(obj,'amount',listener)` | ||
## `.reference` versus `.array` | ||
Do *not* confuse `mobservable.primitive([])` (or `mobservable([])`) with `mobservable.array([])`, | ||
the first creates an observable reference to an array, but does not observe its contents. | ||
The later observes the contents from the array you pass into it. | ||
Do *not* confuse `mobservable.reference([])` / `mobservable.primitive([])` with `mobservable([])` / `mobservable.array([])`, | ||
the first two create a observable reference to an array, but does not observe its contents. | ||
The later two observe the content of the array you passed into it, which is probably what you inteded. |
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
139475
8
1991
657