observable-slim
Advanced tools
Comparing version 0.0.8 to 0.0.9
/* | ||
* Observable Slim | ||
* Version 0.0.8 | ||
* Version 0.0.9 | ||
* https://github.com/elliotnb/observable-slim | ||
@@ -15,3 +15,3 @@ * | ||
var ObservableSlim = (function() { | ||
paths = []; | ||
// An array that stores all of the observables created through the public create() method below. | ||
@@ -47,4 +47,5 @@ var observables = []; | ||
allows one observable to observe change on any nested/child objects. | ||
originalPath - string, the path of the property in relation to the target on the original observable, | ||
exists for recursion purposes, allows one observable to observe change on any nested/child objects. | ||
originalPath - array of objects, each object having the properties 'target' and 'property' -- target referring to the observed object itself | ||
and property referring to the name of that object in the nested structure. the path of the property in relation to the target | ||
on the original observable, exists for recursion purposes, allows one observable to observe change on any nested/child objects. | ||
@@ -57,4 +58,12 @@ Returns: | ||
var observable = originalObservable || null; | ||
var path = originalPath || ""; | ||
// record the nested path taken to access this object -- if there was no path then we provide the first empty entry | ||
var path = originalPath || [{"target":target,"property":""}]; | ||
paths.push(path); | ||
// in order to accurately report the "previous value" of the "length" property on an Array | ||
// we must use a helper property because intercepting a length change is not always possible as of 8/13/2018 in | ||
// Chrome -- the new `length` value is already set by the time the `set` handler is invoked | ||
if (target instanceof Array) target.__length = target.length; | ||
var changes = []; | ||
@@ -74,4 +83,26 @@ | ||
var _getPath = function(target, property, jsonPointer) { | ||
var fullPath = (path !== "") ? (path + "." + property) : property; | ||
var fullPath = ""; | ||
var lastTarget = null; | ||
// loop over each item in the path and append it to full path | ||
for (var i = 0; i < path.length; i++) { | ||
// if the current object was a member of an array, it's possible that the array was at one point | ||
// mutated and would cause the position of the current object in that array to change. we perform an indexOf | ||
// lookup here to determine the current position of that object in the array before we add it to fullPath | ||
if (lastTarget instanceof Array && !isNaN(path[i].property)) { | ||
path[i].property = lastTarget.indexOf(path[i].target); | ||
} | ||
fullPath = fullPath + "." + path[i].property | ||
lastTarget = path[i].target; | ||
} | ||
// add the current property | ||
fullPath = fullPath + "." + property; | ||
// remove the beginning two dots -- ..foo.bar becomes foo.bar (the first item in the nested chain doesn't have a property name) | ||
fullPath = fullPath.substring(2); | ||
if (jsonPointer === true) fullPath = "/" + fullPath.replace(/\./g, "/"); | ||
@@ -160,4 +191,6 @@ | ||
// have to create a new proxy for it | ||
var newPath = (path !== "") ? (path + "." + property) : property; | ||
// create a shallow copy of the path array -- if we didn't create a shallow copy then all nested objects would share the same path array and the path wouldn't be accurate | ||
var newPath = path.slice(0); | ||
newPath.push({"target":targetProp,"property":property}); | ||
return _create(targetProp, domDelay, observable, newPath); | ||
@@ -225,3 +258,8 @@ } else { | ||
set: function(target, property, value, receiver) { | ||
// if the value we're assigning is an object, then we want to ensure | ||
// that we're assigning the original object, not the proxy, in order to avoid mixing | ||
// the actual targets and proxies -- creates issues with path logging if we don't do this | ||
if (value.__isProxy) value = value.__getTarget; | ||
// was this change an original change or was it a change that was re-triggered below | ||
@@ -237,4 +275,10 @@ var originalChange = true; | ||
// only record a change if the new value differs from the old one OR if this proxy was not the original proxy to receive the change | ||
if (targetProp !== value || originalChange === false) { | ||
// Only record this change if: | ||
// 1. the new value differs from the old one | ||
// 2. OR if this proxy was not the original proxy to receive the change | ||
// 3. OR the modified target is an array and the modified property is "length" and our helper property __length indicates that the array length has changed | ||
// | ||
// Regarding #3 above: mutations of arrays via .push or .splice actually modify the .length before the set handler is invoked | ||
// so in order to accurately report the correct previousValue for the .length, we have to use a helper property. | ||
if (targetProp !== value || originalChange === false || (property === "length" && target instanceof Array && target.__length !== value)) { | ||
@@ -260,2 +304,9 @@ var foundObservable = true; | ||
}); | ||
// mutations of arrays via .push or .splice actually modify the .length before the set handler is invoked | ||
// so in order to accurately report the correct previousValue for the .length, we have to use a helper property. | ||
if (property === "length" && target instanceof Array && target.__length !== value) { | ||
changes[changes.length-1].previousValue = target.__length; | ||
target.__length = value; | ||
} | ||
@@ -262,0 +313,0 @@ // !!IMPORTANT!! if this proxy was the first proxy to receive the change, then we need to go check and see |
@@ -1,1 +0,1 @@ | ||
var ObservableSlim=function(){var u=[],d=[],x=[],P=null,f=function(e,l,r,n){var v=r||null,i=n||"",g=[],h=function(e,r,n){var t=""!==i?i+"."+r:r;return!0===n&&(t="/"+t.replace(/\./g,"/")),t},b=function(n){if(!0!==v.paused)if(!0===l)setTimeout(function(){if(n===g.length){var e=g.slice(0);g=[];for(var r=0;r<v.observers.length;r++)v.observers[r](e)}},10);else{var e=g.slice(0);g=[];for(var r=0;r<v.observers.length;r++)v.observers[r](e)}},y=new Proxy(e,{get:function(e,r){if("__getTarget"===r)return e;if("__isProxy"===r)return!0;if("__getParent"===r)return function(e){if(void 0===e)e=1;var r,n=h(0,"__getParent").split(".");return n.splice(-(e+1),e+1),r=v.parentProxy,n.join(".").split(".").reduce(function(e,r){return e?e[r]:void 0},r||self)};var n=e[r];if(!(n instanceof Object&&null!==n&&e.hasOwnProperty(r)))return n;!0===n.__isProxy&&(n=n.__getTarget);for(var t=-1,o=v.targets,a=0,s=o.length;a<s;a++)if(n===o[a]){t=a;break}return-1<t?v.proxies[t]:f(n,l,v,""!==i?i+"."+r:r)},deleteProperty:function(e,r){var n=!0;P===y&&(n=!1,P=null);var t=Object.assign({},e);if(g.push({type:"delete",target:e,property:r,newValue:null,previousValue:t[r],currentPath:h(0,r),jsonPointer:h(0,r,!0),proxy:y}),!0===n){for(var o=0,a=d.length;o<a&&e!==d[o];o++);for(var s=x[o],l=s.length;l--;)s[l].proxy!==y&&(P=s[l].proxy,delete s[l].proxy[r]);v.changesPaused||delete e[r]}return b(g.length),!0},set:function(t,e,r,n){var o=!0;P===y&&(o=!1,P=null);var a=t[e];if(a!==r||!1===o){var s=!0,l=typeof a,i="update";if("undefined"===l&&(i="add"),g.push({type:i,target:t,property:e,newValue:r,previousValue:n[e],currentPath:h(0,e),jsonPointer:h(0,e,!0),proxy:y}),!0===o){for(var u=0,f=d.length;u<f&&t!==d[u];u++);if(s=u<f){var p=x[u],c=0;for(f=p.length;c<f;c++)p[c].proxy!==y&&(P=p[c].proxy,p[c].proxy[e]=r);setTimeout(function(){if("object"===l&&null!==a){for(var e=Object.keys(t),r=0,n=e.length;r<n;r++)if(t[e[r]]===a)return;!function e(r){for(var n=Object.keys(r),t=0,o=n.length;t<o;t++){var a=r[n[t]];a instanceof Object&&null!==a&&e(a)}var s=-1;for(t=0,o=d.length;t<o;t++)if(r===d[t]){s=t;break}if(-1<s){for(var l=x[s],i=l.length;i--;)if(v===l[i].observable){l.splice(i,1);break}0==l.length&&(x.splice(s,1),d.splice(s,1))}}(a)}},1e4)}v.changesPaused||(t[e]=r)}s&&b(g.length)}return!0}});null===v?(v={parentTarget:e,domDelay:l,parentProxy:y,observers:[],targets:[e],proxies:[y],paused:!1,path:i,changesPaused:!1},u.push(v)):(v.targets.push(e),v.proxies.push(y));for(var t={target:e,proxy:y,observable:v},o=-1,a=0,s=d.length;a<s;a++)if(e===d[a]){o=a;break}return-1<o?x[o].push(t):(d.push(e),x.push([t]),o=d.length-1),y};return{create:function(e,r,n){if(!0===e.__isProxy)e=e.__getTarget;var t=f(e,r);return"function"==typeof n&&this.observe(t,n),function e(r){for(var n=r.__getTarget,t=Object.keys(n),o=0,a=t.length;o<a;o++){var s=t[o];n[s]instanceof Object&&null!==n[s]&&e(r[s])}}(t),t},observe:function(e,r){for(var n=u.length;n--;)if(u[n].parentProxy===e){u[n].observers.push(r);break}},pause:function(e){for(var r=u.length,n=!1;r--;)if(u[r].parentProxy===e){n=u[r].paused=!0;break}if(0==n)throw new Error("ObseravableSlim could not pause observable -- matching proxy not found.")},resume:function(e){for(var r=u.length,n=!1;r--;)if(u[r].parentProxy===e){n=!(u[r].paused=!1);break}if(0==n)throw new Error("ObseravableSlim could not resume observable -- matching proxy not found.")},pauseChanges:function(e){for(var r=u.length,n=!1;r--;)if(u[r].parentProxy===e){n=u[r].changesPaused=!0;break}if(0==n)throw new Error("ObseravableSlim could not pause changes on observable -- matching proxy not found.")},resumeChanges:function(e){for(var r=u.length,n=!1;r--;)if(u[r].parentProxy===e){n=!(u[r].changesPaused=!1);break}if(0==n)throw new Error("ObseravableSlim could not resume changes on observable -- matching proxy not found.")},remove:function(e){for(var r=null,n=!1,t=u.length;t--;)if(u[t].parentProxy===e){r=u[t],n=!0;break}for(var o=x.length;o--;)for(var a=x[o].length;a--;)x[o][a].observable===r&&(x[o].splice(a,1),0===x[o].length&&(x.splice(o,1),d.splice(o,1)));!0===n&&u.splice(t,1)}}}();try{module.exports=ObservableSlim}catch(e){} | ||
var ObservableSlim=function(){paths=[];var l=[],d=[],_=[],x=null,f=function(e,i,r,t){var h=r||null,u=t||[{target:e,property:""}];paths.push(u),e instanceof Array&&(e.__length=e.length);var c=[],v=function(e,r,t){for(var n="",a=null,o=0;o<u.length;o++)a instanceof Array&&!isNaN(u[o].property)&&(u[o].property=a.indexOf(u[o].target)),n=n+"."+u[o].property,a=u[o].target;return n=(n=n+"."+r).substring(2),!0===t&&(n="/"+n.replace(/\./g,"/")),n},b=function(t){if(!0!==h.paused)if(!0===i)setTimeout(function(){if(t===c.length){var e=c.slice(0);c=[];for(var r=0;r<h.observers.length;r++)h.observers[r](e)}},10);else{var e=c.slice(0);c=[];for(var r=0;r<h.observers.length;r++)h.observers[r](e)}},y=new Proxy(e,{get:function(e,r){if("__getTarget"===r)return e;if("__isProxy"===r)return!0;if("__getParent"===r)return function(e){if(void 0===e)e=1;var r,t=v(0,"__getParent").split(".");return t.splice(-(e+1),e+1),r=h.parentProxy,t.join(".").split(".").reduce(function(e,r){return e?e[r]:void 0},r||self)};var t=e[r];if(t instanceof Object&&null!==t&&e.hasOwnProperty(r)){!0===t.__isProxy&&(t=t.__getTarget);for(var n=-1,a=h.targets,o=0,s=a.length;o<s;o++)if(t===a[o]){n=o;break}if(-1<n)return h.proxies[n];var l=u.slice(0);return l.push({target:t,property:r}),f(t,i,h,l)}return t},deleteProperty:function(e,r){var t=!0;x===y&&(t=!1,x=null);var n=Object.assign({},e);if(c.push({type:"delete",target:e,property:r,newValue:null,previousValue:n[r],currentPath:v(0,r),jsonPointer:v(0,r,!0),proxy:y}),!0===t){for(var a=0,o=d.length;a<o&&e!==d[a];a++);for(var s=_[a],l=s.length;l--;)s[l].proxy!==y&&(x=s[l].proxy,delete s[l].proxy[r]);h.changesPaused||delete e[r]}return b(c.length),!0},set:function(n,e,r,t){r.__isProxy&&(r=r.__getTarget);var a=!0;x===y&&(a=!1,x=null);var o=n[e];if(o!==r||!1===a||"length"===e&&n instanceof Array&&n.__length!==r){var s=!0,l=typeof o,i="update";if("undefined"===l&&(i="add"),c.push({type:i,target:n,property:e,newValue:r,previousValue:t[e],currentPath:v(0,e),jsonPointer:v(0,e,!0),proxy:y}),"length"===e&&n instanceof Array&&n.__length!==r&&(c[c.length-1].previousValue=n.__length,n.__length=r),!0===a){for(var u=0,f=d.length;u<f&&n!==d[u];u++);if(s=u<f){var p=_[u],g=0;for(f=p.length;g<f;g++)p[g].proxy!==y&&(x=p[g].proxy,p[g].proxy[e]=r);setTimeout(function(){if("object"===l&&null!==o){for(var e=Object.keys(n),r=0,t=e.length;r<t;r++)if(n[e[r]]===o)return;!function e(r){for(var t=Object.keys(r),n=0,a=t.length;n<a;n++){var o=r[t[n]];o instanceof Object&&null!==o&&e(o)}var s=-1;for(n=0,a=d.length;n<a;n++)if(r===d[n]){s=n;break}if(-1<s){for(var l=_[s],i=l.length;i--;)if(h===l[i].observable){l.splice(i,1);break}0==l.length&&(_.splice(s,1),d.splice(s,1))}}(o)}},1e4)}h.changesPaused||(n[e]=r)}s&&b(c.length)}return!0}});null===h?(h={parentTarget:e,domDelay:i,parentProxy:y,observers:[],targets:[e],proxies:[y],paused:!1,path:u,changesPaused:!1},l.push(h)):(h.targets.push(e),h.proxies.push(y));for(var n={target:e,proxy:y,observable:h},a=-1,o=0,s=d.length;o<s;o++)if(e===d[o]){a=o;break}return-1<a?_[a].push(n):(d.push(e),_.push([n]),a=d.length-1),y};return{create:function(e,r,t){if(!0===e.__isProxy)e=e.__getTarget;var n=f(e,r);return"function"==typeof t&&this.observe(n,t),function e(r){for(var t=r.__getTarget,n=Object.keys(t),a=0,o=n.length;a<o;a++){var s=n[a];t[s]instanceof Object&&null!==t[s]&&e(r[s])}}(n),n},observe:function(e,r){for(var t=l.length;t--;)if(l[t].parentProxy===e){l[t].observers.push(r);break}},pause:function(e){for(var r=l.length,t=!1;r--;)if(l[r].parentProxy===e){t=l[r].paused=!0;break}if(0==t)throw new Error("ObseravableSlim could not pause observable -- matching proxy not found.")},resume:function(e){for(var r=l.length,t=!1;r--;)if(l[r].parentProxy===e){t=!(l[r].paused=!1);break}if(0==t)throw new Error("ObseravableSlim could not resume observable -- matching proxy not found.")},pauseChanges:function(e){for(var r=l.length,t=!1;r--;)if(l[r].parentProxy===e){t=l[r].changesPaused=!0;break}if(0==t)throw new Error("ObseravableSlim could not pause changes on observable -- matching proxy not found.")},resumeChanges:function(e){for(var r=l.length,t=!1;r--;)if(l[r].parentProxy===e){t=!(l[r].changesPaused=!1);break}if(0==t)throw new Error("ObseravableSlim could not resume changes on observable -- matching proxy not found.")},remove:function(e){for(var r=null,t=!1,n=l.length;n--;)if(l[n].parentProxy===e){r=l[n],t=!0;break}for(var a=_.length;a--;)for(var o=_[a].length;o--;)_[a][o].observable===r&&(_[a].splice(o,1),0===_[a].length&&(_.splice(a,1),d.splice(a,1)));!0===t&&l.splice(n,1)}}}();try{module.exports=ObservableSlim}catch(e){} |
{ | ||
"name": "observable-slim", | ||
"description": "Observable Slim is a singleton that utilizes ES6 Proxies to observe changes made to an object and any nested children of that object. It is intended to assist with state management and one-way data binding.", | ||
"version": "0.0.8", | ||
"version": "0.0.9", | ||
"main": "observable-slim.js", | ||
@@ -6,0 +6,0 @@ "devDependencies": { |
@@ -6,3 +6,3 @@ [![Build Status](https://travis-ci.org/ElliotNB/observable-slim.svg?branch=master)](https://travis-ci.org/ElliotNB/observable-slim) [![Coverage Status](https://coveralls.io/repos/github/ElliotNB/observable-slim/badge.svg)](https://coveralls.io/github/ElliotNB/observable-slim) | ||
Version 0.0.8 | ||
Version 0.0.9 | ||
@@ -9,0 +9,0 @@ Licensed under the MIT license: |
@@ -160,7 +160,17 @@ var chai = require('chai'); | ||
var test = {"arr":[]}; | ||
var change = 0; | ||
var p = ObservableSlim.create(test, false, function(changes) { | ||
expect(changes[0].type).to.equal("add"); | ||
expect(changes[0].newValue).to.equal("hello world"); | ||
expect(changes[0].currentPath).to.equal("arr.0"); | ||
expect(changes[0].property).to.equal("0"); | ||
if (change === 0) { | ||
expect(changes[0].type).to.equal("add"); | ||
expect(changes[0].newValue).to.equal("hello world"); | ||
expect(changes[0].currentPath).to.equal("arr.0"); | ||
expect(changes[0].property).to.equal("0"); | ||
} else if (change === 1) { | ||
expect(changes[0].type).to.equal("update"); | ||
expect(changes[0].currentPath).to.equal("arr.length"); | ||
expect(changes[0].property).to.equal("length"); | ||
expect(changes[0].previousValue).to.equal(0); | ||
expect(changes[0].newValue).to.equal(1); | ||
} | ||
change++; | ||
}); | ||
@@ -278,3 +288,3 @@ | ||
} | ||
change++ | ||
change++; | ||
}); | ||
@@ -345,3 +355,48 @@ | ||
it('17. Delete a property (not supported with ES5 polyfill).', () => { | ||
it('17. currentPath is updated correctly when the position of an Object in an Array changes.', () => { | ||
var change = 0; | ||
var test = [{},{"foo":"test"}]; | ||
var p = ObservableSlim.create(test, false, function(changes) { | ||
// the change events differ slightly when using the ES5 Proxy polyfill, so we skip that part of the validation | ||
// when the proxy is in use | ||
if (global.Proxy === global.NativeProxy) { | ||
if (change === 0) { | ||
expect(changes[0].type).to.equal("update"); | ||
expect(changes[0].property).to.equal("0"); | ||
expect(changes[0].currentPath).to.equal("0"); | ||
} else if (change === 1) { | ||
expect(changes[0].type).to.equal("delete"); | ||
expect(changes[0].property).to.equal("1"); | ||
expect(changes[0].currentPath).to.equal("1"); | ||
} else if (change === 2) { | ||
expect(changes[0].type).to.equal("update"); | ||
expect(changes[0].property).to.equal("length"); | ||
expect(changes[0].newValue).to.equal(1); | ||
expect(changes[0].previousValue).to.equal(2); | ||
expect(changes[0].currentPath).to.equal("length"); | ||
} else if (change === 3) { | ||
expect(changes[0].type).to.equal("update"); | ||
expect(changes[0].property).to.equal("foo"); | ||
expect(changes[0].newValue).to.equal("bar"); | ||
expect(changes[0].previousValue).to.equal("test"); | ||
expect(changes[0].currentPath).to.equal("0.foo"); | ||
} | ||
} | ||
change++; | ||
}); | ||
p.splice(0, 1); | ||
p[0].foo = "bar"; | ||
expect(test.length).to.equal(1); | ||
expect(test[0].foo).to.equal("bar"); | ||
}); | ||
it('18. Delete a property (not supported with ES5 polyfill).', () => { | ||
if (global.Proxy === global.NativeProxy) { | ||
@@ -361,7 +416,7 @@ ObservableSlim.create(test, function(changes) { | ||
it('18. __isProxy check', () => { | ||
it('19. __isProxy check', () => { | ||
expect(p.__isProxy).to.be.equal(true); | ||
}); | ||
it('19. __getTarget check', () => { | ||
it('20. __getTarget check', () => { | ||
var isSameObject = false; | ||
@@ -372,3 +427,3 @@ if (p.__getTarget === test) isSameObject = true; | ||
it('20. __getParent on nested object (not supported with ES5 polyfill).', () => { | ||
it('21. __getParent on nested object (not supported with ES5 polyfill).', () => { | ||
if (global.Proxy === global.NativeProxy) { | ||
@@ -383,3 +438,3 @@ p.hello = {}; | ||
it('21. Multiple observables on same object.', () => { | ||
it('22. Multiple observables on same object.', () => { | ||
var test = {"dummy":"blah"}; | ||
@@ -405,3 +460,3 @@ var firstProxy = false; | ||
it('22. Multiple observables on same object with nested objects.', () => { | ||
it('23. Multiple observables on same object with nested objects.', () => { | ||
var firstProxy = false; | ||
@@ -427,3 +482,3 @@ var secondProxy = false; | ||
it('23. Multiple observables on same object with nested objects by passing in a Proxy to `create`.', () => { | ||
it('24. Multiple observables on same object with nested objects by passing in a Proxy to `create`.', () => { | ||
var firstProxy = false; | ||
@@ -449,3 +504,3 @@ var secondProxy = false; | ||
it('24. Multiple observables on same object and a Proxy nested within another object.', () => { | ||
it('25. Multiple observables on same object and a Proxy nested within another object.', () => { | ||
@@ -475,3 +530,3 @@ var firstObservableTriggered = false; | ||
it.skip('25. Multiple observables on same object and a Proxy nested within another object set after initialization.', () => { | ||
it.skip('26. Multiple observables on same object and a Proxy nested within another object set after initialization.', () => { | ||
@@ -507,3 +562,3 @@ var firstObservableTriggered = 0; | ||
it('26. Create an observable and then remove it.', () => { | ||
it('27. Create an observable and then remove it.', () => { | ||
@@ -528,3 +583,3 @@ var observed = false; | ||
it('27. Pause and resume observables.', () => { | ||
it('28. Pause and resume observables.', () => { | ||
@@ -554,3 +609,3 @@ var changeCount = 0; | ||
it('28. Pause and resume changes on observables', () => { | ||
it('29. Pause and resume changes on observables', () => { | ||
@@ -584,3 +639,3 @@ var changeCount = 0; | ||
// ensures that the garbage clean-up code runs and that the garbage clean-up doesn't throw an error. | ||
it('29. Clean-up observers of overwritten (orphaned) objects.', (done) => { | ||
it('30. Clean-up observers of overwritten (orphaned) objects.', (done) => { | ||
@@ -587,0 +642,0 @@ var data = {"testing":{"test":{"testb":"hello world"},"testc":"hello again"},"blah":{"tree":"world"}}; |
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
82027
1370