├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── bower.json ├── dist ├── reflux.js └── reflux.min.js ├── docs ├── README.md ├── actions │ └── README.md ├── advanced │ └── README.md ├── components │ └── README.md ├── other │ ├── examples.md │ └── reflux-vs-flux.md └── stores │ └── README.md ├── karma.conf.js ├── old-README.md ├── package.json ├── src ├── ListenerMixin.js ├── addES6.js ├── connect.js ├── connectFilter.js ├── defineReact.js ├── index.js ├── listenTo.js └── listenToMany.js └── test ├── .jshintrc ├── Reflux.Component.spec.js ├── Reflux.GlobalState.spec.js ├── Reflux.Store.spec.js ├── benchmarks ├── index.js ├── listeningToActions.js ├── listeningToJoins.js └── listeningToStores.js ├── listenTo.spec.js ├── listenToMany.spec.js ├── sauceLaunchers.js ├── shims └── phantomjs-shims.js ├── usingConnectFilterMixin.spec.js ├── usingConnectMixin.spec.js ├── usingListenerMethodsMixin.spec.js └── usingListenerMixin.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | trim_trailing_whitespace = true 8 | charset = utf-8 9 | 10 | [*.js] 11 | indent_size = 4 12 | 13 | [{package.json,.travis.yml,Gruntfile.js}] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node.JS specifics 2 | 3 | lib-cov 4 | *.seed 5 | *.log 6 | *.csv 7 | *.dat 8 | *.out 9 | *.pid 10 | *.gz 11 | 12 | pids 13 | logs 14 | results 15 | 16 | npm-debug.log 17 | node_modules 18 | 19 | # MacOSX specifics 20 | .DS_Store 21 | 22 | # Additional tools 23 | .idea 24 | 25 | # Build artifacts 26 | tmp 27 | bower_components 28 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr": 50, 3 | 4 | "curly": true, 5 | "unused": true, 6 | "undef": true, 7 | 8 | "browser": true, 9 | "devel": true, 10 | "node": true, 11 | 12 | "globals": { 13 | "Promise":true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/ 3 | tmp/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "4.1.2" 5 | 6 | env: 7 | global: 8 | - secure: B0emLHNPO7BVpq2lT/KAPW1miUOhgODa/qoVqYbFspcdoBVfJmO1FpcgEppE8fuEecaOv9QfSI3hwGOUkzm9kp8HFde8ZenR5beauhfploXMQB/bbNjd0cBz0ORRMwzsTFg9GeWcnStr+9H05833W84K3N1oy3ShT68kYRWObh4= 9 | - secure: LZNuke26p3RYq5jbSB1oDlG8AeCY1fm8uBoVWmCdKKmmgV/+nKXDgdYygE/yeAPm39XnZ8jGH64ctHVixpgc8TQDa9ZCwOr1q3SWVL+dVAwhKm7WQ9/v2yW/c/zOEuYzvGqr1ylAC8PfwqYA6ShGUnSWAua7TIOeYqdtAjGbU1k= 10 | matrix: 11 | - REACT_VERSION="15" 12 | 13 | before_install: 14 | - "export DISPLAY=:99.0" 15 | - "sh -e /etc/init.d/xvfb start" 16 | 17 | before_script: 18 | - "npm install react@$REACT_VERSION" 19 | 20 | script: 21 | - npm test 22 | - if [ "$SAUCE_USERNAME" ]; then npm run test:sauce; fi 23 | 24 | addons: 25 | sauce_connect: true 26 | 27 | sudo: false 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Check for latest changes on the [milestones page](https://github.com/spoike/refluxjs/milestones). 4 | 5 | For updates on reflux core methods, check the [`CHANGELOG`](https://github.com/reflux/reflux-core/blob/master/CHANGELOG.md) at `reflux-core` project. 6 | 7 | ## v6.4.1 8 | 9 | * NOTE: New `MyStore.state` shortcut (from 6.4.0) is not available in IE10 and below, use accordingly in your projects. 10 | * Remove test for it so that it doesn't fail, and is undocumented feature for now, until the day we can drop IE9 and IE10. 11 | 12 | ## v6.4.0 13 | 14 | * Improved on class extending function used internally. 15 | * Made that extending function available externally at `Reflux.utils.inherits(NewClass, InheritsFrom)` so that it can be used for testing. 16 | * Made `MyStore.state` work as shortcut access to `MyStore.singleton.state` (not available in IE9 and IE10, plan usage accordingly for your project). 17 | 18 | ## v6.3.0 19 | 20 | * Added Reflux.PureComponent which extends from React.PureComponent instead of React.Component. 21 | 22 | ## v6.2.0 23 | 24 | * React.Component constructor arguments for `context` and `updater` added to Reflux.Component contructor. 25 | 26 | ## v6.1.0 27 | 28 | * Documentation about Action listening/unsubscribing added. 29 | * Reflux.serverMode added, only adds listeners when hooking store to component if not in server mode, eliminating memory leak. 30 | * Bugfix with unmounting of reflux components that did not have any store attached to them. 31 | 32 | ## v6.0.0 33 | 34 | * Switch to new docs. 35 | * Updated to `reflux-core` v1.0.0. 36 | * Allow complex definitions for child actions instead of just string for action name. 37 | * Breaking change: actions are sync by default unless child actions are involved. 38 | 39 | ## v5.0.4 40 | 41 | * Workaround to babel __proto__ issue with old browsers. 42 | * Continued documentation refactoring (viewable in new-README.md). 43 | * Minor doc fixes. 44 | 45 | ## v5.0.3 46 | 47 | * Implemented `Reflux.initStore` (basically clone of `Reflux.initializeGlobalStore` but can be used more broadly). 48 | * Deprecate `Reflux.initializeGlobalStore` in favor of more broadly usable `Reflux.initStore`. 49 | * Major documentation refactor started, but not implemented yet (viewable in new-README.md). 50 | * Updated to run on `reflux-core` v0.4.2, which has more broad importing support. 51 | 52 | ## v5.0.2 53 | 54 | * Reduced the need for `defineReact` by better detecting of React. 55 | * Updated to run on `reflux-core` v0.4.1, which solves some memory leaks. 56 | 57 | ## v5.0.1 58 | 59 | * Adding of full ES6 API, most notably `Reflux.Component` and `Reflux.Store` (and their sub-methods/properties). Also `defineReact`, `initializeGlobalStore`, `GlobalState`, `getGlobalState`, `setGlobalState`. 60 | 61 | ## v0.4.x 62 | 63 | * ?? 64 | 65 | ## v0.3.0 66 | 67 | * Bumped up along with breaking changes in [`reflux-core`](https://github.com/reflux/reflux-core). 68 | 69 | ## v0.2.13 70 | 71 | * Fixed React dependency [#425](https://github.com/reflux/refluxjs/pull/425) 72 | * Example on README corrected [#406](https://github.com/reflux/refluxjs/pull/406) 73 | 74 | ## v0.2.12 75 | 76 | * Integrated [`reflux-core`](https://github.com/reflux/reflux-core) v0.2.1, see [#380](https://github.com/reflux/refluxjs/issues/380). 77 | 78 | ## v0.2.11 79 | 80 | * Using setImmediate if available. Fixes issue with IE10+, see [#374](https://github.com/spoike/refluxjs/issues/374). 81 | 82 | ## v0.2.10 83 | 84 | * Exposing Reflux.utils [#253](https://github.com/spoike/refluxjs/issues/253) 85 | * Fixing connectFilter [#371](https://github.com/spoike/refluxjs/pull/371) 86 | * Added links in README to extensions and plugins 87 | 88 | ### Breaking changes to undocumented features 89 | 90 | * Fixed circular referencing to the index js file. Should fix issues building with bower package. This means you can't replace the ActionMethods and StoreMethods objects (as the previous unit tests did), you have to add properties to them. 91 | 92 | * Removed `native-promise-only` dependency. Reflux does an initial check for Promises and will instead create async actions using `triggerAsync` instead of `triggerPromise` if `Promise` is missing from the environment. Do use a Promise polyfill or your own Promise library if you plan to use this behavior. [#302](https://github.com/spoike/refluxjs/issues/302) 93 | 94 | ## v0.2.9 95 | 96 | * Fixes issue with a join check [#272](https://github.com/spoike/refluxjs/pull/272). 97 | * Added links to README. See [#341](https://github.com/spoike/refluxjs/pull/341), [#361](https://github.com/spoike/refluxjs/pull/361) 98 | 99 | ## v0.2.8 100 | 101 | See [#345](https://github.com/spoike/refluxjs/pull/345). 102 | 103 | * Fixes [#239](https://github.com/spoike/refluxjs/pull/239), checks for component mounted state before trying to mount component 104 | * Adds more example apps to README 105 | * Fixes some typo's in the readme 106 | * Improves documentation on action handlers in stores 107 | * Updates dependencies 108 | * Specifies react compatibility via peerDependencies 109 | 110 | ## v0.2.7 111 | 112 | * Fixed IE8 errors and warnings [#286](https://github.com/spoike/refluxjs/pull/286), [#270](https://github.com/spoike/refluxjs/pull/270) 113 | * Ensure triggerPromise original promise [#229](https://github.com/spoike/refluxjs/pull/229) 114 | * Fixed aborted callbacks [#227](https://github.com/spoike/refluxjs/pull/227) 115 | 116 | ## v0.2.6 117 | 118 | * Fixed catch call in Promises [#267](https://github.com/spoike/refluxjs/pull/267) 119 | * Promise and EventEmitter is now properly exported [#258](https://github.com/spoike/refluxjs/pull/258), [#253](https://github.com/spoike/refluxjs/pull/253) 120 | * Getters in stores were accidentally invoked [#231](https://github.com/spoike/refluxjs/pull/231), [#230](https://github.com/spoike/refluxjs/pull/230) 121 | * Asynchronous actions will now return promises [#223](https://github.com/spoike/refluxjs/pull/223), [#216](https://github.com/spoike/refluxjs/issues/216), [#259](https://github.com/spoike/refluxjs/issues/259) 122 | * `dist` folder is now available again in npm package [#266](https://github.com/spoike/refluxjs/pull/266) 123 | * Fixes to README file [#260](https://github.com/spoike/refluxjs/pull/260), [#247](https://github.com/spoike/refluxjs/pull/247), [#244](https://github.com/spoike/refluxjs/pull/244), [#240](https://github.com/spoike/refluxjs/pull/240), [#236](https://github.com/spoike/refluxjs/pull/236), [#235](https://github.com/spoike/refluxjs/pull/235), [#234](https://github.com/spoike/refluxjs/pull/234) 124 | 125 | ## v0.2.5 126 | 127 | * Added `connectFilter` [#222](https://github.com/spoike/refluxjs/pull/222) 128 | * A lot of clarifications and typo fixes in documentation. [#147](https://github.com/spoike/refluxjs/pull/147), [#207](https://github.com/spoike/refluxjs/pull/207), [#208](https://github.com/spoike/refluxjs/pull/208), [#209](https://github.com/spoike/refluxjs/pull/209), [#211](https://github.com/spoike/refluxjs/pull/211), and [#214](https://github.com/spoike/refluxjs/pull/214) 129 | 130 | ## v0.2.4 131 | 132 | * Promisable actions [#185](https://github.com/spoike/refluxjs/issues/185) 133 | * Fixed IE8 bug [#202](https://github.com/spoike/refluxjs/issues/202), [#187](https://github.com/spoike/refluxjs/issues/187) 134 | * Plus other various fixes: [#201](https://github.com/spoike/refluxjs/issues/201), [#200](https://github.com/spoike/refluxjs/issues/202), [#183](https://github.com/spoike/refluxjs/issues/183), and [#182](https://github.com/spoike/refluxjs/issues/182) 135 | 136 | ## v0.2.3 137 | 138 | * Store mixins [#124](https://github.com/spoike/refluxjs/pull/124) 139 | 140 | ## v0.2.2 141 | 142 | * Fixed circular dependency issue that caused browserify builds not to work as expected [#129](https://github.com/spoike/refluxjs/issues/129) [#138](https://github.com/spoike/refluxjs/issues/138) 143 | * Bind store methods before init() method executes. [#168](https://github.com/spoike/refluxjs/issues/168) 144 | * Clarify the meaning of "FRP". [#161](https://github.com/spoike/refluxjs/issues/161) 145 | * Child (async) actions and promise handling [#140](https://github.com/spoike/refluxjs/issues/140) 146 | 147 | ## v0.2.1 148 | 149 | * IE8 trailing comma bug fix [#145](https://github.com/spoike/refluxjs/pull/145) 150 | * Multiple use of Reflux.connect bug fix [#142](https://github.com/spoike/refluxjs/issues/142), [#143](https://github.com/spoike/refluxjs/pull/143) 151 | * Added .npmignore file, removing non-essential files from `npm install` [#125](https://github.com/spoike/refluxjs/issues/125) 152 | 153 | ## v0.2.0 154 | 155 | * Breaking change: Set initial state before componentDidMount (in `Reflux.connect`) [#117](https://github.com/spoike/refluxjs/pull/117) 156 | * Allow extension of actions and stores (with `Reflux.ActionMethods` and `Reflux.StoreMethods`) [#121](https://github.com/spoike/refluxjs/pull/121) 157 | * Automatically bind store methods [#100](https://github.com/spoike/refluxjs/pull/100) 158 | * Bugfix: Connect and listenermixin combo [#131](https://github.com/spoike/refluxjs/pull/131) 159 | 160 | ## v0.1.14, v0.1.15 161 | 162 | * You may now stop listening to joined listenables individually [#96](https://github.com/spoike/refluxjs/pull/96). 163 | * Reflux will now throw an error if you attempt to join less than two listenables [#97](https://github.com/spoike/refluxjs/pull/97). 164 | 165 | ## v0.1.13 166 | 167 | * Added more join methods, i.e. `listener.joinLeading`, `listener.joinTrailing`, `listener.joinConcat` and `listener.joinStrict` 168 | [#92](https://github.com/spoike/refluxjs/pull/92). 169 | * Actions can be set to sync or async trigger [#93](https://github.com/spoike/refluxjs/pull/93). 170 | * And various bug fixes. Check the [milestone page](https://github.com/spoike/refluxjs/issues?q=milestone%3A0.1.13+is%3Aclosed). 171 | 172 | ## v0.1.12 173 | 174 | * Bug fixes. Check the [milestone page](https://github.com/spoike/refluxjs/issues?q=milestone%3A0.1.12+is%3Aclosed). 175 | 176 | ## v0.1.9, v0.1.10, v0.1.11 177 | 178 | * Critical bug fixes. See [#80](https://github.com/spoike/refluxjs/issues/80), [#81](https://github.com/spoike/refluxjs/issues/81), and [#82](https://github.com/spoike/refluxjs/issues/82). 179 | 180 | ## v0.1.8 181 | 182 | * Added `Reflux.connect`, `Reflux.listenTo`, `listenToMany` conveniences. See [#63](https://github.com/spoike/refluxjs/pull/63) and [#75](https://github.com/spoike/refluxjs/pull/75) 183 | * Stores may now use a `listenables` prop [#63](https://github.com/spoike/refluxjs/pull/63) to automatically register actions to callbacks 184 | * `preEmit` may now map or transform the action payload by returning something. See [58](https://github.com/spoike/refluxjs/issues/58) and [#78](https://github.com/spoike/refluxjs/pull/78) 185 | * Reflux now exposes a `keep` for easier introspection on actions and stores [#56](https://github.com/spoike/refluxjs/issues/56) 186 | * Added mixin interfaces `ListenerMethods` and `PublisherMethods` making it possible for users to extend Reflux's actions and stores. See [#45](https://github.com/spoike/refluxjs/issues/45) 187 | 188 | ## v0.1.7 189 | 190 | * Added support for initial data handling [#49](https://github.com/spoike/refluxjs/pull/49) 191 | * Added CHANGELOG.md [#50](https://github.com/spoike/refluxjs/issues/50) 192 | * Bug: Unregistered actions could not be reregistered [#47](https://github.com/spoike/refluxjs/pull/47) 193 | 194 | ## v0.1.6 195 | 196 | * Added possibility to join actions and stores with `Reflux.all` [#27](https://github.com/spoike/refluxjs/issues/27), [#28](https://github.com/spoike/refluxjs/pull/28) 197 | * Added circular dependency check [#26](https://github.com/spoike/refluxjs/issues/26) 198 | 199 | ## v0.1.5 200 | 201 | * Bug fix 202 | 203 | ## v0.1.4 204 | 205 | * Action functors are now deferred [#22](https://github.com/spoike/refluxjs/issues/22), [#23](https://github.com/spoike/refluxjs/pull/23) 206 | * Added web tests using testling.ci [#20](https://github.com/spoike/refluxjs/pull/20) 207 | 208 | ## v0.1.3 209 | 210 | * Added hooks `preEmit` and `shouldEmit` for actions [#16](https://github.com/spoike/refluxjs/issues/16) 211 | * Various bug fixes and `.jshintrc` file created for grunt build 212 | 213 | ## v0.1.2 214 | 215 | * Added `ListenerMixin` useful for React components [#7](https://github.com/spoike/refluxjs/issues/7) 216 | * Using `eventemitter3` instead [#4](https://github.com/spoike/refluxjs/issues/4) 217 | 218 | ## v0.1.1 219 | 220 | * Added convenience function to create multiple actions [#6](https://github.com/spoike/refluxjs/issues/6) 221 | * Bug: createStore's unsubscribe function was broken [#5](https://github.com/spoike/refluxjs/issues/5) 222 | 223 | ## v0.1.0 224 | 225 | * Removed lodash dependency [#1](https://github.com/spoike/refluxjs/issues/1) 226 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | require('es6-promise').polyfill(); 2 | 3 | module.exports = function(grunt) { 4 | 5 | var sauceLaunchers = require('./test/sauceLaunchers'); 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | jshint: { 10 | files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'], 11 | options: { 12 | jshintrc: true 13 | } 14 | }, 15 | mochaTest: { 16 | test: { 17 | src: ['test/**/*.spec.js'] 18 | } 19 | }, 20 | browserify: { 21 | dist: { 22 | src: ['src/index.js'], 23 | dest: 'dist/<%= pkg.name %>.js', 24 | options: { 25 | exclude: ['react'], 26 | browserifyOptions: { 27 | standalone: 'Reflux' 28 | } 29 | }, 30 | } 31 | }, 32 | uglify: { 33 | dist: { 34 | src: 'dist/reflux.js', 35 | dest: 'dist/reflux.min.js' 36 | } 37 | }, 38 | watch: { 39 | files: ['<%= jshint.files %>'], 40 | tasks: ['build'] 41 | }, 42 | karma: { 43 | local: { 44 | configFile: 'karma.conf.js', 45 | options: { 46 | browsers: ['PhantomJS'] 47 | } 48 | }, 49 | devsauce: { 50 | configFile: 'karma.conf.js', 51 | options: { 52 | reporters: ['saucelabs', 'dots'], 53 | sauceLabs: { 54 | "public": "team", 55 | testName: 'RefluxJS Karma Tests (Dev)', 56 | recordVideo: false, 57 | recordScreenshot: false, 58 | tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER, 59 | }, 60 | customLaunchers: sauceLaunchers, 61 | browsers: Object.keys(sauceLaunchers), 62 | captureTimeout: 0 63 | }, 64 | }, 65 | sauce: { 66 | configFile: 'karma.conf.js', 67 | options: { 68 | reporters: ['saucelabs', 'dots'], 69 | sauceLabs: { 70 | "public": "public", 71 | testName: 'RefluxJS Karma Tests (Travis)', 72 | recordVideo: false, 73 | recordScreenshot: false, 74 | startConnect: false, 75 | tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER, 76 | connectOptions: { 77 | port: 5757, 78 | logfile: "sauce_connect.log" 79 | } 80 | }, 81 | customLaunchers: sauceLaunchers, 82 | browsers: Object.keys(sauceLaunchers), 83 | singleRun: true, 84 | captureTimeout: 0 85 | }, 86 | } 87 | } 88 | }); 89 | 90 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 91 | 92 | grunt.registerTask('test', ['jshint', 'mochaTest', 'karma:local']); 93 | 94 | grunt.registerTask('build', ['test', 'browserify', 'uglify']); 95 | 96 | grunt.registerTask('default', ['watch']); 97 | 98 | }; 99 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Mikael Brassman 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # RefluxJS 3 | 4 | A simple library for unidirectional dataflow architecture inspired by ReactJS [Flux](http://facebook.github.io/react/blog/2014/05/06/flux.html). 5 | 6 | [![NPM Version][npm-image]][npm-url] 7 | [![Bower Version][bower-image]][bower-url] 8 | [![Build Status][travis-image]][travis-url] 9 | [![NPM Downloads][downloads-image]][npm-url] 10 | 11 | [](https://saucelabs.com/u/refluxjs) 12 | 13 | -------------------- 14 | 15 | ## Installation 16 | 17 | You can currently install the package as a npm package, a bower component, or import it from a CDN. 18 | 19 | #### NPM 20 | 21 | The following command installs RefluxJS as a npm package: 22 | 23 | npm install reflux 24 | 25 | Then, in your script, you can gain a reference to RefluxJS like so: `var Reflux = require('reflux');` 26 | 27 | #### Bower 28 | 29 | The following command installs reflux as a bower component that can be used in the browser: 30 | 31 | bower install reflux 32 | 33 | Then the files may be imported into your html file via `bower_components/reflux/dist/reflux.js` or `bower_components/reflux/dist/reflux.min.js`. At that point a `Reflux` variable will be globally available to you. It is suggested that you import RefluxJS after React. 34 | 35 | #### CDN 36 | 37 | RefluxJS is available at [jsdelivr](http://www.jsdelivr.com/#!refluxjs). 38 | 39 | You may import the CDN files directly through a script tag. At that point a `Reflux` variable will be globally available to you. It is suggested that you import RefluxJS after React. 40 | 41 | -------------------------------- 42 | 43 | ## Overview 44 | 45 | The main function of Reflux is to introduce a more functional programming style architecture by eschewing MVC like pattern and adopting a single data flow pattern. 46 | 47 | ``` 48 | +---------+ +--------+ +-----------------+ 49 | ¦ Actions ¦------>¦ Stores ¦------>¦ View Components ¦ 50 | +---------+ +--------+ +-----------------+ 51 | ^ ¦ 52 | +--------------------------------------+ 53 | 54 | ``` 55 | 56 | The pattern is composed of actions and data stores, where actions initiate new data to pass through data stores before coming back to the view components again. If a view component has an event that needs to make a change in the application's data stores, they need to do so by signaling to the stores through the actions available. 57 | 58 | -------------------------------- 59 | 60 | ## Usage 61 | 62 | For usage, you need to create actions which can be called from React components. Those actions are listened to by stores which hold and update data. In turn those stores are hooked up to React components and set state within them as it is updated within the store. 63 | 64 | Therefore the 3 main concepts to know are: 65 | 66 | 1. [creating actions](#creating-actions) 67 | 2. [creating stores](#creating-stores) 68 | 3. [hooking stores to React components](#hooking-stores-to-components) 69 | 70 | -------------------------------- 71 | 72 | ## Creating Actions 73 | 74 | Create an action by calling `Reflux.createAction` with an optional options object. 75 | 76 | ```javascript 77 | var statusUpdate = Reflux.createAction(); 78 | ``` 79 | 80 | An action is a [function object](http://en.wikipedia.org/wiki/Function_object) that can be invoked like any other function. 81 | 82 | ```javascript 83 | statusUpdate(data); // Invokes the action statusUpdate 84 | ``` 85 | 86 | There is also a convenience function for creating multiple actions. 87 | 88 | ```javascript 89 | var Actions = Reflux.createActions([ 90 | "statusUpdate", 91 | "statusEdited", 92 | "statusAdded" 93 | ]); 94 | 95 | // Actions object now contains the actions 96 | // with the names given in the array above 97 | // that may be invoked as usual 98 | 99 | Actions.statusUpdate(); 100 | ``` 101 | 102 | #### More on Actions: 103 | Actions can also: 104 | - load files asynchronously with child actions 105 | - do preEmit and shouldEmit checking 106 | - have many shortcuts for easy usage 107 | 108 | See [Reflux Action Documentation](docs/actions/) for more. 109 | 110 | ------------------ 111 | 112 | ## Creating Stores 113 | 114 | Create a data store much like ReactJS's own `React.Component` by creating a class extending `Reflux.Store`. The store has a `state` property much like a component, and uses `setState` like a component as well. You may set up all action listeners in the `constructor` and register them by calling the store's own `listenTo` function. 115 | 116 | ```javascript 117 | class StatusStore extends Reflux.Store 118 | { 119 | constructor() 120 | { 121 | super(); 122 | this.state = {flag:'OFFLINE'}; // <- set store's default state much like in React 123 | this.listenTo(statusUpdate, this.onStatusUpdate); // listen to the statusUpdate action 124 | } 125 | 126 | onStatusUpdate(status) 127 | { 128 | var newFlag = status ? 'ONLINE' : 'OFFLINE'; 129 | this.setState({flag:newFlag}); 130 | } 131 | } 132 | ``` 133 | 134 | In the above example, whenever the action `statusUpdate` is called, the store's `onStatusUpdate` callback will be called with whatever parameters were sent in the action. E.g. if the action is called as `statusUpdate(true)` then the `status` argument in the `onStatusUpdate` function is `true`. 135 | 136 | Stores also integrate easily with sets of actions via things like `this.listenables`. When an actions object (or an Array of multiple actions objects) is applied to `this.listenables` you may automatically add listeners simply by naming convention. Just name the functions either after the action name (such as `actionName`, or the camelcased action name preceded with "on", (such as `onActionName`). 137 | 138 | ```javascript 139 | var Actions = Reflux.createActions(['firstAction', 'secondAction']); 140 | 141 | class StatusStore extends Reflux.Store 142 | { 143 | constructor() 144 | { 145 | super(); 146 | this.listenables = Actions; 147 | } 148 | 149 | onFirstAction() 150 | { 151 | // calls on Actions.firstAction(); 152 | } 153 | 154 | onSecondAction() 155 | { 156 | // calls on Actions.secondAction(); 157 | } 158 | } 159 | ``` 160 | 161 | #### More on Stores: 162 | 163 | Reflux stores are very powerful. They can even do things like contribute to a global state that can be read and set for partial or full-state time-travel, debugging, etc. 164 | 165 | See [Reflux Store Documentation](docs/stores/) to learn more about stores. 166 | 167 | -------------------------- 168 | 169 | ## Hooking Stores to Components 170 | 171 | Once you've created actions and stores, now the last step in working RefluxJS is to hook those stores to a React component. 172 | 173 | This is done as simply as extending `Reflux.Component` instead of `React.Component` and setting the store(s) to use. `Reflux.Component` itself extends `React.Component`, so you use them the exact same way. The only difference is that `Reflux.Component` allows you to set stores for the component to get state from: 174 | 175 | ```javascript 176 | class MyComponent extends Reflux.Component 177 | { 178 | constructor(props) 179 | { 180 | super(props); 181 | this.state = {}; // our store will add its own state to the component's 182 | this.store = StatusStore; // <- just assign the store class itself 183 | } 184 | 185 | render() 186 | { 187 | var flag = this.state.flag; // <- flag is mixed in from the StatusStore 188 | return
The color is: {this.state.color}
; 107 | } 108 | } 109 | ``` 110 | 111 | Note that the `fromStore` in the example above may not always be the full state from the store. It only receives what is actually changing at that time. This way you are able to only map the parts of the store that have actually changed on each call. 112 | 113 | ### Extending a 3rd Party Class with `Reflux.Component.extend` 114 | 115 | Sometimes 3rd party libraries will have their own class that extends `React.Component` that they require you to use. Reflux handles this by exposing the `Reflux.Component.extend` method. If you have such a 3rd party class you can pass that class to this method and it will return a version of `Reflux.Component` that extends it instead of extending `React.Component` directly. Example: 116 | 117 | ```javascript 118 | import {ThirdPartyComponent} from 'third-party'; 119 | 120 | var RefluxThirdPartyComponent = Reflux.Component.extend(ThirdPartyComponent); 121 | 122 | class MyComponent extends RefluxThirdPartyComponent 123 | { 124 | // ... 125 | } 126 | ``` 127 | 128 | ### componentWillMount and componentWillUnmount 129 | 130 | The `Reflux.Component` class utilizes both the `componentWillMount` and `componentWillUnmount` methods in the React lifecycle. This means that any extension you create that uses these methods will override those methods within the `Reflux.Component` class you're extending. If you run into this problem the solution is simple: just run the appropriate `super`. For example, in a `componentWillMount`: 131 | 132 | ```javascript 133 | // ... 134 | 135 | componentWillMount() 136 | { 137 | // ... your stuff ... 138 | 139 | super.componentWillMount(); 140 | } 141 | 142 | //... 143 | ``` 144 | 145 | ### More: 146 | 147 | Learn about [Reflux Stores](../stores/) and [Actions](../actions/), or go back to the overview of the [docs](../). 148 | -------------------------------------------------------------------------------- /docs/other/examples.md: -------------------------------------------------------------------------------- 1 | 2 | ## Examples 3 | 4 | #### Simple Fully Functioning Example 5 | 6 | ```javascript 7 | var Actions = Reflux.createActions([ 8 | 'increment', 9 | 'decrement', 10 | 'changeBy' 11 | ]); 12 | 13 | class CounterStore extends Reflux.Store 14 | { 15 | constructor() 16 | { 17 | super(); 18 | this.state = {count: 0}; 19 | this.listenables = Actions; 20 | } 21 | 22 | onIncrement() 23 | { 24 | this.setState({count: this.state.count+1}); 25 | } 26 | 27 | onDecrement() 28 | { 29 | this.setState({count: this.state.count-1}); 30 | } 31 | 32 | onChangeBy(amount) 33 | { 34 | this.setState({count: this.state.count+amount}); 35 | } 36 | } 37 | 38 | class Counter extends Reflux.Component 39 | { 40 | constructor(props) 41 | { 42 | super(props); 43 | this.store = CounterStore; 44 | } 45 | 46 | render() 47 | { 48 | returnFrom Store: {this.state.storeProp}, Foo: {this.state.foo}
; 713 | } 714 | } 715 | ``` 716 | 717 | The default states of the stores will be mixed in from the start, and any time the store does a `trigger` the triggered data will be mixed in to the component and it will re-render. 718 | 719 | A fully working example may look something like this: 720 | 721 | ```javascript 722 | var Actions = Reflux.createActions(["increment"]); 723 | 724 | var counterStore = Reflux.createStore( 725 | { 726 | listenables: Actions, 727 | 728 | init: function() { 729 | this.state = {count:0}; 730 | }, 731 | 732 | onIncrement: function(txt) { 733 | this.state.count++; 734 | this.trigger(this.state); 735 | } 736 | }); 737 | 738 | class Counter extends Reflux.Component 739 | { 740 | constructor(props) { 741 | super(props); 742 | this.state = {}; 743 | this.store = counterStore; 744 | } 745 | 746 | render() { 747 | returnCount: {this.state.count}
; 748 | } 749 | } 750 | 751 | 752 | ReactDOM.render( 753 |Count: {this.state.count}
; 849 | } 850 | } 851 | ``` 852 | 853 | **Note!** `Reflux.Store` still works with instances of stores (i.e. the class must get intantiated). Assigning the class itself to `this.store` just allows Reflux to handle the instantiation and do some internal things that allow features like global state tracking. it does *not* mean that the class itself is the store. Internally Reflux creates and utilizes a singleton instance of the class. After mounting you may access that singleton instance of the class via `MyStoreClass.singleton`. 854 | 855 | #### Using Reflux.Store without a component 856 | 857 | With to ability to do so much via global states (covered in the next section), and the fact that that functionality is tied to `Reflux.Store`, being able to properly utilize `Reflux.Store` on its own (without binding to a React component) becomes useful. However, just using `new MyStoreClass()` isn't enough, as it has to tie itself into the Reflux global state as a singleton. Therefore Reflux exposes an API for getting a properly globalized singleton instance of a `Reflux.Store` extended class without having to tie it to a React component. You do this via the following: 858 | 859 | ```javascript 860 | var mySingleton = Reflux.initializeGlobalStore(MyClassName); 861 | ``` 862 | 863 | When done this way the singleton instance of your `Reflux.Store` class can, externally, be used much like a non-ES6 store created via `Reflux.createStore` except with the advantages that it: 1) is written in the `Reflux.Store` ES6 syntax and 2) it ties in with the global state being tracked by Reflux. 864 | 865 | Note: your store _must_ be set up with an `id` to be used this way. 866 | 867 | Note: even after instantiating with `Reflux.initializeGlobalStore` you can still later assign the class name itself to `this.store` or `this.stores` in a `Reflux.Component`. The component will recognize that a singleton for the class has already been created and use that singleton. 868 | 869 | ##### A deeper understanding: 870 | 871 | To avoid confusion I want to better explain what `Reflux.initializeGlobalStore` is for on a deeper level. This is also a good section to read for people that just want a better understanding of working with `Reflux.Store` in general. 872 | 873 | When you define a store in ES6 `Reflux.Store` syntax you are creating a class, not an instance of a class. For Reflux to use that store internally an instance of the class must be created. But it's also important that only **one** instance of that store class get created (i.e. a singleton) so that each component using it is using the same store instance. That is why you're told to assign the class itself to `this.store` or `this.stores` in a `Reflux.Component`. Assigning an instance works...but if you assign the class itself then it can use its own internal logic to say *"Is a singleton already made for this class? If yes, use that singleton. If not, then make it first, then use it."* therefore automatically making sure only one singleton instance is used everywhere. On top of that (if the class has an id) it also makes sure that the global state is aware of that store so that it can track it. 874 | 875 | This works great as long as you're going to be using that store in a component. But what if you don't want to use it in any components? 876 | 877 | If you don't care about the global state knowing about it and tracking it then it's easy. Since no components are using it then there's no chance of multiple store instances accidentally being created, so just use `var store = new MyStoreClass();` like any other class you'd be instantiating. If you wanted to be thorough and make your instance in the same singleton fashion as is done internally (just in case some later component decides to use that class without your knowledge) then you could go `var store = new MyStoreClass(); MyStoreClass.singleton = store;` and then any future component usage would know about, and use, that singleton instance. 878 | 879 | But that still leaves a scenario out in the cold: needing to intantiate a store without it being attached to any components to handle it for you, but *also* wanting that store to be properly tracked by the global state. That is where `Reflux.initializeGlobalStore` can be used to create your singleton instance and handle what needs to be handled internally to track the global state. 880 | 881 | Another possible scenario would be if you need to access the singleton instance of the store *before* the `componentWillMount` part of any component's lifecycle (which is where the component would set up the singleton). You can use `Reflux.initializeGlobalStore` to create a singleton that you can access sooner. You can *still* assign the class itself to `this.store` or `this.stores` in any components though! The component will know that a singleton has already been created and automatically use it, there is no need for you to manually track the singleton for the components just because you used `Reflux.initializeGlobalStore` in order to get access to that singleton sooner! 882 | 883 | #### Mapping Stores to Components with `mapStoreToState` 884 | 885 | Reflux's ES6 stores and components work together fairly well in a very declarative syntax by simply assigning stores to components via the component's `this.store` and `this.stores` properties, and that functionality rounds itself out by adding some filtering ability with `this.storeKeys`. With enough thought given to architecture this can get you almost everything you need in a declarative syntax that is easy for you to write and others to read. Therefore it's highly suggested that you put the time into planning your architecture to be able to use those for connecting stores and components. 886 | 887 | However, there still exists a need to have deeper control and to be able to map stores to component states with your own custom logic in some cases. For that each `Reflux.Component` will have a `this.mapStoreToState` method. This is **completely separate** from the previously mentioned declarative side of things (such as `this.store`). That means you should not have the same store attached to a component both via `this.store` *and* using `this.mapStoreToState`, and also that this method is completely unaffected by `this.storeKeys`. These differing methods can both be used within a single component, they just shouldn't be both used for the *same store* within the same component. 888 | 889 | This method takes 2 arguments: the `Reflux.Store` you want mapped to the component state (either the class itself or the singleton instance) and a mapping function supplied by you. The mapping function will be called any time the store instance's `setState` is used to change the state of the store. The mapping function takes an argument which will be the state change object from the store for that particular change. It needs to return an object which will then be mapped to the component state (similar to if that very returned object were used in the component's `setState`). If an object with no properties is returned then the component will *not* re-render. The mapping function is also called with its `this` keyword representing the component, so comparing store values to current component state values via `this.state` is possible as well. 890 | 891 | ```javascript 892 | class Counter extends Reflux.Component 893 | { 894 | constructor(props) { 895 | super(props); 896 | this.mapStoreToState(MyStoreClass, function(fromStore){ 897 | var obj = {}; 898 | if (fromStore.color) 899 | obj.color = fromStore.color; 900 | if (fromStore.data && fromStore.data.classToUse) 901 | obj.class = fromStore.data.classToUse; 902 | return obj; 903 | }); 904 | } 905 | 906 | render() { 907 | returnThe color is: {this.state.color}
; 908 | } 909 | } 910 | ``` 911 | 912 | In the above example `MyStoreClass` could have lots of state properties, but we use a bit of logic to 1) only trigger a re-render if the store's `state.color` or `state.data.classToUse` were among the parts of the state involved in the store's `setState` call (because, remember, if the returned object has no properties no re-render happens), and 2) to map the stores `state.color` straight to the component's `state.color`, but the store's `state.data.classToUse` to the component's `state.class`. 913 | 914 | Note that the example function above is merely that: an example. Whatever sort of logic you want to apply to get from the change object given by the store to how you want that to change the state of your component is fair game, except that you should not mutate the incoming data itself. 915 | 916 | ### Utilizing Reflux.GlobalState 917 | 918 | Another neat feature that the ES6 implementation of Reflux has is the ability to track a global state of all stores in use, as well as initialize all stores in use to a predefined global state. It happens internally too, so you don't have to do hardly anything to make it happen. This would be useful for many things, including tracking the state of an application and going back to that same state the next time the app is used. 919 | 920 | To make it happen you just have to use ES6 style reflux classes and stores like explained in the last couple sections and define a static `id` property in your `Reflux.Store` definition. That id will then be used as a property name within the `Reflux.GlobalState` object for the property holding that store's current state. Then you just need to make sure to use `setState` to modify the state of the `Reflux.Store` instead of mutating the state directly. After that the `Reflux.GlobalState` object will reflect a collection of all your stores at all times once the components using those stores are mounted. An example using the example above: 921 | 922 | ```javascript 923 | class CounterStore extends Reflux.Store 924 | { 925 | constructor() { 926 | super(); 927 | this.listenables = Actions; 928 | this.state = {count:0}; 929 | } 930 | 931 | onIncrement() { 932 | var cnt = this.state.count; 933 | this.setState({count:cnt+1}); 934 | } 935 | 936 | static get id() { 937 | return 'counterstore'; 938 | } 939 | } 940 | 941 | // ... make component and render as normal ... 942 | 943 | console.log(Reflux.GlobalState); // <- would be: {'counterstore':{'count':0}} 944 | ``` 945 | 946 | Notice that you can only read the GlobalState **after** the components using the stores have been mounted. Up until then is the time where you can manually set the `Reflux.GlobalState` in order to initialize the entire app in a state of your choosing (or a previous state you recorded earlier). For example we could do this: 947 | 948 | ```javascript 949 | class CounterStore extends Reflux.Store 950 | { 951 | constructor() { 952 | super(); 953 | this.listenables = Actions; 954 | this.state = {count:0}; 955 | } 956 | 957 | onIncrement() { 958 | var cnt = this.state.count; 959 | this.setState({count:cnt+1}); 960 | } 961 | 962 | static get id() { 963 | return 'counterstore'; 964 | } 965 | } 966 | 967 | Reflux.GlobalState = {'counterstore':{'count':50}}; 968 | 969 | // ... make component and render as normal ... 970 | 971 | // at this point it would render with a count of 50! 972 | ``` 973 | 974 | One of the most useful ways you could do this is to store a `Reflux.GlobalState` state as a JSON string in order to implement it again the next time the app starts up and have the user begin right where they left off. 975 | 976 | #### Reflux.setGlobalState and Reflux.getGlobalState 977 | 978 | Directly accessing `Reflux.GlobalState` is a fine way to do set the starting state of an app and to do automated testing, but it is also helpful to be able to manipulate the global state while the app is running as well. To do this Reflux exposes a `Reflux.getGlobalState()` function and a `Reflux.setGlobalState()` function. The former allows you to get a deep copy of the current global state (so that the copy will not mutate as the global state itself continues to mutate) and the latter allows you to set part or all of the global state at any time in the program. Between these two functions things like state time-travel, undo/redo, and move-by-move tracking become relatively easy. 979 | 980 | ### Making sure Reflux.Component is available 981 | 982 | `Reflux.Component` extends `React.Component`. Therefore Reflux needs to be able to access React in order to expose it. In the browser as long as React is loaded before Reflux then Reflux will automatically find it. Likewise in node-like environments where `require('react')` will function Reflux will try to access React that way. So in almost all situations Reflux will find React on its own. 983 | 984 | However, Reflux also exposes the method `Reflux.defineReact` that you can use to manually give Reflux a reference to the React object in case you need to: 985 | 986 | ```javascript 987 | // only needed if, for some reason, Reflux can't get reference to React: 988 | var React = /* however you access React */; 989 | Reflux.defineReact(React); 990 | // now Reflux.Component is accessible! 991 | ``` 992 | 993 | ### Extending a 3rd Party Class 994 | 995 | Sometimes 3rd party libraries will have their own class that extends `React.Component` that they require you to use. Reflux handles this by exposing the `Reflux.Component.extend` method. If you have such a 3rd party class you can pass that class to this method and it will return a version of `Reflux.Component` that extends it instead of extending `React.Component` directly. Example: 996 | 997 | ```javascript 998 | import {ThirdPartyComponent} from 'third-party'; 999 | 1000 | var RefluxThirdPartyComponent = Reflux.Component.extend(ThirdPartyComponent); 1001 | 1002 | class MyComponent extends RefluxThirdPartyComponent 1003 | { 1004 | // ... 1005 | } 1006 | ``` 1007 | 1008 | [Back to top](#content) 1009 | 1010 | ## Colophon 1011 | 1012 | [List of contributors](https://github.com/spoike/reflux/graphs/contributors) is available on Github. 1013 | 1014 | This project is licensed under [BSD 3-Clause License](http://opensource.org/licenses/BSD-3-Clause). Copyright (c) 2014, Mikael Brassman. 1015 | 1016 | For more information about the license for this particular project [read the LICENSE.md file](LICENSE.md). 1017 | 1018 | This project uses [eventemitter3](https://github.com/3rd-Eden/EventEmitter3), which [is currently MIT licensed](https://github.com/3rd-Eden/EventEmitter3/blob/master/LICENSE). 1019 | 1020 | [npm-image]: http://img.shields.io/npm/v/reflux.svg 1021 | [downloads-image]: http://img.shields.io/npm/dm/reflux.svg 1022 | [dependencies-image]: http://img.shields.io/david/reflux/refluxjs.svg 1023 | [npm-url]: https://www.npmjs.org/package/reflux 1024 | [bower-image]: http://img.shields.io/bower/v/reflux.svg 1025 | [bower-url]: http://bower.io/search/?q=reflux 1026 | [travis-image]: http://img.shields.io/travis/reflux/refluxjs/master.svg 1027 | [travis-url]: https://travis-ci.org/reflux/refluxjs 1028 | [gratipay-image]: http://img.shields.io/gratipay/spoike.svg 1029 | [gratipay-url]: https://gratipay.com/spoike/ 1030 | [thinkful-image]: https://tf-assets-staging.s3.amazonaws.com/badges/thinkful_repo_badge.svg 1031 | [thinkful-url]: http://start.thinkful.com/react/?utm_source=github&utm_medium=badge&utm_campaign=reflux 1032 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reflux", 3 | "version": "6.4.1", 4 | "description": "A simple library for uni-directional dataflow application architecture inspired by ReactJS Flux", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "grunt test", 8 | "test:sauce": "grunt karma:sauce", 9 | "benchmark": "node test/benchmarks", 10 | "build": "grunt build" 11 | }, 12 | "author": "Mikael Brassman", 13 | "license": "BSD-3-Clause", 14 | "repository": { 15 | "type": "git", 16 | "url": "http://github.com/reflux/refluxjs.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/reflux/refluxjs/issues" 20 | }, 21 | "keywords": [ 22 | "reflux", 23 | "react", 24 | "flux", 25 | "architecture", 26 | "dataflow", 27 | "action", 28 | "event", 29 | "data" 30 | ], 31 | "dependencies": { 32 | "eventemitter3": "^1.1.1", 33 | "reflux-core": "^1.0.0" 34 | }, 35 | "devDependencies": { 36 | "benchmark": "^1.0.0", 37 | "browserify": "~10.2.3", 38 | "chai": "latest", 39 | "chai-as-promised": "latest", 40 | "es6-promise": "^2.3.0", 41 | "grunt": "^0.4.5", 42 | "grunt-browserify": "3.8.0", 43 | "grunt-cli": "^0.1.13", 44 | "grunt-contrib-jshint": "^0.11.2", 45 | "grunt-contrib-uglify": "^0.5.0", 46 | "grunt-contrib-watch": "^0.6.1", 47 | "grunt-karma": "latest", 48 | "grunt-mocha-test": "~0.12.7", 49 | "karma": "latest", 50 | "karma-browserify": "latest", 51 | "karma-commonjs": "latest", 52 | "karma-mocha": "latest", 53 | "karma-phantomjs-launcher": "latest", 54 | "karma-sauce-launcher": "latest", 55 | "karma-spec-reporter": "latest", 56 | "matchdep": "^0.3.0", 57 | "mocha": "latest", 58 | "q": "^1.0.1", 59 | "react-dom": "^15.0.2", 60 | "sinon": "^1.10.3" 61 | }, 62 | "peerDependencies": { 63 | "react": "^15.0.2" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ListenerMixin.js: -------------------------------------------------------------------------------- 1 | var _ = require('reflux-core/lib/utils'), 2 | ListenerMethods = require('reflux-core/lib/ListenerMethods'); 3 | 4 | /** 5 | * A module meant to be consumed as a mixin by a React component. Supplies the methods from 6 | * `ListenerMethods` mixin and takes care of teardown of subscriptions. 7 | * Note that if you're using the `connect` mixin you don't need this mixin, as connect will 8 | * import everything this mixin contains! 9 | */ 10 | module.exports = _.extend({ 11 | 12 | /** 13 | * Cleans up all listener previously registered. 14 | */ 15 | componentWillUnmount: ListenerMethods.stopListeningToAll 16 | 17 | }, ListenerMethods); 18 | -------------------------------------------------------------------------------- /src/addES6.js: -------------------------------------------------------------------------------- 1 | 2 | /* globals React: false */ 3 | 4 | var Reflux = require('reflux-core'); 5 | Reflux.defineReact = require('./defineReact'); 6 | 7 | // useful utility for ES6 work, mimics the ability to extend 8 | Reflux.utils.inherits = function(subClass, superClass) { 9 | if (typeof superClass !== "function" && superClass !== null) { 10 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 11 | } 12 | subClass.prototype = Object.create(superClass && superClass.prototype, { 13 | constructor: { 14 | value: subClass, 15 | enumerable: false, 16 | writable: true, 17 | configurable: true 18 | } 19 | }); 20 | if (superClass) { 21 | if (Object.setPrototypeOf) { 22 | Object.setPrototypeOf(subClass, superClass); 23 | } else { 24 | /* jshint proto: true */ 25 | subClass.__proto__ = superClass; 26 | } 27 | } 28 | }; 29 | 30 | // first try to see if there's a global React var and use it 31 | if (typeof React !== 'undefined' && React) { 32 | Reflux.defineReact(React); 33 | // otherwise we're gonna resort to 'try' stuff in case of other environments 34 | } else { 35 | try { 36 | var R = require("react"); // we ignore this in browserify manually (see grunt file), so it's more of a doublecheck for in node 37 | Reflux.defineReact(R); 38 | } catch (e) {} 39 | } 40 | -------------------------------------------------------------------------------- /src/connect.js: -------------------------------------------------------------------------------- 1 | var ListenerMethods = require('reflux-core/lib/ListenerMethods'), 2 | ListenerMixin = require('./ListenerMixin'), 3 | _ = require('reflux-core/lib/utils'); 4 | 5 | module.exports = function(listenable, key) { 6 | 7 | _.throwIf(typeof(key) === 'undefined', 'Reflux.connect() requires a key.'); 8 | 9 | return { 10 | getInitialState: function() { 11 | if (!_.isFunction(listenable.getInitialState)) { 12 | return {}; 13 | } 14 | 15 | return _.object([key],[listenable.getInitialState()]); 16 | }, 17 | componentDidMount: function() { 18 | var me = this; 19 | 20 | _.extend(me, ListenerMethods); 21 | 22 | this.listenTo(listenable, function(v) { 23 | me.setState(_.object([key],[v])); 24 | }); 25 | }, 26 | componentWillUnmount: ListenerMixin.componentWillUnmount 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/connectFilter.js: -------------------------------------------------------------------------------- 1 | var ListenerMethods = require('reflux-core/lib/ListenerMethods'), 2 | ListenerMixin = require('./ListenerMixin'), 3 | _ = require('reflux-core/lib/utils'); 4 | 5 | module.exports = function(listenable, key, filterFunc) { 6 | 7 | _.throwIf(_.isFunction(key), 'Reflux.connectFilter() requires a key.'); 8 | 9 | return { 10 | getInitialState: function() { 11 | if (!_.isFunction(listenable.getInitialState)) { 12 | return {}; 13 | } 14 | 15 | // Filter initial payload from store. 16 | var result = filterFunc.call(this, listenable.getInitialState()); 17 | if (typeof(result) !== 'undefined') { 18 | return _.object([key], [result]); 19 | } else { 20 | return {}; 21 | } 22 | }, 23 | componentDidMount: function() { 24 | var me = this; 25 | 26 | _.extend(this, ListenerMethods); 27 | 28 | this.listenTo(listenable, function(value) { 29 | var result = filterFunc.call(me, value); 30 | me.setState(_.object([key], [result])); 31 | }); 32 | }, 33 | componentWillUnmount: ListenerMixin.componentWillUnmount 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /src/defineReact.js: -------------------------------------------------------------------------------- 1 | /* globals React: false */ 2 | 3 | var Reflux = require('reflux-core'); 4 | 5 | /** 6 | * Reflux.defineReact function where you can manually supply 7 | * the React object in order to create in case Reflux needs to load before 8 | * React or there is a modular environment where there won't be a global 9 | * React variable. 10 | * @note The third param is for internal usage only. 11 | */ 12 | var _react, _defined = false; 13 | function defineReact(react, noLongerUsed, extend) 14 | { 15 | var proto, _extend; 16 | 17 | // if no Reflux object is yet available then return and just wait until defineReact is called manually with it 18 | try { 19 | _react = react || _react || React; 20 | _extend = extend || _react.Component; 21 | } catch (e) { 22 | return; 23 | } 24 | 25 | // if Reflux and React aren't present then ignore, wait until they are properly present 26 | // also ignore if it's been called before UNLESS there's manual extending happening 27 | if (!_react || !_extend || (_defined && !extend)) { 28 | return; 29 | } 30 | 31 | // ----------- BEGIN Reflux.Component ------------ 32 | /** 33 | * Reflux.Component: 34 | * An implementation for idiomatic React.js classes that mix with 35 | * Reflux stores. To utilize extend Reflux.Component instead of 36 | * React.Component. Then you may hook any Reflux store that has a 37 | * `this.state` property containing its state values to the component 38 | * via `this.store` or an Array of Reflux stores via `this.stores` in 39 | * the component's constructor (similar to how you assign initial state 40 | * in the constructor in ES6 style React). The default values of the 41 | * stores will automatically reflect in the component's state, and any 42 | * further `trigger` calls from that store will update properties passed 43 | * in the trigger into the component automatically. 44 | */ 45 | var RefluxComponent = function(props, context, updater) { 46 | _extend.call(this, props, context, updater); 47 | }; 48 | 49 | // equivalent of `extends React.Component` or other class if provided via `extend` param 50 | Reflux.utils.inherits(RefluxComponent, _extend); 51 | 52 | proto = RefluxComponent.prototype; 53 | 54 | /** 55 | * this.storeKeys 56 | * When this is a falsey value (null by default) the component mixes in 57 | * all properties from the stores attached to it and updates on changes 58 | * from all of them. When set to an array of string keys it will only 59 | * utilized state property names of those keys in any store attached. This 60 | * lets you choose which parts of stores update the component on a component- 61 | * by-component basis. If using this it is best set in the constructor. 62 | */ 63 | proto.storeKeys = null; 64 | 65 | // on the mounting of the component that is where the store/stores are attached and initialized if needed 66 | proto.componentWillMount = function () { 67 | // if there is a this.store then simply push it onto the this.stores array or make one if needed 68 | if (this.store) { 69 | if (Array.isArray(this.stores)) { 70 | this.stores.unshift(this.store); 71 | } else { 72 | this.stores = [this.store]; 73 | } 74 | } 75 | 76 | if (this.stores) { 77 | this.__storeunsubscribes__ = this.__storeunsubscribes__ || []; 78 | var sS = this.setState.bind(this); 79 | // this handles the triggering of a store, checking what's updated if proto.storeKeys is utilized 80 | var onStoreTrigger = function(obj){ 81 | var updateObj = filterByStoreKeys(this.storeKeys, obj); 82 | if (updateObj) { 83 | sS(updateObj); 84 | } 85 | }.bind(this); 86 | // for each store in this.stores... 87 | for (var i = 0, ii = this.stores.length; i < ii; i++) { 88 | var str = this.stores[i]; 89 | // if's a function then we know it's a class getting passed, not an instance 90 | if (typeof str === 'function') { 91 | var storeId = str.id; 92 | // if there is NOT a .singleton property on the store then this store has not been initialized yet, so do so 93 | if (!str.singleton) { 94 | str.singleton = new str(); 95 | if (storeId) { 96 | Reflux.stores[storeId] = str.singleton; 97 | } 98 | } 99 | // before we weren't sure if we were working with an instance or class, so now we know an instance is created set it 100 | // to the variables we were using so that we can just continue on knowing it's the instance we're working with 101 | this.stores[i] = str = str.singleton; 102 | // the instance should have an .id property as well if the class does, so set that here 103 | str.id = storeId; 104 | // if there is an id and there is a global state property for this store then merge 105 | // the properties from that global state into the default state of the store AND then 106 | // set the global state to that new state (since it may have previously been partial) 107 | if (storeId && Reflux.GlobalState[storeId]) { 108 | for (var key in Reflux.GlobalState[storeId]) { 109 | str.state[key] = Reflux.GlobalState[storeId][key]; 110 | } 111 | Reflux.GlobalState[storeId] = str.state; 112 | // otherwise (if it has an id) set the global state to the default state of the store 113 | } else if (storeId) { 114 | Reflux.GlobalState[storeId] = str.state; 115 | } 116 | // if no id, then no messing with global state 117 | } 118 | // listen/subscribe for the ".trigger()" in the store, and track the unsubscribes so that we can unsubscribe on unmount 119 | if (!Reflux.serverMode) { 120 | this.__storeunsubscribes__.push(str.listen(onStoreTrigger)); 121 | } 122 | // run set state so that it mixes in the props from the store with the component 123 | var updateObj = filterByStoreKeys(this.storeKeys, str.state); 124 | if (updateObj) { 125 | this.setState(updateObj); 126 | } 127 | } 128 | } 129 | 130 | // mapStoreToState needs to know if is ready to map or must wait 131 | this.__readytomap__ = true; 132 | // if there are mappings that were delayed, do them now 133 | var dmaps = this.__delayedmaps__; 134 | if (dmaps) { 135 | for (var j=0,jj=dmaps.length; jbar
' ); 72 | }); 73 | 74 | it('should accept React ancestors and children', function() 75 | { 76 | var MyChild = React.createClass({ 77 | render: function() { 78 | return React.createElement("span", null, 'Hello'); 79 | } 80 | }); 81 | 82 | var MyComponent = (function (_super) { 83 | __extends(Component, _super); 84 | function Component(props) { 85 | _super.call(this, props); 86 | this.state = {}; 87 | this.store = Reflux.createStore({state:{a:'b'}}); 88 | } 89 | Component.prototype.render = function () { 90 | return React.createElement("p", null, React.createElement(MyChild, null)); 91 | }; 92 | return Component; 93 | }(Reflux.Component)); 94 | 95 | var MyParent = React.createClass({ 96 | render: function() { 97 | return React.createElement("div", null, React.createElement(MyComponent, null)); 98 | } 99 | }); 100 | 101 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyParent, null) ); 102 | 103 | assert.equal( result, 'Hello
World
baz
' ); 170 | }); 171 | 172 | it('should accept values from multiple stores', function() 173 | { 174 | var MyComponent = (function (_super) { 175 | __extends(Component, _super); 176 | function Component(props) { 177 | _super.call(this, props); 178 | this.state = {}; 179 | this.stores = [Reflux.createStore({state:{foo:'bar'}}), Reflux.createStore({state:{bar:'foo'}})]; 180 | } 181 | Component.prototype.render = function () { 182 | return React.createElement("p", null, this.state.bar, ':', this.state.foo); 183 | }; 184 | return Component; 185 | }(Reflux.Component)); 186 | 187 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 188 | 189 | assert.equal( result, 'foo:bar
' ); 190 | }); 191 | 192 | it('should retain normal React state values', function() 193 | { 194 | var MyComponent = (function (_super) { 195 | __extends(Component, _super); 196 | function Component(props) { 197 | _super.call(this, props); 198 | this.state = {bar:'foo'}; 199 | this.store = Reflux.createStore({state:{foo:'bar'}}); 200 | } 201 | Component.prototype.render = function () { 202 | return React.createElement("p", null, this.state.bar, ':', this.state.foo); 203 | }; 204 | return Component; 205 | }(Reflux.Component)); 206 | 207 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 208 | 209 | assert.equal( result, 'foo:bar
' ); 210 | }); 211 | 212 | it('should extend third party components with Reflux.Component.extend()', function() 213 | { 214 | var OtherComponent = (function (_super) { 215 | __extends(Component, _super); 216 | function Component(props) { 217 | _super.call(this, props); 218 | this.foo = 'other class bar and '; 219 | } 220 | return Component; 221 | }(React.Component)); 222 | 223 | var MyComponent = (function (_super) { 224 | __extends(Component, _super); 225 | function Component(props) { 226 | _super.call(this, props); 227 | this.store = Reflux.createStore({state:{foo:'baz'}}); 228 | } 229 | Component.prototype.render = function () { 230 | return React.createElement("p", null, this.foo+this.state.foo); 231 | }; 232 | return Component; 233 | }(Reflux.Component.extend(OtherComponent))); 234 | 235 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 236 | 237 | assert.equal( result, 'other class bar and baz
' ); 238 | }); 239 | 240 | }); 241 | -------------------------------------------------------------------------------- /test/Reflux.GlobalState.spec.js: -------------------------------------------------------------------------------- 1 | 2 | var chai = require('chai'), 3 | assert = chai.assert; 4 | 5 | chai.use(require('chai-as-promised')); 6 | 7 | var React = require('react'); 8 | var ReactDOMServer = require('react-dom/server'); 9 | var Reflux; 10 | 11 | 12 | describe('Working with Reflux.GlobalState', function() 13 | { 14 | it('should allow defining of React with Reflux.defineReact without error and empty', function() 15 | { 16 | Reflux = require('../src'); 17 | Reflux.defineReact(React); 18 | 19 | return true; 20 | }); 21 | 22 | it('should construct with a default empty state object', function() 23 | { 24 | assert.equal( typeof Reflux.GlobalState, 'object' ); 25 | }); 26 | 27 | it('should remain empty when a store is created without a static id', function() 28 | { 29 | var MyStore = (function (_super) { 30 | Reflux.utils.inherits(Store, _super); 31 | function Store() { 32 | _super.call(this); 33 | this.state = {foo:'bar'}; 34 | } 35 | return Store; 36 | }(Reflux.Store)); 37 | 38 | var MyComponent = (function (_super) { 39 | Reflux.utils.inherits(Component, _super); 40 | function Component(props) { 41 | _super.call(this, props); 42 | this.state = {}; 43 | this.store = MyStore; 44 | } 45 | Component.prototype.render = function () { 46 | return React.createElement("p", null, this.state.foo); 47 | }; 48 | return Component; 49 | }(Reflux.Component)); 50 | 51 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 52 | 53 | assert.equal( result, 'bar
' ); 54 | assert.equal( Reflux.GlobalState.MyStore, undefined ); 55 | }); 56 | 57 | it('should gain state when a store is created with a static id', function() 58 | { 59 | Reflux.GlobalState = {}; 60 | Reflux.stores = {}; 61 | 62 | var MyStore = (function (_super) { 63 | Reflux.utils.inherits(Store, _super); 64 | function Store() { 65 | _super.call(this); 66 | this.state = {foo:'bar'}; 67 | } 68 | return Store; 69 | }(Reflux.Store)); 70 | MyStore.id = "MyStore"; 71 | 72 | var MyComponent = (function (_super) { 73 | Reflux.utils.inherits(Component, _super); 74 | function Component(props) { 75 | _super.call(this, props); 76 | this.state = {}; 77 | this.store = MyStore; 78 | } 79 | Component.prototype.render = function () { 80 | return React.createElement("p", null, this.state.foo); 81 | }; 82 | return Component; 83 | }(Reflux.Component)); 84 | 85 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 86 | 87 | assert.equal( result, 'bar
' ); 88 | assert.equal( Reflux.GlobalState.MyStore.foo, 'bar' ); 89 | }); 90 | 91 | it('should gain state when a store is created with a static id - initStore', function() 92 | { 93 | Reflux.GlobalState = {}; 94 | Reflux.stores = {}; 95 | 96 | var MyStore = (function (_super) { 97 | Reflux.utils.inherits(Store, _super); 98 | function Store() { 99 | _super.call(this); 100 | this.state = {foo:'bar'}; 101 | } 102 | return Store; 103 | }(Reflux.Store)); 104 | MyStore.id = "MyStore"; 105 | 106 | Reflux.initStore(MyStore); 107 | 108 | assert.equal( Reflux.GlobalState.MyStore.foo, 'bar' ); 109 | }); 110 | 111 | it('should update upon setState in store', function() 112 | { 113 | Reflux.GlobalState = {}; 114 | Reflux.stores = {}; 115 | 116 | var MyStore = (function (_super) { 117 | Reflux.utils.inherits(Store, _super); 118 | function Store() { 119 | _super.call(this); 120 | this.state = {foo:'bar'}; 121 | } 122 | return Store; 123 | }(Reflux.Store)); 124 | MyStore.id = "MyStore"; 125 | 126 | var MyComponent = (function (_super) { 127 | Reflux.utils.inherits(Component, _super); 128 | function Component(props) { 129 | _super.call(this, props); 130 | this.state = {}; 131 | this.store = MyStore; 132 | } 133 | Component.prototype.render = function () { 134 | return React.createElement("p", null, this.state.foo); 135 | }; 136 | return Component; 137 | }(Reflux.Component)); 138 | 139 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 140 | assert.equal( result, 'bar
' ); 141 | 142 | try { 143 | MyStore.singleton.setState({foo:'BAR'}); 144 | } catch (e) {} 145 | 146 | assert.equal( Reflux.GlobalState.MyStore.foo, 'BAR' ); 147 | }); 148 | 149 | it('should update upon setState in store - initStore', function() 150 | { 151 | Reflux.GlobalState = {}; 152 | Reflux.stores = {}; 153 | 154 | var MyStore = (function (_super) { 155 | Reflux.utils.inherits(Store, _super); 156 | function Store() { 157 | _super.call(this); 158 | this.state = {foo:'bar'}; 159 | } 160 | return Store; 161 | }(Reflux.Store)); 162 | MyStore.id = "MyStore"; 163 | 164 | var str = Reflux.initStore(MyStore); 165 | 166 | try { 167 | str.setState({foo:'BAR'}); 168 | } catch (e) {} 169 | 170 | assert.equal( Reflux.GlobalState.MyStore.foo, 'BAR' ); 171 | }); 172 | 173 | it('when set before component mounting should define initial state in store', function() 174 | { 175 | Reflux.GlobalState = {}; 176 | Reflux.stores = {}; 177 | 178 | var MyStore = (function (_super) { 179 | Reflux.utils.inherits(Store, _super); 180 | function Store() { 181 | _super.call(this); 182 | this.state = {foo:'bar'}; 183 | } 184 | return Store; 185 | }(Reflux.Store)); 186 | MyStore.id = "MyStore"; 187 | 188 | var MyComponent = (function (_super) { 189 | Reflux.utils.inherits(Component, _super); 190 | function Component(props) { 191 | _super.call(this, props); 192 | this.state = {}; 193 | this.store = MyStore; 194 | } 195 | Component.prototype.render = function () { 196 | return React.createElement("p", null, this.state.foo); 197 | }; 198 | return Component; 199 | }(Reflux.Component)); 200 | 201 | Reflux.GlobalState = {'MyStore':{'foo':'not bar'}}; 202 | 203 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 204 | 205 | assert.equal( result, 'not bar
' ); 206 | assert.equal( Reflux.GlobalState.MyStore.foo, 'not bar' ); 207 | }); 208 | 209 | it('when set before initStore should define initial state in store', function() 210 | { 211 | Reflux.GlobalState = {}; 212 | Reflux.stores = {}; 213 | 214 | var MyStore = (function (_super) { 215 | Reflux.utils.inherits(Store, _super); 216 | function Store() { 217 | _super.call(this); 218 | this.state = {foo:'bar'}; 219 | } 220 | return Store; 221 | }(Reflux.Store)); 222 | MyStore.id = "MyStore"; 223 | 224 | Reflux.GlobalState = {'MyStore':{'foo':'not bar'}}; 225 | 226 | var str = Reflux.initStore(MyStore); 227 | 228 | assert.equal( Reflux.GlobalState.MyStore.foo, 'not bar' ); 229 | assert.equal( str.state.foo, 'not bar' ); 230 | }); 231 | 232 | it('when set via setGlobalState before component mounting should define initial state in store', function() 233 | { 234 | Reflux.GlobalState = {}; 235 | Reflux.stores = {}; 236 | 237 | var MyStore = (function (_super) { 238 | Reflux.utils.inherits(Store, _super); 239 | function Store() { 240 | _super.call(this); 241 | this.state = {foo:'bar'}; 242 | } 243 | return Store; 244 | }(Reflux.Store)); 245 | MyStore.id = "MyStore"; 246 | 247 | var MyComponent = (function (_super) { 248 | Reflux.utils.inherits(Component, _super); 249 | function Component(props) { 250 | _super.call(this, props); 251 | this.state = {}; 252 | this.store = MyStore; 253 | } 254 | Component.prototype.render = function () { 255 | return React.createElement("p", null, this.state.foo); 256 | }; 257 | return Component; 258 | }(Reflux.Component)); 259 | 260 | Reflux.setGlobalState( {'MyStore':{'foo':'not bar'}} ); 261 | 262 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 263 | 264 | assert.equal( result, 'not bar
' ); 265 | assert.equal( Reflux.GlobalState.MyStore.foo, 'not bar' ); 266 | }); 267 | 268 | it('values from getGlobalState should not mutate upon further GlobalState mutations', function() 269 | { 270 | Reflux.GlobalState = {}; 271 | Reflux.stores = {}; 272 | 273 | var MyStore = (function (_super) { 274 | Reflux.utils.inherits(Store, _super); 275 | function Store() { 276 | _super.call(this); 277 | this.state = {foo:'bar'}; 278 | } 279 | return Store; 280 | }(Reflux.Store)); 281 | MyStore.id = "MyStore"; 282 | 283 | var MyComponent = (function (_super) { 284 | Reflux.utils.inherits(Component, _super); 285 | function Component(props) { 286 | _super.call(this, props); 287 | this.state = {}; 288 | this.store = MyStore; 289 | } 290 | Component.prototype.render = function () { 291 | return React.createElement("p", null, this.state.foo); 292 | }; 293 | return Component; 294 | }(Reflux.Component)); 295 | 296 | Reflux.setGlobalState( {'MyStore':{'foo':'not bar'}} ); 297 | 298 | var gotVal = Reflux.getGlobalState(); 299 | 300 | Reflux.setGlobalState( {'MyStore':{'foo':'foobar'}} ); 301 | 302 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 303 | 304 | assert.equal( result, 'foobar
' ); 305 | assert.equal( gotVal.MyStore.foo, 'not bar' ); 306 | assert.equal( Reflux.GlobalState.MyStore.foo, 'foobar' ); 307 | }); 308 | 309 | it('setGlobalState should update all global states when used with a full object', function() 310 | { 311 | Reflux.GlobalState = {}; 312 | Reflux.stores = {}; 313 | 314 | var MyStore = (function (_super) { 315 | Reflux.utils.inherits(Store, _super); 316 | function Store() { 317 | _super.call(this); 318 | this.state = {foo:'bar'}; 319 | } 320 | return Store; 321 | }(Reflux.Store)); 322 | MyStore.id = "MyStore"; 323 | 324 | var MyComponent = (function (_super) { 325 | Reflux.utils.inherits(Component, _super); 326 | function Component(props) { 327 | _super.call(this, props); 328 | this.state = {}; 329 | this.store = MyStore; 330 | } 331 | Component.prototype.render = function () { 332 | return React.createElement("p", null, this.state.bar + this.state.foo); 333 | }; 334 | return Component; 335 | }(Reflux.Component)); 336 | 337 | Reflux.GlobalState = {'MyStore':{'foo':'not bar', 'bar':'not foo'}}; 338 | Reflux.setGlobalState( {'MyStore':{'foo':'bar', 'bar':'foo'}} ); 339 | 340 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 341 | 342 | assert.equal( result, 'foobar
' ); 343 | assert.equal( Reflux.GlobalState.MyStore.foo, 'bar' ); 344 | assert.equal( Reflux.GlobalState.MyStore.bar, 'foo' ); 345 | }); 346 | 347 | it('setGlobalState should update partial global states when used with a partial object', function() 348 | { 349 | Reflux.GlobalState = {}; 350 | Reflux.stores = {}; 351 | 352 | var MyStore = (function (_super) { 353 | Reflux.utils.inherits(Store, _super); 354 | function Store() { 355 | _super.call(this); 356 | this.state = {foo:'bar'}; 357 | } 358 | return Store; 359 | }(Reflux.Store)); 360 | MyStore.id = "MyStore"; 361 | var singleton = new MyStore(); 362 | MyStore.singleton = singleton; 363 | Reflux.stores.MyStore = singleton; 364 | 365 | var MyComponent = (function (_super) { 366 | Reflux.utils.inherits(Component, _super); 367 | function Component(props) { 368 | _super.call(this, props); 369 | this.state = {}; 370 | this.store = MyStore; 371 | } 372 | Component.prototype.render = function () { 373 | return React.createElement("p", null, this.state.bar + this.state.foo); 374 | }; 375 | return Component; 376 | }(Reflux.Component)); 377 | 378 | Reflux.GlobalState = {}; 379 | Reflux.setGlobalState( {'MyStore':{'foo':'not bar', 'bar':'not foo'}} ); 380 | Reflux.setGlobalState( {'MyStore':{'foo':'bar'}} ); 381 | 382 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 383 | 384 | assert.equal( Reflux.GlobalState.MyStore.foo, 'bar' ); 385 | assert.equal( Reflux.GlobalState.MyStore.bar, 'not foo' ); 386 | assert.equal( result, 'not foobar
' ); 387 | }); 388 | }); 389 | -------------------------------------------------------------------------------- /test/Reflux.Store.spec.js: -------------------------------------------------------------------------------- 1 | 2 | var chai = require('chai'), 3 | assert = chai.assert, 4 | Reflux = require('../src'); 5 | 6 | chai.use(require('chai-as-promised')); 7 | 8 | var React = require('react'); 9 | var ReactDOMServer = require('react-dom/server'); 10 | 11 | Reflux.defineReact(React); 12 | 13 | 14 | describe('Creating ES6 style stores', function() 15 | { 16 | it('should allow defining of React with Reflux.defineReact without error', function() 17 | { 18 | Reflux.defineReact(React); 19 | 20 | return true; 21 | }); 22 | 23 | it('should construct with a default empty state object', function() 24 | { 25 | var MyStore = (function (_super) { 26 | Reflux.utils.inherits(Store, _super); 27 | function Store() { 28 | _super.call(this); 29 | } 30 | return Store; 31 | }(Reflux.Store)); 32 | 33 | var store = new MyStore(); 34 | 35 | assert.equal( typeof store.state, 'object' ); 36 | }); 37 | 38 | it('should add listenables and accept actions', function() 39 | { 40 | var Actions = Reflux.createActions(['up', 'down', 'finish']); 41 | 42 | var MyStore = (function (_super) { 43 | Reflux.utils.inherits(Store, _super); 44 | function Store() { 45 | _super.call(this); 46 | this.listenTo(Actions.up, function(){ this.state.count++; }); 47 | this.listenTo(Actions.down, function(){ this.state.count--; }); 48 | this.state = {count:0}; 49 | } 50 | return Store; 51 | }(Reflux.Store)); 52 | 53 | var store = new MyStore(); 54 | 55 | Actions.up(); 56 | Actions.up(); 57 | Actions.up(); 58 | Actions.down(); 59 | Actions.finish(); 60 | 61 | return assert.equal(store.state.count, 2); 62 | }); 63 | 64 | it('should accept listenables with \'action\' and \'onAction\' callbacks', function() 65 | { 66 | var Actions = Reflux.createActions(['up', 'down', 'finish']); 67 | 68 | var MyStore = (function (_super) { 69 | Reflux.utils.inherits(Store, _super); 70 | function Store() { 71 | _super.call(this); 72 | this.listenables = Actions; 73 | this.state = {count:0}; 74 | } 75 | Store.prototype.up = function(){ this.state.count++; }; 76 | Store.prototype.onDown = function(){ this.state.count--; }; 77 | return Store; 78 | }(Reflux.Store)); 79 | 80 | var store = new MyStore(); 81 | 82 | Actions.up(); 83 | Actions.up(); 84 | Actions.up(); 85 | Actions.down(); 86 | Actions.finish(); 87 | 88 | return assert.equal(store.state.count, 2); 89 | }); 90 | 91 | it('should be able to modify state with setState', function() 92 | { 93 | var Actions = Reflux.createActions(['up', 'down', 'finish']); 94 | 95 | var MyStore = (function (_super) { 96 | Reflux.utils.inherits(Store, _super); 97 | function Store() { 98 | _super.call(this); 99 | this.listenables = Actions; 100 | this.state = {count:0}; 101 | } 102 | Store.prototype.up = function(){ this.setState({count:this.state.count+1}); }; 103 | Store.prototype.onDown = function(){ this.setState({count:this.state.count-1}); }; 104 | return Store; 105 | }(Reflux.Store)); 106 | 107 | var store = new MyStore(); 108 | 109 | Actions.up(); 110 | Actions.up(); 111 | Actions.up(); 112 | Actions.down(); 113 | Actions.finish(); 114 | 115 | return assert.equal(store.state.count, 2); 116 | }); 117 | 118 | it('should accept actions with arguments', function() 119 | { 120 | var Actions = Reflux.createActions(['up', 'down', 'finish']); 121 | 122 | var MyStore = (function (_super) { 123 | Reflux.utils.inherits(Store, _super); 124 | function Store() { 125 | _super.call(this); 126 | this.listenables = Actions; 127 | this.state = {count:0}; 128 | } 129 | Store.prototype.up = function(){ this.state.count++; }; 130 | Store.prototype.onDown = function(by){ this.state.count -= by; }; 131 | return Store; 132 | }(Reflux.Store)); 133 | 134 | var store = new MyStore(); 135 | 136 | Actions.up(); 137 | Actions.up(); 138 | Actions.up(); 139 | Actions.down(2); 140 | Actions.finish(); 141 | 142 | return assert.equal(store.state.count, 1); 143 | }); 144 | 145 | it('should mix state in with a Reflux.Component instance', function() 146 | { 147 | var MyStore = (function (_super) { 148 | Reflux.utils.inherits(Store, _super); 149 | function Store() { 150 | _super.call(this); 151 | this.state = {foo:'bar'}; 152 | } 153 | return Store; 154 | }(Reflux.Store)); 155 | 156 | var MyComponent = (function (_super) { 157 | Reflux.utils.inherits(Component, _super); 158 | function Component(props) { 159 | _super.call(this, props); 160 | this.state = {}; 161 | this.store = MyStore; 162 | } 163 | Component.prototype.render = function () { 164 | return React.createElement("p", null, this.state.foo); 165 | }; 166 | return Component; 167 | }(Reflux.Component)); 168 | 169 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent, null) ); 170 | 171 | assert.equal( result, 'bar
' ); 172 | }); 173 | 174 | it('should use singleton instance when applied to multiple components', function() 175 | { 176 | var MyStore = (function (_super) { 177 | Reflux.utils.inherits(Store, _super); 178 | function Store() { 179 | _super.call(this); 180 | this.state = {foo:'bar'}; 181 | MyStore.instanceCount++; 182 | } 183 | return Store; 184 | }(Reflux.Store)); 185 | 186 | MyStore.instanceCount = 0; 187 | 188 | var MyComponent1 = (function (_super) { 189 | Reflux.utils.inherits(Component1, _super); 190 | function Component1(props) { 191 | _super.call(this, props); 192 | this.state = {}; 193 | this.store = MyStore; 194 | } 195 | Component1.prototype.render = function () { 196 | return React.createElement("p", null, this.state.foo); 197 | }; 198 | return Component1; 199 | }(Reflux.Component)); 200 | 201 | var MyComponent2 = (function (_super) { 202 | Reflux.utils.inherits(Component2, _super); 203 | function Component2(props) { 204 | _super.call(this, props); 205 | this.state = {}; 206 | this.store = MyStore; 207 | } 208 | Component2.prototype.render = function () { 209 | return React.createElement("p", null, this.state.foo); 210 | }; 211 | return Component2; 212 | }(Reflux.Component)); 213 | 214 | var result = ReactDOMServer.renderToStaticMarkup( React.createElement('div', null, React.createElement(MyComponent1, null), React.createElement(MyComponent2, null)) ); 215 | 216 | assert.equal( result, 'bar
bar
bar
' ); 249 | }); 250 | 251 | it('should mix state separately with two Reflux.Component instances via mapStoreToState', function() 252 | { 253 | var MyStore = (function (_super) { 254 | Reflux.utils.inherits(Store, _super); 255 | function Store() { 256 | _super.call(this); 257 | this.state = {foo:'bar', bar:'foo'}; 258 | } 259 | return Store; 260 | }(Reflux.Store)); 261 | 262 | var MyComponent1 = (function (_super) { 263 | Reflux.utils.inherits(Component, _super); 264 | function Component(props) { 265 | _super.call(this, props); 266 | this.state = {foo:'?'}; 267 | this.mapStoreToState(MyStore, function(fromStore){ 268 | return {bar:fromStore.bar}; 269 | }); 270 | } 271 | Component.prototype.render = function () { 272 | return React.createElement("p", null, this.state.bar+this.state.foo); 273 | }; 274 | return Component; 275 | }(Reflux.Component)); 276 | 277 | var MyComponent2 = (function (_super) { 278 | Reflux.utils.inherits(Component, _super); 279 | function Component(props) { 280 | _super.call(this, props); 281 | this.state = {bar:'?'}; 282 | this.mapStoreToState(MyStore, function(fromStore){ 283 | return {foo:fromStore.foo}; 284 | }); 285 | } 286 | Component.prototype.render = function () { 287 | return React.createElement("p", null, this.state.foo+this.state.bar); 288 | }; 289 | return Component; 290 | }(Reflux.Component)); 291 | 292 | var result1 = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent1, null) ); 293 | var result2 = ReactDOMServer.renderToStaticMarkup( React.createElement(MyComponent2, null) ); 294 | 295 | assert.equal( result1, 'foo?
' ); 296 | assert.equal( result2, 'bar?
' ); 297 | }); 298 | 299 | // this shortcut feature fails in IE9 and IE10, so removing the test for now, until the day that we can drop those 2 browsers 300 | /*it('should allow shortcut access to MyStore.singleton.state via MyStore.state', function() 301 | { 302 | var MyStore = (function (_super) { 303 | Reflux.utils.inherits(Store, _super); 304 | function Store() { 305 | _super.call(this); 306 | this.state = {foo:'bar', bar:'foo'}; 307 | } 308 | return Store; 309 | }(Reflux.Store)); 310 | 311 | Reflux.initStore(MyStore); 312 | 313 | assert.equal( MyStore.state.foo, MyStore.singleton.state.foo ); 314 | assert.equal( MyStore.state.bar, MyStore.singleton.state.bar ); 315 | });*/ 316 | }); 317 | -------------------------------------------------------------------------------- /test/benchmarks/index.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'), 2 | suite = new Benchmark.Suite(), 3 | fs = require('fs'), 4 | path = require('path'); 5 | 6 | // Find all benchmark tests and add them to the suite 7 | fs.readdirSync(__dirname).forEach(function(file) { 8 | if (file === 'index.js') { 9 | return; 10 | } 11 | var filename = path.basename(file, '.js'), 12 | module = require(path.join(__dirname, file)); 13 | 14 | if (module.fn) { 15 | console.log('Added:', file, module.name ? '- ' + module.name : ''); 16 | suite.add(module.name || filename, module.fn); 17 | } 18 | }); 19 | 20 | suite.on('cycle', function(event) { 21 | console.log(String(event.target)); 22 | }) 23 | .run({ 'async': true }); 24 | -------------------------------------------------------------------------------- /test/benchmarks/listeningToActions.js: -------------------------------------------------------------------------------- 1 | var Reflux = require('../../src'), 2 | action = Reflux.createAction(), 3 | i, 4 | noop = function() {}, 5 | NUMBER_OF_LISTENERS = 100; 6 | 7 | // noops are listening to an action emit 8 | for (i = 0; i < NUMBER_OF_LISTENERS; i++) { 9 | action.listen(noop); 10 | } 11 | 12 | exports.name = "Listening to actions with " + NUMBER_OF_LISTENERS + " listeners"; 13 | 14 | exports.fn = function() { 15 | action(Math.random()); 16 | }; 17 | -------------------------------------------------------------------------------- /test/benchmarks/listeningToJoins.js: -------------------------------------------------------------------------------- 1 | var Reflux = require('../../src'), 2 | actions = [], 3 | joinedActions, 4 | i, 5 | noop = function() {}, 6 | NUMBER_OF_LISTENERS = 100, 7 | NUMBER_OF_JOINED_ACTIONS = 10; 8 | 9 | for (i = 0; i < NUMBER_OF_JOINED_ACTIONS; i++) { 10 | actions = Reflux.createAction(); 11 | } 12 | 13 | joinedActions = Reflux.all(actions); 14 | 15 | for (i = 0; i < NUMBER_OF_LISTENERS; i++) { 16 | joinedActions.listen(noop); 17 | } 18 | 19 | exports.name = "Listening to " + NUMBER_OF_JOINED_ACTIONS + " joined actions with " + NUMBER_OF_LISTENERS + " listeners"; 20 | 21 | exports.fn = function() { 22 | for (i = 0; i < actions.length; i++) { 23 | actions[i](Math.random()); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /test/benchmarks/listeningToStores.js: -------------------------------------------------------------------------------- 1 | var Reflux = require('../../src'), 2 | action = Reflux.createAction(), 3 | store, 4 | i, 5 | noop = function() {}, 6 | NUMBER_OF_LISTENERS = 100; 7 | 8 | store = Reflux.createStore({ 9 | init: function() { 10 | this.listenTo(action, this.trigger); 11 | } 12 | }); 13 | 14 | // noops are listening to the store to emit 15 | for (i = 0; i < NUMBER_OF_LISTENERS; i++) { 16 | store.listen(noop); 17 | } 18 | 19 | exports.name = "Listening to stores with " + NUMBER_OF_LISTENERS + " listeners"; 20 | 21 | exports.fn = function() { 22 | action(Math.random()); 23 | }; 24 | -------------------------------------------------------------------------------- /test/listenTo.spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert, 2 | sinon = require('sinon'), 3 | listenTo = require('../src/listenTo'), 4 | Reflux = require('../src'), 5 | _ = Reflux.utils; 6 | 7 | describe('the listenTo shorthand',function(){ 8 | 9 | describe("when calling the factory",function(){ 10 | var unsubscriber = sinon.spy(), 11 | initialstate = "DATA", 12 | listenable = { 13 | listen: sinon.stub().returns(unsubscriber), 14 | getInitialState: sinon.stub().returns(initialstate) 15 | }, 16 | initial = sinon.spy(), 17 | callback = "CALLBACK", 18 | result = _.extend({method:callback},listenTo(listenable,"method",initial)); 19 | 20 | it("should return object with componentDidMount and componentWillUnmount methods",function(){ 21 | assert.isFunction(result.componentDidMount); 22 | assert.isFunction(result.componentWillUnmount); 23 | }); 24 | 25 | describe("when calling the added componentDidMount",function(){ 26 | result.componentDidMount(); 27 | 28 | it("should add all methods from ListenerMethods",function(){ 29 | for(var m in Reflux.ListenerMethods){ 30 | assert.equal(result[m],Reflux.ListenerMethods[m]); 31 | } 32 | }); 33 | 34 | it("should add a subscriptions array",function(){ 35 | assert.isArray(result.subscriptions); 36 | }); 37 | 38 | it("should call listen on the listenable correctly (via listenTo)",function(){ 39 | assert.equal(listenable.listen.callCount,1); 40 | assert.deepEqual(listenable.listen.firstCall.args,[callback,result]); 41 | }); 42 | 43 | it("should send listenable initial state to initial cb (via listenTo)",function(){ 44 | assert.equal(listenable.getInitialState.callCount,1); 45 | assert.equal(initial.callCount,1); 46 | assert.equal(initial.firstCall.args[0],initialstate); 47 | }); 48 | }); 49 | 50 | describe("the componentWillUnmount method",function(){ 51 | it("should be the same as ListenerMethods stopListeningToAll",function(){ 52 | assert.equal(assert.equal(result.componentWillUnmount,Reflux.ListenerMethods.stopListeningToAll)); 53 | }); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/listenToMany.spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert, 2 | sinon = require('sinon'), 3 | listenToMany = require('../src/listenToMany'), 4 | Reflux = require('../src'), 5 | _ = Reflux.utils; 6 | 7 | describe('the listenToMany shorthand',function(){ 8 | 9 | describe("when calling the factory",function(){ 10 | var unsubscriber = sinon.spy(), 11 | listenable1 = {listen: sinon.stub().returns(unsubscriber)}, 12 | listenable2 = {listen: sinon.stub().returns(unsubscriber)}, 13 | listenables = { 14 | firstAction: listenable1, 15 | secondAction: listenable2 16 | }, 17 | context = { 18 | onFirstAction: sinon.spy(), 19 | onSecondAction: sinon.spy() 20 | }, 21 | result = _.extend(context,listenToMany(listenables)); 22 | 23 | it("should return object with componentDidMount and componentWillUnmount methods",function(){ 24 | assert.isFunction(result.componentDidMount); 25 | assert.isFunction(result.componentWillUnmount); 26 | }); 27 | 28 | describe("when calling the added componentDidMount",function(){ 29 | result.componentDidMount(); 30 | 31 | it("should add all methods from ListenerMethods",function(){ 32 | for(var m in Reflux.ListenerMethods){ 33 | assert.equal(result[m],Reflux.ListenerMethods[m]); 34 | } 35 | }); 36 | 37 | it("should add to a subscriptions array (via listenToMany)",function(){ 38 | var subs = result.subscriptions; 39 | assert.isArray(subs); 40 | assert.equal(subs[0].listenable,listenable1); 41 | assert.equal(subs[1].listenable,listenable2); 42 | }); 43 | 44 | it("should call listen on the listenables correctly (via listenToMany)",function(){ 45 | assert.equal(listenable1.listen.callCount,1); 46 | assert.deepEqual(listenable1.listen.firstCall.args,[context.onFirstAction,result]); 47 | assert.equal(listenable2.listen.callCount,1); 48 | assert.deepEqual(listenable2.listen.firstCall.args,[context.onSecondAction,result]); 49 | }); 50 | }); 51 | 52 | describe("the componentWillUnmount method",function(){ 53 | 54 | it("should be the same as ListenerMethods stopListeningToAll",function(){ 55 | assert.equal(assert.equal(result.componentWillUnmount,Reflux.ListenerMethods.stopListeningToAll)); 56 | }); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/sauceLaunchers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sl_chrome: { 3 | base: 'SauceLabs', 4 | browserName: 'chrome', 5 | platform: 'Windows 7', 6 | version: '35' 7 | }, 8 | sl_firefox: { 9 | base: 'SauceLabs', 10 | browserName: 'firefox', 11 | version: '30' 12 | }, 13 | /* 14 | sl_ios_safari: { 15 | base: 'SauceLabs', 16 | browserName: 'iphone', 17 | platform: 'OS X 10.9', 18 | version: '7.1' 19 | },*/ 20 | sl_ie_9: { 21 | base: 'SauceLabs', 22 | browserName: 'internet explorer', 23 | platform: 'Windows 7', 24 | version: '9' 25 | }, 26 | sl_ie_10: { 27 | base: 'SauceLabs', 28 | browserName: 'internet explorer', 29 | platform: 'Windows 7', 30 | version: '10' 31 | }, 32 | sl_ie_11: { 33 | base: 'SauceLabs', 34 | browserName: 'internet explorer', 35 | platform: 'Windows 8.1', 36 | version: '11' 37 | }, 38 | sl_opera_12: { 39 | base: 'SauceLabs', 40 | browserName: 'opera', 41 | version: '12' 42 | }, 43 | sl_opera_11: { 44 | base: 'SauceLabs', 45 | browserName: 'opera', 46 | version: '11' 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /test/shims/phantomjs-shims.js: -------------------------------------------------------------------------------- 1 | // From https://github.com/facebook/react/blob/master/src/test/phantomjs-shims.js 2 | (function() { 3 | 4 | var Ap = Array.prototype; 5 | var slice = Ap.slice; 6 | var Fp = Function.prototype; 7 | require('es6-promise').polyfill(); 8 | 9 | if (!Fp.bind) { 10 | // PhantomJS doesn't support Function.prototype.bind natively, so 11 | // polyfill it whenever this module is required. 12 | Fp.bind = function(context) { 13 | var func = this; 14 | var args = slice.call(arguments, 1); 15 | 16 | function bound() { 17 | var invokedAsConstructor = func.prototype && (this instanceof func); 18 | return func.apply( 19 | // Ignore the context parameter when invoking the bound function 20 | // as a constructor. Note that this includes not only constructor 21 | // invocations using the new keyword but also calls to base class 22 | // constructors such as BaseClass.call(this, ...) or super(...). 23 | !invokedAsConstructor && context || this, 24 | args.concat(slice.call(arguments)) 25 | ); 26 | } 27 | 28 | // The bound function must share the .prototype of the unbound 29 | // function so that any object created by one constructor will count 30 | // as an instance of both constructors. 31 | bound.prototype = func.prototype; 32 | 33 | return bound; 34 | }; 35 | } 36 | 37 | })(); 38 | -------------------------------------------------------------------------------- /test/usingConnectFilterMixin.spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert, 2 | sinon = require('sinon'), 3 | connectFilter = require('../src/connectFilter'), 4 | Reflux = require('../src'), 5 | _ = Reflux.utils; 6 | 7 | var dummyFilter = function(value) { return value.slice(0,2); }; 8 | 9 | describe('using the connectFilter(...) mixin',function(){ 10 | 11 | it("should be exposed in Reflux",function(){ 12 | assert.equal(connectFilter, Reflux.connectFilter); 13 | }); 14 | 15 | describe("when calling with action",function() { 16 | var listenable = { 17 | listen: sinon.spy() 18 | }, 19 | context = {setState: sinon.spy()}; 20 | _.extend(context,connectFilter(listenable, "KEY", dummyFilter)); 21 | 22 | it("should pass empty object to state",function(){ 23 | assert.deepEqual({},context.getInitialState()); 24 | }); 25 | }); 26 | 27 | describe("when filter func returns", function() { 28 | var listenable, context, expectedResult; 29 | function withFilterReturning(val) { 30 | var key = "KEY"; 31 | listenable = { 32 | listen: sinon.spy(), 33 | getInitialState: sinon.stub().returns("DOES NOT MATTER") 34 | }; 35 | context = {setState: sinon.spy()}; 36 | expectedResult = {}; 37 | expectedResult[key] = val; 38 | _.extend(context,connectFilter(listenable, key, function() {return val;})); 39 | } 40 | describe("0", function() { 41 | beforeEach(function () { 42 | withFilterReturning(0); 43 | }); 44 | it("should return that value", function() { 45 | assert.deepEqual(expectedResult, context.getInitialState()); 46 | }); 47 | }); 48 | describe("empty string", function() { 49 | beforeEach(function () { 50 | withFilterReturning(""); 51 | }); 52 | it("should return that value", function() { 53 | assert.deepEqual(expectedResult, context.getInitialState()); 54 | }); 55 | }); 56 | describe("false", function() { 57 | beforeEach(function () { 58 | withFilterReturning(false); 59 | }); 60 | it("should return that value", function() { 61 | assert.deepEqual(expectedResult, context.getInitialState()); 62 | }); 63 | }); 64 | describe("undefined", function() { 65 | beforeEach(function () { 66 | withFilterReturning(); 67 | }); 68 | it("should return empty object", function() { 69 | assert.deepEqual({}, context.getInitialState()); 70 | }); 71 | }); 72 | }); 73 | 74 | describe("when calling without key",function(){ 75 | 76 | it("should throw.",function(){ 77 | 78 | var listenable = { 79 | listen: function() {}, 80 | getInitialState: function() {} 81 | }; 82 | 83 | assert.throws(function () { 84 | connectFilter(listenable, dummyFilter); 85 | }, 'Reflux.connectFilter() requires a key.'); 86 | }); 87 | 88 | }); 89 | 90 | describe("when calling with key",function(){ 91 | var initialstate = "DEFAULTDATA", 92 | triggerdata = "TRIGGERDATA", 93 | key = "KEY", 94 | listenable = { 95 | listen: sinon.spy(), 96 | getInitialState: sinon.stub().returns(initialstate) 97 | }, 98 | context = {setState: sinon.spy()}, 99 | result = _.extend(context,connectFilter(listenable,key,dummyFilter)); 100 | 101 | it("should pass initial state to state correctly",function(){ 102 | assert.deepEqual({KEY:initialstate.slice(0,2)},context.getInitialState()); 103 | }); 104 | 105 | result.componentDidMount(); 106 | 107 | it("should call listen on the listenable correctly",function(){ 108 | assert.equal(1,listenable.listen.callCount); 109 | assert.isFunction(listenable.listen.firstCall.args[0]); 110 | assert.equal(context,listenable.listen.firstCall.args[1]); 111 | }); 112 | 113 | it("should send listenable callback which calls setState correctly",function(){ 114 | listenable.listen.firstCall.args[0](triggerdata); 115 | assert.deepEqual([_.object([key],[triggerdata.slice(0,2)])],context.setState.firstCall.args); 116 | }); 117 | }); 118 | describe("when calling with falsy key",function(){ 119 | var triggerdata = "TRIGGERDATA", 120 | key = 0, 121 | listenable = {listen: sinon.spy()}, 122 | context = {setState: sinon.spy()}, 123 | result = _.extend(context,connectFilter(listenable,key,dummyFilter)); 124 | result.componentDidMount(); 125 | it("should send listenable callback which calls setState correctly",function(){ 126 | listenable.listen.firstCall.args[0](triggerdata); 127 | assert.deepEqual([_.object([key],[triggerdata.slice(0,2)])],context.setState.firstCall.args); 128 | }); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /test/usingConnectMixin.spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert, 2 | sinon = require('sinon'), 3 | connect = require('../src/connect'), 4 | Reflux = require('../src'), 5 | _ = Reflux.utils; 6 | 7 | describe('using the connect(...) mixin',function(){ 8 | 9 | it("should be exposed in Reflux",function(){ 10 | assert.equal(connect, Reflux.connect); 11 | }); 12 | 13 | describe("when calling with action",function() { 14 | var listenable = { 15 | listen: sinon.spy() 16 | }, 17 | context = {setState: sinon.spy()}; 18 | _.extend(context,connect(listenable, "KEY")); 19 | 20 | it("should pass empty object to state",function(){ 21 | assert.deepEqual({},context.getInitialState()); 22 | }); 23 | }); 24 | 25 | describe("when calling without key",function(){ 26 | 27 | it("should throw.",function(){ 28 | 29 | var listenable = { 30 | listen: function() {}, 31 | getInitialState: function() {} 32 | }; 33 | 34 | assert.throws(function () { 35 | connect(listenable); 36 | }, 'Reflux.connect() requires a key.'); 37 | }); 38 | 39 | }); 40 | 41 | describe("when calling with key",function(){ 42 | var initialstate = "DEFAULTDATA", 43 | triggerdata = "TRIGGERDATA", 44 | key = "KEY", 45 | listenable = { 46 | listen: sinon.spy(), 47 | getInitialState: sinon.stub().returns(initialstate) 48 | }, 49 | context = {setState: sinon.spy()}, 50 | result = _.extend(context,connect(listenable,key)); 51 | 52 | it("should pass initial state to state correctly",function(){ 53 | assert.deepEqual({KEY:initialstate},context.getInitialState()); 54 | }); 55 | 56 | result.componentDidMount(); 57 | 58 | it("should call listen on the listenable correctly",function(){ 59 | assert.equal(1,listenable.listen.callCount); 60 | assert.isFunction(listenable.listen.firstCall.args[0]); 61 | assert.equal(context,listenable.listen.firstCall.args[1]); 62 | }); 63 | 64 | it("should send listenable callback which calls setState correctly",function(){ 65 | listenable.listen.firstCall.args[0](triggerdata); 66 | assert.deepEqual([_.object([key],[triggerdata])],context.setState.firstCall.args); 67 | }); 68 | }); 69 | describe("when calling with falsy key",function(){ 70 | var triggerdata = "TRIGGERDATA", 71 | key = 0, 72 | listenable = {listen: sinon.spy()}, 73 | context = {setState: sinon.spy()}, 74 | result = _.extend(context,connect(listenable,key)); 75 | result.componentDidMount(); 76 | it("should send listenable callback which calls setState correctly",function(){ 77 | listenable.listen.firstCall.args[0](triggerdata); 78 | assert.deepEqual([_.object([key],[triggerdata])],context.setState.firstCall.args); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/usingListenerMethodsMixin.spec.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'), 2 | assert = chai.assert, 3 | Reflux = require('../src'), 4 | sinon = require('sinon'); 5 | 6 | describe("using the ListenerMethods",function(){ 7 | var ListenerMethods = Reflux.ListenerMethods; 8 | 9 | describe("the listenToMany function",function(){ 10 | var listenables = { foo: "FOO", bar: "BAR", baz: "BAZ", missing: "MISSING", parent: { 11 | children: ['foo', 'bar', 'baz', "notThere"], foo: "FOOChild", bar: "BARChild", baz: "BAZChild" 12 | }}, 13 | context = { 14 | onFoo:"onFoo", 15 | bar:"bar", 16 | onBaz:"onBaz", 17 | onBazDefault:"onBazDefault", 18 | onParent: "onParent", 19 | onParentFoo: "onParentFoo", 20 | parentBar: "parentBar", 21 | onParentBaz: "onParentBaz", 22 | onParentBazDefault: "onParentBazDefault", 23 | listenTo:sinon.spy() 24 | }; 25 | Reflux.ListenerMixin.listenToMany.call(context,listenables); 26 | 27 | it("should call listenTo for all listenables with corresponding callbacks",function(){ 28 | assert.equal(context.listenTo.callCount,7); 29 | assert.deepEqual(context.listenTo.firstCall.args,[listenables.foo,"onFoo","onFoo"]); 30 | assert.deepEqual(context.listenTo.secondCall.args,[listenables.bar,"bar","bar"]); 31 | assert.deepEqual(context.listenTo.thirdCall.args,[listenables.baz,"onBaz","onBazDefault"]); 32 | assert.deepEqual(context.listenTo.getCall(3).args,[listenables.parent,"onParent","onParent"]); 33 | assert.deepEqual(context.listenTo.getCall(4).args,[listenables.parent.foo,"onParentFoo","onParentFoo"]); 34 | assert.deepEqual(context.listenTo.getCall(5).args,[listenables.parent.bar,"parentBar","parentBar"]); 35 | assert.deepEqual(context.listenTo.getCall(6).args,[listenables.parent.baz,"onParentBaz","onParentBazDefault"]); 36 | }); 37 | }); 38 | 39 | describe("the listenTo function",function(){ 40 | var listenTo = ListenerMethods.listenTo; 41 | 42 | it("will throw error if validation of listenable returns text",function(){ 43 | var errormsg = "ERROR! ERROR!", 44 | context = {validateListening: sinon.stub().returns(errormsg)}, 45 | listenable = "LISTENABLE"; 46 | assert.throws(function() { 47 | listenTo.call(context,listenable); 48 | }, errormsg); 49 | assert.equal(context.validateListening.callCount,1); 50 | assert.equal(context.validateListening.firstCall.args[0],listenable); 51 | }); 52 | 53 | describe("when setting a subscription",function(){ 54 | var unsub = sinon.spy(), 55 | listenable = {listen:sinon.stub().returns(unsub)}, 56 | callback = "CALLBACK", 57 | defaultcallback = "DEFCALL", 58 | context = { 59 | validateListening: function(){}, 60 | fetchInitialState: sinon.stub() 61 | }, 62 | subobj = listenTo.call(context,listenable,callback,defaultcallback); 63 | 64 | it("adds the returned subscription object to a new array if none was there",function(){ 65 | assert.equal(subobj,context.subscriptions[0]); 66 | }); 67 | 68 | it("calls the listen method on the listener",function(){ 69 | assert.deepEqual(listenable.listen.firstCall.args,[callback,context]); 70 | }); 71 | 72 | it("tries to get initial state correctly",function(){ 73 | assert.deepEqual(context.fetchInitialState.firstCall.args,[listenable,defaultcallback]); 74 | }); 75 | 76 | describe("the returned subscription object",function(){ 77 | it("contains the listenable and a stop function",function(){ 78 | assert.isFunction(subobj.stop); 79 | assert.equal(subobj.listenable,listenable); 80 | }); 81 | }); 82 | 83 | describe("when setting a second subscription",function(){ 84 | var secondlistenable = {listen:sinon.stub()}, 85 | secondsubobj = listenTo.call(context,secondlistenable,callback); 86 | it("adds the subobj to the existing array",function(){ 87 | assert.equal(context.subscriptions[1],secondsubobj); 88 | }); 89 | }); 90 | 91 | }); 92 | }); 93 | 94 | describe('the fetchInitialState method',function(){ 95 | 96 | describe('when called with method name and publisher with getInitialState method',function(){ 97 | var initialstate = "DEFAULTDATA", 98 | listenable = { 99 | getInitialState: sinon.stub().returns(initialstate) 100 | }, 101 | context = { 102 | defcb: sinon.spy() 103 | }; 104 | ListenerMethods.fetchInitialState.call(context,listenable,"defcb"); 105 | 106 | it("calls getInitialState on the publisher",function(){ 107 | assert.equal(listenable.getInitialState.callCount,1); 108 | }); 109 | 110 | it("passes the returned data to the named method",function(){ 111 | assert.deepEqual(context.defcb.firstCall.args,[initialstate]); 112 | }); 113 | }); 114 | }); 115 | 116 | describe('the hasListener method',function(){ 117 | var action1 = Reflux.createAction(), 118 | action2 = Reflux.createAction(), 119 | action3 = Reflux.createAction(), 120 | action4 = Reflux.createAction(), 121 | store = Reflux.createStore(); 122 | store.listenTo(action1,function(){}); 123 | store.joinLeading(action1,action2,action3,function(){}); 124 | it('should return true if context is listening',function(){ 125 | assert.equal(true,store.hasListener(action1)); 126 | }); 127 | it('should return false if context isn\'t listening',function(){ 128 | assert.equal(false,store.hasListener(action4)); 129 | }); 130 | it('should return true if context is listening to listenable as part of a join',function(){ 131 | assert.equal(true,store.hasListener(action2)); 132 | assert.equal(true,store.hasListener(action3)); 133 | }); 134 | }); 135 | 136 | }); 137 | -------------------------------------------------------------------------------- /test/usingListenerMixin.spec.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'), 2 | assert = chai.assert, 3 | Reflux = require('../src'), 4 | Q = require('q'); 5 | 6 | chai.use(require('chai-as-promised')); 7 | 8 | describe('Managing subscriptions via ListenerMixin', function() { 9 | var component, 10 | action, 11 | promise, 12 | store; 13 | 14 | beforeEach(function() { 15 | // simulate ReactJS component instantiation and mounting 16 | component = Object.create(Reflux.ListenerMixin); 17 | delete component.subscriptions; 18 | 19 | action = Reflux.createAction(); 20 | 21 | promise = Q.Promise(function(resolve) { 22 | component.listenTo(action, function() { 23 | resolve(Array.prototype.slice.call(arguments, 0)); 24 | }); 25 | }); 26 | }); 27 | 28 | it('should get argument given on action', function() { 29 | action('my argument'); 30 | 31 | return assert.eventually.equal(promise, 'my argument'); 32 | }); 33 | 34 | it('should get any arbitrary arguments given on action', function() { 35 | action(1337, 'ninja'); 36 | 37 | return assert.eventually.deepEqual(promise, [1337, 'ninja']); 38 | }); 39 | 40 | describe('using a store and listening to it', function() { 41 | beforeEach(function () { 42 | store = Reflux.createStore({ 43 | init: function() { 44 | this.listenTo(action, this.trigger); 45 | } 46 | }); 47 | 48 | component.listenTo(store, function() {}); 49 | }); 50 | 51 | it('should be possible to listen to the store using two different components', function() { 52 | var component2 = Object.create(Reflux.ListenerMixin); 53 | component2.listenTo(store, function() {}); 54 | }); 55 | }); 56 | 57 | describe('get initial state', function () { 58 | beforeEach(function() { 59 | component.componentWillUnmount(); 60 | }); 61 | 62 | function mountComponent() { 63 | delete component.subscriptions; 64 | promise = Q.Promise(function(resolve) { 65 | var setData = function () { 66 | resolve(Array.prototype.slice.call(arguments, 0)); 67 | }; 68 | component.listenTo(store, setData, setData); 69 | }); 70 | } 71 | 72 | it('should get initial state from getInitialState()', function () { 73 | store = Reflux.createStore({ 74 | getInitialState: function () { 75 | return 'initial state'; 76 | } 77 | }); 78 | mountComponent(); 79 | return assert.eventually.equal(promise, 'initial state'); 80 | }); 81 | 82 | it('should get initial state from getInitialState() returned promise', function () { 83 | store = Reflux.createStore({ 84 | getInitialState: function () { 85 | return Q.Promise(function (resolve) { 86 | setTimeout(function () { 87 | resolve('initial state'); 88 | }, 20); 89 | }); 90 | } 91 | }); 92 | mountComponent(); 93 | return assert.eventually.equal(promise, 'initial state'); 94 | }); 95 | }); 96 | 97 | it("should include ListenerMethods",function(){ 98 | var s = Reflux.createStore({}); 99 | for(var m in Reflux.ListenerMethods){ 100 | assert.equal(s[m],Reflux.ListenerMethods[m]); 101 | } 102 | }); 103 | 104 | it("should use ListenerMethods.stopListeningToAll as componentWillUnmount",function(){ 105 | assert.equal(Reflux.ListenerMixin.componentWillUnmount,Reflux.ListenerMethods.stopListeningToAll); 106 | }); 107 | 108 | it("should not mix in its own methods into ListenerMethods",function(){ 109 | assert.isUndefined(Reflux.ListenerMethods.componentWillUnmount); 110 | }); 111 | 112 | }); 113 | --------------------------------------------------------------------------------