├── .gitignore ├── .gitmodules ├── README.md ├── dist ├── index.js ├── index.js.map ├── index.min.js └── index.min.js.map ├── docs ├── 00_Getting_Started.md ├── 01_Basics │ ├── 01_State_Management_101.md │ ├── 02_Two_way_data_binding.md │ └── 03_Form_Validation.md ├── 02_Handling_The_Complex_State │ ├── 01_Dirty_Top_And_Pure_Bottom .md │ ├── 02_State_and_Props_Type_Annotations.md │ ├── 03_Update_Watchers_and_Transactions.md │ └── 04_Ownership_and_Shared_References.md ├── 03_Working_With_Forms │ ├── 03_Form_Validation.md │ └── Advanced Data Binding.md ├── 04_Optimizing_For_Performance.md ├── 04_Serialization_and_REST │ ├── 01_Relations_and_Stores.md │ └── store spec.md ├── 05_Migration_from_Backbone.md ├── SLOC-comparison.jpg ├── Using jQuery plugins.md ├── _index.md ├── config.json └── flux-sloc.png ├── examples ├── checklistTree │ ├── .babelrc │ ├── README.md │ ├── app.js │ ├── app.js.map │ ├── dist │ │ ├── app.js │ │ └── app.js.map │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── screenshot.png │ ├── src │ │ ├── index.jsx │ │ ├── model.js │ │ └── styles.css │ └── webpack.config.js ├── flux-comparison │ ├── README.md │ ├── application.jpg │ ├── cart.jpg │ ├── index.html │ ├── js │ │ ├── app.js │ │ ├── components │ │ │ ├── App.jsx │ │ │ ├── CartContainer.jsx │ │ │ └── ProductsContainer.jsx │ │ └── models │ │ │ ├── Cart.js │ │ │ └── Product.js │ ├── product.jpg │ ├── sloc-comparison.png │ └── unidirectional-data-flow.jpg ├── todomvc │ ├── .babelrc │ ├── SLOC-comparison.jpg │ ├── app.js │ ├── app.js.map │ ├── css │ │ └── app.css │ ├── dist │ │ ├── app.js │ │ └── app.js.map │ ├── index.html │ ├── js │ │ ├── addtodo.jsx │ │ ├── filter.jsx │ │ ├── main.jsx │ │ ├── model.js │ │ └── todolist.jsx │ ├── package-lock.json │ ├── package.json │ ├── pure-components.jpg │ ├── readme.md │ └── webpack.config.js └── userslist │ ├── .babelrc │ ├── README.md │ ├── app.js │ ├── app.js.map │ ├── dist │ ├── app.js │ └── app.js.map │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── main.css │ └── main.jsx │ └── webpack.config.js ├── lib ├── component-view.d.ts ├── component-view.js ├── component-view.js.map ├── createClass.d.ts ├── createClass.js ├── createClass.js.map ├── index.d.ts ├── index.js ├── index.js.map ├── react-mvx │ ├── component.d.ts │ ├── component.js │ ├── component.js.map │ ├── define │ │ ├── common.d.ts │ │ ├── common.js │ │ ├── common.js.map │ │ ├── context.d.ts │ │ ├── context.js │ │ ├── context.js.map │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.js.map │ │ ├── props.d.ts │ │ ├── props.js │ │ ├── props.js.map │ │ ├── pureRender.d.ts │ │ ├── pureRender.js │ │ ├── pureRender.js.map │ │ ├── state.d.ts │ │ ├── state.js │ │ ├── state.js.map │ │ ├── store.d.ts │ │ ├── store.js │ │ ├── store.js.map │ │ ├── typeSpecs.d.ts │ │ ├── typeSpecs.js │ │ └── typeSpecs.js.map │ ├── index.d.ts │ ├── index.js │ ├── index.js.map │ ├── link.d.ts │ ├── link.js │ ├── link.js.map │ └── valuelink │ │ ├── helpers.d.ts │ │ ├── helpers.js │ │ ├── helpers.js.map │ │ ├── link.d.ts │ │ ├── link.js │ │ └── link.js.map ├── view-element.d.ts ├── view-element.js └── view-element.js.map ├── package-lock.json ├── package.json ├── rollup.config.js ├── rollup.config.minify.js ├── src ├── component-view.ts ├── createClass.ts ├── index.ts ├── react-mvx │ ├── Link.ts │ ├── component.ts │ ├── define │ │ ├── common.ts │ │ ├── context.ts │ │ ├── index.ts │ │ ├── props.ts │ │ ├── pureRender.ts │ │ ├── state.ts │ │ ├── store.ts │ │ ├── typeSpecs.ts │ │ └── types.ts │ ├── index.ts │ └── valuelink │ │ ├── component.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── link.ts │ │ └── tags.tsx └── view-element.ts ├── tags.js ├── tags.jsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | *.orig 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/React-MVx"] 2 | path = submodules/React-MVx 3 | url = https://github.com/Volicon/React-MVx 4 | branch = master -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important notice 2 | 3 | NestedReact is the BackboneJS compatibility layer for [React-MVx](https://volicon.github.io/React-MVx/) - modern React MVVM application framework. It will be maintained as long as Verizon/Volicon systems will depend in the legacy technologies - BackboneJS Views and jQuery. 4 | 5 | If you don't need to reuse BackboneJS Views in your React application - please, switch to [ReactMVx](https://volicon.github.io/React-MVx/). 6 | 7 | NestedReact documentation won't be updated. Use React-MVx docs as your primary source of docs. 8 | 9 | # Features 10 | 11 | Feature list consists of all the features of React-MVx v2.x, plus this: 12 | 13 | - Gradual transition procedure for backbone applications ([Backbone Migration Guide](/docs/05_Migration_from_Backbone.md)): 14 | - Complete interoperation with existing Backbone Views allowing you to reuse existing code and avoid upfront application rewrite. 15 | - Any type of application refactoring strategy is possible - top-to-bottom, bottom-to-top, and random parts at the middle. 16 | - Support for Backbone events and jQuery accessors in React components simplifies View refactoring. 17 | 18 | ## [Documentation](https://volicon.github.io/React-MVx/) 19 | 20 | Please, use React-MVx documentation as a primary source of documentation and examples. 21 | 22 | ## Installation and Requirements 23 | 24 | It's packed as single UMD, thus grab the module or use `npm` to install. 25 | It has [NestedTypes model framework](http://volicon.github.io/NestedTypes/), `react`, `react-dom`, `prop-types`, `jquery`, and `underscore` as strong dependencies. 26 | 27 | npm install --save-dev nestedreact nestedtypes underscore jquery react react-dom prop-types 28 | 29 | Module extends React namespace (without modifying original React), and its 30 | safe to use it as a replacement for `react`. 31 | 32 | import React from 'nestedreact' 33 | 34 | If you're migrating from backbone-based frameworks such as `ChaplinJS` or `Marionette`, 35 | you need to do following things to make convergence layer work properly: 36 | 37 | - Make sure that frameworks includes `nestedtypes` instead of `backbone`. 38 | - On application start, tell `nestedreact` to use proper base class for the View. 39 | 40 | React.useView( Chaplin.View ); 41 | -------------------------------------------------------------------------------- /docs/00_Getting_Started.md: -------------------------------------------------------------------------------- 1 | NestedReact is the front-end framework which brings advantages of React's view layer 2 | to the classic MVC architecture which is familiar to the most application developers. You've got the complete solution 3 | covering all needs of large SPA _except_ the routing (choose the one which suites you best). 4 | 5 | - *Models* as data layer building blocks: 6 | - deeply observable ("reactive"); 7 | - automatically serializable (any model can be instantly turned to the REST endpoint); 8 | - aware of one-to-many and many-to-many relations out of the box. 9 | - Unified state management. 10 | - component's state is the *model* (looks like plain object, observable, serializable, etc); 11 | - components observes state changes and update itself. 12 | - Forms handling as you expect it to be in XXIst century: 13 | - two-way data binding; 14 | - declarative validation. 15 | - Unidirectional data flow and pure render. 16 | - No tricks. No mind-blowing concepts or crazy looking code. It just works. 17 | - React Mixins support for ES6 classes components. 18 | - Easy migration from BackboneJS, if you need it. 19 | 20 | NestedReact is built around NestedTypes data framework (for M), React (for V), and don't enforce any routing solution (unopinionated on C). 21 | 22 | ## Basic Usage 23 | 24 | NestedReact exports modified React namespace (original React is untouched). So, use it as React replacement like this: 25 | 26 | ```javascript 27 | import React from 'nestedreact' 28 | ``` 29 | 30 | The most important capability NestedReact brings to React is universal state management; the same technique is used for handling both component's local UI state, persistent data, and shared global state. 31 | 32 | In the simplest case of the local UI state, 33 | it looks to your as if your state just instantly became observable. You handle the flat state as if it would be the plain object. 34 | 35 | ```javascript 36 | import React from 'nestedreact' 37 | 38 | export const MyComponent = React.createClass({ 39 | state : { 40 | count : 0 41 | }, 42 | 43 | render(){ 44 | const { state } = this; 45 | 46 | return ( 47 |
state.count++ }> 48 | { state.count } 49 |
50 | ); 51 | } 52 | }); 53 | ``` 54 | 55 | In case of ES6 classes declarations are done with statics members 56 | and class definition must be preceded with the `@define` decorator. 57 | 58 | ```javascript 59 | import React, { define } from 'nestedreact' 60 | 61 | @define 62 | export class MyComponent extends React.Component { 63 | static state = { 64 | count : 0 65 | } 66 | 67 | render(){ 68 | const { state } = this; 69 | 70 | return ( 71 |
state.count++ }> 72 | { state.count } 73 |
74 | ); 75 | } 76 | } 77 | ``` 78 | 79 | If you prefer to avoid decorators, just invoke component's static `define()` method directly after the class definition. 80 | 81 | ```javascript 82 | MyComponent.define(); 83 | ``` 84 | 85 | If you prefer to avoid inline statics, you may pass the specification directly to the `define()` method: 86 | 87 | ```javascript 88 | MyComponent.define({ 89 | state : { 90 | count : 0 91 | } 92 | }); 93 | ``` 94 | 95 | ## Installation 96 | 97 | All modern browsers and IE10+ are supported. Packed as UMD, can be installed using npm. 98 | 99 | Requires `nestedtypes`, `react`, and `react-dom` as a peer dependencies. 100 | 101 | *NestedTypes* itself can be used as a drop-in *BackboneJS* replacement, 102 | and thus requires `jquery` and `underscore` as peer dependencies. Therefore: 103 | 104 | `npm install react react-dom nestedreact nestedtypes jquery underscore --save-dev` 105 | 106 | See examples in the [examples folder](https://github.com/Volicon/NestedReact/tree/master/examples) 107 | for the starting boilerplate. 108 | 109 | ## Optimizing for size 110 | 111 | If you're not interested in legacy technologies support and would like to reduce the 112 | size of your app assembly, wait a bit for stripped version which is one the way. 113 | Meanwhile, you can: 114 | 115 | - Omit jQuery. Without that Model/Collection REST endpoints and Backbone shims will stop working. 116 | - Use lightweight jQuery replacements in the same way as people do with Backbone. 117 | - Don't bother and enjoy. That extra legacy stuff is really the small fraction of the assembly. 118 | 119 | ## Why another JS framework 120 | 121 | React was the huge advancement over client side templating solutions used 122 | previously for "V" in the majority of MVC frameworks. Unfortunately, we 123 | don't feel the same about the application state management architectures 124 | being [advocated by Facebook](https://facebook.github.io/flux/). 125 | 126 | As NestedReact demonstrates, the dramatical shift in paradigm is not required. 127 | It is possible to implement unidirectional data flow with "dirty" mutable data. 128 | 129 | And it's not only possible, but [highly beneficial](https://medium.com/@gaperton/software-managing-the-complexity-caff5c4964cf#.dn3aq4riy). 130 | Here's [TodoMVC](http://todomvc.com/) solution size chart for different 131 | frameworks and combinations of tools. As you can see, an old-school MVC approach the majority of developers are perfectly familiar with 132 | [allowing us to write twice less code](https://github.com/Volicon/NestedReact/tree/master/examples/todomvc) 133 | for the same task than all the "flux" stuff. So the overall result is comparable 134 | to Angular 2. We're in the same league now. 135 | 136 | ![TodoMVC solution size comparison](SLOC-comparison.jpg) 137 | 138 | If you take [flux-comparison](https://github.com/voronianski/flux-comparison) example, difference will become even more spectacular. 139 | 140 | ![flux-comparison solution size](flux-sloc.png) 141 | 142 | Here's our [solution](https://github.com/Volicon/NestedReact/tree/master/examples/flux-comparison). Check it out. 143 | -------------------------------------------------------------------------------- /docs/01_Basics/01_State_Management_101.md: -------------------------------------------------------------------------------- 1 | The first and most notable thing NestedReact does is that it deprecates 2 | React state, replacing it with NestedTypes model. Thus, you have the single 3 | technique to manage all the state in your application. 4 | 5 | You start with importing React from `nestedreact`. 6 | 7 | ```jsx 8 | import React, { define } from 'nestedreact' 9 | ``` 10 | 11 | Definition of the component must be preceded with `@define` decorator. 12 | 13 | ```jsx 14 | @define 15 | export class MyComponent extends React.Component { 16 | ``` 17 | 18 | Then, we define the state. In the simplest case, the definition 19 | looks like the plain JS object where state attributes are listed along with their default values. 20 | All attributes of the state you're going to use must be declared. 21 | 22 | ```jsx 23 | static state = { 24 | count : 0 25 | } 26 | ``` 27 | 28 | And then, you just access `this.state` as if it would be the plain object. 29 | 30 | ```jsx 31 | render(){ 32 | const { state } = this; 33 | 34 | return ( 35 |
state.count++ }> 36 | { state.count } 37 |
38 | ); 39 | } 40 | } 41 | ``` 42 | 43 | Fairly simple. Now, it's time to describe what really happens behind the scene 44 | inside of the `@define` decorator and how this example works. 45 | 46 | ## NestedTypes models as React state 47 | 48 | First, `@define` looks for the `state` static variable. If it present, 49 | it defines the NestedTypes model describing the state. Whatever you write in 50 | `state` will just go to this model's `attributes` spec. Like this: 51 | 52 | ```jsx 53 | @define 54 | class MyState extends Nested.Model { 55 | static attributes = { 56 | count : 0 57 | } 58 | } 59 | ``` 60 | 61 | Then, it will attach special mixin to your component which will create 62 | an instance of this model before mount, and will subscribe to the model 63 | changes to update UI. It works as if you have the following code in your component: 64 | 65 | ```jsx 66 | componentWillMount(){ 67 | this.state = new MyState(); 68 | } 69 | 70 | componentDidMount(){ 71 | this.state.on( 'change', () => this.forceUpdate() ); 72 | } 73 | ``` 74 | 75 | An then, considering the fact that NestedTypes model behaves mostly as 76 | an object with observable changes, you can just assign `this.state.count` 77 | directly. Which will emit `change` event, and trigger UI update. 78 | 79 | Alternatively, you can define the state separately like we did here and tell the component 80 | to use it as state. You just need to use `Model` static property instead of `state`. 81 | 82 | ```jsx 83 | static Model = MyState; 84 | ``` -------------------------------------------------------------------------------- /docs/01_Basics/02_Two_way_data_binding.md: -------------------------------------------------------------------------------- 1 | All modern JS frontend application frameworks supports two-way data binding. 2 | NestedReact is not an exception. In this example we will bind the state member to the `input` control. 3 | 4 | In order to use data binding, you need to import data bound controls from `nestedreact/tags.jsx` module first. 5 | 6 | ```javascript 7 | import React, { define } from 'nestedreact' 8 | import { Input } from 'nestedreact/tags.jsx' 9 | ``` 10 | 11 | Then, you have to create data binding link for the state attribute using `getLink( key )` method, 12 | and pass it to `valueLink` Input's property. 13 | 14 | ```javascript 15 | @define 16 | export class MyComponent extends React.Component { 17 | static state = { 18 | text : '' 19 | } 20 | 21 | render(){ 22 | return ; 23 | } 24 | } 25 | ``` 26 | 27 | Link can be created for any NestedTypes model attribute (and `state` is the NestedTypes model internally). 28 | 29 | `const link = model.getLink( 'attr' )` 30 | 31 | If you have a form with a lot of controls, you can create links in a bulk with a single line 32 | using `model.linkAll( ... )` method. Which is the preferable way of dealing with the complex forms. 33 | 34 | ```javascript 35 | render(){ 36 | const links = this.state.linkAll( 'a', 'b', 'c' ); 37 | 38 | return ( 39 |
40 | 41 | 42 | 43 |
44 | ); 45 | } 46 | ``` 47 | 48 | ## How it works 49 | 50 | [To explain `valuelink` pattern simply](https://medium.com/@gaperton/managing-state-and-forms-with-react-part-1-12eacb647112#.6mqtojilu), is an object holding the *value* and the *callback to update the value*. It is something 51 | close to this: 52 | 53 | ```javascript 54 | render(){ 55 | const link = { 56 | value : this.state.text, 57 | set : x => this.state.text = x 58 | }; 59 | 60 | return ; 61 | } 62 | ``` 63 | 64 | And, an Input control which consumes such a link would look like this: 65 | 66 | ```javascript 67 | const Input = ({ valueLink }) => ( 68 | valueLink.set( e.target.value ) /> 70 | ); 71 | ``` 72 | 73 | NestedReact's [link implementation](https://github.com/Volicon/NestedLink) works close to the code above, but is way more 74 | sophisticated. Links are cached inside of the React component, so they are reused on subsequent render 75 | if the enclosed value is was not changed. Links also the basis for [declarative form validation](https://medium.com/@gaperton/react-forms-with-value-links-part-2-validation-9d1ba78f8e49#.10rn9ug6w). 76 | 77 | NestedReact's links are also available as a [separate package](https://github.com/Volicon/NestedLink), and can be used independently. -------------------------------------------------------------------------------- /docs/01_Basics/03_Form_Validation.md: -------------------------------------------------------------------------------- 1 | There are two different approaches 2 | -------------------------------------------------------------------------------- /docs/02_Handling_The_Complex_State/01_Dirty_Top_And_Pure_Bottom .md: -------------------------------------------------------------------------------- 1 | *"Complex state"* is the state composed of nested parts (typically 2 | other models and collections). It's done with mentioning type 3 | constructors in model or state attribute declaration instead of the default value. 4 | 5 | Such a members called *aggregated members*. When the model is... 6 | 7 | - ...created with `new`, aggregated members are created too. 8 | - ...cloned with `model.clone()`, aggregated members are cloned. 9 | - ...disposed with `model.dispose()`, aggregated members are disposed. 10 | 11 | And when nested models and collections are *modified*, root model holding 12 | React component state detects the change, and force its component to update. 13 | In conjunction, these features allows you to describe fairly complex 14 | components using just component state. 15 | 16 | ## Example: unidirectional data flow with complex state 17 | 18 | Here we're demonstrating the main pattern which is used to structure 19 | applications with `NestedReact`. Top-level "dirty" component holding the complex 20 | mutable state, passing its parts down to "pure" children components with props. 21 | 22 | In order to define models which are parts of the React state, you 23 | will need to import `Model` base class from `nestedtypes`. 24 | 25 | ```javascript 26 | import { Model, define } from 'nestedtypes' 27 | import React from 'nestedreact' 28 | ``` 29 | 30 | It doesn't matter where `@define` decorator comes from, it's the same. 31 | Now lets define some simple model. 32 | 33 | ```javascript 34 | @define 35 | class Counter extends Nested.Model { 36 | static attributes = { 37 | count : 0 38 | } 39 | } 40 | ``` 41 | 42 | Then, use it to define more complex structure. 43 | Model collection can be referenced as `Model.Collection`, and mostly 44 | conforms to [BackboneJS collection API](http://backbonejs.org/#Collection). 45 | They can be created directly with `new` or used as attribute type. 46 | 47 | ```javascript 48 | @define 49 | class ComplexState extends Nested.Model { 50 | static attributes = { 51 | created : Date, 52 | counter : Counter, 53 | counters : Counter.Collection 54 | } 55 | } 56 | ``` 57 | 58 | And now, let's define some root component managing the complex UI state. 59 | 60 | ```javascript 61 | @define 62 | class View extends React.Component { 63 | static state = { // <- define the model describing UI state 64 | dummy : ComplexState 65 | } 66 | 67 | render(){ 68 | return ( 69 |
70 | { this.state.dummy.counters.map( counter => ( 71 | 72 | ) ) } 73 |
74 | ); 75 | } 76 | } 77 | ``` 78 | 79 | And finally, we can make pure component for the counter, receiving 80 | the nested model in props. It will handle the click, 81 | and increment the counter. Which will be noticed by the root component 82 | because this counter is the part of its complex state, thus it will trigger 83 | UI update. 84 | 85 | ```javascript 86 | const CounterItem = ({ model }) => ( 87 |
model.counter++ }> 88 | { model.counter } 89 |
90 | ); 91 | ``` 92 | 93 | > Conceptually, this is the most common pattern of structuring an application 94 | > in the majority of the functional languages which are used for production in the wild. 95 | > Those like OCaml (and F#), or Erlang (and Elixir). 96 | -------------------------------------------------------------------------------- /docs/02_Handling_The_Complex_State/02_State_and_Props_Type_Annotations.md: -------------------------------------------------------------------------------- 1 | In NestedTypes, model does not behave like key-value hash but rather 2 | like a record or class from statically typed languages like Java or C++. 3 | All model attributes have to be declared and they are *typed*. 4 | 5 | Full form of attribute type annotation is this: 6 | 7 | `attrName : Constructor.value( defaultValue )` 8 | 9 | With this spec, attribute will be initialized like this: 10 | 11 | `model.attrName = new Constructor( defaultValue )` 12 | 13 | Two shorter forms of the type spec are: 14 | 15 | 1. `attrName : defaultValue`. Attribute type will be inferred from the default value. 16 | 2. `attrName : Constructor`. Every function here is assumed to be constructor. 17 | 18 | > For an attribute of type `Function` you have to use full form of the spec: 19 | > 20 | > `attrName : Function.value( x => x )` 21 | > 22 | > Type inference can be turned off completely with `Nested.value( ... )` spec: 23 | > 24 | > `attrName : Nested.value( x => x )` 25 | 26 | ### Dynamic Type Safety 27 | 28 | NestedTypes uses information about types in the variety of different ways. 29 | Although it doesn't check types at compile time, it *guarantees that 30 | attributes will always have values of declared types* because they are guarded 31 | with run-time type assertions and automatic type casts. 32 | 33 | Type casts are performed slightly differently for different types, but 34 | semantically they follow the simple rule: value of incompatible type is being 35 | passed as an argument to the attribute's type constructor. 36 | 37 | `m.attr = new Constructor( incompatibleValue );` 38 | 39 | ```javascript 40 | @define 41 | class Sample extends Nested.Model { 42 | static attributes = { 43 | time : Date, 44 | num : 0, // same as Number or Number.value( 0 ) 45 | str : '', // same as String or String.value( '' ) 46 | bool : false, // same as Boolean or Boolean.value( false ) 47 | any : null // same as Nested.value( null ). Untyped attribute. 48 | } 49 | } 50 | 51 | const s = new Sample(); 52 | 53 | s.time = 32787878; // = new Date( 32787878 ) 54 | s.num = "676743"; // = 676743 55 | s.num = "hjkghjgfdsj"; // = NaN 56 | s.str = 123; // = "123" 57 | s.bool = s.num && s.str; // = Boolean( s.num && s.str ) 58 | s.any = holdsAnothing; // no type convertions. 59 | ``` 60 | 61 | Even if you omit explicit type annotation, attribute is still typed 62 | (and guarded) according to the type of the default value. 63 | 64 | To turn type inference off in attribute declaration, either specify `null` 65 | or `void 0` as default value, or use `Nested.value( defaultValue )` as type spec. 66 | 67 | ### Props and Context Type Annotations 68 | 69 | Same style type annotations may be used for the `props` as 70 | a replacement for `propTypes` and `getDefaultProps()`. 71 | As they are just compiled to `propTypes` and `getDefaultProps()`, they works a bit differently from the state attributes annotations: 72 | 73 | - No automatic type conversion. Just standard `propTypes` type asserts. 74 | - Default value is created only when it is provided explicitly. 75 | 76 | It works in the same way for the `context` and `childContext` as a replacement for React's 77 | `contextTypes` and `childContextTypes` respectively. But default values are ignored. 78 | 79 | ```javascript 80 | @define 81 | class MyComponent extends React.Component { 82 | static props = { 83 | callback : Function.value( () => void 0 ), 84 | optionalProp : String, // no default value 85 | propWithDefaultValue : '' 86 | } 87 | 88 | render(){ /*...*/ } 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /docs/02_Handling_The_Complex_State/03_Update_Watchers_and_Transactions.md: -------------------------------------------------------------------------------- 1 | NestedTypes (and NestedReact) gives you precise control over 2 | the way how state is being changed, allowing you to watch attributes 3 | for changes and attach some reactions to it. This (and variety of 4 | other things) can be done with the help of `has` type annotation. 5 | 6 | In this example, we have `local` state member, which is a string. On every 7 | change it will call the *watcher* function, which is taken from the props. 8 | 9 | ```javascript 10 | @define 11 | class MyComponent extends React.Component { 12 | static props = { 13 | callback : Function.value( x => void 0 ) 14 | } 15 | 16 | static state = { 17 | local : String.has.watcher( '^props.callback' ) 18 | } 19 | 20 | render(){ /*...*/ } 21 | } 22 | ``` 23 | 24 | `.has.watcher( path | function )` annotation either takes "watcher" function 25 | as an argument, or the string *path* to the watcher function taken relative to the current 26 | model's `this`. `^` in the path is the shorthand for the `getOwner()` call, which in our case returns 27 | React component itself. So, `^props.callback` is being translated to this: 28 | 29 | `this.getOwner().props.callback`. 30 | 31 | ## How watchers works 32 | 33 | NestedTypes is mostly backward compatible with BackboneJS in term of [events 34 | emitted by models and collections](http://backbonejs.org/#Events-catalog). 35 | The difference is that mechanics of change events and updates is generalized 36 | on the case of nested models and collections. 37 | 38 | ### How model tree handles an update 39 | 40 | All updates are executed in the scope of transactions, and transaction 41 | have three stages. 42 | 43 | At the first stage, update is applied and attributes receive the new values. 44 | 45 | At the second stage, `change:attrName` event is being triggered for every 46 | changed attribute. Any additional changes to the current models and its 47 | attributes which made as a reaction to these events are executed in the scope of 48 | the same transaction and will trigger additional `change:attr` events. 49 | 50 | At the third stage, model triggers `change` event, and when `change` event 51 | processing sequence is done, it closes the transaction and notifies upper 52 | level model, collection, or React component (if any) of change. 53 | 54 | Therefore, change events handlers are synchronous, but *they are executed as wave 55 | from bottom to the top*. And updates made from inside of the handlers 56 | won't result in additional change events at the upper levels. 57 | 58 | *Attribute watcher* behaves mostly similar to `change:attrName` event handler, 59 | with the following differences: 60 | 61 | - Relative symbolic references (paths) to event handlers are allowed, 62 | and handler is called with the right context according to the path. 63 | - Event handler receives the new attribute value as first argument. 64 | - Event handler can be missing, it's fine. 65 | 66 | For the case of the collection, the transactions mechanic is the same, with 67 | a difference that individual model's `add`, `remove`, and `change` events are 68 | emitted during the phase 2 instead of `change:attrName`, and `changes` event 69 | is triggered at the stage 3 instead of `change`. 70 | 71 | ### Explicit transactions control 72 | 73 | Every model attribute assignment or collection modification create implicit 74 | transaction which triggers the wave of change events from the bottom to the top. 75 | 76 | You can group the series of modifications to the single transaction 77 | explicitly with `transaction()` method. 78 | 79 | ```javascript 80 | my.model.transaction( model => { 81 | model.a = 1; 82 | model.and.nested.attr = 2; 83 | model.itemsCollection.add( { id: 1, name : 'Hi' } ); 84 | }); 85 | ``` 86 | 87 | This code executed synchronously and results in the single `change` 88 | event triggered by `my.model` *only in case* there have been any real 89 | changes made, i.e. `model.a` wasn't already equal to 1 and so on. 90 | 91 | This technique works for both models and collections, can improve performance, 92 | and reduce overall amount of renders. Additionally, collection has 93 | transactional version of `each()` method (`collection.updateEach()`) 94 | which iterates through the collection in the scope of transaction. 95 | 96 | There are other methods of model and collection which are transactional: 97 | - `model.set( { a : 1, b : 2, ... } )` 98 | - `collection.set( ... )`, `collection.add( ... )`, `collection.remove( ... )` 99 | 100 | -------------------------------------------------------------------------------- /docs/02_Handling_The_Complex_State/04_Ownership_and_Shared_References.md: -------------------------------------------------------------------------------- 1 | Simple form of the type annotation for the nested model/collection 2 | assumes that the attribute is *aggregated*. Which means, 3 | it an integral part of model, so when the model is... 4 | 5 | - ...created with `new`, aggregated members are created too. 6 | - ...cloned with `model.clone()`, aggregated members are cloned. 7 | - ...disposed with `model.dispose()`, aggregated members are disposed. 8 | 9 | Aggregation (and its strong form - composition) implies that there 10 | are one and only one "owner" for the aggregated object. 11 | 12 | That's, however, is not the thing what we always want. 13 | 14 | ## Example: stateful select list 15 | 16 | We want to create the component which would display the list of items 17 | and allow us to select one of them. For the purpose of the example, 18 | it would be completely *stateful* and self-dependent component. 19 | 20 | How its state would look like? Obviously, it should hold the collection. 21 | And the selected element. The code would look like this, and it would be 22 | almost correct. 23 | 24 | ```javascript 25 | import React, { define } from 'nestedreact' 26 | import Item from './item' 27 | 28 | class SelectList extends React.Component { 29 | static state = { 30 | items : Item.Collection, 31 | selected : Item 32 | } 33 | 34 | componentWillMount(){ 35 | /* some IO returning the promise to load the state... Not really important.*/ 36 | .done( json => this.state.set( json, { parse : true } ); 37 | } 38 | 39 | render(){ 40 | const { state } = this; 41 | return ( 42 |
43 | { state.items.map( item => ( 44 |
state.selected = item } 47 | > 48 | { item.name } 49 |
50 | ) 51 | ) } 52 |
53 | ); 54 | } 55 | } 56 | ``` 57 | 58 | There are few problems, though. 59 | 60 | First, we don't want `state.selected` to create any model. Nothing is selected 61 | by default (so it should be `null`). 62 | 63 | Second, `state.selected` holds the members of the `state.items` collection. Thus, 64 | they have an owner already. And in that exotic case if we will need to clone our state, 65 | `selected` should not be cloned. Rather copied by reference. And if we will try to 66 | dispose `selected`, it will be disposed twice. 67 | 68 | Clearly, `selected` attribute holds *shared*, not an aggregated model. 69 | Which we can indicate adding `.shared` modifier right after its constructor type. 70 | Indicating that the value of an attribute is not really belong here, 71 | but is borrowed from some other place. 72 | 73 | ```javascript 74 | static state = { 75 | items : Item.Collection, 76 | selected : Item.shared // null by default, don't take ownership 77 | } 78 | ``` 79 | 80 | So, what exactly `.shared` means? 81 | 82 | Considering that the aggregation forms model/collection tree which 83 | elements have the single owner. `shared` attribute can hold models and 84 | collections taken from *other* ownership trees. 85 | 86 | Speaking simply, if aggregated attribute behaves as if it's included *by value*, 87 | shared attribute is included *"by reference"*: 88 | 89 | - it's null by default. 90 | - when the model is cloned, it's not cloned. 91 | - when the model is disposed, it keeps living. 92 | 93 | ### Example: select from the global list 94 | 95 | For the purpose of the example, imagine that we have the global 96 | collection of some items which we would like to reference in some 97 | component. It can be the select list of some globally available list of 98 | options. And we want to update the UI in case if this list is changed. 99 | 100 | Many people would argue that global `items` list shouldn't be the part 101 | of the local component state. 102 | 103 | Good news that in this example attribute type for 104 | the `state.items` is inferred as `Item.Collection.shared`. So, it 105 | not an aggregation, and doesn't behave as an integral part of `state`. 106 | 107 | Doing so, though, is the convenient way to ensure that component 108 | will be automatically updated in case if `items` will change. The 109 | rest of UI shouldn't. Should it? 110 | 111 | ```javascript 112 | import React, { define } from 'nestedreact' 113 | import { items } from './globals' 114 | 115 | @define 116 | class SelectList extends React.Component { 117 | static props = { 118 | selectedLink : React.Link // <- its value link holding the selected model 119 | } 120 | 121 | static state = { 122 | items : items // <- our global list of items 123 | } 124 | 125 | componentWillMount(){ 126 | // some IO returning the promise to load the state... Not really important. 127 | .done( json => this.state.set( json, { parse : true } ); 128 | } 129 | 130 | render(){ 131 | const { state, props } = this; 132 | return ( 133 |
134 | { state.items.map( item => ( 135 |
item ) } 138 | > 139 | { item.name } 140 |
141 | ) 142 | ) } 143 |
144 | ); 145 | } 146 | } 147 | ``` 148 | -------------------------------------------------------------------------------- /docs/03_Working_With_Forms/03_Form_Validation.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | @define 3 | class User extends Model { 4 | static attributes = { 5 | name : String.has 6 | .check( isRequired ) 7 | .check( x => x.indexOf( ' ' ) < 0, 'Spaces are not allowed' ), 8 | 9 | email : String.has 10 | .check( isRequired ) 11 | .check( isEmail ), 12 | 13 | isActive : true 14 | } 15 | 16 | remove(){ this.collection.remove( this ); } 17 | } 18 | ``` 19 | 20 | 21 | 22 | 23 | ## How it works 24 | Links carry additional `validationError` field for validation purposes (to be used inside of custom UI controls). It's populated automatically for links created from models and collection, 25 | utilizing `nestedtypes` validation mechanics. Therefore, if model attribute has any `check` attached, its link will carry its `validationError` object. 26 | 27 | ```javascript 28 | var M = Nested.Model.extend({ 29 | defaults : { 30 | attr : Number.has.check( x => x > 0, 'attr should be positive' ) 31 | } 32 | }); 33 | 34 | ... 35 | 36 | var m = new M({ attr : -1 }); 37 | var attrLink = m.getLink( 'attr' ); 38 | 39 | console.assert( m.value === -1 ); 40 | console.assert( m.validationError === 'attr should be positive' ); 41 | ``` 42 | 43 | It's possible to use ad-hoc validation in component's `render` method with `link.check` method. 44 | 45 | ```javascript 46 | var link = model.getLink( 'something' ) 47 | .check( x => x > 0, 'Shoulld be positive' ) 48 | .check( x => x < 10, 'Shoulld be lesser than 10' ); 49 | ``` 50 | 51 | Failed checks will populate link's `validationError` with first failed check's message. 52 | -------------------------------------------------------------------------------- /docs/04_Optimizing_For_Performance.md: -------------------------------------------------------------------------------- 1 | ## Performance optimizations 2 | 3 | ### Pure Render 4 | 5 | ### Transactional data updates 6 | 7 | ### Local UI updates 8 | 9 | ## Old 10 | 11 | ## Props specs and pure render optimization 12 | 13 | One of the problems of unidirectional data flow is that large part of UI is being 14 | updated for every small change. Though React does its job avoiding unnecessary DOM manipulations, 15 | it's still takes a lot of computation resources to compare new and old UI components trees. 16 | `Pure render` optimization strategy avoids rendering and comparison of subtrees which has not been changed 17 | by adding special `props` comparison function (`shouldComponentUpdate`). This optimization 18 | is most effective on the top level close to the state holder component. 19 | 20 | NestedReact support this optimization, comparing props model's and collection version tokens to ones used during the last render, 21 | and comparing other props values for strict equality. 22 | 23 | To enable this optimization for the particular component, you need to: 24 | 25 | - Declare all props that will be tracked for changes in `props` (or `propTypes`) spec. 26 | Which is the good practice by itself, so you encouraged to do it unless you're using 27 | stateless function components syntax, which is preferable. 28 | - Add `pureRender : true` to component definition. 29 | 30 | As from previous example: 31 | 32 | ```javscript 33 | var Bottom = React.createClass({ 34 | props : { 35 | model : Counter 36 | }, 37 | 38 | pureRender : true, 39 | 40 | render(){ 41 | const { model } = this.props; 42 | return ( 43 |
model.count += 1 }> 44 | { model.count } 45 |
46 | ); 47 | ) 48 | }); 49 | ``` 50 | 51 | NestedReact `props` spec uses the simple subset of `state` spec, and acts as substitution for `propTypes` (in fact, 52 | it internally compiles itself to the `propTypes` and `getDefaultProps()`). 53 | 54 | Following type annotations are allowed for `props`: 55 | 56 | 1. Constructor functions: `prop1 : String` 57 | 2. Constructors with default value: `prop2 : String.value( "default string" )` 58 | 3. JSON and primitive values: `prop3 : "default string"` 59 | 4. Special PropTypes cases: 60 | - `PropTypes.any` -> `undefined` (no default value) or `null` (with `null` default value) 61 | - `PropTypes.node` -> `React.Node` 62 | - `PropTypes.element` -> `React.Element` 63 | 64 | If prop has explicitly declared default value, as in (2) or (3), it will be added to `getDefaultProps`. 65 | 66 | ## Partial component subtree updates 67 | 68 | For the number of reasons, you may need some parts of components subtrees to listen for props updates independently. 69 | It might be required if model or collection props are not the part of any upper component state. 70 | This situation frequently happens during transition period when you're in the middle of refactoring large 71 | backbone application. 72 | 73 | To make `` component listen to and update on its `model` prop change, it's enough to add 74 | `listenToProps` option to component spec. It will play well with `pureRender`, effectively 75 | avoiding unnecessary renders if top level component will trigger update on the same change too. 76 | 77 | ```javscript 78 | var Bottom = React.createClass({ 79 | props : { 80 | model : Counter 81 | }, 82 | 83 | listenToProps : 'model', // space separated list of prop names 84 | 85 | // ...all other stays the same... 86 | }); 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/04_Serialization_and_REST/01_Relations_and_Stores.md: -------------------------------------------------------------------------------- 1 | PROPOSAL 2 | 3 | As we move to the local store architecture, 4 | the typical app structure looks like this: 5 | 6 | - There's the global default store, which is available across the pages. 7 | - There are local stores which lifecycle is tied to the root page components. 8 | 9 | There are two general approaches to handle this situation: 10 | 11 | - Dedicated component to handle store (which is then exposed through the context). 12 | - Facilities to handle store inside of the stateful component. 13 | 14 | ```javascript 15 | @define 16 | @mixins( React.mixins.LocalStorageState ) 17 | class MyPage extends React.RestStoreComponent { 18 | static store = { 19 | /* store definition */ 20 | } 21 | 22 | static state = { // references to the defined store. 23 | 24 | } 25 | 26 | render(){ // Fetches the data, delays the render. Wrap it in define. 27 | this.store.xxx 28 | } 29 | } 30 | ``` 31 | 32 | 33 | ## Option A. Support for declaring stores inside of the components. 34 | 35 | Make the specified store the default one for all state elements in the subtree. 36 | 37 | Not clear, if its really needed at all (for the subtree). Should be really the rare case. 38 | 39 | Might be helpful for the local state, though, and for the local change events subscription. 40 | 41 | ```javascript 42 | @define 43 | class AppPage extends React.Component { 44 | static defaultStore = externalStoreInstance 45 | } 46 | ``` 47 | 48 | Create local store with a lifecycle bound to the component. 49 | 50 | ```javascript 51 | @define 52 | class MyStore extends RestStore { 53 | static attributes = { 54 | users : User.Collection, 55 | roles : Role.Collection 56 | } 57 | } 58 | 59 | @define 60 | class AppPage extends React.Component { 61 | // Not clear if it's any better than the plain model. 62 | // Might be the matter of convinience to separate it from the local state. 63 | static store = MyStore 64 | 65 | componentWillMount(){ 66 | // This thingy should be useful. Need to take the sequence of promises. 67 | this.wait( this.store.fetch() ); 68 | } 69 | } 70 | ``` 71 | 72 | If we don't propagate it in the context, there almost no difference 73 | with state. 74 | 75 | ## Option B. Minimal changes. 76 | 77 | - Just use custom RestStore as the local state 78 | - Introduce `wait()` method. 79 | - `defaultStore` spec? 80 | - singleton listener spec 81 | 82 | ```javascript 83 | @define 84 | class MyStore extends RestStore { 85 | static attributes = { 86 | users : User.Collection, 87 | roles : Role.Collection 88 | } 89 | } 90 | 91 | @define 92 | class AppPage extends React.Component { 93 | // Not clear if it's any better than the plain model. 94 | // Might be the matter of convinience to separate it from the local state. 95 | static Model = MyStore 96 | 97 | componentWillMount(){ 98 | // This thingy should be useful. Need to take the sequence of promises. 99 | this.renderAfter( this.store.fetch() ); 100 | } 101 | } 102 | ``` 103 | 104 | ## Option C. Dedicated component 105 | 106 | The good thing is that we can wrap all IO related stuff there. 107 | 108 | Problem is that you cannot access the stuff there. 109 | 110 | ```javascript 111 | @define 112 | class MyStore extends RestStore { 113 | static attributes = { 114 | users : User.Collection, 115 | roles : Role.Collection 116 | } 117 | } 118 | 119 | @define 120 | class AppPage extends React.Component { 121 | render(){ 122 | // Create and fetch the thingy, delay rendering. 123 | 124 | 125 | 126 | } 127 | } 128 | ``` 129 | 130 | ## Option B': Rely on hxr to hide content. 131 | 132 | If `hasPendingIO()` returns true, don't render. 133 | 134 | update on 'request' and 'sync' events. 135 | 136 | or 137 | 138 | force change event at the begin and at the end of the transaction. 139 | Better, because of renders deduplication. Much more complex. 140 | 141 | or 142 | 143 | modify _xhr exactly _before_ 'request', 'sync', and 'error', but after all changes 144 | are applied. 145 | 146 | ```javascript 147 | 148 | 149 | 150 | ``` 151 | 152 | -------------------------------------------------------------------------------- /docs/04_Serialization_and_REST/store spec.md: -------------------------------------------------------------------------------- 1 | ## reference to existing store 2 | 3 | ```javascript 4 | static store = storeInstance; 5 | ``` 6 | 7 | - Make this store the default store for the current component and to the whole components subtree. 8 | - Chained store lookup. 9 | - Listen to the 'change' event and render. 10 | 11 | ## entirely local store 12 | 13 | ```javascript 14 | static store = StoreClass; 15 | ``` 16 | 17 | - componentWillMount: create the store, fetch if possible. 18 | - componentWillUnmount: dispose the store. 19 | -------------------------------------------------------------------------------- /docs/SLOC-comparison.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/docs/SLOC-comparison.jpg -------------------------------------------------------------------------------- /docs/Using jQuery plugins.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/docs/Using jQuery plugins.md -------------------------------------------------------------------------------- /docs/_index.md: -------------------------------------------------------------------------------- 1 |

2 | NestedReact bridges the gap between React and traditional MVC frameworks, 3 | combining familiar OO state management and two-way data binding with all the benefits of true unidirectional data flow. 4 |

5 | 6 |

7 | It's MVC framework taking React for "V" and NestedTypes for "M", while being unopinionated on "C". 8 |

9 | 10 |
11 |
12 |
13 |
14 | 15 | #### NestedTypes for "M" 16 | 17 | Does to your data the same as React does to the DOM. 18 | 19 | * Observable (reactive) data structures 20 | * Easy state synchronization 21 | * Automatic serialization and REST 22 | * Aggregation, "to-one", and "to-many" relashionships. 23 | * Type annotations and assertions 24 | * 10 times faster than BackboneJS. 25 | 26 |
27 |
28 | 29 | #### React for "V" 30 | 31 | React. Extended to play well with MVC. 32 | 33 | * Simple technique to manage all application state 34 | * Handle complex UI state with NestedTypes 35 | * Unidirectional data flow 36 | * Automatic "pure render" optimization 37 | * Handle forms naturally 38 | * Two-way data binding 39 | * Declarative form validation 40 | * *React mixins* with ES6 classes. 41 | * Cuts your code 2-5x compared to React/Redux. 42 | 43 |
44 |
45 | 46 | #### Choose your "C" 47 | 48 | * [react-router](https://github.com/ReactTraining/react-router) 49 | * No router 50 | * Whatever-you-like router 51 | 52 |
53 |
54 | 55 |
-------------------------------------------------------------------------------- /docs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "NestedReact", 3 | "tagline": "BackboneJS compatibility layer for the React-MVx", 4 | "author": "Vlad Balin", 5 | "image": "", 6 | "ignore": { 7 | }, 8 | "live": { 9 | "clean_urls": true 10 | }, 11 | "html": { 12 | "theme": "daux-navy", 13 | "breadcrumbs": true, 14 | "breadcrumb_separator": "Chevrons", 15 | "toggle_code": true, 16 | "date_modified": true, 17 | "float": true, 18 | "inherit_index": true, 19 | 20 | "repo": "Volicon/NestedReact", 21 | "edit_on_github": "Volicon/NestedReact/blob/develop/docs", 22 | "twitter": ["gaperton"], 23 | "links": { 24 | "Help/Support/Bugs": "https://github.com/Volicon/NestedReact/issues", 25 | "Made by Volicon (a Verizon company)": "http://volicon.com" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /docs/flux-sloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/docs/flux-sloc.png -------------------------------------------------------------------------------- /examples/checklistTree/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets":["react", ["es2015", {"loose": true}] ], 3 | "plugins":[ 4 | "transform-decorators-legacy", 5 | "transform-object-rest-spread", 6 | "add-module-exports", 7 | "transform-class-properties" 8 | ], 9 | 10 | "env": { 11 | "production": { 12 | "plugins": [ 13 | "transform-react-constant-elements", 14 | "transform-react-inline-elements" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /examples/checklistTree/README.md: -------------------------------------------------------------------------------- 1 | # Hiearchical Checklist. 2 | 3 | Classic hierarchical checklist example, which you could often see in installers. 4 | This is checklist with a rule. Item is checked, if all children are checked. 5 | 6 | ![Screenshot](./screenshot.png) 7 | 8 | 59 SLOC for jsx components, 28 for the model definition. 9 | 10 | This example demonstrates unidirectional data flow with mutable state and pure render optimisation. And different features of the NestedReact state management. 11 | 12 | - Declarative definition of mutable observable state. `static state = { element : ConstructorFunction }`. 13 | - Two-way data binding with advanced value links. `const links = model.linkAll( 'checked', 'name' )`. 14 | - Simplified React props spec. `static props = { name : ConstructorFunction, name2 : Constructor.value( defaultValue ) }` 15 | - Declarative `pureRender` for mutable data and value links. `static pureRender = true`. 16 | - Recursive state definitions. Model can mention itself in its attribute specs, thus, it's possible to define 17 | (potentially) infinite recursive data structures. 18 | - Change watchers. It's possible to attach 'watchers' to any attribute, which would be called on value change. 19 | - Transactional updates. Any change made to the model from the watcher is being added to the current transaction, 20 | resulting in the single 'change' event per transaction. Look at the render count in the example - you've got one 21 | render no matter how many checkboxes was flipped. 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/checklistTree/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NestedReact • TodoMVC 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/checklistTree/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "todomvc-app-css": "^2.0.0", 5 | "todomvc-common": "^1.0.0" 6 | }, 7 | "devDependencies": { 8 | "babel-cli": "*", 9 | "babel-core": "*", 10 | "babel-loader": "*", 11 | "babel-plugin-add-module-exports": "*", 12 | "babel-plugin-transform-class-properties": "*", 13 | "babel-plugin-transform-decorators-legacy": "*", 14 | "babel-plugin-transform-object-rest-spread": "*", 15 | "babel-plugin-transform-react-constant-elements": "*", 16 | "babel-plugin-transform-react-inline-elements": "*", 17 | "babel-preset-es2015": "*", 18 | "babel-preset-react": "*", 19 | "babel-runtime": "*", 20 | "classnames": "*", 21 | "css-loader": "*", 22 | "jquery": "^2.1.4", 23 | "nestedtypes": "^2.0.0-a00", 24 | "nestedreact": "^1.0.1-rc0", 25 | "react": "^16.0.0", 26 | "react-dom": "^16.0.0", 27 | "react-modal": "*", 28 | "style-loader": "*", 29 | "tslib": "^1.8.0", 30 | "underscore": "^1.8.3", 31 | "valuelink": "^1.1.1", 32 | "webpack": "*" 33 | }, 34 | "scripts": { 35 | "test": "echo \"Error: no test specified\" && exit 1", 36 | "build": "node_modules/.bin/webpack", 37 | "watch": "node_modules/.bin/webpack --progress --colors --watch" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/checklistTree/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/examples/checklistTree/screenshot.png -------------------------------------------------------------------------------- /examples/checklistTree/src/index.jsx: -------------------------------------------------------------------------------- 1 | import './styles.css' 2 | 3 | // You should import React from nestedreact, and use it as drop-in replacement. 4 | // It's 100% compatible. 5 | import React, { define } from 'nestedreact' 6 | import ReactDOM from 'react-dom' 7 | 8 | // Import input controls, modified to support valueLink. Otherwise they behave as standard. 9 | import { Checkbox, Input } from 'valuelink/tags' 10 | 11 | // Import checklist model definition. Think of "model" as of an observable serializable class. 12 | import { ChecklistItem } from './model' 13 | 14 | // Local counter to help us count top-level renders. 15 | let _renders = 0; 16 | 17 | @define // <- That should be places before every class definition, which uses NestedReact features. 18 | class App extends React.Component { 19 | // NestedReact state definition. Syntax is the same as NestedTypes model attributes spec. 20 | // In fact, this state _is_ the NestedTypes model internally. 21 | static state = { 22 | // 'items' is a collection of ChecklistItem model. 23 | items : ChecklistItem.Collection // <- It's type annotation. Constructor function designates type. 24 | }; 25 | 26 | // Save and restore state. 27 | componentWillMount(){ 28 | // All state in NestedReact is serializable by default. 29 | const { state } = this, 30 | // load raw JSON from local storage 31 | json = JSON.parse( localStorage.getItem( 'checklist' ) || "{}" ); 32 | 33 | // Initialize state with JSON. 34 | state.set( json, { parse : true } ); 35 | 36 | window.onunload = () =>{ 37 | // Save state back to the local storage 38 | localStorage.setItem( 'checklist', JSON.stringify( state ) ); 39 | } 40 | } 41 | 42 | render(){ 43 | const { items } = this.state; 44 | 45 | return ( 46 |
47 |
Renders count: { _renders++ } 48 | 51 |
52 | 53 |
54 | ); 55 | } 56 | } 57 | 58 | // Simple pure component to render the list of checklist items. 59 | // They must _not_ be prefixed with @define. No magic here, just raw React. 60 | const List = ({ items }) => ( 61 |
62 | { items.map( item => ( /* <- collections have 'map' method as an array */ 63 | /* models have cid - unique client id to be used in 'key' */ 64 | 65 | ))} 66 |
67 | ); 68 | 69 | @define // <- Don't forget @define 70 | class Item extends React.Component{ 71 | // NestedReact props definition. Same syntax as for the 'state'. 72 | static props = { 73 | model : ChecklistItem // <- Type annotation, using constructor function. No default value. 74 | }; 75 | 76 | static pureRender = true; // <- that's all you should do to enable pure render optimization. 77 | 78 | render(){ 79 | const { model } = this.props, 80 | // Two way data binding! Using our advanced value links. 81 | // First, prepare the links. 82 | links = model.linkAll( 'checked', 'name' ); 83 | 84 | return ( 85 |
86 |
87 | 88 | {model.created.toLocaleTimeString()} 89 | 90 | 93 | 96 |
97 | 98 |
99 | ); 100 | } 101 | } 102 | 103 | // That's really it! Let's render it. 104 | ReactDOM.render( , document.getElementById( 'app-mount-root' ) ); -------------------------------------------------------------------------------- /examples/checklistTree/src/model.js: -------------------------------------------------------------------------------- 1 | // Data objects are defined in nestedtypes package. 2 | import { Model, define } from 'nestedtypes' 3 | 4 | /** 5 | * Data layer is described as models and collections, which can contains each other. 6 | * Model is represented as an object in JSON, Collection - as an array of objects. 7 | * 8 | * We're going to define the structure for checklist as a tree formed of Checklist 9 | * collection and ChecklistItem model. 10 | * 11 | * Every definition must be preceeded with @define decorator. 12 | */ 13 | @define 14 | class Checklist extends Model.Collection { 15 | // This is the boolean calculated property we're using for convenience. 16 | // True, whenever all subitems are selected. 17 | get checked(){ 18 | return this.every( item => item.checked ); 19 | } 20 | 21 | // And it's writable property. 22 | set checked( checked ){ 23 | if( checked !== this.checked ){ 24 | // 'updateEach' works as each, but wrap the changes in transaction. 25 | // Simply speaking, it means that just one change event will be fired from subitems, 26 | // despite the fact that there is a bulk change. 27 | this.updateEach( item => { item.checked = checked } ); 28 | } 29 | } 30 | 31 | } 32 | 33 | /** 34 | * Think of the models as of the class like in statically typed languages. 35 | * Such as Java or C++. Difference here is that types are being checked and coerced _dynamically_. 36 | * And - every model is serializable and observable by default. 37 | */ 38 | @define 39 | export class ChecklistItem extends Model { 40 | // Link Checklist collection with its model definition, overriding the default collection. 41 | // Once it's linked, it becomes the collection of the ChecklistItem models. 42 | // It's useful in our case as we can reference Checklist in attributes spec, creating recursive definition. 43 | static Collection = Checklist; 44 | 45 | static attributes = { // <- Here's an attribute spec. Think of it as a type spec. 46 | name : String, 47 | 48 | // Basic type spec form is just mentioning the constructor function. 49 | // New Date class instance will be automatically created for this attribute. 50 | created : Date, 51 | 52 | // checked - it's boolean value, which has watcher. Watcher is model's function which 53 | // is called whenever attribute value is changed. 54 | // Watchers reacts on every change inside of the attribute structure, no matter how deep it happened. 55 | // All changes made to the model inside of the watcher won't trigger any 56 | // additional 'change' events and won't cause extra renders. They are executed in the scope 57 | // of transaction. 58 | checked : Boolean.has.watcher( 'checkedWatcher' ), 59 | 60 | // Now it's interesting. subitems - is a collection of checklist items. 61 | // And, it has watcher. Which will be called whenever anything inside will be changed. 62 | subitems : Checklist.has.watcher( 'subitemsWatcher' ) 63 | }; 64 | 65 | // This is going to be the watcher function for 'checked' attribute. 66 | checkedWatcher( checked ){ 67 | this.subitems.checked = checked; 68 | } 69 | 70 | // And this one - for 'subitems', which is nested checklist attribute. 71 | subitemsWatcher( subitems ){ 72 | if( subitems.length ){ 73 | this.checked = this.subitems.checked; 74 | } 75 | } 76 | 77 | // Helper method to delete model from collection, without reference to the collection. 78 | // In NestedTypes, aggregation is distinguished from references to the shared objects. 79 | // By default, all attributes and collection elements are aggregated. 80 | // And model.collection points to the owner collection (if any). 81 | remove(){ this.collection.remove( this ); } 82 | } -------------------------------------------------------------------------------- /examples/checklistTree/src/styles.css: -------------------------------------------------------------------------------- 1 | .checkbox { 2 | display: inline-block; 3 | width: 10px; 4 | height: 10px; 5 | border: solid; 6 | border-width: 1px; 7 | margin: 3px; 8 | vertical-align: middle; 9 | } 10 | 11 | .children { 12 | margin-left: 1em; 13 | } 14 | 15 | .checkbox.selected { 16 | background-color: black; 17 | } 18 | 19 | input { 20 | border: none; 21 | border-bottom: solid; 22 | border-width: 1px; 23 | margin: 3px; 24 | margin-left : 10px; 25 | } 26 | 27 | input:focus { 28 | outline: none; 29 | border-width: 2px; 30 | } 31 | 32 | button { 33 | border-radius: 3px; 34 | background-color: white; 35 | } 36 | 37 | .created { 38 | margin : 3px; 39 | font-size: 11px; 40 | } -------------------------------------------------------------------------------- /examples/checklistTree/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require( 'webpack' ); 2 | 3 | module.exports = { 4 | entry : './src/index.jsx', 5 | output : { 6 | // export itself to a global var 7 | path : __dirname + '/dist', 8 | publicPath : '/dist/', 9 | filename : 'app.js' 10 | }, 11 | 12 | devtool : 'source-map', 13 | 14 | resolve : { 15 | modules : [ 'node_modules', 'src' ] 16 | }, 17 | 18 | plugins : [ 19 | new webpack.ProvidePlugin( { 20 | $ : "jquery", 21 | jQuery : 'expose?jQuery!jquery', 22 | _ : "underscore" 23 | } ) 24 | ], 25 | 26 | module : { 27 | rules : [ 28 | { 29 | test : /\.css$/, 30 | use : [ 31 | { 32 | loader: "style-loader" 33 | }, 34 | { 35 | loader: "css-loader" 36 | } 37 | ] 38 | }, 39 | { 40 | test : /\.jsx?$/, 41 | exclude : /(node_modules|lib)/, 42 | loader : 'babel-loader' 43 | }, 44 | { 45 | test: /\.js$/, 46 | //use: ["source-map-loader"], 47 | enforce: "pre" 48 | } 49 | ] 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /examples/flux-comparison/README.md: -------------------------------------------------------------------------------- 1 | # Flux Comparison by Example 2 | 3 | There's great comparison of popular React flux implementation, made by Dmitri Voronianski. 4 | 5 | > Similar app implemented with different [Flux](https://facebook.github.io/flux/) solutions including Facebook's, Yahoo's and others. 6 | 7 | ## Demo 8 | 9 | Select several products from _Flux Online Shop_ and add them to cart. Open browser console and click _Checkout_ button, you'll see payload with products that you just "bought". 10 | 11 | [![](https://dl.dropboxusercontent.com/u/100463011/flux-shop-demo2.gif)](http://labs.voronianski.com/flux-comparison) 12 | 13 | http://labs.voronianski.com/flux-comparison 14 | 15 | **You may find few notes about all implementations from author's ; [blog](http://pixelhunter.me/post/110248593059/flux-solutions-compared-by-example).** 16 | 17 | Discussion on HackerNews - https://news.ycombinator.com/item?id=8989495. 18 | 19 | ## NestedReact implementation notes 20 | 21 | It's really hard to dig through them all, especially considering the fact 22 | that if you trained to read flux code for one particular implementation, 23 | it doesn't mean that you will understand another one. 24 | 25 | So, let's take solutions for some popular flux libraries, and look at them 26 | through the diagram with solution size first. And see how they compares to 27 | each other and typical NestedReact coding style. 28 | 29 | ![](sloc-comparison.png) 30 | 31 | If you will measure data layer size for all of them (it's blue bars in our chart), you'll find out that 32 | there are two groups of flux solutions. One of them with data layer size closer 33 | to (but not less than) 100 SLOC, with `alt` being one of the shortest. 34 | And another one, which is closer to 200, with `redux` being one of the longest. 35 | 36 | Meanwhile, if you look at NestedTypes code, you will 37 | understand how ridiculously trivial this data layer is. 38 | Enough words, let's see what we're talking about. 39 | 40 | So, they have products. Which is, essentially, this thing: 41 | 42 | ![](product.jpg) 43 | 44 | And cart. Which is just another collection of products. 45 | 46 | ![](cart.jpg) 47 | 48 | But that's just type definitions. How do we actually work with these data? We add them to the top-level application state, like this: 49 | 50 | ![](application.jpg) 51 | 52 | And that's it. All you have to do next, is to relax and enjoy pure unidirectional data flow. 53 | 54 | Few words to explain how this code actually works. First, App's `state` is the model, 55 | and our models and collection can contain each other forming deeply nested structures. Second - they are able to detect deeply nested changes. Somewhat you could achieve with Object.observe, but better, because it works across the whole models ownership tree, and logic is 56 | smart enough to merge many change events into one in case of bulk changes. 57 | 58 | So, whenever anything will be changed inside of products or cart, our `state` will notice it, 59 | and tell App component to update. 60 | 61 | Because of that, and due to the fact that models and 62 | collections has they own behavior, it's safe and practical just to pass state elements down to the component tree as `props`. Therefore, `` component will depend on just `Cart` collection, while `` component doesn't know anything about `` and `Cart`. 63 | 64 | And that type of isolation is exactly what we want, when our system grow large. How do we deal with something big and messy? We split it to smaller isolated parts we can understand and change independently. 65 | 66 | ![](unidirectional-data-flow.jpg) 67 | 68 | On a longer example, difference will become even bigger. Why? Because, we got quite [advanced facilities to 69 | deal with complex data](https://github.com/Volicon/NestedTypes/blob/master/docs/RelationsGuide.md) up our sleeve, which will come into play. They don't. 70 | -------------------------------------------------------------------------------- /examples/flux-comparison/application.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/examples/flux-comparison/application.jpg -------------------------------------------------------------------------------- /examples/flux-comparison/cart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/examples/flux-comparison/cart.jpg -------------------------------------------------------------------------------- /examples/flux-comparison/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NestedReact Anti-Flux Sample 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/flux-comparison/js/app.js: -------------------------------------------------------------------------------- 1 | import React from 'nestedreact' 2 | import App from './components/App.jsx' 3 | 4 | React.render( 5 | React.createElement( App, null ), 6 | document.getElementById( 'non-flux-app' ) 7 | ); 8 | -------------------------------------------------------------------------------- /examples/flux-comparison/js/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'nestedreact' 2 | 3 | import ProductsContainer from './ProductsContainer.jsx' 4 | import CartContainer from './CartContainer.jsx' 5 | 6 | import Cart from '../models/Cart' 7 | import Product from '../models/Product' 8 | 9 | const App = React.createClass( { 10 | // Declare application's state 11 | // UI will be updated automatically on every state, 12 | // deep changes will be detected too. 13 | state : { 14 | cart : Cart, 15 | products : Product.Collection 16 | }, 17 | 18 | componentWillMount(){ 19 | // fetch data on application start... 20 | this.state.products.fetch(); 21 | }, 22 | 23 | render(){ 24 | const { state } = this; 25 | return ( 26 |
27 | state.cart.add( product ) }/> 29 | 30 | 31 |
32 | ); 33 | } 34 | } ); 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /examples/flux-comparison/js/components/CartContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'nestedreact' 2 | import CCart from '../../../common/components/Cart.jsx' 3 | 4 | const CartContainer = ( { cart } ) => ( 5 | cart.checkout() }/> // But likely for us, cart is not an array. 8 | ); 9 | 10 | export default CartContainer; 11 | -------------------------------------------------------------------------------- /examples/flux-comparison/js/components/ProductsContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'nestedreact' 2 | 3 | import CProductItem from '../../../common/components/ProductItem.jsx' 4 | import CProductsList from '../../../common/components/ProductsList.jsx' 5 | 6 | const ProductsContainer = ( { products, addToCart } ) =>( 7 | 8 | { products.map( product => ( 9 | 12 | ) )} 13 | 14 | ); 15 | 16 | export default ProductsContainer; 17 | -------------------------------------------------------------------------------- /examples/flux-comparison/js/models/Cart.js: -------------------------------------------------------------------------------- 1 | import { Collection } from 'nestedtypes' 2 | import Product from './Product' 3 | import shop from '../../../common/api/shop' 4 | 5 | const Cart = Collection.extend( { 6 | // Cart is a collection of products... 7 | model : Product, 8 | 9 | // ... which you can checkout. 10 | checkout(){ 11 | shop.buyProducts( this.toJSON(), () => this.reset() ); 12 | } 13 | } ); 14 | 15 | export default Cart; 16 | -------------------------------------------------------------------------------- /examples/flux-comparison/js/models/Product.js: -------------------------------------------------------------------------------- 1 | import { Model } from 'nestedtypes' 2 | import shop from '../../../common/api/shop' 3 | 4 | const Product = Model.extend( { 5 | attributes : { 6 | image : String, 7 | title : String, 8 | price : Number, 9 | inventory : Number 10 | }, 11 | 12 | collection : { 13 | fetch(){ 14 | shop.getProducts( products => this.reset( products, { parse : true } ) ); 15 | } 16 | } 17 | } ); 18 | 19 | export default Product; 20 | -------------------------------------------------------------------------------- /examples/flux-comparison/product.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/examples/flux-comparison/product.jpg -------------------------------------------------------------------------------- /examples/flux-comparison/sloc-comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/examples/flux-comparison/sloc-comparison.png -------------------------------------------------------------------------------- /examples/flux-comparison/unidirectional-data-flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/examples/flux-comparison/unidirectional-data-flow.jpg -------------------------------------------------------------------------------- /examples/todomvc/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets":["react", ["es2015", {"loose": true}] ], 3 | "plugins":[ 4 | "transform-decorators-legacy", 5 | "transform-object-rest-spread", 6 | "add-module-exports", 7 | "transform-class-properties" 8 | ], 9 | 10 | "env": { 11 | "production": { 12 | "plugins": [ 13 | "transform-react-constant-elements", 14 | "transform-react-inline-elements" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /examples/todomvc/SLOC-comparison.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/examples/todomvc/SLOC-comparison.jpg -------------------------------------------------------------------------------- /examples/todomvc/css/app.css: -------------------------------------------------------------------------------- 1 | .something { 2 | background: black; 3 | } 4 | -------------------------------------------------------------------------------- /examples/todomvc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NestedReact • TodoMVC 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/todomvc/js/addtodo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'nestedreact' 2 | import { define } from 'nestedtypes' 3 | import { Input } from 'valuelink/tags' 4 | 5 | @define 6 | class AddTodo extends React.Component { 7 | static props = { 8 | onEnter : Function.value( function(){} ) 9 | } 10 | 11 | static state = { 12 | desc : String 13 | } 14 | 15 | render(){ 16 | return ( 17 |
18 |

