├── .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 | 
137 |
138 | If you take [flux-comparison](https://github.com/voronianski/flux-comparison) example, difference will become even more spectacular.
139 |
140 | 
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 |
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 |
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 |
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 |
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 | 
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 |
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 | [](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 | 
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 | 
43 |
44 | And cart. Which is just another collection of products.
45 |
46 | 
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 | 
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 | 
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 |