209 | );
210 | }
211 | });
212 |
213 | React.render(, document.getElementById("app"));
214 |
--------------------------------------------------------------------------------
/examples/async/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/async/start.sh:
--------------------------------------------------------------------------------
1 | ../../node_modules/.bin/webpack-dev-server --port 8089 --no-info --content-base app
2 |
--------------------------------------------------------------------------------
/examples/async/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack");
2 |
3 | module.exports = {
4 | cache: true,
5 | entry: "./app/app.jsx",
6 | output: {
7 | path: __dirname + "/app",
8 | filename: "bundle.js"
9 | },
10 | devtool: "source-map",
11 | module: {
12 | loaders: [
13 | { test: /\.less$/, loader: "style!css!less" },
14 | { test: /\.jsx$/, loader: "jsx-loader" },
15 | { test: /\.json$/, loader: "json" }
16 | ]
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/examples/carousel/README.md:
--------------------------------------------------------------------------------
1 | Carousel Example
2 | ================
3 |
4 | This is a small example React application using Fluxxor.
5 |
6 | 
7 |
8 | To run, simply open `app/index.html` in your web browser—`index.html`, `bundle.js`, and the `images/` directory are the only files required to run the application (`bundle.js` is build with [Webpack](http://webpack.github.io/)). If you want to modify the source and see your updates, run `./start.sh` and point your browser to `http://localhost:8089/index.html` (you will need to install the `devDependencies` from the root of the Fluxxor project first).
9 |
10 | Alternatively, check out the running example [on the Fluxxor website](http://fluxxor.com/examples/carousel/).
11 |
12 | The entry point of the application is `app/app.jsx`. The `Fluxxor.Flux` instance is exported to `window.flux` so that you can manipulate the application from the standard JavaScript console (e.g. the methods on `window.flux.actions`).
13 |
14 | Stores
15 | ------
16 |
17 | The system contains two stores:
18 |
19 | * `ImageStore` - Contains the list of images to display in the carousel
20 | * `CarouselStore` - Contains information about the carousel position (which image is being displayed, etc.) and ensures that the user cannot change images while a transition is in progress
21 |
22 | This separation is perhaps a bit contrived, but shows how you might use multiple stores in a Flux-based React application.
23 |
24 | Actions
25 | -------
26 |
27 | There are four actions that manipulate the stores:
28 |
29 | * `prevImage` - Moves the carousel to the previous image (modifies `CarouselStore`)
30 | * `nextImage` - Moves the carousel to the next image (modifies `CarouselStore`)
31 | * `selectImage` - Moves the carousel to a specific image (modifies `CarouselStore`)
32 | * `addImage` - Adds an image to the carousel and moves to that image (modifies `ImageStore` and `CarouselStore`)
33 |
34 | `addImage` is particularly interesting because the action handler inside `CarouselStore` utilizes the `waitFor` mechanism to allow the `ImageStore` to accept or reject adding the image (based on its file extension and whether the image is already in the carousel).
35 |
36 | Components
37 | ----------
38 |
39 | * `Application` - The top-level component that maintains state from the stores using `Fluxxor.StoreWatchMixin`
40 | * `Carousel` - The actual image carousel itself; all data is passed as props, and user interaction is propagated back to `Application`
41 | * `ImageForm` - A small form for adding images to the carousel; user interaction is propagated back to `Application`
42 |
43 | Note that `ImageForm` has state outside of the data from the stores—however, it's localized to the form, and isn't part of the larger "application state." When the form is submitted, this local state is sent to the parent component through a property, where it is then sent to the dispatcher via the `addImage` action.
44 |
45 | ---
46 |
47 | In this small application, only the top level `Application` component requires access to the Flux data. However, since `Application` mixes in `Fluxxor.FluxMixin`, any descendants of `Application` with the `Fluxxor.FluxChildMixin` would automatically have access to the `Fluxxor.Flux` instance via `this.getFlux()`.
48 |
--------------------------------------------------------------------------------
/examples/carousel/app/actions.js:
--------------------------------------------------------------------------------
1 | var Constants = require("./constants");
2 |
3 | module.exports = {
4 | nextImage: function() {
5 | this.dispatch(Constants.NEXT_IMAGE);
6 | },
7 |
8 | prevImage: function() {
9 | this.dispatch(Constants.PREV_IMAGE);
10 | },
11 |
12 | selectImage: function(i) {
13 | this.dispatch(Constants.SEL_IMAGE, {index: i});
14 | },
15 |
16 | addImage: function(url) {
17 | this.dispatch(Constants.ADD_IMAGE, {url: url});
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/examples/carousel/app/app.jsx:
--------------------------------------------------------------------------------
1 | var React = require("react"),
2 | Fluxxor = require("../../../");
3 |
4 | var Application = require("./components/application.jsx"),
5 | ImageStore = require("./stores/image_store"),
6 | CarouselStore = require("./stores/carousel_store"),
7 | actions = require("./actions");
8 |
9 | require("./style.less");
10 |
11 | var images = ["images/bttf1.png", "images/bttf2.png", "images/bttf3.png",
12 | "images/bttf4.png", "images/bttf5.png", "images/bttf6.png"];
13 |
14 | var stores = {
15 | CarouselStore: new CarouselStore({count: images.length}),
16 | ImageStore: new ImageStore({images: images})
17 | };
18 |
19 | var flux = new Fluxxor.Flux(stores, actions);
20 | window.flux = flux;
21 |
22 | flux.on("dispatch", function(type, payload) {
23 | if (console && console.log) {
24 | console.log("[Dispatch]", type, payload);
25 | }
26 | });
27 |
28 | React.render(, document.getElementById("app"));
29 |
--------------------------------------------------------------------------------
/examples/carousel/app/components/application.jsx:
--------------------------------------------------------------------------------
1 | var React = require("react"),
2 | Fluxxor = require("../../../../"),
3 | FluxMixin = Fluxxor.FluxMixin(React),
4 | StoreWatchMixin = Fluxxor.StoreWatchMixin;
5 |
6 | var Carousel = require("./carousel.jsx"),
7 | ImageForm = require("./image_form.jsx");
8 |
9 | var Application = React.createClass({
10 | mixins: [FluxMixin, StoreWatchMixin("ImageStore", "CarouselStore")],
11 |
12 | // Required by StoreWatchMixin
13 | getStateFromFlux: function() {
14 | var flux = this.getFlux();
15 | return {
16 | images: flux.store("ImageStore").getState(),
17 | carousel: flux.store("CarouselStore").getState()
18 | };
19 | },
20 |
21 | render: function() {
22 | return (
23 |
24 |
29 |
30 |
31 | );
32 | },
33 |
34 | onClickLeft: function() {
35 | this.getFlux().actions.prevImage();
36 | },
37 |
38 | onClickRight: function() {
39 | this.getFlux().actions.nextImage();
40 | },
41 |
42 | onSelectImage: function(i) {
43 | this.getFlux().actions.selectImage(i);
44 | },
45 |
46 | onAddUrl: function(url) {
47 | this.getFlux().actions.addImage(url);
48 | }
49 | });
50 |
51 | module.exports = Application;
52 |
--------------------------------------------------------------------------------
/examples/carousel/app/components/carousel.jsx:
--------------------------------------------------------------------------------
1 | var React = require("react");
2 |
3 | var Carousel = React.createClass({
4 | propTypes: {
5 | images: React.PropTypes.array.isRequired,
6 | selected: React.PropTypes.number.isRequired,
7 | onClickLeft: React.PropTypes.func.isRequired,
8 | onClickRight: React.PropTypes.func.isRequired,
9 | onSelectImage: React.PropTypes.func.isRequired
10 | },
11 |
12 | render: function() {
13 | var left = this.props.selected * 300 * -1,
14 | ulStyle = {
15 | width: this.props.images.length * 300,
16 | "-ms-transform": "translate(" + left + "px,0px)",
17 | "-webkit-transform": "translate(" + left + "px,0px)",
18 | transform: "translate(" + left + "px,0px)"
19 | };
20 |
21 | return (
22 |
132 | );
133 | },
134 |
135 | handleTodoTextChange: function(e) {
136 | this.setState({newTodoText: e.target.value});
137 | },
138 |
139 | onSubmitForm: function(e) {
140 | e.preventDefault();
141 | if (this.state.newTodoText.trim()) {
142 | this.getFlux().actions.addTodo(this.state.newTodoText);
143 | this.setState({newTodoText: ""});
144 | }
145 | },
146 |
147 | clearCompletedTodos: function(e) {
148 | this.getFlux().actions.clearTodos();
149 | }
150 | });
151 |
152 | var TodoItem = React.createClass({
153 | mixins: [FluxMixin],
154 |
155 | propTypes: {
156 | todo: React.PropTypes.object.isRequired
157 | },
158 |
159 | render: function() {
160 | var style = {
161 | textDecoration: this.props.todo.complete ? "line-through" : ""
162 | };
163 |
164 | return {this.props.todo.text};
165 | },
166 |
167 | onClick: function() {
168 | this.getFlux().actions.toggleTodo(this.props.todo.id);
169 | }
170 | });
171 |
172 | React.render(, document.getElementById("app"));
173 |
--------------------------------------------------------------------------------
/examples/todo-basic/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/todo-basic/start.sh:
--------------------------------------------------------------------------------
1 | ../../node_modules/.bin/webpack-dev-server --port 8089 --no-info --content-base app
2 |
--------------------------------------------------------------------------------
/examples/todo-basic/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack");
2 |
3 | module.exports = {
4 | cache: true,
5 | entry: "./app/app.jsx",
6 | output: {
7 | path: __dirname + "/app",
8 | filename: "bundle.js"
9 | },
10 | devtool: "source-map",
11 | module: {
12 | loaders: [
13 | { test: /\.less$/, loader: "style!css!less" },
14 | { test: /\.jsx$/, loader: "jsx-loader" },
15 | { test: /\.json$/, loader: "json" }
16 | ]
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/fluxxor.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
98 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var Dispatcher = require("./lib/dispatcher"),
2 | Flux = require("./lib/flux"),
3 | FluxMixin = require("./lib/flux_mixin"),
4 | FluxChildMixin = require("./lib/flux_child_mixin"),
5 | StoreWatchMixin = require("./lib/store_watch_mixin"),
6 | createStore = require("./lib/create_store");
7 |
8 | var Fluxxor = {
9 | Dispatcher: Dispatcher,
10 | Flux: Flux,
11 | FluxMixin: FluxMixin,
12 | FluxChildMixin: FluxChildMixin,
13 | StoreWatchMixin: StoreWatchMixin,
14 | createStore: createStore,
15 | version: require("./version")
16 | };
17 |
18 | module.exports = Fluxxor;
19 |
--------------------------------------------------------------------------------
/lib/create_store.js:
--------------------------------------------------------------------------------
1 | var _each = require("lodash/collection/forEach"),
2 | _isFunction = require("lodash/lang/isFunction"),
3 | Store = require("./store"),
4 | inherits = require("./util/inherits");
5 |
6 | var RESERVED_KEYS = ["flux", "waitFor"];
7 |
8 | var createStore = function(spec) {
9 | _each(RESERVED_KEYS, function(key) {
10 | if (spec[key]) {
11 | throw new Error("Reserved key '" + key + "' found in store definition");
12 | }
13 | });
14 |
15 | var constructor = function(options) {
16 | options = options || {};
17 | Store.call(this);
18 |
19 | for (var key in spec) {
20 | if (key === "actions") {
21 | this.bindActions(spec[key]);
22 | } else if (key === "initialize") {
23 | // do nothing
24 | } else if (_isFunction(spec[key])) {
25 | this[key] = spec[key].bind(this);
26 | } else {
27 | this[key] = spec[key];
28 | }
29 | }
30 |
31 | if (spec.initialize) {
32 | spec.initialize.call(this, options);
33 | }
34 | };
35 |
36 | inherits(constructor, Store);
37 | return constructor;
38 | };
39 |
40 | module.exports = createStore;
41 |
--------------------------------------------------------------------------------
/lib/dispatcher.js:
--------------------------------------------------------------------------------
1 | var _clone = require("lodash/lang/clone"),
2 | _mapValues = require("lodash/object/mapValues"),
3 | _forOwn = require("lodash/object/forOwn"),
4 | _intersection = require("lodash/array/intersection"),
5 | _keys = require("lodash/object/keys"),
6 | _map = require("lodash/collection/map"),
7 | _each = require("lodash/collection/forEach"),
8 | _size = require("lodash/collection/size"),
9 | _findKey = require("lodash/object/findKey"),
10 | _uniq = require("lodash/array/uniq");
11 |
12 | var defaultDispatchInterceptor = function(action, dispatch) {
13 | dispatch(action);
14 | };
15 |
16 | var Dispatcher = function(stores) {
17 | this.stores = {};
18 | this.currentDispatch = null;
19 | this.currentActionType = null;
20 | this.waitingToDispatch = [];
21 | this.dispatchInterceptor = defaultDispatchInterceptor;
22 | this._boundDispatch = this._dispatch.bind(this);
23 |
24 | for (var key in stores) {
25 | if (stores.hasOwnProperty(key)) {
26 | this.addStore(key, stores[key]);
27 | }
28 | }
29 | };
30 |
31 | Dispatcher.prototype.addStore = function(name, store) {
32 | store.dispatcher = this;
33 | this.stores[name] = store;
34 | };
35 |
36 | Dispatcher.prototype.dispatch = function(action) {
37 | this.dispatchInterceptor(action, this._boundDispatch);
38 | };
39 |
40 | Dispatcher.prototype._dispatch = function(action) {
41 | if (!action || !action.type) {
42 | throw new Error("Can only dispatch actions with a 'type' property");
43 | }
44 |
45 | if (this.currentDispatch) {
46 | var complaint = "Cannot dispatch an action ('" + action.type + "') while another action ('" +
47 | this.currentActionType + "') is being dispatched";
48 | throw new Error(complaint);
49 | }
50 |
51 | this.waitingToDispatch = _clone(this.stores);
52 |
53 | this.currentActionType = action.type;
54 | this.currentDispatch = _mapValues(this.stores, function() {
55 | return { resolved: false, waitingOn: [], waitCallback: null };
56 | });
57 |
58 | try {
59 | this.doDispatchLoop(action);
60 | } finally {
61 | this.currentActionType = null;
62 | this.currentDispatch = null;
63 | }
64 | };
65 |
66 | Dispatcher.prototype.doDispatchLoop = function(action) {
67 | var dispatch, canBeDispatchedTo, wasHandled = false,
68 | removeFromDispatchQueue = [], dispatchedThisLoop = [];
69 |
70 | _forOwn(this.waitingToDispatch, function(value, key) {
71 | dispatch = this.currentDispatch[key];
72 | canBeDispatchedTo = !dispatch.waitingOn.length ||
73 | !_intersection(dispatch.waitingOn, _keys(this.waitingToDispatch)).length;
74 | if (canBeDispatchedTo) {
75 | if (dispatch.waitCallback) {
76 | var stores = _map(dispatch.waitingOn, function(key) {
77 | return this.stores[key];
78 | }, this);
79 | var fn = dispatch.waitCallback;
80 | dispatch.waitCallback = null;
81 | dispatch.waitingOn = [];
82 | dispatch.resolved = true;
83 | fn.apply(null, stores);
84 | wasHandled = true;
85 | } else {
86 | dispatch.resolved = true;
87 | var handled = this.stores[key].__handleAction__(action);
88 | if (handled) {
89 | wasHandled = true;
90 | }
91 | }
92 |
93 | dispatchedThisLoop.push(key);
94 |
95 | if (this.currentDispatch[key].resolved) {
96 | removeFromDispatchQueue.push(key);
97 | }
98 | }
99 | }, this);
100 |
101 | if (_keys(this.waitingToDispatch).length && !dispatchedThisLoop.length) {
102 | var storesWithCircularWaits = _keys(this.waitingToDispatch).join(", ");
103 | throw new Error("Indirect circular wait detected among: " + storesWithCircularWaits);
104 | }
105 |
106 | _each(removeFromDispatchQueue, function(key) {
107 | delete this.waitingToDispatch[key];
108 | }, this);
109 |
110 | if (_size(this.waitingToDispatch)) {
111 | this.doDispatchLoop(action);
112 | }
113 |
114 | if (!wasHandled && console && console.warn) {
115 | console.warn("An action of type " + action.type + " was dispatched, but no store handled it");
116 | }
117 |
118 | };
119 |
120 | Dispatcher.prototype.waitForStores = function(store, stores, fn) {
121 | if (!this.currentDispatch) {
122 | throw new Error("Cannot wait unless an action is being dispatched");
123 | }
124 |
125 | var waitingStoreName = _findKey(this.stores, function(val) {
126 | return val === store;
127 | });
128 |
129 | if (stores.indexOf(waitingStoreName) > -1) {
130 | throw new Error("A store cannot wait on itself");
131 | }
132 |
133 | var dispatch = this.currentDispatch[waitingStoreName];
134 |
135 | if (dispatch.waitingOn.length) {
136 | throw new Error(waitingStoreName + " already waiting on stores");
137 | }
138 |
139 | _each(stores, function(storeName) {
140 | var storeDispatch = this.currentDispatch[storeName];
141 | if (!this.stores[storeName]) {
142 | throw new Error("Cannot wait for non-existent store " + storeName);
143 | }
144 | if (storeDispatch.waitingOn.indexOf(waitingStoreName) > -1) {
145 | throw new Error("Circular wait detected between " + waitingStoreName + " and " + storeName);
146 | }
147 | }, this);
148 |
149 | dispatch.resolved = false;
150 | dispatch.waitingOn = _uniq(dispatch.waitingOn.concat(stores));
151 | dispatch.waitCallback = fn;
152 | };
153 |
154 | Dispatcher.prototype.setDispatchInterceptor = function(fn) {
155 | if (fn) {
156 | this.dispatchInterceptor = fn;
157 | } else {
158 | this.dispatchInterceptor = defaultDispatchInterceptor;
159 | }
160 | };
161 |
162 | module.exports = Dispatcher;
163 |
--------------------------------------------------------------------------------
/lib/flux.js:
--------------------------------------------------------------------------------
1 | var EventEmitter = require("eventemitter3"),
2 | inherits = require("./util/inherits"),
3 | objectPath = require("object-path"),
4 | _each = require("lodash/collection/forEach"),
5 | _reduce = require("lodash/collection/reduce"),
6 | _isFunction = require("lodash/lang/isFunction"),
7 | _isString = require("lodash/lang/isString");
8 |
9 | var Dispatcher = require("./dispatcher");
10 |
11 | var findLeaves = function(obj, path, callback) {
12 | path = path || [];
13 |
14 | for (var key in obj) {
15 | if (obj.hasOwnProperty(key)) {
16 | if (_isFunction(obj[key])) {
17 | callback(path.concat(key), obj[key]);
18 | } else {
19 | findLeaves(obj[key], path.concat(key), callback);
20 | }
21 | }
22 | }
23 | };
24 |
25 | var Flux = function(stores, actions) {
26 | EventEmitter.call(this);
27 | this.dispatcher = new Dispatcher(stores);
28 | this.actions = {};
29 | this.stores = {};
30 |
31 | var dispatcher = this.dispatcher;
32 | var flux = this;
33 | this.dispatchBinder = {
34 | flux: flux,
35 | dispatch: function(type, payload) {
36 | try {
37 | flux.emit("dispatch", type, payload);
38 | } finally {
39 | dispatcher.dispatch({type: type, payload: payload});
40 | }
41 | }
42 | };
43 |
44 | this.addActions(actions);
45 | this.addStores(stores);
46 | };
47 |
48 | inherits(Flux, EventEmitter);
49 |
50 | Flux.prototype.addActions = function(actions) {
51 | findLeaves(actions, [], this.addAction.bind(this));
52 | };
53 |
54 | // addAction has two signatures:
55 | // 1: string[, string, string, string...], actionFunction
56 | // 2: arrayOfStrings, actionFunction
57 | Flux.prototype.addAction = function() {
58 | if (arguments.length < 2) {
59 | throw new Error("addAction requires at least two arguments, a string (or array of strings) and a function");
60 | }
61 |
62 | var args = Array.prototype.slice.call(arguments);
63 |
64 | if (!_isFunction(args[args.length - 1])) {
65 | throw new Error("The last argument to addAction must be a function");
66 | }
67 |
68 | var func = args.pop().bind(this.dispatchBinder);
69 |
70 | if (!_isString(args[0])) {
71 | args = args[0];
72 | }
73 |
74 | var leadingPaths = _reduce(args, function(acc, next) {
75 | if (acc) {
76 | var nextPath = acc[acc.length - 1].concat([next]);
77 | return acc.concat([nextPath]);
78 | } else {
79 | return [[next]];
80 | }
81 | }, null);
82 |
83 | // Detect trying to replace a function at any point in the path
84 | _each(leadingPaths, function(path) {
85 | if (_isFunction(objectPath.get(this.actions, path))) {
86 | throw new Error("An action named " + args.join(".") + " already exists");
87 | }
88 | }, this);
89 |
90 | // Detect trying to replace a namespace at the final point in the path
91 | if (objectPath.get(this.actions, args)) {
92 | throw new Error("A namespace named " + args.join(".") + " already exists");
93 | }
94 |
95 | objectPath.set(this.actions, args, func, true);
96 | };
97 |
98 | Flux.prototype.store = function(name) {
99 | return this.stores[name];
100 | };
101 |
102 | Flux.prototype.getAllStores = function() {
103 | return this.stores;
104 | };
105 |
106 | Flux.prototype.addStore = function(name, store) {
107 | if (name in this.stores) {
108 | throw new Error("A store named '" + name + "' already exists");
109 | }
110 | store.flux = this;
111 | this.stores[name] = store;
112 | this.dispatcher.addStore(name, store);
113 | };
114 |
115 | Flux.prototype.addStores = function(stores) {
116 | for (var key in stores) {
117 | if (stores.hasOwnProperty(key)) {
118 | this.addStore(key, stores[key]);
119 | }
120 | }
121 | };
122 |
123 | Flux.prototype.setDispatchInterceptor = function(fn) {
124 | this.dispatcher.setDispatchInterceptor(fn);
125 | };
126 |
127 | module.exports = Flux;
128 |
--------------------------------------------------------------------------------
/lib/flux_child_mixin.js:
--------------------------------------------------------------------------------
1 | var FluxChildMixin = function(React) {
2 | return {
3 | componentWillMount: function() {
4 | if (console && console.warn) {
5 | var namePart = this.constructor.displayName ? " in " + this.constructor.displayName : "",
6 | message = "Fluxxor.FluxChildMixin was found in use" + namePart + ", " +
7 | "but has been deprecated. Use Fluxxor.FluxMixin instead.";
8 | console.warn(message);
9 | }
10 | },
11 |
12 | contextTypes: {
13 | flux: React.PropTypes.object
14 | },
15 |
16 | getFlux: function() {
17 | return this.context.flux;
18 | }
19 | };
20 | };
21 |
22 | FluxChildMixin.componentWillMount = function() {
23 | throw new Error("Fluxxor.FluxChildMixin is a function that takes React as a " +
24 | "parameter and returns the mixin, e.g.: mixins[Fluxxor.FluxChildMixin(React)]");
25 | };
26 |
27 | module.exports = FluxChildMixin;
28 |
--------------------------------------------------------------------------------
/lib/flux_mixin.js:
--------------------------------------------------------------------------------
1 | var FluxMixin = function(React) {
2 | return {
3 | componentWillMount: function() {
4 | if (!this.props.flux && (!this.context || !this.context.flux)) {
5 | var namePart = this.constructor.displayName ? " of " + this.constructor.displayName : "";
6 | throw new Error("Could not find flux on this.props or this.context" + namePart);
7 | }
8 | },
9 |
10 | childContextTypes: {
11 | flux: React.PropTypes.object
12 | },
13 |
14 | contextTypes: {
15 | flux: React.PropTypes.object
16 | },
17 |
18 | getChildContext: function() {
19 | return {
20 | flux: this.getFlux()
21 | };
22 | },
23 |
24 | getFlux: function() {
25 | return this.props.flux || (this.context && this.context.flux);
26 | }
27 | };
28 | };
29 |
30 | FluxMixin.componentWillMount = function() {
31 | throw new Error("Fluxxor.FluxMixin is a function that takes React as a " +
32 | "parameter and returns the mixin, e.g.: mixins: [Fluxxor.FluxMixin(React)]");
33 | };
34 |
35 | module.exports = FluxMixin;
36 |
--------------------------------------------------------------------------------
/lib/store.js:
--------------------------------------------------------------------------------
1 | var EventEmitter = require("eventemitter3"),
2 | inherits = require("./util/inherits"),
3 | _isFunction = require("lodash/lang/isFunction"),
4 | _isObject = require("lodash/lang/isObject");
5 |
6 | function Store(dispatcher) {
7 | this.dispatcher = dispatcher;
8 | this.__actions__ = {};
9 | EventEmitter.call(this);
10 | }
11 |
12 | inherits(Store, EventEmitter);
13 |
14 | Store.prototype.__handleAction__ = function(action) {
15 | var handler;
16 | if (!!(handler = this.__actions__[action.type])) {
17 | if (_isFunction(handler)) {
18 | handler.call(this, action.payload, action.type);
19 | } else if (handler && _isFunction(this[handler])) {
20 | this[handler].call(this, action.payload, action.type);
21 | } else {
22 | throw new Error("The handler for action type " + action.type + " is not a function");
23 | }
24 | return true;
25 | } else {
26 | return false;
27 | }
28 | };
29 |
30 | Store.prototype.bindActions = function() {
31 | var actions = Array.prototype.slice.call(arguments);
32 |
33 | if (actions.length > 1 && actions.length % 2 !== 0) {
34 | throw new Error("bindActions must take an even number of arguments.");
35 | }
36 |
37 | var bindAction = function(type, handler) {
38 | if (!handler) {
39 | throw new Error("The handler for action type " + type + " is falsy");
40 | }
41 |
42 | this.__actions__[type] = handler;
43 | }.bind(this);
44 |
45 | if (actions.length === 1 && _isObject(actions[0])) {
46 | actions = actions[0];
47 | for (var key in actions) {
48 | if (actions.hasOwnProperty(key)) {
49 | bindAction(key, actions[key]);
50 | }
51 | }
52 | } else {
53 | for (var i = 0; i < actions.length; i += 2) {
54 | var type = actions[i],
55 | handler = actions[i+1];
56 |
57 | if (!type) {
58 | throw new Error("Argument " + (i+1) + " to bindActions is a falsy value");
59 | }
60 |
61 | bindAction(type, handler);
62 | }
63 | }
64 | };
65 |
66 | Store.prototype.waitFor = function(stores, fn) {
67 | this.dispatcher.waitForStores(this, stores, fn.bind(this));
68 | };
69 |
70 | module.exports = Store;
71 |
--------------------------------------------------------------------------------
/lib/store_watch_mixin.js:
--------------------------------------------------------------------------------
1 | var _each = require("lodash/collection/forEach");
2 |
3 | var StoreWatchMixin = function() {
4 | var storeNames = Array.prototype.slice.call(arguments);
5 | return {
6 | componentDidMount: function() {
7 | var flux = this.props.flux || this.context.flux;
8 | this.mounted = true;
9 |
10 | // No autobinding in ES6 classes
11 | this._setStateFromFlux = function() {
12 | if(this.mounted) {
13 | this.setState(this.getStateFromFlux());
14 | }
15 | }.bind(this);
16 |
17 | _each(storeNames, function(store) {
18 | flux.store(store).on("change", this._setStateFromFlux);
19 | }, this);
20 | },
21 |
22 | componentWillUnmount: function() {
23 | var flux = this.props.flux || this.context.flux;
24 | this.mounted = false;
25 | _each(storeNames, function(store) {
26 | flux.store(store).removeListener("change", this._setStateFromFlux);
27 | }, this);
28 | },
29 |
30 | getInitialState: function() {
31 | return this.getStateFromFlux();
32 | }
33 | };
34 | };
35 |
36 | StoreWatchMixin.componentWillMount = function() {
37 | throw new Error("Fluxxor.StoreWatchMixin is a function that takes one or more " +
38 | "store names as parameters and returns the mixin, e.g.: " +
39 | "mixins: [Fluxxor.StoreWatchMixin(\"Store1\", \"Store2\")]");
40 | };
41 |
42 | module.exports = StoreWatchMixin;
43 |
--------------------------------------------------------------------------------
/lib/util/inherits.js:
--------------------------------------------------------------------------------
1 | // From https://github.com/isaacs/inherits
2 | // inherits is licensed under the ISC license:
3 | //
4 | //
5 | // The ISC License
6 | //
7 | // Copyright (c) Isaac Z. Schlueter
8 | //
9 | // Permission to use, copy, modify, and/or distribute this software for any
10 | // purpose with or without fee is hereby granted, provided that the above
11 | // copyright notice and this permission notice appear in all copies.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
14 | // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
15 | // FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
16 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
17 | // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
18 | // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19 | // PERFORMANCE OF THIS SOFTWARE.
20 |
21 | if (typeof Object.create === 'function') {
22 | // implementation from standard node.js 'util' module
23 | module.exports = function inherits(ctor, superCtor) {
24 | ctor.super_ = superCtor;
25 | ctor.prototype = Object.create(superCtor.prototype, {
26 | constructor: {
27 | value: ctor,
28 | enumerable: false,
29 | writable: true,
30 | configurable: true
31 | }
32 | });
33 | };
34 | } else {
35 | // old school shim for old browsers
36 | module.exports = function inherits(ctor, superCtor) {
37 | ctor.super_ = superCtor;
38 | var TempCtor = function () {};
39 | TempCtor.prototype = superCtor.prototype;
40 | ctor.prototype = new TempCtor();
41 | ctor.prototype.constructor = ctor;
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fluxxor",
3 | "version": "1.7.3",
4 | "description": "Flux architecture tools for React",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/BinaryMuse/fluxxor.git"
8 | },
9 | "main": "index.js",
10 | "scripts": {
11 | "test": "npm run jshint && mocha --recursive",
12 | "jshint": "jsxhint lib/ test/",
13 | "build": "./script/build-fluxxor && ./script/build-examples",
14 | "preview-site": "wintersmith preview -C site",
15 | "build-site": "wintersmith build -C site"
16 | },
17 | "keywords": [
18 | "react",
19 | "flux"
20 | ],
21 | "author": "Michelle Tilley ",
22 | "license": "MIT",
23 | "devDependencies": {
24 | "chai": "^1.9.1",
25 | "css-loader": "^0.6.12",
26 | "envify": "^1.2.1",
27 | "jsdom": "~3.1.2",
28 | "json-loader": "^0.5.0",
29 | "jsx-loader": "^0.12.0",
30 | "jsxhint": "^0.5.0",
31 | "less": "^1.7.0",
32 | "less-loader": "^0.7.3",
33 | "mocha": "^2.2.1",
34 | "react": "^0.13.0",
35 | "react-router": "^0.13.0",
36 | "sinon": "^1.9.1",
37 | "sinon-chai": "^2.5.0",
38 | "style-loader": "^0.6.3",
39 | "tcomb-form": "^0.4.8",
40 | "webpack": "^1.1.11",
41 | "webpack-dev-server": "^1.2.7",
42 | "wintersmith": "^2.0.10",
43 | "wintersmith-ejs": "^0.1.4",
44 | "wintersmith-less": "^0.2.2"
45 | },
46 | "dependencies": {
47 | "eventemitter3": "^0.1.5",
48 | "lodash": "^3.8.0",
49 | "object-path": "^0.6.0"
50 | },
51 | "jshintConfig": {
52 | "camelcase": true,
53 | "curly": true,
54 | "eqeqeq": true,
55 | "forin": true,
56 | "latedef": true,
57 | "newcap": false,
58 | "undef": true,
59 | "unused": true,
60 | "trailing": true,
61 | "node": true,
62 | "browser": true,
63 | "predef": [
64 | "it",
65 | "describe",
66 | "beforeEach",
67 | "afterEach"
68 | ]
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/script/build-examples:
--------------------------------------------------------------------------------
1 | cd examples/todo-basic && ../../node_modules/.bin/webpack -p && cd -
2 | cd examples/carousel && ../../node_modules/.bin/webpack -p && cd -
3 | cd examples/async && ../../node_modules/.bin/webpack -p && cd -
4 | cd examples/react-router && ../../node_modules/.bin/webpack -p && cd -
5 |
6 | cp examples/carousel/app/bundle.js site/contents/examples/carousel/carousel-bundle.js
7 | cp examples/todo-basic/app/bundle.js site/contents/guides/todo-bundle.js
8 | cp examples/async/app/bundle.js site/contents/guides/async-bundle.js
9 | cp examples/react-router/app/bundle.js site/contents/examples/react-router-bundle.js
10 |
--------------------------------------------------------------------------------
/script/build-fluxxor:
--------------------------------------------------------------------------------
1 | ./script/write-version-file
2 | ./node_modules/.bin/webpack
3 | ./node_modules/.bin/webpack -p --output-file "fluxxor.min.js" --output-source-map-file "fluxxor.min.js.map"
4 |
--------------------------------------------------------------------------------
/script/write-version-file:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var fs = require("fs"),
4 | pkg = require("../package.json"),
5 | bower = require("../bower.json");
6 |
7 | bower.version = pkg.version;
8 | fs.writeFileSync("version.js", "module.exports = " + JSON.stringify(pkg.version), "utf8");
9 | fs.writeFileSync("bower.json", JSON.stringify(bower, null, " ") + "\n", "utf8");
10 |
--------------------------------------------------------------------------------
/site/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "locals": {
3 | "title": "Fluxxor"
4 | },
5 | "plugins": ["wintersmith-ejs", "wintersmith-less"]
6 | }
7 |
--------------------------------------------------------------------------------
/site/contents/CNAME:
--------------------------------------------------------------------------------
1 | fluxxor.com
2 |
--------------------------------------------------------------------------------
/site/contents/changelog.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Changelog
3 | template: page.ejs
4 | ---
5 |
6 | Changelog
7 | =========
8 |
9 | Version 1.7.3
10 | -------------
11 |
12 | * Fix `bind` warning when using StoreWatchMixin with createClass (#140)
13 |
14 | Version 1.7.2
15 | -------------
16 |
17 | * Update `StoreWatchMixin` to support ES6 classes (#135)
18 |
19 | Version 1.7.1
20 | -------------
21 |
22 | * Relax restrictions on Lodash version (#128)
23 |
24 | Version 1.7.0
25 | -------------
26 |
27 | * Add `Flux#getAllStores()` to retrieve all currently registered stores (#127)
28 |
29 | Version 1.6.0
30 | -------------
31 |
32 | * Add `Flux#setDispatchInterceptor` to wrap or replace dispatch functionality (#100, #92)
33 |
34 | Version 1.5.4
35 | -------------
36 |
37 | * Fix incompatibility with Lodash 3.9.0
38 |
39 | Version 1.5.3
40 | -------------
41 |
42 | * Use built-in inherits instead of npm package (#116)
43 |
44 | Version 1.5.2
45 | -------------
46 |
47 | * Upgrade to Lo-Dash 3.x
48 | * Fix minor typo in mixin warnings
49 |
50 | Version 1.5.1
51 | -------------
52 |
53 | * Watch stores in `componentDidMount` instead of `componentWillMount` to make it harder to leak memory on the server
54 |
55 | Version 1.5.0
56 | -------------
57 |
58 | **Additions/Non-Breaking Changes**
59 |
60 | * You can add stores and actions to existing Flux instances via `addStore`, `addStores`, `addAction`, and `addActions` (#68, #71, #77)
61 | * `Flux` instances are now EventEmitters, and emit a `"dispatch"` event (with `type` and `payload` arguments) when an action calls `this.dispatch(type, payload)`, useful for cross-cutting concerns like logging
62 | * `Store#bindActions` now takes a hash (similar to the static `actions` hash) in addition to an argument list (#51, #78)
63 | * Fluxxor will throw more descriptive errors in many situations that are obviously incorrect (for example, when an action handler is not defined, or an action type in `bindActions` is falsy)
64 |
65 | **Deprecations**
66 |
67 | * `Fluxxor.FluxChildMixin` is now deprecated; instead, use `FluxMixin` anywhere you want access to `getFlux()` (#59)
68 |
69 | Version 1.4.2
70 | -------------
71 |
72 | * Throw an error when binding to a falsy action type (#50)
73 | * Reduce npm package size with `.npmignore` (#65)
74 | * Warn if a dispatch is not handled by any store (#66)
75 | * Reduce file size slightly by using [EventEmitter3](https://github.com/3rd-Eden/EventEmitter3) instead of Node.js `events` module
76 |
77 | Version 1.4.1
78 | -------------
79 |
80 | * Reduce file size by generating a smaller version file (#63) and using inherits package (#64)
81 |
82 | Version 1.4.0
83 | -------------
84 |
85 | * Action generating methods (methods in the `actions` parameter to the `Fluxxor.Flux` constructor) can access the `Flux` instance via `this.flux`.
86 |
87 | Version 1.3.2
88 | -------------
89 |
90 | * Ensure component is mounted before setting state in in StoreWatchMixin
91 |
92 | Version 1.3.1
93 | -------------
94 |
95 | * Fix Bower versioning
96 |
97 | Version 1.3.0
98 | -------------
99 |
100 | * Add support for namespaced actions
101 |
102 | Version 1.2.2
103 | -------------
104 |
105 | * Maintain references to stores on Flux object
106 |
107 | Version 1.2.1
108 | -------------
109 |
110 | * Throw when dispatching a value without a `type` property
111 |
112 | Version 1.2.0
113 | -------------
114 |
115 | * Allow synchronous back-to-back dispatches
116 | * Gracefully handle exceptions in action handlers
117 | * Add hasOwnProperty(key) checks throughout codebase
118 |
119 | Version 1.1.3
120 | -------------
121 |
122 | * Add [Bower](http://bower.io/) support
123 |
124 | Version 1.1.2
125 | -------------
126 |
127 | * Fix compilation when using webpack (#9)
128 |
129 | Version 1.1.1
130 | -------------
131 |
132 | * Reduce bundle size by 40%
133 |
134 | Version 1.1.0
135 | -------------
136 |
137 | * Add `FluxChildMixin` to access flux on child components
138 | * Use `getFlux()` to access flux with either `FluxMixin` or `FluxChildMixin`
139 |
140 | Version 1.0.2
141 | -------------
142 |
143 | * Documentation updates
144 |
145 | Version 1.0.1
146 | -------------
147 |
148 | * Throw when mixing `StoreWatchMixin` in without calling it as a function
149 |
150 | Version 1.0.0
151 | -------------
152 |
153 | First stable release
154 |
--------------------------------------------------------------------------------
/site/contents/documentation/actions.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Actions
3 | template: page.ejs
4 | ---
5 |
6 | Actions
7 | =======
8 |
9 | Actions, which are simply combinations of a string `type` and an object `payload`, represent the intent to perform some manipulation of the data in a Flux application. They are dispatched via the aptly named dispatcher to every store the dispatcher knows about. The only way to update stores is to send them actions.
10 |
11 | To make programming with Flux more semantic, the [Flux](/documentation/flux.html) constructor takes an object with function values; these functions can call `this.dispatch(type, payload)` to dispatch an action to the dispatcher. Dispatching an action then looks like a normal method call. As an example, imagine you want to dispatch a method to add a URL at a certain index. Dispatching manually, you would write
12 |
13 | ```javascript
14 | flux.dispatcher.dispatch({type: "ADD_URL", payload: {url: "http://google.com", index: 3}});
15 | ```
16 |
17 | By using the `actions` hash, it would look more like this:
18 |
19 | ```javascript
20 | var actions = {
21 | addUrl: function(url, index) {
22 | this.dispatch("ADD_URL", {url: url, index: index});
23 | }
24 | };
25 |
26 | var flux = new Fluxxor.Flux(stores, actions);
27 |
28 | // somewhere later...
29 | flux.actions.addUrl("http://google.com", 3);
30 | ```
31 |
32 | Actions can be added to a `Flux` instance programmatically using `Flux#addAction` or `Flux#addActions`; see the [Flux documentation](/documentation/flux.html) for more details.
33 |
34 | Accessing the Flux Instance
35 | ---------------------------
36 |
37 | The [Flux](/documentation/flux.html) instance the action generators are bound to is available via `this.flux` from inside the functions.
38 |
39 | ```javascript
40 | var actions = {
41 | addUrl: function(url) {
42 | var someData = this.flux.store("someStore").getData();
43 | this.dispatch("ADD_URL", {url: url, additionalData: someData});
44 | }
45 | };
46 |
47 | var stores = {
48 | someStore: new SomeStore(...);
49 | };
50 |
51 | var flux = new Fluxxor.Flux(stores, actions);
52 | ```
53 |
54 | Namespaced Actions
55 | ------------------
56 |
57 | Fluxxor will iterate over objects in your actions definition, allowing for namespaced actions:
58 |
59 | ```javascript
60 | var actions = {
61 | user: {
62 | login: function() { this.dispatch(...); },
63 | logout: function() { this.dispatch(...); }
64 | },
65 | post: {
66 | add: function() { this.dispatch(...); },
67 | remove: function() { this.dispatch(...); }
68 | }
69 | };
70 |
71 | var flux = new Fluxxor.Flux(stores, actions);
72 |
73 | // somewhere later...
74 | flux.actions.user.login(...);
75 | ```
76 |
77 | Responding to Actions
78 | ---------------------
79 |
80 | [Stores](/documentation/stores.html) respond to specific action types via the `actions` object in their spec or via calls to `bindActions` during initialization.
81 |
82 | ```javascript
83 | var UrlStore = Fluxxor.createStore({
84 | actions: {
85 | "ADD_URL": "handleAddUrl"
86 | },
87 |
88 | // ...
89 |
90 | handleAddUrl: function(payload, type) {
91 | this.urls.splice(payload.index, 0, payload.url);
92 | this.emit("change");
93 | }
94 | });
95 | ```
96 |
97 | See the documentation on [stores](/documentation/stores.html) for more information.
98 |
--------------------------------------------------------------------------------
/site/contents/documentation/flux-mixin.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: FluxMixin
3 | template: page.ejs
4 | ---
5 |
6 | Fluxxor.FluxMixin
7 | =================
8 |
9 | `Fluxxor.FluxMixin` is a simple React mixin that assists with making a [`Flux`](/documentation/flux.html) instance available to a component hierarchy. Pass an instance of `Flux` as a property named `flux` and mix `FluxMixin` in to a top level component, and mix in `FluxMixin` to any child components, and the `Flux` object will be available as `this.getFlux()` in any component using the mixin.
10 |
11 | Keep in mind that implicitly passing data through context can make it more difficult to reason about things like `shouldComponentUpdate`. Ideally, an instance of `Flux` on the context of a child component should only be used to dispatch actions, and *not* to read data from the stores—read data from the stores at the top-level component and pass the data through props as necessary.
12 |
13 | Note that `FluxMixin` is a function that takes `React` as an argument and returns the associated mixin.
14 |
15 | Example:
16 |
17 | ```javascript
18 | var React = require("react"),
19 | Fluxxor = require("fluxxor"),
20 | FluxMixin = Fluxxor.FluxMixin(React); // or window.React, etc.
21 |
22 | var ParentComponent = React.createClass({
23 | mixins: [FluxMixin],
24 |
25 | render: function() {
26 | return ;
27 | }
28 | });
29 |
30 | var ChildComponent = React.createClass({
31 | render: function() {
32 | return ;
33 | }
34 | });
35 |
36 | var GrandchildComponent = React.createClass({
37 | mixins: [FluxMixin],
38 |
39 | render: function() {
40 | return ;
41 | },
42 |
43 | onClick: function() {
44 | this.getFlux().actions.someAction();
45 | }
46 | });
47 |
48 | var flux = new Fluxxor.Flux(...);
49 | React.renderComponent(, ...);
50 | ```
51 |
--------------------------------------------------------------------------------
/site/contents/documentation/flux.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Fluxxor.Flux
3 | template: page.ejs
4 | ---
5 |
6 | Fluxxor.Flux
7 | ============
8 |
9 | `Fluxxor.Flux` is the main container object for a Flux application. It provides access to the [stores](/documentation/stores.html) and the [actions](/documentation/actions.html), and is responsible for managing the dispatcher internally.
10 |
11 | ## `new Fluxxor.Flux(stores, actions)`
12 |
13 | Creates a new `Flux` instance.
14 |
15 | * `stores` - An object map of store names to store instances. Stores can be retrieved later by their name. See [Stores](/documentation/stores.html) for more information.
16 | * `actions` - An object map of action names to action functions. See [Actions](/documentation/actions.html) for more information.
17 |
18 | Example:
19 |
20 | ```javascript
21 | var stores = {
22 | MyStore: new MyStore({options: here}),
23 | OtherStore: new OtherStore({options: here})
24 | };
25 |
26 | var actions = {
27 | processThing: function(thing) {
28 | this.dispatch("PROCESS_THING", {thing: thing});
29 | }
30 | };
31 |
32 | var flux = new Fluxxor.Flux(stores, actions);
33 | ```
34 |
35 | ## `Fluxxor.Flux#store(name)`
36 |
37 | Retrieves a store by its name.
38 |
39 | * `name` - The name of the store as passed to the `Flux` constructor.
40 |
41 | Example:
42 |
43 | ```javascript
44 | var stores = {
45 | MyStore: new MyStore();
46 | };
47 |
48 | var flux = new Fluxxor.Flux(stores, actions);
49 |
50 | var myStore = flux.store("MyStore");
51 | ```
52 |
53 | ## `Fluxxor.Flux#getAllStores()`
54 |
55 | Retrieves all stores. The return value is an object where the keys are the names of the stores and the values are the stores themselves.
56 |
57 | **Note:** This is a reference to the underlying stores implementation, and should not be modified.
58 |
59 | ## `Fluxxor.Flux#actions`
60 |
61 | Retrieves the map of actions.
62 |
63 | Example:
64 |
65 | ```javascript
66 | var actions = {
67 | processThing: function(thing) {
68 | this.dispatch("PROCESS_THING", {thing: thing});
69 | }
70 | };
71 |
72 | var flux = new Fluxxor.Flux(stores, actions);
73 |
74 | flux.actions.processThing(myThing);
75 | ```
76 |
77 | ## `Fluxxor.Flux#addStore(name, store)`
78 |
79 | Adds a new store to the `Flux` instance.
80 |
81 | * `name` - The name used to identify the store. Stores can be retrieved later by their name. See [Stores](/documentation/stores.html) for more information.
82 | * `store` - The store instance to add.
83 |
84 | ```javascript
85 | flux.addStore("user", new UserStore());
86 | ```
87 |
88 | ## `Fluxxor.Flux#addStores(stores)`
89 |
90 | Adds stores to the `Flux` instance.
91 |
92 | * `stores` - A hash of stores to add, in the same format as the `Fluxxor.Flux` constructor.
93 |
94 | ```javascript
95 | var newStores = {
96 | user: new UserStore(),
97 | post: new PostStore()
98 | };
99 |
100 | flux.addStores(newStores);
101 | ```
102 |
103 | ## `Fluxxor.Flux#addAction(path..., function)`
104 |
105 | Adds an action to the `Flux` instance's action hash. This function takes a path (either as an array of strings or as individual strings) followed by a function.
106 |
107 | * `path...` - The path in the actions object to add the action.
108 | * `function` - The action function to add.
109 |
110 | `path` can be specified either as an array of strings where each element is one part of the path, or as a free list of strings. For example:
111 |
112 | ```javascript
113 | // The action hash we want to end up with:
114 | {
115 | user: {
116 | login: function() { ... }
117 | }
118 | }
119 |
120 | // Path as an array
121 | flux.addAction(["user", "login"], function() { ... });
122 |
123 | // Path as free arguments
124 | flux.addAction("user", "login", function() { ... });
125 | ```
126 |
127 | Fluxxor will automatically create any intermediary objects as necessary, and will intelligently merge the new action into the existing hash, but does not allow overwriting any existing functions.
128 |
129 | ## `Fluxxor.Flux#addActions(actions)`
130 |
131 | Adds actions to the `Flux` instance's action hash.
132 |
133 | * `actions` - A hash of actions to add, in the same format as the `Fluxxor.Flux` constructor.
134 |
135 | ```javascript
136 | var newActions = {
137 | user: {
138 | login: function() { ... },
139 | logout: function() { ... }
140 | }
141 | };
142 |
143 | flux.addActions(newActions);
144 | ```
145 |
146 | Fluxxor will intelligently merge the new actions with the existing actions, but does not allow overwriting any existing functions.
147 |
148 | ## `Fluxxor.Flux#setDispatchInterceptor(interceptor)`
149 |
150 | Sets `interceptor` as the `Flux` instance's dispatch interceptor. The dispatch interceptor allows you to surround or replace the action dispatch with custom functionality.
151 |
152 | * `interceptor` - A function with the signature `function(action, dispatch)` where `action` is the action being dispatched and `dispatch` is the original (non-intercepted) dispatch function. If a falsy value, resets the dispatch interceptor to the default (no-op) interceptor.
153 |
154 | Sometimes it's useful to inject custom logic into the normal dispatch flow. `setDispatchInterceptor` allows you to wrap or replace the original dispatch function with your own logic. The default dispatch interceptor is essentially a no-op:
155 |
156 | ```javascript
157 | flux.setDispatchInterceptor(function(action, dispatch) {
158 | dispatch(action);
159 | });
160 | ```
161 |
162 | In particular, it can be very useful to wrap action dispatches in React's batched updates (if you're using React). To do so, wrap the dispatch in `ReactDOM.unstable_batchedUpdates` (which was `React.addons.batchedUpdates` before React v0.14):
163 |
164 | ```javascript
165 | flux.setDispatchInterceptor(function(action, dispatch) {
166 | ReactDOM.unstable_batchedUpdates(function() {
167 | dispatch(action);
168 | });
169 | });
170 | ```
171 |
172 | (See the [Using with React page](/guides/react.html) for more information on how `unstable_batchedUpdates` can help.)
173 |
174 | You can even bypass the original dispatch function entirely for testing or more exotic implementations:
175 |
176 | ```javascript
177 | flux.setDispatchInterceptor(function(action, dispatch) {
178 | // Ignore the `dispatch` argument and do our own thing with the action, for example:
179 | window.postMessage({ type: "myCustomThing", action: action });
180 | });
181 | ```
182 |
183 | ## `EventEmitter` methods
184 |
185 | `Flux` instances are also instances of EventEmitters, and thus [inherit all the EventEmitter methods](http://nodejs.org/api/events.html#events_class_events_eventemitter). Most notably, `Flux` instances dispatch a `"dispatch"` event with `type` and `payload` arguments when `this.dispatch` is called from an action. This is useful for cross-cutting concerns (like logging), and should not be used for managing the flow of data in a Fluxxor application.
186 |
187 | Example:
188 |
189 | ```javascript
190 | flux.on("dispatch", function(type, payload) {
191 | console.log("Dispatched", type, payload);
192 | }
193 | ```
194 |
195 | Note that the action will still be dispatched even if the `"dispatch"` event handler throws an exception.
196 |
--------------------------------------------------------------------------------
/site/contents/documentation/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Documentation
3 | template: page.ejs
4 | ---
5 |
6 | Documentation
7 | =============
8 |
9 | Fluxxor consists of three core pieces:
10 |
11 | * [Fluxxor.Flux](/documentation/flux.html)
12 | * [Stores](/documentation/stores.html)
13 | * [Actions](/documentation/actions.html)
14 |
15 | Additionally, Fluxxor ships with some mixins to make it easier to use in conjunction with React:
16 |
17 | * [Fluxxor.FluxMixin](/documentation/flux-mixin.html)
18 | * [Fluxxor.StoreWatchMixin](/documentation/store-watch-mixin.html)
19 |
20 | For installation instructions, see the [installation](/guides/installation.html) page. Be sure to check out the [quick-start guide](/guides/quick-start.html) and the [examples](/examples/), too!
21 |
--------------------------------------------------------------------------------
/site/contents/documentation/store-watch-mixin.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: StoreWatchMixin
3 | template: page.ejs
4 | ---
5 |
6 | Fluxxor.StoreWatchMixin
7 | =======================
8 |
9 | `Fluxxor.StoreWatchMixin` is a simple React mixin that assists with watching for `"change"` events on one or more stores. Normally, you'd need to bind to store change events in `componentDidMount` and unbind them in `componentWillUnmount` to keep from leaking memory. Additionally, you'd need to set up handlers to pull data from the stores during change events and lifecycle hooks (such as `getInitialState`).
10 |
11 | `StoreWatchMixin` simply requires that you:
12 |
13 | 1. Define a method on your component called `getStateFromFlux` that returns an object representing the part of the component's state that comes from the Flux stores.
14 | 2. Have a `prop` or `context` property named `flux` that points to the `Flux` instance with the stores. This is automatic if you use [`FluxMixin`](/documentation/flux-mixin.html).
15 |
16 | The mixin will then automatically
17 |
18 | 1. Bind to `"change"` events for each store when the component mounts
19 | 2. Unbind from `"change"` events when the component unmounts
20 | 3. Automatically call `setState` with the return value of `getStateFromFlux` when a store emits a `"change"` event
21 | 4. Automatically set the component's initial state based on the return value of `getStateFromFlux` when the component mounts (note that this object is merged with any other `getInitialState` functions defined on the component or other mixins)
22 |
23 | Note that `StoreWatchMixin` binds events in `componentDidMount` and not `componentWillMount` in order to prevent memory leaks when rendering React components that use Fluxxor on the server using Node.js. This means that actions fired from `componentWillMount` that result in `"change"` events in your stores will not update the component; consider moving such actions to `componentDidMount` instead.
24 |
25 | Example:
26 |
27 | ```javascript
28 | var React = require("react"),
29 | Fluxxor = require("fluxxor"),
30 | FluxMixin = Fluxxor.FluxMixin(React), // or window.React, etc.
31 | StoreWatchMixin = Fluxxor.StoreWatchMixin;
32 |
33 | var MyStore = Fluxxor.createStore({ ... }),
34 | OtherStore = Fluxxor.createStore({ ... });
35 |
36 | var stores = {
37 | MyStore: new MyStore(),
38 | OtherStore: new OtherStore()
39 | };
40 | var actions = { ... };
41 |
42 | var flux = new Fluxxor.Flux(stores, actions);
43 |
44 | var MyComponent = React.createClass({
45 | mixins: [FluxMixin, StoreWatchMixin("MyStore", "OtherStore")],
46 |
47 | getStateFromFlux: function() {
48 | var flux = this.getFlux();
49 | return {
50 | stateFromMyStore: flux.store("MyStore").getSomeData(),
51 | stateFromOtherStore: flux.store("OtherStore").getMoreData(),
52 | };
53 | },
54 |
55 | render: function() {
56 | // ...
57 | }
58 | });
59 | ```
60 |
--------------------------------------------------------------------------------
/site/contents/documentation/stores.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Stores
3 | template: page.ejs
4 | ---
5 |
6 | Stores
7 | ======
8 |
9 | In a Flux application, the stores are responsible for managing business logic and data. They're akin to models or collections in MVC systems, but stores may manage more than a single piece of data or a single collection, as they are responsible for a *domain* of the application.
10 |
11 | The *only* way to update stores is to send them an action by way of the dispatcher; stores should not have setter methods or properties that allow users to manipulate the store directly. Stores register their intent to respond to certain action types and actions with those types are routed to the appropriate handlers in the stores. Handlers are called with the action's payload and type as parameters.
12 |
13 | Fluxxor supports dependencies between stores when necessary. If a store depends on data from other stores, it can wait for those stores to finish handling the currently dispatched action with the `waitFor` method.
14 |
15 | Action dispatches are synchronous; stores can perform asynchronous updates, but should fire new actions at the end of the asynchronous operation if the rest of the system should be notified of a change in state as a result of the async operation. (Alternatively, perform asynchronous operations in the action methods themselves.) Once a store returns anything (including `undefined`) from an action handler, that store is considered to be done with that action's dispatch (unless it calls `waitFor`).
16 |
17 | Stores can be added to a `Flux` instance programmatically using `Flux#addStore` or `Flux#addStores`; see the [Flux documentation](/documentation/flux.html) for more details.
18 |
19 | ## `Fluxxor.createStore(spec)`
20 |
21 | Create a new store constructor.
22 |
23 | * `spec` - An object describing the stores created with the returned constructor.
24 |
25 | `spec` may contain any properties; functions will be automatically bound to the store instance and attached to it, and other properties will simply be attached to it. The following properties of `spec` behave specially:
26 |
27 | * `actions` - An object map of action types to method names. Actions dispatched to the store with the given type will automatically be handled by the method with the corresponding name. Dynamic action types can be bound with the `bindActions` method. (`bindActions` is the recommended approach, since you can refer to constants defined elsewhere, which helps catch errors earlier and better supports minification.)
28 |
29 | * `initialize(options)` - A function that will be called right after a store is instantiated. `options` is an optional object passed from the constructor.
30 |
31 | Example:
32 |
33 | ```javascript
34 | var MyStore = Fluxxor.createStore({
35 | actions: {
36 | "ACTION_TYPE": "handleActionType"
37 | },
38 |
39 | initialize: function(options) {
40 | this.value = options.value;
41 |
42 | // We could also use this in place of the `actions` hash, above:
43 | this.bindActions(
44 | "ACTION_TYPE", this.handleActionType
45 | );
46 | },
47 |
48 | handleActionType: function(payload, type) {
49 | // ...
50 | }
51 | });
52 |
53 | var myStore = new MyStore({value: 123});
54 | ```
55 |
56 | ## `Store#flux`
57 |
58 | The [Flux](/documentation/flux.html) instance this store is contained within.
59 |
60 | ## `Store#bindActions(type, handler[, ...])`
61 | ## `Store#bindActions(actions)`
62 |
63 | Binds action types to methods on the store.
64 |
65 | `bindActions(type, handler[, ...])` takes any even number of arguments:
66 |
67 | * `type` - The action type to bind to.
68 | * `handler` - A function reference or method name (as a string) to call when actions of that type are dispatched to the store.
69 |
70 | `bindActions(actions)` takes an actions hash in the same format as `spec.actions` described in `createStore`, above.
71 |
72 | Using `bindActions` with constants defined elsewhere is less error prone and supports minification better than using the `actions` hash in the `createStore` spec or passing a hash to `bindActions`.
73 |
74 | Example:
75 |
76 | ```javascript
77 | var ACTION_TYPE = "ACTION_TYPE_1",
78 | OTHER_ACTION_TYPE = "ACTION_TYPE_2";
79 |
80 | var MyStore = Fluxxor.createStore({
81 | initialize: function() {
82 | this.bindActions(
83 | ACTION_TYPE, this.handleActionType,
84 | OTHER_ACTION_TYPE, "handleOtherActionType"
85 | );
86 |
87 | // OR:
88 |
89 | this.bindActions({
90 | "ACTION_TYPE_1": "handleActionType",
91 | "ACTION_TYPE_2": "handleOtherActionType"
92 | });
93 | },
94 |
95 | handleActionType: function(payload, type) {
96 | // ...
97 | },
98 |
99 | handleOtherActionType: function(payload, type) {
100 | // ...
101 | }
102 | });
103 | ```
104 |
105 | ## `Store#waitFor(stores, callback)`
106 |
107 | Waits for other stores to finish dispatching the current action, executing `callback` afterwards. Since action handlers are synchronous, a store is "finished" handling an action when the handler function returns.
108 |
109 | * `stores` - An array of names of stores to wait for.
110 | * `callback(stores)` - A function to call after all the specified stores have handled the current dispatch. The function is called with the store instances (corresponding to the array of names).
111 |
112 | `waitFor` will throw if it detects circular dependencies among the stores being dispatched to.
113 |
114 | Example:
115 |
116 | ```javascript
117 | Fluxxor.createStore({
118 | actions: {
119 | "ACTION_TYPE": "actionHandler"
120 | },
121 |
122 | actionHandler: function(payload, type) {
123 | this.waitFor(["otherStore", "anotherStore"], function(other, another) {
124 | // ...
125 | });
126 | }
127 | });
128 | ```
129 |
130 | Note that the callback is called synchronously.
131 |
132 | ## `EventEmitter` methods
133 |
134 | Stores are instances of EventEmitters, and thus [inherit all the EventEmitter methods](http://nodejs.org/api/events.html#events_class_events_eventemitter). Most notably, stores should `emit` an event to notify the views that their data has changed.
135 |
136 | The [`StoreWatchMixin`](/documentation/store-watch-mixin.html) assists with attaching event handlers to store change events in React applications.
137 |
138 | Example:
139 |
140 | ```javascript
141 | var MyStore = Fluxxor.createStore({
142 | // ...
143 |
144 | actionHandler: function(payload, type) {
145 | // some update to the store's data here
146 | this.emit("change");
147 | }
148 | });
149 |
150 | var myStore = new MyStore();
151 |
152 | myStore.on("change", function() {
153 | // the store has updated its data
154 | });
155 | ```
156 |
--------------------------------------------------------------------------------
/site/contents/examples/carousel/images/bttf1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/examples/carousel/images/bttf1.png
--------------------------------------------------------------------------------
/site/contents/examples/carousel/images/bttf2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/examples/carousel/images/bttf2.png
--------------------------------------------------------------------------------
/site/contents/examples/carousel/images/bttf3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/examples/carousel/images/bttf3.png
--------------------------------------------------------------------------------
/site/contents/examples/carousel/images/bttf4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/examples/carousel/images/bttf4.png
--------------------------------------------------------------------------------
/site/contents/examples/carousel/images/bttf5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/examples/carousel/images/bttf5.png
--------------------------------------------------------------------------------
/site/contents/examples/carousel/images/bttf6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/examples/carousel/images/bttf6.png
--------------------------------------------------------------------------------
/site/contents/examples/carousel/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Carousel
3 | template: page.ejs
4 | ---
5 |
6 | Image Carousel
7 | ==============
8 |
9 |
10 |
11 | This image carousel is a very simple React application built using Fluxxor. The source for the example can be found [on GitHub](https://github.com/BinaryMuse/fluxxor/tree/master/examples/carousel).
12 |
13 | The entry point of the application is `app/app.jsx`. The `Fluxxor.Flux` instance is exported to `window.flux` so that you can manipulate the application from the standard JavaScript console (e.g. the methods on `window.flux.actions`).
14 |
15 | Stores
16 | ------
17 |
18 | The system contains two stores:
19 |
20 | * `ImageStore` - Contains the list of images to display in the carousel
21 | * `CarouselStore` - Contains information about the carousel position (which image is being displayed, etc.) and ensures that the user cannot change images while a transition is in progress
22 |
23 | This separation is perhaps a bit contrived, but shows how you might use multiple stores in a Flux-based React application.
24 |
25 | Actions
26 | -------
27 |
28 | There are four actions that manipulate the stores:
29 |
30 | * `prevImage` - Moves the carousel to the previous image (modifies `CarouselStore`)
31 | * `nextImage` - Moves the carousel to the next image (modifies `CarouselStore`)
32 | * `selectImage` - Moves the carousel to a specific image (modifies `CarouselStore`)
33 | * `addImage` - Adds an image to the carousel and moves to that image (modifies `ImageStore` and `CarouselStore`)
34 |
35 | `addImage` is particularly interesting because the action handler inside `CarouselStore` utilizes the `waitFor` mechanism to allow the `ImageStore` to accept or reject adding the image (based on its file extension and whether the image is already in the carousel).
36 |
37 | Components
38 | ----------
39 |
40 | * `Application` - The top-level component that maintains state from the stores using `Fluxxor.StoreWatchMixin`
41 | * `Carousel` - The actual image carousel itself; all data is passed as props, and user interaction is propagated back to `Application`
42 | * `ImageForm` - A small form for adding images to the carousel; user interaction is propagated back to `Application`
43 |
44 | Note that `ImageForm` has state outside of the data from the stores—however, it's localized to the form, and isn't part of the larger "application state." When the form is submitted, this local state is sent to the parent component through a property, where it is then sent to the dispatcher via the `addImage` action.
45 |
46 | ---
47 |
48 | In this small application, only the top level `Application` component requires access to the Flux data. However, since `Application` mixes in `Fluxxor.FluxMixin`, any descendants of `Application` with the `Fluxxor.FluxMixin` would automatically have access to the `Fluxxor.Flux` instance via `this.getFlux()`.
49 |
50 |
51 |
62 |
--------------------------------------------------------------------------------
/site/contents/examples/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Examples
3 | template: page.ejs
4 | ---
5 |
6 | Examples
7 | ========
8 |
9 | * [Basic Todos](/guides/quick-start.html) is a *very* simple todo app; build this app yourself with step-by-step instructions in the [quick-start guide](/guides/quick-start.html).
10 | * [Image Carousel](/examples/carousel/) is a simple image carousel that uses two stores.
11 | * [Asynchronous Data](/guides/async-data.html) demonstrates how to deal with asynchronous data loading in a flux application.
12 | * [React Router](/examples/react-router.html) demonstrates how to integrate Fluxxor with react-router, a React-based routing solution.
13 |
--------------------------------------------------------------------------------
/site/contents/examples/react-router.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: React Router
3 | template: page.ejs
4 | ---
5 |
6 | React Router
7 | ============
8 |
9 | This example demonstrates the techniques explained in [the routing guide](/guides/routing.html), utilizing [React Router](https://github.com/rackt/react-router) to provide a client-side routing solution. The code for this example can be found [on GitHub](https://github.com/BinaryMuse/fluxxor/tree/master/examples/react-router).
10 |
11 |
12 |
13 |
14 |
15 |
16 | The route definition and run block for this app looks like this:
17 |
18 | ```javascript
19 | var routes = (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 |
32 | var flux = new Fluxxor.Flux(...);
33 |
34 | Router.run(routes, function(Handler) {
35 | React.render(
36 | ,
37 | document.getElementById("app")
38 | );
39 | });
40 | ```
41 |
42 |
--------------------------------------------------------------------------------
/site/contents/faq.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: FAQ
3 | template: page.ejs
4 | ---
5 |
6 | FAQ
7 | ===
8 |
9 | **Q:** Does Fluxxor require React?
10 |
11 | **A:** Fluxxor works great with React, and if you want to use the mixins, you will of course need React, but Fluxxor has no dependency on React itself.
12 |
13 |
14 |
15 | **Q:** Why does `Fluxxor.FluxMixin` constantly throw an error?
16 |
17 | **A:** `Fluxxor.FluxMixin` is a function that takes React as a parameter and returns the associated mixin:
18 |
19 | ```javascript
20 | var FluxMixin = Fluxxor.FluxMixin(React);
21 |
22 | React.createClass({
23 | mixins: [FluxMixin],
24 |
25 | // ...
26 | });
27 | ```
28 |
29 |
30 |
31 | **Q:** How do I deal with asynchronous operations in stores?
32 |
33 | **A:** Dispatches to stores are always synchronous, but stores can perform asynchronous operations. At the end of the async call, the store can dispatch a separate action that represents the *availability* of the asynchronously-obtained data. See [Dealing with Asynchronous Data](/guides/async-data.html) for more details.
34 |
35 |
36 |
37 | **Q:** Why does dispatching an action while another action is in progress throw an error?
38 |
39 | **A:** Fluxxor prevents cascading updates where one action triggers another, and so on. See [What is Flux](/what-is-flux.html) and [Flux Application Architecture](http://facebook.github.io/react/docs/flux-overview.html) for more information on Flux.
40 |
41 |
42 |
43 | **Q:** Why am I getting an error saying that I can't dispatch an action while another action is being dispatched if I'm dispatching an action from `componentWillMount` or `componentDidMount`?
44 |
45 | **A:** React automatically batches updates when they're triggered from inside its synthetic event system (e.g. from an `onKeyPress` or `onClick` handler), but otherwise you have to batch them yourself. See the "Batched Updates" section of [Using with React](/guides/react.html) page for a solution to this problem.
46 |
47 |
48 |
49 | **Q:** Why is Fluxxor throwing an error saying an action is already being dispatched when I'm sending an action from an asynchronous operation?
50 |
51 | **A:** Some libraries will sometimes call callbacks on the same tick, for example if data is cached. You can wrap the action dispatch call in a `setTimeout` to ensure the function is asynchronous. For bonus points, notify the author of the offending library that [their asynchronous callbacks are sometimes synchronous](http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/).
52 |
53 |
54 |
55 | **Q:** Why do I see a warning that says "possible EventEmitter memory leak detected"?
56 |
57 | **A:** This warning is built in to the Node.js [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) to help avoid accidentally leaking handlers. If you know you're not actually leaking and you want to suppress this warning, you can call [`setMaxListeners(n)`](http://nodejs.org/api/events.html#events_emitter_setmaxlisteners_n) on your store (you can use a value of `0` for unlimited).
58 |
59 | As of version 1.4.2, Fluxxor uses [EventEmitter3](https://github.com/3rd-Eden/EventEmitter3) instead of the built-in Node.js EventEmitter, which should resolve this issue.
60 |
--------------------------------------------------------------------------------
/site/contents/fluxbox.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
102 |
--------------------------------------------------------------------------------
/site/contents/fluxxor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/fluxxor.png
--------------------------------------------------------------------------------
/site/contents/fluxxor.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
98 |
--------------------------------------------------------------------------------
/site/contents/getting-started/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Getting Started
3 | template: page.ejs
4 | ---
5 |
6 | Getting Started
7 | ===============
8 |
9 |
10 |
11 | [This content has moved. Click here if you are not automatically redirected.](/guides/)
12 |
--------------------------------------------------------------------------------
/site/contents/getting-started/installation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Installation
3 | template: page.ejs
4 | ---
5 |
6 | Installation
7 | ============
8 |
9 |
10 |
11 | [This content has moved. Click here if you are not automatically redirected.](/guides/installation.html)
12 |
--------------------------------------------------------------------------------
/site/contents/getting-started/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Quick-Start Guide
3 | template: page.ejs
4 | ---
5 |
6 | Quick-Start Guide / Basic Todos Example
7 | =======================================
8 |
9 |
10 |
11 | [This content has moved. Click here if you are not automatically redirected.](/guides/quick-start.html)
12 |
--------------------------------------------------------------------------------
/site/contents/guides/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Guides
3 | template: page.ejs
4 | ---
5 |
6 | Guides
7 | ======
8 |
9 | * [Installation](/guides/installation.html)
10 | * [Quick-Start Guide](/guides/quick-start.html)
11 | * [Dealing with Asynchronous Data](/guides/async-data.html)
12 |
--------------------------------------------------------------------------------
/site/contents/guides/installation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Installation
3 | template: page.ejs
4 | ---
5 |
6 | Installation
7 | ============
8 |
9 | You have a few options when it comes to installing Fluxxor. Keep in mind that, while Fluxxor works great with [React](http://facebook.github.io/react/), React is not required for Fluxxor.
10 |
11 | CommonJS Module Bundlers
12 | ------------------------
13 |
14 | Fluxxor is distributed [on npm](https://www.npmjs.org/package/fluxxor). You can install it with
15 |
16 | `npm install [--save] fluxxor`
17 |
18 | If you're using a client-side module bundler like [Browserify](http://browserify.org/) or [Webpack](http://webpack.github.io/), you're done! Simply `require("fluxxor")` to get a reference to the library.
19 |
20 | Browser Builds
21 | --------------
22 |
23 | Browser builds and corresponding source map files can be downloaded from [the Fluxxor releases page](https://github.com/BinaryMuse/fluxxor/releases) or installed via [Bower](http://bower.io/) via `bower install fluxxor`. Browser builds use a universal module definition, and should work in the following environments:
24 |
25 | ### CommonJS
26 |
27 | ```javascript
28 | var Fluxxor = require("path/to/fluxxor");
29 | ```
30 |
31 | ### AMD
32 |
33 | ```javascript
34 | define("someModule", ["Fluxxor"], function(Fluxxor) {
35 | // ...
36 | });
37 | ```
38 |
39 | ### Standalone
40 |
41 | ```html
42 |
43 | ```
44 |
45 | ```javascript
46 | window.Fluxxor.createStore({ ... });
47 | ```
48 |
49 | Third-Party Releases
50 | --------------------
51 |
52 | The following releases are maintained by third parties, and support inquiries should be directed to their maintainers.
53 |
54 | ### WebJar
55 |
56 | For JVM languages, there are [WebJar](http://www.webjars.org) packages available on Maven Central and jsDelivr as the following:
57 |
58 | SBT/Play Framework 2:
59 |
60 | ```scala
61 | "org.webjars" % "fluxxor" % fluxxorVersion
62 | ```
63 |
64 | Maven:
65 |
66 | ```xml
67 |
68 | org.webjars
69 | fluxxor
70 | ${fluxxor.version}
71 |
72 | ```
73 |
74 | For detailed instructions, refer to the [WebJars documentation](http://www.webjars.org/documentation). For update requests, open a pull request on the [Fluxxor WebJar repository on Github](https://github.com/webjars/fluxxor).
75 |
76 | Browser Compatibility
77 | ---------------------
78 |
79 | Fluxxor is compatible with any [ES5-compliant browser](http://kangax.github.io/compat-table/es5/) (IE 9+, FF 4+, Safari 5.1.4+, Chrome 19+, Opera 12.10+). You can use [es5-shim](https://github.com/es-shims/es5-shim) for other browsers.
80 |
81 | Getting Started
82 | ---------------
83 |
84 | Check out [the quick-start guide](/guides/quick-start.html) to get up-to-speed building apps with Fluxxor in no time.
85 |
--------------------------------------------------------------------------------
/site/contents/guides/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Quick-Start Guide
3 | template: page.ejs
4 | ---
5 |
6 | Quick-Start Guide / Basic Todos Example
7 | =======================================
8 |
9 | Once you have Fluxxor and React [installed](/guides/installation.html), it's time to build an app! Since this guide is designed to cover the basics quickly, we'll start with a *very* basic todo app—namely, this one right here:
10 |
11 |
12 |
13 | Stores and Actions
14 | ------------------
15 |
16 | First, let's create a store to keep track of our todo items. It will respond to the following actions:
17 |
18 | * `"ADD_TODO"` - adds a new todo
19 | * `"TOGGLE_TODO"` - completes or uncompletes a specific todo item
20 | * `"CLEAR_TODOS"` - removes all complete todo items
21 |
22 | ```javascript
23 | var constants = {
24 | ADD_TODO: "ADD_TODO",
25 | TOGGLE_TODO: "TOGGLE_TODO",
26 | CLEAR_TODOS: "CLEAR_TODOS"
27 | };
28 |
29 | var TodoStore = Fluxxor.createStore({
30 | initialize: function() {
31 | this.todoId = 0;
32 | this.todos = {};
33 |
34 | this.bindActions(
35 | constants.ADD_TODO, this.onAddTodo,
36 | constants.TOGGLE_TODO, this.onToggleTodo,
37 | constants.CLEAR_TODOS, this.onClearTodos
38 | );
39 | },
40 |
41 | onAddTodo: function(payload) {
42 | var id = this._nextTodoId();
43 | var todo = {
44 | id: id,
45 | text: payload.text,
46 | complete: false
47 | };
48 | this.todos[id] = todo;
49 | this.emit("change");
50 | },
51 |
52 | onToggleTodo: function(payload) {
53 | var id = payload.id;
54 | this.todos[id].complete = !this.todos[id].complete;
55 | this.emit("change");
56 | },
57 |
58 | onClearTodos: function() {
59 | var todos = this.todos;
60 |
61 | Object.keys(todos).forEach(function(key) {
62 | if(todos[key].complete) {
63 | delete todos[key];
64 | }
65 | });
66 |
67 | this.emit("change");
68 | },
69 |
70 | getState: function() {
71 | return {
72 | todos: this.todos
73 | };
74 | },
75 |
76 | _nextTodoId: function() {
77 | return ++this.todoId;
78 | }
79 | });
80 | ```
81 |
82 | Let's create a few semantic actions to go along with our action types.
83 |
84 | ```javascript
85 | var actions = {
86 | addTodo: function(text) {
87 | this.dispatch(constants.ADD_TODO, {text: text});
88 | },
89 |
90 | toggleTodo: function(id) {
91 | this.dispatch(constants.TOGGLE_TODO, {id: id});
92 | },
93 |
94 | clearTodos: function() {
95 | this.dispatch(constants.CLEAR_TODOS);
96 | }
97 | };
98 | ```
99 |
100 | Now we can instantiate our store and build a `Flux` instance:
101 |
102 | ```javascript
103 | var stores = {
104 | TodoStore: new TodoStore()
105 | };
106 |
107 | var flux = new Fluxxor.Flux(stores, actions);
108 | ```
109 |
110 | Finally, let's use the `"dispatch"` event to add some logging:
111 |
112 | ```javascript
113 | flux.on("dispatch", function(type, payload) {
114 | if (console && console.log) {
115 | console.log("[Dispatch]", type, payload);
116 | }
117 | });
118 | ```
119 |
120 | React Application
121 | -----------------
122 |
123 | Let's build out our UI with React.
124 |
125 | Our top-level `Application` component will use the [FluxMixin](/documentation/flux-mixin.html) as well as the [StoreWatchMixin](/documentation/store-watch-mixin.html) to make our lives a bit easier. The component will iterate over the array of todos and emit a `TodoItem` component for each one.
126 |
127 | We'll also add a quick form for adding new todo items, and a button for clearing completed todos.
128 |
129 | ```javascript
130 | var FluxMixin = Fluxxor.FluxMixin(React),
131 | StoreWatchMixin = Fluxxor.StoreWatchMixin;
132 |
133 | var Application = React.createClass({
134 | mixins: [FluxMixin, StoreWatchMixin("TodoStore")],
135 |
136 | getInitialState: function() {
137 | return { newTodoText: "" };
138 | },
139 |
140 | getStateFromFlux: function() {
141 | var flux = this.getFlux();
142 | // Our entire state is made up of the TodoStore data. In a larger
143 | // application, you will likely return data from multiple stores, e.g.:
144 | //
145 | // return {
146 | // todoData: flux.store("TodoStore").getState(),
147 | // userData: flux.store("UserStore").getData(),
148 | // fooBarData: flux.store("FooBarStore").someMoreData()
149 | // };
150 | return flux.store("TodoStore").getState();
151 | },
152 |
153 | render: function() {
154 | var todos = this.state.todos;
155 | return (
156 |
170 | );
171 | },
172 |
173 | handleTodoTextChange: function(e) {
174 | this.setState({newTodoText: e.target.value});
175 | },
176 |
177 | onSubmitForm: function(e) {
178 | e.preventDefault();
179 | if (this.state.newTodoText.trim()) {
180 | this.getFlux().actions.addTodo(this.state.newTodoText);
181 | this.setState({newTodoText: ""});
182 | }
183 | },
184 |
185 | clearCompletedTodos: function(e) {
186 | this.getFlux().actions.clearTodos();
187 | }
188 | });
189 | ```
190 |
191 | The `TodoItem` component will display and style itself based on the completion of the todo, and will dispatch an action indicating its intent to toggle its completion state.
192 |
193 | ```javascript
194 | var TodoItem = React.createClass({
195 | mixins: [FluxMixin],
196 |
197 | propTypes: {
198 | todo: React.PropTypes.object.isRequired
199 | },
200 |
201 | render: function() {
202 | var style = {
203 | textDecoration: this.props.todo.complete ? "line-through" : ""
204 | };
205 |
206 | return {this.props.todo.text};
207 | },
208 |
209 | onClick: function() {
210 | this.getFlux().actions.toggleTodo(this.props.todo.id);
211 | }
212 | });
213 | ```
214 |
215 | Bringing it Together
216 | --------------------
217 |
218 | Now that we have a `Flux` instance and all our components are defined, we can finally render our app. We'll put it inside a `div` in our HTML with an ID of "app".
219 |
220 | ```javascript
221 | React.render(, document.getElementById("app"));
222 | ```
223 |
224 | And that's it! We've created a (super simple) Flux application with React and Fluxxor. You can find the full source code [on GitHub](https://github.com/BinaryMuse/fluxxor/tree/master/examples/todo-basic).
225 |
226 |
227 |
--------------------------------------------------------------------------------
/site/contents/guides/react.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Using with React
3 | template: page.ejs
4 | ---
5 |
6 | Using with React
7 | ================
8 |
9 | Fluxxor can be used with any JavaScript framework (or no framework at all!), but it's designed to work well with [React](http://facebook.github.io/react/).
10 |
11 | Batched Updates
12 | ---------------
13 |
14 | Starting in React v0.12, React exposed an addon called `batchedUpdates` that allows you to manually batch React updates when outside of the normal React synthetic event lifecycle. This can be useful to ensure you don't get cascading dispatch errors with Fluxxor if you dispatch actions from within `componentWillMount` or `componentDidMount`. In React v0.14, this was moved to the `react-dom` package, and renamed to `unstable_batchedUpdates`.
15 |
16 | As of Fluxxor v1.6.0, there is a new method available you can use to tie Fluxxor action dispatches to the React batched update addon. To use `unstable_batchedUpdates` with Fluxxor, use `Flux#setDispatchInterceptor` with `ReactDOM.unstable_batchedUpdates`; for example:
17 |
18 | ```javascript
19 | var ReactDOM = require("react-dom"),
20 | Fluxxor = require("fluxxor");
21 |
22 | var flux = new Fluxxor.Flux(stores, actions);
23 | flux.setDispatchInterceptor(function(action, dispatch) {
24 | ReactDOM.unstable_batchedUpdates(function() {
25 | dispatch(action);
26 | });
27 | });
28 | ```
29 |
30 | Note that `ReactDOM.unstable_batchedUpdates` is specific to the DOM renderer, and is not appropriate for use in React Native apps, or apps that use other custom renderers.
31 |
32 | Mixins
33 | ------
34 |
35 | Fluxxor includes a couple mixins to make interop with React easier; check out the documentation on [Fluxxor.FluxMixin](/documentation/flux-mixin.html) and [Fluxxor.StoreWatchMixin](/documentation/store-watch-mixin.html) for more information.
36 |
--------------------------------------------------------------------------------
/site/contents/guides/routing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Routing
3 | template: page.ejs
4 | ---
5 |
6 | Routing
7 | =======
8 |
9 | When using a client-side router with Fluxxor, you need to find a way to get your `Flux` instance into the instantiated component. The [React Router project](https://github.com/rackt/react-router) makes this a cinch.
10 |
11 | First, define your routes:
12 |
13 | ```javascript
14 | var routes = (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | ```
28 |
29 | For each component you list as a route handler (`App`, `Inbox`, `Message`, `InboxStats`, `Calendar`, and `Dashboard` in this example), be sure to mix in [Fluxxor's `FluxMixin`](/documentation/flux-mixin.html).
30 |
31 | Finally, run your router, and when you instantiate your component, pass in `flux` as a property. Since each defined route handler uses `FluxMixin`, the flux object will be propagated throughout your application.
32 |
33 | ```javascript
34 | Router.run(routes, function(Handler) {
35 | React.render(, document.body);
36 | });
37 | ```
38 |
39 | Be sure to check out [the React Router example](/examples/react-router.html) for a working demonstration of this technique.
40 |
--------------------------------------------------------------------------------
/site/contents/images/flux-complex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/images/flux-complex.png
--------------------------------------------------------------------------------
/site/contents/images/flux-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/images/flux-diagram.png
--------------------------------------------------------------------------------
/site/contents/images/flux-simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/images/flux-simple.png
--------------------------------------------------------------------------------
/site/contents/images/mvc-complex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/images/mvc-complex.png
--------------------------------------------------------------------------------
/site/contents/images/mvc-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/images/mvc-diagram.png
--------------------------------------------------------------------------------
/site/contents/images/mvc-simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BinaryMuse/fluxxor/d11e52a4e6c99909e9584a8cd84b0a709ffb3d22/site/contents/images/mvc-simple.png
--------------------------------------------------------------------------------
/site/contents/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Home
3 | template: index.ejs
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Fluxxor is a set of tools to facilitate building JavaScript data layers using the Flux architecture by reifying many of the core Flux concepts. It works particularly well in conjunction with React as the view layer, and contains a few helpers to make integration with React applications easier.
12 |
13 |
The Flux architecture...
14 |
15 |
16 |
...makes it easier to reason about changes to your application's data
17 |
...eschews complex MVC hierarchies in favor of a one-way data flow
18 |
...helps improve data consistency
19 |
...prevents hard-to-debug cascading updates
20 |
...works great with React and complements its reactive data flow
21 |
22 |
23 |
**Want to learn more?** Start by checking out What is Flux.
58 |
--------------------------------------------------------------------------------
/site/contents/style/main.less:
--------------------------------------------------------------------------------
1 | @import url(http://fonts.googleapis.com/css?family=Lato:300,400,700);
2 |
3 | body {
4 | font: 15px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
5 | color: #000;
6 | font-weight: 300;
7 | padding: 10px;
8 | }
9 |
10 | h1, h2, h3, h4, h5, h6 {
11 | color: #000;
12 | }
13 |
14 | h1 {
15 | font-size: 30px;
16 | padding-bottom: 5px;
17 | border-bottom: 1px solid #ccc;
18 | }
19 |
20 | h1, h2, h3 {
21 | line-height: 1.1;
22 | }
23 |
24 | h1, h2, h3, h4, h5, h6 {
25 | position: relative;
26 |
27 | .anchor-symbol {
28 | visibility: hidden;
29 | margin-left: 10px;
30 | color: #999;
31 | }
32 |
33 | &:hover {
34 | .anchor-symbol {
35 | visibility: visible;
36 | }
37 | }
38 | }
39 |
40 | a {
41 | color: #39c;
42 | text-decoration: none;
43 | font-weight: 400;
44 | }
45 |
46 | a:visited {
47 | color: #7D33CC;
48 | }
49 |
50 | strong a {
51 | font-weight: bold;
52 | }
53 |
54 | .logo-container {
55 | text-align: right;
56 | float: left;
57 | padding-right: 20px;
58 | width: 350px;
59 |
60 | .logo-svg {
61 | margin-top: 3px;
62 | height: 420px;
63 | }
64 | }
65 |
66 | .logo-container-sidebar {
67 | padding-left: 15px;
68 |
69 | .logo-svg {
70 | max-height: 150px;
71 | }
72 | }
73 |
74 | .homepage-callout {
75 | float: left;
76 | width: 520px;
77 | padding-top: 0;
78 | }
79 |
80 | code.install {
81 | border: 1px solid #ccc;
82 | background-color: #eee;
83 | padding: 5px;
84 | border-radius: 5px;
85 | }
86 |
87 | .wrapper {
88 | width: 910px;
89 | margin: 0 auto;
90 | }
91 |
92 | figure {
93 | padding: 0;
94 | margin: 0;
95 |
96 | figcaption {
97 | font-style: italic;
98 | margin-left: 20px;
99 | font-size: 90%;
100 | }
101 | }
102 |
103 | header {
104 | width: 225px;
105 | float: left;
106 | position: fixed;
107 | overflow-y: auto;
108 | height: 100%;
109 | padding-bottom: 50px;
110 |
111 | @media screen and (max-height: 800px) {
112 | position: static;
113 | }
114 |
115 | ul {
116 | padding-left: 20px;
117 | }
118 | }
119 |
120 | .tweet-button-container {
121 | display: inline-block;
122 |
123 | iframe {
124 | margin-bottom: 2px;
125 | }
126 | }
127 |
128 | section {
129 | width: 660px;
130 | float: right;
131 | padding-bottom: 50px;
132 |
133 | > h1:first-child, > h2:first-child, > h3:first-child {
134 | padding-top: 0;
135 | margin-top: 0;
136 | }
137 |
138 | img {
139 | max-width: 100%;
140 | }
141 | }
142 |
143 | .bottom-button-container {
144 | padding-top: 50px;
145 | }
146 |
147 | .index-button-container {
148 | width: 222px;
149 | display: inline-block;
150 | text-align: center;
151 |
152 | .cta {
153 | font-weight: 700;
154 | }
155 |
156 | a {
157 | display: inline-block;
158 | width: 150px;
159 | padding: 10px;
160 | margin-top: 5px;
161 | background-color: #ccc;
162 | border-radius: 4px;
163 | color: black;
164 | font-weight: 700;
165 | color: white;
166 | }
167 | }
168 |
169 | .colored-button(@color) {
170 | background-color: @color;
171 | border: 2px solid darken(@color, 15%);
172 |
173 | &:hover {
174 | background-color: darken(@color, 5%);
175 | }
176 | }
177 |
178 | .index-button-container.teal a {
179 | .colored-button(#009A93);
180 | }
181 |
182 | .index-button-container.orange a {
183 | .colored-button(#E96633);
184 | }
185 |
186 | .index-button-container.red a {
187 | .colored-button(#D95C5C);
188 | }
189 |
190 | .index-button-container.purple a {
191 | .colored-button(#3E3773);
192 | }
193 |
194 | .hljs {
195 | background: transparent;
196 | font-size: 14px;
197 |
198 | .xml {
199 | opacity: 1;
200 | }
201 | }
202 |
203 | .show-hide-nav {
204 | display: none;
205 | }
206 |
207 | pre {
208 | overflow-x: auto;
209 | }
210 |
211 | .footer-info {
212 | display: none;
213 | }
214 |
215 | @media screen and (max-width: 1060px) {
216 | .github-banner {
217 | display: none;
218 | }
219 | }
220 |
221 | @media screen and (max-width: 940px) {
222 | .wrapper {
223 | width: auto;
224 | }
225 |
226 | .logo-container, .logo-container-sidebar {
227 | width: auto;
228 | float: none;
229 | text-align: center;
230 | padding-right: 0;
231 | }
232 |
233 | .logo-container .logo-svg {
234 | max-width: 100%;
235 | }
236 |
237 | .logo-container-sidebar {
238 | text-align: left;
239 | }
240 |
241 | .homepage-callout {
242 | width: auto;
243 | padding-top: 0;
244 | }
245 |
246 | header {
247 | float: none;
248 | display: block;
249 | position: relative;
250 | width: auto;
251 | padding-bottom: 20px;
252 |
253 | .nav-list {
254 | display: none;
255 | }
256 |
257 | .show-hide-nav {
258 | display: block;
259 | padding-left: 15px;
260 |
261 | a {
262 | display: block;
263 | }
264 |
265 | a.hide-nav {
266 | display: none;
267 | }
268 | }
269 | }
270 |
271 | section {
272 | width: auto;
273 | float: none;
274 | padding-bottom: 0;
275 | }
276 |
277 | .index-button-container {
278 | width: auto;
279 | display: block;
280 | padding-bottom: 20px;
281 | }
282 |
283 | .github-banner {
284 | display: none;
285 | }
286 |
287 | .sidebar-info {
288 | display: none;
289 | }
290 |
291 | .footer-info {
292 | display: block;
293 | }
294 | }
295 |
296 | #react-router-example-section {
297 | margin-top: 25px;
298 | margin-bottom: 50px;
299 | padding-left: 10px;
300 | border-left: 10px solid #ccc;
301 |
302 | #app h1 {
303 | padding-top: 0;
304 | margin-top: 0;
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/site/contents/what-is-flux.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: What is Flux?
3 | template: page.ejs
4 | ---
5 |
6 | What is Flux?
7 | =============
8 |
9 | Flux is an architecture for creating data layers in JavaScript applications. It was designed at Facebook along with the [React](http://facebook.github.io/react/) view library. It places a focus on creating **explicit and understandable update paths** for your application's data, which makes **tracing changes during development simpler** and makes **bugs easier to track down and fix**.
10 |
11 | Fluxxor is an implementation of the flux architecture pattern.
12 |
13 | Overview
14 | --------
15 |
16 | To best describe flux, we will compare it to one of the leading client-side architectures: MVC. In a client-side MVC application, a user interaction triggers code in a controller. The controller knows how to coordinate changes to one or more models by calling methods on the models. When the models change, they notify one or more views, which in turn read the new data from the models and update themselves accordingly so that the user can see that new data.
17 |
18 |
19 | 
20 | A simple MVC flow
21 |
22 |
23 | As an MVC application grows and controllers, models, and views are added, the dependencies become more complex.
24 |
25 |
26 | 
27 | A more complex MVC flow
28 |
29 |
30 | With the addition of just three views, one controller, and one model, the dependency graph is already harder to trace. When the user interacts with the UI, multiple branching code paths are executed, and debugging problems in application state becomes an exercise in figuring out which module (or modules) in one (or more) of these potential code paths contains a bug. In the worst cases, a user interaction will trigger updates which in turn trigger additional updates, resulting in error-prone and difficult-to-debug cascading effects along several of these, sometimes overlapping, paths.
31 |
32 | Flux eschews this design in favor of a one-way data flow. All user interactions within a view call an *action creator*, which causes an *action* event to be emitted from a singleton *dispatcher*. The dispatcher is a single-point-of-emission for all actions in a flux application. The action is sent from the dispatcher to *stores*, which update themselves in response to the action.
33 |
34 |
35 | 
36 | A simple flux flow
37 |
38 |
39 | The flow doesn't change significantly for additional stores or views. The dispatcher simply sends every action to *all* the stores in the application. Note that it does not contain knowledge about how to actually update the stores—the stores themselves contain this business logic. Each store is responsible for a domain of the application, and only update themselves in response to actions.
40 |
41 |
42 | 
43 | A more complex flux flow
44 |
45 |
46 | When a store updates, it emits a change event. In many React applications, special views (known sometimes as "controller-views") are responsible for watching for this change event, reading the stores' new data, and passing that data through properties to child views. It's not uncommon in a React application for a store change event to trigger a re-render of the top-level view, effectively re-rendering the entire view hierarchy (which React handles in an efficient manner). This completely avoids complex bugs and performance problems that can arise out of trying to watch for specific property changes on models and modifying parts of views only slightly.
47 |
48 | Key Properties
49 | --------------
50 |
51 | The flux architecture has a few key properties that make it unique and provide important guarantees, all of which are centered around making the flow of data explicit and easily understandable and increasing the locality of bugs (so that you don't have to hunt down many code paths to find incorrect logic). There are many flux and flux-like implementations available; these properties are, to me, most important to the flux architecture.
52 |
53 | ### Enforced Synchrony
54 |
55 | Action dispatches and their handlers inside the stores are synchronous. All asynchronous operations should trigger an action dispatch that tells the system about the result of the operation (see the [async data guide](/guides/async-data.html) for more details). While action creators can call out to asynchronous APIs, action handlers in the stores will ideally not do so. This rule makes the flow of information in the application extremely explicit; debugging errors in application state simply involves figuring out which action caused the bad state and then finding the incorrect logic that responded to that action.
56 |
57 | ### Inversion of Control
58 |
59 | Since stores update themselves internally in response to actions (rather than being updated from outside by a controller or a similar module), no other piece of the system needs to know how to modify the application's state. All the logic for updating the state is contained within the store itself. And, since stores only ever update in response to actions, and only synchronously, testing stores simply becomes a matter of putting them in an initial state, sending them an action, and testing to see if they're in the correct final state.
60 |
61 | ### Semantic Actions
62 |
63 | Since the stores need to update themselves in response to actions, the actions tend to be semantically descriptive. For example, in a flux forum application, to mark a thread as read, you might dispatch an action with a type of `MARK_THREAD_READ`. The action (and the component generating the action) doesn't know *how* to perform the update, but *describes* what it wants to happen.
64 |
65 | Because of this property, you rarely have to change your action types, only how the stores respond to them. As long as your application has a concept of a "thread" and you have a button or other interaction that should mark a thread as read, the `MARK_THREAD_READ` action type is semantically valid.
66 |
67 | ### No Cascading Actions
68 |
69 | Flux disallows dispatching a second action as a result of dispatching an action. This helps prevent hard-to-debug cascading updates and helps you think about interactions in your application in terms of semantic actions.
70 |
71 | Further Reading
72 | ---------------
73 |
74 | For a more in-depth look at the Flux architecture, check out [Flux Application Architecture](http://facebook.github.io/flux/docs/overview.html) on the official Flux site, and be sure to check out [Rethinking Web App Development at Facebook](https://www.youtube.com/watch?v=nYkdrAPrdcw) from F8 on YouTube to hear Jing Chen talk more about Flux.
75 |
76 | Ready to Try it Out?
77 | --------------------
78 |
79 | Check out the [installation](/guides/installation.html) and [getting started](/guides/quick-start.html) guides to get up and running with Fluxxor!
80 |
--------------------------------------------------------------------------------
/site/templates/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%= locals.title %> - <%= page.title %>
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |