f?f:s+u));return 1===n?(e=t[r-1],i+=y[e>>2],i+=y[e<<4&63],i+="=="):2===n&&(e=(t[r-2]<<8)+t[r-1],i+=y[e>>10],i+=y[e>>4&63],i+=y[e<<2&63],i+="="),o.push(i),o.join("")}r.byteLength=c,r.toByteArray=l,r.fromByteArray=g;for(var y=[],v=[],b="undefined"!=typeof Uint8Array?Uint8Array:Array,w="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",m=0,P=w.length;m=l())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+l().toString(16)+" bytes");return 0|t}function A(t){return+t!=t&&(t=0),i.alloc(+t)}function B(t,e){if(i.isBuffer(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var r=t.length;if(0===r)return 0;for(var n=!1;;)switch(e){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":case void 0:return Q(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return et(t).length;default:if(n)return Q(t).length;e=(""+e).toLowerCase(),n=!0}}function E(t,e,r){var n=!1;if((void 0===e||e<0)&&(e=0),e>this.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if(r>>>=0,e>>>=0,r<=e)return"";for(t||(t="utf8");;)switch(t){case"hex":return N(this,e,r);case"utf8":case"utf-8":return L(this,e,r);case"ascii":return D(this,e,r);case"latin1":case"binary":return V(this,e,r);case"base64":return x(this,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return k(this,e,r);default:if(n)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),n=!0}}function T(t,e,r){var n=t[e];t[e]=t[r],t[r]=n}function C(t,e,r,n,o){if(0===t.length)return-1;if("string"==typeof r?(n=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),r=+r,isNaN(r)&&(r=o?0:t.length-1),r<0&&(r=t.length+r),r>=t.length){if(o)return-1;r=t.length-1}else if(r<0){if(!o)return-1;r=0}if("string"==typeof e&&(e=i.from(e,n)),i.isBuffer(e))return 0===e.length?-1:R(t,e,r,n,o);if("number"==typeof e)return e=255&e,i.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(t,e,r):Uint8Array.prototype.lastIndexOf.call(t,e,r):R(t,[e],r,n,o);throw new TypeError("val must be string, number or Buffer")}function R(t,e,r,n,i){function o(t,e){return 1===u?t[e]:t.readUInt16BE(e*u)}var u=1,s=t.length,f=e.length;if(void 0!==n&&(n=String(n).toLowerCase(),"ucs2"===n||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(t.length<2||e.length<2)return-1;u=2,s/=2,f/=2,r/=2}var a;if(i){var h=-1;for(a=r;as&&(r=s-f),a=r;a>=0;a--){for(var c=!0,l=0;li&&(n=i)):n=i;var o=e.length;if(o%2!==0)throw new TypeError("Invalid hex string");n>o/2&&(n=o/2);for(var u=0;u239?4:o>223?3:o>191?2:1;if(i+s<=r){var f,a,h,c;switch(s){case 1:o<128&&(u=o);break;case 2:f=t[i+1],128===(192&f)&&(c=(31&o)<<6|63&f,c>127&&(u=c));break;case 3:f=t[i+1],a=t[i+2],128===(192&f)&&128===(192&a)&&(c=(15&o)<<12|(63&f)<<6|63&a,c>2047&&(c<55296||c>57343)&&(u=c));break;case 4:f=t[i+1],a=t[i+2],h=t[i+3],128===(192&f)&&128===(192&a)&&128===(192&h)&&(c=(15&o)<<18|(63&f)<<12|(63&a)<<6|63&h,c>65535&&c<1114112&&(u=c))}}null===u?(u=65533,s=1):u>65535&&(u-=65536,n.push(u>>>10&1023|55296),u=56320|1023&u),n.push(u),i+=s}return M(n)}function M(t){var e=t.length;if(e<=st)return String.fromCharCode.apply(String,t);for(var r="",n=0;nn)&&(r=n);for(var i="",o=e;or)throw new RangeError("Trying to access beyond buffer length")}function z(t,e,r,n,o,u){if(!i.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>o||et.length)throw new RangeError("Index out of range")}function q(t,e,r,n){e<0&&(e=65535+e+1);for(var i=0,o=Math.min(t.length-r,2);i>>8*(n?i:1-i)}function K(t,e,r,n){e<0&&(e=4294967295+e+1);for(var i=0,o=Math.min(t.length-r,4);i>>8*(n?i:3-i)&255}function $(t,e,r,n,i,o){if(r+n>t.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function G(t,e,r,n,i){return i||$(t,e,r,4,3.4028234663852886e38,-3.4028234663852886e38),ot.write(t,e,r,n,23,4),r+4}function J(t,e,r,n,i){return i||$(t,e,r,8,1.7976931348623157e308,-1.7976931348623157e308),ot.write(t,e,r,n,52,8),r+8}function X(t){if(t=Z(t).replace(ft,""),t.length<2)return"";for(;t.length%4!==0;)t+="=";return t}function Z(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function H(t){return t<16?"0"+t.toString(16):t.toString(16)}function Q(t,e){e=e||1/0;for(var r,n=t.length,i=null,o=[],u=0;u55295&&r<57344){if(!i){if(r>56319){(e-=3)>-1&&o.push(239,191,189);continue}if(u+1===n){(e-=3)>-1&&o.push(239,191,189);continue}i=r;continue}if(r<56320){(e-=3)>-1&&o.push(239,191,189),i=r;continue}r=(i-55296<<10|r-56320)+65536}else i&&(e-=3)>-1&&o.push(239,191,189);if(i=null,r<128){if((e-=1)<0)break;o.push(r)}else if(r<2048){if((e-=2)<0)break;o.push(r>>6|192,63&r|128)}else if(r<65536){if((e-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return o}function W(t){for(var e=[],r=0;r>8,i=r%256,o.push(i),o.push(n);return o}function et(t){return it.toByteArray(X(t))}function rt(t,e,r,n){for(var i=0;i=e.length||i>=t.length);++i)e[i+r]=t[i];return i}function nt(t){return t!==t}var it=t("base64-js"),ot=t("ieee754"),ut=t("isarray");r.Buffer=i,r.SlowBuffer=A,r.INSPECT_MAX_BYTES=50,i.TYPED_ARRAY_SUPPORT=void 0!==n.TYPED_ARRAY_SUPPORT?n.TYPED_ARRAY_SUPPORT:c(),r.kMaxLength=l(),i.poolSize=8192,i._augment=function(t){return t.__proto__=i.prototype,t},i.from=function(t,e,r){return d(null,t,e,r)},i.TYPED_ARRAY_SUPPORT&&(i.prototype.__proto__=Uint8Array.prototype,i.__proto__=Uint8Array,"undefined"!=typeof Symbol&&Symbol.species&&i[Symbol.species]===i&&Object.defineProperty(i,Symbol.species,{value:null,configurable:!0})),i.alloc=function(t,e,r){return y(null,t,e,r)},i.allocUnsafe=function(t){return v(null,t)},i.allocUnsafeSlow=function(t){return v(null,t)},i.isBuffer=function(t){return!(null==t||!t._isBuffer)},i.compare=function(t,e){if(!i.isBuffer(t)||!i.isBuffer(e))throw new TypeError("Arguments must be Buffers");if(t===e)return 0;for(var r=t.length,n=e.length,o=0,u=Math.min(r,n);o0&&(t=this.toString("hex",0,e).match(/.{2}/g).join(" "),this.length>e&&(t+=" ... ")),""},i.prototype.compare=function(t,e,r,n,o){if(!i.isBuffer(t))throw new TypeError("Argument must be a Buffer");if(void 0===e&&(e=0),void 0===r&&(r=t?t.length:0),void 0===n&&(n=0),void 0===o&&(o=this.length),e<0||r>t.length||n<0||o>this.length)throw new RangeError("out of range index");if(n>=o&&e>=r)return 0;if(n>=o)return-1;if(e>=r)return 1;if(e>>>=0,r>>>=0,n>>>=0,o>>>=0,this===t)return 0;for(var u=o-n,s=r-e,f=Math.min(u,s),a=this.slice(n,o),h=t.slice(e,r),c=0;ci)&&(r=i),t.length>0&&(r<0||e<0)||e>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var o=!1;;)switch(n){case"hex":return O(this,t,e,r);case"utf8":case"utf-8":return S(this,t,e,r);case"ascii":return U(this,t,e,r);case"latin1":case"binary":return j(this,t,e,r);case"base64":return I(this,t,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return Y(this,t,e,r);default:if(o)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),o=!0}},i.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var st=4096;i.prototype.slice=function(t,e){var r=this.length;t=~~t,e=void 0===e?r:~~e,t<0?(t+=r,t<0&&(t=0)):t>r&&(t=r),e<0?(e+=r,e<0&&(e=0)):e>r&&(e=r),e0&&(i*=256);)n+=this[t+--e]*i;return n},i.prototype.readUInt8=function(t,e){return e||F(t,1,this.length),this[t]},i.prototype.readUInt16LE=function(t,e){return e||F(t,2,this.length),this[t]|this[t+1]<<8},i.prototype.readUInt16BE=function(t,e){return e||F(t,2,this.length),this[t]<<8|this[t+1]},i.prototype.readUInt32LE=function(t,e){return e||F(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},i.prototype.readUInt32BE=function(t,e){return e||F(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},i.prototype.readIntLE=function(t,e,r){t=0|t,e=0|e,r||F(t,e,this.length);for(var n=this[t],i=1,o=0;++o=i&&(n-=Math.pow(2,8*e)),n},i.prototype.readIntBE=function(t,e,r){t=0|t,e=0|e,r||F(t,e,this.length);for(var n=e,i=1,o=this[t+--n];n>0&&(i*=256);)o+=this[t+--n]*i;return i*=128,o>=i&&(o-=Math.pow(2,8*e)),o},i.prototype.readInt8=function(t,e){return e||F(t,1,this.length),128&this[t]?(255-this[t]+1)*-1:this[t]},i.prototype.readInt16LE=function(t,e){e||F(t,2,this.length);var r=this[t]|this[t+1]<<8;return 32768&r?4294901760|r:r},i.prototype.readInt16BE=function(t,e){e||F(t,2,this.length);var r=this[t+1]|this[t]<<8;return 32768&r?4294901760|r:r},i.prototype.readInt32LE=function(t,e){return e||F(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},i.prototype.readInt32BE=function(t,e){return e||F(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},i.prototype.readFloatLE=function(t,e){return e||F(t,4,this.length),ot.read(this,t,!0,23,4)},i.prototype.readFloatBE=function(t,e){return e||F(t,4,this.length),ot.read(this,t,!1,23,4)},i.prototype.readDoubleLE=function(t,e){return e||F(t,8,this.length),ot.read(this,t,!0,52,8)},i.prototype.readDoubleBE=function(t,e){return e||F(t,8,this.length),ot.read(this,t,!1,52,8)},i.prototype.writeUIntLE=function(t,e,r,n){if(t=+t,e=0|e,r=0|r,!n){var i=Math.pow(2,8*r)-1;z(this,t,e,r,i,0)}var o=1,u=0;for(this[e]=255&t;++u=0&&(u*=256);)this[e+o]=t/u&255;return e+r},i.prototype.writeUInt8=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,1,255,0),i.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),this[e]=255&t,e+1},i.prototype.writeUInt16LE=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,2,65535,0),i.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):q(this,t,e,!0),e+2},i.prototype.writeUInt16BE=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,2,65535,0),i.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):q(this,t,e,!1),e+2},i.prototype.writeUInt32LE=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,4,4294967295,0),i.TYPED_ARRAY_SUPPORT?(this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t):K(this,t,e,!0),e+4},i.prototype.writeUInt32BE=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,4,4294967295,0),i.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):K(this,t,e,!1),e+4},i.prototype.writeIntLE=function(t,e,r,n){if(t=+t,e=0|e,!n){var i=Math.pow(2,8*r-1);z(this,t,e,r,i-1,-i)}var o=0,u=1,s=0;for(this[e]=255&t;++o>0)-s&255;return e+r},i.prototype.writeIntBE=function(t,e,r,n){if(t=+t,e=0|e,!n){var i=Math.pow(2,8*r-1);z(this,t,e,r,i-1,-i)}var o=r-1,u=1,s=0;for(this[e+o]=255&t;--o>=0&&(u*=256);)t<0&&0===s&&0!==this[e+o+1]&&(s=1),this[e+o]=(t/u>>0)-s&255;return e+r},i.prototype.writeInt8=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,1,127,-128),i.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),t<0&&(t=255+t+1),this[e]=255&t,e+1},i.prototype.writeInt16LE=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,2,32767,-32768),i.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):q(this,t,e,!0),e+2},i.prototype.writeInt16BE=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,2,32767,-32768),i.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):q(this,t,e,!1),e+2},i.prototype.writeInt32LE=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,4,2147483647,-2147483648),i.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24):K(this,t,e,!0),e+4},i.prototype.writeInt32BE=function(t,e,r){return t=+t,e=0|e,r||z(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),i.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):K(this,t,e,!1),e+4},i.prototype.writeFloatLE=function(t,e,r){return G(this,t,e,!0,r)},i.prototype.writeFloatBE=function(t,e,r){return G(this,t,e,!1,r)},i.prototype.writeDoubleLE=function(t,e,r){return J(this,t,e,!0,r)},i.prototype.writeDoubleBE=function(t,e,r){return J(this,t,e,!1,r)},i.prototype.copy=function(t,e,r,n){if(r||(r=0),n||0===n||(n=this.length),e>=t.length&&(e=t.length),e||(e=0),n>0&&n=this.length)throw new RangeError("sourceStart out of bounds");if(n<0)throw new RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),t.length-e=0;--o)t[o+e]=this[o+r];else if(u<1e3||!i.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,r=void 0===r?this.length:r>>>0,t||(t=0);var u;if("number"==typeof t)for(u=e;u>1,h=-7,c=r?i-1:0,l=r?-1:1,p=t[e+c];for(c+=l,o=p&(1<<-h)-1,p>>=-h,h+=s;h>0;o=256*o+t[e+c],c+=l,h-=8);for(u=o&(1<<-h)-1,o>>=-h,h+=n;h>0;u=256*u+t[e+c],c+=l,h-=8);if(0===o)o=1-a;else{if(o===f)return u?NaN:(p?-1:1)*(1/0);u+=Math.pow(2,n),o-=a}return(p?-1:1)*u*Math.pow(2,o-n)},r.write=function(t,e,r,n,i,o){var u,s,f,a=8*o-i-1,h=(1<>1,l=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,p=n?0:o-1,d=n?1:-1,g=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(s=isNaN(e)?1:0,u=h):(u=Math.floor(Math.log(e)/Math.LN2),e*(f=Math.pow(2,-u))<1&&(u--,f*=2),e+=u+c>=1?l/f:l*Math.pow(2,1-c),e*f>=2&&(u++,f/=2),u+c>=h?(s=0,u=h):u+c>=1?(s=(e*f-1)*Math.pow(2,i),u+=c):(s=e*Math.pow(2,c-1)*Math.pow(2,i),u=0));i>=8;t[r+p]=255&s,p+=d,s/=256,i-=8);for(u=u<0;t[r+p]=255&u,p+=d,u/=256,a-=8);t[r+p-d]|=128*g}}).call(this,t("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},t("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules\\ieee754\\index.js","/node_modules\\ieee754")},{_process:5,buffer:2}],4:[function(t,e,r){(function(t,r,n,i,o,u,s,f,a){var h={}.toString;e.exports=Array.isArray||function(t){return"[object Array]"==h.call(t)}}).call(this,t("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},t("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules\\isarray\\index.js","/node_modules\\isarray")},{_process:5,buffer:2}],5:[function(t,e,r){(function(t,r,n,i,o,u,s,f,a){function h(){throw new Error("setTimeout has not been defined")}function c(){throw new Error("clearTimeout has not been defined")}function l(t){if(b===setTimeout)return setTimeout(t,0);if((b===h||!b)&&setTimeout)return b=setTimeout,setTimeout(t,0);try{return b(t,0)}catch(e){try{return b.call(null,t,0)}catch(e){return b.call(this,t,0)}}}function p(t){if(w===clearTimeout)return clearTimeout(t);if((w===c||!w)&&clearTimeout)return w=clearTimeout,clearTimeout(t);try{return w(t)}catch(e){try{return w.call(null,t)}catch(e){return w.call(this,t)}}}function d(){_&&m&&(_=!1,m.length?P=m.concat(P):A=-1,P.length&&g())}function g(){if(!_){var t=l(d);_=!0;for(var e=P.length;e;){for(m=P,P=[];++A1)for(var r=1;r-1&&t%1==0&&t);})
32 | * + this.props.model.add()
33 | * + this.props.model.remove(item)
34 | * + supports for "value/requestChange" interface also to enable to use [ReactLink][valueLink] attribute
35 | * + valueLink={this.bindTo(employee,"FirstName")}
36 | * + enables binding with value converters
37 | * + supports both directions - format (toView) and parse (fromView)
38 | * + support for converter parameter - valueLink={this.bindToState("data", "Duration.From",converter, "DD.MM.YYYY")}
39 | * + converter parameter can be data-bound - valueLink={this.bindToState("data", "Duration.From",converter, this.state.format)}
40 | * + usable with any css frameworks -
41 | * + react-bootstrap
42 | * + material-ui
43 | *
44 | */
45 | export default class Binder {
46 |
47 | static createStateKeySetter(component, key) {
48 | var partialState = {};
49 |
50 | return (value?) => {
51 | partialState[key] = (value !== undefined) ? value : component.state[key];
52 | component.setState(partialState);
53 | }
54 | }
55 |
56 | /**
57 | * It enables to bind to object property with path expression
58 | * + using [valueLink](http://facebook.github.io/react/docs/two-way-binding-helpers.html)
59 | * ``` js
60 | *
61 | * ```
62 | *
63 | * + without [valueLink](http://facebook.github.io/react/docs/two-way-binding-helpers.html)
64 | * ``` js
65 | *
66 | * ```
67 | *
68 | * ``` js
69 | * var TextBoxInput = React.createClass({
70 | * render: function() {
71 | * var valueModel = this.props.model;
72 | * var handleChange = function(e){
73 | * valueModel.value = e.target.value;
74 | * }
75 | * return (
76 | *
77 | * )}
78 | * });
79 | * ```
80 | *
81 | * @param key - property name in state (this.state[key])
82 | * @param path - expression to bind to property
83 | * @param converter {DataBinding.IValueConverter} - value converter
84 | * @param converterParams - parameters used by converter
85 | * @returns {DataBinding.PathObjectBinding}
86 | */
87 | static bindToState(component, key: string, path?: string, converter?: IValueConverter, converterParams?): IPathObjectBinding {
88 | return new PathObjectBinding(
89 | new Provider(component["state"][key]),
90 | path,
91 | Binder.createStateKeySetter(component, key),
92 | converterParams !== undefined ? new CurryConverter(converter, converterParams) : converter
93 | //ReactStateSetters.createStateKeySetter(this, key)
94 | );
95 | }
96 |
97 | /**
98 | * It enables to bind to complex object with nested properties and reuse bindings in components.
99 | *
100 | * + binding to state at root level
101 | *
102 | * ``` js
103 | *
104 | *
105 | * ```
106 | *
107 | * + binding to parent
108 | *
109 | * ``` js
110 | *
111 | * ```
112 | *
113 | * + reuse bindings in component
114 | *
115 | * ``` js
116 | * var PersonComponent = React.createClass({
117 | * mixins:[BindToMixin],
118 | * render: function() {
119 | * return (
120 | *
121 | *
122 | *
123 | *
124 | *
125 | * );
126 | * }
127 | * });
128 | *
129 | * ```
130 | *
131 | * @param parent - the parent object
132 | * @param path - expression to bind to property
133 | * @param converter - value converter {DataBinding.IValueConverter}
134 | * @param converterParams - parameters used by converter
135 | * @returns {DataBinding.PathParentBinding}
136 | */
137 | static bindTo(parent, path?: string, converter?, converterParams?): IPathObjectBinding {
138 | return BinderCore.bindTo(Provider, parent, path, converter, converterParams);
139 | }
140 |
141 | /**
142 | * It enables binding to collection-based structures (array). It enables to add and remove items.
143 | *
144 | * + binding to array
145 | *
146 | * ``` js
147 | *
148 | * ```
149 | *
150 | * @param key - property name in state (this.state[key]) - it must be array
151 | * @param path - expression to array to bind to property
152 | * @returns {DataBinding.ArrayObjectBinding}
153 | */
154 | static bindArrayToState(component, key: string, path?: string): ArrayObjectBinding {
155 | return new ArrayObjectBinding(
156 | new Provider(component["state"][key]),
157 | path,
158 | Binder.createStateKeySetter(component, key)
159 | //ReactStateSetters.createStateKeySetter(this, key)
160 | );
161 | }
162 |
163 |
164 | /**
165 | * It enables binding to collection-based structures (array) for nested arrays. It enables to add and remove items.
166 | *
167 | * + binding to parent
168 | *
169 | * ``` js
170 | *
171 | * ```
172 | *
173 | * @param parent - the parent object
174 | * @param path - expression to bind to property - relative path from parent
175 | * @returns {DataBinding.PathParentBinding}
176 | */
177 | static bindArrayTo(parent, path?: string, converter?, converterParams?): any {
178 | return BinderCore.bindArrayTo(Provider, parent, path, converter, converterParams);
179 | }
180 | }
181 | //export default Binder;
--------------------------------------------------------------------------------
/src/DataBinding.ts:
--------------------------------------------------------------------------------
1 | import { castPath } from "./utils";
2 |
3 | export interface BinderStatic {
4 | bindToState?(data, key: string, path?: Path, converter?: IValueConverter, converterParams?: any): ObjectBinding
5 | bindTo(parent, path?: Path, converter?: IValueConverter, converterParams?: any): ObjectBinding
6 | bindArrayToState?(data, key: string, path?: Path, converter?: IValueConverter, converterParams?: any): ArrayBinding
7 | bindArrayTo(parent, path?: Path, converter?: IValueConverter, converterParams?: any): ArrayBinding
8 | }
9 | export interface Binding {
10 | path?: Array;
11 | parent: Binding;
12 | root: Binding;
13 | }
14 | export interface ObjectBinding extends Binding {
15 | value: any;
16 | }
17 | export interface ArrayBinding extends Binding {
18 | items: Array;
19 | add(defautItem?: any);
20 | remove(itemToRemove: any);
21 | splice(start: number, deleteCount: number, elementsToAdd?: any);
22 | move(x: number, y: number);
23 | }
24 | export type Path = string | Array;
25 |
26 |
27 | /**
28 | * Two-way data binding for React.
29 | */
30 |
31 | /**
32 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName")
33 | */
34 | export interface IPathObjectBinder {
35 | /**
36 | It gets value at the passed path expression.
37 | */
38 | getValue(path?: Path)
39 | /**
40 | It sets the passed value at the passed path.
41 | */
42 | setValue(path: Path, value: any);
43 |
44 | subscribe(fce): void;
45 | }
46 | /**
47 | It represents change notification function. It is called whenever there is a change.
48 | */
49 | export interface INotifyChange {
50 | (any?): void
51 | }
52 |
53 | /**
54 | It represents change notifikcation function with changed value. It supports valueLink interface
55 | */
56 | export interface IRequestChange {
57 | (any): void
58 | }
59 |
60 | /**
61 | It represents binding to property at source object at a given path.
62 | */
63 | export interface IPathObjectBinding extends ObjectBinding {
64 | //value: any;
65 | source: IPathObjectBinder;
66 |
67 | notifyChange?: INotifyChange;
68 | requestChange?: IRequestChange;
69 |
70 | valueConverter?: IValueConverter;
71 | //path?: any;
72 | }
73 | /**
74 | It represents binding to property at source object at a given path.
75 | */
76 | export class PathObjectBinding implements IPathObjectBinding {
77 |
78 | //public source: IPathObjectBinder;
79 | public path: Array;
80 | constructor(public source: IPathObjectBinder, rootPath?: Path, public notifyChange?: INotifyChange, public valueConverter?: IValueConverter, public parentNode?: Binding) {
81 | this.path = rootPath === undefined ? [] : castPath(rootPath);
82 | }
83 |
84 | public get requestChange(): IRequestChange {
85 | return (value) => { this.value = value; }
86 | }
87 | public get root(): Binding {
88 | return this.parentNode !== undefined ? this.parentNode.root : this;
89 | }
90 | public get parent(): Binding {
91 | return this.parentNode !== undefined ? this.parentNode : undefined;
92 | }
93 |
94 | public get value() {
95 | var value = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path);
96 | //get value - optional call converter
97 | return this.valueConverter !== undefined ? this.valueConverter.format(value) : value;
98 | }
99 |
100 | public set value(value: any) {
101 |
102 | var previousValue = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path);
103 | var convertedValueToBeSet = this.valueConverter !== undefined ? this.valueConverter.parse(value) : value;
104 |
105 | //check if the value is really changed - strict equality
106 | if (previousValue !== undefined && previousValue === convertedValueToBeSet) return;
107 |
108 | if (this.path === undefined) {
109 | if (this.notifyChange !== undefined) this.notifyChange(convertedValueToBeSet);
110 | } else {
111 | this.source.setValue(this.path, convertedValueToBeSet);
112 | if (this.notifyChange !== undefined) this.notifyChange();
113 | }
114 | }
115 | }
116 |
117 | /**
118 | It represents binding to property at source object at a given path.
119 | */
120 |
121 | export class ArrayObjectBinding implements ArrayBinding {
122 |
123 | public path: Array;
124 | constructor(public source: IPathObjectBinder, rootPath?: Path, public notifyChange?: INotifyChange, public valueConverter?: IValueConverter) {
125 | this.path = rootPath === undefined ? [] : castPath(rootPath);
126 | }
127 |
128 | public get parent(): ArrayBinding {
129 | return undefined;
130 | }
131 | public get root(): ArrayBinding {
132 | return this;
133 | }
134 |
135 | public get items(): Array {
136 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path);
137 |
138 | if (items === undefined) return [];
139 | return items.map(function (item, index) {
140 | //console.log(item);
141 | return new PathObjectBinding(this.source.createNew(this.path.concat(index), item), undefined, this.notifyChange, undefined, this);
142 | }, this);
143 | }
144 |
145 | public add(defaultItem?) {
146 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path);
147 | if (items === undefined) {
148 | this.source.setValue(this.path, []);
149 | items = this.source.getValue(this.path);
150 | }
151 | if (defaultItem === undefined) defaultItem = {};
152 | items.push(defaultItem);
153 | if (this.notifyChange !== undefined) this.notifyChange();
154 | }
155 |
156 | public remove(itemToRemove) {
157 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path);
158 | if (items === undefined) return;
159 | var index = items.indexOf(itemToRemove);
160 | if (index === -1) return;
161 | items.splice(index, 1);
162 |
163 | if (this.notifyChange !== undefined) this.notifyChange();
164 | }
165 |
166 |
167 | public splice(start: number, deleteCount: number, elementsToAdd?: any) {
168 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path);
169 | if (items === undefined) return;
170 | return elementsToAdd ? items.splice(start, deleteCount, elementsToAdd) : items.splice(start, deleteCount);
171 |
172 | //if (this.notifyChange !== undefined) this.notifyChange();
173 | }
174 | public move(x: number, y: number) {
175 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path);
176 | if (items === undefined) return;
177 | //@TODO: use more effective way to clone array
178 | var itemsCloned = JSON.parse(JSON.stringify(items));
179 |
180 | itemsCloned.splice(y, 0, itemsCloned.splice(x, 1)[0]);
181 | this.source.setValue(this.path, itemsCloned);
182 | }
183 | }
184 |
185 | /**
186 | It represents binding to array using relative path to parent object.
187 | */
188 | export class ArrayParentBinding implements ArrayBinding {
189 | public relativePath: Array;
190 | constructor(private parentBinding: IPathObjectBinding, subPath?: Path, public valueConverter?: IValueConverter) {
191 | this.relativePath = subPath === undefined ? [] : castPath(subPath);
192 | }
193 |
194 | //wrapped properties - delegate call to parent
195 | public get source(): IPathObjectBinder {
196 | return this.parentBinding.source;
197 | }
198 | public get root(): Binding {
199 | return this.parentBinding.root;
200 | }
201 | public get parent(): Binding {
202 | return this.parentBinding;
203 | }
204 |
205 | public get notifyChange() {
206 | return this.parentBinding.notifyChange;
207 | }
208 |
209 | public set notifyChange(value: INotifyChange) {
210 | this.parentBinding.notifyChange = value;
211 | }
212 |
213 | //concatenate path
214 | public get path(): Array {
215 |
216 | if (this.parentBinding.path === undefined) return this.relativePath;
217 | if (this.relativePath === undefined) return this.parentBinding.path;
218 | return this.parentBinding.path.concat(this.relativePath);
219 | }
220 | private cachedBindings: { items:Array,length:number, bindings: Array };
221 | private clearCache() {
222 | this.cachedBindings === undefined;
223 | }
224 | private getItems(): Array {
225 | if (this.source === undefined) return;
226 | var value = this.source.getValue(this.path);
227 | return this.valueConverter !== undefined ? this.valueConverter.format(value) : value;
228 | }
229 | public get items(): Array {
230 | // var path = this.path.join(".");
231 | // console.time(path);
232 |
233 | var items = this.getItems();
234 | if (items === undefined) return [];
235 | if (this.cachedBindings !== undefined && this.cachedBindings.items === items && this.cachedBindings.length === items.length) return this.cachedBindings.bindings;
236 |
237 | this.cachedBindings = {
238 | items: items,
239 | length: items.length,
240 | bindings: items.map(function (item, index) {
241 | return new PathObjectBinding(this.source.createNew(this.path.concat(index), item), undefined, this.notifyChange, undefined, this);
242 | }, this)
243 | };
244 | //console.timeEnd(path);
245 | return this.cachedBindings.bindings;
246 |
247 | }
248 |
249 | public add(defaultItem?) {
250 | var items = this.getItems();
251 | if (items === undefined) {
252 | this.source.setValue(this.path, []);
253 | items = this.source.getValue(this.path);
254 | }
255 |
256 | if (defaultItem === undefined) defaultItem = {};
257 | items.push(defaultItem);
258 | this.clearCache();
259 | if (this.notifyChange !== undefined) this.notifyChange();
260 | }
261 |
262 | public remove(itemToRemove) {
263 | var items = this.getItems();
264 | if (items === undefined) return;
265 | var index = items.indexOf(itemToRemove);
266 | if (index === -1) return;
267 | items.splice(index, 1);
268 | this.clearCache();
269 | if (this.notifyChange !== undefined) this.notifyChange();
270 | }
271 | public splice(start: number, deleteCount: number, elementsToAdd?: any) {
272 | var items = this.getItems();
273 | if (items === undefined) return;
274 | return elementsToAdd ? items.splice(start, deleteCount, elementsToAdd) : items.splice(start, deleteCount);
275 |
276 | //if (this.notifyChange !== undefined) this.notifyChange();
277 | }
278 | public move(x, y) {
279 | this.splice(y, 0, this.splice(x, 1)[0]);
280 | this.clearCache();
281 | if (this.notifyChange !== undefined) this.notifyChange();
282 | }
283 | }
284 |
285 | /**
286 | It represents binding to relative path for parent object.
287 | */
288 | export class PathParentBinding implements IPathObjectBinding {
289 |
290 | public relativePath: Array;
291 | constructor(private parentBinding: IPathObjectBinding, subPath: Path, public valueConverter?: IValueConverter) {
292 | this.relativePath = subPath === undefined ? [] : castPath(subPath);
293 | }
294 |
295 | //wrapped properties - delegate call to parent
296 | public get source(): IPathObjectBinder {
297 | return this.parentBinding.source;
298 | }
299 |
300 | public get root(): Binding {
301 | return this.parentBinding.root;
302 | }
303 | public get parent(): Binding {
304 | return this.parentBinding;
305 | }
306 |
307 |
308 | public get notifyChange() {
309 | return this.parentBinding.notifyChange;
310 | }
311 |
312 | public set notifyChange(value: INotifyChange) {
313 | this.parentBinding.notifyChange = value;
314 | }
315 |
316 | public get requestChange(): IRequestChange {
317 | return (value) => {
318 | this.value = value;
319 | }
320 | }
321 |
322 | //concatenate path
323 | public get path(): Array {
324 | if (this.parentBinding.path === undefined) return this.relativePath;
325 | return this.parentBinding.path.concat(this.relativePath);
326 | }
327 |
328 |
329 | public get value() {
330 | var value = this.source.getValue(this.path);
331 | //get value - optional call converter
332 | var result = this.valueConverter !== undefined ? this.valueConverter.format(value) : value;
333 | return result;
334 | }
335 |
336 | public set value(value: any) {
337 | //var path = this.path.join(".");
338 | //console.time(path);
339 |
340 | //check if the value is really changed - strict equality
341 | var previousValue = this.source.getValue(this.path);
342 | var convertedValueToBeSet = this.valueConverter !== undefined ? this.valueConverter.parse(value) : value;
343 |
344 | if (previousValue === convertedValueToBeSet) return;
345 |
346 | //set value - optional call converter
347 | this.source.setValue(this.path, convertedValueToBeSet);
348 | //console.timeEnd(path);
349 | if (this.notifyChange !== undefined) this.notifyChange();
350 |
351 |
352 | }
353 | }
354 | /**
355 | * Provides a way to apply custom logic to a binding.
356 | * It enables to make bi-directional convertions between source (data) and target (view) binding.
357 | *
358 | * + apply various formats to values
359 | * + parse values from user input
360 | */
361 | export interface IValueConverter {
362 | /**
363 | * Convert value into another value before return binding getter. Typically from model(data) to view.
364 | * @param value - source binding object (value)
365 | * @param parameters - enable parametrization of conversion
366 | */
367 | format?(value, parameters?);
368 |
369 | /**
370 | * Convert value into another value before call binding setter. Typically from view to model(data).
371 | * @param value - target binding object (value)
372 | * @param parameters - enable parametrization of conversion
373 | */
374 | parse?(value, parameters?);
375 | }
376 |
377 | export class CurryConverter implements IValueConverter {
378 |
379 | private formatFce;
380 | private parseFce;
381 |
382 | constructor(converter: IValueConverter, args: any) {
383 | this.formatFce = this.curryParameters(converter.format, [args]);
384 | this.parseFce = this.curryParameters(converter.parse, [args]);
385 | }
386 |
387 | private curryParameters(fn, args) {
388 | return function () {
389 | return fn.apply(this, Array.prototype.slice.call(arguments).concat(args));
390 | };
391 | }
392 | public format(value) {
393 | return this.formatFce(value);
394 | }
395 | public parse(value) {
396 | return this.parseFce(value);
397 | }
398 |
399 | }
--------------------------------------------------------------------------------
/src/FreezerBinder.ts:
--------------------------------------------------------------------------------
1 | import Provider from './FreezerProvider';
2 | import { BinderCore } from './Binder';
3 |
4 | export default class Binder {
5 |
6 | static bindTo(parent, path?: string, converter?, converterParams?): any {
7 | return BinderCore.bindTo(Provider, parent, path, converter, converterParams);
8 | }
9 |
10 | static bindArrayTo(parent, path?: string, converter?, converterParams?): any {
11 | return BinderCore.bindArrayTo(Provider, parent, path, converter, converterParams);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/FreezerProvider.ts:
--------------------------------------------------------------------------------
1 | var Freezer = require('freezer-js');
2 |
3 | import { baseSet as set, baseGet as get, castPath, followRef } from './utils';
4 | import { isIndex, isObject } from './utils-lodash';
5 | import { IPathObjectBinder, Path } from './DataBinding';
6 | /**
7 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName")
8 | */
9 | export default class FreezerPathObjectBinder implements IPathObjectBinder {
10 |
11 | private root;
12 | constructor(rootParams: any, private subSource?: any) {
13 | this.root = subSource === undefined ? new Freezer(rootParams) : rootParams;
14 | //this.source = source === undefined ? this.root : source;
15 | }
16 | private get source() {
17 | return this.subSource !== undefined?this.subSource.get():this.root.get();
18 | }
19 | public createNew(path: Path, newItem?: any): IPathObjectBinder {
20 | var item = newItem || this.getValue(path);
21 | //var item = followRef(this.root.get(), newItem || this.getValue(path));
22 | return new FreezerPathObjectBinder(this.root, new Freezer(item))
23 | }
24 | public subscribe(updateFce) {
25 | this.root.on('update', function (state, prevState) {
26 | //console.log(state);
27 | if (updateFce !== undefined) updateFce(state, prevState)
28 | }
29 | );
30 | }
31 |
32 | public getValue(path?: Path) {
33 | if (path === undefined) return this.source;
34 | var cursorPath = castPath(path);
35 | if (cursorPath.length === 0) return this.source;
36 |
37 | var parent = this.getParent(cursorPath);
38 | if (parent === undefined) return;
39 | var property = cursorPath[cursorPath.length - 1];
40 | return parent[property];
41 |
42 | }
43 |
44 | public setValue(path: Path, value: string) {
45 | if (path === undefined) return;
46 | var cursorPath = castPath(path);
47 | if (cursorPath.length === 0) return;
48 |
49 | var parent = this.getParent(cursorPath);
50 |
51 | if (parent === undefined) return;
52 | var property = cursorPath[cursorPath.length - 1];
53 | var updated = parent.set(property, value);
54 | return updated;
55 | }
56 |
57 | private getParent(cursorPath: Array) {
58 | if (cursorPath.length == 0) return;
59 | var source = this.source;
60 | if (cursorPath.length == 1) return followRef(this.root.get(), source);
61 |
62 | var parentPath = cursorPath.slice(0, cursorPath.length - 1);
63 | var parent = get(source, parentPath);
64 | if (parent !== undefined) return followRef(this.root.get(), parent);
65 |
66 | var updated = this.setWith(source, parentPath, {});
67 | return get(updated, parentPath);
68 |
69 | }
70 |
71 | setWith(object: any, path: Array, value: any) {
72 | const length = path.length;
73 | const lastIndex = length - 1;
74 |
75 | let index = -1;
76 | let nested = object;
77 |
78 | while (nested != null && ++index < length) {
79 | const key = path[index];
80 | let newValue = value;
81 |
82 | if (index != lastIndex) {
83 | const objValue = nested[key];
84 | if (newValue === undefined) {
85 | newValue = isObject(objValue)
86 | ? objValue
87 | : (isIndex(path[index + 1]) ? [] : {});
88 | }
89 | }
90 | //assignValue(nested, key, newValue);
91 | var updated = nested.set(key, newValue);
92 | nested = updated[key];
93 | }
94 | return nested;
95 |
96 | }
97 | }
98 |
99 |
--------------------------------------------------------------------------------
/src/MobxBinder.ts:
--------------------------------------------------------------------------------
1 | import Provider from './MobxProvider';
2 | import { BinderCore } from './Binder';
3 |
4 | export default class Binder {
5 |
6 | static bindTo(parent, path?: string, converter?, converterParams?): any {
7 | return BinderCore.bindTo(Provider, parent, path, converter, converterParams);
8 | }
9 | static bindArrayTo(parent, path?: string, converter?, converterParams?): any {
10 | return BinderCore.bindArrayTo(Provider, parent, path, converter, converterParams);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/MobxProvider.ts:
--------------------------------------------------------------------------------
1 | import { baseSet as set, baseGet as get, castPath, followRef } from './utils';
2 | import { extendObservable, isObservable, autorun, observable, computed } from 'mobx';
3 | import { IPathObjectBinder,Path } from './DataBinding';
4 |
5 |
6 | /**
7 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName")
8 | */
9 | export default class MobxPathObjectBinder implements IPathObjectBinder {
10 |
11 | private root;
12 | private source;
13 | private current;
14 | private previous;
15 | constructor(root: any,source?:any) {
16 | this.root = observable(root);
17 | this.source = source=== undefined?this.root:source;
18 | }
19 | public createNew(path:Path,newItem?:any):IPathObjectBinder{
20 | //var item = followRef(this.root,newItem || this.getValue(path))
21 | return new MobxPathObjectBinder(this.root,newItem || this.getValue(path))
22 | }
23 | public subscribe(updateFce) {
24 | // var previousState;
25 | // if (updateFce !== undefined) autorun(
26 | // () => {
27 | // var current = this.current.get()
28 | // updateFce(current, this.previous);
29 | // this.previous = current;
30 | // });
31 | // //if (updateFce!==undefined) autorun(updateFce);
32 | }
33 |
34 | public getValue(path: Path) {
35 | if (path === undefined) return this.source;
36 | var cursorPath = castPath(path);
37 | if (cursorPath.length === 0) return this.source;
38 |
39 | var parent = this.getParent(cursorPath);
40 | if (parent === undefined) return;
41 |
42 | var property = cursorPath[cursorPath.length -1];
43 |
44 | var value = parent[property];
45 | if (value === undefined && !parent.hasOwnProperty(property)) {
46 | this.setValueAsObservable(parent, property);
47 |
48 | }
49 | return parent[property];
50 |
51 | }
52 |
53 | public setValue(path: Path, value: any) {
54 | if (path === undefined) return;
55 | var cursorPath = castPath(path);
56 | if (cursorPath.length === 0) return;
57 | var parent = this.getParent(cursorPath);
58 | var property = cursorPath[cursorPath.length -1];
59 |
60 | if (isObservable(parent, property)) {
61 | parent[property] = value;
62 | return;
63 | }
64 | this.setValueAsObservable(parent,property,value);
65 |
66 | }
67 | private getParent(cursorPath:Array)
68 | {
69 | if (cursorPath.length == 0) return;
70 | if (cursorPath.length == 1) return followRef(this.root,this.source);
71 | var parentPath = cursorPath.slice(0, cursorPath.length - 1);
72 | var parent = get(this.source, parentPath);
73 | if (parent !== undefined) return followRef(this.root,parent);
74 | set(this.source, parentPath, {}, Object);
75 | return get(this.source,parentPath);
76 | }
77 |
78 | private setValueAsObservable(parent: Object, property: string | number, value?: any) {
79 | var newProps = {};
80 | newProps[property] = value;
81 | extendObservable(parent, newProps);
82 | }
83 |
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/src/PlainObjectProvider.ts:
--------------------------------------------------------------------------------
1 | import { baseSet as set, baseGet as get, castPath, followRef } from './utils';
2 | import { IPathObjectBinder, Path } from './DataBinding';
3 | /**
4 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName")
5 | */
6 | export default class PathObjectBinder implements IPathObjectBinder {
7 |
8 | private source: any;
9 | constructor(private root: any, source?: any) {
10 | this.source = source === undefined ? this.root : source;
11 | }
12 |
13 | public subscribe(updateFce) {
14 | // this.freezer.on('update',function(state,prevState){
15 | // if (updateFce!==undefined) updateFce(state,prevState)}
16 | // );
17 | }
18 | public createNew(path: Path, newItem?: any): IPathObjectBinder {
19 | //var item = followRef(this.root, newItem || this.getValue(path));
20 | return new PathObjectBinder(this.root, newItem || this.getValue(path));
21 | }
22 |
23 | public getValue(path: Path) {
24 | if (path === undefined) return this.source;
25 | var cursorPath = castPath(path);
26 | if (cursorPath.length === 0) return this.source;
27 |
28 | var parent = this.getParent(cursorPath);
29 | if (parent === undefined) return;
30 | var property = cursorPath[cursorPath.length - 1];
31 | return parent[property];
32 |
33 | }
34 |
35 | public setValue(path: Path, value: any) {
36 | if (path === undefined) return;
37 | var cursorPath = castPath(path);
38 | if (cursorPath.length === 0) return;
39 |
40 | var parent = this.getParent(cursorPath);
41 | if (parent === undefined) return;
42 | var property = cursorPath[cursorPath.length - 1];
43 | //console.log(parent);
44 | parent[property] = value;
45 | //console.log(parent);
46 | }
47 | private getParent(cursorPath: Array) {
48 | if (cursorPath.length == 0) return;
49 | if (cursorPath.length == 1) return followRef(this.root, this.source);
50 | var parentPath = cursorPath.slice(0, cursorPath.length - 1);
51 | var parent = get(this.source, parentPath);
52 | if (parent !== undefined) return followRef(this.root, parent);
53 | set(this.source, parentPath, {}, Object);
54 | return get(this.source, parentPath);
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/src/utils-lodash.ts:
--------------------------------------------------------------------------------
1 | // Copyright JS Foundation and other contributors
2 |
3 | // Based on Underscore.js, copyright Jeremy Ashkenas,
4 | // DocumentCloud and Investigative Reporters & Editors
5 |
6 | // This software consists of voluntary contributions made by many
7 | // individuals. For exact contribution history, see the revision history
8 | // available at https://github.com/lodash/lodash
9 |
10 |
11 | /** Used as references for various `Number` constants. */
12 | const MAX_SAFE_INTEGER = 9007199254740991;
13 |
14 | /** Used to detect unsigned integer values. */
15 | const reIsUint = /^(?:0|[1-9]\d*)$/;
16 |
17 | export function isIndex(value: any, length?: number) {
18 | length = length == null ? MAX_SAFE_INTEGER : length;
19 | return !!length &&
20 | (typeof value == 'number' || reIsUint.test(value)) &&
21 | (value > -1 && value % 1 == 0 && value < length);
22 | }
23 | export function isObject(value) {
24 | const type = typeof value;
25 | return value != null && (type == 'object' || type == 'function');
26 | }
27 |
28 | /** Used to match property names within property paths. */
29 | const reLeadingDot = /^\./;
30 | const rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
31 |
32 | /** Used to match backslashes in property paths. */
33 | const reEscapeChar = /\\(\\)?/g;
34 |
35 | /**
36 | * Converts `string` to a property path array.
37 | *
38 | * @private
39 | * @param {string} string The string to convert.
40 | * @returns {Array} Returns the property path array.
41 | */
42 | export function stringToPath(string: string) {
43 | const result = [];
44 | if (reLeadingDot.test(string)) {
45 | result.push('');
46 | }
47 | string.replace(rePropName, (match, number, quote, string): any => {
48 | result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
49 | });
50 | return result;
51 | }
52 |
53 | /** Used to match property names within property paths. */
54 | const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/;
55 | const reIsPlainProp = /^\w*$/;
56 |
57 | /**
58 | * Checks if `value` is a property name and not a property path.
59 | *
60 | * @private
61 | * @param {*} value The value to check.
62 | * @param {Object} [object] The object to query keys on.
63 | * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
64 | */
65 | export function isKey(value, object) {
66 | if (Array.isArray(value)) {
67 | return false;
68 | }
69 | const type = typeof value;
70 | if (type == 'number' || type == 'symbol' || type == 'boolean' ||
71 | value == null || typeof value == 'symbol') {
72 | return true;
73 | }
74 | return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
75 | (object != null && value in Object(object));
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | import { isKey, stringToPath, isObject, isIndex } from './utils-lodash';
3 |
4 | const $ref = "ref";
5 |
6 | export function castPath(value, object?): Array {
7 | if (Array.isArray(value)) {
8 | return value;
9 | }
10 | return isKey(value, object) ? [value] : stringToPath(value == null ? '' : value.toString());
11 | }
12 | export function followRef(root, ref) {
13 | if (ref === undefined) return ref;
14 | return ref.$type === $ref ? baseGet(root, ref.value) : ref;
15 | }
16 | export function baseGet(root: any, path: Array) {
17 | let index = 0;
18 | const length = path.length;
19 | var object = root;
20 | while (object != null && index < length) {
21 | object = followRef(root, object)
22 | object = object[path[index++]];
23 | }
24 | return (index && index == length) ? object : undefined;
25 | }
26 | export function baseSet(object: any, path: Array, value: any, customizer?) {
27 | if (!isObject(object)) {
28 | return object;
29 | }
30 | const length = path.length;
31 | const lastIndex = length - 1;
32 |
33 | let index = -1;
34 | let nested = object;
35 |
36 | while (nested != null && ++index < length) {
37 | const key = path[index];
38 | let newValue = value;
39 | if (index != lastIndex) {
40 | const objValue = nested[key];
41 | newValue = customizer ? customizer(objValue, key, nested) : undefined;
42 | if (newValue === undefined) {
43 | newValue = isObject(objValue)
44 | ? objValue
45 | : (isIndex(path[index + 1]) ? [] : {});
46 | }
47 | }
48 | nested[key] = newValue;
49 | nested = nested[key];
50 | }
51 | return nested;
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/test/BindingReactions.ts:
--------------------------------------------------------------------------------
1 | var Freezer = require('freezer-js');
2 | var expect1 = require('expect.js');
3 |
4 | import MobxBinder from '../src/MobxBinder';
5 | import { PersonConverter } from './utils/converters';
6 | import { observable, reaction } from 'mobx';
7 |
8 |
9 | describe('DataBinding - Mobx reactions', function () {
10 |
11 | var initValues = { firstName: "Roman", lastName: "Samec", email: "email" };
12 | var changedValues = { firstName: "Roman changed", lastName: "Samec changed", email: "email changed" };
13 |
14 | it('bind values and check reactions', function () {
15 | //when
16 | var data = {
17 | Data: {
18 | "Person": {
19 | // "FirstName": initValues.firstName,
20 | // "LastName": initValues.lastName,
21 | // "Contact": {
22 | // "Email": initValues.email
23 | // }
24 | }
25 | }
26 | };
27 |
28 | //exec
29 | var root = MobxBinder.bindTo(data.Data);
30 | //var person = Binder.bindTo(root, "Person");
31 | var firstName = MobxBinder.bindTo(root, "Person.FirstName");
32 | var lastName = MobxBinder.bindTo(root, "Person.LastName");
33 | var email = MobxBinder.bindTo(root, "Person.Contact.Email");
34 |
35 | var converter = new PersonConverter();
36 |
37 | //exec
38 | var fullName = MobxBinder.bindTo(root, "Person", converter);
39 |
40 |
41 |
42 | //verify
43 |
44 | root.source.subscribe((state, previous) => {
45 | //console.log(state, previous)
46 | });
47 |
48 |
49 | reaction("Person converter",
50 | () => { return fullName.value },
51 | (val) => {
52 | //console.log(val)
53 | }, true);
54 |
55 | //verify pathes
56 | expect1(firstName.path.join(".")).to.equal("Person.FirstName");
57 | expect1(lastName.path.join(".")).to.equal("Person.LastName");
58 | expect1(email.path.join(".")).to.equal("Person.Contact.Email");
59 |
60 | //verify value getter
61 | // expect1(firstName.value).to.equal(initValues.firstName);
62 | // expect1(lastName.value).to.equal(initValues.lastName);
63 | // expect1(email.value).to.equal(initValues.email);
64 |
65 | //verify initial values at the source object
66 | // expect1(root.value.Person.FirstName).to.equal(initValues.firstName);
67 | // expect1(root.value.Person.LastName).to.equal(initValues.lastName);
68 | // expect1(root.value.Person.Contact.Email).to.equal(initValues.email);
69 |
70 | //exec -> setter -> change values
71 | firstName.value = initValues.firstName;
72 | lastName.value = initValues.lastName;
73 | email.value = initValues.email;
74 |
75 |
76 | // reaction("Person converter 2",
77 | // () => {
78 | // return fullName.value
79 | // },
80 | // (val) => {console.log(val)},true);
81 |
82 |
83 | firstName.value = changedValues.firstName;
84 | lastName.value = changedValues.lastName;
85 | email.value = changedValues.email;
86 |
87 | //verify value getter
88 | expect1(firstName.value).to.equal(changedValues.firstName);
89 | expect1(lastName.value).to.equal(changedValues.lastName);
90 | expect1(email.value).to.equal(changedValues.email);
91 |
92 | //verify changed values at the source object
93 | expect1(root.value.Person.FirstName).to.equal(changedValues.firstName);
94 | expect1(root.value.Person.LastName).to.equal(changedValues.lastName);
95 | expect1(root.value.Person.Contact.Email).to.equal(changedValues.email);
96 | });
97 | });
98 |
99 | /*
100 | describe('Freezer test', function () {
101 |
102 | // Let's create a freezer object
103 | var freezer = new Freezer({
104 | app: {
105 | human: { 0: {} },
106 | dog: {}
107 | }
108 | });
109 |
110 |
111 | // Listen to changes in the state
112 | freezer.on('update', function (currentState, prevState) {
113 | // currentState will have the new state for your app
114 | // prevState contains the old state, in case you need
115 | // to do some comparisons
116 | console.log('I was updated');
117 | });
118 |
119 | it('bind values and check reactions', function (done) {
120 | // Let's get the frozen data stored
121 |
122 | let state = freezer.get();
123 |
124 | // setTimeout(() => {state.app.human.set(0, { firstName: 'Bernd' })},10);
125 | // setTimeout(() => {state.app.dog.set(99, { name: 'Brutus' })},10);
126 |
127 | setTimeout(() => {
128 |
129 |
130 | state.app.human.set(0, { firstName: 'Bernd' });
131 | state.app.dog.set(99, { name: 'Brutus' });
132 |
133 | //console.log( state.app === freezer.get().app);
134 | console.log(JSON.stringify(freezer.get()));
135 |
136 | //console.log(JSON.stringify(state.app));
137 | setTimeout(() => {
138 | let state = freezer.get();
139 | state.app.human.set(0, Object.assign({}, { lastName: 'Wessels', dog: state.app.dog[99] }, state.app.human[0]));
140 |
141 | setTimeout(() => {
142 | let state = freezer.get();
143 | state.app.dog.set(99, Object.assign({}, { age: 88 }, state.app.dog[99]));
144 |
145 |
146 | //refersh state;
147 | state = freezer.get();
148 |
149 | console.log(JSON.stringify(state));
150 |
151 | //human
152 | expect1(state.app.human[0].dog.name).to.equal("Brutus");
153 | expect1(state.app.human[0].dog.age).to.equal(88)
154 |
155 | //dog
156 | expect1(state.app.dog[99].name).to.equal("Brutus");
157 | expect1(state.app.dog[99].age).to.equal(88);
158 |
159 | done();
160 |
161 | }, 100);
162 |
163 | }, 100);
164 |
165 | }, 100);
166 |
167 |
168 | });
169 | });
170 | describe('Freezer pivot test', function () {
171 |
172 | // Let's create a freezer object
173 | var freezer = new Freezer({
174 | people: {
175 | John: { age: 23 },
176 | Alice: { age: 40 }
177 | }
178 | });
179 |
180 | var state = freezer.get();
181 | var john = state.people.John;
182 | var alice = state.people.Alice;
183 |
184 | state.people.set("Karel", {})
185 |
186 |
187 | it('bind values and check reactions', function (done) {
188 | // Let's get the frozen data stored
189 |
190 | // If we don't pivot, the updated node is returned
191 | // update = freezer.get().people.John.set({ age: 18 });
192 | // console.log(update); // {age: 18}
193 | // Listen to changes in the state
194 | freezer.on('update', function (currentState, prevState) {
195 | // currentState will have the new state for your app
196 | // prevState contains the old state, in case you need
197 | // to do some comparisons
198 | console.log('I was updated');
199 | console.log(currentState);
200 | console.log(freezer.get());
201 |
202 | done();
203 |
204 | });
205 | setTimeout(function () {
206 | john.set({ age: 18 });
207 | }, 10);
208 |
209 |
210 | setTimeout(function () {
211 | alice.set({ age: 30 });
212 | }, 10);
213 |
214 |
215 | // If we want to update two people at
216 | // a time we need to pivot
217 | // var update = freezer.get().people.pivot()
218 | // .John.set({ age: 30 })
219 | // .Alice.set({ age: 30 })
220 | // ;
221 | //console.log(update);
222 |
223 | });
224 | });
225 | */
226 |
227 | describe('Freezer props test', function () {
228 |
229 | var data = observable({
230 | FirstName: 'Roman',
231 | LastName: 'Samec'
232 | });
233 |
234 | // Let's create a freezer object
235 | var freezer = new Freezer({
236 | boxes: [
237 | {
238 | name: 'Input_FirstName',
239 | props: {
240 | valueLink: {}
241 | }
242 | },
243 | {
244 | name: 'Input_LastName',
245 | props: {
246 | valueLink: {}
247 | }
248 | },
249 | {
250 | name: 'LastName',
251 | bindings: {
252 | content: () => { return data.LastName }
253 | },
254 | props: {
255 | //content: 'Samec'
256 | }
257 | },
258 | {
259 | name: 'FullName',
260 | bindings: {
261 | content: () => { return data.FirstName + data.LastName }
262 | },
263 | props: {
264 | //content: 'Roman Samec'
265 | }
266 | }]
267 | });
268 |
269 | var state = freezer.get();
270 |
271 |
272 | it('bind values and check reactions', function (done) {
273 | // Let's get the frozen data stored
274 |
275 | for (var i = 0; i !== state.boxes.length; i++) {
276 | let box = state.boxes[i];
277 | if (box.bindings === undefined) {
278 |
279 | //box.props.set('valueLink',{});
280 | continue;
281 | }
282 | }
283 | state = freezer.get();
284 |
285 |
286 | for (var i = 0; i !== state.boxes.length; i++) {
287 | let currentCursor: Array = ['boxes'];
288 | let box = state.boxes[i];
289 | currentCursor.push(i);
290 | for (let prop in box.bindings) {
291 | let bindingProp = box.bindings[prop];
292 | let props = box.props;
293 | //currentCursor.push(prop);
294 | reaction(box.name, bindingProp,
295 | (val) => {
296 | //get(freezer.get(), currentCursor).set(prop, val);
297 | //props.set(prop, val);
298 | //console.log(currentCursor.join(','))
299 | }, false);
300 | }
301 | }
302 |
303 | // Listen to changes in the state
304 | freezer.on('update', function (currentState, prevState) {
305 | // currentState will have the new state for your app
306 | // prevState contains the old state, in case you need
307 | // to do some comparisons
308 | //console.log('I was updated');
309 | //console.log(currentState);
310 |
311 | //var newState = freezer.get();
312 | var newState = currentState;
313 | //console.log(JSON.stringify(newState));
314 |
315 | //expect1(newState.boxes[2].props.content).to.equal("Smith");
316 | //expect1(newState.boxes[3].props.content).to.equal("RomanSmith");
317 |
318 | //done();
319 |
320 | });
321 |
322 |
323 |
324 | setTimeout(function () {
325 | data.LastName = "Smith";
326 |
327 | setTimeout(function () {
328 | data.LastName = "Smith 2";
329 |
330 | setTimeout(function () {
331 | data.LastName = "Smith 3";
332 |
333 | done();
334 | }, 10);
335 | }, 10);
336 | }, 10);
337 |
338 |
339 |
340 | // If we want to update two people at
341 | // a time we need to pivot
342 | // var update = freezer.get().people.pivot()
343 | // .John.set({ age: 30 })
344 | // .Alice.set({ age: 30 })
345 | // ;
346 | //console.log(update);
347 |
348 | });
349 | });
350 |
--------------------------------------------------------------------------------
/test/utils/converters.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var DateValueConverter = (function () {
3 | function DateValueConverter() {
4 | }
5 | DateValueConverter.prototype.format = function (value) {
6 | if (value === undefined)
7 | return value;
8 | return value.toISOString().slice(0, 10);
9 | };
10 | DateValueConverter.prototype.parse = function (value) {
11 | if (value === undefined)
12 | return value;
13 | var regPattern = "\\d{4}\\/\\d{2}\\/\\d{2}";
14 | var dateString = value.match(regPattern);
15 | return new Date(dateString);
16 | };
17 | return DateValueConverter;
18 | }());
19 | exports.DateValueConverter = DateValueConverter;
20 | var PersonConverter = (function () {
21 | function PersonConverter() {
22 | }
23 | PersonConverter.prototype.format = function (value) {
24 | if (value === undefined)
25 | return value;
26 | return value.FirstName + ' ' + value.LastName;
27 | };
28 | return PersonConverter;
29 | }());
30 | exports.PersonConverter = PersonConverter;
31 | var DateValueSuffixConverter = (function () {
32 | function DateValueSuffixConverter() {
33 | }
34 | DateValueSuffixConverter.prototype.format = function (value, parameters) {
35 | if (value === undefined)
36 | return value;
37 | if (parameters === undefined)
38 | parameters = "";
39 | return value.toISOString().slice(0, 10) + parameters;
40 | };
41 | DateValueSuffixConverter.prototype.parse = function (value) {
42 | if (value === undefined)
43 | return value;
44 | var regPattern = "\\d{4}\\/\\d{2}\\/\\d{2}";
45 | var dateString = value.match(regPattern);
46 | return new Date(dateString);
47 | };
48 | return DateValueSuffixConverter;
49 | }());
50 | exports.DateValueSuffixConverter = DateValueSuffixConverter;
51 | ;
52 | var ArraySizeConverter = (function () {
53 | function ArraySizeConverter() {
54 | }
55 | ArraySizeConverter.prototype.format = function (count) {
56 | return Array.apply(0, Array(count))
57 | .map(function (element, index) {
58 | return { pageIndex: index };
59 | });
60 | };
61 | return ArraySizeConverter;
62 | }());
63 | exports.ArraySizeConverter = ArraySizeConverter;
64 | var ArrayConverter = (function () {
65 | function ArrayConverter() {
66 | }
67 | ArrayConverter.prototype.format = function (input) {
68 | return !!input ? input.length : 0;
69 | };
70 | ArrayConverter.prototype.parse = function (count) {
71 | return Array.apply(0, Array(count))
72 | .map(function (element, index) {
73 | return {
74 | Person: {
75 | "FirstName": "Roman " + index,
76 | "LastName": "Samec " + index, Addresses: []
77 | }
78 | };
79 | });
80 | };
81 | return ArrayConverter;
82 | }());
83 | exports.ArrayConverter = ArrayConverter;
84 |
--------------------------------------------------------------------------------
/test/utils/converters.ts:
--------------------------------------------------------------------------------
1 |
2 | export class DateValueConverter {
3 | format(value) {
4 | if (value === undefined) return value;
5 | return value.toISOString().slice(0, 10);
6 | }
7 |
8 | parse(value) {
9 | if (value === undefined) return value;
10 | var regPattern = "\\d{4}\\/\\d{2}\\/\\d{2}";
11 | var dateString = value.match(regPattern);
12 | return new Date(dateString);
13 | }
14 | }
15 |
16 | export class PersonConverter {
17 | format(value) {
18 | if (value === undefined) return value;
19 | return value.FirstName + ' ' + value.LastName;
20 | }
21 | }
22 |
23 | export class DateValueSuffixConverter {
24 | format(value, parameters) {
25 | if (value === undefined) return value;
26 | if (parameters === undefined) parameters = "";
27 | return value.toISOString().slice(0, 10) + parameters;
28 | }
29 |
30 | parse(value) {
31 | if (value === undefined) return value;
32 | var regPattern = "\\d{4}\\/\\d{2}\\/\\d{2}";
33 | var dateString = value.match(regPattern);
34 | return new Date(dateString);
35 | }
36 | };
37 |
38 | export class ArraySizeConverter {
39 | format(count) {
40 | return Array.apply(0, Array(count))
41 | .map(function (element, index) {
42 | return { pageIndex: index };
43 | });
44 | }
45 | }
46 | export class ArrayConverter {
47 | format(input) {
48 | return !!input ? input.length : 0;
49 | }
50 | parse(count) {
51 | return Array.apply(0, Array(count))
52 | .map(function (element, index) {
53 | return {
54 | Person: {
55 | "FirstName": "Roman " + index,
56 | "LastName": "Samec " + index, Addresses: []
57 | }
58 | };
59 | });
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "watch": true
5 | //"experimentalDecorators": true
6 | }
7 | }
--------------------------------------------------------------------------------
/tsd.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "v4",
3 | "repo": "borisyankov/DefinitelyTyped",
4 | "ref": "master",
5 | "path": "typings",
6 | "bundle": "typings/tsd.d.ts",
7 | "installed": {
8 | "mocha/mocha.d.ts": {
9 | "commit": "49b821f06b711a86be349c4bbff4351467c024b9"
10 | },
11 | "node/node.d.ts": {
12 | "commit": "49b821f06b711a86be349c4bbff4351467c024b9"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------