├── .gitignore
├── README.md
├── index.js
├── lib
├── ListViewModel.js
├── Provider.js
└── ViewModel.js
├── package.json
└── sample
├── counter
├── .eslintrc
├── .gitignore
├── index.html
├── index.js
├── src
│ └── app.js
└── webpack.config.js
└── todolist
├── .eslintrc
├── .gitignore
├── index.html
├── index.js
├── src
├── app.js
├── viewmodels
│ ├── root.js
│ └── todolist.js
└── views
│ ├── app.js
│ ├── item.js
│ └── item.less
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /.idea
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Redux-ViewModel #
2 |
3 | Redux-ViewModel is designed to beautify your code with [React](facebook.github.io/react/) & [Redux](http://rackt.github.io/redux/).
4 |
5 | You don't have to write action factory and switch-cases to identity actions any more.
6 |
7 | ## Changelog ##
8 |
9 | ### 0.4.2 ###
10 |
11 | bugfix: state doesn't change sometime when use with react-router.
12 |
13 | ### 0.4.1 ###
14 |
15 | Please skip this version.
16 |
17 | ### 0.4.0 ###
18 |
19 | * Now, return a undefined state will remove self in parent state. Specially in a list. See [#3](https://github.com/tdzl2003/redux-viewmodel/issues/3)
20 |
21 | * Fix issue: "0" and 0 was consider as same key in a array before 0.3.1. Now they are not.
22 |
23 | ### 0.3.1 ###
24 |
25 | * Change dependency of 'react' version to '*' for issue with React 0.14.0
26 |
27 | ### 0.3.0 ###
28 |
29 | * Constructor arguments changed.
30 |
31 | * ViewModel.state property works fine now.
32 |
33 | * Parent state should not change if reducer doesn't change state. This can make view re-render less.
34 |
35 | * Key will store in a pair of square brackets like '[key]' in path.
36 |
37 | * ListViewModel only have different defaultState, all it's method can be accessed in ViewModel.js. You can use ViewModel even if your state is a array.
38 |
39 | * Add method `getDefaultSubViewModelClass(name)` and `getDefaultItemClass(key)`. You can override this to provide
40 | a default class for sub-viewmodel or item-viewmodel
41 |
42 | * Add property `name`
43 |
44 | * Fix Array.prototype.find on IE9
45 |
46 | ### 0.2.1 ###
47 |
48 | * Fix a bug that provider didn't rerender when use together with react-router
49 |
50 | * Known issue: ViewModel.state was broken now.
51 |
52 | ### 0.2.0 ###
53 |
54 | * Add getter/reducer parameter to view model constructor, make's you can create view model alias or special view model like .first/.last
55 |
56 | * Known issue: ViewModel.state was broken now.
57 |
58 | ## Overview ##
59 |
60 | ### Counter demo: ###
61 |
62 | ```javascript
63 | /**
64 | * Created by tdzl2_000 on 2015-08-28.
65 | */
66 |
67 | import React from 'react';
68 | import {createStore} from 'redux';
69 | import {ViewModel, Provider} from 'redux-viewmodel';
70 |
71 | class CounterViewModel extends ViewModel
72 | {
73 | static get defaultState(){
74 | return 0;
75 | }
76 | increment(state, val){
77 | return state + (val||1);
78 | }
79 | decrement(state, val){
80 | return state - (val||1);
81 | }
82 | }
83 |
84 | class RootViewModel extends ViewModel
85 | {
86 | static get defaultState(){
87 | return {
88 | counter: CounterViewModel.defaultState
89 | }
90 | }
91 | get counter(){
92 | return this._counter = this._counter || this.getSubViewModel('counter', CounterViewModel);
93 | }
94 | }
95 |
96 | class NumbericView extends React.Component
97 | {
98 | render(){
99 | return ({this.props.value});
100 | }
101 | }
102 |
103 | var rootViewModel = new RootViewModel();
104 |
105 | class AppView extends React.Component
106 | {
107 | render(){
108 | return (
109 |
110 |
111 |
114 |
117 |
);
118 | }
119 | }
120 |
121 | React.render((
122 |
123 | ), document.getElementById("container"));
124 |
125 | ```
126 |
127 | ## Introduce ##
128 |
129 | React-ViewModel make it possible to write 'ViewModel' classes to reorganize code for reducer implement in Redux.
130 |
131 | ### View-Model Class ###
132 |
133 | View-Model class in React-ViewModel is defined as a class that contains reducer functions as methods.
134 |
135 | In general, there should be a single 'RootViewModel' for a single app. It will hold store instance
136 | and will be used to generate any other View-Model object in getters.
137 |
138 | ### Reducer Method ###
139 |
140 | You can define reducer method in View-Model Class. Like reducer function in Redux, reducer method
141 | is method of View-Model class that receive a state and return a new state. But unlike reducer function
142 | in Redux, reducer method can receive any count of parameters instead of a single action parameter.
143 |
144 | You can call `viewModel.dispatch(methodName, ...args)` to dispatch a reducer action. The method will be execute later
145 | and the state tree will be updated.
146 |
147 | ### SubViewModel ###
148 |
149 | ViewModel can have sub-view-models as property. Sub-view-model can be get by call `this.getSubViewModel(name, clazz)`.
150 |
151 | Name of sub-view-model Does not need to match state field from 0.2.0. You can create alias of a sub-view-model by
152 | implement a getter that returns a sub-view-model with different getter/reducer.
153 |
154 | It doesn't matter that whether you cache the sub-view-model object. It doesn't store any state, instead, it store a
155 | path from RootViewModel. If the path doesn't exists, any reducer that return a valid value will create the path.
156 |
157 | Sub-view-model will use same store with the root one.
158 |
159 | ### ListViewModel ###
160 |
161 | ListViewModel is view-model that operate a Array state. All item in state should be either a object with 'key' property,
162 | or a string/number value that mark key of itself. All actions to list should use key to identify items instead of array index.
163 |
164 | You can also create sub-view-model alias for ListViewModel from 0.2.0. See 'Switch first' button in 'todolist' for a sample.
165 |
166 | From 0.3.0, there's no different between ViewModel and ListViewModel except defaultState. You can
167 | use ViewModel on a array state.
168 |
169 | ### Provider ###
170 |
171 | Use redux-viewmodel with react, you should use Provider component as root component.
172 |
173 | Provider will subscribe the store, and wrap state of root view-model as props of the real root view.
174 |
175 | ## Reference ##
176 |
177 | ### class ViewModel ###
178 |
179 | All View-Model class should extend class ViewModel.
180 |
181 | #### constructor() ####
182 |
183 | Create a root view-model. A new store will be created.
184 |
185 | #### constructor(parent, name, getter, reducer) ####
186 |
187 | Create a sub view-model. This should be called via ViewModel::getSubViewModel or ViewModel::getItemByKey.
188 |
189 | * parent: parent view-model.
190 |
191 | * name: Name of this view model.
192 |
193 | * getter(state, name): get sub-state object from state of parent view-model.
194 |
195 | * reduceParent(state, newValue, name): reduce state of current view-model in parent state.
196 |
197 | #### static get defaultState() ####
198 |
199 | Return a default State. Can be overridden. Return `undefined` if not.
200 |
201 | #### getSubViewModelClass(name) ####
202 |
203 | Return view-model class for sub-view-model.
204 |
205 | #### getItemClass(name) ####
206 |
207 | Return view-model class for item.
208 |
209 | #### get name() ####
210 |
211 | Return name of current view-model class.
212 |
213 | #### get key() ####
214 |
215 | Return key of current view-model class if the view-model is a item view-model.
216 |
217 | #### get store() ####
218 |
219 | Return store instance associated with the view model.
220 |
221 | #### get path() ####
222 |
223 | Return path of current view-model.
224 |
225 | #### get state() ####
226 |
227 | Return state data of the view model.
228 |
229 | #### getSubViewModel(name[, clazz, [getter, reduceParent]]) ####
230 |
231 | Create sub-view-model instance of field `name`
232 |
233 | See description of `constructor(parent, name, getter, reduceParent)` about `getter` and `reduceParent`.
234 |
235 | #### getItemByKey(key[, clazz, [getter, reduceParent]]) ####
236 |
237 | Create sub-view-model instance of item with speficied `key`.
238 |
239 | See description of `constructor(parent, name, getter, reduceParent)` about `getter` and `reduceParent`.
240 |
241 | #### dispatch(methodName, ...args) ####
242 |
243 | Dispatch a reduce action. The method `methodName` will be executed and the state tree of the store will be updated.
244 |
245 | The generated action is like this:
246 |
247 | ```javascript
248 | {
249 | "type": "REDUX_VM_DISPATCH",
250 | "path": [/*An array with all path splits */],
251 | "method": "methodName",
252 | args: [/*All extra arguments.*/]
253 | }
254 | ```
255 |
256 | #### _reduce(state, path, method, args) ####
257 |
258 | Called before the reducer method is invoked. Path is relative to current view-model.
259 |
260 | You can override this method to do extra work.
261 |
262 | You can also invoke this method to simulate multiple works in a reducer method.
263 |
264 | ### Provider ###
265 |
266 | #### prop: viewModel ####
267 |
268 | The view-model used inside the provider.
269 |
270 | #### prop: viewClass ####
271 |
272 | The root component class of this application.
273 |
274 | #### prop: viewFactory (props, viewModel) ####
275 |
276 | A function that return the root component of this application. If viewFactory is passed, viewClass will be ignored.
277 |
278 | The root state(as props) and the root view model will be passed to viewFactory.
279 |
280 | #### prop: children ####
281 |
282 | Children of provider will be passed to the component directly.
283 |
284 | ## F.A.Q. ##
285 |
286 | ### Can I create many root view models? ###
287 |
288 | I think you can, but usually you don't need to.
289 |
290 | ### Can I pass view-models as prop of components? ###
291 |
292 | Yes, you can.
293 |
294 | ### Can a view-model be associated to two or more state object? ###
295 |
296 | No. Reducer method will called only on one sub-state. You can implement reducer function on parent view-model to do this.
297 |
298 | You can invoke `_reduce` method to simulate action on multiply sub-view model, like this:
299 |
300 | ```
301 | class someListViewModel extends ListViewModel
302 | {
303 | checkAll(state){
304 | // run 'check' on each item in a single action.
305 | var keys = state.map(v=>v.key)
306 | keys.forEach(key=>{
307 | state = this._reduce(state, ['['+key+']'], 'check', []);
308 | })
309 | return state;
310 | }
311 | }
312 | ```
313 |
314 | ### Can I create a sub-view-model alias? ###
315 |
316 | You can since v0.2.0. You can just return another sub-view-model object(which should also be accessible), or return
317 | a view-model class with specified `getter` and `reducer`.
318 |
319 | See 'Switch first' button in 'todolist' for a sample.
320 |
321 | ### Can I create many providers? ###
322 |
323 | Yes. And they can be bind to different view model created from a same root.
324 |
325 | ### Can I use same view-model/component class in different path? ###
326 |
327 | Yes, that's the right way to use Redux-ViewModel.
328 |
329 | Data of same `class` may use same view-model class, even they are on different path.
330 |
331 | For example, if we are writing a site like github, the following path with different value may have same class:
332 |
333 | ```
334 | projects, tdzl2003/lua.js
335 | users, tdzl2003, ownedProjects, tdzl2003/redux-viewmodel
336 | users, tdzl2003, stars, stewartlord/identicon.js
337 | users, tdzl2003, contributed, facebook/react-native.js
338 | ```
339 |
340 | So they can be visited with same view-model class like `ProjectViewModel`, and be rendered with same component like 'ProjectShortInfo' or 'ProjectDetails'.
341 |
342 | Also, you can use same view-model on different components to render/interact differently, but don't use different
343 | view-model class with same path.
344 |
345 | ### Can I use redux-viewmodel with react-native? ###
346 |
347 | Yes.
348 |
349 | ### Can I use redux-viewmodel with angularjs? ###
350 |
351 | I think so, but I didn't test it.
352 |
353 | ### Can I use redux-viewmodel with react-router? ###
354 |
355 | Yes, use Provider as root of your router handler and use viewFactory to create actually views, like this:
356 |
357 | ```javascript
358 | import {Provider} from 'redux-viewmodel';
359 | import RootViewModel from '../viewmodels/root';
360 |
361 | class Site extends React.Component
362 | {
363 | renderContent(props, children, vm){
364 | return (