├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── dist ├── README.md ├── definitions │ ├── Binder.d.ts │ ├── DataBinding.d.ts │ ├── FreezerBinder.d.ts │ ├── FreezerProvider.d.ts │ ├── MobxBinder.d.ts │ ├── MobxProvider.d.ts │ ├── PlainObjectProvider.d.ts │ ├── utils-lodash.d.ts │ └── utils.d.ts ├── lib │ ├── Binder.js │ ├── DataBinding.js │ ├── FreezerBinder.js │ ├── FreezerProvider.js │ ├── MobxBinder.js │ ├── MobxProvider.js │ ├── PlainObjectProvider.js │ ├── utils-lodash.js │ └── utils.js ├── package.json ├── react-binding.js └── react-binding.min.js ├── gulpfile.js ├── package.json ├── src ├── Binder.ts ├── DataBinding.ts ├── FreezerBinder.ts ├── FreezerProvider.ts ├── MobxBinder.ts ├── MobxProvider.ts ├── PlainObjectProvider.ts ├── utils-lodash.ts └── utils.ts ├── test ├── BindingReactions.ts ├── DataBinding.ts ├── DataBindingPerf.ts └── utils │ ├── converters.js │ └── converters.ts ├── tsconfig.json └── tsd.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | .gulp 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | # Commenting this out is preferred by some people, see 25 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 26 | node_modules 27 | 28 | # TypeScript definitons 29 | typings 30 | 31 | # Users Environment Variables 32 | .lock-wscript 33 | 34 | #JetBrains WebStorm 35 | .idea 36 | 37 | #TypeDoc 38 | out 39 | 40 | #Visual studio code 41 | launch.json 42 | settings.json 43 | tasks.json 44 | 45 | #Typescript Compiled 46 | src/*.js 47 | test/*.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Roman Samec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-binding 2 | 3 | React-binding is lightweight utility for two-way data binding in [React][react]. 4 | 5 | ``` js 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom'; 8 | import Binder from 'react-binding'; 9 | 10 | export default class Form extends React.Component { 11 | constructor(props){ 12 | super(props); 13 | this.state = {data: {}}; 14 | }, 15 | render { 16 | return ( 17 |
18 | 19 |
FirstName: {this.state.data.Employee.FirstName}
20 |
21 | )} 22 | }); 23 | 24 | ReactDOM.render( 25 |
, 26 | document.getElementById('content') 27 | ); 28 | 29 | ``` 30 | ## Features: 31 | 32 | + No dependencies. 33 | + Minimal interface - using path with dot notation. 34 | + Support for complex objects. 35 | + Support for collection-based structures - arrays and lists. 36 | + Support for value converters. 37 | + No need to define initial values, nested structures. Binder creates this for you. 38 | + Support concept for references to allow JSON to be used to represent graph information. 39 | 40 | [react-binding](https://github.com/rsamec/react-binding) offers two-way data binding support for: 41 | 42 | + object properties with path expression (dot notation) 43 | + Binder.bindToState(this,"data","__Employee.FirstName__"); 44 | + Binder.bindToState(this,"data","__Employee.Contact.Email__"); 45 | + complex objects (json) with __nested properties__ 46 | + Binder.bindTo(__employee__,"__FirstName__"); 47 | + Binder.bindTo(__employee__,"__Contact.Email__"); 48 | + collection-based structures - arrays and lists 49 | + model={Binder.bindArrayToState(this,"data","__Hobbies__")} 50 | + this.props.model.__items__.map(function(item){ return ();}) 51 | + this.props.model.__add()__ 52 | + this.props.model.__remove(item)__ 53 | + supports for "value/requestChange" interface also to enable to use [ReactLink][valueLink] attribute 54 | + valueLink={Binder.bindTo(employee,"FirstName")} 55 | + enables binding with value converters 56 | + supports both directions - __format__ (toView) and __parse__ (fromView) 57 | + support for converter parameter - valueLink={Binder.bindToState(this,"data", "Duration.From",__converter, "DD.MM.YYYY"__)} 58 | + converter parameter can be data-bound - valueLink={Binder.bindToState(this,"data", "Duration.From",converter, __this.state.format__)} 59 | + usable with any css frameworks 60 | + [react-bootstrap][reactBootstrap] 61 | + [material-ui][materialUi] 62 | 63 | # Basic principle 64 | 65 | Each bindTo return and uses interface called "value/onChange". 66 | Each bindTo component is passed a value (to render it to UI) as well as setter to a value that triggers a re-render (typically at the top location). 67 | 68 | The re-render is done in the component where you bind to the state via (bindToState, bindArrayToState). 69 | 70 | BindTo can be nested - composed to support components composition. Then path is concatenate according to parent-child relationship. 71 | 72 | 73 | # Get started 74 | 75 | * [node package manager][npm] 76 | ``` js 77 | npm install react-binding 78 | ``` 79 | 80 | * [client-side code package manager][bower] 81 | ``` js 82 | bower install react-binding 83 | ``` 84 | 85 | * [bundling with browserify][browserify] 86 | ``` js 87 | npm install -g browserify 88 | npm install reactify 89 | browserify ./index.js > bundle.js 90 | ``` 91 | 92 | __minimal example__ 93 | 94 | ``` js 95 | import React from 'react'; 96 | import ReactDOM from 'react-dom'; 97 | import Binder from 'react-binding'; 98 | 99 | export default class Form extends React.Component { 100 | constructor(props){ 101 | super(props); 102 | this.state = {data: {}}; 103 | }, 104 | render { 105 | return ( 106 |
107 | 108 |
FirstName: {this.state.data.Employee.FirstName}
109 |
110 | )} 111 | }); 112 | 113 | ReactDOM.render( 114 | , 115 | document.getElementById('content') 116 | ); 117 | 118 | ``` 119 | 120 | __Note__: React-binding as mixins - use npm install react-binding@0.6.4 121 | 122 | # Overview 123 | 124 | ### bindToState(key,pathExpression) 125 | 126 | It enables to bind to object property with path expression 127 | 128 | + using [ReactLink][valueLink] 129 | ``` js 130 | 131 | ``` 132 | 133 | + without [ReactLink][valueLink] 134 | 135 | ``` js 136 | 137 | ``` 138 | 139 | ``` js 140 | var TextBoxInput = React.createClass({ 141 | render: function() { 142 | var valueModel = this.props.model; 143 | var handleChange = function(e){ 144 | valueModel.value = e.target.value; 145 | } 146 | return ( 147 | 148 | ) 149 | } 150 | }); 151 | ``` 152 | 153 | ### bindTo(parent,pathExpression) 154 | 155 | It enables to bind to complex object with nested properties and reuse bindings in components. 156 | 157 | + binding to state at root level 158 | ``` js 159 | 160 | 161 | ``` 162 | 163 | + binding to parent 164 | ``` js 165 | 166 | ``` 167 | 168 | + reuse bindings in component 169 | ``` js 170 | var PersonComponent = React.createClass({ 171 | render: function() { 172 | return ( 173 |
174 | 175 | 176 | 177 |
178 | ); 179 | } 180 | }); 181 | 182 | ``` 183 | 184 | ### bindArrayToState(key,pathExpression) 185 | 186 | It enables binding to collection-based structures (array). It enables to add and remove items. 187 | 188 | + binding to array 189 | 190 | ``` js 191 | 192 | ``` 193 | 194 | + access items (this.props.model.items) 195 | 196 | ``` js 197 | var HobbyList = React.createClass({ 198 | render: function() { 199 | if (this.props.model.items === undefined) return There are no items.; 200 | 201 | var hobbies = this.props.model.items.map(function(hobby, index) { 202 | return ( 203 | 204 | ); 205 | },this); 206 | return ( 207 |
{hobbies}
208 | ); 209 | } 210 | }); 211 | 212 | ``` 213 | + add new items (this.props.model.add(newItem?)) 214 | ``` js 215 | handleAdd: function(){ 216 | return this.props.model.add(); 217 | }, 218 | ``` 219 | + remove exiting items (this.props.model.props.delete(item)) 220 | ``` js 221 | handleDelete: function(hobby){ 222 | return this.props.model.remove(hobby); 223 | }, 224 | ``` 225 | 226 | ### bindArrayTo(parent,pathExpression) 227 | 228 | It enables binding to collection-based structures (array) for nested arrays. It enables to add and remove items. 229 | 230 | + binding to array 231 | 232 | ``` js 233 | 234 | ``` 235 | 236 | + access items (this.props.model.items) 237 | 238 | ``` js 239 | var HobbyList = React.createClass({ 240 | render: function() { 241 | if (this.props.model.items === undefined) return There are no items.; 242 | 243 | var hobbies = this.props.model.items.map(function(hobby, index) { 244 | return ( 245 | 246 | ); 247 | },this); 248 | return ( 249 |
{hobbies}
250 | ); 251 | } 252 | }); 253 | 254 | ``` 255 | + add new items (this.props.model.add(newItem?)) 256 | ``` js 257 | handleAdd: function(){ 258 | return this.props.model.add(); 259 | }, 260 | ``` 261 | + remove exiting items (this.props.model.props.delete(item)) 262 | ``` js 263 | handleDelete: function(hobby){ 264 | return this.props.model.remove(hobby); 265 | }, 266 | ``` 267 | ### Value converters 268 | 269 | 270 | Value converters 271 | 272 | + format - translates data to a format suitable for the view 273 | + parse - convert data from the view to a format expected by your data (typically when using two-way binding with input elements to data). 274 | 275 | Example - date converter -> using parameters 'dateFormat' is optional 276 | 277 | ``` js 278 | var dateConverter = function() { 279 | this.parse = function (input, dateFormat) { 280 | if (!!!input) return undefined; 281 | if (input.length < 8) return undefined; 282 | var date = moment(input, dateFormat); 283 | if (date.isValid()) return date.toDate(); 284 | return undefined; 285 | } 286 | this.format = function (input,dateFormat) { 287 | if (!!!input) return undefined; 288 | return moment(input).format(dateFormat); 289 | } 290 | } 291 | ``` 292 | 293 | using converter 294 | 295 | ``` js 296 | 297 | 298 | ``` 299 | 300 | [try in Plunker](http://embed.plnkr.co/gGWe82wT2JJflZt095Gk/preview) 301 | 302 | ### References 303 | 304 | JSON models trees, and most application domains are graphs. Binding supports concept for references to allow JSON to be used to represent graph information. 305 | 306 | + Each entity is inserted into a single, globally unique location in the JSON with a unique identifier. 307 | + Each reference is an object of the $type='ref' and must contain value as path to single, globally unique location in the JSON - {$type:'ref',value:['todosById',44]} 308 | 309 | 310 | ``` js 311 | { 312 | todosById: { 313 | "44": { 314 | name: "get milk from corner store", 315 | done: false, 316 | prerequisites: [{ $type: "ref", value: ["todosById", 54] }] 317 | }, 318 | "54": { 319 | name: "withdraw money from ATM", 320 | done: false, 321 | prerequisites: [] 322 | } 323 | }, 324 | todos: [ 325 | { $type: "ref", value: ["todosById", 44] }, 326 | { $type: "ref", value: ["todosById", 54] } 327 | ] 328 | }; 329 | ``` 330 | 331 | # Examples 332 | 333 | hobby form - data binding only 334 | 335 | + no UI framework - [try in Plunker](http://embed.plnkr.co/aTilRFEJe0gEWaZzr8PC/preview) 336 | + with react-bootstrap - [try in Plunker](http://embed.plnkr.co/7tumC62YO8GixKEMhJcw/preview) 337 | 338 | hobby form with validation using [business-rules-engine][bre] 339 | 340 | + no UI framework - [try in Plunker](http://embed.plnkr.co/qXlUQ7a3YLEypwT2vvSb/preview) 341 | + with react-bootstrap - [try in Plunker](http://embed.plnkr.co/6hoCCd7Bl1PHnb57rQbT/preview) 342 | + with material-ui 343 | + [demo](http://polymer-formvalidation.rhcloud.com/dist/index.html) 344 | + [sources](https://github.com/rsamec/react-hobby-form-app) 345 | 346 | value converters 347 | 348 | + date picker - [try in Plunker](http://embed.plnkr.co/gGWe82wT2JJflZt095Gk/preview) 349 | 350 | ## Contact 351 | 352 | For more information on react-binding please check out [my blog][blog]. 353 | 354 | 355 | [git]: http://git-scm.com/ 356 | [bower]: http://bower.io 357 | [npm]: https://www.npmjs.org/ 358 | [node]: http://nodejs.org 359 | [browserify]: http://browserify.org/ 360 | [blog]: http://rsamec.github.io/ 361 | [valueLink]: http://facebook.github.io/react/docs/two-way-binding-helpers.html 362 | [materialUi]: https://github.com/callemall/material-ui 363 | [reactBootstrap]: http://react-bootstrap.github.io/ 364 | [bre]: https://github.com/rsamec/business-rules-engine 365 | [react]: http://facebook.github.io/react/ 366 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-binding", 3 | "version": "0.8.1", 4 | "homepage": "https://github.com/rsamec/react-binding", 5 | "authors": [ 6 | "Roman Samec " 7 | ], 8 | "description": "BindToMixin - react data binding for complex objects and arrays.", 9 | "main": "dist/react-binding", 10 | "moduleType": [ 11 | "globals", 12 | "node" 13 | ], 14 | "keywords": [ 15 | "react", 16 | "binding" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests", 25 | "typings", 26 | "src", 27 | "tsd.json", 28 | "out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | # react-binding 2 | 3 | React-binding is lightweight utility for two-way data binding in [React][react]. 4 | 5 | ``` js 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom'; 8 | import Binder from 'react-binding'; 9 | 10 | export default class Form extends React.Component { 11 | constructor(props){ 12 | super(props); 13 | this.state = {data: {}}; 14 | }, 15 | render { 16 | return ( 17 |
18 | 19 |
FirstName: {this.state.data.Employee.FirstName}
20 |
21 | )} 22 | }); 23 | 24 | ReactDOM.render( 25 | , 26 | document.getElementById('content') 27 | ); 28 | 29 | ``` 30 | ## Features: 31 | 32 | + No dependencies. 33 | + Minimal interface - using path with dot notation. 34 | + Support for complex objects. 35 | + Support for collection-based structures - arrays and lists. 36 | + Support for value converters. 37 | + No need to define initial values, nested structures. Binder creates this for you. 38 | + Support concept for references to allow JSON to be used to represent graph information. 39 | 40 | [react-binding](https://github.com/rsamec/react-binding) offers two-way data binding support for: 41 | 42 | + object properties with path expression (dot notation) 43 | + Binder.bindToState(this,"data","__Employee.FirstName__"); 44 | + Binder.bindToState(this,"data","__Employee.Contact.Email__"); 45 | + complex objects (json) with __nested properties__ 46 | + Binder.bindTo(__employee__,"__FirstName__"); 47 | + Binder.bindTo(__employee__,"__Contact.Email__"); 48 | + collection-based structures - arrays and lists 49 | + model={Binder.bindArrayToState(this,"data","__Hobbies__")} 50 | + this.props.model.__items__.map(function(item){ return ();}) 51 | + this.props.model.__add()__ 52 | + this.props.model.__remove(item)__ 53 | + supports for "value/requestChange" interface also to enable to use [ReactLink][valueLink] attribute 54 | + valueLink={Binder.bindTo(employee,"FirstName")} 55 | + enables binding with value converters 56 | + supports both directions - __format__ (toView) and __parse__ (fromView) 57 | + support for converter parameter - valueLink={Binder.bindToState(this,"data", "Duration.From",__converter, "DD.MM.YYYY"__)} 58 | + converter parameter can be data-bound - valueLink={Binder.bindToState(this,"data", "Duration.From",converter, __this.state.format__)} 59 | + usable with any css frameworks 60 | + [react-bootstrap][reactBootstrap] 61 | + [material-ui][materialUi] 62 | 63 | # Basic principle 64 | 65 | Each bindTo return and uses interface called "value/onChange". 66 | Each bindTo component is passed a value (to render it to UI) as well as setter to a value that triggers a re-render (typically at the top location). 67 | 68 | The re-render is done in the component where you bind to the state via (bindToState, bindArrayToState). 69 | 70 | BindTo can be nested - composed to support components composition. Then path is concatenate according to parent-child relationship. 71 | 72 | 73 | # Get started 74 | 75 | * [node package manager][npm] 76 | ``` js 77 | npm install react-binding 78 | ``` 79 | 80 | * [client-side code package manager][bower] 81 | ``` js 82 | bower install react-binding 83 | ``` 84 | 85 | * [bundling with browserify][browserify] 86 | ``` js 87 | npm install -g browserify 88 | npm install reactify 89 | browserify ./index.js > bundle.js 90 | ``` 91 | 92 | __minimal example__ 93 | 94 | ``` js 95 | import React from 'react'; 96 | import ReactDOM from 'react-dom'; 97 | import Binder from 'react-binding'; 98 | 99 | export default class Form extends React.Component { 100 | constructor(props){ 101 | super(props); 102 | this.state = {data: {}}; 103 | }, 104 | render { 105 | return ( 106 |
107 | 108 |
FirstName: {this.state.data.Employee.FirstName}
109 |
110 | )} 111 | }); 112 | 113 | ReactDOM.render( 114 | , 115 | document.getElementById('content') 116 | ); 117 | 118 | ``` 119 | 120 | __Note__: React-binding as mixins - use npm install react-binding@0.6.4 121 | 122 | # Overview 123 | 124 | ### bindToState(key,pathExpression) 125 | 126 | It enables to bind to object property with path expression 127 | 128 | + using [ReactLink][valueLink] 129 | ``` js 130 | 131 | ``` 132 | 133 | + without [ReactLink][valueLink] 134 | 135 | ``` js 136 | 137 | ``` 138 | 139 | ``` js 140 | var TextBoxInput = React.createClass({ 141 | render: function() { 142 | var valueModel = this.props.model; 143 | var handleChange = function(e){ 144 | valueModel.value = e.target.value; 145 | } 146 | return ( 147 | 148 | ) 149 | } 150 | }); 151 | ``` 152 | 153 | ### bindTo(parent,pathExpression) 154 | 155 | It enables to bind to complex object with nested properties and reuse bindings in components. 156 | 157 | + binding to state at root level 158 | ``` js 159 | 160 | 161 | ``` 162 | 163 | + binding to parent 164 | ``` js 165 | 166 | ``` 167 | 168 | + reuse bindings in component 169 | ``` js 170 | var PersonComponent = React.createClass({ 171 | render: function() { 172 | return ( 173 |
174 | 175 | 176 | 177 |
178 | ); 179 | } 180 | }); 181 | 182 | ``` 183 | 184 | ### bindArrayToState(key,pathExpression) 185 | 186 | It enables binding to collection-based structures (array). It enables to add and remove items. 187 | 188 | + binding to array 189 | 190 | ``` js 191 | 192 | ``` 193 | 194 | + access items (this.props.model.items) 195 | 196 | ``` js 197 | var HobbyList = React.createClass({ 198 | render: function() { 199 | if (this.props.model.items === undefined) return There are no items.; 200 | 201 | var hobbies = this.props.model.items.map(function(hobby, index) { 202 | return ( 203 | 204 | ); 205 | },this); 206 | return ( 207 |
{hobbies}
208 | ); 209 | } 210 | }); 211 | 212 | ``` 213 | + add new items (this.props.model.add(newItem?)) 214 | ``` js 215 | handleAdd: function(){ 216 | return this.props.model.add(); 217 | }, 218 | ``` 219 | + remove exiting items (this.props.model.props.delete(item)) 220 | ``` js 221 | handleDelete: function(hobby){ 222 | return this.props.model.remove(hobby); 223 | }, 224 | ``` 225 | 226 | ### bindArrayTo(parent,pathExpression) 227 | 228 | It enables binding to collection-based structures (array) for nested arrays. It enables to add and remove items. 229 | 230 | + binding to array 231 | 232 | ``` js 233 | 234 | ``` 235 | 236 | + access items (this.props.model.items) 237 | 238 | ``` js 239 | var HobbyList = React.createClass({ 240 | render: function() { 241 | if (this.props.model.items === undefined) return There are no items.; 242 | 243 | var hobbies = this.props.model.items.map(function(hobby, index) { 244 | return ( 245 | 246 | ); 247 | },this); 248 | return ( 249 |
{hobbies}
250 | ); 251 | } 252 | }); 253 | 254 | ``` 255 | + add new items (this.props.model.add(newItem?)) 256 | ``` js 257 | handleAdd: function(){ 258 | return this.props.model.add(); 259 | }, 260 | ``` 261 | + remove exiting items (this.props.model.props.delete(item)) 262 | ``` js 263 | handleDelete: function(hobby){ 264 | return this.props.model.remove(hobby); 265 | }, 266 | ``` 267 | ### Value converters 268 | 269 | 270 | Value converters 271 | 272 | + format - translates data to a format suitable for the view 273 | + parse - convert data from the view to a format expected by your data (typically when using two-way binding with input elements to data). 274 | 275 | Example - date converter -> using parameters 'dateFormat' is optional 276 | 277 | ``` js 278 | var dateConverter = function() { 279 | this.parse = function (input, dateFormat) { 280 | if (!!!input) return undefined; 281 | if (input.length < 8) return undefined; 282 | var date = moment(input, dateFormat); 283 | if (date.isValid()) return date.toDate(); 284 | return undefined; 285 | } 286 | this.format = function (input,dateFormat) { 287 | if (!!!input) return undefined; 288 | return moment(input).format(dateFormat); 289 | } 290 | } 291 | ``` 292 | 293 | using converter 294 | 295 | ``` js 296 | 297 | 298 | ``` 299 | 300 | [try in Plunker](http://embed.plnkr.co/gGWe82wT2JJflZt095Gk/preview) 301 | 302 | ### References 303 | 304 | JSON models trees, and most application domains are graphs. Binding supports concept for references to allow JSON to be used to represent graph information. 305 | 306 | + Each entity is inserted into a single, globally unique location in the JSON with a unique identifier. 307 | + Each reference is an object of the $type='ref' and must contain value as path to single, globally unique location in the JSON - {$type:'ref',value:['todosById',44]} 308 | 309 | 310 | ``` js 311 | { 312 | todosById: { 313 | "44": { 314 | name: "get milk from corner store", 315 | done: false, 316 | prerequisites: [{ $type: "ref", value: ["todosById", 54] }] 317 | }, 318 | "54": { 319 | name: "withdraw money from ATM", 320 | done: false, 321 | prerequisites: [] 322 | } 323 | }, 324 | todos: [ 325 | { $type: "ref", value: ["todosById", 44] }, 326 | { $type: "ref", value: ["todosById", 54] } 327 | ] 328 | }; 329 | ``` 330 | 331 | # Examples 332 | 333 | hobby form - data binding only 334 | 335 | + no UI framework - [try in Plunker](http://embed.plnkr.co/aTilRFEJe0gEWaZzr8PC/preview) 336 | + with react-bootstrap - [try in Plunker](http://embed.plnkr.co/7tumC62YO8GixKEMhJcw/preview) 337 | 338 | hobby form with validation using [business-rules-engine][bre] 339 | 340 | + no UI framework - [try in Plunker](http://embed.plnkr.co/qXlUQ7a3YLEypwT2vvSb/preview) 341 | + with react-bootstrap - [try in Plunker](http://embed.plnkr.co/6hoCCd7Bl1PHnb57rQbT/preview) 342 | + with material-ui 343 | + [demo](http://polymer-formvalidation.rhcloud.com/dist/index.html) 344 | + [sources](https://github.com/rsamec/react-hobby-form-app) 345 | 346 | value converters 347 | 348 | + date picker - [try in Plunker](http://embed.plnkr.co/gGWe82wT2JJflZt095Gk/preview) 349 | 350 | ## Contact 351 | 352 | For more information on react-binding please check out [my blog][blog]. 353 | 354 | 355 | [git]: http://git-scm.com/ 356 | [bower]: http://bower.io 357 | [npm]: https://www.npmjs.org/ 358 | [node]: http://nodejs.org 359 | [browserify]: http://browserify.org/ 360 | [blog]: http://rsamec.github.io/ 361 | [valueLink]: http://facebook.github.io/react/docs/two-way-binding-helpers.html 362 | [materialUi]: https://github.com/callemall/material-ui 363 | [reactBootstrap]: http://react-bootstrap.github.io/ 364 | [bre]: https://github.com/rsamec/business-rules-engine 365 | [react]: http://facebook.github.io/react/ 366 | -------------------------------------------------------------------------------- /dist/definitions/Binder.d.ts: -------------------------------------------------------------------------------- 1 | import { IPathObjectBinder, IPathObjectBinding, IValueConverter, ArrayObjectBinding } from './DataBinding'; 2 | export declare class BinderCore { 3 | static bindTo(type: { 4 | new (data): IPathObjectBinder; 5 | }, parent: any, path?: string, converter?: any, converterParams?: any): IPathObjectBinding; 6 | static bindArrayTo(type: { 7 | new (data): IPathObjectBinder; 8 | }, parent: any, path?: string, converter?: any, converterParams?: any): any; 9 | } 10 | /** 11 | * React [LinkedStateMixin](http://facebook.github.io/react/docs/two-way-binding-helpers.html) is an easy way to express two-way data binding in React. 12 | * 13 | * React-binding comes with utility [Binder](https://github.com/rsamec/react-binding) for two-way binding that supports binding to 14 | * 15 | * + object properties with path expression (dot notation) 16 | * + this.bindToState("data","Employee.FirstName"); 17 | * + this.bindToState("data","Employee.Contact.Email"); 18 | * + complex objects (json) with nested properties 19 | * + this.bindTo(employee,"FirstName"); 20 | * + this.bindTo(employee,"Contact.Email"); 21 | * + collection-based structures - arrays and lists 22 | * + model={this.bindTo(employee,"FirstName")} 23 | * + this.props.model.items.map(function(item){ return ();}) 24 | * + this.props.model.add() 25 | * + this.props.model.remove(item) 26 | * + supports for "value/requestChange" interface also to enable to use [ReactLink][valueLink] attribute 27 | * + valueLink={this.bindTo(employee,"FirstName")} 28 | * + enables binding with value converters 29 | * + supports both directions - format (toView) and parse (fromView) 30 | * + support for converter parameter - valueLink={this.bindToState("data", "Duration.From",converter, "DD.MM.YYYY")} 31 | * + converter parameter can be data-bound - valueLink={this.bindToState("data", "Duration.From",converter, this.state.format)} 32 | * + usable with any css frameworks - 33 | * + react-bootstrap 34 | * + material-ui 35 | * 36 | */ 37 | export default class Binder { 38 | static createStateKeySetter(component: any, key: any): (value?: any) => void; 39 | /** 40 | * It enables to bind to object property with path expression 41 | * + using [valueLink](http://facebook.github.io/react/docs/two-way-binding-helpers.html) 42 | * ``` js 43 | * 44 | * ``` 45 | * 46 | * + without [valueLink](http://facebook.github.io/react/docs/two-way-binding-helpers.html) 47 | * ``` js 48 | * 49 | * ``` 50 | * 51 | * ``` js 52 | * var TextBoxInput = React.createClass({ 53 | * render: function() { 54 | * var valueModel = this.props.model; 55 | * var handleChange = function(e){ 56 | * valueModel.value = e.target.value; 57 | * } 58 | * return ( 59 | * 60 | * )} 61 | * }); 62 | * ``` 63 | * 64 | * @param key - property name in state (this.state[key]) 65 | * @param path - expression to bind to property 66 | * @param converter {DataBinding.IValueConverter} - value converter 67 | * @param converterParams - parameters used by converter 68 | * @returns {DataBinding.PathObjectBinding} 69 | */ 70 | static bindToState(component: any, key: string, path?: string, converter?: IValueConverter, converterParams?: any): IPathObjectBinding; 71 | /** 72 | * It enables to bind to complex object with nested properties and reuse bindings in components. 73 | * 74 | * + binding to state at root level 75 | * 76 | * ``` js 77 | * 78 | * 79 | * ``` 80 | * 81 | * + binding to parent 82 | * 83 | * ``` js 84 | * 85 | * ``` 86 | * 87 | * + reuse bindings in component 88 | * 89 | * ``` js 90 | * var PersonComponent = React.createClass({ 91 | * mixins:[BindToMixin], 92 | * render: function() { 93 | * return ( 94 | *
95 | * 96 | * 97 | * 98 | *
99 | * ); 100 | * } 101 | * }); 102 | * 103 | * ``` 104 | * 105 | * @param parent - the parent object 106 | * @param path - expression to bind to property 107 | * @param converter - value converter {DataBinding.IValueConverter} 108 | * @param converterParams - parameters used by converter 109 | * @returns {DataBinding.PathParentBinding} 110 | */ 111 | static bindTo(parent: any, path?: string, converter?: any, converterParams?: any): IPathObjectBinding; 112 | /** 113 | * It enables binding to collection-based structures (array). It enables to add and remove items. 114 | * 115 | * + binding to array 116 | * 117 | * ``` js 118 | * 119 | * ``` 120 | * 121 | * @param key - property name in state (this.state[key]) - it must be array 122 | * @param path - expression to array to bind to property 123 | * @returns {DataBinding.ArrayObjectBinding} 124 | */ 125 | static bindArrayToState(component: any, key: string, path?: string): ArrayObjectBinding; 126 | /** 127 | * It enables binding to collection-based structures (array) for nested arrays. It enables to add and remove items. 128 | * 129 | * + binding to parent 130 | * 131 | * ``` js 132 | * 133 | * ``` 134 | * 135 | * @param parent - the parent object 136 | * @param path - expression to bind to property - relative path from parent 137 | * @returns {DataBinding.PathParentBinding} 138 | */ 139 | static bindArrayTo(parent: any, path?: string, converter?: any, converterParams?: any): any; 140 | } 141 | -------------------------------------------------------------------------------- /dist/definitions/DataBinding.d.ts: -------------------------------------------------------------------------------- 1 | export interface BinderStatic { 2 | bindToState?(data: any, key: string, path?: Path, converter?: IValueConverter, converterParams?: any): ObjectBinding; 3 | bindTo(parent: any, path?: Path, converter?: IValueConverter, converterParams?: any): ObjectBinding; 4 | bindArrayToState?(data: any, key: string, path?: Path, converter?: IValueConverter, converterParams?: any): ArrayBinding; 5 | bindArrayTo(parent: any, path?: Path, converter?: IValueConverter, converterParams?: any): ArrayBinding; 6 | } 7 | export interface Binding { 8 | path?: Array; 9 | parent: Binding; 10 | root: Binding; 11 | } 12 | export interface ObjectBinding extends Binding { 13 | value: any; 14 | } 15 | export interface ArrayBinding extends Binding { 16 | items: Array; 17 | add(defautItem?: any): any; 18 | remove(itemToRemove: any): any; 19 | splice(start: number, deleteCount: number, elementsToAdd?: any): any; 20 | move(x: number, y: number): any; 21 | } 22 | export declare type Path = string | Array; 23 | /** 24 | * Two-way data binding for React. 25 | */ 26 | /** 27 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName") 28 | */ 29 | export interface IPathObjectBinder { 30 | /** 31 | It gets value at the passed path expression. 32 | */ 33 | getValue(path?: Path): any; 34 | /** 35 | It sets the passed value at the passed path. 36 | */ 37 | setValue(path: Path, value: any): any; 38 | subscribe(fce: any): void; 39 | } 40 | /** 41 | It represents change notification function. It is called whenever there is a change. 42 | */ 43 | export interface INotifyChange { 44 | (any?: any): void; 45 | } 46 | /** 47 | It represents change notifikcation function with changed value. It supports valueLink interface 48 | */ 49 | export interface IRequestChange { 50 | (any: any): void; 51 | } 52 | /** 53 | It represents binding to property at source object at a given path. 54 | */ 55 | export interface IPathObjectBinding extends ObjectBinding { 56 | source: IPathObjectBinder; 57 | notifyChange?: INotifyChange; 58 | requestChange?: IRequestChange; 59 | valueConverter?: IValueConverter; 60 | } 61 | /** 62 | It represents binding to property at source object at a given path. 63 | */ 64 | export declare class PathObjectBinding implements IPathObjectBinding { 65 | source: IPathObjectBinder; 66 | notifyChange: INotifyChange; 67 | valueConverter: IValueConverter; 68 | parentNode: Binding; 69 | path: Array; 70 | constructor(source: IPathObjectBinder, rootPath?: Path, notifyChange?: INotifyChange, valueConverter?: IValueConverter, parentNode?: Binding); 71 | requestChange: IRequestChange; 72 | root: Binding; 73 | parent: Binding; 74 | value: any; 75 | } 76 | /** 77 | It represents binding to property at source object at a given path. 78 | */ 79 | export declare class ArrayObjectBinding implements ArrayBinding { 80 | source: IPathObjectBinder; 81 | notifyChange: INotifyChange; 82 | valueConverter: IValueConverter; 83 | path: Array; 84 | constructor(source: IPathObjectBinder, rootPath?: Path, notifyChange?: INotifyChange, valueConverter?: IValueConverter); 85 | parent: ArrayBinding; 86 | root: ArrayBinding; 87 | items: Array; 88 | add(defaultItem?: any): void; 89 | remove(itemToRemove: any): void; 90 | splice(start: number, deleteCount: number, elementsToAdd?: any): any; 91 | move(x: number, y: number): void; 92 | } 93 | /** 94 | It represents binding to array using relative path to parent object. 95 | */ 96 | export declare class ArrayParentBinding implements ArrayBinding { 97 | private parentBinding; 98 | valueConverter: IValueConverter; 99 | relativePath: Array; 100 | constructor(parentBinding: IPathObjectBinding, subPath?: Path, valueConverter?: IValueConverter); 101 | source: IPathObjectBinder; 102 | root: Binding; 103 | parent: Binding; 104 | notifyChange: INotifyChange; 105 | path: Array; 106 | private cachedBindings; 107 | private clearCache(); 108 | private getItems(); 109 | items: Array; 110 | add(defaultItem?: any): void; 111 | remove(itemToRemove: any): void; 112 | splice(start: number, deleteCount: number, elementsToAdd?: any): any[]; 113 | move(x: any, y: any): void; 114 | } 115 | /** 116 | It represents binding to relative path for parent object. 117 | */ 118 | export declare class PathParentBinding implements IPathObjectBinding { 119 | private parentBinding; 120 | valueConverter: IValueConverter; 121 | relativePath: Array; 122 | constructor(parentBinding: IPathObjectBinding, subPath: Path, valueConverter?: IValueConverter); 123 | source: IPathObjectBinder; 124 | root: Binding; 125 | parent: Binding; 126 | notifyChange: INotifyChange; 127 | requestChange: IRequestChange; 128 | path: Array; 129 | value: any; 130 | } 131 | /** 132 | * Provides a way to apply custom logic to a binding. 133 | * It enables to make bi-directional convertions between source (data) and target (view) binding. 134 | * 135 | * + apply various formats to values 136 | * + parse values from user input 137 | */ 138 | export interface IValueConverter { 139 | /** 140 | * Convert value into another value before return binding getter. Typically from model(data) to view. 141 | * @param value - source binding object (value) 142 | * @param parameters - enable parametrization of conversion 143 | */ 144 | format?(value: any, parameters?: any): any; 145 | /** 146 | * Convert value into another value before call binding setter. Typically from view to model(data). 147 | * @param value - target binding object (value) 148 | * @param parameters - enable parametrization of conversion 149 | */ 150 | parse?(value: any, parameters?: any): any; 151 | } 152 | export declare class CurryConverter implements IValueConverter { 153 | private formatFce; 154 | private parseFce; 155 | constructor(converter: IValueConverter, args: any); 156 | private curryParameters(fn, args); 157 | format(value: any): any; 158 | parse(value: any): any; 159 | } 160 | -------------------------------------------------------------------------------- /dist/definitions/FreezerBinder.d.ts: -------------------------------------------------------------------------------- 1 | export default class Binder { 2 | static bindTo(parent: any, path?: string, converter?: any, converterParams?: any): any; 3 | static bindArrayTo(parent: any, path?: string, converter?: any, converterParams?: any): any; 4 | } 5 | -------------------------------------------------------------------------------- /dist/definitions/FreezerProvider.d.ts: -------------------------------------------------------------------------------- 1 | import { IPathObjectBinder, Path } from './DataBinding'; 2 | /** 3 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName") 4 | */ 5 | export default class FreezerPathObjectBinder implements IPathObjectBinder { 6 | private subSource; 7 | private root; 8 | constructor(rootParams: any, subSource?: any); 9 | private source; 10 | createNew(path: Path, newItem?: any): IPathObjectBinder; 11 | subscribe(updateFce: any): void; 12 | getValue(path?: Path): any; 13 | setValue(path: Path, value: string): any; 14 | private getParent(cursorPath); 15 | setWith(object: any, path: Array, value: any): any; 16 | } 17 | -------------------------------------------------------------------------------- /dist/definitions/MobxBinder.d.ts: -------------------------------------------------------------------------------- 1 | export default class Binder { 2 | static bindTo(parent: any, path?: string, converter?: any, converterParams?: any): any; 3 | static bindArrayTo(parent: any, path?: string, converter?: any, converterParams?: any): any; 4 | } 5 | -------------------------------------------------------------------------------- /dist/definitions/MobxProvider.d.ts: -------------------------------------------------------------------------------- 1 | import { IPathObjectBinder, Path } from './DataBinding'; 2 | /** 3 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName") 4 | */ 5 | export default class MobxPathObjectBinder implements IPathObjectBinder { 6 | private root; 7 | private source; 8 | private current; 9 | private previous; 10 | constructor(root: any, source?: any); 11 | createNew(path: Path, newItem?: any): IPathObjectBinder; 12 | subscribe(updateFce: any): void; 13 | getValue(path: Path): any; 14 | setValue(path: Path, value: any): void; 15 | private getParent(cursorPath); 16 | private setValueAsObservable(parent, property, value?); 17 | } 18 | -------------------------------------------------------------------------------- /dist/definitions/PlainObjectProvider.d.ts: -------------------------------------------------------------------------------- 1 | import { IPathObjectBinder, Path } from './DataBinding'; 2 | /** 3 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName") 4 | */ 5 | export default class PathObjectBinder implements IPathObjectBinder { 6 | private root; 7 | private source; 8 | constructor(root: any, source?: any); 9 | subscribe(updateFce: any): void; 10 | createNew(path: Path, newItem?: any): IPathObjectBinder; 11 | getValue(path: Path): any; 12 | setValue(path: Path, value: any): void; 13 | private getParent(cursorPath); 14 | } 15 | -------------------------------------------------------------------------------- /dist/definitions/utils-lodash.d.ts: -------------------------------------------------------------------------------- 1 | export declare function isIndex(value: any, length?: number): boolean; 2 | export declare function isObject(value: any): boolean; 3 | /** 4 | * Converts `string` to a property path array. 5 | * 6 | * @private 7 | * @param {string} string The string to convert. 8 | * @returns {Array} Returns the property path array. 9 | */ 10 | export declare function stringToPath(string: string): any[]; 11 | /** 12 | * Checks if `value` is a property name and not a property path. 13 | * 14 | * @private 15 | * @param {*} value The value to check. 16 | * @param {Object} [object] The object to query keys on. 17 | * @returns {boolean} Returns `true` if `value` is a property name, else `false`. 18 | */ 19 | export declare function isKey(value: any, object: any): boolean; 20 | -------------------------------------------------------------------------------- /dist/definitions/utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare function castPath(value: any, object?: any): Array; 2 | export declare function followRef(root: any, ref: any): any; 3 | export declare function baseGet(root: any, path: Array): any; 4 | export declare function baseSet(object: any, path: Array, value: any, customizer?: any): any; 5 | -------------------------------------------------------------------------------- /dist/lib/Binder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var PlainObjectProvider_1 = require('./PlainObjectProvider'); 3 | var DataBinding_1 = require('./DataBinding'); 4 | var BinderCore = (function () { 5 | function BinderCore() { 6 | } 7 | BinderCore.bindTo = function (type, parent, path, converter, converterParams) { 8 | var converter = converterParams !== undefined ? new DataBinding_1.CurryConverter(converter, converterParams) : converter; 9 | return (parent instanceof DataBinding_1.PathObjectBinding || parent instanceof DataBinding_1.PathParentBinding) ? new DataBinding_1.PathParentBinding(parent, path, converter) : new DataBinding_1.PathObjectBinding(new type(parent), path, converter); 10 | }; 11 | BinderCore.bindArrayTo = function (type, parent, path, converter, converterParams) { 12 | var converter = converterParams !== undefined ? new DataBinding_1.CurryConverter(converter, converterParams) : converter; 13 | return (parent instanceof DataBinding_1.PathObjectBinding || parent instanceof DataBinding_1.PathParentBinding) ? new DataBinding_1.ArrayParentBinding(parent, path, converter) : new DataBinding_1.ArrayObjectBinding(new type(parent), path, converter); 14 | }; 15 | return BinderCore; 16 | }()); 17 | exports.BinderCore = BinderCore; 18 | /** 19 | * React [LinkedStateMixin](http://facebook.github.io/react/docs/two-way-binding-helpers.html) is an easy way to express two-way data binding in React. 20 | * 21 | * React-binding comes with utility [Binder](https://github.com/rsamec/react-binding) for two-way binding that supports binding to 22 | * 23 | * + object properties with path expression (dot notation) 24 | * + this.bindToState("data","Employee.FirstName"); 25 | * + this.bindToState("data","Employee.Contact.Email"); 26 | * + complex objects (json) with nested properties 27 | * + this.bindTo(employee,"FirstName"); 28 | * + this.bindTo(employee,"Contact.Email"); 29 | * + collection-based structures - arrays and lists 30 | * + model={this.bindTo(employee,"FirstName")} 31 | * + this.props.model.items.map(function(item){ return ();}) 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 | var Binder = (function () { 46 | function Binder() { 47 | } 48 | Binder.createStateKeySetter = function (component, key) { 49 | var partialState = {}; 50 | return function (value) { 51 | partialState[key] = (value !== undefined) ? value : component.state[key]; 52 | component.setState(partialState); 53 | }; 54 | }; 55 | /** 56 | * It enables to bind to object property with path expression 57 | * + using [valueLink](http://facebook.github.io/react/docs/two-way-binding-helpers.html) 58 | * ``` js 59 | * 60 | * ``` 61 | * 62 | * + without [valueLink](http://facebook.github.io/react/docs/two-way-binding-helpers.html) 63 | * ``` js 64 | * 65 | * ``` 66 | * 67 | * ``` js 68 | * var TextBoxInput = React.createClass({ 69 | * render: function() { 70 | * var valueModel = this.props.model; 71 | * var handleChange = function(e){ 72 | * valueModel.value = e.target.value; 73 | * } 74 | * return ( 75 | * 76 | * )} 77 | * }); 78 | * ``` 79 | * 80 | * @param key - property name in state (this.state[key]) 81 | * @param path - expression to bind to property 82 | * @param converter {DataBinding.IValueConverter} - value converter 83 | * @param converterParams - parameters used by converter 84 | * @returns {DataBinding.PathObjectBinding} 85 | */ 86 | Binder.bindToState = function (component, key, path, converter, converterParams) { 87 | return new DataBinding_1.PathObjectBinding(new PlainObjectProvider_1.default(component["state"][key]), path, Binder.createStateKeySetter(component, key), converterParams !== undefined ? new DataBinding_1.CurryConverter(converter, converterParams) : converter); 88 | }; 89 | /** 90 | * It enables to bind to complex object with nested properties and reuse bindings in components. 91 | * 92 | * + binding to state at root level 93 | * 94 | * ``` js 95 | * 96 | * 97 | * ``` 98 | * 99 | * + binding to parent 100 | * 101 | * ``` js 102 | * 103 | * ``` 104 | * 105 | * + reuse bindings in component 106 | * 107 | * ``` js 108 | * var PersonComponent = React.createClass({ 109 | * mixins:[BindToMixin], 110 | * render: function() { 111 | * return ( 112 | *
113 | * 114 | * 115 | * 116 | *
117 | * ); 118 | * } 119 | * }); 120 | * 121 | * ``` 122 | * 123 | * @param parent - the parent object 124 | * @param path - expression to bind to property 125 | * @param converter - value converter {DataBinding.IValueConverter} 126 | * @param converterParams - parameters used by converter 127 | * @returns {DataBinding.PathParentBinding} 128 | */ 129 | Binder.bindTo = function (parent, path, converter, converterParams) { 130 | return BinderCore.bindTo(PlainObjectProvider_1.default, parent, path, converter, converterParams); 131 | }; 132 | /** 133 | * It enables binding to collection-based structures (array). It enables to add and remove items. 134 | * 135 | * + binding to array 136 | * 137 | * ``` js 138 | * 139 | * ``` 140 | * 141 | * @param key - property name in state (this.state[key]) - it must be array 142 | * @param path - expression to array to bind to property 143 | * @returns {DataBinding.ArrayObjectBinding} 144 | */ 145 | Binder.bindArrayToState = function (component, key, path) { 146 | return new DataBinding_1.ArrayObjectBinding(new PlainObjectProvider_1.default(component["state"][key]), path, Binder.createStateKeySetter(component, key)); 147 | }; 148 | /** 149 | * It enables binding to collection-based structures (array) for nested arrays. It enables to add and remove items. 150 | * 151 | * + binding to parent 152 | * 153 | * ``` js 154 | * 155 | * ``` 156 | * 157 | * @param parent - the parent object 158 | * @param path - expression to bind to property - relative path from parent 159 | * @returns {DataBinding.PathParentBinding} 160 | */ 161 | Binder.bindArrayTo = function (parent, path, converter, converterParams) { 162 | return BinderCore.bindArrayTo(PlainObjectProvider_1.default, parent, path, converter, converterParams); 163 | }; 164 | return Binder; 165 | }()); 166 | Object.defineProperty(exports, "__esModule", { value: true }); 167 | exports.default = Binder; 168 | //export default Binder; 169 | -------------------------------------------------------------------------------- /dist/lib/DataBinding.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var utils_1 = require("./utils"); 3 | /** 4 | It represents binding to property at source object at a given path. 5 | */ 6 | var PathObjectBinding = (function () { 7 | function PathObjectBinding(source, rootPath, notifyChange, valueConverter, parentNode) { 8 | this.source = source; 9 | this.notifyChange = notifyChange; 10 | this.valueConverter = valueConverter; 11 | this.parentNode = parentNode; 12 | this.path = rootPath === undefined ? [] : utils_1.castPath(rootPath); 13 | } 14 | Object.defineProperty(PathObjectBinding.prototype, "requestChange", { 15 | get: function () { 16 | var _this = this; 17 | return function (value) { _this.value = value; }; 18 | }, 19 | enumerable: true, 20 | configurable: true 21 | }); 22 | Object.defineProperty(PathObjectBinding.prototype, "root", { 23 | get: function () { 24 | return this.parentNode !== undefined ? this.parentNode.root : this; 25 | }, 26 | enumerable: true, 27 | configurable: true 28 | }); 29 | Object.defineProperty(PathObjectBinding.prototype, "parent", { 30 | get: function () { 31 | return this.parentNode !== undefined ? this.parentNode : undefined; 32 | }, 33 | enumerable: true, 34 | configurable: true 35 | }); 36 | Object.defineProperty(PathObjectBinding.prototype, "value", { 37 | get: function () { 38 | var value = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path); 39 | //get value - optional call converter 40 | return this.valueConverter !== undefined ? this.valueConverter.format(value) : value; 41 | }, 42 | set: function (value) { 43 | var previousValue = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path); 44 | var convertedValueToBeSet = this.valueConverter !== undefined ? this.valueConverter.parse(value) : value; 45 | //check if the value is really changed - strict equality 46 | if (previousValue !== undefined && previousValue === convertedValueToBeSet) 47 | return; 48 | if (this.path === undefined) { 49 | if (this.notifyChange !== undefined) 50 | this.notifyChange(convertedValueToBeSet); 51 | } 52 | else { 53 | this.source.setValue(this.path, convertedValueToBeSet); 54 | if (this.notifyChange !== undefined) 55 | this.notifyChange(); 56 | } 57 | }, 58 | enumerable: true, 59 | configurable: true 60 | }); 61 | return PathObjectBinding; 62 | }()); 63 | exports.PathObjectBinding = PathObjectBinding; 64 | /** 65 | It represents binding to property at source object at a given path. 66 | */ 67 | var ArrayObjectBinding = (function () { 68 | function ArrayObjectBinding(source, rootPath, notifyChange, valueConverter) { 69 | this.source = source; 70 | this.notifyChange = notifyChange; 71 | this.valueConverter = valueConverter; 72 | this.path = rootPath === undefined ? [] : utils_1.castPath(rootPath); 73 | } 74 | Object.defineProperty(ArrayObjectBinding.prototype, "parent", { 75 | get: function () { 76 | return undefined; 77 | }, 78 | enumerable: true, 79 | configurable: true 80 | }); 81 | Object.defineProperty(ArrayObjectBinding.prototype, "root", { 82 | get: function () { 83 | return this; 84 | }, 85 | enumerable: true, 86 | configurable: true 87 | }); 88 | Object.defineProperty(ArrayObjectBinding.prototype, "items", { 89 | get: function () { 90 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path); 91 | if (items === undefined) 92 | return []; 93 | return items.map(function (item, index) { 94 | //console.log(item); 95 | return new PathObjectBinding(this.source.createNew(this.path.concat(index), item), undefined, this.notifyChange, undefined, this); 96 | }, this); 97 | }, 98 | enumerable: true, 99 | configurable: true 100 | }); 101 | ArrayObjectBinding.prototype.add = function (defaultItem) { 102 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path); 103 | if (items === undefined) { 104 | this.source.setValue(this.path, []); 105 | items = this.source.getValue(this.path); 106 | } 107 | if (defaultItem === undefined) 108 | defaultItem = {}; 109 | items.push(defaultItem); 110 | if (this.notifyChange !== undefined) 111 | this.notifyChange(); 112 | }; 113 | ArrayObjectBinding.prototype.remove = function (itemToRemove) { 114 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path); 115 | if (items === undefined) 116 | return; 117 | var index = items.indexOf(itemToRemove); 118 | if (index === -1) 119 | return; 120 | items.splice(index, 1); 121 | if (this.notifyChange !== undefined) 122 | this.notifyChange(); 123 | }; 124 | ArrayObjectBinding.prototype.splice = function (start, deleteCount, elementsToAdd) { 125 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path); 126 | if (items === undefined) 127 | return; 128 | return elementsToAdd ? items.splice(start, deleteCount, elementsToAdd) : items.splice(start, deleteCount); 129 | //if (this.notifyChange !== undefined) this.notifyChange(); 130 | }; 131 | ArrayObjectBinding.prototype.move = function (x, y) { 132 | var items = this.path === undefined ? this.source.getValue() : this.source.getValue(this.path); 133 | if (items === undefined) 134 | return; 135 | //@TODO: use more effective way to clone array 136 | var itemsCloned = JSON.parse(JSON.stringify(items)); 137 | itemsCloned.splice(y, 0, itemsCloned.splice(x, 1)[0]); 138 | this.source.setValue(this.path, itemsCloned); 139 | }; 140 | return ArrayObjectBinding; 141 | }()); 142 | exports.ArrayObjectBinding = ArrayObjectBinding; 143 | /** 144 | It represents binding to array using relative path to parent object. 145 | */ 146 | var ArrayParentBinding = (function () { 147 | function ArrayParentBinding(parentBinding, subPath, valueConverter) { 148 | this.parentBinding = parentBinding; 149 | this.valueConverter = valueConverter; 150 | this.relativePath = subPath === undefined ? [] : utils_1.castPath(subPath); 151 | } 152 | Object.defineProperty(ArrayParentBinding.prototype, "source", { 153 | //wrapped properties - delegate call to parent 154 | get: function () { 155 | return this.parentBinding.source; 156 | }, 157 | enumerable: true, 158 | configurable: true 159 | }); 160 | Object.defineProperty(ArrayParentBinding.prototype, "root", { 161 | get: function () { 162 | return this.parentBinding.root; 163 | }, 164 | enumerable: true, 165 | configurable: true 166 | }); 167 | Object.defineProperty(ArrayParentBinding.prototype, "parent", { 168 | get: function () { 169 | return this.parentBinding; 170 | }, 171 | enumerable: true, 172 | configurable: true 173 | }); 174 | Object.defineProperty(ArrayParentBinding.prototype, "notifyChange", { 175 | get: function () { 176 | return this.parentBinding.notifyChange; 177 | }, 178 | set: function (value) { 179 | this.parentBinding.notifyChange = value; 180 | }, 181 | enumerable: true, 182 | configurable: true 183 | }); 184 | Object.defineProperty(ArrayParentBinding.prototype, "path", { 185 | //concatenate path 186 | get: function () { 187 | if (this.parentBinding.path === undefined) 188 | return this.relativePath; 189 | if (this.relativePath === undefined) 190 | return this.parentBinding.path; 191 | return this.parentBinding.path.concat(this.relativePath); 192 | }, 193 | enumerable: true, 194 | configurable: true 195 | }); 196 | ArrayParentBinding.prototype.clearCache = function () { 197 | this.cachedBindings === undefined; 198 | }; 199 | ArrayParentBinding.prototype.getItems = function () { 200 | if (this.source === undefined) 201 | return; 202 | var value = this.source.getValue(this.path); 203 | return this.valueConverter !== undefined ? this.valueConverter.format(value) : value; 204 | }; 205 | Object.defineProperty(ArrayParentBinding.prototype, "items", { 206 | get: function () { 207 | // var path = this.path.join("."); 208 | // console.time(path); 209 | var items = this.getItems(); 210 | if (items === undefined) 211 | return []; 212 | if (this.cachedBindings !== undefined && this.cachedBindings.items === items && this.cachedBindings.length === items.length) 213 | return this.cachedBindings.bindings; 214 | this.cachedBindings = { 215 | items: items, 216 | length: items.length, 217 | bindings: items.map(function (item, index) { 218 | return new PathObjectBinding(this.source.createNew(this.path.concat(index), item), undefined, this.notifyChange, undefined, this); 219 | }, this) 220 | }; 221 | //console.timeEnd(path); 222 | return this.cachedBindings.bindings; 223 | }, 224 | enumerable: true, 225 | configurable: true 226 | }); 227 | ArrayParentBinding.prototype.add = function (defaultItem) { 228 | var items = this.getItems(); 229 | if (items === undefined) { 230 | this.source.setValue(this.path, []); 231 | items = this.source.getValue(this.path); 232 | } 233 | if (defaultItem === undefined) 234 | defaultItem = {}; 235 | items.push(defaultItem); 236 | this.clearCache(); 237 | if (this.notifyChange !== undefined) 238 | this.notifyChange(); 239 | }; 240 | ArrayParentBinding.prototype.remove = function (itemToRemove) { 241 | var items = this.getItems(); 242 | if (items === undefined) 243 | return; 244 | var index = items.indexOf(itemToRemove); 245 | if (index === -1) 246 | return; 247 | items.splice(index, 1); 248 | this.clearCache(); 249 | if (this.notifyChange !== undefined) 250 | this.notifyChange(); 251 | }; 252 | ArrayParentBinding.prototype.splice = function (start, deleteCount, elementsToAdd) { 253 | var items = this.getItems(); 254 | if (items === undefined) 255 | return; 256 | return elementsToAdd ? items.splice(start, deleteCount, elementsToAdd) : items.splice(start, deleteCount); 257 | //if (this.notifyChange !== undefined) this.notifyChange(); 258 | }; 259 | ArrayParentBinding.prototype.move = function (x, y) { 260 | this.splice(y, 0, this.splice(x, 1)[0]); 261 | this.clearCache(); 262 | if (this.notifyChange !== undefined) 263 | this.notifyChange(); 264 | }; 265 | return ArrayParentBinding; 266 | }()); 267 | exports.ArrayParentBinding = ArrayParentBinding; 268 | /** 269 | It represents binding to relative path for parent object. 270 | */ 271 | var PathParentBinding = (function () { 272 | function PathParentBinding(parentBinding, subPath, valueConverter) { 273 | this.parentBinding = parentBinding; 274 | this.valueConverter = valueConverter; 275 | this.relativePath = subPath === undefined ? [] : utils_1.castPath(subPath); 276 | } 277 | Object.defineProperty(PathParentBinding.prototype, "source", { 278 | //wrapped properties - delegate call to parent 279 | get: function () { 280 | return this.parentBinding.source; 281 | }, 282 | enumerable: true, 283 | configurable: true 284 | }); 285 | Object.defineProperty(PathParentBinding.prototype, "root", { 286 | get: function () { 287 | return this.parentBinding.root; 288 | }, 289 | enumerable: true, 290 | configurable: true 291 | }); 292 | Object.defineProperty(PathParentBinding.prototype, "parent", { 293 | get: function () { 294 | return this.parentBinding; 295 | }, 296 | enumerable: true, 297 | configurable: true 298 | }); 299 | Object.defineProperty(PathParentBinding.prototype, "notifyChange", { 300 | get: function () { 301 | return this.parentBinding.notifyChange; 302 | }, 303 | set: function (value) { 304 | this.parentBinding.notifyChange = value; 305 | }, 306 | enumerable: true, 307 | configurable: true 308 | }); 309 | Object.defineProperty(PathParentBinding.prototype, "requestChange", { 310 | get: function () { 311 | var _this = this; 312 | return function (value) { 313 | _this.value = value; 314 | }; 315 | }, 316 | enumerable: true, 317 | configurable: true 318 | }); 319 | Object.defineProperty(PathParentBinding.prototype, "path", { 320 | //concatenate path 321 | get: function () { 322 | if (this.parentBinding.path === undefined) 323 | return this.relativePath; 324 | return this.parentBinding.path.concat(this.relativePath); 325 | }, 326 | enumerable: true, 327 | configurable: true 328 | }); 329 | Object.defineProperty(PathParentBinding.prototype, "value", { 330 | get: function () { 331 | var value = this.source.getValue(this.path); 332 | //get value - optional call converter 333 | var result = this.valueConverter !== undefined ? this.valueConverter.format(value) : value; 334 | return result; 335 | }, 336 | set: function (value) { 337 | //var path = this.path.join("."); 338 | //console.time(path); 339 | //check if the value is really changed - strict equality 340 | var previousValue = this.source.getValue(this.path); 341 | var convertedValueToBeSet = this.valueConverter !== undefined ? this.valueConverter.parse(value) : value; 342 | if (previousValue === convertedValueToBeSet) 343 | return; 344 | //set value - optional call converter 345 | this.source.setValue(this.path, convertedValueToBeSet); 346 | //console.timeEnd(path); 347 | if (this.notifyChange !== undefined) 348 | this.notifyChange(); 349 | }, 350 | enumerable: true, 351 | configurable: true 352 | }); 353 | return PathParentBinding; 354 | }()); 355 | exports.PathParentBinding = PathParentBinding; 356 | var CurryConverter = (function () { 357 | function CurryConverter(converter, args) { 358 | this.formatFce = this.curryParameters(converter.format, [args]); 359 | this.parseFce = this.curryParameters(converter.parse, [args]); 360 | } 361 | CurryConverter.prototype.curryParameters = function (fn, args) { 362 | return function () { 363 | return fn.apply(this, Array.prototype.slice.call(arguments).concat(args)); 364 | }; 365 | }; 366 | CurryConverter.prototype.format = function (value) { 367 | return this.formatFce(value); 368 | }; 369 | CurryConverter.prototype.parse = function (value) { 370 | return this.parseFce(value); 371 | }; 372 | return CurryConverter; 373 | }()); 374 | exports.CurryConverter = CurryConverter; 375 | -------------------------------------------------------------------------------- /dist/lib/FreezerBinder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var FreezerProvider_1 = require('./FreezerProvider'); 3 | var Binder_1 = require('./Binder'); 4 | var Binder = (function () { 5 | function Binder() { 6 | } 7 | Binder.bindTo = function (parent, path, converter, converterParams) { 8 | return Binder_1.BinderCore.bindTo(FreezerProvider_1.default, parent, path, converter, converterParams); 9 | }; 10 | Binder.bindArrayTo = function (parent, path, converter, converterParams) { 11 | return Binder_1.BinderCore.bindArrayTo(FreezerProvider_1.default, parent, path, converter, converterParams); 12 | }; 13 | return Binder; 14 | }()); 15 | Object.defineProperty(exports, "__esModule", { value: true }); 16 | exports.default = Binder; 17 | -------------------------------------------------------------------------------- /dist/lib/FreezerProvider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var Freezer = require('freezer-js'); 3 | var utils_1 = require('./utils'); 4 | var utils_lodash_1 = require('./utils-lodash'); 5 | /** 6 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName") 7 | */ 8 | var FreezerPathObjectBinder = (function () { 9 | function FreezerPathObjectBinder(rootParams, subSource) { 10 | this.subSource = subSource; 11 | this.root = subSource === undefined ? new Freezer(rootParams) : rootParams; 12 | //this.source = source === undefined ? this.root : source; 13 | } 14 | Object.defineProperty(FreezerPathObjectBinder.prototype, "source", { 15 | get: function () { 16 | return this.subSource !== undefined ? this.subSource.get() : this.root.get(); 17 | }, 18 | enumerable: true, 19 | configurable: true 20 | }); 21 | FreezerPathObjectBinder.prototype.createNew = function (path, newItem) { 22 | var item = newItem || this.getValue(path); 23 | //var item = followRef(this.root.get(), newItem || this.getValue(path)); 24 | return new FreezerPathObjectBinder(this.root, new Freezer(item)); 25 | }; 26 | FreezerPathObjectBinder.prototype.subscribe = function (updateFce) { 27 | this.root.on('update', function (state, prevState) { 28 | //console.log(state); 29 | if (updateFce !== undefined) 30 | updateFce(state, prevState); 31 | }); 32 | }; 33 | FreezerPathObjectBinder.prototype.getValue = function (path) { 34 | if (path === undefined) 35 | return this.source; 36 | var cursorPath = utils_1.castPath(path); 37 | if (cursorPath.length === 0) 38 | return this.source; 39 | var parent = this.getParent(cursorPath); 40 | if (parent === undefined) 41 | return; 42 | var property = cursorPath[cursorPath.length - 1]; 43 | return parent[property]; 44 | }; 45 | FreezerPathObjectBinder.prototype.setValue = function (path, value) { 46 | if (path === undefined) 47 | return; 48 | var cursorPath = utils_1.castPath(path); 49 | if (cursorPath.length === 0) 50 | return; 51 | var parent = this.getParent(cursorPath); 52 | if (parent === undefined) 53 | return; 54 | var property = cursorPath[cursorPath.length - 1]; 55 | var updated = parent.set(property, value); 56 | return updated; 57 | }; 58 | FreezerPathObjectBinder.prototype.getParent = function (cursorPath) { 59 | if (cursorPath.length == 0) 60 | return; 61 | var source = this.source; 62 | if (cursorPath.length == 1) 63 | return utils_1.followRef(this.root.get(), source); 64 | var parentPath = cursorPath.slice(0, cursorPath.length - 1); 65 | var parent = utils_1.baseGet(source, parentPath); 66 | if (parent !== undefined) 67 | return utils_1.followRef(this.root.get(), parent); 68 | var updated = this.setWith(source, parentPath, {}); 69 | return utils_1.baseGet(updated, parentPath); 70 | }; 71 | FreezerPathObjectBinder.prototype.setWith = function (object, path, value) { 72 | var length = path.length; 73 | var lastIndex = length - 1; 74 | var index = -1; 75 | var nested = object; 76 | while (nested != null && ++index < length) { 77 | var key = path[index]; 78 | var newValue = value; 79 | if (index != lastIndex) { 80 | var objValue = nested[key]; 81 | if (newValue === undefined) { 82 | newValue = utils_lodash_1.isObject(objValue) 83 | ? objValue 84 | : (utils_lodash_1.isIndex(path[index + 1]) ? [] : {}); 85 | } 86 | } 87 | //assignValue(nested, key, newValue); 88 | var updated = nested.set(key, newValue); 89 | nested = updated[key]; 90 | } 91 | return nested; 92 | }; 93 | return FreezerPathObjectBinder; 94 | }()); 95 | Object.defineProperty(exports, "__esModule", { value: true }); 96 | exports.default = FreezerPathObjectBinder; 97 | -------------------------------------------------------------------------------- /dist/lib/MobxBinder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var MobxProvider_1 = require('./MobxProvider'); 3 | var Binder_1 = require('./Binder'); 4 | var Binder = (function () { 5 | function Binder() { 6 | } 7 | Binder.bindTo = function (parent, path, converter, converterParams) { 8 | return Binder_1.BinderCore.bindTo(MobxProvider_1.default, parent, path, converter, converterParams); 9 | }; 10 | Binder.bindArrayTo = function (parent, path, converter, converterParams) { 11 | return Binder_1.BinderCore.bindArrayTo(MobxProvider_1.default, parent, path, converter, converterParams); 12 | }; 13 | return Binder; 14 | }()); 15 | Object.defineProperty(exports, "__esModule", { value: true }); 16 | exports.default = Binder; 17 | -------------------------------------------------------------------------------- /dist/lib/MobxProvider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var utils_1 = require('./utils'); 3 | var mobx_1 = require('mobx'); 4 | /** 5 | It wraps getting and setting object properties by setting path expression (dotted path - e.g. "Data.Person.FirstName", "Data.Person.LastName") 6 | */ 7 | var MobxPathObjectBinder = (function () { 8 | function MobxPathObjectBinder(root, source) { 9 | this.root = mobx_1.observable(root); 10 | this.source = source === undefined ? this.root : source; 11 | } 12 | MobxPathObjectBinder.prototype.createNew = function (path, newItem) { 13 | //var item = followRef(this.root,newItem || this.getValue(path)) 14 | return new MobxPathObjectBinder(this.root, newItem || this.getValue(path)); 15 | }; 16 | MobxPathObjectBinder.prototype.subscribe = function (updateFce) { 17 | // var previousState; 18 | // if (updateFce !== undefined) autorun( 19 | // () => { 20 | // var current = this.current.get() 21 | // updateFce(current, this.previous); 22 | // this.previous = current; 23 | // }); 24 | // //if (updateFce!==undefined) autorun(updateFce); 25 | }; 26 | MobxPathObjectBinder.prototype.getValue = function (path) { 27 | if (path === undefined) 28 | return this.source; 29 | var cursorPath = utils_1.castPath(path); 30 | if (cursorPath.length === 0) 31 | return this.source; 32 | var parent = this.getParent(cursorPath); 33 | if (parent === undefined) 34 | return; 35 | var property = cursorPath[cursorPath.length - 1]; 36 | var value = parent[property]; 37 | if (value === undefined && !parent.hasOwnProperty(property)) { 38 | this.setValueAsObservable(parent, property); 39 | } 40 | return parent[property]; 41 | }; 42 | MobxPathObjectBinder.prototype.setValue = function (path, value) { 43 | if (path === undefined) 44 | return; 45 | var cursorPath = utils_1.castPath(path); 46 | if (cursorPath.length === 0) 47 | return; 48 | var parent = this.getParent(cursorPath); 49 | var property = cursorPath[cursorPath.length - 1]; 50 | if (mobx_1.isObservable(parent, property)) { 51 | parent[property] = value; 52 | return; 53 | } 54 | this.setValueAsObservable(parent, property, value); 55 | }; 56 | MobxPathObjectBinder.prototype.getParent = function (cursorPath) { 57 | if (cursorPath.length == 0) 58 | return; 59 | if (cursorPath.length == 1) 60 | return utils_1.followRef(this.root, this.source); 61 | var parentPath = cursorPath.slice(0, cursorPath.length - 1); 62 | var parent = utils_1.baseGet(this.source, parentPath); 63 | if (parent !== undefined) 64 | return utils_1.followRef(this.root, parent); 65 | utils_1.baseSet(this.source, parentPath, {}, Object); 66 | return utils_1.baseGet(this.source, parentPath); 67 | }; 68 | MobxPathObjectBinder.prototype.setValueAsObservable = function (parent, property, value) { 69 | var newProps = {}; 70 | newProps[property] = value; 71 | mobx_1.extendObservable(parent, newProps); 72 | }; 73 | return MobxPathObjectBinder; 74 | }()); 75 | Object.defineProperty(exports, "__esModule", { value: true }); 76 | exports.default = MobxPathObjectBinder; 77 | -------------------------------------------------------------------------------- /dist/lib/PlainObjectProvider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var utils_1 = require('./utils'); 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 | var PathObjectBinder = (function () { 7 | function PathObjectBinder(root, source) { 8 | this.root = root; 9 | this.source = source === undefined ? this.root : source; 10 | } 11 | PathObjectBinder.prototype.subscribe = function (updateFce) { 12 | // this.freezer.on('update',function(state,prevState){ 13 | // if (updateFce!==undefined) updateFce(state,prevState)} 14 | // ); 15 | }; 16 | PathObjectBinder.prototype.createNew = function (path, newItem) { 17 | //var item = followRef(this.root, newItem || this.getValue(path)); 18 | return new PathObjectBinder(this.root, newItem || this.getValue(path)); 19 | }; 20 | PathObjectBinder.prototype.getValue = function (path) { 21 | if (path === undefined) 22 | return this.source; 23 | var cursorPath = utils_1.castPath(path); 24 | if (cursorPath.length === 0) 25 | return this.source; 26 | var parent = this.getParent(cursorPath); 27 | if (parent === undefined) 28 | return; 29 | var property = cursorPath[cursorPath.length - 1]; 30 | return parent[property]; 31 | }; 32 | PathObjectBinder.prototype.setValue = function (path, value) { 33 | if (path === undefined) 34 | return; 35 | var cursorPath = utils_1.castPath(path); 36 | if (cursorPath.length === 0) 37 | return; 38 | var parent = this.getParent(cursorPath); 39 | if (parent === undefined) 40 | return; 41 | var property = cursorPath[cursorPath.length - 1]; 42 | //console.log(parent); 43 | parent[property] = value; 44 | //console.log(parent); 45 | }; 46 | PathObjectBinder.prototype.getParent = function (cursorPath) { 47 | if (cursorPath.length == 0) 48 | return; 49 | if (cursorPath.length == 1) 50 | return utils_1.followRef(this.root, this.source); 51 | var parentPath = cursorPath.slice(0, cursorPath.length - 1); 52 | var parent = utils_1.baseGet(this.source, parentPath); 53 | if (parent !== undefined) 54 | return utils_1.followRef(this.root, parent); 55 | utils_1.baseSet(this.source, parentPath, {}, Object); 56 | return utils_1.baseGet(this.source, parentPath); 57 | }; 58 | return PathObjectBinder; 59 | }()); 60 | Object.defineProperty(exports, "__esModule", { value: true }); 61 | exports.default = PathObjectBinder; 62 | -------------------------------------------------------------------------------- /dist/lib/utils-lodash.js: -------------------------------------------------------------------------------- 1 | // Copyright JS Foundation and other contributors 2 | "use strict"; 3 | // Based on Underscore.js, copyright Jeremy Ashkenas, 4 | // DocumentCloud and Investigative Reporters & Editors 5 | // This software consists of voluntary contributions made by many 6 | // individuals. For exact contribution history, see the revision history 7 | // available at https://github.com/lodash/lodash 8 | /** Used as references for various `Number` constants. */ 9 | var MAX_SAFE_INTEGER = 9007199254740991; 10 | /** Used to detect unsigned integer values. */ 11 | var reIsUint = /^(?:0|[1-9]\d*)$/; 12 | function isIndex(value, length) { 13 | length = length == null ? MAX_SAFE_INTEGER : length; 14 | return !!length && 15 | (typeof value == 'number' || reIsUint.test(value)) && 16 | (value > -1 && value % 1 == 0 && value < length); 17 | } 18 | exports.isIndex = isIndex; 19 | function isObject(value) { 20 | var type = typeof value; 21 | return value != null && (type == 'object' || type == 'function'); 22 | } 23 | exports.isObject = isObject; 24 | /** Used to match property names within property paths. */ 25 | var reLeadingDot = /^\./; 26 | var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; 27 | /** Used to match backslashes in property paths. */ 28 | var reEscapeChar = /\\(\\)?/g; 29 | /** 30 | * Converts `string` to a property path array. 31 | * 32 | * @private 33 | * @param {string} string The string to convert. 34 | * @returns {Array} Returns the property path array. 35 | */ 36 | function stringToPath(string) { 37 | var result = []; 38 | if (reLeadingDot.test(string)) { 39 | result.push(''); 40 | } 41 | string.replace(rePropName, function (match, number, quote, string) { 42 | result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match)); 43 | }); 44 | return result; 45 | } 46 | exports.stringToPath = stringToPath; 47 | /** Used to match property names within property paths. */ 48 | var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/; 49 | var reIsPlainProp = /^\w*$/; 50 | /** 51 | * Checks if `value` is a property name and not a property path. 52 | * 53 | * @private 54 | * @param {*} value The value to check. 55 | * @param {Object} [object] The object to query keys on. 56 | * @returns {boolean} Returns `true` if `value` is a property name, else `false`. 57 | */ 58 | function isKey(value, object) { 59 | if (Array.isArray(value)) { 60 | return false; 61 | } 62 | var type = typeof value; 63 | if (type == 'number' || type == 'symbol' || type == 'boolean' || 64 | value == null || typeof value == 'symbol') { 65 | return true; 66 | } 67 | return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || 68 | (object != null && value in Object(object)); 69 | } 70 | exports.isKey = isKey; 71 | -------------------------------------------------------------------------------- /dist/lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var utils_lodash_1 = require('./utils-lodash'); 3 | var $ref = "ref"; 4 | function castPath(value, object) { 5 | if (Array.isArray(value)) { 6 | return value; 7 | } 8 | return utils_lodash_1.isKey(value, object) ? [value] : utils_lodash_1.stringToPath(value == null ? '' : value.toString()); 9 | } 10 | exports.castPath = castPath; 11 | function followRef(root, ref) { 12 | if (ref === undefined) 13 | return ref; 14 | return ref.$type === $ref ? baseGet(root, ref.value) : ref; 15 | } 16 | exports.followRef = followRef; 17 | function baseGet(root, path) { 18 | var index = 0; 19 | var length = path.length; 20 | var object = root; 21 | while (object != null && index < length) { 22 | object = followRef(root, object); 23 | object = object[path[index++]]; 24 | } 25 | return (index && index == length) ? object : undefined; 26 | } 27 | exports.baseGet = baseGet; 28 | function baseSet(object, path, value, customizer) { 29 | if (!utils_lodash_1.isObject(object)) { 30 | return object; 31 | } 32 | var length = path.length; 33 | var lastIndex = length - 1; 34 | var index = -1; 35 | var nested = object; 36 | while (nested != null && ++index < length) { 37 | var key = path[index]; 38 | var newValue = value; 39 | if (index != lastIndex) { 40 | var objValue = nested[key]; 41 | newValue = customizer ? customizer(objValue, key, nested) : undefined; 42 | if (newValue === undefined) { 43 | newValue = utils_lodash_1.isObject(objValue) 44 | ? objValue 45 | : (utils_lodash_1.isIndex(path[index + 1]) ? [] : {}); 46 | } 47 | } 48 | nested[key] = newValue; 49 | nested = nested[key]; 50 | } 51 | return nested; 52 | } 53 | exports.baseSet = baseSet; 54 | -------------------------------------------------------------------------------- /dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-binding", 3 | "version": "0.9.2", 4 | "description": "Binder - react two-way data binding for complex objects and arrays.", 5 | "main": "lib/Binder.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "mocha -R spec ./test" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rsamec/react-binding" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "binding" 19 | ], 20 | "author": "Roman Samec ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/rsamec/react-binding/issues" 24 | }, 25 | "homepage": "https://github.com/rsamec/react-binding", 26 | "devDependencies": {}, 27 | "dependencies": {} 28 | } 29 | -------------------------------------------------------------------------------- /dist/react-binding.min.js: -------------------------------------------------------------------------------- 1 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Binder=t()}}(function(){return function t(e,r,n){function i(u,s){if(!r[u]){if(!e[u]){var f="function"==typeof require&&require;if(!s&&f)return f(u,!0);if(o)return o(u,!0);var a=new Error("Cannot find module '"+u+"'");throw a.code="MODULE_NOT_FOUND",a}var h=r[u]={exports:{}};e[u][0].call(h.exports,function(t){var r=e[u][1][t];return i(r?r:t)},h,h.exports,t,e,r,n)}return r[u].exports}for(var o="function"==typeof require&&require,u=0;u0)throw new Error("Invalid string. Length must be a multiple of 4");return"="===t[e-2]?2:"="===t[e-1]?1:0}function c(t){return 3*t.length/4-h(t)}function l(t){var e,r,n,i,o,u,s=t.length;o=h(t),u=new b(3*s/4-o),n=o>0?s-4:s;var f=0;for(e=0,r=0;e>16&255,u[f++]=i>>8&255,u[f++]=255&i;return 2===o?(i=v[t.charCodeAt(e)]<<2|v[t.charCodeAt(e+1)]>>4,u[f++]=255&i):1===o&&(i=v[t.charCodeAt(e)]<<10|v[t.charCodeAt(e+1)]<<4|v[t.charCodeAt(e+2)]>>2,u[f++]=i>>8&255,u[f++]=255&i),u}function p(t){return y[t>>18&63]+y[t>>12&63]+y[t>>6&63]+y[63&t]}function d(t,e,r){for(var n,i=[],o=e;of?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 | --------------------------------------------------------------------------------