todos

19 | 20 | this.onKeyDown( e ) } 23 | /> 24 |
25 | ); 26 | } 27 | 28 | onKeyDown( { keyCode } ){ 29 | if( keyCode === 13 ){ 30 | let { state, props } = this; 31 | 32 | state.desc && props.onEnter( state.desc ); 33 | state.desc = ""; 34 | } 35 | } 36 | } 37 | 38 | export default AddTodo; 39 | -------------------------------------------------------------------------------- /examples/todomvc/js/filter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'nestedreact' 2 | 3 | const Filter = ( { count, filterLink, onClear } ) => ( 4 |
5 | 6 | { count } item left 7 | 8 | 9 |
    10 |
  • 11 | 12 | All 13 | 14 |
  • 15 |
  • 16 | 17 | Active 18 | 19 |
  • 20 |
  • 21 | 22 | Completed 23 | 24 |
  • 25 |
26 | 27 | 30 |
31 | ); 32 | 33 | export default Filter; 34 | 35 | const Radio = ( { checkedLink, children, ...props } ) => ( 36 | checkedLink.set( true ) } 38 | { ...props }> 39 | { children } 40 | 41 | ); 42 | -------------------------------------------------------------------------------- /examples/todomvc/js/main.jsx: -------------------------------------------------------------------------------- 1 | import '../css/app.css' 2 | import React, { define } from 'nestedreact' 3 | import ReactDOM from 'react-dom' 4 | import {ToDo} from './model.js' 5 | import TodoList from './todolist.jsx' 6 | import Filter from './filter.jsx' 7 | import AddTodo from './addtodo.jsx' 8 | 9 | @define 10 | class App extends React.Component { 11 | // Declare component state 12 | static state = { 13 | todos : ToDo.Collection, 14 | filterDone : Boolean.value( null ) // null | true | false, initialized with null. 15 | } 16 | 17 | componentWillMount(){ 18 | const { state } = this, 19 | // load raw JSON from local storage 20 | json = JSON.parse( localStorage.getItem( 'todo-mvc' ) || "{}" ); 21 | 22 | // initialize state with raw JSON 23 | state.set( json, { parse : true } ); 24 | 25 | window.onunload = () =>{ 26 | // Save state back to the local storage 27 | localStorage.setItem( 'todo-mvc', JSON.stringify( state ) ); 28 | } 29 | } 30 | 31 | render(){ 32 | const { todos, filterDone } = this.state, 33 | hasTodos = Boolean( todos.length ); 34 | 35 | return ( 36 |
37 |
38 | todos.add({ desc : desc }) }/> 39 | 40 | { hasTodos && } 42 | 43 | { hasTodos && todos.clearCompleted() } 46 | />} 47 |
48 | 49 | 55 |
56 | ); 57 | } 58 | } 59 | 60 | ReactDOM.render( , document.getElementById( 'app-mount-root' ) ); 61 | 62 | -------------------------------------------------------------------------------- /examples/todomvc/js/model.js: -------------------------------------------------------------------------------- 1 | import { Model, Collection, define } from 'nestedtypes' 2 | 3 | @define 4 | class ToDoCollection extends Collection { 5 | clearCompleted(){ 6 | this.remove( this.filter( todo => todo.done ) ); 7 | } 8 | 9 | get allDone(){ 10 | return this.every( todo => todo.done ); 11 | } 12 | 13 | set allDone( val ){ 14 | this.transaction( () =>{ 15 | this.each( todo => todo.done = val ); 16 | }); 17 | } 18 | 19 | get activeCount(){ 20 | return this.filter( todo => !todo.done ).length; 21 | } 22 | } 23 | 24 | @define 25 | export class ToDo extends Model { 26 | static Collection = ToDoCollection 27 | static attributes = { 28 | done : Boolean, 29 | desc : String 30 | } 31 | 32 | remove(){ 33 | this.collection.remove( this ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/todomvc/js/todolist.jsx: -------------------------------------------------------------------------------- 1 | import React, { define } from 'nestedreact' 2 | import cx from 'classnames' 3 | import { Input } from 'valuelink/tags' 4 | 5 | import { ToDo } from './model' 6 | 7 | @define 8 | class TodoList extends React.Component { 9 | static props = { 10 | todos : ToDo.Collection, 11 | filterDone : Boolean 12 | } 13 | 14 | static state = { 15 | editing : ToDo.from( '^props.todos' ) 16 | } 17 | 18 | static pureRender = true 19 | 20 | render(){ 21 | const { todos, filterDone } = this.props, 22 | filtered = filterDone === null ? todos.models 23 | : todos.filter( todo => todo.done === filterDone ), 24 | 25 | editingLink = this.state.getLink( 'editing' ); 26 | 27 | return ( 28 |
29 | 31 | 32 | 33 | 34 |
    35 | { filtered.map( todo => ( 36 | 38 | ) )} 39 |
40 |
41 | ); 42 | } 43 | } 44 | 45 | export default TodoList; 46 | 47 | function clearOnEnter( x, e ){ 48 | if( e.keyCode === 13 ) return null; 49 | } 50 | 51 | const TodoItem = ( { todo, editingLink } ) =>{ 52 | const editing = editingLink.value === todo, 53 | className = cx( { 54 | 'completed' : todo.done, 55 | 'view' : !todo.done, 56 | 'editing' : editing 57 | } ); 58 | 59 | return ( 60 |
  • 61 |
    62 | 64 | 65 | 68 | 69 |
    71 | 72 | { editing && null ) } 76 | onKeyDown={ editingLink.action( clearOnEnter ) }/> } 77 |
  • 78 | ); 79 | }; 80 | -------------------------------------------------------------------------------- /examples/todomvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "todomvc-app-css": "^2.0.0", 5 | "todomvc-common": "^1.0.0" 6 | }, 7 | "devDependencies": { 8 | "babel-cli": "*", 9 | "babel-core": "*", 10 | "babel-loader": "*", 11 | "babel-plugin-add-module-exports": "*", 12 | "babel-plugin-transform-class-properties": "*", 13 | "babel-plugin-transform-decorators-legacy": "*", 14 | "babel-plugin-transform-object-rest-spread": "*", 15 | "babel-plugin-transform-react-constant-elements": "*", 16 | "babel-plugin-transform-react-inline-elements": "*", 17 | "babel-preset-es2015": "*", 18 | "babel-preset-react": "*", 19 | "babel-runtime": "*", 20 | "classnames": "*", 21 | "css-loader": "*", 22 | "jquery": "^2.1.4", 23 | "nestedreact": "^1.0.1-rc0", 24 | "nestedtypes": "^2.0.0-a00", 25 | "react": "^16.0.0", 26 | "react-dom": "^16.0.0", 27 | "source-map-loader": "*", 28 | "style-loader": "*", 29 | "tslib": "^1.8.0", 30 | "underscore": "^1.8.3", 31 | "valuelink": "^1.1.1", 32 | "webpack": "*" 33 | }, 34 | "scripts": { 35 | "test": "echo \"Error: no test specified\" && exit 1", 36 | "build": "node_modules/.bin/webpack", 37 | "watch": "node_modules/.bin/webpack --progress --colors --watch" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/todomvc/pure-components.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoliJS/NestedReact/94ddfd6e2b09d1fefd8847160bfff5150a65d54c/examples/todomvc/pure-components.jpg -------------------------------------------------------------------------------- /examples/todomvc/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require( 'webpack' ); 2 | 3 | module.exports = { 4 | entry : './js/main.jsx', 5 | output : { 6 | path : __dirname + '/dist', 7 | publicPath : '/dist/', 8 | filename : 'app.js' 9 | }, 10 | 11 | devtool : 'source-map', 12 | 13 | resolve : { 14 | modules : [ 'node_modules', 'js' ] 15 | }, 16 | 17 | plugins : [ 18 | new webpack.ProvidePlugin( { 19 | $ : "jquery", 20 | jQuery : 'expose?jQuery!jquery', 21 | _ : "underscore" 22 | } ) 23 | ], 24 | 25 | module : { 26 | rules : [ 27 | { 28 | test : /\.css$/, 29 | use : [ 30 | { 31 | loader: "style-loader" 32 | }, 33 | { 34 | loader: "css-loader" 35 | } 36 | ] 37 | }, 38 | { 39 | test : /\.jsx?$/, 40 | exclude : /(node_modules|lib)/, 41 | loader : 'babel-loader' 42 | }, 43 | { 44 | test: /\.js$/, 45 | use: ["source-map-loader"], 46 | enforce: "pre" 47 | } 48 | ] 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /examples/userslist/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets":["react", ["es2015", {"loose": true}] ], 3 | "plugins":[ 4 | "transform-decorators-legacy", 5 | "transform-object-rest-spread", 6 | "add-module-exports", 7 | "transform-class-properties" 8 | ], 9 | 10 | "env": { 11 | "production": { 12 | "plugins": [ 13 | "transform-react-constant-elements", 14 | "transform-react-inline-elements" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /examples/userslist/README.md: -------------------------------------------------------------------------------- 1 | # UsersList Example 2 | 3 | Here's the UsersList application example, written originally for NestedLink. 4 | 5 | It's changed to use NestedReact. Feel the difference. 6 | 7 | NestedLink data binding is so powerful that there are no any difference 8 | in size, so expressiveness is roughly the same. 9 | 10 | But. Check out how much less magical NestedReact solution feels - compare the code. 11 | 12 | In fact, there's an incredible amount of dirty magic involved, 13 | just to make you feel that there are no any magic at all :). 14 | 15 | That's NestedReact. Relax, and write in natural way, no tricks are required. 16 | -------------------------------------------------------------------------------- /examples/userslist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NestedReact • TodoMVC 7 | 8 | 9 | 10 |
    11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/userslist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "todomvc-app-css": "^2.0.0", 5 | "todomvc-common": "^1.0.0" 6 | }, 7 | "devDependencies": { 8 | "babel-cli": "*", 9 | "babel-core": "*", 10 | "babel-loader": "*", 11 | "babel-plugin-add-module-exports": "*", 12 | "babel-plugin-transform-class-properties": "*", 13 | "babel-plugin-transform-decorators-legacy": "*", 14 | "babel-plugin-transform-object-rest-spread": "*", 15 | "babel-plugin-transform-react-constant-elements": "*", 16 | "babel-plugin-transform-react-inline-elements": "*", 17 | "babel-preset-es2015": "*", 18 | "babel-preset-react": "*", 19 | "classnames": "*", 20 | "css-loader": "*", 21 | "jquery": "^2.1.4", 22 | "nestedreact": "^1.0.1-a00", 23 | "nestedtypes": "^2.0.0-a00", 24 | "react": "^16.0.0", 25 | "react-dom": "^16.0.0", 26 | "react-modal": "*", 27 | "source-map-loader": "^0.2.3", 28 | "style-loader": "*", 29 | "tslib": "^1.8.0", 30 | "underscore": "^1.8.3", 31 | "valuelink": "^1.1.1", 32 | "webpack": "*" 33 | }, 34 | "scripts": { 35 | "test": "echo \"Error: no test specified\" && exit 1", 36 | "build": "node_modules/.bin/webpack", 37 | "watch": "node_modules/.bin/webpack --progress --colors --watch" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/userslist/src/main.css: -------------------------------------------------------------------------------- 1 | .invalid { 2 | border-color: red; 3 | } 4 | 5 | .invalid.required { 6 | border-color: yellow; 7 | } 8 | 9 | .checkbox,.radio { 10 | margin: 3px; 11 | display: inline-block; 12 | width: 10px; 13 | height : 10px; 14 | border: solid; 15 | border-width: 1px; 16 | } 17 | 18 | .selected { 19 | background-color: black; 20 | } 21 | 22 | label { 23 | display: block; 24 | margin: 5px; 25 | } 26 | 27 | input { 28 | margin: 3px; 29 | } 30 | 31 | 32 | .users-row>div { 33 | display: inline-block; 34 | width : 15em; 35 | } 36 | 37 | .validation-error { 38 | display: inline-block; 39 | color: red; 40 | } 41 | 42 | label>div { 43 | display: inline-block; 44 | } 45 | -------------------------------------------------------------------------------- /examples/userslist/src/main.jsx: -------------------------------------------------------------------------------- 1 | import './main.css' 2 | import ReactDOM from 'react-dom' 3 | 4 | import React from 'nestedreact' 5 | import { Model, define } from 'nestedtypes' 6 | 7 | import Modal from 'react-modal' 8 | import {Input, isRequired, isEmail } from 'valuelink/tags' 9 | 10 | @define 11 | class User extends Model{ 12 | static attributes = { 13 | name : String.has 14 | .check( isRequired ) 15 | .check( x => x.indexOf( ' ' ) < 0, 'Spaces are not allowed' ), 16 | 17 | email : String.has 18 | .check( isRequired ) 19 | .check( isEmail ), 20 | 21 | isActive : true 22 | } 23 | 24 | remove(){ this.collection.remove( this ); } 25 | } 26 | 27 | @define 28 | export class UsersList extends React.Component { 29 | static state = { 30 | users : User.Collection, // No comments required, isn't it? 31 | editing : User.from( 'users' ), // User from user collection, which is being edited. 32 | adding : User.shared.value( null ) // New user, which is being added. 33 | } 34 | 35 | render(){ 36 | const { state } = this; 37 | 38 | return ( 39 |
    40 | 43 | 44 |
    45 | 46 | { state.users.map( user => ( 47 | state.editing = user } 50 | /> 51 | ) )} 52 | 53 | 54 | 56 | 57 | 58 | 59 | state.editing = null }/> 61 | 62 |
    63 | ); 64 | } 65 | 66 | addUser = ( user ) => { 67 | const { state } = this; 68 | 69 | if( user ){ 70 | state.users.add( user ); 71 | } 72 | 73 | state.adding = null; 74 | } 75 | } 76 | 77 | const Header = () =>( 78 |
    79 |
    Name
    80 |
    Email
    81 |
    Is Active
    82 |
    83 |
    84 | ); 85 | 86 | const UserRow = ( { user, onEdit } ) =>( 87 |
    88 |
    { user.name }
    89 |
    { user.email }
    90 |
    user.isActive = !user.isActive }> 91 | { user.isActive ? 'Yes' : 'No' }
    92 |
    93 | 94 | 95 |
    96 |
    97 | ); 98 | 99 | @define({ 100 | props : { 101 | user : User, 102 | onClose : Function 103 | }, 104 | 105 | state : { 106 | user : User 107 | } 108 | }) 109 | class EditUser extends React.Component { 110 | 111 | componentWillMount(){ 112 | this.state.user = this.props.user.clone(); 113 | } 114 | 115 | onSubmit = ( e ) => { 116 | e.preventDefault(); 117 | 118 | const { user, onClose } = this.props; 119 | 120 | user.set( this.state.user.attributes ); 121 | onClose( user ); 122 | } 123 | 124 | onCancel = () => { 125 | this.props.onClose(); 126 | } 127 | 128 | render(){ 129 | const linked = this.state.user.linkAll( 'name', 'email', 'isActive' ); 130 | 131 | return ( 132 |
    133 | 136 | 137 | 140 | 141 | 144 | 145 | 148 | 151 |
    152 | ); 153 | } 154 | } 155 | 156 | const ValidatedInput = ( props ) => ( 157 |
    158 | 159 |
    160 | { props.valueLink.error || '' } 161 |
    162 |
    163 | ); 164 | 165 | ReactDOM.render( , document.getElementById( 'app-mount-root' ) ); 166 | -------------------------------------------------------------------------------- /examples/userslist/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require( 'webpack' ); 2 | 3 | module.exports = { 4 | entry : './src/main.jsx', 5 | output : { 6 | path : __dirname + '/dist', 7 | publicPath : '/dist/', 8 | filename : 'app.js' 9 | }, 10 | 11 | devtool : 'source-map', 12 | 13 | resolve : { 14 | modules : [ 'node_modules', 'js' ] 15 | }, 16 | 17 | plugins : [ 18 | new webpack.ProvidePlugin( { 19 | $ : "jquery", 20 | jQuery : 'expose?jQuery!jquery', 21 | _ : "underscore" 22 | } ) 23 | ], 24 | 25 | module : { 26 | rules : [ 27 | { 28 | test : /\.css$/, 29 | use : [ 30 | { 31 | loader: "style-loader" 32 | }, 33 | { 34 | loader: "css-loader" 35 | } 36 | ] 37 | }, 38 | { 39 | test : /\.jsx?$/, 40 | exclude : /(node_modules|lib)/, 41 | loader : 'babel-loader' 42 | }, 43 | { 44 | test: /\.js$/, 45 | use: ["source-map-loader"], 46 | enforce: "pre" 47 | } 48 | ] 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /lib/component-view.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | Page: { 4 | forceResize(); 5 | }; 6 | } 7 | } 8 | export default function use(View: any): any; 9 | export {}; 10 | -------------------------------------------------------------------------------- /lib/component-view.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { tools } from 'type-r'; 4 | window.Page || (window.Page = { forceResize: function () { } }); 5 | export default function use(View) { 6 | var dispose = View.prototype.dispose || function () { }, setElement = View.prototype.setElement; 7 | var ComponentView = View.extend({ 8 | reactClass: null, 9 | props: {}, 10 | element: null, 11 | initialize: function (props) { 12 | // memorise arguments to pass to React 13 | this.options = props || {}; 14 | }, 15 | setElement: function () { 16 | this.unmountComponent(true); 17 | return setElement.apply(this, arguments); 18 | }, 19 | // cached instance of react component... 20 | component: null, 21 | prevState: null, 22 | resize: function () { 23 | window.Page.forceResize(); 24 | }, 25 | render: function () { 26 | var options = this.prevState ? tools.fastAssign({ __keepState: this.prevState }, this.options) : this.options, element = React.createElement(this.reactClass, options), component = ReactDOM.render(element, this.el); 27 | this.component || this.mountComponent(component); 28 | }, 29 | mountComponent: function (component) { 30 | this.component = component; 31 | this.prevState = null; 32 | component.trigger && this.listenTo(component, 'all', function () { 33 | this.trigger.apply(this, arguments); 34 | }); 35 | }, 36 | unmountComponent: function (keepModel) { 37 | var component = this.component; 38 | if (component) { 39 | this.prevState = component.state; 40 | if (component.trigger) { 41 | this.stopListening(component); 42 | } 43 | component._preventDispose = Boolean(keepModel); 44 | ReactDOM.unmountComponentAtNode(this.el); 45 | this.component = null; 46 | } 47 | }, 48 | dispose: function () { 49 | this.unmountComponent(); 50 | return dispose.apply(this, arguments); 51 | } 52 | }); 53 | Object.defineProperty(ComponentView.prototype, 'model', { 54 | get: function () { 55 | this.component || this.render(); 56 | return this.component && this.component.state; 57 | } 58 | }); 59 | return ComponentView; 60 | } 61 | //# sourceMappingURL=component-view.js.map -------------------------------------------------------------------------------- /lib/component-view.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"component-view.js","sourceRoot":"","sources":["../src/component-view.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/createClass.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /** 3 | * ES5 components definition factory 4 | */ 5 | export default function createClass({statics, ...a_spec}: React.ComponentSpec): React.ClassicComponentClass

    ; 6 | -------------------------------------------------------------------------------- /lib/createClass.js: -------------------------------------------------------------------------------- 1 | import * as tslib_1 from "tslib"; 2 | import { Component } from './react-mvx'; 3 | var dontAutobind = [ 4 | 'State', 'Store', 'constructor', 5 | 'componentWillMount', 'componentDidMount', 'componentWillReceiveProps', 'shouldComponentUpdate', 6 | 'componentWillUpdate', 'componentDidUpdate', 'componentWillUnmount', 7 | 'render', 'getDefaultProps', 'getChildContext' 8 | ]; 9 | /** 10 | * ES5 components definition factory 11 | */ 12 | export default function createClass(_a) { 13 | var statics = _a.statics, a_spec = tslib_1.__rest(_a, ["statics"]); 14 | // Gather all methods to pin them to `this` later. 15 | var methods = []; 16 | var Subclass = Component.extend(tslib_1.__assign({ 17 | // Override constructor to autobind all the methods... 18 | constructor: function () { 19 | Component.apply(this, arguments); 20 | for (var _i = 0, methods_1 = methods; _i < methods_1.length; _i++) { 21 | var method = methods_1[_i]; 22 | this[method] = this[method].bind(this); 23 | } 24 | } }, a_spec), statics); 25 | // Need to bind methods from mixins as well, so populate it here. 26 | var Proto = Subclass.prototype; 27 | for (var key in Proto) { 28 | if (Proto.hasOwnProperty(key) && dontAutobind.indexOf(key) === -1 && typeof Proto[key] === 'function') { 29 | methods.push(key); 30 | } 31 | } 32 | return Subclass; 33 | } 34 | //# sourceMappingURL=createClass.js.map -------------------------------------------------------------------------------- /lib/createClass.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"createClass.js","sourceRoot":"","sources":["../src/createClass.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC,IAAM,YAAY,GAAG;IACjB,OAAO,EAAE,OAAO,EAAE,aAAa;IAC/B,oBAAoB,EAAC,mBAAmB,EAAC,2BAA2B,EAAC,uBAAuB;IAC5F,qBAAqB,EAAC,oBAAoB,EAAC,sBAAsB;IACjE,QAAQ,EAAE,iBAAiB,EAAE,iBAAiB;CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,OAAO,sBAA8B,EAAkD;IAAhD,IAAA,oBAAO,EAAE,wCAAS;IAC5D,kDAAkD;IAClD,IAAM,OAAO,GAAG,EAAE,CAAC;IAEnB,IAAM,QAAQ,GAAS,SAAS,CAAC,MAAM;QACnC,sDAAsD;QACtD,WAAW;YACP,SAAS,CAAC,KAAK,CAAE,IAAI,EAAE,SAAS,CAAE,CAAC;YAEnC,GAAG,CAAA,CAAgB,UAAO,EAAP,mBAAO,EAAP,qBAAO,EAAP,IAAO;gBAArB,IAAI,MAAM,gBAAA;gBACX,IAAI,CAAE,MAAM,CAAE,GAAG,IAAI,CAAE,MAAM,CAAE,CAAC,IAAI,CAAE,IAAI,CAAE,CAAC;aAChD;QACL,CAAC,IACE,MAAM,GACV,OAAO,CAAE,CAAC;IAEb,iEAAiE;IACjE,IAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC;IACjC,GAAG,CAAA,CAAE,IAAI,GAAG,IAAI,KAAM,CAAC,CAAA,CAAC;QACpB,EAAE,CAAA,CAAE,KAAK,CAAC,cAAc,CAAE,GAAG,CAAE,IAAI,YAAY,CAAC,OAAO,CAAE,GAAG,CAAE,KAAK,CAAC,CAAC,IAAI,OAAO,KAAK,CAAE,GAAG,CAAE,KAAK,UAAW,CAAC,CAAA,CAAC;YAC1G,OAAO,CAAC,IAAI,CAAE,GAAG,CAAE,CAAC;QACxB,CAAC;IACL,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC;AACpB,CAAC"} -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const NestedReact: any; 2 | export default NestedReact; 3 | export * from './react-mvx'; 4 | import * as PropTypes from 'prop-types'; 5 | import subview from './view-element'; 6 | export { subview }; 7 | import createClass from './createClass'; 8 | export { PropTypes, createClass }; 9 | export declare function useView(View: any): void; 10 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // Re-export react-mvx 2 | import ReactMVx from './react-mvx'; 3 | var NestedReact = Object.create(ReactMVx); 4 | export default NestedReact; 5 | export * from './react-mvx'; 6 | // NestedReact backward compatibility layer 7 | import * as ReactDOM from 'react-dom'; 8 | import Nested, { View, Record } from 'type-r'; 9 | import * as PropTypes from 'prop-types'; 10 | import subview from './view-element'; 11 | NestedReact.subview = subview; 12 | export { subview }; 13 | import use from './component-view'; 14 | import createClass from './createClass'; 15 | Object.defineProperty(NestedReact, 'createClass', { value: createClass }); 16 | Object.defineProperty(NestedReact, 'PropTypes', { value: PropTypes }); 17 | export { PropTypes, createClass }; 18 | var BaseView; 19 | // export hook to override base View class used... 20 | export function useView(View) { 21 | BaseView = use(View); 22 | } 23 | var onDefine = NestedReact.Component.onDefine; 24 | NestedReact.Component.onDefine = function (definitions, BaseClass) { 25 | this.View = BaseView.extend({ reactClass: this }); 26 | return onDefine.call(this, definitions, BaseClass); 27 | }; 28 | // Deprecated API for backward compatibility 29 | var RecordProto = Record.prototype; 30 | RecordProto.getLink = RecordProto.linkAt; 31 | RecordProto.deepLink = RecordProto.linkPath; 32 | var CollectionProto = Record.Collection.prototype; 33 | CollectionProto.hasLink = CollectionProto.linkContains; 34 | useView(View); 35 | // Extend react components to have backbone-style jquery accessors 36 | var BackboneViewProps = { 37 | el: { get: function () { return ReactDOM.findDOMNode(this); } }, 38 | $el: { get: function () { return Nested.$(this.el); } }, 39 | $: { value: function (sel) { return this.$el.find(sel); } } 40 | }; 41 | Object.defineProperties(NestedReact.Component.prototype, BackboneViewProps); 42 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /lib/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sBAAsB;AACtB,OAAO,QAAQ,MAAM,aAAa,CAAA;AAClC,IAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAE,QAAQ,CAAE,CAAC;AAC9C,eAAe,WAAW,CAAC;AAC3B,cAAc,aAAa,CAAA;AAE3B,2CAA2C;AAC3C,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAA;AACrC,OAAO,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,KAAK,SAAS,MAAM,YAAY,CAAA;AAEvC,OAAO,OAAO,MAAM,gBAAgB,CAAA;AACpC,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,CAAA;AAElB,OAAO,GAAG,MAAM,kBAAkB,CAAA;AAClC,OAAO,WAAW,MAAM,eAAe,CAAA;AAEvC,MAAM,CAAC,cAAc,CAAE,WAAW,EAAE,aAAa,EAAE,EAAE,KAAK,EAAG,WAAW,EAAC,CAAE,CAAC;AAC5E,MAAM,CAAC,cAAc,CAAE,WAAW,EAAE,WAAW,EAAE,EAAE,KAAK,EAAG,SAAS,EAAE,CAAE,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAElC,IAAI,QAAQ,CAAC;AAEb,kDAAkD;AAClD,MAAM,kBAAmB,IAAI;IACzB,QAAQ,GAAG,GAAG,CAAE,IAAI,CAAE,CAAC;AAC3B,CAAC;AAEO,IAAA,yCAAQ,CAA2B;AAE3C,WAAW,CAAC,SAAS,CAAC,QAAQ,GAAG,UAAU,WAAW,EAAE,SAAS;IAC7D,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAE,EAAE,UAAU,EAAG,IAAI,EAAE,CAAE,CAAC;IAErD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAE,IAAI,EAAE,WAAW,EAAE,SAAS,CAAE,CAAC;AACzD,CAAC,CAAA;AAED,4CAA4C;AAC5C,IAAM,WAAW,GAAS,MAAM,CAAC,SAAS,CAAC;AAC3C,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC;AACzC,WAAW,CAAC,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC;AAE5C,IAAM,eAAe,GAAS,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;AAC1D,eAAe,CAAC,OAAO,GAAG,eAAe,CAAC,YAAY,CAAC;AAEvD,OAAO,CAAE,IAAI,CAAE,CAAC;AAEhB,kEAAkE;AAClE,IAAM,iBAAiB,GAAG;IACtB,EAAE,EAAI,EAAE,GAAG,EAAG,cAAY,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAE,IAAI,CAAE,CAAA,CAAC,CAAC,EAAE;IACjE,GAAG,EAAG,EAAE,GAAG,EAAG,cAAY,MAAM,CAAC,MAAM,CAAC,CAAC,CAAE,IAAI,CAAC,EAAE,CAAE,CAAA,CAAC,CAAC,EAAE;IACxD,CAAC,EAAK,EAAE,KAAK,EAAG,UAAU,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAE,GAAG,CAAE,CAAA,CAAC,CAAC,EAAE;CACnE,CAAC;AAEF,MAAM,CAAC,gBAAgB,CAAE,WAAW,CAAC,SAAS,CAAC,SAAS,EAAE,iBAAiB,CAAE,CAAC"} -------------------------------------------------------------------------------- /lib/react-mvx/component.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /** 3 | * React-Type-R component base class. Overrides React component. 4 | */ 5 | import * as React from 'react'; 6 | import { Record, Store, CallbacksByEvents, Messenger } from 'type-r'; 7 | import Link from './link'; 8 | import onDefine, { TypeSpecs } from './define'; 9 | export declare class Component extends React.Component { 10 | cid: string; 11 | static state?: TypeSpecs | typeof Record; 12 | static store?: TypeSpecs | typeof Store; 13 | static props?: TypeSpecs; 14 | static context?: TypeSpecs; 15 | static childContext?: TypeSpecs; 16 | static pureRender?: boolean; 17 | private _disposed; 18 | private static propTypes; 19 | private static defaultProps; 20 | private static contextTypes; 21 | private static childContextTypes; 22 | private PropsChangeTokens; 23 | static extend: (spec: object, statics?: object) => Component; 24 | linkAt(key: string): Link; 25 | linkAll(...keys: string[]): { 26 | [key: string]: Link; 27 | }; 28 | linkPath(path: string): Link; 29 | readonly links: any; 30 | static onDefine: typeof onDefine; 31 | readonly state: S; 32 | readonly store?: Store; 33 | constructor(props?: any, context?: any); 34 | _initializeState(): void; 35 | assignToState(x: any, key: string): void; 36 | isMounted: () => boolean; 37 | on: (events: string | CallbacksByEvents, callback, context?) => this; 38 | once: (events: string | CallbacksByEvents, callback, context?) => this; 39 | off: (events?: string | CallbacksByEvents, callback?, context?) => this; 40 | trigger: (name: string, a?, b?, c?, d?, e?) => this; 41 | stopListening: (source?: Messenger, a?: string | CallbacksByEvents, b?: Function) => this; 42 | listenTo: (source: Messenger, a: string | CallbacksByEvents, b?: Function) => this; 43 | listenToOnce: (source: Messenger, a: string | CallbacksByEvents, b?: Function) => this; 44 | dispose: () => void; 45 | componentWillUnmount(): void; 46 | /** 47 | * Performs transactional update for both props and state. 48 | * Suppress updates during the transaction, and force update aftewards. 49 | * Wrapping the sequence of changes in a transactions guarantees that 50 | * React component will be updated _after_ all the changes to the 51 | * both props and local state are applied. 52 | */ 53 | transaction(fun: (state?: Record) => void): void; 54 | asyncUpdate(): void; 55 | } 56 | -------------------------------------------------------------------------------- /lib/react-mvx/component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React-Type-R component base class. Overrides React component. 3 | */ 4 | import * as tslib_1 from "tslib"; 5 | import * as React from 'react'; 6 | import { mixinRules, define, mixins, definitions, Messenger } from 'type-r'; 7 | import onDefine, { EmptyPropsChangeTokensCtor } from './define'; 8 | var Component = (function (_super) { 9 | tslib_1.__extends(Component, _super); 10 | function Component(props, context) { 11 | var _this = _super.call(this, props, context) || this; 12 | _this._initializeState(); 13 | return _this; 14 | } 15 | Component.prototype.linkAt = function (key) { 16 | // Quick and dirty hack to suppres type error - refactor later. 17 | return this.state.linkAt(key); 18 | }; 19 | Component.prototype.linkAll = function () { 20 | // Quick and dirty hack to suppres type error - refactor later. 21 | var state = this.state; 22 | return state.linkAll.apply(state, arguments); 23 | }; 24 | Component.prototype.linkPath = function (path) { 25 | return this.state.linkPath(path); 26 | }; 27 | Object.defineProperty(Component.prototype, "links", { 28 | get: function () { 29 | return this.state._links; 30 | }, 31 | enumerable: true, 32 | configurable: true 33 | }); 34 | Component.prototype._initializeState = function () { 35 | this.state = null; 36 | }; 37 | Component.prototype.assignToState = function (x, key) { 38 | this.state.assignFrom((_a = {}, _a[key] = x, _a)); 39 | var _a; 40 | }; 41 | Component.prototype.componentWillUnmount = function () { 42 | this.dispose(); 43 | }; 44 | /** 45 | * Performs transactional update for both props and state. 46 | * Suppress updates during the transaction, and force update aftewards. 47 | * Wrapping the sequence of changes in a transactions guarantees that 48 | * React component will be updated _after_ all the changes to the 49 | * both props and local state are applied. 50 | */ 51 | Component.prototype.transaction = function (fun) { 52 | var shouldComponentUpdate = this.shouldComponentUpdate, isRoot = shouldComponentUpdate !== returnFalse; 53 | if (isRoot) { 54 | this.shouldComponentUpdate = returnFalse; 55 | } 56 | var _a = this, state = _a.state, store = _a.store, withStore = store ? function (state) { return store.transaction(function () { return fun(state); }); } : fun; 57 | state ? state.transaction(withStore) : withStore(state); 58 | if (isRoot) { 59 | this.shouldComponentUpdate = shouldComponentUpdate; 60 | this.asyncUpdate(); 61 | } 62 | }; 63 | // Safe version of the forceUpdate suitable for asynchronous callbacks. 64 | Component.prototype.asyncUpdate = function () { 65 | this.shouldComponentUpdate === returnFalse || this._disposed || this.forceUpdate(); 66 | }; 67 | return Component; 68 | }(React.Component)); 69 | Component.onDefine = onDefine; 70 | Component = tslib_1.__decorate([ 71 | define({ 72 | PropsChangeTokens: EmptyPropsChangeTokensCtor 73 | }), 74 | definitions({ 75 | // Definitions to be extracted from mixins and statics and passed to `onDefine()` 76 | state: mixinRules.merge, 77 | State: mixinRules.value, 78 | store: mixinRules.merge, 79 | Store: mixinRules.value, 80 | props: mixinRules.merge, 81 | context: mixinRules.merge, 82 | childContext: mixinRules.merge, 83 | pureRender: mixinRules.protoValue 84 | }), 85 | mixinRules({ 86 | // Apply old-school React mixin rules. 87 | componentWillMount: mixinRules.classLast, 88 | componentDidMount: mixinRules.classLast, 89 | componentWillReceiveProps: mixinRules.classLast, 90 | componentWillUpdate: mixinRules.classLast, 91 | componentDidUpdate: mixinRules.classLast, 92 | componentWillUnmount: mixinRules.classFirst, 93 | // And a bit more to fix inheritance quirks. 94 | shouldComponentUpdate: mixinRules.some, 95 | getChildContext: mixinRules.defaults 96 | }) 97 | // Component can send and receive events... 98 | , 99 | mixins(Messenger) 100 | ], Component); 101 | export { Component }; 102 | function returnFalse() { return false; } 103 | // Looks like React guys _really_ want to deprecate it. But no way. 104 | // We will work around their attempt. 105 | Object.defineProperty(Component.prototype, 'isMounted', { 106 | value: function isMounted() { 107 | return !this._disposed; 108 | } 109 | }); 110 | //# sourceMappingURL=component.js.map -------------------------------------------------------------------------------- /lib/react-mvx/component.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/react-mvx/component.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/react-mvx/define/common.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { ComponentClass as ReactComponentClass, Component } from 'react'; 3 | import { MixableConstructor, Messenger } from 'type-r'; 4 | export interface ComponentClass extends ReactComponentClass, MixableConstructor { 5 | prototype: Proto & ComponentProto; 6 | } 7 | export declare type ComponentProto = Component & Messenger; 8 | -------------------------------------------------------------------------------- /lib/react-mvx/define/common.js: -------------------------------------------------------------------------------- 1 | //# sourceMappingURL=common.js.map -------------------------------------------------------------------------------- /lib/react-mvx/define/common.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"common.js","sourceRoot":"","sources":["../../../src/react-mvx/define/common.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /lib/react-mvx/define/context.d.ts: -------------------------------------------------------------------------------- 1 | import { TypeSpecs } from './typeSpecs'; 2 | import { ComponentClass } from './common'; 3 | export interface ContextDefinition { 4 | context: TypeSpecs; 5 | childContext: TypeSpecs; 6 | } 7 | export interface ContextProto { 8 | _context: TypeSpecs; 9 | _childContext: TypeSpecs; 10 | } 11 | export default function onDefine(this: ComponentClass, {context, childContext}: ContextDefinition, BaseClass: ComponentClass): void; 12 | -------------------------------------------------------------------------------- /lib/react-mvx/define/context.js: -------------------------------------------------------------------------------- 1 | import { compileSpecs } from './typeSpecs'; 2 | import { tools } from 'type-r'; 3 | export default function onDefine(_a, BaseClass) { 4 | var context = _a.context, childContext = _a.childContext; 5 | var prototype = this.prototype; 6 | if (context) { 7 | // Merge in inherited members... 8 | prototype._context = tools.defaults(context, BaseClass.prototype._context || {}); 9 | // Compile to propTypes... 10 | this.contextTypes = compileSpecs(context).propTypes; 11 | } 12 | if (childContext) { 13 | prototype._childContext = tools.defaults(childContext, BaseClass.prototype._childContext); 14 | this.childContextTypes = compileSpecs(childContext).propTypes; 15 | } 16 | } 17 | //# sourceMappingURL=context.js.map -------------------------------------------------------------------------------- /lib/react-mvx/define/context.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/react-mvx/define/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAa,MAAM,aAAa,CAAA;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAa9B,MAAM,CAAC,OAAO,mBAAyD,EAA6C,EAAE,SAAwC;QAArF,oBAAO,EAAE,8BAAY;IAClF,IAAA,0BAAS,CAAU;IAE3B,EAAE,CAAA,CAAE,OAAQ,CAAC,CAAA,CAAC;QACV,gCAAgC;QAChC,SAAS,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAE,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,QAAQ,IAAI,EAAE,CAAE,CAAC;QAEnF,0BAA0B;QAC1B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAE,OAAO,CAAE,CAAC,SAAS,CAAC;IAC1D,CAAC;IAED,EAAE,CAAA,CAAE,YAAa,CAAC,CAAA,CAAC;QACf,SAAS,CAAC,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAE,YAAY,EAAE,SAAS,CAAC,SAAS,CAAC,aAAa,CAAE,CAAC;QAC5F,IAAI,CAAC,iBAAiB,GAAG,YAAY,CAAE,YAAY,CAAE,CAAC,SAAS,CAAC;IACpE,CAAC;AACL,CAAC"} -------------------------------------------------------------------------------- /lib/react-mvx/define/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ComponentClass } from './common'; 2 | import { StoreDefinition, StoreProto } from './store'; 3 | import { StateDefinition, StateProto } from './state'; 4 | import { ContextDefinition, ContextProto } from './context'; 5 | import { PropsDefinition, PropsProto } from './props'; 6 | export interface ComponentDefinition extends StoreDefinition, StateDefinition, ContextDefinition, PropsDefinition { 7 | } 8 | export interface ComponentProto extends StoreProto, StateProto, ContextProto, PropsProto { 9 | } 10 | export default function onDefine(this: ComponentClass, definition: ComponentDefinition, BaseClass: ComponentClass): void; 11 | export { Node, Element, TypeSpecs } from './typeSpecs'; 12 | export { EmptyPropsChangeTokensCtor } from './pureRender'; 13 | -------------------------------------------------------------------------------- /lib/react-mvx/define/index.js: -------------------------------------------------------------------------------- 1 | import { Messenger } from 'type-r'; 2 | import onDefineStore from './store'; 3 | import onDefineState from './state'; 4 | import onDefineContext from './context'; 5 | import onDefineProps from './props'; 6 | export default function onDefine(definition, BaseClass) { 7 | // Initialize mixins placeholder... 8 | onDefineStore.call(this, definition, BaseClass); 9 | onDefineState.call(this, definition, BaseClass); 10 | onDefineContext.call(this, definition, BaseClass); 11 | onDefineProps.call(this, definition, BaseClass); 12 | Messenger.onDefine.call(this, definition, BaseClass); 13 | } 14 | ; 15 | export { Node, Element } from './typeSpecs'; 16 | export { EmptyPropsChangeTokensCtor } from './pureRender'; 17 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /lib/react-mvx/define/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react-mvx/define/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAElC,OAAO,aAA8C,MAAM,SAAS,CAAA;AACpE,OAAO,aAA8C,MAAM,SAAS,CAAA;AACpE,OAAO,eAAoD,MAAM,WAAW,CAAA;AAC5E,OAAO,aAA8C,MAAM,SAAS,CAAA;AAKpE,MAAM,CAAC,OAAO,mBAA2D,UAAgC,EAAE,SAA0C;IACjJ,mCAAmC;IACnC,aAAa,CAAC,IAAI,CAAE,IAAI,EAAE,UAAU,EAAE,SAAS,CAAE,CAAC;IAClD,aAAa,CAAC,IAAI,CAAE,IAAI,EAAE,UAAU,EAAE,SAAS,CAAE,CAAC;IAClD,eAAe,CAAC,IAAI,CAAE,IAAI,EAAE,UAAU,EAAE,SAAS,CAAE,CAAC;IACpD,aAAa,CAAC,IAAI,CAAE,IAAI,EAAE,UAAU,EAAE,SAAS,CAAE,CAAC;IAElD,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAE,IAAI,EAAE,UAAU,EAAE,SAAS,CAAE,CAAC;AAC3D,CAAC;AAAA,CAAC;AAEF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAa,MAAM,aAAa,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA"} -------------------------------------------------------------------------------- /lib/react-mvx/define/props.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Handle props specification and everything which is related: 3 | * - local listening to props changes 4 | * - pure render mixin 5 | */ 6 | import { TypeSpecs } from './typeSpecs'; 7 | import { ComponentClass } from './common'; 8 | export interface PropsDefinition { 9 | pureRender?: boolean; 10 | props: TypeSpecs; 11 | } 12 | export interface PropsProto { 13 | pureRender?: boolean; 14 | _props?: TypeSpecs; 15 | _watchers?: any; 16 | _changeHandlers?: any; 17 | PropsChangeTokens?: any; 18 | } 19 | export default function onDefine(this: ComponentClass, {props, pureRender}: PropsDefinition, BaseClass: ComponentClass): void; 20 | -------------------------------------------------------------------------------- /lib/react-mvx/define/props.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Handle props specification and everything which is related: 3 | * - local listening to props changes 4 | * - pure render mixin 5 | */ 6 | import { compileSpecs } from './typeSpecs'; 7 | import { PureRenderMixin, createChangeTokensConstructor } from './pureRender'; 8 | import { tools } from 'type-r'; 9 | export default function onDefine(_a, BaseClass) { 10 | var props = _a.props, pureRender = _a.pureRender; 11 | var prototype = this.prototype; 12 | // process props spec... 13 | if (props) { 14 | // Merge with inherited members... 15 | prototype._props = tools.defaults(props, BaseClass.prototype._props || {}); 16 | var _b = compileSpecs(props), propTypes = _b.propTypes, defaults = _b.defaults, watchers = _b.watchers, changeHandlers = _b.changeHandlers; 17 | this.propTypes = propTypes; 18 | if (defaults) 19 | this.defaultProps = defaults; 20 | if (watchers) { 21 | prototype._watchers = watchers; 22 | this.mixins.merge([WatchersMixin]); 23 | } 24 | if (changeHandlers) { 25 | prototype._changeHandlers = changeHandlers; 26 | this.mixins.merge([ChangeHandlersMixin]); 27 | } 28 | if (prototype.pureRender) { 29 | prototype.PropsChangeTokens = createChangeTokensConstructor(props); 30 | } 31 | } 32 | if (pureRender) { 33 | this.mixins.merge([PureRenderMixin]); 34 | } 35 | } 36 | /** 37 | * ChangeHandlers are fired in sequence upon props replacement. 38 | * Fires _after_ UI is updated. Used for managing events subscriptions. 39 | */ 40 | var ChangeHandlersMixin = { 41 | componentDidMount: function () { 42 | handlePropsChanges(this, {}, this.props); 43 | }, 44 | componentDidUpdate: function (prev) { 45 | handlePropsChanges(this, prev, this.props); 46 | }, 47 | componentWillUnmount: function () { 48 | handlePropsChanges(this, this.props, {}); 49 | } 50 | }; 51 | function handlePropsChanges(component, prev, next) { 52 | var _changeHandlers = component._changeHandlers; 53 | for (var name_1 in _changeHandlers) { 54 | if (prev[name_1] !== next[name_1]) { 55 | for (var _i = 0, _a = _changeHandlers[name_1]; _i < _a.length; _i++) { 56 | var handler = _a[_i]; 57 | handler(next[name_1], prev[name_1], component); 58 | } 59 | } 60 | } 61 | } 62 | /** 63 | * Watchers works on props replacement and fires _before_ any change will be applied and UI is updated. 64 | * Fired in componentWillMount as well, which makes it a nice way to sync state from props. 65 | */ 66 | var WatchersMixin = { 67 | componentWillReceiveProps: function (next) { 68 | var _a = this, _watchers = _a._watchers, props = _a.props; 69 | for (var name_2 in _watchers) { 70 | if (next[name_2] !== props[name_2]) { 71 | _watchers[name_2].call(this, next[name_2], name_2); 72 | } 73 | } 74 | }, 75 | componentWillMount: function () { 76 | var _a = this, _watchers = _a._watchers, props = _a.props; 77 | for (var name_3 in _watchers) { 78 | _watchers[name_3].call(this, props[name_3], name_3); 79 | } 80 | } 81 | }; 82 | //# sourceMappingURL=props.js.map -------------------------------------------------------------------------------- /lib/react-mvx/define/props.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"props.js","sourceRoot":"","sources":["../../../src/react-mvx/define/props.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/react-mvx/define/pureRender.d.ts: -------------------------------------------------------------------------------- 1 | export declare function createChangeTokensConstructor(props: any): Function; 2 | export declare const EmptyPropsChangeTokensCtor: Function; 3 | export declare const PureRenderMixin: { 4 | shouldComponentUpdate(nextProps: any): any; 5 | componentDidMount: () => void; 6 | componentDidUpdate: () => void; 7 | }; 8 | -------------------------------------------------------------------------------- /lib/react-mvx/define/pureRender.js: -------------------------------------------------------------------------------- 1 | export function createChangeTokensConstructor(props) { 2 | var propNames = Object.keys(props); 3 | var PropsChangeTokens = new Function('p', 's', "\n var v;\n this._s = s && s._changeToken;\n " + propNames.map(function (name) { return "\n this." + name + " = ( ( v = p." + name + ") && v._changeToken ) || v;\n "; }).join('') + "\n "); 4 | PropsChangeTokens.prototype._hasChanges = new Function('p', 's', "\n var v;\n return ( ( s && s._changeToken ) !== this._s ) " + propNames.map(function (name) { return " ||\n this." + name + " !== ( ( ( v = p." + name + ") && v._changeToken ) || v )\n "; }).join('') + ";\n "); 5 | return PropsChangeTokens; 6 | } 7 | ; 8 | export var EmptyPropsChangeTokensCtor = createChangeTokensConstructor({}); 9 | export var PureRenderMixin = { 10 | shouldComponentUpdate: function (nextProps) { 11 | return this._propsChangeTokens._hasChanges(nextProps, this.state); 12 | }, 13 | componentDidMount: updateChangeTokens, 14 | componentDidUpdate: updateChangeTokens 15 | }; 16 | function updateChangeTokens() { 17 | this._propsChangeTokens = new this.PropsChangeTokens(this.props, this.state); 18 | } 19 | //# sourceMappingURL=pureRender.js.map -------------------------------------------------------------------------------- /lib/react-mvx/define/pureRender.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"pureRender.js","sourceRoot":"","sources":["../../../src/react-mvx/define/pureRender.ts"],"names":[],"mappings":"AAAA,MAAM,wCAAyC,KAAK;IAChD,IAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAE,KAAK,CAAE,CAAC;IAEvC,IAAM,iBAAiB,GAAG,IAAI,QAAQ,CAAE,GAAG,EAAE,GAAG,EAAE,uEAG3C,SAAS,CAAC,GAAG,CAAE,UAAA,IAAI,IAAI,OAAA,wBACd,IAAI,qBAAkB,IAAI,0CACrC,EAFyB,CAEzB,CAAC,CAAC,IAAI,CAAE,EAAE,CAAE,WAChB,CAAC,CAAC;IAEH,iBAAiB,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,QAAQ,CAAE,GAAG,EAAE,GAAG,EAAE,8EAEZ,SAAS,CAAC,GAAG,CAAE,UAAA,IAAI,IAAI,OAAA,2BAC7D,IAAI,yBAAsB,IAAI,2CACzC,EAFwE,CAExE,CAAC,CAAC,IAAI,CAAE,EAAE,CAAE,YAChB,CAAC,CAAC;IAEH,MAAM,CAAC,iBAAiB,CAAC;AAC7B,CAAC;AAAA,CAAC;AAEF,MAAM,CAAC,IAAM,0BAA0B,GAAG,6BAA6B,CAAC,EAAE,CAAC,CAAC;AAE5E,MAAM,CAAC,IAAM,eAAe,GAAG;IAC3B,qBAAqB,YAAE,SAAS;QAC5B,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAE,CAAC;IACxE,CAAC;IAED,iBAAiB,EAAG,kBAAkB;IACtC,kBAAkB,EAAG,kBAAkB;CAC1C,CAAA;AAED;IACI,IAAI,CAAC,kBAAkB,GAAG,IAAI,IAAI,CAAC,iBAAiB,CAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAE,CAAC;AACnF,CAAC"} -------------------------------------------------------------------------------- /lib/react-mvx/define/state.d.ts: -------------------------------------------------------------------------------- 1 | /***************** 2 | * State 3 | */ 4 | import { Record, Store } from 'type-r'; 5 | import { ComponentClass } from './common'; 6 | export interface StateDefinition { 7 | state?: object | typeof Record; 8 | State?: typeof Record; 9 | } 10 | export interface StateProto { 11 | State?: typeof Record; 12 | } 13 | export default function process(this: ComponentClass, definition: StateDefinition, BaseComponentClass: ComponentClass): void; 14 | export declare const StateMixin: { 15 | _initializeState(): void; 16 | context: { 17 | _nestedStore: typeof Store; 18 | }; 19 | getStore(): any; 20 | componentWillUnmount(): void; 21 | }; 22 | export declare const UpdateOnNestedChangesMixin: { 23 | _onChildrenChange(): void; 24 | componentDidMount(): void; 25 | }; 26 | -------------------------------------------------------------------------------- /lib/react-mvx/define/state.js: -------------------------------------------------------------------------------- 1 | import * as tslib_1 from "tslib"; 2 | /***************** 3 | * State 4 | */ 5 | import { define, Record, Store } from 'type-r'; 6 | export default function process(definition, BaseComponentClass) { 7 | var prototype = this.prototype; 8 | var state = definition.state, State = definition.State; 9 | if (typeof state === 'function') { 10 | State = state; 11 | state = void 0; 12 | } 13 | if (state) { 14 | var BaseClass = State || prototype.State || Record; 15 | var ComponentState = (function (_super) { 16 | tslib_1.__extends(ComponentState, _super); 17 | function ComponentState() { 18 | return _super !== null && _super.apply(this, arguments) || this; 19 | } 20 | return ComponentState; 21 | }(BaseClass)); 22 | ComponentState.attributes = state; 23 | ComponentState = tslib_1.__decorate([ 24 | define 25 | ], ComponentState); 26 | prototype.State = ComponentState; 27 | } 28 | else if (State) { 29 | prototype.State = State; 30 | } 31 | if (state || State) { 32 | this.mixins.merge([StateMixin, UpdateOnNestedChangesMixin]); 33 | } 34 | } 35 | export var StateMixin = { 36 | //state : null, 37 | _initializeState: function () { 38 | // props.__keepState is used to workaround issues in Backbone intergation layer 39 | var state = this.state = this.props.__keepState || new this.State(); 40 | // Take ownership on state... 41 | state._owner = this; 42 | state._ownerKey = 'state'; 43 | }, 44 | context: { 45 | _nestedStore: Store 46 | }, 47 | // reference global store to fix model's store locator 48 | getStore: function () { 49 | // Attempt to get the store from the context first. Then - fallback to the state's default store. 50 | // TBD: Need to figure out a good way of managing local stores. 51 | var context, state; 52 | return ((context = this.context) && context._nestedStore) || 53 | ((state = this.state) && state._defaultStore); 54 | }, 55 | componentWillUnmount: function () { 56 | var state = this.state; 57 | state._owner = state._ownerKey = void 0; 58 | this._preventDispose /* hack for component-view to preserve the state */ || state.dispose(); 59 | this.state = void 0; 60 | } 61 | }; 62 | export var UpdateOnNestedChangesMixin = { 63 | _onChildrenChange: function () { }, 64 | componentDidMount: function () { 65 | this._onChildrenChange = this.asyncUpdate; 66 | } 67 | }; 68 | //# sourceMappingURL=state.js.map -------------------------------------------------------------------------------- /lib/react-mvx/define/state.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/react-mvx/define/state.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAY9C,MAAM,CAAC,OAAO,kBAAsD,UAA4B,EAAE,kBAA+C;IACrI,IAAA,0BAAS,CAAU;IAErB,IAAA,wBAAK,EAAE,wBAAK,CAAgB;IAElC,EAAE,CAAA,CAAE,OAAO,KAAK,KAAK,UAAW,CAAC,CAAA,CAAC;QAC9B,KAAK,GAAG,KAAK,CAAC;QACd,KAAK,GAAG,KAAK,CAAC,CAAC;IACnB,CAAC;IAED,EAAE,CAAA,CAAE,KAAM,CAAC,CAAA,CAAC;QACR,IAAM,SAAS,GAAG,KAAK,IAAI,SAAS,CAAC,KAAK,IAAI,MAAM,CAAC;QAE7C,IAAM,cAAc;YAAS,0CAAS;YAAtC;;YAER,CAAC;YAAD,qBAAC;QAAD,CAAC,AAFO,CAA6B,SAAS,GAE7C;QADU,yBAAU,GAAG,KAAK,CAAC;QADhB,cAAc;YAA3B,MAAM;WAAO,cAAc,CAE3B;QAED,SAAS,CAAC,KAAK,GAAG,cAAc,CAAC;IACrC,CAAC;IACD,IAAI,CAAC,EAAE,CAAA,CAAE,KAAM,CAAC,CAAA,CAAC;QACb,SAAS,CAAC,KAAK,GAAG,KAAK,CAAC;IAC5B,CAAC;IAED,EAAE,CAAA,CAAE,KAAK,IAAI,KAAM,CAAC,CAAA,CAAC;QACjB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAE,UAAU,EAAE,0BAA0B,CAAE,CAAC,CAAC;IAClE,CAAC;AACL,CAAC;AAED,MAAM,CAAC,IAAM,UAAU,GAAG;IACtB,eAAe;IAEf,gBAAgB;QACZ,+EAA+E;QAC/E,IAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAEtE,6BAA6B;QAC7B,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;QACpB,KAAK,CAAC,SAAS,GAAG,OAAO,CAAC;IAC9B,CAAC;IAED,OAAO,EAAG;QACN,YAAY,EAAG,KAAK;KACvB;IAED,sDAAsD;IACtD,QAAQ;QACJ,iGAAiG;QACjG,+DAA+D;QAC/D,IAAI,OAAO,EAAE,KAAK,CAAC;QAEnB,MAAM,CAAE,CAAE,CAAE,OAAO,GAAG,IAAI,CAAC,OAAO,CAAE,IAAI,OAAO,CAAC,YAAY,CAAE;YACtD,CAAE,CAAE,KAAK,GAAG,IAAI,CAAC,KAAK,CAAE,IAAI,KAAK,CAAC,aAAa,CAAE,CAAC;IAC9D,CAAC;IAED,oBAAoB;QACR,IAAA,kBAAK,CAAU;QACvB,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,eAAe,CAAC,mDAAmD,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAC5F,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;IACxB,CAAC;CACJ,CAAC;AAEF,MAAM,CAAC,IAAM,0BAA0B,GAAG;IACtC,iBAAiB,gBAAG,CAAC;IAErB,iBAAiB;QACb,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC;IAC9C,CAAC;CACJ,CAAC"} -------------------------------------------------------------------------------- /lib/react-mvx/define/store.d.ts: -------------------------------------------------------------------------------- 1 | import { Store } from 'type-r'; 2 | import { ComponentClass } from './common'; 3 | export interface StoreDefinition { 4 | store?: typeof Store | Store | object; 5 | Store?: typeof Store; 6 | } 7 | export interface StoreProto { 8 | store?: Store; 9 | Store?: typeof Store; 10 | } 11 | export default function onDefine(this: ComponentClass, definition: StoreDefinition, BaseClass: ComponentClass): void; 12 | -------------------------------------------------------------------------------- /lib/react-mvx/define/store.js: -------------------------------------------------------------------------------- 1 | import * as tslib_1 from "tslib"; 2 | import { define, Store } from 'type-r'; 3 | import { StateMixin, UpdateOnNestedChangesMixin } from './state'; 4 | export default function onDefine(definition, BaseClass) { 5 | var store = definition.store, StoreClass = definition.Store; 6 | if (store && store instanceof Store) { 7 | // Direct reference to an existing store. Put it to the prototype. 8 | this.prototype.store = store; 9 | this.mixins.merge([ExternalStoreMixin, ExposeStoreMixin]); 10 | } 11 | else if (store || definition.Store) { 12 | if (typeof store === 'function') { 13 | StoreClass = store; 14 | store = void 0; 15 | } 16 | if (store) { 17 | var BaseClass_1 = StoreClass || this.prototype.Store || Store; 18 | var InternalStore = (function (_super) { 19 | tslib_1.__extends(InternalStore, _super); 20 | function InternalStore() { 21 | return _super !== null && _super.apply(this, arguments) || this; 22 | } 23 | return InternalStore; 24 | }(BaseClass_1)); 25 | InternalStore.attrbutes = store; 26 | InternalStore = tslib_1.__decorate([ 27 | define 28 | ], InternalStore); 29 | ; 30 | this.prototype.Store = InternalStore; 31 | } 32 | else if (StoreClass) { 33 | this.prototype.Store = StoreClass; 34 | } 35 | this.mixins.merge([InternalStoreMixin, UpdateOnNestedChangesMixin, ExposeStoreMixin]); 36 | } 37 | } 38 | /** 39 | * Attached whenever the store declaration of any form is present in the component. 40 | */ 41 | var ExposeStoreMixin = { 42 | childContext: { 43 | _nestedStore: Store 44 | }, 45 | getChildContext: function () { 46 | return { _nestedStore: this.store }; 47 | }, 48 | getStore: function () { 49 | return this.store; 50 | }, 51 | // Will be called by the store when the lookup will fail. 52 | get: function (key) { 53 | // Ask upper store. 54 | var store = StateMixin.getStore.call(this, key); 55 | return store && store.get(key); 56 | } 57 | }; 58 | /** 59 | * External store must just track the changes and trigger render. 60 | * TBD: don't use it yet. 61 | */ 62 | var ExternalStoreMixin = { 63 | componentDidMount: function () { 64 | // Start UI updates on state changes. 65 | this.listenTo(this.store, 'change', this.asyncUpdate); 66 | } 67 | }; 68 | var InternalStoreMixin = { 69 | componentWillMount: function () { 70 | var store = this.store = new this.Store(); 71 | store._owner = this; 72 | store._ownerKey = 'store'; 73 | }, 74 | componentWillUnmount: function () { 75 | this.store._ownerKey = this.store._owner = void 0; 76 | this.store.dispose(); 77 | this.store = void 0; 78 | } 79 | }; 80 | //# sourceMappingURL=store.js.map -------------------------------------------------------------------------------- /lib/react-mvx/define/store.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/react-mvx/define/store.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAA;AAahE,MAAM,CAAC,OAAO,mBAAuD,UAA4B,EAAE,SAAsC;IAC/H,IAAA,wBAAK,EAAE,6BAAkB,CAAgB;IAE/C,EAAE,CAAA,CAAE,KAAK,IAAI,KAAK,YAAY,KAAM,CAAC,CAAA,CAAC;QAClC,kEAAkE;QAClE,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,KAAK,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAE,kBAAkB,EAAE,gBAAgB,CAAE,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,CAAC,EAAE,CAAA,CAAE,KAAK,IAAI,UAAU,CAAC,KAAM,CAAC,CAAC,CAAC;QAClC,EAAE,CAAA,CAAE,OAAO,KAAK,KAAK,UAAW,CAAC,CAAA,CAAC;YAC9B,UAAU,GAAG,KAAK,CAAC;YACnB,KAAK,GAAG,KAAK,CAAC,CAAC;QACnB,CAAC;QAED,EAAE,CAAA,CAAE,KAAM,CAAC,CAAA,CAAC;YACR,IAAM,WAAS,GAAG,UAAU,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC;YACtD,IAAM,aAAa;gBAAS,yCAAS;gBAArC;;gBAER,CAAC;gBAAD,oBAAC;YAAD,CAAC,AAFO,CAA4B,WAAS,GAE5C;YADU,uBAAS,GAAG,KAAK,CAAC;YADf,aAAa;gBAA1B,MAAM;eAAO,aAAa,CAE1B;YAAA,CAAC;YAEF,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,aAAa,CAAC;QACzC,CAAC;QACD,IAAI,CAAC,EAAE,CAAA,CAAE,UAAW,CAAC,CAAA,CAAC;YAClB,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,UAAU,CAAC;QACtC,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAE,kBAAkB,EAAE,0BAA0B,EAAE,gBAAgB,CAAE,CAAC,CAAC;IAC5F,CAAC;AACL,CAAC;AAED;;GAEG;AACH,IAAM,gBAAgB,GAAG;IACrB,YAAY,EAAG;QACX,YAAY,EAAG,KAAK;KACvB;IAED,eAAe;QACX,MAAM,CAAC,EAAE,YAAY,EAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IACzC,CAAC;IAED,QAAQ;QACJ,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,yDAAyD;IACzD,GAAG,YAAE,GAAG;QACJ,mBAAmB;QACnB,IAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAE,IAAI,EAAE,GAAG,CAAE,CAAC;QACpD,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,CAAE,GAAG,CAAE,CAAC;IACrC,CAAC;CACJ,CAAC;AAEF;;;GAGG;AACH,IAAM,kBAAkB,GAAG;IACvB,iBAAiB;QACb,qCAAqC;QACrC,IAAI,CAAC,QAAQ,CAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAE,CAAC;IAC5D,CAAC;CACJ,CAAC;AAEF,IAAM,kBAAkB,GAAG;IACvB,kBAAkB;QACd,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC1C,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;QACpB,KAAK,CAAC,SAAS,GAAG,OAAO,CAAC;IAC9B,CAAC;IAED,oBAAoB;QAChB,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;QAClD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;IACxB,CAAC;CACJ,CAAC"} -------------------------------------------------------------------------------- /lib/react-mvx/define/typeSpecs.d.ts: -------------------------------------------------------------------------------- 1 | import { ChangeHandler } from 'type-r'; 2 | import { ComponentProto } from './common'; 3 | export interface TypeSpecs { 4 | [name: string]: object | Function; 5 | } 6 | export declare function compileSpecs(props: TypeSpecs): { 7 | propTypes: {}; 8 | defaults: any; 9 | watchers: { 10 | [name: string]: (this: ComponentProto, propValue: any, propName: string) => void; 11 | }; 12 | changeHandlers: { 13 | [name: string]: ChangeHandler[]; 14 | }; 15 | }; 16 | export declare class Node { 17 | } 18 | export declare class Element { 19 | } 20 | declare global { 21 | interface NumberConstructor { 22 | integer: Function; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/react-mvx/define/typeSpecs.js: -------------------------------------------------------------------------------- 1 | import * as PropTypes from 'prop-types'; 2 | import { Record } from 'type-r'; 3 | export function compileSpecs(props) { 4 | var propTypes = {}, 5 | // Create NestedTypes model definition to process props spec. 6 | modelProto = Record.defaults(props).prototype; 7 | var defaults, watchers, changeHandlers; 8 | modelProto.forEachAttr(modelProto._attributes, function (spec, name) { 9 | // Skip auto-generated `id` attribute. 10 | if (name !== 'id') { 11 | var value = spec.value, type = spec.type, options = spec.options; 12 | // Translate props type to the propTypes guard. 13 | propTypes[name] = translateType(type, options.isRequired); 14 | if (options._onChange) { 15 | watchers || (watchers = {}); 16 | watchers[name] = toLocalWatcher(options._onChange); 17 | } 18 | // Handle listening to event maps... 19 | if (options.changeHandlers && options.changeHandlers.length) { 20 | changeHandlers || (changeHandlers = {}); 21 | changeHandlers[name] = options.changeHandlers; 22 | } 23 | // Handle listening to props changes... 24 | if (options.changeEvents) { 25 | changeHandlers || (changeHandlers = {}); 26 | var handlers = changeHandlers[name] || (changeHandlers[name] = []), changeEvents_1 = typeof options.changeEvents === 'string' ? options.changeEvents : null; 27 | handlers.push(function (next, prev, component) { 28 | prev && component.stopListening(prev); 29 | next && component.listenTo(next, changeEvents_1 || next._changeEventName, component.asyncUpdate); 30 | }); 31 | } 32 | // If default value is explicitly provided... 33 | if (value !== void 0) { 34 | //...append it to getDefaultProps function. 35 | defaults || (defaults = {}); 36 | defaults[name] = spec.convert(value, void 0, null, {}); 37 | } 38 | } 39 | }); 40 | return { propTypes: propTypes, defaults: defaults, watchers: watchers, changeHandlers: changeHandlers }; 41 | } 42 | function toLocalWatcher(ref) { 43 | return typeof ref === 'function' ? ref : function (value, name) { 44 | this[ref] && this[ref](value, name); 45 | }; 46 | } 47 | var Node = (function () { 48 | function Node() { 49 | } 50 | return Node; 51 | }()); 52 | export { Node }; 53 | var Element = (function () { 54 | function Element() { 55 | } 56 | return Element; 57 | }()); 58 | export { Element }; 59 | function translateType(Type, isRequired) { 60 | var T = _translateType(Type); 61 | return isRequired ? T.isRequired : T; 62 | } 63 | function _translateType(Type) { 64 | switch (Type) { 65 | case Number: 66 | case Number.integer: 67 | return PropTypes.number; 68 | case String: 69 | return PropTypes.string; 70 | case Boolean: 71 | return PropTypes.bool; 72 | case Array: 73 | return PropTypes.array; 74 | case Function: 75 | return PropTypes.func; 76 | case Object: 77 | return PropTypes.object; 78 | case Node: 79 | return PropTypes.node; 80 | case Element: 81 | return PropTypes.element; 82 | case void 0: 83 | case null: 84 | return PropTypes.any; 85 | default: 86 | return PropTypes.instanceOf(Type); 87 | } 88 | } 89 | //# sourceMappingURL=typeSpecs.js.map -------------------------------------------------------------------------------- /lib/react-mvx/define/typeSpecs.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"typeSpecs.js","sourceRoot":"","sources":["../../../src/react-mvx/define/typeSpecs.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/react-mvx/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import * as React from 'react'; 3 | import { define, mixins, mixinRules, ChainableAttributeSpec } from 'type-r'; 4 | import { Node, Element } from './define'; 5 | import Link from './link'; 6 | import { Component } from './component'; 7 | interface ReactMVx { 8 | default: ReactMVx; 9 | define: typeof define; 10 | mixins: typeof mixins; 11 | mixinRules: typeof mixinRules; 12 | Component: typeof Component; 13 | Link: typeof Link; 14 | Node: ChainableAttributeSpec; 15 | Element: ChainableAttributeSpec; 16 | assignToState(key: string): any; 17 | } 18 | declare const ReactMVx: ReactMVx & typeof React; 19 | declare const assignToState: (key: string) => (prop: any) => void; 20 | export default ReactMVx; 21 | export { define, mixins, Node, Element, Link, Component, assignToState }; 22 | -------------------------------------------------------------------------------- /lib/react-mvx/index.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { define, mixins } from 'type-r'; 3 | import { Node, Element } from './define'; 4 | import Link from './link'; 5 | import { Component } from './component'; 6 | // extend React namespace 7 | var ReactMVx = Object.create(React); 8 | // Make it compatible with ES6 module format. 9 | ReactMVx.default = ReactMVx; 10 | // listenToProps, listenToState, model, attributes, Model 11 | ReactMVx.define = define; 12 | ReactMVx.mixins = mixins; 13 | ReactMVx.Node = Node.value(null); 14 | ReactMVx.Element = Element.value(null); 15 | ReactMVx.Link = Link; 16 | ReactMVx.Component = Component; 17 | var assignToState = ReactMVx.assignToState = function (key) { 18 | return function (prop) { 19 | var source = prop && prop instanceof Link ? prop.value : prop; 20 | this.state.assignFrom((_a = {}, _a[key] = source, _a)); 21 | if (source && source._changeToken) { 22 | this.state[key]._changeToken = source._changeToken; 23 | } 24 | var _a; 25 | }; 26 | }; 27 | export default ReactMVx; 28 | export { define, mixins, Node, Element, Link, Component, assignToState }; 29 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /lib/react-mvx/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react-mvx/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,MAAM,EAAiB,MAAM,EAAsC,MAAM,QAAQ,CAAA;AAC1F,OAAoB,EAAE,IAAI,EAAE,OAAO,EAAa,MAAM,UAAU,CAAA;AAChE,OAAO,IAAI,MAAM,QAAQ,CAAA;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAuBvC,yBAAyB;AACzB,IAAM,QAAQ,GAA6B,MAAM,CAAC,MAAM,CAAE,KAAK,CAAE,CAAC;AAElE,6CAA6C;AAC7C,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC;AAC5B,yDAAyD;AACzD,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;AACzB,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;AAEzB,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAE,IAAI,CAAE,CAAC;AACnC,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAE,IAAI,CAAE,CAAC;AACzC,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;AAErB,QAAQ,CAAC,SAAS,GAAG,SAAgB,CAAC;AACtC,IAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,GAAG,UAAA,GAAG;IAC9C,MAAM,CAAC,UAAU,IAAI;QACjB,IAAM,MAAM,GAAG,IAAI,IAAI,IAAI,YAAY,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAChE,IAAI,CAAC,KAAK,CAAC,UAAU,WAAG,GAAE,GAAG,IAAK,MAAM,MAAG,CAAC;QAC5C,EAAE,CAAA,CAAE,MAAM,IAAI,MAAM,CAAC,YAAa,CAAC,CAAA,CAAC;YAChC,IAAI,CAAC,KAAK,CAAE,GAAG,CAAE,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;QACzD,CAAC;;IACL,CAAC,CAAA;AACL,CAAC,CAAA;AAED,eAAe,QAAQ,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,CAAA"} -------------------------------------------------------------------------------- /lib/react-mvx/link.d.ts: -------------------------------------------------------------------------------- 1 | import { Link } from './valuelink/link'; 2 | export default Link; 3 | -------------------------------------------------------------------------------- /lib/react-mvx/link.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Import ValueLink library 3 | * Define value links binding mixins to the Record and Collection 4 | */ 5 | import * as tslib_1 from "tslib"; 6 | import { Mixable, MixinsState, Record } from 'type-r'; 7 | import { Link } from './valuelink/link'; 8 | export default Link; 9 | Mixable.mixins.populate(Link); 10 | /** 11 | * Record 12 | */ 13 | MixinsState.get(Record).merge([{ 14 | // Link to the record's attribute by its key. 15 | linkAt: function (key) { 16 | return cacheLink(getLinksCache(this), this, key); 17 | }, 18 | // Link to the attribute of the record's tree by symbolic path. 19 | linkPath: function (path, options) { 20 | return new RecordDeepLink(this, path, options); 21 | }, 22 | // Link all (or listed) attributes and return links cache. 23 | linkAll: function () { 24 | var links = getLinksCache(this); 25 | if (arguments.length) { 26 | for (var i = 0; i < arguments.length; i++) { 27 | cacheLink(links, this, arguments[i]); 28 | } 29 | } 30 | else { 31 | var attributes = this.attributes; 32 | for (var key in attributes) { 33 | attributes[key] === void 0 || cacheLink(links, this, key); 34 | } 35 | } 36 | return links; 37 | } 38 | }]); 39 | /** 40 | * Link to Type-R's record attribute. 41 | * Strict evaluation of value, lazy evaluation of validation error. 42 | * Links are cached in the records 43 | */ 44 | var RecordLink = (function (_super) { 45 | tslib_1.__extends(RecordLink, _super); 46 | function RecordLink(record, attr, value) { 47 | var _this = _super.call(this, value) || this; 48 | _this.record = record; 49 | _this.attr = attr; 50 | return _this; 51 | } 52 | RecordLink.prototype.set = function (x) { 53 | this.record[this.attr] = x; 54 | }; 55 | Object.defineProperty(RecordLink.prototype, "error", { 56 | get: function () { 57 | return this._error === void 0 ? 58 | this.record.getValidationError(this.attr) : 59 | this._error; 60 | }, 61 | set: function (x) { 62 | this._error = x; 63 | }, 64 | enumerable: true, 65 | configurable: true 66 | }); 67 | return RecordLink; 68 | }(Link)); 69 | var RecordDeepLink = (function (_super) { 70 | tslib_1.__extends(RecordDeepLink, _super); 71 | function RecordDeepLink(record, path, options) { 72 | var _this = _super.call(this, record.deepGet(path)) || this; 73 | _this.record = record; 74 | _this.path = path; 75 | _this.options = options; 76 | return _this; 77 | } 78 | Object.defineProperty(RecordDeepLink.prototype, "error", { 79 | get: function () { 80 | if (this._error === void 0) { 81 | this._error = this.record.deepValidationError(this.path) || null; 82 | } 83 | return this._error; 84 | }, 85 | set: function (x) { 86 | this._error = x; 87 | }, 88 | enumerable: true, 89 | configurable: true 90 | }); 91 | Object.defineProperty(RecordDeepLink.prototype, "_changeToken", { 92 | get: function () { 93 | return this.record._changeToken; 94 | }, 95 | enumerable: true, 96 | configurable: true 97 | }); 98 | RecordDeepLink.prototype.set = function (x) { 99 | this.record.deepSet(this.path, x, this.options); 100 | }; 101 | return RecordDeepLink; 102 | }(Link)); 103 | function getLinksCache(record) { 104 | return record._links || (record._links = new record.AttributesCopy({})); 105 | } 106 | function cacheLink(links, record, key) { 107 | var cached = links[key], value = record[key]; 108 | return cached && cached.value === value ? cached 109 | : links[key] = new RecordLink(record, key, value); 110 | } 111 | /*********************************** 112 | * Collection 113 | */ 114 | MixinsState.get(Record.Collection).merge([{ 115 | // Boolean link to the record's presence in the collection 116 | linkContains: function (record) { 117 | return new CollectionLink(this, record); 118 | }, 119 | // Link to collection's property 120 | linkAt: function (prop) { 121 | var _this = this; 122 | return Link.value(this[prop], function (x) { return _this[prop] = x; }); 123 | } 124 | }]); 125 | /** 126 | * Boolean link to presence of NestedType's record in collection. 127 | * Strict evaluation of value, no error. 128 | * Safe implementation of _changeToken. 129 | */ 130 | var CollectionLink = (function (_super) { 131 | tslib_1.__extends(CollectionLink, _super); 132 | function CollectionLink(collection, record) { 133 | var _this = _super.call(this, Boolean(collection.get(record))) || this; 134 | _this.collection = collection; 135 | _this.record = record; 136 | return _this; 137 | } 138 | CollectionLink.prototype.set = function (x) { 139 | this.collection.toggle(this.record, x); 140 | }; 141 | return CollectionLink; 142 | }(Link)); 143 | //# sourceMappingURL=link.js.map -------------------------------------------------------------------------------- /lib/react-mvx/link.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/react-mvx/link.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/react-mvx/valuelink/helpers.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Select appropriate helpers function for particular value type. 3 | */ 4 | export interface IterableLink { 5 | value: any; 6 | at(key: number | string): any; 7 | } 8 | export declare type Iterator = (link: any, key: string | number) => any; 9 | export interface Helper { 10 | map(link: IterableLink, iterator: Iterator): any[]; 11 | clone(obj: any): any; 12 | remove(obj: any, key: string | number): any; 13 | } 14 | export declare function helpers(value: any): Helper; 15 | export declare const objectHelpers: Helper; 16 | export declare const arrayHelpers: Helper; 17 | -------------------------------------------------------------------------------- /lib/react-mvx/valuelink/helpers.js: -------------------------------------------------------------------------------- 1 | var ArrayProto = Array.prototype, ObjectProto = Object.prototype; 2 | export function helpers(value) { 3 | if (value && typeof value === 'object') { 4 | switch (Object.getPrototypeOf(value)) { 5 | case ArrayProto: return arrayHelpers; 6 | case ObjectProto: return objectHelpers; 7 | } 8 | } 9 | return dummyHelpers; 10 | } 11 | // Do nothing for types other than Array and plain Object. 12 | var dummyHelpers = { 13 | clone: function (value) { return value; }, 14 | map: function (link, fun) { return []; }, 15 | remove: function (value) { return value; } 16 | }; 17 | // `map` and `clone` for plain JS objects 18 | export var objectHelpers = { 19 | // Map through the link to object 20 | map: function (link, iterator) { 21 | var mapped = []; 22 | for (var key in link.value) { 23 | var element = iterator(link.at(key), key); 24 | element === void 0 || (mapped.push(element)); 25 | } 26 | return mapped; 27 | }, 28 | remove: function (object, key) { 29 | delete object[key]; 30 | return object; 31 | }, 32 | // Shallow clone plain JS object 33 | clone: function (object) { 34 | var cloned = {}; 35 | for (var key in object) { 36 | cloned[key] = object[key]; 37 | } 38 | return cloned; 39 | } 40 | }; 41 | // `map` and `clone` helpers for arrays. 42 | export var arrayHelpers = { 43 | // Shallow clone array 44 | clone: function (array) { 45 | return array.slice(); 46 | }, 47 | remove: function (array, i) { 48 | array.splice(i, 1); 49 | return array; 50 | }, 51 | // Map through the link to array 52 | map: function (link, iterator) { 53 | var length = link.value.length, mapped = Array(length); 54 | for (var i = 0, j = 0; i < length; i++) { 55 | var y = iterator(link.at(i), i); 56 | y === void 0 || (mapped[j++] = y); 57 | } 58 | mapped.length === j || (mapped.length = j); 59 | return mapped; 60 | } 61 | }; 62 | //# sourceMappingURL=helpers.js.map -------------------------------------------------------------------------------- /lib/react-mvx/valuelink/helpers.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/react-mvx/valuelink/helpers.ts"],"names":[],"mappings":"AAgBA,IAAM,UAAU,GAAG,KAAK,CAAC,SAAS,EAC5B,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC;AAErC,MAAM,kBAAmB,KAAK;IAC1B,EAAE,CAAA,CAAE,KAAK,IAAI,OAAO,KAAK,KAAK,QAAS,CAAC,CAAA,CAAC;QACrC,MAAM,CAAA,CAAE,MAAM,CAAC,cAAc,CAAE,KAAK,CAAG,CAAC,CAAA,CAAC;YACrC,KAAK,UAAU,EAAI,MAAM,CAAC,YAAY,CAAC;YACvC,KAAK,WAAW,EAAG,MAAM,CAAC,aAAa,CAAC;QAC5C,CAAC;IACL,CAAC;IAED,MAAM,CAAC,YAAY,CAAC;AACxB,CAAC;AAED,0DAA0D;AAC1D,IAAM,YAAY,GAAY;IAC1B,KAAK,YAAE,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/B,GAAG,YAAE,IAAmB,EAAE,GAAG,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7C,MAAM,YAAE,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;CACnC,CAAC;AAEF,yCAAyC;AACzC,MAAM,CAAC,IAAM,aAAa,GAAY;IAClC,iCAAiC;IACjC,GAAG,EAAH,UAAK,IAAmB,EAAE,QAAmB;QACzC,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,GAAG,CAAA,CAAE,IAAI,GAAG,IAAI,IAAI,CAAC,KAAM,CAAC,CAAA,CAAC;YACzB,IAAM,OAAO,GAAG,QAAQ,CAAE,IAAI,CAAC,EAAE,CAAE,GAAG,CAAE,EAAE,GAAG,CAAE,CAAC;YAChD,OAAO,KAAK,KAAK,CAAC,IAAI,CAAE,MAAM,CAAC,IAAI,CAAE,OAAO,CAAE,CAAE,CAAC;QACrD,CAAC;QAED,MAAM,CAAC,MAAM,CAAC;IAClB,CAAC;IAED,MAAM,EAAN,UAAQ,MAAW,EAAE,GAAY;QAC7B,OAAO,MAAM,CAAE,GAAG,CAAE,CAAC;QACrB,MAAM,CAAC,MAAM,CAAC;IAClB,CAAC;IAEA,gCAAgC;IACjC,KAAK,EAAL,UAAO,MAAW;QACd,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,GAAG,CAAA,CAAE,IAAI,GAAG,IAAI,MAAO,CAAC,CAAA,CAAC;YACrB,MAAM,CAAE,GAAG,CAAE,GAAG,MAAM,CAAE,GAAG,CAAE,CAAC;QAClC,CAAC;QAED,MAAM,CAAC,MAAM,CAAC;IAClB,CAAC;CACJ,CAAC;AAEF,wCAAwC;AACxC,MAAM,CAAC,IAAM,YAAY,GAAY;IACjC,sBAAsB;IACtB,KAAK,EAAL,UAAO,KAAa;QAChB,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,MAAM,EAAN,UAAQ,KAAa,EAAE,CAAU;QAC7B,KAAK,CAAC,MAAM,CAAE,CAAC,EAAE,CAAC,CAAE,CAAC;QACrB,MAAM,CAAC,KAAK,CAAC;IACjB,CAAC;IAED,gCAAgC;IAChC,GAAG,EAAH,UAAK,IAAmB,EAAE,QAAmB;QACzC,IAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAC1B,MAAM,GAAG,KAAK,CAAE,MAAM,CAAE,CAAC;QAE/B,GAAG,CAAA,CAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,IAAM,CAAC,GAAG,QAAQ,CAAE,IAAI,CAAC,EAAE,CAAE,CAAC,CAAE,EAAE,CAAC,CAAE,CAAC;YACtC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAE,MAAM,CAAE,CAAC,EAAE,CAAE,GAAG,CAAC,CAAE,CAAC;QAC1C,CAAC;QAED,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,CAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;QAE7C,MAAM,CAAC,MAAM,CAAC;IAClB,CAAC;CACJ,CAAC"} -------------------------------------------------------------------------------- /lib/react-mvx/valuelink/link.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare type Transform = (value: T, event?: {}) => T; 3 | export declare type EventHandler = (event: {}) => void; 4 | export interface Validator { 5 | (value: T): boolean; 6 | error?: any; 7 | } 8 | export declare type LinksCache = { 9 | [K in X]: Link; 10 | }; 11 | export declare abstract class Link { 12 | value: T; 13 | static state: (component: React.Component, key: K) => Link; 14 | static all: (component: React.Component, ..._keys: K[]) => LinksCache; 15 | static value(value: T, set: (x: T) => void): Link; 16 | constructor(value: T); 17 | error: any; 18 | readonly validationError: any; 19 | abstract set(x: T): void; 20 | onChange(handler: (x: T) => void): Link; 21 | readonly props: { 22 | checked: (T & true) | (T & false); 23 | onChange: (e: any) => void; 24 | } | { 25 | value: T; 26 | onChange: (e: any) => void; 27 | }; 28 | requestChange(x: T): void; 29 | update(transform: Transform, e?: Object): void; 30 | pipe(handler: Transform): Link; 31 | action(transform: Transform): EventHandler; 32 | equals(truthyValue: T): Link; 33 | enabled(defaultValue?: T): Link; 34 | contains(this: Link, element: E): Link; 35 | push(this: Link, ...args: E[]): void; 36 | unshift(this: Link, ...args: E[]): void; 37 | splice(start: number, deleteCount?: number): any; 38 | map(this: Link, iterator: (link: LinkAt, idx: number) => Z): Z[]; 39 | map(this: Link<{ 40 | [key: string]: E; 41 | }>, iterator: (link: LinkAt, idx: string) => Z): Z[]; 42 | removeAt(this: Link, key: number): void; 43 | removeAt(this: Link<{ 44 | [key: string]: E; 45 | }>, key: string): void; 46 | at(this: Link, key: number): LinkAt; 47 | at(key: K): LinkAt; 48 | clone(): T; 49 | pick(...keys: K[]): { 50 | [P in K]: Link; 51 | }; 52 | /** 53 | * Validate link with validness predicate and optional custom error object. Can be chained. 54 | */ 55 | check(whenValid: Validator, error?: any): this; 56 | } 57 | export declare class CustomLink extends Link { 58 | set(x: any): void; 59 | constructor(value: T, set: (x: T) => void); 60 | } 61 | export declare class CloneLink extends Link { 62 | set(x: any): void; 63 | constructor(parent: Link, set: (x: T) => void); 64 | } 65 | export declare class EqualsLink extends Link { 66 | parent: Link; 67 | truthyValue: any; 68 | constructor(parent: Link, truthyValue: any); 69 | set(x: boolean): void; 70 | } 71 | export declare class EnabledLink extends Link { 72 | parent: Link; 73 | defaultValue: any; 74 | constructor(parent: Link, defaultValue: any); 75 | set(x: boolean): void; 76 | } 77 | export declare class ContainsLink extends Link { 78 | parent: Link; 79 | element: any; 80 | constructor(parent: Link, element: any); 81 | set(x: boolean): void; 82 | } 83 | /** 84 | * Link to array or object element enclosed in parent link. 85 | * Performs purely functional update of the parent, shallow copying its value on `set`. 86 | */ 87 | export declare class LinkAt extends Link { 88 | private parent; 89 | key: K; 90 | constructor(parent: Link, key: K); 91 | remove(): void; 92 | set(x: E): void; 93 | } 94 | -------------------------------------------------------------------------------- /lib/view-element.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Component } from './react-mvx'; 3 | export interface BackboneViewProps { 4 | View: any; 5 | options: object; 6 | className?: string; 7 | } 8 | export default class BackboneView extends Component { 9 | shouldComponentUpdate(next: any): boolean; 10 | view: any; 11 | hasUnsavedChanges(): any; 12 | root: any; 13 | saveRef: (element: any) => void; 14 | render(): React.DetailedReactHTMLElement<{ 15 | ref: (element: any) => void; 16 | className: string; 17 | }, any>; 18 | componentDidMount(): void; 19 | componentDidUpdate(): void; 20 | componentWillUnmount(): void; 21 | _mountView(): void; 22 | _dispose(): void; 23 | } 24 | -------------------------------------------------------------------------------- /lib/view-element.js: -------------------------------------------------------------------------------- 1 | import * as tslib_1 from "tslib"; 2 | import React, { Component } from './react-mvx'; 3 | import { tools } from 'type-r'; 4 | var notEqual = tools.notEqual; 5 | var BackboneView = (function (_super) { 6 | tslib_1.__extends(BackboneView, _super); 7 | function BackboneView() { 8 | var _this = _super !== null && _super.apply(this, arguments) || this; 9 | _this.saveRef = function (element) { 10 | _this.root = element; 11 | }; 12 | return _this; 13 | } 14 | BackboneView.prototype.shouldComponentUpdate = function (next) { 15 | var props = this.props; 16 | return next.View !== props.View || notEqual(next.options, props.options); 17 | }; 18 | BackboneView.prototype.hasUnsavedChanges = function () { 19 | var view = this.view; 20 | return view && (typeof view.hasUnsavedChanges === 'function' ? view.hasUnsavedChanges() : view.hasUnsavedChanges); 21 | }; 22 | BackboneView.prototype.render = function () { 23 | return React.createElement('div', { 24 | ref: this.saveRef, 25 | className: this.props.className 26 | }); 27 | }; 28 | BackboneView.prototype.componentDidMount = function () { 29 | this._mountView(); 30 | }; 31 | BackboneView.prototype.componentDidUpdate = function () { 32 | this._dispose(); 33 | this._mountView(); 34 | }; 35 | BackboneView.prototype.componentWillUnmount = function () { 36 | this._dispose(); 37 | }; 38 | BackboneView.prototype._mountView = function () { 39 | var el = this.root, p = this.props; 40 | var view = this.view = p.options ? new p.View(p.options) : new p.View(); 41 | el.appendChild(view.el); 42 | view.render(); 43 | }; 44 | BackboneView.prototype._dispose = function () { 45 | var view = this.view; 46 | if (view) { 47 | if (view.dispose) { 48 | view.dispose(); 49 | } 50 | else { 51 | view.stopListening(); 52 | view.off(); 53 | } 54 | this.root.innerHTML = ""; 55 | this.view = null; 56 | } 57 | }; 58 | return BackboneView; 59 | }(Component)); 60 | export default BackboneView; 61 | //# sourceMappingURL=view-element.js.map -------------------------------------------------------------------------------- /lib/view-element.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"view-element.js","sourceRoot":"","sources":["../src/view-element.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAU,SAAS,EAAE,MAAM,aAAa,CAAA;AAEtD,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAEtB,IAAA,yBAAQ,CAAW;AAQ3B;IAA0C,wCAAoC;IAA9E;QAAA,qEAkEC;QAjDG,aAAO,GAAG,UAAA,OAAO;YACb,KAAI,CAAC,IAAI,GAAG,OAAO,CAAC;QACxB,CAAC,CAAA;;IA+CL,CAAC;IAjEG,4CAAqB,GAArB,UAAuB,IAAI;QACvB,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAE,CAAC;IAC/E,CAAC;IAID,wCAAiB,GAAjB;QACI,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAErB,MAAM,CAAC,IAAI,IAAI,CACX,OAAO,IAAI,CAAC,iBAAiB,KAAK,UAAU,GAAG,IAAI,CAAC,iBAAiB,EAAE,GAAG,IAAI,CAAC,iBAAiB,CACnG,CAAC;IACN,CAAC;IAOD,6BAAM,GAAN;QACI,MAAM,CAAC,KAAK,CAAC,aAAa,CAAE,KAAK,EAAE;YAC/B,GAAG,EAAS,IAAI,CAAC,OAAO;YACxB,SAAS,EAAG,IAAI,CAAC,KAAK,CAAC,SAAS;SACnC,CAAE,CAAC;IACR,CAAC;IAED,wCAAiB,GAAjB;QACI,IAAI,CAAC,UAAU,EAAE,CAAC;IACtB,CAAC;IAED,yCAAkB,GAAlB;QACI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,IAAI,CAAC,UAAU,EAAE,CAAC;IACtB,CAAC;IAED,2CAAoB,GAApB;QACI,IAAI,CAAC,QAAQ,EAAE,CAAC;IACpB,CAAC;IAED,iCAAU,GAAV;QACI,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,EACd,CAAC,GAAI,IAAI,CAAC,KAAK,CAAC;QAEpB,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,IAAI,CAAE,CAAC,CAAC,OAAO,CAAE,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QAE1E,EAAE,CAAC,WAAW,CAAE,IAAI,CAAC,EAAE,CAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;IAClB,CAAC;IAED,+BAAQ,GAAR;QACI,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACrB,EAAE,CAAA,CAAE,IAAK,CAAC,CAAA,CAAC;YACP,EAAE,CAAA,CAAE,IAAI,CAAC,OAAQ,CAAC,CAAA,CAAC;gBACf,IAAI,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC;YACD,IAAI,CAAA,CAAC;gBACD,IAAI,CAAC,aAAa,EAAE,CAAC;gBACrB,IAAI,CAAC,GAAG,EAAE,CAAC;YACf,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,GAAI,IAAI,CAAC;QACtB,CAAC;IACL,CAAC;IACL,mBAAC;AAAD,CAAC,AAlED,CAA0C,SAAS,GAkElD"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestedreact", 3 | "version": "2.1.1", 4 | "main": "dist/index.js", 5 | "lib": "lib/index.js", 6 | "description": "Advanced models, state management, and data binding solution for React", 7 | "homepage": "https://github.com/Volicon/react-backbone.glue", 8 | "keywords": [ 9 | "backbone", 10 | "model", 11 | "view", 12 | "valuelink", 13 | "data", 14 | "binding", 15 | "nestedtypes", 16 | "react", 17 | "react-component" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/Volicon/react-backbone.glue.git" 22 | }, 23 | "author": "Vlad Balin ", 24 | "contributors": [], 25 | "dependencies": { 26 | "tslib": "^1.8.0" 27 | }, 28 | "peerDependencies": { 29 | "react": "*", 30 | "prop-types": "*", 31 | "react-dom": "*", 32 | "jquery": "*", 33 | "underscore": "*", 34 | "nestedtypes": "^2.1.0-rc00" 35 | }, 36 | "devDependencies": { 37 | "@types/jquery": "^2.0.48", 38 | "@types/prop-types": "^15.5.2", 39 | "@types/react": "^16.0.25", 40 | "jquery": "*", 41 | "nestedtypes": "^2.1.1", 42 | "prop-types": "^15.6.0", 43 | "react": "^16.2.0", 44 | "react-dom": "^16.2.0", 45 | "rollup": "^0.50.0", 46 | "rollup-plugin-node-resolve": "^3.0.0", 47 | "rollup-plugin-replace": "^2.0.0", 48 | "rollup-plugin-uglify": "^2.0.1", 49 | "typescript": "<2.4.0", 50 | "underscore": "*" 51 | }, 52 | "files": [ 53 | "dist", 54 | "lib", 55 | "src", 56 | "tags.js", 57 | "tags.jsx" 58 | ], 59 | "license": "MIT", 60 | "scripts": { 61 | "build": "npm run pull && npm run compile", 62 | "pull": "git submodule update --remote && cp ./submodules/React-MVx/tags.* . && cp -R ./submodules/React-MVx/src/* ./src/react-mvx", 63 | "compile": "./node_modules/.bin/tsc && ./node_modules/.bin/rollup --config && ./node_modules/.bin/rollup --config ./rollup.config.minify.js", 64 | "deploy": "cp -R ./dist ./examples/*/node_modules/nestedreact", 65 | "deploy:example": "bash -c 'target=$0/node_modules/nestedreact && mkdir -p $target && cp -R ./dist/ $target && cp ./dist/index.min.js.map $target && ((unlink $target/package.json || true) >/dev/null 2>&1) && cp ./tags.js* $target'", 66 | "deploy:examples": "for exmpl in `ls -d ./examples/*`; do npm run deploy:example $exmpl; done" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import replace from 'rollup-plugin-replace'; 3 | 4 | export default { 5 | input : 'lib/index.js', 6 | context: 'this', //used to silence warnings 7 | output : { 8 | file : 'dist/index.js', 9 | format : 'umd', 10 | name : 'ReactMVx', 11 | exports: 'named', 12 | globals: { 13 | react: 'React', 14 | 'nestedtypes': 'Nested', 15 | 'react-dom': 'ReactDOM', 16 | 'prop-types': 'PropTypes' 17 | } 18 | }, 19 | plugins: [ 20 | resolve(), 21 | replace({ //workaround for webpack `externals` configuration 22 | exclude: 'node_modules/**', 23 | 'type-r': 'nestedtypes' 24 | }) 25 | ], 26 | sourcemap: true, 27 | external: [ 28 | 'react', 29 | 'nestedtypes', 30 | 'react-dom', 31 | 'prop-types' 32 | ] 33 | }; -------------------------------------------------------------------------------- /rollup.config.minify.js: -------------------------------------------------------------------------------- 1 | import uglify from 'rollup-plugin-uglify'; 2 | 3 | import config from './rollup.config' 4 | 5 | config.output.file = 'dist/index.min.js'; 6 | config.plugins.push(uglify()); 7 | export default config; -------------------------------------------------------------------------------- /src/component-view.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as ReactDOM from 'react-dom' 3 | import { tools } from 'type-r' 4 | 5 | declare global { 6 | interface Window { 7 | Page : { 8 | forceResize(); 9 | } 10 | } 11 | } 12 | 13 | window.Page || ( window.Page = { forceResize(){} } ); 14 | 15 | export default function use( View ){ 16 | const dispose = View.prototype.dispose || function(){}, 17 | { setElement } = View.prototype; 18 | 19 | const ComponentView = View.extend( { 20 | reactClass : null, 21 | props : {}, 22 | element : null, 23 | 24 | initialize( props ){ 25 | // memorise arguments to pass to React 26 | this.options = props || {}; 27 | }, 28 | 29 | setElement(){ 30 | this.unmountComponent( true ); 31 | return setElement.apply( this, arguments ); 32 | }, 33 | 34 | // cached instance of react component... 35 | component : null, 36 | prevState : null, 37 | 38 | resize(){ 39 | window.Page.forceResize(); 40 | }, 41 | 42 | render(){ 43 | const options = this.prevState ? tools.fastAssign( { __keepState : this.prevState }, this.options ) : this.options, 44 | element = React.createElement( this.reactClass, options ), 45 | component = ReactDOM.render( element, this.el ); 46 | 47 | this.component || this.mountComponent( component ); 48 | }, 49 | 50 | mountComponent( component ){ 51 | this.component = component; 52 | this.prevState = null; 53 | 54 | component.trigger && this.listenTo( component, 'all', function(){ 55 | this.trigger.apply( this, arguments ); 56 | } ); 57 | }, 58 | 59 | unmountComponent( keepModel ){ 60 | var component = this.component; 61 | 62 | if( component ){ 63 | this.prevState = component.state; 64 | 65 | if( component.trigger ){ 66 | this.stopListening( component ); 67 | } 68 | 69 | component._preventDispose = Boolean( keepModel ); 70 | 71 | ReactDOM.unmountComponentAtNode( this.el ); 72 | this.component = null; 73 | } 74 | }, 75 | 76 | dispose(){ 77 | this.unmountComponent(); 78 | return dispose.apply( this, arguments ); 79 | } 80 | } ); 81 | 82 | Object.defineProperty( ComponentView.prototype, 'model', { 83 | get(){ 84 | this.component || this.render(); 85 | return this.component && this.component.state; 86 | } 87 | } ); 88 | 89 | return ComponentView; 90 | } 91 | -------------------------------------------------------------------------------- /src/createClass.ts: -------------------------------------------------------------------------------- 1 | import { Component } from './react-mvx' 2 | 3 | const dontAutobind = [ 4 | 'State', 'Store', 'constructor', 5 | 'componentWillMount','componentDidMount','componentWillReceiveProps','shouldComponentUpdate', 6 | 'componentWillUpdate','componentDidUpdate','componentWillUnmount', 7 | 'render', 'getDefaultProps', 'getChildContext' 8 | ]; 9 | 10 | /** 11 | * ES5 components definition factory 12 | */ 13 | export default function createClass< P, S>( { statics, ...a_spec } : React.ComponentSpec ) : React.ClassicComponentClass

    { 14 | // Gather all methods to pin them to `this` later. 15 | const methods = []; 16 | 17 | const Subclass : any = Component.extend({ 18 | // Override constructor to autobind all the methods... 19 | constructor(){ 20 | Component.apply( this, arguments ); 21 | 22 | for( let method of methods ){ 23 | this[ method ] = this[ method ].bind( this ); 24 | } 25 | }, 26 | ...a_spec 27 | }, statics ); 28 | 29 | // Need to bind methods from mixins as well, so populate it here. 30 | const Proto = Subclass.prototype; 31 | for( let key in Proto ){ 32 | if( Proto.hasOwnProperty( key ) && dontAutobind.indexOf( key ) === -1 && typeof Proto[ key ] === 'function' ){ 33 | methods.push( key ); 34 | } 35 | } 36 | 37 | return Subclass; 38 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Re-export react-mvx 2 | import ReactMVx from './react-mvx' 3 | const NestedReact = Object.create( ReactMVx ); 4 | export default NestedReact; 5 | export * from './react-mvx' 6 | 7 | // NestedReact backward compatibility layer 8 | import * as ReactDOM from 'react-dom' 9 | import Nested, { View, Record } from 'type-r' 10 | import * as PropTypes from 'prop-types' 11 | 12 | import subview from './view-element' 13 | NestedReact.subview = subview; 14 | export { subview } 15 | 16 | import use from './component-view' 17 | import createClass from './createClass' 18 | 19 | Object.defineProperty( NestedReact, 'createClass', { value : createClass} ); 20 | Object.defineProperty( NestedReact, 'PropTypes', { value : PropTypes } ); 21 | export { PropTypes, createClass }; 22 | 23 | let BaseView; 24 | 25 | // export hook to override base View class used... 26 | export function useView( View ){ 27 | BaseView = use( View ); 28 | } 29 | 30 | const { onDefine } = NestedReact.Component; 31 | 32 | NestedReact.Component.onDefine = function( definitions, BaseClass ){ 33 | this.View = BaseView.extend( { reactClass : this } ); 34 | 35 | return onDefine.call( this, definitions, BaseClass ); 36 | } 37 | 38 | // Deprecated API for backward compatibility 39 | const RecordProto : any = Record.prototype; 40 | RecordProto.getLink = RecordProto.linkAt; 41 | RecordProto.deepLink = RecordProto.linkPath; 42 | 43 | const CollectionProto : any = Record.Collection.prototype; 44 | CollectionProto.hasLink = CollectionProto.linkContains; 45 | 46 | useView( View ); 47 | 48 | // Extend react components to have backbone-style jquery accessors 49 | const BackboneViewProps = { 50 | el : { get : function(){ return ReactDOM.findDOMNode( this ) } }, 51 | $el : { get : function(){ return Nested.$( this.el ) } }, 52 | $ : { value : function( sel ){ return this.$el.find( sel ) } } 53 | }; 54 | 55 | Object.defineProperties( NestedReact.Component.prototype, BackboneViewProps ); 56 | -------------------------------------------------------------------------------- /src/react-mvx/Link.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Import ValueLink library 3 | * Define value links binding mixins to the Record and Collection 4 | */ 5 | 6 | import { Mixable, MixinsState, Record } from 'type-r' 7 | import { Link } from './valuelink/link' 8 | 9 | export default Link; 10 | 11 | Mixable.mixins.populate( Link as any ); 12 | 13 | interface LinksCache { 14 | [ key : string ] : RecordLink 15 | } 16 | 17 | /** 18 | * Record 19 | */ 20 | MixinsState.get( Record ).merge([{ 21 | // Link to the record's attribute by its key. 22 | linkAt( key : string ) : RecordLink { 23 | return cacheLink( getLinksCache( this ), this, key ); 24 | }, 25 | 26 | // Link to the attribute of the record's tree by symbolic path. 27 | linkPath( path : string, options? : {} ) : RecordDeepLink { 28 | return new RecordDeepLink( this, path, options ) 29 | }, 30 | 31 | // Link all (or listed) attributes and return links cache. 32 | linkAll() : LinksCache { 33 | const links = getLinksCache( this ); 34 | 35 | if( arguments.length ){ 36 | for( let i = 0; i < arguments.length; i++ ){ 37 | cacheLink( links, this, arguments[ i ] ); 38 | } 39 | } 40 | else{ 41 | const { attributes } = this; 42 | 43 | for( let key in attributes ){ 44 | attributes[ key ] === void 0 || cacheLink( links, this, key ); 45 | } 46 | } 47 | 48 | return links; 49 | } 50 | }]); 51 | 52 | /** 53 | * Link to Type-R's record attribute. 54 | * Strict evaluation of value, lazy evaluation of validation error. 55 | * Links are cached in the records 56 | */ 57 | class RecordLink extends Link< any > { 58 | constructor( public record, public attr, value ){ 59 | super( value ); 60 | } 61 | 62 | set( x ){ 63 | this.record[ this.attr ] = x; 64 | } 65 | 66 | _error? : any 67 | 68 | get error(){ 69 | return this._error === void 0 ? 70 | this.record.getValidationError( this.attr ) : 71 | this._error; 72 | } 73 | 74 | set error( x ){ 75 | this._error = x; 76 | } 77 | } 78 | 79 | class RecordDeepLink extends Link< any > { 80 | constructor( public record, public path, public options ){ 81 | super( record.deepGet( path ) ); 82 | } 83 | 84 | _error? : any 85 | 86 | get error(){ 87 | if( this._error === void 0 ){ 88 | this._error = this.record.deepValidationError( this.path ) || null; 89 | } 90 | 91 | return this._error; 92 | } 93 | 94 | set error( x ){ 95 | this._error = x; 96 | } 97 | 98 | get _changeToken(){ 99 | return this.record._changeToken; 100 | } 101 | 102 | set( x ){ 103 | this.record.deepSet( this.path, x, this.options ); 104 | } 105 | } 106 | 107 | function getLinksCache( record : Record ) : LinksCache { 108 | return ( record )._links || ( ( record )._links = new record.AttributesCopy( {} ) ); 109 | } 110 | 111 | function cacheLink( links : LinksCache, record : Record, key : string ) : RecordLink { 112 | var cached = links[ key ], 113 | value = record[ key ]; 114 | 115 | return cached && cached.value === value ? cached 116 | : links[ key ] = new RecordLink( record, key, value ); 117 | } 118 | 119 | /*********************************** 120 | * Collection 121 | */ 122 | MixinsState.get( Record.Collection ).merge([{ 123 | // Boolean link to the record's presence in the collection 124 | linkContains( record : Record ){ 125 | return new CollectionLink( this, record ); 126 | }, 127 | 128 | // Link to collection's property 129 | linkAt( prop : string ){ 130 | return Link.value( this[ prop ], x => this[ prop ] = x ); 131 | } 132 | }]); 133 | 134 | /** 135 | * Boolean link to presence of NestedType's record in collection. 136 | * Strict evaluation of value, no error. 137 | * Safe implementation of _changeToken. 138 | */ 139 | class CollectionLink extends Link< boolean >{ 140 | constructor( public collection, public record ){ 141 | super( Boolean( collection.get( record ) ) ); 142 | } 143 | 144 | set( x ){ 145 | this.collection.toggle( this.record, x ); 146 | } 147 | } -------------------------------------------------------------------------------- /src/react-mvx/component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * React-Type-R component base class. Overrides React component. 3 | */ 4 | 5 | import * as React from 'react' 6 | import { Record, Store, CallbacksByEvents, mixinRules, define, mixins, definitions, tools, Messenger } from 'type-r' 7 | import Link from './link' 8 | import onDefine, { TypeSpecs, EmptyPropsChangeTokensCtor } from './define' 9 | 10 | @define({ 11 | PropsChangeTokens : EmptyPropsChangeTokensCtor 12 | }) 13 | @definitions({ 14 | // Definitions to be extracted from mixins and statics and passed to `onDefine()` 15 | state : mixinRules.merge, 16 | State : mixinRules.value, 17 | store : mixinRules.merge, 18 | Store : mixinRules.value, 19 | props : mixinRules.merge, 20 | context : mixinRules.merge, 21 | childContext : mixinRules.merge, 22 | pureRender : mixinRules.protoValue 23 | }) 24 | @mixinRules( { 25 | // Apply old-school React mixin rules. 26 | componentWillMount : mixinRules.classLast, 27 | componentDidMount : mixinRules.classLast, 28 | componentWillReceiveProps : mixinRules.classLast, 29 | componentWillUpdate : mixinRules.classLast, 30 | componentDidUpdate : mixinRules.classLast, 31 | componentWillUnmount : mixinRules.classFirst, 32 | 33 | // And a bit more to fix inheritance quirks. 34 | shouldComponentUpdate : mixinRules.some, 35 | getChildContext : mixinRules.defaults 36 | } ) 37 | // Component can send and receive events... 38 | @mixins( Messenger ) 39 | export class Component extends React.Component { 40 | cid : string 41 | 42 | static state? : TypeSpecs | typeof Record 43 | static store? : TypeSpecs | typeof Store 44 | static props? : TypeSpecs 45 | static context? : TypeSpecs 46 | static childContext? : TypeSpecs 47 | static pureRender? : boolean 48 | 49 | private _disposed : boolean 50 | private static propTypes: any; 51 | private static defaultProps: any; 52 | private static contextTypes : any; 53 | private static childContextTypes : any; 54 | 55 | private PropsChangeTokens : Function 56 | 57 | static extend : ( spec : object, statics? : object ) => Component< any > 58 | 59 | linkAt( key : string ) : Link { 60 | // Quick and dirty hack to suppres type error - refactor later. 61 | return ( this.state as any ).linkAt( key ); 62 | } 63 | 64 | linkAll( ...keys : string[] ) : { [ key : string ] : Link } 65 | linkAll(){ 66 | // Quick and dirty hack to suppres type error - refactor later. 67 | const { state } = this as any; 68 | return state.linkAll.apply( state, arguments ); 69 | } 70 | 71 | linkPath( path : string ) : Link { 72 | return ( this.state as any ).linkPath( path ); 73 | } 74 | 75 | get links(){ 76 | return ( this.state as any )._links; 77 | } 78 | 79 | static onDefine = onDefine; 80 | 81 | readonly state : S 82 | readonly store? : Store 83 | 84 | constructor( props?, context? ){ 85 | super( props, context ); 86 | this._initializeState(); 87 | } 88 | 89 | _initializeState(){ 90 | ( this as any ).state = null; 91 | } 92 | 93 | assignToState( x, key : string ){ 94 | this.state.assignFrom({ [ key ] : x }); 95 | } 96 | 97 | isMounted : () => boolean 98 | 99 | // Messenger methods... 100 | on : ( events : string | CallbacksByEvents, callback, context? ) => this 101 | once : ( events : string | CallbacksByEvents, callback, context? ) => this 102 | off : ( events? : string | CallbacksByEvents, callback?, context? ) => this 103 | trigger : (name : string, a?, b?, c?, d?, e? ) => this 104 | 105 | stopListening : ( source? : Messenger, a? : string | CallbacksByEvents, b? : Function ) => this 106 | listenTo : ( source : Messenger, a : string | CallbacksByEvents, b? : Function ) => this 107 | listenToOnce : ( source : Messenger, a : string | CallbacksByEvents, b? : Function ) => this 108 | 109 | dispose : () => void 110 | 111 | componentWillUnmount(){ 112 | this.dispose(); 113 | } 114 | 115 | /** 116 | * Performs transactional update for both props and state. 117 | * Suppress updates during the transaction, and force update aftewards. 118 | * Wrapping the sequence of changes in a transactions guarantees that 119 | * React component will be updated _after_ all the changes to the 120 | * both props and local state are applied. 121 | */ 122 | transaction( fun : ( state? : Record ) => void ){ 123 | var shouldComponentUpdate = this.shouldComponentUpdate, 124 | isRoot = shouldComponentUpdate !== returnFalse; 125 | 126 | if( isRoot ){ 127 | this.shouldComponentUpdate = returnFalse; 128 | } 129 | 130 | const { state, store } = this, 131 | withStore = store ? state => store.transaction( () => fun( state ) ) : fun; 132 | 133 | state ? state.transaction( withStore ) : withStore( state ); 134 | 135 | if( isRoot ){ 136 | this.shouldComponentUpdate = shouldComponentUpdate; 137 | this.asyncUpdate(); 138 | } 139 | } 140 | 141 | // Safe version of the forceUpdate suitable for asynchronous callbacks. 142 | asyncUpdate(){ 143 | this.shouldComponentUpdate === returnFalse || this._disposed || this.forceUpdate(); 144 | } 145 | } 146 | 147 | function returnFalse(){ return false; } 148 | 149 | // Looks like React guys _really_ want to deprecate it. But no way. 150 | // We will work around their attempt. 151 | Object.defineProperty( Component.prototype, 'isMounted', { 152 | value : function isMounted(){ 153 | return !this._disposed; 154 | } 155 | }) -------------------------------------------------------------------------------- /src/react-mvx/define/common.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComponentClass as ReactComponentClass, 3 | Component 4 | } from 'react' 5 | 6 | import { MixableConstructor, Messenger } from 'type-r' 7 | 8 | export interface ComponentClass extends ReactComponentClass, MixableConstructor { 9 | prototype : Proto & ComponentProto 10 | } 11 | 12 | export type ComponentProto = Component & Messenger -------------------------------------------------------------------------------- /src/react-mvx/define/context.ts: -------------------------------------------------------------------------------- 1 | import { compileSpecs, TypeSpecs } from './typeSpecs' 2 | import { tools } from 'type-r' 3 | import { ComponentClass } from './common' 4 | 5 | export interface ContextDefinition { 6 | context : TypeSpecs 7 | childContext : TypeSpecs 8 | } 9 | 10 | export interface ContextProto { 11 | _context : TypeSpecs 12 | _childContext : TypeSpecs 13 | } 14 | 15 | export default function onDefine( this : ComponentClass, { context, childContext } : ContextDefinition, BaseClass : ComponentClass ){ 16 | const { prototype } = this; 17 | 18 | if( context ){ 19 | // Merge in inherited members... 20 | prototype._context = tools.defaults( context, BaseClass.prototype._context || {} ); 21 | 22 | // Compile to propTypes... 23 | this.contextTypes = compileSpecs( context ).propTypes; 24 | } 25 | 26 | if( childContext ){ 27 | prototype._childContext = tools.defaults( childContext, BaseClass.prototype._childContext ); 28 | this.childContextTypes = compileSpecs( childContext ).propTypes; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/react-mvx/define/index.ts: -------------------------------------------------------------------------------- 1 | import { ComponentClass } from './common' 2 | import { Messenger } from 'type-r' 3 | 4 | import onDefineStore, { StoreDefinition, StoreProto } from './store' 5 | import onDefineState, { StateDefinition, StateProto } from './state' 6 | import onDefineContext, { ContextDefinition, ContextProto } from './context' 7 | import onDefineProps, { PropsDefinition, PropsProto } from './props' 8 | 9 | export interface ComponentDefinition extends StoreDefinition, StateDefinition, ContextDefinition, PropsDefinition {} 10 | export interface ComponentProto extends StoreProto, StateProto, ContextProto, PropsProto {} 11 | 12 | export default function onDefine( this : ComponentClass, definition : ComponentDefinition, BaseClass : ComponentClass ){ 13 | // Initialize mixins placeholder... 14 | onDefineStore.call( this, definition, BaseClass ); 15 | onDefineState.call( this, definition, BaseClass ); 16 | onDefineContext.call( this, definition, BaseClass ); 17 | onDefineProps.call( this, definition, BaseClass ); 18 | 19 | Messenger.onDefine.call( this, definition, BaseClass ); 20 | }; 21 | 22 | export { Node, Element, TypeSpecs } from './typeSpecs' 23 | export { EmptyPropsChangeTokensCtor } from './pureRender' -------------------------------------------------------------------------------- /src/react-mvx/define/props.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Handle props specification and everything which is related: 3 | * - local listening to props changes 4 | * - pure render mixin 5 | */ 6 | 7 | import { compileSpecs, TypeSpecs } from './typeSpecs' 8 | import { PureRenderMixin, createChangeTokensConstructor } from './pureRender' 9 | import { tools } from 'type-r' 10 | import { ComponentClass } from './common' 11 | 12 | export interface PropsDefinition { 13 | pureRender? : boolean 14 | props : TypeSpecs 15 | } 16 | 17 | 18 | export interface PropsProto { 19 | pureRender? : boolean 20 | _props? : TypeSpecs 21 | _watchers? : any 22 | _changeHandlers? : any 23 | PropsChangeTokens? : any 24 | } 25 | 26 | export default function onDefine( this : ComponentClass, { props, pureRender } : PropsDefinition, BaseClass : ComponentClass ){ 27 | const { prototype } = this; 28 | 29 | // process props spec... 30 | if( props ){ 31 | // Merge with inherited members... 32 | prototype._props = tools.defaults( props, BaseClass.prototype._props || {} ); 33 | 34 | const { propTypes, defaults, watchers, changeHandlers } = compileSpecs( props ); 35 | this.propTypes = propTypes; 36 | 37 | if( defaults ) this.defaultProps = defaults; 38 | 39 | if( watchers ){ 40 | prototype._watchers = watchers; 41 | this.mixins.merge([ WatchersMixin ]); 42 | } 43 | 44 | if( changeHandlers ){ 45 | prototype._changeHandlers = changeHandlers; 46 | this.mixins.merge([ ChangeHandlersMixin ]); 47 | } 48 | 49 | if( prototype.pureRender ){ 50 | prototype.PropsChangeTokens = createChangeTokensConstructor( props ); 51 | } 52 | } 53 | 54 | if( pureRender ){ 55 | this.mixins.merge([ PureRenderMixin ]); 56 | } 57 | } 58 | 59 | /** 60 | * ChangeHandlers are fired in sequence upon props replacement. 61 | * Fires _after_ UI is updated. Used for managing events subscriptions. 62 | */ 63 | const ChangeHandlersMixin = { 64 | componentDidMount(){ 65 | handlePropsChanges( this, {}, this.props ); 66 | }, 67 | 68 | componentDidUpdate( prev ){ 69 | handlePropsChanges( this, prev, this.props ); 70 | }, 71 | 72 | componentWillUnmount(){ 73 | handlePropsChanges( this, this.props, {} ); 74 | } 75 | }; 76 | 77 | function handlePropsChanges( component : any, prev : object, next : object ){ 78 | const { _changeHandlers } = component; 79 | 80 | for( let name in _changeHandlers ){ 81 | if( prev[ name ] !== next[ name ] ){ 82 | for( let handler of _changeHandlers[ name ] ){ 83 | handler( next[ name ], prev[ name ], component ); 84 | } 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * Watchers works on props replacement and fires _before_ any change will be applied and UI is updated. 91 | * Fired in componentWillMount as well, which makes it a nice way to sync state from props. 92 | */ 93 | const WatchersMixin = { 94 | componentWillReceiveProps( next ){ 95 | const { _watchers, props } = this; 96 | 97 | for( let name in _watchers ){ 98 | if( next[ name ] !== props[ name ] ){ 99 | _watchers[ name ].call( this, next[ name ], name ); 100 | } 101 | } 102 | }, 103 | 104 | componentWillMount(){ 105 | const { _watchers, props } = this; 106 | 107 | for( let name in _watchers ){ 108 | _watchers[ name ].call( this, props[ name ], name ); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/react-mvx/define/pureRender.ts: -------------------------------------------------------------------------------- 1 | export function createChangeTokensConstructor( props ) { 2 | const propNames = Object.keys( props ); 3 | 4 | const PropsChangeTokens = new Function( 'p', 's', ` 5 | var v; 6 | this._s = s && s._changeToken; 7 | ${ propNames.map( name => ` 8 | this.${ name } = ( ( v = p.${ name }) && v._changeToken ) || v; 9 | `).join( '' )} 10 | `); 11 | 12 | PropsChangeTokens.prototype._hasChanges = new Function( 'p', 's', ` 13 | var v; 14 | return ( ( s && s._changeToken ) !== this._s ) ${ propNames.map( name => ` || 15 | this.${ name } !== ( ( ( v = p.${ name }) && v._changeToken ) || v ) 16 | `).join( '' )}; 17 | `); 18 | 19 | return PropsChangeTokens; 20 | }; 21 | 22 | export const EmptyPropsChangeTokensCtor = createChangeTokensConstructor({}); 23 | 24 | export const PureRenderMixin = { 25 | shouldComponentUpdate( nextProps ){ 26 | return this._propsChangeTokens._hasChanges( nextProps, this.state ); 27 | }, 28 | 29 | componentDidMount : updateChangeTokens, 30 | componentDidUpdate : updateChangeTokens 31 | } 32 | 33 | function updateChangeTokens(){ 34 | this._propsChangeTokens = new this.PropsChangeTokens( this.props, this.state ); 35 | } -------------------------------------------------------------------------------- /src/react-mvx/define/state.ts: -------------------------------------------------------------------------------- 1 | /***************** 2 | * State 3 | */ 4 | import { define, Record, Store } from 'type-r' 5 | import { ComponentClass } from './common' 6 | 7 | export interface StateDefinition { 8 | state? : object | typeof Record 9 | State? : typeof Record 10 | } 11 | 12 | export interface StateProto { 13 | State? : typeof Record 14 | } 15 | 16 | export default function process( this : ComponentClass, definition : StateDefinition, BaseComponentClass : ComponentClass ){ 17 | const { prototype } = this; 18 | 19 | let { state, State } = definition; 20 | 21 | if( typeof state === 'function' ){ 22 | State = state; 23 | state = void 0; 24 | } 25 | 26 | if( state ){ 27 | const BaseClass = State || prototype.State || Record; 28 | 29 | @define class ComponentState extends BaseClass { 30 | static attributes = state; 31 | } 32 | 33 | prototype.State = ComponentState; 34 | } 35 | else if( State ){ 36 | prototype.State = State; 37 | } 38 | 39 | if( state || State ){ 40 | this.mixins.merge([ StateMixin, UpdateOnNestedChangesMixin ]); 41 | } 42 | } 43 | 44 | export const StateMixin = { 45 | //state : null, 46 | 47 | _initializeState(){ 48 | // props.__keepState is used to workaround issues in Backbone intergation layer 49 | const state = this.state = this.props.__keepState || new this.State(); 50 | 51 | // Take ownership on state... 52 | state._owner = this; 53 | state._ownerKey = 'state'; 54 | }, 55 | 56 | context : { 57 | _nestedStore : Store 58 | }, 59 | 60 | // reference global store to fix model's store locator 61 | getStore(){ 62 | // Attempt to get the store from the context first. Then - fallback to the state's default store. 63 | // TBD: Need to figure out a good way of managing local stores. 64 | let context, state; 65 | 66 | return ( ( context = this.context ) && context._nestedStore ) || 67 | ( ( state = this.state ) && state._defaultStore ); 68 | }, 69 | 70 | componentWillUnmount(){ 71 | const { state } = this; 72 | state._owner = state._ownerKey = void 0; 73 | this._preventDispose /* hack for component-view to preserve the state */ || state.dispose(); 74 | this.state = void 0; 75 | } 76 | }; 77 | 78 | export const UpdateOnNestedChangesMixin = { 79 | _onChildrenChange(){}, 80 | 81 | componentDidMount(){ 82 | this._onChildrenChange = this.asyncUpdate; 83 | } 84 | }; -------------------------------------------------------------------------------- /src/react-mvx/define/store.ts: -------------------------------------------------------------------------------- 1 | import { define, Store } from 'type-r' 2 | import { StateMixin, UpdateOnNestedChangesMixin } from './state' 3 | import { ComponentClass } from './common' 4 | 5 | export interface StoreDefinition { 6 | store? : typeof Store | Store | object 7 | Store? : typeof Store 8 | } 9 | 10 | export interface StoreProto { 11 | store? : Store 12 | Store? : typeof Store 13 | } 14 | 15 | export default function onDefine( this : ComponentClass, definition : StoreDefinition, BaseClass : ComponentClass ){ 16 | let { store, Store : StoreClass } = definition; 17 | 18 | if( store && store instanceof Store ){ 19 | // Direct reference to an existing store. Put it to the prototype. 20 | this.prototype.store = store; 21 | this.mixins.merge([ ExternalStoreMixin, ExposeStoreMixin ]); 22 | } 23 | else if( store || definition.Store ) { 24 | if( typeof store === 'function' ){ 25 | StoreClass = store; 26 | store = void 0; 27 | } 28 | 29 | if( store ){ 30 | const BaseClass = StoreClass || this.prototype.Store || Store; 31 | @define class InternalStore extends BaseClass { 32 | static attrbutes = store; 33 | }; 34 | 35 | this.prototype.Store = InternalStore; 36 | } 37 | else if( StoreClass ){ 38 | this.prototype.Store = StoreClass; 39 | } 40 | 41 | this.mixins.merge([ InternalStoreMixin, UpdateOnNestedChangesMixin, ExposeStoreMixin ]); 42 | } 43 | } 44 | 45 | /** 46 | * Attached whenever the store declaration of any form is present in the component. 47 | */ 48 | const ExposeStoreMixin = { 49 | childContext : { 50 | _nestedStore : Store 51 | }, 52 | 53 | getChildContext(){ 54 | return { _nestedStore : this.store }; 55 | }, 56 | 57 | getStore(){ 58 | return this.store; 59 | }, 60 | 61 | // Will be called by the store when the lookup will fail. 62 | get( key ){ 63 | // Ask upper store. 64 | const store = StateMixin.getStore.call( this, key ); 65 | return store && store.get( key ); 66 | } 67 | }; 68 | 69 | /** 70 | * External store must just track the changes and trigger render. 71 | * TBD: don't use it yet. 72 | */ 73 | const ExternalStoreMixin = { 74 | componentDidMount(){ 75 | // Start UI updates on state changes. 76 | this.listenTo( this.store, 'change', this.asyncUpdate ); 77 | } 78 | }; 79 | 80 | const InternalStoreMixin = { 81 | componentWillMount(){ 82 | var store = this.store = new this.Store(); 83 | store._owner = this; 84 | store._ownerKey = 'store'; 85 | }, 86 | 87 | componentWillUnmount(){ 88 | this.store._ownerKey = this.store._owner = void 0; 89 | this.store.dispose(); 90 | this.store = void 0; 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /src/react-mvx/define/typeSpecs.ts: -------------------------------------------------------------------------------- 1 | import * as PropTypes from 'prop-types' 2 | import { Record, tools, AnyType, ChangeHandler } from 'type-r' 3 | import { ComponentProto } from './common' 4 | 5 | export interface TypeSpecs { 6 | [ name : string ] : object | Function 7 | } 8 | 9 | export function compileSpecs( props : TypeSpecs ){ 10 | const propTypes = {}, 11 | // Create NestedTypes model definition to process props spec. 12 | modelProto = Record.defaults( props ).prototype; 13 | 14 | let defaults, 15 | watchers : { [ name : string ] : PropWatcher }, 16 | changeHandlers : { [ name : string ] : ChangeHandler[] }; 17 | 18 | modelProto.forEachAttr( modelProto._attributes, ( spec : AnyType, name : string ) => { 19 | // Skip auto-generated `id` attribute. 20 | if( name !== 'id' ){ 21 | const { value, type, options } = spec; 22 | 23 | // Translate props type to the propTypes guard. 24 | propTypes[ name ] = translateType( type, options.isRequired ); 25 | 26 | if( options._onChange ){ 27 | watchers || ( watchers = {} ); 28 | watchers[ name ] = toLocalWatcher( options._onChange ); 29 | } 30 | 31 | // Handle listening to event maps... 32 | if( options.changeHandlers && options.changeHandlers.length ){ 33 | changeHandlers || ( changeHandlers = {} ); 34 | changeHandlers[ name ] = options.changeHandlers; 35 | } 36 | 37 | // Handle listening to props changes... 38 | if( options.changeEvents ){ 39 | changeHandlers || ( changeHandlers = {} ); 40 | const handlers = changeHandlers[ name ] || ( changeHandlers[ name ] = [] ), 41 | changeEvents = typeof options.changeEvents === 'string' ? options.changeEvents : null; 42 | 43 | handlers.push( 44 | function( next, prev, component : any ){ 45 | prev && component.stopListening( prev ); 46 | next && component.listenTo( next, changeEvents || next._changeEventName, component.asyncUpdate ); 47 | } 48 | ); 49 | } 50 | 51 | // If default value is explicitly provided... 52 | if( value !== void 0 ){ 53 | //...append it to getDefaultProps function. 54 | defaults || ( defaults = {} ); 55 | defaults[ name ] = spec.convert( value, void 0, null, {} ); 56 | } 57 | } 58 | }); 59 | 60 | return { propTypes, defaults, watchers, changeHandlers }; 61 | } 62 | 63 | type PropWatcher = ( this : ComponentProto, propValue : any, propName : string ) => void 64 | 65 | function toLocalWatcher( ref ) : PropWatcher { 66 | return typeof ref === 'function' ? ref : function( value, name ){ 67 | this[ ref ] && this[ ref ]( value, name ); 68 | } 69 | } 70 | 71 | export class Node {} 72 | export class Element {} 73 | 74 | function translateType( Type : Function, isRequired : boolean ){ 75 | const T = _translateType( Type ); 76 | return isRequired ? T.isRequired : T; 77 | } 78 | 79 | declare global { 80 | interface NumberConstructor { 81 | integer : Function 82 | } 83 | } 84 | 85 | function _translateType( Type : Function ){ 86 | switch( Type ){ 87 | case Number : 88 | case Number.integer : 89 | return PropTypes.number; 90 | case String : 91 | return PropTypes.string; 92 | case Boolean : 93 | return PropTypes.bool; 94 | case Array : 95 | return PropTypes.array; 96 | case Function : 97 | return PropTypes.func; 98 | case Object : 99 | return PropTypes.object; 100 | case Node : 101 | return PropTypes.node; 102 | case Element : 103 | return PropTypes.element; 104 | case void 0 : 105 | case null : 106 | return PropTypes.any; 107 | default: 108 | return PropTypes.instanceOf( Type ); 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/react-mvx/define/types.ts: -------------------------------------------------------------------------------- 1 | export type Processor = ( spec : ComponentDefinition, baseProto : Object ) => ComponentDefinition; 2 | 3 | export interface ComponentDefinition { 4 | 5 | } -------------------------------------------------------------------------------- /src/react-mvx/index.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { define, Record, Store, mixins, mixinRules, ChainableAttributeSpec } from 'type-r' 3 | import processSpec, { Node, Element, TypeSpecs } from './define' 4 | import Link from './link' 5 | import { Component } from './component' 6 | 7 | interface ReactMVx { 8 | // It's ES6 module 9 | default : ReactMVx 10 | 11 | // MixtureJS decorators... 12 | define : typeof define 13 | mixins : typeof mixins 14 | mixinRules : typeof mixinRules 15 | 16 | // Overriden components 17 | Component : typeof Component 18 | 19 | // additional ReactMVx types 20 | Link : typeof Link 21 | Node : ChainableAttributeSpec 22 | Element : ChainableAttributeSpec 23 | 24 | // Helper methods 25 | assignToState( key : string ) 26 | } 27 | 28 | // extend React namespace 29 | const ReactMVx : ReactMVx & typeof React = Object.create( React ); 30 | 31 | // Make it compatible with ES6 module format. 32 | ReactMVx.default = ReactMVx; 33 | // listenToProps, listenToState, model, attributes, Model 34 | ReactMVx.define = define; 35 | ReactMVx.mixins = mixins; 36 | 37 | ReactMVx.Node = Node.value( null ); 38 | ReactMVx.Element = Element.value( null ); 39 | ReactMVx.Link = Link; 40 | 41 | ReactMVx.Component = Component as any; 42 | const assignToState = ReactMVx.assignToState = key => { 43 | return function( prop ){ 44 | const source = prop && prop instanceof Link ? prop.value : prop; 45 | this.state.assignFrom({ [ key ] : source }); 46 | if( source && source._changeToken ){ 47 | this.state[ key ]._changeToken = source._changeToken; 48 | } 49 | } 50 | } 51 | 52 | export default ReactMVx; 53 | export { define, mixins, Node, Element, Link, Component, assignToState } 54 | -------------------------------------------------------------------------------- /src/react-mvx/valuelink/component.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Link, LinksCache } from './link' 3 | 4 | export interface DataBindingSource< S >{ 5 | linkAt< K extends keyof S>( key : K ) : Link< S[ K ] > 6 | linkAll( ...keys : K[] ) : LinksCache< S, K > 7 | } 8 | 9 | export abstract class LinkedComponent< P, S > extends React.Component< P, S > implements DataBindingSource< S > { 10 | links : LinksCache< S, keyof S > = null; 11 | 12 | linkAt< K extends keyof S>( key : K ) : Link< S[ K ] >{ 13 | return linkAt( this, key ); 14 | } 15 | 16 | linkAll( ...keys : K[] ) : LinksCache< S, K >; 17 | linkAll( ...args : ( keyof S )[] ){ 18 | return linkAll( this, args ); 19 | } 20 | } 21 | 22 | Link.all = < P, S >( component : React.Component< P, S >, ..._keys : ( keyof S )[] ) => linkAll( >component, _keys ); 23 | Link.state = linkAt; 24 | 25 | function linkAll< P, S, K extends keyof S >( component : LinkedComponent< P, S >, _keys : K[] ) : LinksCache< S, K >{ 26 | const { state } = component, 27 | cache = component.links || ( component.links = {} ), 28 | keys = _keys.length ? _keys : <( keyof S )[]>Object.keys( state ); 29 | 30 | for( let key of keys ){ 31 | const value = state[ key ], 32 | cached = cache[ key ]; 33 | 34 | if( !cached || cached.value !== value ) { 35 | cache[ key ] = new StateLink( component, key, value ); 36 | } 37 | } 38 | 39 | return cache; 40 | } 41 | 42 | function linkAt< P, S, K extends keyof S>( component : LinkedComponent< P, S>, key : K ) : Link< S[ K ] >{ 43 | const value = component.state[ key ], 44 | cache = component.links || ( component.links = {} ), 45 | cached = cache[ key ]; 46 | 47 | return cached && cached.value === value ? cached : cache[ key ] = new StateLink( component, key, value ); 48 | } 49 | 50 | export class StateLink< P, S, K extends keyof S > extends Link< S[ K ] > { 51 | constructor( public component : LinkedComponent< P, S >, public key : K, value : S[ K ] ){ 52 | super( value ); 53 | } 54 | 55 | set( x : S[ K ] ) : void { 56 | const attrs = > {}; 57 | attrs[ this.key ] = x; 58 | this.component.setState( attrs ); 59 | } 60 | } -------------------------------------------------------------------------------- /src/react-mvx/valuelink/helpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Select appropriate helpers function for particular value type. 3 | */ 4 | export interface IterableLink { 5 | value : any 6 | at( key : number | string ) : any 7 | } 8 | 9 | export type Iterator = ( link : any, key : string | number ) => any; 10 | 11 | export interface Helper { 12 | map( link : IterableLink, iterator : Iterator ) : any[] 13 | clone( obj : any ) : any, 14 | remove( obj : any, key : string | number ) : any 15 | } 16 | 17 | const ArrayProto = Array.prototype, 18 | ObjectProto = Object.prototype; 19 | 20 | export function helpers( value ) : Helper { 21 | if( value && typeof value === 'object' ){ 22 | switch( Object.getPrototypeOf( value ) ){ 23 | case ArrayProto : return arrayHelpers; 24 | case ObjectProto : return objectHelpers; 25 | } 26 | } 27 | 28 | return dummyHelpers; 29 | } 30 | 31 | // Do nothing for types other than Array and plain Object. 32 | const dummyHelpers : Helper = { 33 | clone( value ){ return value; }, 34 | map( link : IterableLink, fun ){ return []; }, 35 | remove( value ){ return value; } 36 | }; 37 | 38 | // `map` and `clone` for plain JS objects 39 | export const objectHelpers : Helper = { 40 | // Map through the link to object 41 | map( link : IterableLink, iterator : Iterator ) : any[] { 42 | let mapped = []; 43 | 44 | for( let key in link.value ){ 45 | const element = iterator( link.at( key ), key ); 46 | element === void 0 || ( mapped.push( element ) ); 47 | } 48 | 49 | return mapped; 50 | }, 51 | 52 | remove( object : {}, key : string ) : {} { 53 | delete object[ key ]; 54 | return object; 55 | }, 56 | 57 | // Shallow clone plain JS object 58 | clone( object : {} ) : {} { 59 | let cloned = {}; 60 | 61 | for( let key in object ){ 62 | cloned[ key ] = object[ key ]; 63 | } 64 | 65 | return cloned; 66 | } 67 | }; 68 | 69 | // `map` and `clone` helpers for arrays. 70 | export const arrayHelpers : Helper = { 71 | // Shallow clone array 72 | clone( array : any[] ) : any[] { 73 | return array.slice(); 74 | }, 75 | 76 | remove( array : any[], i : number ) : any[] { 77 | array.splice( i, 1 ); 78 | return array; 79 | }, 80 | 81 | // Map through the link to array 82 | map( link : IterableLink, iterator : Iterator ) : any[] { 83 | const length = link.value.length, 84 | mapped = Array( length ); 85 | 86 | for( var i = 0, j = 0; i < length; i++ ){ 87 | const y = iterator( link.at( i ), i ); 88 | y === void 0 || ( mapped[ j++ ] = y ); 89 | } 90 | 91 | mapped.length === j || ( mapped.length = j ); 92 | 93 | return mapped; 94 | } 95 | }; -------------------------------------------------------------------------------- /src/react-mvx/valuelink/index.ts: -------------------------------------------------------------------------------- 1 | import { Link } from './link' 2 | export default Link; 3 | export * from './component' 4 | export * from './link' -------------------------------------------------------------------------------- /src/view-element.ts: -------------------------------------------------------------------------------- 1 | import React, { define, Component } from './react-mvx' 2 | import ReactDOM from 'react-dom' 3 | import { tools } from 'type-r' 4 | 5 | const { notEqual } = tools; 6 | 7 | export interface BackboneViewProps{ 8 | View : any 9 | options : object 10 | className? : string 11 | } 12 | 13 | export default class BackboneView extends Component< BackboneViewProps, null >{ 14 | shouldComponentUpdate( next ){ 15 | var props = this.props; 16 | return next.View !== props.View || notEqual( next.options, props.options ); 17 | } 18 | 19 | view : any 20 | 21 | hasUnsavedChanges(){ 22 | var view = this.view; 23 | 24 | return view && ( 25 | typeof view.hasUnsavedChanges === 'function' ? view.hasUnsavedChanges() : view.hasUnsavedChanges 26 | ); 27 | } 28 | 29 | root : any; 30 | saveRef = element => { 31 | this.root = element; 32 | } 33 | 34 | render(){ 35 | return React.createElement( 'div', { 36 | ref : this.saveRef, 37 | className : this.props.className 38 | } ); 39 | } 40 | 41 | componentDidMount(){ 42 | this._mountView(); 43 | } 44 | 45 | componentDidUpdate(){ 46 | this._dispose(); 47 | this._mountView(); 48 | } 49 | 50 | componentWillUnmount(){ 51 | this._dispose(); 52 | } 53 | 54 | _mountView(){ 55 | var el = this.root, 56 | p = this.props; 57 | 58 | var view = this.view = p.options ? new p.View( p.options ) : new p.View(); 59 | 60 | el.appendChild( view.el ); 61 | view.render(); 62 | } 63 | 64 | _dispose(){ 65 | var view = this.view; 66 | if( view ){ 67 | if( view.dispose ){ 68 | view.dispose(); 69 | } 70 | else{ 71 | view.stopListening(); 72 | view.off(); 73 | } 74 | 75 | this.root.innerHTML = ""; 76 | this.view = null; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "moduleResolution": "node", 5 | "importHelpers": true, 6 | "noEmitHelpers": false, 7 | "outDir": "lib", 8 | "declaration": true, 9 | "target": "es5", 10 | "module": "es6", 11 | "sourceMap": true, 12 | "jsx": "react", 13 | "baseUrl": "./", 14 | "experimentalDecorators": true, 15 | "paths": { 16 | "tslib" : ["node_modules/tslib/tslib.d.ts"], 17 | "type-r": ["node_modules/nestedtypes"] 18 | } 19 | }, 20 | "files":[ 21 | "./src/index.ts" 22 | ] 23 | } --------------------------------------------------------------------------------