├── .editorconfig
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── codegen
├── index.js
└── templates
│ ├── action.js
│ ├── actionHandler.js
│ ├── actions.js
│ ├── constants.js
│ ├── index.js
│ ├── module.js
│ └── store.js
├── dist
└── react-flux.js
├── examples
└── example1
│ ├── .gitignore
│ ├── flux
│ ├── actions
│ │ └── user.js
│ ├── constants
│ │ └── user.js
│ └── stores
│ │ ├── father.js
│ │ ├── mother.js
│ │ └── user.js
│ ├── gulpfile.js
│ ├── index.html
│ ├── index.js
│ ├── index.jsx
│ └── package.json
├── index.js
├── lib
├── actions.js
├── configs.js
├── constants.js
├── dispatcher.js
├── index.js
├── mixinFor.js
├── store.js
└── storeActionHandler.js
├── package.json
├── test
├── actions.js
├── configs.js
├── constants.js
├── dispatcher.js
├── mixinFor.js
├── store.js
└── storeActionHandler.js
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | insert_final_newline = true
9 | indent_style = tab
10 | indent_size = 2
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.11"
4 | - "0.10"
5 | script: "npm run test-cov"
6 | # Send coverage data to Coveralls
7 | after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Khaled Jouda
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/kjda/ReactFlux)
2 | [](https://coveralls.io/r/kjda/ReactFlux?branch=master)
3 |
4 |
5 | **This library is not maintained anymore!**
6 |
7 | React-Flux
8 | ==========
9 |
10 | Read about [Flux-Data-Flow](http://facebook.github.io/flux/docs/overview.html)
11 |
12 | Install
13 | =======
14 | Npm:
15 | ```
16 | $ npm install react-flux
17 | ```
18 | Bower:
19 | ```
20 | $ bower install react-flux
21 | ```
22 |
23 | Use it
24 | ======
25 | ```
26 | var ReactFlux = require("react-flux");
27 | ```
28 |
29 | Browser:
30 | ```
31 |
32 | ```
33 |
34 |
35 | ReactFlux
36 | =========
37 | ReactFlux expose the following methods:
38 | 1. createConstants( Array constants, String prefix )
39 | 2. createActions( Object actions )
40 | 3. createStore( Object mixin, Array listeners )
41 | 4. dispatch( String constant, Object payload )
42 | 5. mixinFor( Store store, ..... )
43 |
44 | Constants
45 | =========
46 | ```
47 | var userConstants = ReactFlux.createConstants([
48 | 'LOGIN',
49 | 'LOGOUT'
50 | ], 'USER');
51 | ```
52 | The second parameter which is a prefix to be added to all messages is optional
53 |
54 |
55 | this will result in the following:
56 |
57 | ```
58 | {
59 | LOGIN: 'USER_LOGIN',
60 | LOGIN_SUCCESS: 'USER_LOGIN_SUCCESS',
61 | LOGIN_FAIL: 'USER_LOGIN_FAIL',
62 | LOGIN_AFTER: 'USER_LOGIN_AFTER',
63 | LOGOUT: 'USER_LOGOUT',
64 | ...
65 | }
66 | ```
67 |
68 | It is also possible to configure constant generation, one may configure separator, success- and fail suffixes
69 | ```
70 | ReactFlux.configs.constants.setSeparator(':');
71 | ReactFlux.configs.constants.setSuccessSuffix('OK');
72 | ReactFlux.configs.constants.setFailSuffix('ERROR');
73 | ReactFlux.configs.constants.setAfterSuffix('DONE');
74 | ```
75 |
76 | now the previous example will result in:
77 | ```
78 | {
79 | LOGIN: 'USER:LOGIN',
80 | LOGIN_OK: 'USER:LOGIN:OK',
81 | LOGIN_ERROR: 'USER:LOGIN:ERROR',
82 | LOGIN_DONE: 'USER:LOGIN:DONE',
83 | LOGOUT: 'USER:LOGOUT',
84 | ...
85 | }
86 | ```
87 | to go back to default configurations use:
88 | ```
89 | ReactFlux.configs.constants.resetToDefaults();
90 | ```
91 |
92 | Actions
93 | =======
94 | ```
95 | var userActions = ReactFlux.createActions({
96 |
97 | login: [userConstants.LOGIN, function loginAction(username, password){
98 | return {
99 | id: 1,
100 | username: 'Max Mustermann'
101 | }
102 | }]
103 |
104 | });
105 | ```
106 | calling userActions.login("mustermann", "1234567") will do the following:
107 | 1. **USER_LOGIN** gets dispatched directly before the passed callback gets executed. which means: any store handlers registered for USER_LOGIN will get invoked.
108 | 2. the passed callback gets executed, in this case **loginAction**.
109 | 3. Depending on the result of the action callback, it will either dispatch **USER_LOGIN_SUCCESS** or **USER_LOGIN_FAIL**.
110 | 4. **USER_LOGIN_AFTER** get's dispatched after step 3
111 |
112 | **USER_LOGIN_SUCCESS** gets dispatched in two cases:
113 | 1. The callback returns a value, like in the example above
114 | 2. the callback returns a promise which gets resolved
115 |
116 | **USER_LOGIN_FAIL** gets dispatched in two cases:
117 | 1. The action callback throws an exception or returns an Error
118 | 2. It returns a promise which gets rejected
119 |
120 |
121 | Stores
122 | ====
123 | ```
124 | var userStore = ReactFlux.createStore({
125 |
126 | mixins: [ SomeStoreMixin, AnotherStoreMixin ],
127 |
128 | getInitialState: function(){
129 | return {
130 | isAuth: false,
131 | data: null,
132 | isLoggingIn: false,
133 | error: null
134 | };
135 | },
136 |
137 | storeDidMount: function(){
138 | //Get called when store is ready
139 | },
140 |
141 | isAuth: function(){
142 | return this.get('isAuth')
143 | }
144 |
145 | }, [
146 |
147 | /**
148 | * called directly before executing login action
149 | */
150 | [userConstants.LOGIN, function onLogin(){
151 | this.setState({
152 | isLoggingIn: true,
153 | error: null
154 | });
155 | }],
156 |
157 | /**
158 | * called if login action was successful
159 | */
160 | [userConstants.LOGIN_SUCCESS, function onLoginSuccess(payload){
161 | this.setState({
162 | isAuth: true,
163 | data: payload
164 | });
165 | }],
166 |
167 | /**
168 | * called if login action failed
169 | */
170 | [userConstants.LOGIN_FAIL, function onloginFail(error){
171 | this.setState({
172 | error: error.message
173 | });
174 | }]
175 |
176 | /**
177 | * called after login action succeeds or fails
178 | */
179 | [userConstants.LOGIN_AFTER, function onloginAfter(error){
180 | this.setState({
181 | isLoggingIn: false
182 | });
183 | }],
184 |
185 | ]);
186 | ```
187 |
188 |
189 | ReactFlux.createStore takes two parameters:
190 |
191 |
192 | 1. A mixin object for the store:
193 |
194 |
195 | > Thanks to [Leland Richardson][1] store definition accepts mixins which get mixed into the store. A store mixin may be recursive and it may hook into store lifecycle events i.e getInitialState and storeDidMount. Please have a look at the tests for more insights.
196 |
197 | 2. an array of action listeners
198 |
199 |
200 | ###all *_SUCCESS callbacks get payload as parameter, which is the value returned from an actioin, or the payload passed to it's promise resolve function
201 |
202 | ###all *_FAIL callbacks get an Error object, or whatever you pass to a promise reject function
203 |
204 | ###Store API
205 | #####store.setState(obj)
206 | sets the state of the store
207 | #####store.replaceState(obj)
208 | replaces the state of the store
209 | #####store.getState()
210 | gets the store state. getState does not return a copy or a clone of state, rather it returns the object itself.
211 | #####store.getStateClone()
212 | gets a clone of the store state
213 | #####store.get(key)
214 | gets the value corresponding to the key from store's state. *get* does not return a clone of the value if it's an object, rather it returns the object itself.
215 | #####store.getClone(key)
216 | gets a clone of the value corresponding to the key from store's state
217 | #####store.onChange(callback)
218 | registers a callback which gets invoked whenever store's state changes
219 | #####store.offChange(callback)
220 | deregisters callback from store changes
221 | #####store.mixinFor()
222 | Each store exposes a "mixinFor" method which returns a ReactMixin, so that you don't need to manually couple your your components with different stores. If you use this mixin you must implement a getStateFromStores method on the component which gets called in componentWillMount and whenever the store's state gets updated
223 | ```
224 | var fooComponent = React.createClass({
225 | mixins: [
226 | fooStore.mixinFor()
227 | ],
228 | getStateFromStores: function(){
229 | return {
230 | fooState: fooStore.getState()
231 | };
232 | },
233 |
234 | });
235 | ```
236 | #####store.addActionHandler(constant, actionHandlerMixin)
237 | register an action handler for the given constant.. please refer to ActionHandlers sections for more details.
238 |
239 | #####store.getActionState(constant)
240 | returns a copy of the state of the action handler related to the given constant
241 | #####store.setActionState(const, newState)
242 | sets the state of the action handler related to the given constant
243 |
244 |
245 | ### Store Action Handlers
246 | stores also provide a different way for setting handlers, namely through StoreActionHandler.
247 | StoreActionHandler is an object defining handlers for all action possible constants. It provides a **seperate sub-state** specific to every single action handler. This could be useful when maintaining different UI states in a store that is used by different UI views. This way we don't need to pollute a Store's state with many variables correlating to the state of UI Views, we can just dump those variables into sub-states, while keeping store's state dedicated to real data.
248 |
249 | ```
250 | UserStore.addActionHandler(constants.SAVE_NEW_USERNAME, {
251 |
252 | //returns initial state specific only to this handler
253 | getInitialState: function(){
254 | isSaving: false,
255 | error: null,
256 | success: false
257 | },
258 |
259 | //this gets called before the action associated with SAVE_NEW_USERNAME is executed
260 | before: function(){
261 | //this inside handler callbacks refers to the action handler itself and not to the store
262 | this.setState({
263 | isSaving: true,
264 | error: null
265 | });
266 | },
267 |
268 | //this gets called after the action associated with SAVE_NEW_USERNAME succeeds or fails
269 | after: function(){
270 | this.setState({
271 | isSaving: false
272 | });
273 | },
274 |
275 | //this gets called if the action associated with SAVE_NEW_USERNAME succeeds
276 | success: function(payload){
277 | this.setState({
278 | success: true
279 | });
280 |
281 | //here we set the state of parent store(UserStore) using this.parent.setState
282 | this.parent.setState({
283 | username: payload.username
284 | });
285 | },
286 |
287 | //this gets called if the action associated with SAVE_NEW_USERNAME fails
288 | fail: function(error){
289 | this.setState({
290 | error: error
291 | });
292 | }
293 |
294 | });
295 | ```
296 |
297 | **getInitialState**, **before**, **after**, **success** and fail are optional.
298 |
299 |
300 | We can access handler specific state using:
301 | ```
302 | UserStore.getActionState(constants.SAVE_NEW_USERNAME); // returns the state object
303 | ```
304 | or we can get a specific property from handler state
305 | ```
306 | UserStore.getActionState(constants.SAVE_NEW_USERNAME, 'isSaving');
307 | ```
308 | to reset a handler state use:
309 | ```
310 | UserStore.resetActionState(constants.SAVE_NEW_USERNAME);
311 | ```
312 | to set a handler state use:
313 | ```
314 | UserStore.setActionState(constants.SAVE_NEW_USERNAME, {
315 | //.....
316 | });
317 | ```
318 |
319 | setting handler state will cause the store to emit a change event
320 |
321 | Inside an ActionHandler the parent store is accessible through this.parent. So, to update the state of the parent store from within the actionHandler, use **this.parent.setState**
322 |
323 | Code Generation
324 | ===============
325 | ReactFlux ships with a code generation utility which can make life a lot easier.
326 | To use this functionality create a special js file in your working directory:
327 |
328 | flux.js
329 | ```
330 | #!/usr/bin/env node
331 | require('react-flux/codegen');
332 | ```
333 |
334 | make it executable
335 | ```
336 | chmod +x flux.js
337 | ```
338 |
339 | then look at the help
340 | ```
341 | ./flux --help
342 | ```
343 |
344 | code generator will put your files into "flux" directory by default. if you want to change it, create another file "reactflux.json" in the same directory as "flux.js" and specify where you want to have your flux folder
345 | ```
346 | {
347 | "directory": "my-special-flux-directory"
348 | }
349 | ```
350 |
351 |
352 |
353 | Example React component
354 | =======================
355 | ```
356 | var LoginComponent = React.createClass({
357 |
358 | mixins: [
359 | ReactFlux.mixinFor(userStore) //or userStore.mixin()
360 | ],
361 |
362 | getStateFromStores: function(){
363 | return {
364 | user: userStore.state
365 | };
366 | },
367 |
368 | render: function(){
369 | if( !this.state.user.get('isAuth') ){
370 | return this.renderLogin();
371 | }
372 | return this.renderLogout();
373 | },
374 |
375 | renderLogin: function(){
376 | if( this.state.user.get('isLoggingIn') ){
377 | return (
...
);
378 | }
379 | return(
380 |
381 | Hello Stranger!
382 |
login
383 |
384 | );
385 | },
386 |
387 | renderLogout: function(){
388 | return(
389 |
390 | Hello {this.state.user.get('data').username}!
391 |
Logout
392 |
393 | );
394 | },
395 |
396 | login: function(){
397 | userActions.login("mustermann", "1234567");
398 | return false;
399 | },
400 |
401 | logout: function(){
402 | userActions.logout();
403 | return false;
404 | }
405 |
406 | });
407 | ```
408 | [1]: https://github.com/lelandrichardson
409 |
--------------------------------------------------------------------------------
/codegen/index.js:
--------------------------------------------------------------------------------
1 | var Template = require('underscore').template;
2 | var fs = require('fs');
3 | var path = require('path');
4 | var colors = require('colors');
5 | var commander = require('commander');
6 |
7 | var FLUX_DIR, CONSTANTS_DIR, ACTIONS_DIR, STORES_DIR, INDEX_PATH;
8 |
9 | if( fs.existsSync('./reactflux.json') ){
10 | var configs = fs.readFileSync(path.resolve('./reactflux.json')).toString();
11 | configs = JSON.parse(configs);
12 | FLUX_DIR = configs.directory;
13 | }
14 | else{
15 | FLUX_DIR = path.resolve('./flux');
16 | }
17 |
18 | CONSTANTS_DIR = FLUX_DIR + '/constants';
19 | ACTIONS_DIR = FLUX_DIR + '/actions';
20 | STORES_DIR = FLUX_DIR + '/stores';
21 | INDEX_PATH = FLUX_DIR + '/index.js';
22 | var DID_INIT_FLUX = fs.existsSync(INDEX_PATH);
23 |
24 | function initFluxDirectory(){
25 | var directories = [
26 | FLUX_DIR,
27 | CONSTANTS_DIR,
28 | ACTIONS_DIR,
29 | STORES_DIR
30 | ];
31 | for(var i = 0; i < directories.length; i++){
32 | if( !fs.existsSync(directories[i]) ){
33 | log("Creating directory: " + directories[i]);
34 | fs.mkdirSync(directories[i]);
35 | }
36 | }
37 | if( !fs.existsSync(INDEX_PATH) ){
38 | var contents = getDefaultIndexFile();
39 | fs.writeFileSync(INDEX_PATH, contents);
40 | }
41 | }
42 |
43 |
44 | function logError(error){
45 | console.error(error.red);
46 | }
47 |
48 | function log(msg){
49 | console.log(msg.blue);
50 | }
51 |
52 | function getTemplate(templateName){
53 | var templatePath = path.resolve(__dirname + '/templates/' + templateName + '.js');
54 | var content = fs.readFileSync(templatePath).toString();
55 | return Template(content);
56 | }
57 |
58 | function getFluxFilePaths(name){
59 | return {
60 | constants: path.resolve(CONSTANTS_DIR + '/' + name + '.js'),
61 | actions: path.resolve(ACTIONS_DIR + '/' + name + '.js'),
62 | store: path.resolve(STORES_DIR + '/' + name + '.js'),
63 | module: path.resolve(FLUX_DIR + '/' + name + '.js')
64 | };
65 | }
66 |
67 | function getDefaultConstantsFile(name){
68 | var template = getTemplate('constants');
69 | return template({
70 | PREFIX: ", '" + name.toUpperCase() + "'"
71 | });
72 | }
73 |
74 | function getDefaultActionsFile(name){
75 | var template = getTemplate('actions');
76 | return template({
77 | name: name
78 | });
79 | }
80 |
81 | function getDefaultStoreFile(name){
82 | var template = getTemplate('store');
83 | return template({
84 | name: name
85 | });
86 | }
87 |
88 | function getDefaultModuleFile(name){
89 | var template = getTemplate('module');
90 | return template({
91 | name: name
92 | });
93 | }
94 |
95 | function getDefaultIndexFile(){
96 | var template = getTemplate('index');
97 | return template();
98 | }
99 |
100 |
101 | function createFlux(name){
102 | var fluxPaths = getFluxFilePaths(name);
103 | if (fs.existsSync(fluxPaths.module)) {
104 | logError("FILE EXISTS: " + fluxPaths.module);
105 | return;
106 | }
107 | if (fs.existsSync(fluxPaths.constants)) {
108 | logError("FILE EXISTS: " + fluxPaths.constants);
109 | return;
110 | }
111 | if (fs.existsSync(fluxPaths.actions)) {
112 | logError("FILE EXISTS: " + fluxPaths.actions);
113 | return;
114 | }
115 | if (fs.existsSync(fluxPaths.store)) {
116 | logError("FILE EXISTS: " + fluxPaths.store);
117 | return;
118 | }
119 | log("Creating file: " + fluxPaths.module);
120 | fs.writeFileSync(fluxPaths.module, getDefaultModuleFile(name));
121 | log("Creating file: " + fluxPaths.constants);
122 | fs.writeFileSync(fluxPaths.constants, getDefaultConstantsFile(name));
123 | log("Creating file: " + fluxPaths.actions);
124 | fs.writeFileSync(fluxPaths.actions, getDefaultActionsFile(name));
125 | log("Creating file: " + fluxPaths.store);
126 | fs.writeFileSync(fluxPaths.store, getDefaultStoreFile(name));
127 | addFluxToIndex(name);
128 | }
129 |
130 | function addFluxToIndex(name){
131 | console.log("ADDING TO INDEX");
132 | var contents = fs.readFileSync(INDEX_PATH).toString();
133 | var lines = contents.split("\n");
134 | var newLines = [];
135 | for(var i=0, len = lines.length; i < len; i++){
136 | newLines.push(lines[i]);
137 | if( lines[i].match('module\\.exports') ){
138 | newLines.push( "\t" + name + ": require('./" + name + "')," );
139 | }
140 | }
141 | fs.writeFileSync(INDEX_PATH, newLines.join("\n"));
142 | }
143 |
144 | function createConstant(filePath, constantName) {
145 | constantName = constantName.toUpperCase();
146 | if (!fs.existsSync(filePath)) {
147 | logError("FILE DOES NOT EXIST: " + filePath);
148 | return false;
149 | }
150 | var fileContents = fs.readFileSync(filePath).toString();
151 | if (fileContents.match("'" + constantName + "'")) {
152 | var msg = "ERROR: Constant '" + constantName + "' seems to exists in " + filePath + "";
153 | logError(msg);
154 | return false;
155 | }
156 | var lines = fileContents.split('\n');
157 | var newLines = [];
158 | for (var i = 0, len = lines.length; i < len; i++) {
159 | var line = lines[i].trim();
160 | newLines.push(lines[i]);
161 | if (lines[i].match('createConstants')) {
162 | var nextLine = lines[i + 1].trim();
163 | if (nextLine.match('\'')) {
164 | newLines.push("\t'" + constantName + "',");
165 | } else {
166 | newLines.push("\t'" + constantName + "'");
167 | }
168 | }
169 | }
170 | fs.writeFileSync(filePath, newLines.join("\n"));
171 | return true;
172 | }
173 |
174 | function createAction(filePath, constantName) {
175 | constantName = constantName.toUpperCase();
176 | if (!fs.existsSync(filePath)) {
177 | logError("FILE DOES NOT EXIST: " + filePath);
178 | return false;
179 | }
180 | var ps = constantName.toLowerCase().split('_');
181 | var actionName = ps[0];
182 | for(var i=1; i < ps.length; i++){
183 | actionName += ps[i].charAt(0).toUpperCase() + ps[i].substr(1);
184 | }
185 | var fileContents = fs.readFileSync(filePath).toString();
186 | if (fileContents.match(actionName + ":")) {
187 | logError("ERROR: Action for '" + constantName + "' seems to exists in " + filePath);
188 | return false;
189 | }
190 |
191 | var lines = fileContents.split('\n');
192 | var newLines = [];
193 | for (var i = 0, len = lines.length; i < len; i++) {
194 | newLines.push(lines[i]);
195 | if (lines[i].match(".createActions")) {
196 | var template = getTemplate('action');
197 | var contents = template({
198 | actionName: actionName,
199 | constant: constantName
200 | });
201 | newLines.push(contents);
202 | }
203 | }
204 | fs.writeFileSync(filePath, newLines.join("\n"));
205 | return true;
206 | }
207 |
208 | function createActionHandler(filePath, constantName) {
209 | constantName = constantName.toUpperCase();
210 | if (!fs.existsSync(filePath)) {
211 | logError("FILE DOES NOT EXIST: " + filePath);
212 | return false;
213 | }
214 | var fileContents = fs.readFileSync(filePath).toString();
215 | var expr = "addActionHandler\\(constants\\." + constantName;
216 | if ( fileContents.match(expr) ) {
217 | logError("ERROR: ActionHandler for '" + constantName + "' seems to exists in " + filePath);
218 | return false;
219 | }
220 | var lines = fileContents.split('\n');
221 | var newLines = [];
222 | for (var i = 0, len = lines.length; i < len; i++) {
223 | if (lines[i].match('module\\.exports')) {
224 | var template = getTemplate('actionHandler');
225 | var handlerContents = template({
226 | constant: constantName
227 | });
228 | newLines.push(handlerContents);
229 | }
230 | newLines.push(lines[i]);
231 | }
232 | fs.writeFileSync(filePath, newLines.join("\n"));
233 | return true;
234 | }
235 |
236 |
237 | commander.version('0.0.1');
238 |
239 | commander
240 | .command('init')
241 | .description('initiates flux')
242 | .action(function(){
243 | initFluxDirectory();
244 | });
245 |
246 | commander
247 | .command('flux ')
248 | .description('Creates a flux group(constants, actions and store)')
249 | .action(function (fluxName) {
250 | if( !DID_INIT_FLUX ){
251 | logError('You did not init flux. run init command');
252 | return;
253 | }
254 | createFlux(fluxName);
255 | });
256 |
257 | commander
258 | .command('constant ')
259 | .option('-a', 'create a corresponding action')
260 | .option('-s', 'create a corresponding storeActionHandler')
261 | .description('Creates a constant')
262 | .action(function (fluxName, constant, opts) {
263 | if( !DID_INIT_FLUX ){
264 | logError('You did not init flux. run init command');
265 | return;
266 | }
267 | var paths = getFluxFilePaths(fluxName);
268 | if( createConstant(paths.constants, constant) ){
269 | log('Created a new constant [' + constant + '] in [' + fluxName + ']');
270 | }
271 | if( !!opts.A && createAction(paths.actions, constant) ){
272 | log('Created an action for constant [' + constant + ']');
273 | }
274 | if( !!opts.S && createActionHandler(paths.store, constant) ){
275 | log('Created an actionHandler for constant [' + constant + ']');
276 | }
277 | });
278 |
279 | commander.on('--help', function(){
280 | console.log(' Options for constant creation:');
281 | console.log(' -a: creates a corresponding action');
282 | console.log(' -h: creates a corresponding storeActionHandler');
283 | console.log('');
284 | console.log(" Examples:");
285 | var progName = module.parent.filename.split('/').pop();
286 | console.log(" Init flux directory:");
287 | console.log(" ./" + progName + ' init\n');
288 | console.log(" Create user flux group:");
289 | console.log(" ./" + progName + ' flux user\n');
290 | console.log(" Create a constant[LOAD] for user group:");
291 | console.log(" ./" + progName + ' constant user LOAD\n');
292 | console.log(" Create a constant[LOAD] for user group along with a corresponding action:");
293 | console.log(" ./" + progName + ' constant -a user LOAD\n');
294 | console.log(" Create a constant[LOAD] for user group along with a corresponding store handler:");
295 | console.log(" ./" + progName + ' constant -s user LOAD\n');
296 | console.log(" Create a constant[LOAD] for user group along with corresponding action and store handler:");
297 | console.log(" ./" + progName + ' constant -as user LOAD\n');
298 | });
299 |
300 | commander.parse(process.argv);
301 |
--------------------------------------------------------------------------------
/codegen/templates/action.js:
--------------------------------------------------------------------------------
1 |
2 | <%= actionName %>: [constants.<%= constant%>, function(){
3 | return {};
4 | }],
5 |
--------------------------------------------------------------------------------
/codegen/templates/actionHandler.js:
--------------------------------------------------------------------------------
1 |
2 | Store.addActionHandler(constants.<%= constant %>, {
3 |
4 | getInitialState: function(){
5 | return {
6 |
7 | };
8 | },
9 |
10 | before: function(){
11 | this.setState(
12 | this.getInitialState()
13 | );
14 | },
15 |
16 | success: function(resp){
17 |
18 | },
19 |
20 | fail: function(resp){
21 |
22 | },
23 |
24 | after: function(){
25 |
26 | }
27 |
28 | });
--------------------------------------------------------------------------------
/codegen/templates/actions.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('react-flux');
2 | var constants = require('../constants/<%= name %>');
3 |
4 | var Actions = ReactFlux.createActions({
5 |
6 | });
7 |
8 | module.exports = Actions;
--------------------------------------------------------------------------------
/codegen/templates/constants.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('react-flux');
2 |
3 | var Constants = ReactFlux.createConstants([
4 | ]<%= PREFIX %>);
5 |
6 | module.exports = Constants;
--------------------------------------------------------------------------------
/codegen/templates/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | };
--------------------------------------------------------------------------------
/codegen/templates/module.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | constants: require('./constants/<%= name %>.js'),
3 | actions: require('./actions/<%= name %>.js'),
4 | store: require('./stores/<%= name %>.js'),
5 | };
--------------------------------------------------------------------------------
/codegen/templates/store.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('react-flux');
2 | var constants = require('../constants/<%= name %>');
3 |
4 | var Store = ReactFlux.createStore({
5 |
6 | displayName: '<%= name %>Store',
7 |
8 | getInitialState: function(){
9 | return {
10 | };
11 | }
12 |
13 | });
14 |
15 |
16 | module.exports = Store;
--------------------------------------------------------------------------------
/dist/react-flux.js:
--------------------------------------------------------------------------------
1 | !function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n(require("promise")):"function"==typeof define&&define.amd?define(["promise"],n):"object"==typeof exports?exports.ReactFlux=n(require("promise")):t.ReactFlux=n(t.promise)}(this,function(t){return function(t){function n(r){if(e[r])return e[r].exports;var o=e[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,n),o.loaded=!0,o.exports}var e={};return n.m=t,n.c=e,n.p="",n(0)}([function(t,n,e){var r=e(28),o=e(31),i=e(45),s=e(1),a=e(56),c=e(30),u=new a;t.exports={configs:c,createActions:function(t){return new o(u,t)},createStore:function(t,n){return new i(u,t,n)},createConstants:function(t,n){return new r(t,n)},dispatch:function(t,n){u.dispatch(t,n)},dispatcher:u,mixinFor:s}},function(t,n,e){var r=e(2);t.exports=function(){var t=Array.prototype.slice.call(arguments);if(!t.length)throw new Error("Flux.mixinFor expects a store or a list of stores");return r(t,function(t){var n="undefined"==typeof t||"function"!=typeof t.onChange||"function"!=typeof t.offChange;if(n)throw new Error("Flux.mixinFor expects a store or an array of stores")}),{componentWillMount:function(){"undefined"==typeof this._react_flux_onChange&&(this._react_flux_onChange=function(){this.isMounted()&&this.setState(this.getStateFromStores())}.bind(this)),this.setState(this.getStateFromStores())},componentDidMount:function(){for(var n=0;n-1&&t%1==0&&o>=t}var o=9007199254740991;t.exports=r},function(t,n,e){function r(t){for(var n=c(t),e=n.length,r=e&&t.length,u=!!r&&a(r)&&(i(t)||o(t)),p=-1,l=[];++p-1&&t%1==0&&n>t}var o=/^\d+$/,i=9007199254740991;t.exports=r},function(t,n,e){function r(t){if(null==t)return[];c(t)||(t=Object(t));var n=t.length;n=n&&a(n)&&(i(t)||o(t))&&n||0;for(var e=t.constructor,r=-1,u="function"==typeof e&&e.prototype===t,p=Array(n),l=n>0;++r0&&(n+=s.separator);var e={};return r(t,function(t){if(!i(t))throw new Error("createConstants expects all constants to be strings");e[t]=n+t,e[t+"_"+s.successSuffix]=n+t+s.separator+s.successSuffix,e[t+"_"+s.failSuffix]=n+t+s.separator+s.failSuffix,e[t+"_"+s.afterSuffix]=n+t+s.separator+s.afterSuffix}),e}},function(t,n,e){function r(t){return"string"==typeof t||o(t)&&a.call(t)==i}var o=e(14),i="[object String]",s=Object.prototype,a=s.toString;t.exports=r},function(t,n,e){var r=e(29),o="_",i="SUCCESS",s="FAIL",a="AFTER",c={constants:{separator:o,successSuffix:i,failSuffix:s,afterSuffix:a}};t.exports={constants:{setSeparator:function(t){if(!r(t)||!t.length)throw new Error("Constants.separator must be a non empty string");c.constants.separator=t},setSuccessSuffix:function(t){if(!r(t)||!t.length)throw new Error("Constants.successSuffix must be a non empty string");c.constants.successSuffix=t},setFailSuffix:function(t){if(!r(t)||!t.length)throw new Error("Constants.failSuffix must be a non empty string");c.constants.failSuffix=t},setAfterSuffix:function(t){if(!r(t)||!t.length)throw new Error("Constants.afterSuffix must be a non empty string");c.constants.afterSuffix=t},resetToDefaults:function(){c.constants.separator=o,c.constants.successSuffix=i,c.constants.failSuffix=s,c.constants.afterSuffix=a},get:function(){return c.constants}}}},function(t,n,e){var r=e(32),o=e(2),i=e(21),s=s||e(44),a=e(30).constants.get(),c=function(t,n){this._dispatcher=t,this._registerActions(n)};c.prototype=r(c.prototype,{_registerActions:function(t){o(t,function(t,n){if(!i(t))throw new Error("ReactFlux.Actions: Action must be an array {login: [CONSTANT, callback]}");var e=t[0],r=t[1];if("undefined"==typeof r)r=function(){};else if("function"!=typeof r)throw new Error("ReactFlux.Actions: you did not provide a valid callback for action: "+n);this[n]=this._createAction(n,e,r)}.bind(this))},_createAction:function(t,n,e){return function(){this._dispatch(n,null,arguments);var t=null;try{if(t=e.apply(this,arguments),t&&"object"==typeof t&&"[object Error]"==Object.prototype.toString.call(t))throw t}catch(r){t=new s(function(t,n){n(r)})}s.resolve(t).then(function(t){this._dispatch(n,"successSuffix",t),this._dispatch(n,"afterSuffix",t)}.bind(this),function(t){this._dispatch(n,"failSuffix",t),this._dispatch(n,"afterSuffix",t)}.bind(this))["catch"](function(t){console.error(t.toString(),t.stack)})}.bind(this)},_dispatch:function(t,n,e){n&&(t+=a.separator+a[n]),this._dispatcher.dispatch(t,e)}}),t.exports=c},function(t,n,e){var r=e(33),o=e(41),i=o(r);t.exports=i},function(t,n,e){function r(t,n,e,l,h){if(!c(t))return t;var d=a(n)&&(s(n)||f(n)),v=d?void 0:p(n);return o(v||n,function(o,s){if(v&&(s=o,o=n[s]),u(o))l||(l=[]),h||(h=[]),i(t,n,s,r,e,l,h);else{var a=t[s],c=e?e(a,o,s,t,n):void 0,f=void 0===c;f&&(c=o),void 0===c&&(!d||s in t)||!f&&(c===c?c===a:a!==a)||(t[s]=c)}}),t}var o=e(3),i=e(34),s=e(21),a=e(15),c=e(9),u=e(14),f=e(38),p=e(10);t.exports=r},function(t,n,e){function r(t,n,e,r,p,l,h){for(var d=l.length,v=n[e];d--;)if(l[d]==v)return void(t[e]=h[d]);var g=t[e],y=p?p(g,v,e,t,n):void 0,b=void 0===y;b&&(y=v,a(v)&&(s(v)||u(v))?y=s(g)?g:a(g)?o(g):[]:c(v)||i(v)?y=i(g)?f(g):c(g)?g:{}:b=!1),l.push(v),h.push(y),b?t[e]=r(y,v,p,l,h):(y===y?y!==g:g===g)&&(t[e]=y)}var o=e(35),i=e(20),s=e(21),a=e(15),c=e(36),u=e(38),f=e(39);t.exports=r},function(t,n,e){function r(t,n){var e=-1,r=t.length;for(n||(n=Array(r));++e2?e[s-2]:void 0,c=s>2?e[2]:void 0,u=s>1?e[s-1]:void 0;for("function"==typeof a?(a=o(a,u,5),s-=2):(a="function"==typeof u?u:void 0,s-=a?1:0),c&&i(e[0],e[1],c)&&(a=3>s?void 0:a,s=1);++re;e++)if(this._events[t][e]===n){this._events[t].splice(e,1);break}},_emit:function(t){if("undefined"!=typeof this._events[t]){var n=Array.prototype.slice.call(arguments,1);i(this._events[t],function(t){"function"==typeof t&&t.apply(null,n)})}}},t.exports=p},function(t,n,e){function r(t,n,e){return"function"==typeof n?o(t,!0,i(n,e,3)):o(t,!0)}var o=e(47),i=e(26);t.exports=r},function(t,n,e){function r(t,n,e,d,v,g,y){var x;if(e&&(x=v?e(t,d,v):e(t)),void 0!==x)return x;if(!l(t))return t;var _=p(t);if(_){if(x=c(t),!n)return o(t,x)}else{var j=B.call(t),w=j==b;if(j!=S&&j!=h&&(!w||v))return R[j]?u(t,j,n):v?t:{};if(x=f(w?{}:t),!n)return s(x,t)}g||(g=[]),y||(y=[]);for(var m=g.length;m--;)if(g[m]==t)return y[m];return g.push(t),y.push(x),(_?i:a)(t,function(o,i){x[i]=r(o,n,e,i,t,g,y)}),x}var o=e(35),i=e(3),s=e(48),a=e(5),c=e(49),u=e(50),f=e(52),p=e(21),l=e(9),h="[object Arguments]",d="[object Array]",v="[object Boolean]",g="[object Date]",y="[object Error]",b="[object Function]",x="[object Map]",_="[object Number]",S="[object Object]",j="[object RegExp]",w="[object Set]",m="[object String]",A="[object WeakMap]",E="[object ArrayBuffer]",C="[object Float32Array]",I="[object Float64Array]",F="[object Int8Array]",O="[object Int16Array]",D="[object Int32Array]",H="[object Uint8Array]",k="[object Uint8ClampedArray]",M="[object Uint16Array]",U="[object Uint32Array]",R={};R[h]=R[d]=R[E]=R[v]=R[g]=R[C]=R[I]=R[F]=R[O]=R[D]=R[_]=R[S]=R[j]=R[m]=R[H]=R[k]=R[M]=R[U]=!0,R[y]=R[b]=R[x]=R[w]=R[A]=!1;var P=Object.prototype,B=P.toString;t.exports=r},function(t,n,e){function r(t,n){return null==n?t:o(n,i(n),t)}var o=e(40),i=e(10);t.exports=r},function(t,n,e){function r(t){var n=t.length,e=new t.constructor(n);return n&&"string"==typeof t[0]&&i.call(t,"index")&&(e.index=t.index,e.input=t.input),e}var o=Object.prototype,i=o.hasOwnProperty;t.exports=r},function(t,n,e){function r(t,n,e){var r=t.constructor;switch(n){case f:return o(t);case i:case s:return new r(+t);case p:case l:case h:case d:case v:case g:case y:case b:case x:var S=t.buffer;return new r(e?o(S):S,t.byteOffset,t.length);case a:case u:return new r(t);case c:var j=new r(t.source,_.exec(t));j.lastIndex=t.lastIndex}return j}var o=e(51),i="[object Boolean]",s="[object Date]",a="[object Number]",c="[object RegExp]",u="[object String]",f="[object ArrayBuffer]",p="[object Float32Array]",l="[object Float64Array]",h="[object Int8Array]",d="[object Int16Array]",v="[object Int32Array]",g="[object Uint8Array]",y="[object Uint8ClampedArray]",b="[object Uint16Array]",x="[object Uint32Array]",_=/\w*$/;t.exports=r},function(t,n,e){(function(n){function e(t){var n=new r(t.byteLength),e=new o(n);return e.set(new o(t)),n}var r=n.ArrayBuffer,o=n.Uint8Array;t.exports=e}).call(n,function(){return this}())},function(t,n,e){function r(t){var n=t.constructor;return"function"==typeof n&&n instanceof n||(n=Object),new n}t.exports=r},function(t,n,e){var r=e(54),o=e(48),i=e(41),s=i(function(t,n,e){return e?r(t,n,e):o(t,n)});t.exports=s},function(t,n,e){function r(t,n,e){for(var r=-1,i=o(n),s=i.length;++rc;c++){var u=s[c];if(null!==this[u]){if("function"!=typeof this[u])throw new Error('StoreActionHandler expects "'+u+'" to be a function');var f=this.constant;"before"!==u&&(f+=i.separator+i[u+"Suffix"]),r.push([f,this[u].bind(this)])}}t._setConstantHandlers(r)}var o=e(29),i=e(30).constants.get(),s=["before","after","success","fail"];r.prototype={setState:function(t){this.parent.setActionState(this.constant,t)},getState:function(){return this.parent.getActionState(this.constant)}},t.exports=r},function(t,n,e){function r(){this._registry={}}var o=o||e(44),i=e(32),s=e(21),a=e(29),c=e(2);r.prototype=i(r.prototype,{register:function(t,n,e){if(!a(t)||!t.length)throw new Error("Dispatcher.register: constant must be a string");if(e=e||null,"function"!=typeof n)throw new Error("Dispatcher.register expects second parameter to be a callback");if(null!==e&&!s(e))throw new Error("Dispatcher.register expects third parameter to be null or an array");var r=this._getRegistry(t);return r.callbacks.push(n),r.waitFor.push(e),r.callbacks.length-1},dispatch:function(t,n){var e=this._getRegistry(t);e.dispatchQueue.push({constant:t,payload:n}),this._dispatch(e)},_dispatch:function(t){if(!t.isDispatching&&t.dispatchQueue.length){t.isDispatching=!0;var n=t.dispatchQueue.shift();this._createDispatchPromises(t),c(t.callbacks,function(e,r){var i=function(t,n,e){return function(){o.resolve(t.callbacks[n](e)).then(function(){t.resolves[n](e)},function(){t.rejects[n](new Error("Dispatch callback error"))})}}(t,r,n.payload),s=t.waitFor[r];if(s){var a=this._getPromisesByIndexes(t,s);o.all(a).then(i,i)}else i()}.bind(this)),o.all(t.promises).then(function(){this._onDispatchEnd(t)}.bind(this),function(){this._onDispatchEnd(t)})}},_getRegistry:function(t){return"undefined"==typeof this._registry[t]&&(this._registry[t]={callbacks:[],waitFor:[],promises:[],resolves:[],rejects:[],dispatchQueue:[],isDispatching:!1}),this._registry[t]},_getPromisesByIndexes:function(t,n){return n.map(function(n){return t.promises[n]})},_createDispatchPromises:function(t){t.promises=[],t.resolves=[],t.rejects=[],c(t.callbacks,function(n,e){t.promises[e]=new o(function(n,r){t.resolves[e]=n,t.rejects[e]=r})})},_onDispatchEnd:function(t){t.promises=[],t.resolves=[],t.rejects=[],t.isDispatching=!1,this._dispatch(t)}}),t.exports=r}])});
--------------------------------------------------------------------------------
/examples/example1/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
--------------------------------------------------------------------------------
/examples/example1/flux/actions/user.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../../../../index');
2 | var userConstants = require('../constants/user');
3 |
4 | module.exports = ReactFlux.createActions({
5 |
6 | /**
7 | * Action may return a value(SUCCESS PAYLOAD), an error, or a promise
8 | */
9 | login: [userConstants.LOGIN, function(username, password){
10 | console.log("UserActions.login", username, password);
11 | var promise = new Promise(function(resolve, reject){
12 | setTimeout(function(){
13 | if( username.length ){
14 | resolve({
15 | id: 1,
16 | username: username
17 | });
18 | }
19 | else{
20 | reject(new Error('Invalid login'));
21 | }
22 | }, 500);
23 | });
24 | return promise;
25 | }],
26 |
27 |
28 | /**
29 | * An actoin without a callback will always be successful
30 | */
31 | logout: [userConstants.LOGOUT]
32 |
33 |
34 | });
35 |
--------------------------------------------------------------------------------
/examples/example1/flux/constants/user.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../../../../index');
2 |
3 | module.exports = ReactFlux.createConstants([
4 | 'LOGIN',
5 | 'LOGOUT'
6 | ], 'USER');
--------------------------------------------------------------------------------
/examples/example1/flux/stores/father.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../../../../index');
2 | var userConstants = require('../constants/user');
3 |
4 | var MotherStore = require('./mother');
5 |
6 | var Store = ReactFlux.createStore(null, [
7 |
8 | [userConstants.LOGIN_SUCCESS, [MotherStore], function notifyFatherOfLogin(payload){
9 |
10 | console.log("I'm Father, I need 1 sec to answer!");
11 |
12 | var promise = new Promise(function(resolve, reject){
13 | setTimeout(function(){
14 | console.log("I am Father, I have been notifed of login! Username: ", payload.username);
15 | resolve();
16 | }, 1000);
17 | });
18 |
19 | return promise;
20 |
21 | }]
22 |
23 | ]);
24 |
25 |
26 | module.exports = Store;
--------------------------------------------------------------------------------
/examples/example1/flux/stores/mother.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../../../../index');
2 | var userConstants = require('../constants/user');
3 |
4 |
5 | var Store = ReactFlux.createStore({
6 |
7 | }, [
8 |
9 | [userConstants.LOGIN_SUCCESS, function NotifyMotherOfLogin(payload){
10 |
11 | console.log("I'm Mother, I need 0.5 sec to answer!");
12 |
13 | var promise = new Promise(function(resolve, reject){
14 | setTimeout(function(){
15 | console.log("I'm Mother, I have been notified of login! Username:", payload.username);
16 | resolve();
17 | }, 500);
18 | });
19 |
20 | return promise;
21 |
22 | }]
23 |
24 | ]);
25 |
26 | module.exports = Store;
--------------------------------------------------------------------------------
/examples/example1/flux/stores/user.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../../../../index');
2 | var userConstants = require('../constants/user');
3 |
4 | var MotherStore = require('./mother');
5 | var FatherStore = require('./father');
6 |
7 | var Store = ReactFlux.createStore({
8 |
9 | getInitialState: function(){
10 | console.log("UserStore.getInitialState");
11 | return {
12 | data: null,
13 | isAuth: false,
14 | error: null
15 | }
16 | },
17 |
18 | storeDidMount: function(){
19 | console.log("UserStore.storeDidMount");
20 | },
21 |
22 | getUsername: function(){
23 | return this.get('isAuth') ? this.get('data').username : null;
24 | }
25 |
26 | }, [
27 |
28 | /**
29 | * Dispatcher calls this directly when it receives a USER_LOGIN message,
30 | * just before it tries to execute the corresponding action
31 | */
32 | [userConstants.LOGIN, function onLogin(payload){
33 | console.log("UserStore.onLogin", JSON.stringify(payload));
34 | this.setState({
35 | isLoggingIn: true
36 | });
37 | }],
38 |
39 | /**
40 | * This gets called if USER_LOGIN action was successfull
41 | * This store waits for MotherStore and FatherStore to process this message
42 | */
43 | [userConstants.LOGIN_SUCCESS, [MotherStore, FatherStore], function handleLoginSuccess(payload){
44 | console.log("UserStore.handleLogin", JSON.stringify(payload));
45 | this.setState({
46 | isLoggingIn: false,
47 | error: null,
48 | data: payload,
49 | isAuth: true
50 | });
51 | }],
52 |
53 | /**
54 | * This gets called if USER_LOGIN action was unsuccessful
55 | */
56 | [userConstants.LOGIN_FAIL, function handleLoginFailure(error){
57 | console.log("UserStore.handleLoginFailure", error.message);
58 | this.setState({
59 | isLoggingIn: false,
60 | error: error.message
61 | });
62 | }]
63 |
64 | ]);
65 |
66 | Store.addActionHandler(userConstants.LOGOUT, {
67 | success: function(){
68 | console.log("UserStore.handleLogout");
69 | this.parent.setState({
70 | isAuth: false,
71 | data: null
72 | });
73 | }
74 | });
75 |
76 | module.exports = Store;
77 |
--------------------------------------------------------------------------------
/examples/example1/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var browserify = require('gulp-browserify');
3 | var react = require('gulp-react');
4 | var plumber = require('gulp-plumber');
5 | var concat = require('gulp-concat');
6 |
7 | gulp.task('default', function(cb){
8 | return gulp.src([
9 | './index.jsx'
10 | ])
11 | .pipe(plumber())
12 | .pipe(react({ addPragma: false }))
13 | .pipe(browserify({
14 | insertGlobals: true,
15 | debug: false
16 | }))
17 | .pipe(concat('index.js'))
18 | .pipe(gulp.dest('./'));
19 | });
20 |
--------------------------------------------------------------------------------
/examples/example1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/example1/index.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var userStore = require('./flux/stores/user');
3 | var userActions = require('./flux/actions/user');
4 |
5 | var Flux = require('../../');
6 | var App = React.createClass({
7 |
8 | mixins: [
9 | userStore.mixinFor()
10 | ],
11 |
12 | getStateFromStores: function(){
13 | console.log("App.getStateFromStores");
14 | return {
15 | user: userStore.getState()
16 | };
17 | },
18 |
19 | login: function(){
20 | var username = this.refs.username.getDOMNode().value;
21 | userActions.login(username, '1234567');
22 | return false;
23 | },
24 |
25 | logout: function(){
26 | userActions.logout();
27 | return false;
28 | },
29 |
30 | render: function(){
31 | if( !this.state.user.isAuth ){
32 | return this.renderLogin();
33 | }
34 | return this.renderHome();
35 | },
36 |
37 | renderHome: function(){
38 | return (
39 |
40 |
Hello {this.state.user.data.username}!
41 |
Logout
42 |
43 | );
44 | },
45 |
46 | renderLogin: function(){
47 | if( this.state.user.isLoggingIn ){
48 | return(Logging in...
);
49 | }
50 | return(
51 |
52 |
LOGIN
53 | Username: Leave empty to cause an error
54 |
55 | Click to login
56 | {this.renderLoginError()}
57 |
58 | );
59 | },
60 |
61 | renderLoginError: function(){
62 | if( !this.state.user.error ){
63 | return;
64 | }
65 | return ({this.state.user.error}
)
66 | }
67 |
68 | });
69 |
70 | window.onload = function(){
71 | React.renderComponent( , document.getElementById('__wrap'));
72 | };
73 |
--------------------------------------------------------------------------------
/examples/example1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ReactFluxExample",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [
10 | "React",
11 | "Flux"
12 | ],
13 | "author": "Khaled Jouda",
14 | "license": "MIT",
15 | "dependencies": {
16 | "react": "~0.11.0",
17 | "gulp": "~3.8.6",
18 | "gulp-browserify": "~0.5.0",
19 | "gulp-plumber": "~0.6.4",
20 | "gulp-react": "~0.5.0",
21 | "gulp-concat": "~2.3.3"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/index');
--------------------------------------------------------------------------------
/lib/actions.js:
--------------------------------------------------------------------------------
1 | var _merge = require('lodash/object/merge');
2 | var _forEach = require('lodash/collection/forEach');
3 | var _isArray = require('lodash/lang/isArray');
4 | var Promise = Promise || require('promise');
5 |
6 | var constantsConfigs = require('./configs').constants.get();
7 |
8 | var Actions = function (dispather, actions) {
9 |
10 | this._dispatcher = dispather;
11 | this._registerActions(actions);
12 |
13 | };
14 |
15 | Actions.prototype = _merge(Actions.prototype, {
16 |
17 | /**
18 | *@param {object} actions
19 | */
20 | _registerActions: function (actions) {
21 | _forEach(actions, function (options, actionName) {
22 | if (!_isArray(options)) {
23 | throw new Error('ReactFlux.Actions: Action must be an array {login: [CONSTANT, callback]}');
24 | }
25 | var constant = options[0];
26 | var callback = options[1];
27 | if (typeof callback === "undefined") {
28 | callback = function () {
29 | };
30 | }
31 | else if (typeof callback != 'function') {
32 | throw new Error('ReactFlux.Actions: you did not provide a valid callback for action: ' + actionName);
33 | }
34 | this[actionName] = this._createAction(actionName, constant, callback);
35 | }.bind(this));
36 | },
37 |
38 | /**
39 | *@param {string} name
40 | *@param {string} constant
41 | *@param {string} callback
42 | */
43 | _createAction: function (name, constant, callback) {
44 | return function () {
45 | this._dispatch(constant, null, arguments);
46 | var resp = null;
47 | try {
48 | resp = callback.apply(this, arguments);
49 | if (!!resp && typeof resp == 'object' && Object.prototype.toString.call(resp) == '[object Error]') {
50 | throw resp;
51 | }
52 | } catch (e) {
53 | resp = new Promise(function (_, reject) {
54 | reject(e);
55 | });
56 | }
57 | Promise.resolve(resp).then(function (payload) {
58 | this._dispatch(constant, 'successSuffix', payload);
59 | this._dispatch(constant, 'afterSuffix', payload);
60 | }.bind(this), function (payload) {
61 | this._dispatch(constant, 'failSuffix', payload);
62 | this._dispatch(constant, 'afterSuffix', payload);
63 | }.bind(this))
64 | .catch(function (e) {
65 | console.error(e.toString(), e.stack);
66 | });
67 | }.bind(this);
68 | },
69 |
70 | /**
71 | *@param {string} constant
72 | *@param {string} suffixName
73 | *@param {mixed} payload
74 | */
75 | _dispatch: function (constant, suffixName, payload) {
76 | if (!!suffixName) {
77 | constant += constantsConfigs.separator + constantsConfigs[suffixName];
78 | }
79 | this._dispatcher.dispatch(constant, payload);
80 | }
81 |
82 | });
83 |
84 | module.exports = Actions;
85 |
--------------------------------------------------------------------------------
/lib/configs.js:
--------------------------------------------------------------------------------
1 | var _isString = require('lodash/lang/isString');
2 |
3 | var CONSTANTS_DEFAULT_SEPARATOR = '_';
4 | var CONSTANTS_DEFAULT_SUCCESS_SUFFIX = 'SUCCESS';
5 | var CONSTANTS_DEFAULT_FAIL_SUFFIX = 'FAIL';
6 | var CONSTANTS_DEFAULT_AFTER_SUFFIX = 'AFTER';
7 |
8 | var CONFIGS = {
9 | constants: {
10 | separator: CONSTANTS_DEFAULT_SEPARATOR,
11 | successSuffix: CONSTANTS_DEFAULT_SUCCESS_SUFFIX,
12 | failSuffix: CONSTANTS_DEFAULT_FAIL_SUFFIX,
13 | afterSuffix: CONSTANTS_DEFAULT_AFTER_SUFFIX
14 |
15 | }
16 | };
17 |
18 | module.exports = {
19 |
20 | /**
21 | * constants
22 | */
23 | constants: {
24 |
25 | /**
26 | * @param {string} separator
27 | */
28 | setSeparator: function (separator) {
29 | if (!_isString(separator) || !separator.length) {
30 | throw new Error('Constants.separator must be a non empty string');
31 | }
32 | CONFIGS.constants.separator = separator;
33 | },
34 |
35 | /**
36 | * @param {string} suffix
37 | */
38 | setSuccessSuffix: function (suffix) {
39 | if (!_isString(suffix) || !suffix.length) {
40 | throw new Error('Constants.successSuffix must be a non empty string');
41 | }
42 | CONFIGS.constants.successSuffix = suffix;
43 | },
44 |
45 | /**
46 | * @param {string} suffix
47 | */
48 | setFailSuffix: function (suffix) {
49 | if (!_isString(suffix) || !suffix.length) {
50 | throw new Error('Constants.failSuffix must be a non empty string');
51 | }
52 | CONFIGS.constants.failSuffix = suffix;
53 | },
54 |
55 | /**
56 | * @param {string} suffix
57 | */
58 | setAfterSuffix: function (suffix) {
59 | if (!_isString(suffix) || !suffix.length) {
60 | throw new Error('Constants.afterSuffix must be a non empty string');
61 | }
62 | CONFIGS.constants.afterSuffix = suffix;
63 | },
64 |
65 | /**
66 | *
67 | */
68 | resetToDefaults: function () {
69 | CONFIGS.constants.separator = CONSTANTS_DEFAULT_SEPARATOR;
70 | CONFIGS.constants.successSuffix = CONSTANTS_DEFAULT_SUCCESS_SUFFIX;
71 | CONFIGS.constants.failSuffix = CONSTANTS_DEFAULT_FAIL_SUFFIX;
72 | CONFIGS.constants.afterSuffix = CONSTANTS_DEFAULT_AFTER_SUFFIX;
73 | },
74 |
75 | get: function () {
76 | return CONFIGS.constants;
77 | }
78 |
79 | }
80 |
81 | };
82 |
--------------------------------------------------------------------------------
/lib/constants.js:
--------------------------------------------------------------------------------
1 | var _forEach = require('lodash/collection/forEach');
2 | var _isArray = require('lodash/lang/isArray');
3 | var _isString = require('lodash/lang/isString');
4 |
5 | var cfgs = require('./configs').constants.get();
6 |
7 | module.exports = function (constants, prefix) {
8 |
9 | if (!_isArray(constants)) {
10 | throw new Error('createConstants expects first parameter to be an array of strings');
11 | }
12 |
13 | prefix = prefix || '';
14 |
15 | if (!_isString(prefix)) {
16 | throw new Error('createConstants expects second parameter string');
17 | }
18 |
19 | if (prefix.length > 0) {
20 | prefix += cfgs.separator;
21 | }
22 |
23 | var ret = {};
24 | _forEach(constants, function (constant) {
25 |
26 | if (!_isString(constant)) {
27 | throw new Error('createConstants expects all constants to be strings');
28 | }
29 |
30 | ret[constant] = prefix + constant;
31 | ret[constant + '_' + cfgs.successSuffix] = prefix + constant + cfgs.separator + cfgs.successSuffix;
32 | ret[constant + '_' + cfgs.failSuffix] = prefix + constant + cfgs.separator + cfgs.failSuffix;
33 | ret[constant + '_' + cfgs.afterSuffix] = prefix + constant + cfgs.separator + cfgs.afterSuffix;
34 | });
35 | return ret;
36 |
37 | };
38 |
--------------------------------------------------------------------------------
/lib/dispatcher.js:
--------------------------------------------------------------------------------
1 | var Promise = Promise || require('promise');
2 | var _merge = require('lodash/object/merge');
3 | var _isArray = require('lodash/lang/isArray');
4 | var _isString = require('lodash/lang/isString');
5 | var _forEach = require('lodash/collection/forEach');
6 |
7 | /**
8 | * Dispatcher
9 | */
10 | function Dispatcher() {
11 | /**
12 | * Registry of callbacks, waitFor, promises, etc
13 | * each constant has it's own array of callbacks, waitFor, promises, etc
14 | */
15 | this._registry = {};
16 | }
17 |
18 | Dispatcher.prototype = _merge(Dispatcher.prototype, {
19 |
20 | /**
21 | * Registers a callback
22 | * @param {string} constant
23 | * @param {function} callback
24 | * @param {array|null} waitFor Array indexes to callbacks
25 | */
26 | register: function (constant, callback, waitForIndexes) {
27 | if (!_isString(constant) || !constant.length) {
28 | throw new Error('Dispatcher.register: constant must be a string');
29 | }
30 | waitForIndexes = waitForIndexes || null;
31 |
32 | if (typeof callback != 'function') {
33 | throw new Error('Dispatcher.register expects second parameter to be a callback');
34 | }
35 |
36 | if (waitForIndexes !== null && !_isArray(waitForIndexes)) {
37 | throw new Error('Dispatcher.register expects third parameter to be null or an array');
38 | }
39 |
40 | var registry = this._getRegistry(constant);
41 | registry.callbacks.push(callback);
42 | registry.waitFor.push(waitForIndexes);
43 | return registry.callbacks.length - 1;
44 | },
45 |
46 |
47 | /**
48 | * Dispatch
49 | * @param {string} constant
50 | * @param {object} payload
51 | */
52 | dispatch: function (constant, payload) {
53 | var registry = this._getRegistry(constant);
54 | registry.dispatchQueue.push({
55 | constant: constant,
56 | payload: payload
57 | });
58 | this._dispatch(registry);
59 | },
60 |
61 | _dispatch: function (registry) {
62 |
63 | if (registry.isDispatching || !registry.dispatchQueue.length) {
64 | return;
65 | }
66 |
67 | registry.isDispatching = true;
68 | var job = registry.dispatchQueue.shift();
69 |
70 | this._createDispatchPromises(registry);
71 |
72 | _forEach(registry.callbacks, function (callback, idx) {
73 |
74 | var resolver = (function (registry, idx, payload) {
75 | return function () {
76 | Promise.resolve(registry.callbacks[idx](payload)).then(function () {
77 | registry.resolves[idx](payload);
78 | }, function () {
79 | registry.rejects[idx](new Error('Dispatch callback error'));
80 | });
81 | };
82 | })(registry, idx, job.payload);
83 |
84 | var waitFor = registry.waitFor[idx];
85 | if (!waitFor) {
86 | resolver();
87 | }
88 | else {
89 | var promisesToWaitFor = this._getPromisesByIndexes(registry, waitFor);
90 | Promise.all(promisesToWaitFor).then(
91 | resolver,
92 | resolver //Should we really resolve the callback here?
93 | // Some of the WaitForStores callbacks rejected the request
94 | );
95 | }
96 | }.bind(this));//_forEach(registry.callbacks,
97 |
98 | Promise.all(registry.promises).then(function () {
99 | this._onDispatchEnd(registry);
100 | }.bind(this), function () {
101 | this._onDispatchEnd(registry);
102 | });
103 |
104 | },//dispatch
105 |
106 |
107 | /**
108 | * Gets a registry for a constant
109 | * @param {string} constant
110 | */
111 | _getRegistry: function (constant) {
112 | if (typeof this._registry[constant] == "undefined") {
113 | this._registry[constant] = {
114 | callbacks: [],
115 | waitFor: [],
116 | promises: [],
117 | resolves: [],
118 | rejects: [],
119 | dispatchQueue: [],
120 | isDispatching: false
121 | };
122 | }
123 | return this._registry[constant];
124 | },
125 |
126 | /**
127 | * @param {object} registry
128 | * @param {array} callbacks
129 | */
130 | _getPromisesByIndexes: function (registry, indexes) {
131 | return indexes.map(function (idx) {
132 | return registry.promises[idx];
133 | });
134 | },
135 |
136 | /**
137 | * Create promises for all callbacks in this registry
138 | * @param {object} registry
139 | */
140 | _createDispatchPromises: function (registry) {
141 | registry.promises = [];
142 | registry.resolves = [];
143 | registry.rejects = [];
144 | _forEach(registry.callbacks, function (callback, i) {
145 | registry.promises[i] = new Promise(function (resolve, reject) {
146 | registry.resolves[i] = resolve;
147 | registry.rejects[i] = reject;
148 | });
149 | });
150 | },
151 |
152 |
153 | /**
154 | * Clean registry
155 | * @param {object} registry
156 | */
157 | _onDispatchEnd: function (registry) {
158 | registry.promises = [];
159 | registry.resolves = [];
160 | registry.rejects = [];
161 | registry.isDispatching = false;
162 | this._dispatch(registry);
163 | }
164 |
165 | });
166 |
167 | module.exports = Dispatcher;
168 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | var Constants = require('./constants');
2 | var Actions = require('./actions');
3 | var Store = require('./store');
4 | var MixinFor = require('./mixinFor');
5 | var Dispatcher = require('./dispatcher');
6 | var Configs = require('./configs');
7 |
8 | var dispatcher = new Dispatcher();
9 |
10 | module.exports = {
11 |
12 | configs: Configs,
13 | /**
14 | * createActions
15 | * @param {object} actions
16 | */
17 | createActions: function (actions) {
18 | return new Actions(dispatcher, actions);
19 | },
20 |
21 | /**
22 | * createStore
23 | * @param {object} storeMixin
24 | * @param {array} handlers
25 | */
26 | createStore: function (storeMixin, handlers) {
27 | return new Store(dispatcher, storeMixin, handlers);
28 | },
29 |
30 | /**
31 | * createConstants
32 | * @param {array} constants
33 | *
34 | */
35 | createConstants: function (constants, prefix) {
36 | return new Constants(constants, prefix);
37 | },
38 |
39 | /**
40 | * dispatch a message
41 | * @param {string} constant
42 | * @param {object} payload
43 | */
44 | dispatch: function (constant, payload) {
45 | dispatcher.dispatch(constant, payload);
46 | },
47 |
48 | /**
49 | * The global dispatcher
50 | */
51 | dispatcher: dispatcher,
52 |
53 | /**
54 | * Mixin
55 | */
56 | mixinFor: MixinFor
57 |
58 | };
59 |
--------------------------------------------------------------------------------
/lib/mixinFor.js:
--------------------------------------------------------------------------------
1 | var _forEach = require('lodash/collection/forEach');
2 |
3 |
4 | module.exports = function () {
5 | var stores = Array.prototype.slice.call(arguments);
6 | if (!stores.length) {
7 | throw new Error('Flux.mixinFor expects a store or a list of stores');
8 | }
9 | _forEach(stores, function (store) {
10 | var isNotOk = (
11 | typeof store === 'undefined' || typeof store.onChange !== 'function' || typeof store.offChange !== 'function'
12 | );
13 | if (isNotOk) {
14 | throw new Error('Flux.mixinFor expects a store or an array of stores');
15 | }
16 | });
17 |
18 |
19 | return {
20 |
21 | componentWillMount: function () {
22 | if (typeof this._react_flux_onChange === "undefined") {
23 | this._react_flux_onChange = function () {
24 | if (this.isMounted()) {
25 | this.setState(this.getStateFromStores());
26 | }
27 | }.bind(this);
28 | }
29 | this.setState(this.getStateFromStores());
30 | },
31 |
32 | componentDidMount: function () {
33 | for (var i = 0; i < stores.length; i++) {
34 | stores[i].onChange(this._react_flux_onChange);
35 | }
36 | },
37 |
38 | componentWillUnmount: function () {
39 | for (var i = 0; i < stores.length; i++) {
40 | stores[i].offChange(this._react_flux_onChange);
41 | }
42 | }
43 | };
44 |
45 | };
46 |
--------------------------------------------------------------------------------
/lib/store.js:
--------------------------------------------------------------------------------
1 | var _assign = require('lodash/object/assign');
2 | var _forEach = require('lodash/collection/forEach');
3 | var _isArray = require('lodash/lang/isArray');
4 | var _cloneDeep = require('lodash/lang/cloneDeep');
5 | var MixinFor = require('./mixinFor');
6 |
7 | var CHANGE_EVENT = 'change';
8 |
9 | var StoreActionHandler = require('./storeActionHandler');
10 |
11 |
12 | function FluxState(initState) {
13 | var _state = initState;
14 |
15 | this.assign = function (newState) {
16 | _state = _assign(_state, newState);
17 | },
18 |
19 | this.get = function (key) {
20 | return _state[key];
21 | },
22 |
23 | this.getClone = function (key) {
24 | var v = _state[key];
25 | if (v instanceof Object && v !== null) {
26 | return _cloneDeep(v);
27 | }
28 | return v;
29 | },
30 |
31 | this.replaceState = function (newState) {
32 | _state = newState;
33 | },
34 |
35 | this.getState = function () {
36 | return _state;
37 | }
38 |
39 | this.getStateClone = function () {
40 | return _cloneDeep(_state);
41 | }
42 |
43 | }
44 |
45 | /**
46 | * Flux Store
47 | * @param {object} dispatcher
48 | * @param {object} storeMixin
49 | * @param {array} handlers
50 | */
51 | var Store = function (dispatcher, storeMixin, handlers) {
52 | this.state = new FluxState({
53 | _action_states: {}
54 | });
55 | this._events = {};//used by store event emitter
56 | this._actionHandlers = {};
57 | this._constantHandlers = {};
58 | this._dispatcherIndexes = {};
59 | this._dispatcher = dispatcher;
60 | this._getInitialStateCalls = [];
61 | this._storeDidMountCalls = [];
62 |
63 | this._storeMixin(storeMixin);
64 | this._setInitialState();
65 | if (!!handlers) {
66 | this._setConstantHandlers(handlers);
67 | }
68 | _forEach(this._storeDidMountCalls, function (fn) {
69 | fn();
70 | });
71 | };
72 |
73 |
74 | Store.prototype = {
75 |
76 | /**
77 | * @param {object} newState
78 | */
79 | setState: function (newState) {
80 | this.state.assign(newState);
81 | this._emit(CHANGE_EVENT);
82 | },
83 |
84 | /**
85 | * @param {object} newState
86 | */
87 | replaceState: function (newState) {
88 | this.state.replaceState(newState);
89 | this._emit(CHANGE_EVENT);
90 | },
91 |
92 | /**
93 | * @param {string} propertyName
94 | * @return {mixed}
95 | */
96 | get: function (propertyName) {
97 | return this.state.get(propertyName);
98 | },
99 |
100 | getClone: function (propertyName) {
101 | return this.state.getClone(propertyName);
102 | },
103 |
104 | getState: function () {
105 | return this.state.getState();
106 | },
107 |
108 | getStateClone: function () {
109 | return this.state.getStateClone();
110 | },
111 |
112 |
113 | /**
114 | * @param {string} constant
115 | * @param {object} newState
116 | */
117 | setActionState: function (constant, newState) {
118 | var actionStates = this.state.get('_action_states');
119 | actionStates[constant] = _assign(actionStates[constant] || {}, newState);
120 | this.setState({
121 | '_action_states': actionStates
122 | });
123 | },
124 |
125 | /**
126 | * @param {string} constant
127 | */
128 | resetActionState: function (constant) {
129 | if (typeof this._actionHandlers[constant] === 'undefined') {
130 | throw new Error('Store.resetActionState constant handler for [' + constant + '] is not defined');
131 | }
132 | var actionStates = this.state.get('_action_states');
133 | actionStates[constant] = this._actionHandlers[constant].getInitialState();
134 | this.setState({
135 | '_action_states': actionStates
136 | });
137 | },
138 |
139 | /**
140 | * @param {string} constant - constant to get handler state for
141 | * @param {string} [key] - a specfic key to get
142 | */
143 | getActionState: function (constant, key) {
144 | if (typeof this._actionHandlers[constant] === 'undefined') {
145 | throw new Error('Store.getActionState constant handler for [' + constant + '] is not defined');
146 | }
147 |
148 | var actionState = this.state.get('_action_states');
149 |
150 | if (typeof key === "undefined") {
151 | return actionState[constant];
152 | }
153 | return actionState[constant][key];
154 | },
155 |
156 | /**
157 | *
158 | */
159 | isStore: function () {
160 | return true;
161 | },
162 |
163 | /**
164 | * @param {function} callback
165 | */
166 | onChange: function (callback) {
167 | this._on(CHANGE_EVENT, callback);
168 | },
169 |
170 | /**
171 | * @param {function} callback
172 | */
173 | offChange: function (callback) {
174 | this._off(CHANGE_EVENT, callback);
175 | },
176 |
177 |
178 | /**
179 | * set extra properties & methods for this Store
180 | * @param {object} storeMixin
181 | */
182 | _storeMixin: function (storeMixin) {
183 | if (storeMixin && storeMixin.mixins && _isArray(storeMixin.mixins)) {
184 | _forEach(storeMixin.mixins, this._storeMixin.bind(this));
185 | }
186 | _forEach(storeMixin, function (prop, propName) {
187 | if (propName === 'mixins') {
188 | return;
189 | }
190 |
191 | if (typeof prop === 'function') {
192 | prop = prop.bind(this);
193 | }
194 |
195 | if (propName === 'getInitialState') {
196 | this._getInitialStateCalls.push(prop);
197 | } else if (propName === 'storeDidMount') {
198 | this._storeDidMountCalls.push(prop);
199 | } else {
200 | this[propName] = prop;
201 | }
202 | }.bind(this));
203 | },
204 |
205 | /**
206 | * @param {string} constant
207 | * @param {object} configs
208 | * @return {FluxStore} self
209 | */
210 | addActionHandler: function (constant, configs) {
211 | this._actionHandlers[constant] = new StoreActionHandler(this, constant, configs);
212 | return this;
213 | },
214 |
215 | /**
216 | * Set constant handlers for this Store
217 | * @param {array} handlers
218 | */
219 | _setConstantHandlers: function (handlers) {
220 | if (!_isArray(handlers)) {
221 | throw new Error('store expects handler definitions to be an array');
222 | }
223 | _forEach(handlers, function (options) {
224 | if (!_isArray(options)) {
225 | throw new Error('store expects handler definition to be an array');
226 | }
227 | var constant, handler, waitFor;
228 |
229 | constant = options[0];
230 | if (options.length === 2) {
231 | waitFor = null;
232 | handler = options[1];
233 | }
234 | else {
235 | waitFor = options[1];
236 | handler = options[2];
237 | }
238 | if (typeof constant !== 'string') {
239 | throw new Error('store expects all handler definitions to contain a constant as the first parameter');
240 | }
241 | if (typeof handler !== 'function') {
242 | throw new Error('store expects all handler definitions to contain a callback');
243 | }
244 | if (!!waitFor && !_isArray(waitFor)) {
245 | throw new Error('store expects waitFor to be an array of stores');
246 | }
247 | var waitForIndexes = null;
248 | if (waitFor) {
249 | waitForIndexes = waitFor.map(function (store) {
250 | if (!(store instanceof Store)) {
251 | throw new Error('store expects waitFor to be an array of stores');
252 | }
253 | return store._getHandlerIndex(constant);
254 | });
255 | }
256 |
257 | this._constantHandlers[constant] = handler.bind(this);
258 | var dispatcherIndex = this._dispatcher.register(constant, this._constantHandlers[constant], waitForIndexes);
259 | this._dispatcherIndexes[constant] = dispatcherIndex;
260 | }.bind(this));
261 | },
262 |
263 | /**
264 | * Get dispatcher idx of this constant callback for this store
265 | * @param {string} constant
266 | * @return {number} Index of constant callback
267 | */
268 | _getHandlerIndex: function (constant) {
269 | if (typeof this._dispatcherIndexes[constant] === "undefined") {
270 | throw new Error('Can not get store handler for constant: ' + constant);
271 | }
272 | return this._dispatcherIndexes[constant];
273 | },
274 |
275 | /**
276 | * Sets intial state of the Store
277 | */
278 | _setInitialState: function () {
279 | this.setState(this.getInitialState());
280 | },
281 |
282 | /**
283 | * Gets initial state of the store
284 | *
285 | * @return {mixed} Store initial state
286 | */
287 | getInitialState: function () {
288 | var state = {};
289 |
290 | _forEach(this._getInitialStateCalls, function (fn) {
291 | _assign(state, fn());
292 | });
293 |
294 | return state;
295 | },
296 |
297 |
298 | /**
299 | * @return {Object} A mixin for React Components
300 | */
301 | mixinFor: function () {
302 | return MixinFor(this);
303 | },
304 |
305 | /**
306 | *
307 | * @param {String} evt
308 | * @param {Function} handler
309 | * @return {Function} handler
310 | */
311 | _on: function (evt, handler) {
312 | if (typeof this._events[evt] === 'undefined') {
313 | this._events[evt] = [];
314 | }
315 | this._events[evt].push(handler);
316 | return handler;
317 | },
318 |
319 | /**
320 | *
321 | * @param {String} evt
322 | * @param {Function} handler
323 | */
324 | _off: function (evt, handler) {
325 | if (typeof this._events[evt] !== 'undefined') {
326 | for (var i = 0, len = this._events[evt].length; i < len; i++) {
327 | if (this._events[evt][i] === handler) {
328 | this._events[evt].splice(i, 1);
329 | break;
330 | }
331 | }
332 | }
333 | },
334 |
335 | /**
336 | *
337 | * @param {String} evt
338 | */
339 | _emit: function (evt) {
340 | if (typeof this._events[evt] === 'undefined') {
341 | return;
342 | }
343 | var args = Array.prototype.slice.call(arguments, 1);
344 | _forEach(this._events[evt], function (listener) {
345 | if ('function' === typeof listener) {
346 | listener.apply(null, args);
347 | }
348 | });
349 | },
350 |
351 | };
352 |
353 | module.exports = Store;
354 |
--------------------------------------------------------------------------------
/lib/storeActionHandler.js:
--------------------------------------------------------------------------------
1 | var _isString = require('lodash/lang/isString');
2 |
3 | var constantsConfigs = require('./configs').constants.get();
4 |
5 | var HANDLER_NAMES = ['before', 'after', 'success', 'fail'];
6 |
7 | function StoreActionHandler(store, constant, configs) {
8 | if (!store || typeof store.isStore !== 'function' || !store.isStore()) {
9 | throw new Error('StoreActionHandler expects first parameter to be a store');
10 | }
11 | if (!_isString(constant) || !constant.length) {
12 | throw new Error('StoreActionHandler expects second parameter to be a constant(string)');
13 | }
14 | if (typeof configs.getInitialState == 'undefined') {
15 | configs.getInitialState = function () {
16 | return {};
17 | };
18 | }
19 | if (typeof configs.getInitialState != 'function') {
20 | throw new Error('StoreActionHandler expects getInitialState to be a function');
21 | }
22 |
23 | configs = configs || {};
24 | this.parent = store;
25 | this.constant = constant;
26 | this.getInitialState = configs.getInitialState;
27 | this.before = configs.before || null;
28 | this.after = configs.after || null;
29 | this.success = configs.success || null;
30 | this.fail = configs.fail || null;
31 |
32 | this.parent.setActionState(this.constant, this.getInitialState());
33 |
34 | //register handlers for this constant
35 | var handlers = [];
36 | var len = HANDLER_NAMES.length;
37 | for (var i = 0; i < len; i++) {
38 | var handlerName = HANDLER_NAMES[i];
39 | if (this[handlerName] === null) {
40 | continue;
41 | }
42 | if (typeof this[handlerName] !== 'function') {
43 | throw new Error('StoreActionHandler expects "' + handlerName + '" to be a function');
44 | }
45 | var constantName = this.constant;
46 | if (handlerName !== 'before') {
47 | constantName += constantsConfigs.separator + constantsConfigs[handlerName + 'Suffix'];
48 | }
49 | handlers.push([constantName, this[handlerName].bind(this)]);
50 | }
51 | store._setConstantHandlers(handlers);
52 | }
53 |
54 | StoreActionHandler.prototype = {
55 | /**
56 | * @param {object} newState
57 | */
58 | setState: function (newState) {
59 | this.parent.setActionState(this.constant, newState);
60 | },
61 | /**
62 | * @return {object} state
63 | */
64 | getState: function () {
65 | return this.parent.getActionState(this.constant);
66 | }
67 | };
68 |
69 | module.exports = StoreActionHandler;
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-flux",
3 | "version": "1.0.1",
4 | "description": "A library implementing React Flux data flow",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "./node_modules/mocha/bin/mocha -G",
8 | "test-cov": "./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- -R spec"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/kjda/ReactFlux.git"
13 | },
14 | "keywords": [
15 | "React",
16 | "Flux",
17 | "ReactJS"
18 | ],
19 | "author": "Khaled Jouda",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/kjda/ReactFlux/issues"
23 | },
24 | "homepage": "https://github.com/kjda/ReactFlux",
25 | "dependencies": {
26 | "lodash": "^3.10.1",
27 | "promise": "^7.0.1"
28 | },
29 | "devDependencies": {
30 | "chai": "^2.2.0",
31 | "colors": "^1.0.3",
32 | "commander": "^2.7.1",
33 | "coveralls": "^2.11.2",
34 | "istanbul": "^0.3.13",
35 | "mocha": "^2.2.4",
36 | "sinon": "^1.14.1",
37 | "underscore": "^1.8.3",
38 | "webpack": "^1.9.10"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/actions.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../');
2 | var assert = require('chai').assert;
3 | var sinon = require("sinon");
4 |
5 | describe("actions", function(){
6 |
7 | var constants = ReactFlux.createConstants(['ONE', 'TWO']);
8 | var actions, action1Spy;
9 |
10 | beforeEach(function(){
11 | action1Spy = sinon.spy(function(par1){
12 | return {
13 | par1: par1
14 | };
15 | });
16 |
17 | actions = ReactFlux.createActions({
18 |
19 | action1: [constants.ONE, action1Spy],
20 |
21 | action2: [constants.TWO, function(){
22 |
23 | }]
24 |
25 | });
26 |
27 | });
28 |
29 | it("should create actions", function(){
30 | assert.typeOf(actions, 'object');
31 | assert.typeOf(actions.action1, 'function');
32 | assert.typeOf(actions.action2, 'function');
33 | });
34 |
35 | it("action callback should be callable", function(){
36 | actions.action1('arg1', 'arg2');
37 | assert.isTrue(action1Spy.calledWith('arg1', 'arg2'));
38 | });
39 |
40 | it("accepts an array as the only possible action definition", function(){
41 | function throwsError(){
42 | ReactFlux.createActions({
43 | action1: function(){}
44 | });
45 | }
46 | assert.throw(throwsError, /Action must be an array/g);
47 | });
48 |
49 | it("action must be a function", function(){
50 | function throwsError(){
51 | ReactFlux.createActions({
52 | action1: ['ACTION1', 'someOtherValue']
53 | });
54 | }
55 | assert.throw(throwsError, /you did not provide a valid callback for action/g);
56 | });
57 |
58 | it("creates a default empty callbacl", function(){
59 | var actions = ReactFlux.createActions({
60 | action1: ['ACTION1']
61 | });
62 | assert.isFunction(actions.action1);
63 | });
64 |
65 | });
66 |
--------------------------------------------------------------------------------
/test/configs.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../');
2 | var assert = require('chai').assert;
3 |
4 |
5 | describe("configs", function(){
6 |
7 | it("constant generation should be configurable", function(){
8 | ReactFlux.configs.constants.setSeparator(':');
9 | ReactFlux.configs.constants.setSuccessSuffix('OK');
10 | ReactFlux.configs.constants.setFailSuffix('ERROR');
11 | ReactFlux.configs.constants.setAfterSuffix('DONE');
12 |
13 | var fooConstants = ReactFlux.createConstants(['ONE'], 'FOO');
14 | assert.equal(fooConstants.ONE_OK, 'FOO:ONE:OK');
15 | assert.equal(fooConstants.ONE_ERROR, 'FOO:ONE:ERROR');
16 | assert.equal(fooConstants.ONE_DONE, 'FOO:ONE:DONE');
17 |
18 | ReactFlux.configs.constants.resetToDefaults();
19 |
20 | var barConstants = ReactFlux.createConstants(['TWO'], 'BAR');
21 | assert.equal(barConstants.TWO_SUCCESS, 'BAR_TWO_SUCCESS');
22 | assert.equal(barConstants.TWO_FAIL, 'BAR_TWO_FAIL');
23 | assert.equal(barConstants.TWO_AFTER, 'BAR_TWO_AFTER');
24 | });
25 |
26 |
27 |
28 | it("complains if Constants separator is not a string", function(){
29 | assert.throw(function(){
30 | ReactFlux.configs.constants.setSeparator({});
31 | }, 'Constants.separator must be a non empty string');
32 |
33 | assert.throw(function(){
34 | ReactFlux.configs.constants.setSeparator('');
35 | }, 'Constants.separator must be a non empty string');
36 |
37 | });
38 |
39 |
40 | it("complains if successSuffix is not a string", function(){
41 | assert.throw(function(){
42 | ReactFlux.configs.constants.setSuccessSuffix({});
43 | }, 'Constants.successSuffix must be a non empty string');
44 |
45 | assert.throw(function(){
46 | ReactFlux.configs.constants.setSuccessSuffix('');
47 | }, 'Constants.successSuffix must be a non empty string');
48 |
49 | });
50 |
51 | it("complains if failSuffix is not a string", function(){
52 | assert.throw(function(){
53 | ReactFlux.configs.constants.setFailSuffix({});
54 | }, 'Constants.failSuffix must be a non empty string');
55 |
56 | assert.throw(function(){
57 | ReactFlux.configs.constants.setFailSuffix('');
58 | }, 'Constants.failSuffix must be a non empty string');
59 |
60 | });
61 |
62 | it("complains if afterSuffix is not a string", function(){
63 | assert.throw(function(){
64 | ReactFlux.configs.constants.setAfterSuffix({});
65 | }, 'Constants.afterSuffix must be a non empty string');
66 |
67 | assert.throw(function(){
68 | ReactFlux.configs.constants.setAfterSuffix('');
69 | }, 'Constants.afterSuffix must be a non empty string');
70 |
71 | });
72 |
73 | });
74 |
--------------------------------------------------------------------------------
/test/constants.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../');
2 | var assert = require('chai').assert;
3 |
4 |
5 |
6 | describe("constants", function(){
7 |
8 | var constantsWithPrefix = ReactFlux.createConstants(['ONE', 'TWO'], 'PRE');
9 |
10 | var constants = ReactFlux.createConstants(['ONE', 'TWO']);
11 |
12 | it("should create constants without prefix", function(){
13 | assert.equal(constants.ONE, 'ONE');
14 | assert.equal(constants.TWO, 'TWO');
15 | });
16 |
17 | it("should create constants with prefix", function(){
18 | assert.equal(constantsWithPrefix.ONE, 'PRE_ONE');
19 | assert.equal(constantsWithPrefix.TWO, 'PRE_TWO');
20 | });
21 |
22 | it("should create success constants", function(){
23 | assert.equal(constants.ONE_SUCCESS, 'ONE_SUCCESS');
24 | assert.equal(constants.TWO_SUCCESS, 'TWO_SUCCESS');
25 | });
26 |
27 | it("should create failure constants", function(){
28 | assert.equal(constants.ONE_FAIL, 'ONE_FAIL');
29 | assert.equal(constants.TWO_FAIL, 'TWO_FAIL');
30 | });
31 |
32 | it("should accepts an array of constants only", function(){
33 | assert.throw(function(){
34 | ReactFlux.createConstants({});
35 | }, /createConstants expects first parameter to be an array of strings/);
36 | });
37 |
38 | it("all constants must be strings", function(){
39 | assert.throw(function(){
40 | ReactFlux.createConstants(['ONE', null]);
41 | }, /createConstants expects all constants to be strings/);
42 | });
43 |
44 | it("prefix should be a string", function(){
45 | assert.throw(function(){
46 | ReactFlux.createConstants([], 12);
47 | }, /createConstants expects second parameter string/);
48 | });
49 |
50 |
51 |
52 | });
53 |
--------------------------------------------------------------------------------
/test/dispatcher.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../');
2 | var assert = require('chai').assert;
3 | var sinon = require("sinon");
4 |
5 | var constants = ReactFlux.createConstants(['ONE', 'TWO', 'THREE'], 'DIS');
6 |
7 | describe("dispatcher", function(){
8 |
9 | var action1Spy, action2Spy;
10 | var store1Action1BeforeSpy, store1Action1AfterSpy, store1Action1SuccessSpy;
11 | var actionHandlerBeforeSpy, actionHandlerAfterSpy, actionHandlerSuccessSpy, actionHandlerFailSpy;
12 | var store1Action2FailSpy;
13 | var store2Action1SuccessSpy;
14 |
15 | var actions;
16 | var store1, store2;
17 |
18 | beforeEach(function(){
19 |
20 | action1Spy = sinon.spy(function(id, username){
21 | return {
22 | id: id,
23 | username: username
24 | };
25 | });
26 |
27 | action2Spy = sinon.spy(function(id, username){
28 | return new Error('error2');
29 | });
30 |
31 | actions = ReactFlux.createActions({
32 | action1: [constants.ONE, action1Spy],
33 | action2: [constants.TWO, action2Spy],
34 | action3: [constants.THREE, function(){ return {};}]
35 | });
36 |
37 | store1Action1BeforeSpy = sinon.spy(function(){});
38 | store1Action1AfterSpy = sinon.spy(function(){});
39 | store1Action1SuccessSpy = sinon.spy(function(){});
40 | store1Action2FailSpy = sinon.spy(function(){});
41 |
42 | store2Action1SuccessSpy = sinon.spy(function(){});
43 |
44 | actionHandlerBeforeSpy = sinon.spy(function(){});
45 | actionHandlerAfterSpy = sinon.spy(function(){});
46 | actionHandlerSuccessSpy = sinon.spy(function(){});
47 | actionHandlerFailSpy = sinon.spy(function(){});
48 |
49 |
50 | store1 = ReactFlux.createStore({
51 | getName: function(){
52 | return "store1";
53 | }
54 | }, [
55 | [constants.ONE, store1Action1BeforeSpy],
56 | [constants.ONE_AFTER, store1Action1AfterSpy],
57 | [constants.ONE_SUCCESS, store1Action1SuccessSpy],
58 | [constants.TWO_FAIL, store1Action2FailSpy]
59 | ]);
60 |
61 | store1.addActionHandler(constants.THREE, {
62 | before: actionHandlerBeforeSpy,
63 | after: actionHandlerAfterSpy,
64 | success: actionHandlerSuccessSpy,
65 | fail: actionHandlerFailSpy
66 | });
67 |
68 | store2 = ReactFlux.createStore({
69 | getName: function(){
70 | return "store2";
71 | }
72 | }, [
73 | [constants.ONE, sinon.spy(function(){})],
74 | [constants.TWO_FAIL, sinon.spy(function(){})],
75 | [constants.ONE_SUCCESS, [store1], store2Action1SuccessSpy]
76 | ]);
77 | });
78 |
79 | it("should throw an error if constant is empty or undefined", function(){
80 | function createStore(constant){
81 | ReactFlux.createStore({}, [
82 | [constant, function(){}]
83 | ]);
84 | }
85 | assert.throws(function(){
86 | createStore();
87 | }, /constant/);
88 | assert.throws(function(){
89 | createStore('');
90 | }, /constant/);
91 | });
92 |
93 | it("should execute action", function(){
94 | actions.action1(1, "mustermann");
95 | assert.isTrue(action1Spy.called);
96 | actions.action2();
97 | assert.isTrue(action2Spy.called);
98 | });
99 |
100 | it("should dispatch action to store", function(){
101 | actions.action1(1, "mustermann");
102 | assert.isTrue(store1Action1BeforeSpy.called);
103 | });
104 |
105 | it("should dispatch AFTER to store", function(done){
106 | actions.action1(1, "mustermann");
107 | setTimeout(function(){
108 | assert.isTrue(store1Action1AfterSpy.called);
109 | done();
110 | }, 0);
111 | });
112 |
113 | it("should dispatch SUCCESS to store", function(done){
114 | actions.action1(1, "mustermann");
115 | setTimeout(function(){
116 | assert.isTrue(store1Action1SuccessSpy.called);
117 | done();
118 | }, 0);
119 | });
120 |
121 | it("should dispatch SUCCESS with correct payload", function(done){
122 | actions.action1(1, "mustermann");
123 | setTimeout(function(){
124 | assert.isTrue(store1Action1SuccessSpy.calledWith({id: 1, username: 'mustermann'}));
125 | done();
126 | }, 0);
127 | });
128 |
129 | it("should dispatch FAIL to store", function(done){
130 | actions.action2();
131 | setTimeout(function(){
132 | assert.isTrue(store1Action2FailSpy.called);
133 | done();
134 | }, 0);
135 | });
136 |
137 | it("should be able to wait for other stores", function(done){
138 | actions.action1();
139 | setTimeout(function(){
140 | assert.isTrue(store2Action1SuccessSpy.calledAfter(store1Action1SuccessSpy));
141 | done();
142 | }, 0);
143 | });
144 |
145 | it("should be able to dispatch specific messages without going through actions", function(done){
146 | ReactFlux.dispatch(constants.ONE_SUCCESS);
147 | setTimeout(function(){
148 | assert.isTrue( store1Action1SuccessSpy.called );
149 | assert.isTrue( store2Action1SuccessSpy.called );
150 | done();
151 | }, 0);
152 | });
153 |
154 | it("should dispatch BEFORE to actionHandler", function(done){
155 | actions.action3(1, "mustermann");
156 | assert.isTrue(actionHandlerBeforeSpy.called);
157 | done();
158 | });
159 |
160 | it("should dispatch AFTER to actionHandler", function(done){
161 | actions.action3(1, "mustermann");
162 | setTimeout(function(){
163 | assert.isTrue(actionHandlerAfterSpy.called);
164 | done();
165 | }, 0);
166 | });
167 |
168 | it("should dispatch SUCCESS to actionHandler", function(done){
169 | actions.action3(1, "mustermann");
170 | setTimeout(function(){
171 | assert.isTrue(actionHandlerSuccessSpy.called);
172 | done();
173 | }, 0);
174 | });
175 |
176 | it("should NOT dispatch FAIL to actionHandler when SUCCESS", function(done){
177 | actions.action3(1, "mustermann");
178 | setTimeout(function(){
179 | assert.isFalse(actionHandlerFailSpy.called);
180 | done();
181 | }, 0);
182 | });
183 |
184 | it("register should throw an error if callback is not a function", function(){
185 | assert.throw(function(){
186 | ReactFlux.dispatcher.register("SOME_CONST", null);
187 | }, /Dispatcher.register expects second parameter to be a callback/);
188 | });
189 |
190 | it("register should throw an error if waitForIndexes is not an array", function(){
191 | assert.throw(function(){
192 | ReactFlux.dispatcher.register("SOME_CONST", function(){}, {});
193 | }, /Dispatcher.register expects third parameter to be null or an array/);
194 | });
195 |
196 | });
197 |
--------------------------------------------------------------------------------
/test/mixinFor.js:
--------------------------------------------------------------------------------
1 | var assert = require('chai').assert;
2 | var sinon = require("sinon");
3 | var Flux = require('../');
4 | var sinon = require("sinon");
5 |
6 | describe("MixinFor", function(){
7 |
8 | it("Flux should provide mixinFor interface", function(){
9 | assert.isFunction(Flux.mixinFor);
10 |
11 | var mixinFor = Flux.mixinFor(Flux.createStore());
12 | assert.isFunction(mixinFor.componentWillMount, 'mixinFor.componentWillMount is misiing');
13 | assert.isFunction(mixinFor.componentDidMount, 'mixinFor.componentDidMount is misiing');
14 | assert.isFunction(mixinFor.componentWillUnmount, 'mixinFor.componentWillUnmount is missing');
15 | });
16 |
17 | it("accepts a store or an array of stores only", function(){
18 | assert.throws(function(){
19 | Flux.mixinFor();
20 | }, Error, /stores/);
21 | assert.throws(function(){
22 | Flux.mixinFor('');
23 | }, Error, /stores/);
24 | assert.throws(function(){
25 | Flux.mixinFor({});
26 | }, Error, /stores/);
27 | });
28 |
29 | it("should call component.setState", function(done){
30 | var mixinFor = Flux.mixinFor( Flux.createStore() );
31 | mixinFor.state = {};
32 | mixinFor.setState = function(state){
33 | mixinFor.state = state;
34 | };
35 | mixinFor.getStateFromStores = function(){
36 | return {
37 | foo: 'bar'
38 | };
39 | };
40 | mixinFor.isMounted = function(){
41 | return true;
42 | };
43 | mixinFor.componentWillMount();
44 | setTimeout(function(){
45 | assert.equal(mixinFor.state.foo, 'bar');
46 | done();
47 | }, 0);
48 | });
49 |
50 | it("should call getStateFromStores onChange", function(done){
51 | var store = Flux.createStore();
52 | var mixinFor = Flux.mixinFor( store );
53 | mixinFor.state = {};
54 | mixinFor.setState = function(state){
55 | mixinFor.state = state;
56 | };
57 | mixinFor.getStateFromStores = sinon.spy(function(){
58 | return {
59 | foo: 'bar'
60 | };
61 | });
62 | mixinFor.isMounted = function(){
63 | return true;
64 | };
65 | mixinFor.componentWillMount();
66 | mixinFor.componentDidMount();
67 | store.setState({foo: 'bar'});
68 | setTimeout(function(){
69 | assert.isTrue( mixinFor.getStateFromStores.calledTwice );
70 | done();
71 | }, 0);
72 | });
73 |
74 | it("should not call getStateFromStores onChange if component is not mounted", function(done){
75 | var store = Flux.createStore();
76 | var mixinFor = Flux.mixinFor( store );
77 | mixinFor.state = {};
78 | mixinFor.setState = function(state){
79 | mixinFor.state = state;
80 | };
81 | mixinFor.getStateFromStores = sinon.spy(function(){
82 | return {
83 | foo: 'bar'
84 | };
85 | });
86 | mixinFor.isMounted = function(){
87 | return false;
88 | };
89 | mixinFor.componentWillMount();
90 | mixinFor.componentDidMount();
91 | store.setState({foo: 'bar'});
92 | setTimeout(function(){
93 | assert.isTrue( mixinFor.getStateFromStores.calledOnce );
94 | done();
95 | }, 0);
96 | });
97 |
98 | it("it should subscribe and unsubscribe for store changes", function(){
99 |
100 | var store = Flux.createStore();
101 | store.onChange = sinon.spy(function(){
102 | });
103 | store.offChange = sinon.spy(function(){
104 | });
105 |
106 | var mixinFor = Flux.mixinFor( store );
107 |
108 | mixinFor.componentDidMount();
109 | assert.isTrue(store.onChange.called, 'mixinFor did not subscribe for store changes');
110 | assert.isFalse(store.offChange.called);
111 |
112 | mixinFor.componentWillUnmount();
113 | assert.isTrue(store.offChange.called, 'mixinFor did not unsubscribe from store changes');
114 |
115 | });
116 |
117 | });
118 |
--------------------------------------------------------------------------------
/test/store.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../');
2 | var assert = require('chai').assert;
3 | var sinon = require("sinon");
4 |
5 | var constants = ReactFlux.createConstants(['ONE','TWO'], 'STORE');
6 |
7 |
8 | var storeDidMountSpy = sinon.spy();
9 |
10 | var FooMixin = {
11 | bar: sinon.spy(),
12 | getInitialState: sinon.spy(function () {
13 | return {
14 | abc: "abc",
15 | xyz: "xyz"
16 | };
17 | }),
18 | storeDidMount: sinon.spy()
19 | };
20 |
21 | var BarMixin = {
22 | foo: sinon.spy(),
23 | getInitialState: sinon.spy(function () {
24 | return {
25 | def: "def"
26 | };
27 | }),
28 | storeDidMount: sinon.spy()
29 | };
30 |
31 | var EnhancedFooMixin = {
32 | mixins: [FooMixin],
33 | enhancedBar: sinon.spy(),
34 | getInitialState: sinon.spy(function () {
35 | return {
36 | abc: "abc-enhanced"
37 | };
38 | }),
39 | storeDidMount: sinon.spy()
40 | };
41 |
42 | var getInitialStateSpy = sinon.spy(function(){
43 | return {
44 | id: 1,
45 | username: 'mustermann'
46 | };
47 | });
48 |
49 | var store = ReactFlux.createStore({
50 |
51 | mixins: [
52 | EnhancedFooMixin,
53 | BarMixin
54 | ],
55 |
56 | getInitialState: getInitialStateSpy,
57 |
58 | storeDidMount: storeDidMountSpy,
59 |
60 | getId: function(){
61 | return this.state.get('id');
62 | }
63 |
64 | }, [
65 | [constants.ONE, function(){}]
66 | ]);
67 |
68 | store.addActionHandler(constants.TWO, {
69 | getInitialState: function(){
70 | return {
71 | name: 'TWO_HANDLER'
72 | };
73 | },
74 | before: function(){
75 | },
76 | after: function(){
77 | },
78 | success: function(){
79 | },
80 | fail: function(){
81 | }
82 | });
83 |
84 | describe("store", function(){
85 |
86 | it("should create store with mixins", function(){
87 | assert.typeOf(store, 'object');
88 | assert.typeOf(store.getId, 'function');
89 | });
90 |
91 | it("should use the mixins property", function(){
92 | assert.typeOf(store.foo, 'function');
93 | assert.typeOf(store.enhancedBar, 'function');
94 | });
95 |
96 | it("should recursively use the mixins property", function(){
97 | assert.typeOf(store.bar, 'function');
98 | });
99 |
100 | it("should call each mixin's storeDidMount function", function(){
101 | assert.isTrue(FooMixin.storeDidMount.calledOnce);
102 | assert.isTrue(BarMixin.storeDidMount.calledOnce);
103 | assert.isTrue(EnhancedFooMixin.storeDidMount.calledOnce);
104 | assert.isTrue(storeDidMountSpy.calledOnce);
105 | });
106 |
107 | it("should call and merge each mixin's getInitialState function", function(){
108 | assert.equal(store.state.get('abc'),'abc-enhanced');
109 | assert.equal(store.state.get('xyz'),'xyz');
110 | assert.equal(store.state.get('def'),'def');
111 | });
112 |
113 | it("should call getInitialState", function(){
114 | assert.isTrue( getInitialStateSpy.called );
115 | });
116 |
117 | it("should call storeDidMount", function(){
118 | assert.isTrue( storeDidMountSpy.called );
119 | });
120 |
121 | it("should call storeDidMount after getInitialState", function(){
122 | assert.isTrue( storeDidMountSpy.calledAfter( getInitialStateSpy ) );
123 | });
124 |
125 | it("should have a state", function(){
126 | assert.typeOf(store.state, "object");
127 | });
128 |
129 | it("store.state.getState() should work", function(){
130 | assert.typeOf(store.getState, "function");
131 | assert.typeOf(store.getState(), "object");
132 | });
133 |
134 | it("store.state.getStateClone() should return a copy of state", function(){
135 | var id = store.get('id');
136 | var stateCopy = store.getStateClone();
137 | stateCopy.id = 'foo';
138 | assert.equal(store.get('id'), id);
139 | });
140 |
141 | it("store.state.get() should work", function(){
142 | assert.equal(store.state.get('id'), 1);
143 | assert.equal(store.state.get('username'), "mustermann");
144 | });
145 |
146 | it("store.get() should work", function(){
147 | assert.equal(store.get('id'), 1);
148 | assert.equal(store.get('username'), "mustermann");
149 | });
150 |
151 | it("store.state.getClone() should return a copy of state", function(){
152 | var stateClone = store.getStateClone();
153 | stateClone.deepFoo = {
154 | x: 1
155 | };
156 | store.setState(stateClone);
157 | var deepFoo = store.getClone('deepFoo');
158 | deepFoo.x = 'bar';
159 | assert.equal(store.get('deepFoo').x, 1);
160 | });
161 |
162 | it("should be able to call mixin methods", function(){
163 | assert.equal(store.getId(), 1);
164 | });
165 |
166 | it("should have a working setState", function(){
167 | store.setState({
168 | id: 3
169 | });
170 | assert.equal(store.state.get('id'), 3);
171 | assert.equal(store.state.get('username'), 'mustermann');
172 | });
173 |
174 |
175 | it("should have onChange/offChange", function(){
176 | assert.typeOf(store.onChange, "function");
177 | assert.typeOf(store.offChange, "function");
178 | });
179 |
180 | it("should call onChange when state changes", function(){
181 | var spy = sinon.spy();
182 | store.onChange( spy );
183 | store.setState({id: 2});
184 | assert.isTrue( spy.called );
185 | });
186 |
187 | it("offChange should remove listener", function(){
188 | var spy = sinon.spy();
189 | store.onChange( spy );
190 | store.offChange( spy );
191 | store.setState({id: 2});
192 | assert.isFalse( spy.called );
193 | });
194 |
195 | it("getActionState should work", function(){
196 |
197 | assert.typeOf(store.getActionState, 'function', 'store.getActionState should be a function');
198 |
199 | var state = store.getActionState(constants.TWO);
200 |
201 | assert.typeOf(state, 'object', 'store.getActionState should return an object');
202 | assert.equal(state.name, 'TWO_HANDLER', 'store.getActionState should return state object correctly');
203 | assert.equal(store.getActionState(constants.TWO, 'name'), 'TWO_HANDLER');
204 |
205 | assert.throw(function(){
206 | store.getActionState('nonexistant');
207 | }, 'Store.getActionState constant handler for [nonexistant] is not defined');
208 |
209 | assert.equal(store.getActionState(constants.TWO, 'nonexistantKey'), undefined);
210 | });
211 |
212 | it("should have a working setActionState method", function(){
213 | assert.typeOf(store.setActionState, 'function', 'store.setActionState should be a function');
214 |
215 | store.setActionState(constants.TWO, {'name': 'bar'});
216 | assert.equal(store.getActionState(constants.TWO, 'name'), 'bar', 'setActionState should reset state');
217 | });
218 |
219 | it("should have a working resetActionState method", function(){
220 | assert.typeOf(store.resetActionState, 'function', 'store.resetActionState should be a function');
221 |
222 | assert.throw(function(){
223 | store.resetActionState('nonexistant');
224 | }, 'Store.resetActionState constant handler for [nonexistant] is not defined');
225 |
226 | store.setActionState(constants.TWO, {'name': 'bar'});
227 | store.resetActionState(constants.TWO);
228 | assert.equal(store.getActionState(constants.TWO, 'name'), 'TWO_HANDLER', 'resetActionState should reset state');
229 | });
230 |
231 | it("should be able to replaceState", function(){
232 | store.setState({'foo': 'bar'});
233 | store.replaceState({
234 | 'foo2': 'bar2'
235 | });
236 | assert.isUndefined(store.get('foo'), "foo should not exist");
237 | assert.equal(store.get('foo2'), 'bar2', "foo2 should equal bar2");
238 | });
239 |
240 | it("_getHandlerIndex should throw an error when provided a non-existant constant", function(){
241 | assert.throw(function(){
242 | store._getHandlerIndex();
243 | }, /Can not get store handler for constant/);
244 | });
245 |
246 | it("should provide a mixinFor method for react components", function(){
247 | var mixinFor = store.mixinFor();
248 | assert.typeOf(mixinFor, 'Object', 'Store mixinFor should return a mixin');
249 | assert.typeOf(mixinFor.componentWillMount, 'function', 'store.mixinFor should return a mixin with componentWillMount');
250 | assert.typeOf(mixinFor.componentDidMount, 'function', 'store.mixinFor should return a mixin with componentDidMount');
251 | assert.typeOf(mixinFor.componentWillUnmount, 'function', 'store.mixinFor should return a mixin with componentWillUnmount');
252 | });
253 |
254 |
255 | it("createStore should complain about wrong handler definitions", function(){
256 | assert.throw(function(){
257 | ReactFlux.createStore({
258 |
259 | }, {});
260 | }, /store expects handler definitions to be an array/);
261 |
262 | assert.throw(function(){
263 | ReactFlux.createStore({
264 | }, [
265 | [null, function(){}]
266 | ]);
267 | }, /store expects all handler definitions to contain a constant as the first parameter/);
268 |
269 | assert.throw(function(){
270 | ReactFlux.createStore({
271 | }, [
272 | ['FOO', null]
273 | ]);
274 | }, /store expects all handler definitions to contain a callback/);
275 |
276 | assert.throw(function(){
277 | ReactFlux.createStore({
278 | },[
279 | 'buggy', function(){}
280 | ]);
281 | }, /store expects handler definition to be an array/);
282 |
283 | assert.throw(function(){
284 | ReactFlux.createStore({
285 | },[
286 | ['FOO', store, function(){}]
287 | ]);
288 | }, /store expects waitFor to be an array of stores/);
289 |
290 | assert.throw(function(){
291 | ReactFlux.createStore({
292 | },[
293 | ['FOO', [function(){}], function(){}]
294 | ]);
295 | }, /store expects waitFor to be an array of stores/);
296 |
297 | });
298 |
299 | });
300 |
--------------------------------------------------------------------------------
/test/storeActionHandler.js:
--------------------------------------------------------------------------------
1 | var ReactFlux = require('../');
2 | var assert = require('chai').assert;
3 |
4 | var StoreActionHandler = require('../lib/storeActionHandler');
5 |
6 | describe("storeActionHandler", function(){
7 |
8 | it("should work on store only", function(){
9 | assert.throw(function(){
10 | new StoreActionHandler({});
11 | }, 'StoreActionHandler expects first parameter to be a store');
12 | });
13 |
14 | it("expects second parameter to be a constant", function(){
15 | assert.throw(function(){
16 | var store = ReactFlux.createStore();
17 | new StoreActionHandler(store, '');
18 | }, 'StoreActionHandler expects second parameter to be a constant(string)');
19 | });
20 |
21 | it("should create a default getInitialState", function(){
22 | var store = ReactFlux.createStore();
23 | var actionHandler = new StoreActionHandler(store, 'FOO', {});
24 | assert.isFunction(actionHandler.getInitialState);
25 | });
26 |
27 | it("should have a setState", function(){
28 | var store = ReactFlux.createStore();
29 | var actionHandler = new StoreActionHandler(store, 'FOO', {});
30 | assert.isFunction(actionHandler.setState);
31 | });
32 |
33 | it("should have a getState", function(){
34 | var store = ReactFlux.createStore();
35 | var actionHandler = new StoreActionHandler(store, 'FOO', {});
36 | assert.isFunction(actionHandler.getState);
37 | });
38 |
39 | it("should have a parent", function(){
40 | var store = ReactFlux.createStore();
41 | var actionHandler = new StoreActionHandler(store, 'FOO', {});
42 | assert.isObject(actionHandler.parent)
43 | assert.isTrue(actionHandler.parent.isStore());
44 | });
45 |
46 | //@todo: test that setting state work
47 |
48 | it("it should complain if getInitialState is not a function", function(){
49 | assert.throw(function(){
50 | var store = ReactFlux.createStore();
51 | new StoreActionHandler(store, 'FOO', {
52 | getInitialState: {}
53 | });
54 | }, 'StoreActionHandler expects getInitialState to be a function');
55 | });
56 |
57 | it("it should complain if before,success,error or after is not a function", function(){
58 | assert.throw(function(){
59 | var store = ReactFlux.createStore();
60 | new StoreActionHandler(store, 'FOO', {
61 | before: {}
62 | });
63 | }, 'StoreActionHandler expects "before" to be a function');
64 | });
65 |
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 |
3 | var UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
4 |
5 | module.exports = {
6 |
7 | context: __dirname + '/lib/',
8 |
9 | watch: true,
10 |
11 | entry: './index.js',
12 |
13 | output: {
14 | path: './dist/',
15 | filename: 'react-flux.js',
16 | library: 'ReactFlux',
17 | libraryTarget: 'umd'
18 | },
19 |
20 | resolve: {
21 | extensions: ['', '.js'],
22 | },
23 |
24 | module: {},
25 |
26 | externals: {
27 | promise: true,
28 | react: {
29 | root: 'React',
30 | commonjs: 'react',
31 | commonjs2: 'react',
32 | amd: 'react'
33 | }
34 | },
35 |
36 | plugins: [
37 | new UglifyJsPlugin()
38 | ]
39 | };
40 |
--------------------------------------------------------------------------------