Remove TodoMVC flux example
It lives at https://github.com/facebook/flux/tree/master/examples/flux-todomvc now.
Paul O’Shannessy committed
Aug 11, 2014 at 13:00 UTC
1033c4805f64924c0656472333c5b143a4397155
27 files changed
+425
-2333
docs/_posts/2014-05-06-flux.md
+1
-1
@@ -14,4 +14,4 @@ In Flux, the Dispatcher is a singleton that directs the flow of data and ensures
14
15
When a user interacts with a React view, the view sends an action (usually represented as a JavaScript object with some fields) through the dispatcher, which notifies the various stores that hold the application's data and business logic. When the stores change state, they notify the views that something has updated. This works especially well with React's declarative model, which allows the stores to send updates without specifying how to transition views between states.
16
17
-Flux is more of a pattern than a formal framework, so you can start using Flux immediately without a lot of new code. An [example of this architecture](https://github.com/facebook/react/tree/master/examples/todomvc-flux) is available, along with more [detailed documentation](http://facebook.github.io/react/docs/flux-overview.html) and a [tutorial](http://facebook.github.io/react/docs/flux-todo-list.html). Look for more examples to come in the future.
17
+Flux is more of a pattern than a formal framework, so you can start using Flux immediately without a lot of new code. An [example of this architecture](https://github.com/facebook/flux/tree/master/examples/flux-todomvc) is available, along with more [detailed documentation](http://facebook.github.io/react/docs/flux-overview.html) and a [tutorial](http://facebook.github.io/react/docs/flux-todo-list.html). Look for more examples to come in the future.
docs/docs/flux-overview.md
+27
-27
@@ -29,67 +29,67 @@ Views ---> (actions) ----> Dispatcher ---> (registered callback) ---> Stores ---
29
+-- (Controller-Views "change" event handlers) ---- (Stores emit "change" events) --+
30
</pre>
31
32
-A unidirectional data flow is central to the Flux pattern, and in fact Flux takes its name from the Latin word for flow. In the above diagram, the dispatcher, stores and views are independent nodes with distinct inputs and outputs. The actions are simply discrete, semantic helper functions that facilitate passing data to the dispatcher.
32
+A unidirectional data flow is central to the Flux pattern, and in fact Flux takes its name from the Latin word for flow. In the above diagram, the dispatcher, stores and views are independent nodes with distinct inputs and outputs. The actions are simply discrete, semantic helper functions that facilitate passing data to the dispatcher.
33
34
All data flows through the dispatcher as a central hub. Actions most often originate from user interactions with the views, and are nothing more than a call into the dispatcher. The dispatcher then invokes the callbacks that the stores have registered with it, effectively dispatching the data payload contained in the actions to all stores. Within their registered callbacks, stores determine which actions they are interested in, and respond accordingly. The stores then emit a "change" event to alert the controller-views that a change to the data layer has occurred. Controller-views listen for these events and retrieve data from the stores in an event handler. The controller-views call their own `render()` method via `setState()` or `forceUpdate()`, updating themselves and all of their children.
35
36
-This structure allows us to reason easily about our application in a way that is reminiscent of functional reactive programming, or more specifically data-flow programming or flow-based programming, where data flows through the application in a single direction — there are no two-way bindings. Application state is maintained only in the stores, allowing the different parts of the application to remain highly decoupled. Where dependencies do occur between stores, they are kept in a strict hierarchy, with synchronous updates managed by the dispatcher.
36
+This structure allows us to reason easily about our application in a way that is reminiscent of functional reactive programming, or more specifically data-flow programming or flow-based programming, where data flows through the application in a single direction — there are no two-way bindings. Application state is maintained only in the stores, allowing the different parts of the application to remain highly decoupled. Where dependencies do occur between stores, they are kept in a strict hierarchy, with synchronous updates managed by the dispatcher.
37
38
We found that two-way data bindings led to cascading updates, where changing one object led to another object changing, which could also trigger more updates. As applications grew, these cascading updates made it very difficult to predict what would change as the result of one user interaction. When updates can only change data within a single round, the system as a whole becomes more predictable.
39
40
-Let's look at the various parts of the Flux update cycle up close. A good place to start is the dispatcher.
40
+Let's look at the various parts of the Flux update cycle up close. A good place to start is the dispatcher.
41
42
43
-### A Single Dispatcher
43
+### A Single Dispatcher
44
45
-The dispatcher is the central hub that manages all data flow in a Flux application. It is essentially a registry of callbacks into the stores. Each store registers itself and provides a callback. When the dispatcher responds to an action, all stores in the application are sent the data payload provided by the action via the callbacks in the registry.
45
+The dispatcher is the central hub that manages all data flow in a Flux application. It is essentially a registry of callbacks into the stores. Each store registers itself and provides a callback. When the dispatcher responds to an action, all stores in the application are sent the data payload provided by the action via the callbacks in the registry.
46
47
As an application grows, the dispatcher becomes more vital, as it can manage dependencies between stores by invoking the registered callbacks in a specific order. Stores can declaratively wait for other stores to finish updating, and then update themselves accordingly.
48
49
50
-### Stores
50
+### Stores
51
52
-Stores contain the application state and logic. Their role is somewhat similar to a model in a traditional MVC, but they manage the state of many objects — they are not instances of one object. Nor are they the same as Backbone's collections. More than simply managing a collection of ORM-style objects, stores manage the application state for a particular __domain__ within the application.
52
+Stores contain the application state and logic. Their role is somewhat similar to a model in a traditional MVC, but they manage the state of many objects — they are not instances of one object. Nor are they the same as Backbone's collections. More than simply managing a collection of ORM-style objects, stores manage the application state for a particular __domain__ within the application.
53
54
-For example, Facebook's [Lookback Video Editor](https://facebook.com/lookback/edit) utilized a TimeStore that kept track of the playback time position and the playback state. On the other hand, the same application's ImageStore kept track of a collection of images. The TodoStore in our [TodoMVC example](https://github.com/facebook/react/tree/master/examples/todomvc-flux) is similar in that it manages a collection of to-do items. A store exhibits characteristics of both a collection of models and a singleton model of a logical domain.
54
+For example, Facebook's [Lookback Video Editor](https://facebook.com/lookback/edit) utilized a TimeStore that kept track of the playback time position and the playback state. On the other hand, the same application's ImageStore kept track of a collection of images. The TodoStore in our [TodoMVC example](https://github.com/facebook/flux/tree/master/examples/flux-todomvc) is similar in that it manages a collection of to-do items. A store exhibits characteristics of both a collection of models and a singleton model of a logical domain.
55
56
-As mentioned above, a store registers itself with the dispatcher and provides it with a callback. This callback receives the action's data payload as a parameter. The payload contains a type attribute, identifying the action's type. Within the store's registered callback, a switch statement based on the action's type is used to interpret the payload and to provide the proper hooks into the store's internal methods. This allows an action to result in an update to the state of the store, via the dispatcher. After the stores are updated, they broadcast an event declaring that their state has changed, so the views may query the new state and update themselves.
56
+As mentioned above, a store registers itself with the dispatcher and provides it with a callback. This callback receives the action's data payload as a parameter. The payload contains a type attribute, identifying the action's type. Within the store's registered callback, a switch statement based on the action's type is used to interpret the payload and to provide the proper hooks into the store's internal methods. This allows an action to result in an update to the state of the store, via the dispatcher. After the stores are updated, they broadcast an event declaring that their state has changed, so the views may query the new state and update themselves.
57
58
59
-### Views and Controller-Views
60
-
59
+### Views and Controller-Views
60
+
61
React provides the kind of composable views we need for the view layer. Close to the top of the nested view hierarchy, a special kind of view listens for events that are broadcast by the stores that it depends on. One could call this a controller-view, as it provides the glue code to get the data from the stores and to pass this data down the chain of its descendants. We might have one of these controller-views governing any significant section of the page.
62
63
-When it receives the event from the store, it first requests the new data it needs via the stores' public getter methods. It then calls its own `setState()` or `forceUpdate()` methods, causing its `render()` method and the `render()` method of all its descendants to run.
63
+When it receives the event from the store, it first requests the new data it needs via the stores' public getter methods. It then calls its own `setState()` or `forceUpdate()` methods, causing its `render()` method and the `render()` method of all its descendants to run.
64
65
-We often pass the entire state of the store down the chain of views in a single object, allowing different descendants to use what they need. In addition to keeping the controller-like behavior at the top of the hierarchy, and thus keeping our descendant views as functionally pure as possible, passing down the entire state of the store in a single object also has the effect of reducing the number of props we need to manage.
65
+We often pass the entire state of the store down the chain of views in a single object, allowing different descendants to use what they need. In addition to keeping the controller-like behavior at the top of the hierarchy, and thus keeping our descendant views as functionally pure as possible, passing down the entire state of the store in a single object also has the effect of reducing the number of props we need to manage.
66
67
Occasionally we may need to add additional controller-views deeper in the hierarchy to keep components simple. This might help us to better encapsulate a section of the hierarchy related to a specific data domain. Be aware, however, that controller-views deeper in the hierarchy can violate the singular flow of data by introducing a new, potentially conflicting entry point for the data flow. In making the decision of whether to add a deep controller-view, balance the gain of simpler components against the complexity of multiple data updates flowing into the hierarchy at different points. These multiple data updates can lead to odd effects, with React's render method getting invoked repeatedly by updates from different controller-views, potentially increasing the difficulty of debugging.
68
69
70
-### Actions
70
+### Actions
71
72
-The dispatcher exposes a method that allows a view to trigger a dispatch to the stores, and to include a payload of data, or an action. The action construction may be wrapped into a semantic helper method which sends the payload to the dispatcher. For example, we may want to change the text of a to-do item in a to-do list application. We would create an action with a function signature like `updateText(todoId, newText)` in our `TodoActions` module. This method may be invoked from within our views' event handlers, so we can call it in response to a user action. This action method also adds the action type to the payload, so that when the payload is interpreted in the store, it can respond appropriately to a payload with a particular action type. In our example, this type might be named something like `TODO_UPDATE_TEXT`.
72
+The dispatcher exposes a method that allows a view to trigger a dispatch to the stores, and to include a payload of data, or an action. The action construction may be wrapped into a semantic helper method which sends the payload to the dispatcher. For example, we may want to change the text of a to-do item in a to-do list application. We would create an action with a function signature like `updateText(todoId, newText)` in our `TodoActions` module. This method may be invoked from within our views' event handlers, so we can call it in response to a user action. This action method also adds the action type to the payload, so that when the payload is interpreted in the store, it can respond appropriately to a payload with a particular action type. In our example, this type might be named something like `TODO_UPDATE_TEXT`.
73
74
Actions may also come from other places, such as the server. This happens, for example, during data initialization. It may also happen when the server returns an error code or when the server has updates to provide to the application. We'll talk more about server actions in a future article. In this post we're only concerned with the basics of the data flow.
75
76
77
-### What About that Dispatcher?
77
+### What About that Dispatcher?
78
79
-As mentioned earlier, the dispatcher is also able to manage dependencies between stores. This functionality is available through the `waitFor()` method within the Dispatcher class. We did not need to use this method within the extremely simple [TodoMVC application](https://github.com/facebook/react/tree/master/examples/todomvc-flux), but we have included it [in the example dispatcher](https://github.com/facebook/react/blob/master/examples/todomvc-flux/js/dispatcher/Dispatcher.js#L110) as an example of what a dispatcher should be able to do in a larger, more complex application.
79
+As mentioned earlier, the dispatcher is also able to manage dependencies between stores. This functionality is available through the `waitFor()` method within the Dispatcher class. We did not need to use this method within the extremely simple [TodoMVC application](https://github.com/facebook/flux/tree/master/examples/flux-todomvc), but we have included it [in the example dispatcher](https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/dispatcher/Dispatcher.js#L110) as an example of what a dispatcher should be able to do in a larger, more complex application.
80
81
-Within the TodoStore's registered callback we could explicitly wait for any dependencies to first update before moving forward:
81
+Within the TodoStore's registered callback we could explicitly wait for any dependencies to first update before moving forward:
82
83
```javascript
84
-case 'TODO_CREATE':
85
- Dispatcher.waitFor([
86
- PrependedTextStore.dispatcherIndex,
87
- YetAnotherStore.dispatcherIndex
88
- ], function() {
89
- TodoStore.create(PrependedTextStore.getText() + ' ' + action.text);
90
- TodoStore.emit('change');
91
- });
92
- break;
84
+case 'TODO_CREATE':
85
+ Dispatcher.waitFor([
86
+ PrependedTextStore.dispatcherIndex,
87
+ YetAnotherStore.dispatcherIndex
88
+ ], function() {
89
+ TodoStore.create(PrependedTextStore.getText() + ' ' + action.text);
90
+ TodoStore.emit('change');
91
+ });
92
+ break;
93
```
94
95
The arguments for `waitFor()` are an array of dispatcher registry indexes, and a final callback to invoke after the callbacks at the given indexes have completed. Thus the store that is invoking `waitFor()` can depend on the state of another store to inform how it should update its own state.
docs/docs/flux-todo-list.md
+396
-396
@@ -5,54 +5,54 @@ layout: docs
5
prev: flux-overview.html
6
---
7
8
-To demonstrate the Flux architecture with some example code, let's take on the classic TodoMVC application. The entire application is available in the React GitHub repo within the [todomvc-flux](https://github.com/facebook/react/tree/master/examples/todomvc-flux) example directory, but let's walk through the development of it a step at a time.
8
+To demonstrate the Flux architecture with some example code, let's take on the classic TodoMVC application. The entire application is available in the Flux GitHub repo within the [flux-todomvc](https://github.com/facebook/flux/tree/master/examples/flux-todomvc) example directory, but let's walk through the development of it a step at a time.
9
10
To begin, we'll need some boilerplate and get up and running with a module system. Node's module system, based on CommonJS, will fit the bill very nicely and we can build off of [react-boilerplate](https://github.com/petehunt/react-boilerplate) to get up and running quickly. Assuming you have npm installed, simply clone the react-boilerplate code from GitHub, and navigate into the resulting directory in Terminal (or whatever CLI application you like). Next run the npm scripts to get up and running: `npm install`, then `npm run build`, and lastly `npm start` to continuously build using Browserify.
11
12
-The TodoMVC example has all this built into it as well, but if you're starting with react-boilerplate make sure you edit your package.json file to match the file structure and dependencies described in the TodoMVC example's [package.json](https://github.com/facebook/react/tree/master/examples/todomvc-flux/package.json), or else your code won't match up with the explanations below.
12
+The TodoMVC example has all this built into it as well, but if you're starting with react-boilerplate make sure you edit your package.json file to match the file structure and dependencies described in the TodoMVC example's [package.json](https://github.com/facebook/flux/tree/master/examples/flux-todomvc/package.json), or else your code won't match up with the explanations below.
13
14
15
Source Code Structure
16
----------------------
17
-The resulting index.js file may be used as the entry point into our app, but we'll put most of our code in a 'js' directory. Let's let Browserify do its thing, and now we'll open a new tab in Terminal (or a GUI file browser) to look at the directory. It should look something like this:
16
+---------------------
17
+The resulting index.js file may be used as the entry point into our app, but we'll put most of our code in a 'js' directory. Let's let Browserify do its thing, and now we'll open a new tab in Terminal (or a GUI file browser) to look at the directory. It should look something like this:
18
19
-```
20
-myapp
21
- |
22
- + ...
19
+```
20
+myapp
21
+ |
22
+ + ...
23
+ js
24
|
25
+ app.js
26
- + bundle.js // generated by Browserify whenever we make changes.
27
- + index.html
28
- + ...
29
-```
30
-
31
-Next we'll dive into the js directory, and layout our application's primary directory structure:
32
-
33
-```
34
-myapp
35
- |
36
- + ...
37
- + js
38
- |
39
- + actions
26
+ + bundle.js // generated by Browserify whenever we make changes.
27
+ + index.html
28
+ + ...
29
+```
30
+
31
+Next we'll dive into the js directory, and layout our application's primary directory structure:
32
+
33
+```
34
+myapp
35
+ |
36
+ + ...
37
+ + js
38
+ |
39
+ + actions
40
+ components // all React components, both views and controller-views
41
+ constants
42
- + dispatcher
43
- + stores
42
+ + dispatcher
43
+ + stores
44
+ app.js
45
+ bundle.js
46
+ index.html
47
+ ...
48
-```
48
+```
49
50
-Creating a Dispatcher
51
----------------------
50
+Creating a Dispatcher
51
+---------------------
52
53
-Now we are ready to create a dispatcher. Here is an naive example of a Dispatcher class, written with JavaScript promises, polyfilled with Jake Archibald's [ES6-Promises](https://github.com/jakearchibald/ES6-Promises) module.
53
+Now we are ready to create a dispatcher. Here is an naive example of a Dispatcher class, written with JavaScript promises, polyfilled with Jake Archibald's [ES6-Promises](https://github.com/jakearchibald/ES6-Promises) module.
54
55
-```javascript
55
+```javascript
56
var Promise = require('es6-promise').Promise;
57
var merge = require('react/lib/merge');
58
@@ -98,16 +98,16 @@ Dispatcher.prototype = merge(Dispatcher.prototype, {
98
});
99
_promises = [];
100
}
101
-});
102
-
103
-module.exports = Dispatcher;
104
-```
101
+});
102
106
-The public API of this basic Dispatcher consists of only two methods: register() and dispatch(). We'll use register() within our stores to register each store's callback. We'll use dispatch() within our actions to trigger the invocation of the callbacks.
103
+module.exports = Dispatcher;
104
+```
105
108
-Now we are all set to create a dispatcher that is more specific to our app, which we'll call AppDispatcher.
106
+The public API of this basic Dispatcher consists of only two methods: register() and dispatch(). We'll use register() within our stores to register each store's callback. We'll use dispatch() within our actions to trigger the invocation of the callbacks.
107
110
-```javascript
108
+Now we are all set to create a dispatcher that is more specific to our app, which we'll call AppDispatcher.
109
+
110
+```javascript
111
var Dispatcher = require('./Dispatcher');
112
113
var merge = require('react/lib/merge');
@@ -129,144 +129,144 @@ var AppDispatcher = merge(Dispatcher.prototype, {
129
});
130
131
module.exports = AppDispatcher;
132
-```
132
+```
133
+
134
+Now we've created an implementation that is a bit more specific to our needs, with a helper function we can use in the actions coming from our views' event handlers. We might expand on this later to provide a separate helper for server updates, but for now this is all we need.
135
+
136
+
137
+Creating Stores
138
+----------------
139
134
-Now we've created an implementation that is a bit more specific to our needs, with a helper function we can use in the actions coming from our views' event handlers. We might expand on this later to provide a separate helper for server updates, but for now this is all we need.
140
+We can use Node's EventEmitter to get started with a store. We need EventEmitter to broadcast the 'change' event to our controller-views. So let's take a look at what that looks like. I've omitted some of the code for the sake of brevity, but for the full version see [TodoStore.js](https://github.com/Facebook/flux/blob/master/examples/flux-todomvc/js/stores/TodoStore.js) in the TodoMVC example code.
141
+
142
+```javascript
143
+var AppDispatcher = require('../dispatcher/AppDispatcher');
144
+var EventEmitter = require('events').EventEmitter;
145
+var TodoConstants = require('../constants/TodoConstants');
146
+var merge = require('react/lib/merge');
147
148
+var CHANGE_EVENT = 'change';
149
137
-Creating Stores
138
-----------------
150
+var _todos = {}; // collection of todo items
151
140
-We can use Node's EventEmitter to get started with a store. We need EventEmitter to broadcast the 'change' event to our controller-views. So let's take a look at what that looks like. I've omitted some of the code for the sake of brevity, but for the full version see [TodoStore.js](https://github.com/Facebook/react/blob/master/examples/todomvc-flux/js/stores/TodoStore.js) in the TodoMVC example code.
152
+/**
153
+ * Create a TODO item.
154
+ * @param {string} text The content of the TODO
155
+ */
156
+function create(text) {
157
+ // Using the current timestamp in place of a real id.
158
+ var id = Date.now();
159
+ _todos[id] = {
160
+ id: id,
161
+ complete: false,
162
+ text: text
163
+ };
164
+}
165
142
-```javascript
143
-var AppDispatcher = require('../dispatcher/AppDispatcher');
144
-var EventEmitter = require('events').EventEmitter;
145
-var TodoConstants = require('../constants/TodoConstants');
146
-var merge = require('react/lib/merge');
147
-
148
-var CHANGE_EVENT = 'change';
149
-
150
-var _todos = {}; // collection of todo items
151
-
166
/**
153
- * Create a TODO item.
154
- * @param {string} text The content of the TODO
155
- */
156
-function create(text) {
157
- // Using the current timestamp in place of a real id.
158
- var id = Date.now();
159
- _todos[id] = {
160
- id: id,
161
- complete: false,
162
- text: text
163
- };
164
-}
165
-
166
-/**
167
- * Delete a TODO item.
168
- * @param {string} id
169
- */
170
-function destroy(id) {
171
- delete _todos[id];
172
-}
173
-
174
-var TodoStore = merge(EventEmitter.prototype, {
175
-
176
- /**
177
- * Get the entire collection of TODOs.
178
- * @return {object}
179
- */
180
- getAll: function() {
181
- return _todos;
182
- },
183
-
184
- emitChange: function() {
185
- this.emit(CHANGE_EVENT);
186
- },
187
-
188
- /**
189
- * @param {function} callback
190
- */
191
- addChangeListener: function(callback) {
192
- this.on(CHANGE_EVENT, callback);
193
- },
194
-
195
- /**
196
- * @param {function} callback
197
- */
198
- removeChangeListener: function(callback) {
199
- this.removeListener(CHANGE_EVENT, callback);
200
- }
201
-
202
- dispatcherIndex: AppDispatcher.register(function(payload) {
203
- var action = payload.action;
204
- var text;
205
-
206
- switch(action.actionType) {
207
- case TodoConstants.TODO_CREATE:
208
- text = action.text.trim();
209
- if (text !== '') {
210
- create(text);
211
- TodoStore.emitChange();
212
- }
213
- break;
214
-
215
- case TodoConstants.TODO_DESTROY:
216
- destroy(action.id);
217
- TodoStore.emitChange();
218
- break;
219
-
220
- // add more cases for other actionTypes, like TODO_UPDATE, etc.
167
+ * Delete a TODO item.
168
+ * @param {string} id
169
+ */
170
+function destroy(id) {
171
+ delete _todos[id];
172
+}
173
+
174
+var TodoStore = merge(EventEmitter.prototype, {
175
+
176
+ /**
177
+ * Get the entire collection of TODOs.
178
+ * @return {object}
179
+ */
180
+ getAll: function() {
181
+ return _todos;
182
+ },
183
+
184
+ emitChange: function() {
185
+ this.emit(CHANGE_EVENT);
186
+ },
187
+
188
+ /**
189
+ * @param {function} callback
190
+ */
191
+ addChangeListener: function(callback) {
192
+ this.on(CHANGE_EVENT, callback);
193
+ },
194
+
195
+ /**
196
+ * @param {function} callback
197
+ */
198
+ removeChangeListener: function(callback) {
199
+ this.removeListener(CHANGE_EVENT, callback);
200
+ }
201
+
202
+ dispatcherIndex: AppDispatcher.register(function(payload) {
203
+ var action = payload.action;
204
+ var text;
205
+
206
+ switch(action.actionType) {
207
+ case TodoConstants.TODO_CREATE:
208
+ text = action.text.trim();
209
+ if (text !== '') {
210
+ create(text);
211
+ TodoStore.emitChange();
212
+ }
213
+ break;
214
+
215
+ case TodoConstants.TODO_DESTROY:
216
+ destroy(action.id);
217
+ TodoStore.emitChange();
218
+ break;
219
+
220
+ // add more cases for other actionTypes, like TODO_UPDATE, etc.
221
}
222
-
223
- return true; // No errors. Needed by promise in Dispatcher.
224
- })
225
-
226
-});
227
-
228
-module.exports = TodoStore;
229
-```
230
-
231
-There are a few important things to note in the above code. To start, we are maintaining a private data structure called _todos. This object contains all the individual to-do items. Because this variable lives outside the class, but within the closure of the module, it remains private — it cannot be directly changed from the outside. This helps us preserve a distinct input/output interface for the flow of data by making it impossible to update the store without using an action.
232
-
233
-Another important part is the registration of the store's callback with the dispatcher. We pass in our payload handling callback to the dispatcher and preserve the index that this store has in the dispatcher's registry. The callback function currently only handles two actionTypes, but later we can add as many as we need.
234
-
235
-
236
-Listening to Changes with a Controller-View
237
--------------------------------------------
238
-
239
-We need a React component near the top of our component hierarchy to listen for changes in the store. In a larger app, we would have more of these listening components, perhaps one for every section of the page. In Facebook's Ads Creation Tool, we have many of these controller-like views, each governing a specific section of the UI. In the Lookback Video Editor, we only had two: one for the animated preview and one for the image selection interface. Here's one for our TodoMVC example. Again, this is slightly abbreviated, but for the full code you can take a look at the TodoMVC example's [TodoApp.react.js](https://github.com/facebook/react/blob/master/examples/todomvc-flux/js/components/TodoApp.react.js)
240
-
222
+
223
+ return true; // No errors. Needed by promise in Dispatcher.
224
+ })
225
+
226
+});
227
+
228
+module.exports = TodoStore;
229
+```
230
+
231
+There are a few important things to note in the above code. To start, we are maintaining a private data structure called _todos. This object contains all the individual to-do items. Because this variable lives outside the class, but within the closure of the module, it remains private — it cannot be directly changed from the outside. This helps us preserve a distinct input/output interface for the flow of data by making it impossible to update the store without using an action.
232
+
233
+Another important part is the registration of the store's callback with the dispatcher. We pass in our payload handling callback to the dispatcher and preserve the index that this store has in the dispatcher's registry. The callback function currently only handles two actionTypes, but later we can add as many as we need.
234
+
235
+
236
+Listening to Changes with a Controller-View
237
+-------------------------------------------
238
+
239
+We need a React component near the top of our component hierarchy to listen for changes in the store. In a larger app, we would have more of these listening components, perhaps one for every section of the page. In Facebook's Ads Creation Tool, we have many of these controller-like views, each governing a specific section of the UI. In the Lookback Video Editor, we only had two: one for the animated preview and one for the image selection interface. Here's one for our TodoMVC example. Again, this is slightly abbreviated, but for the full code you can take a look at the TodoMVC example's [TodoApp.react.js](https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/components/TodoApp.react.js)
240
+
241
```javascript
242
-/** @jsx React.DOM */
243
-
244
-var Footer = require('./Footer.react');
245
-var Header = require('./Header.react');
246
-var MainSection = require('./MainSection.react');
247
-var React = require('react');
248
-var TodoStore = require('../stores/TodoStore');
249
-
250
-function getTodoState() {
251
- return {
252
- allTodos: TodoStore.getAll()
253
- };
254
-}
255
-
256
-var TodoApp = React.createClass({
257
-
258
- getInitialState: function() {
259
- return getTodoState();
260
- },
261
-
262
- componentDidMount: function() {
263
- TodoStore.addChangeListener(this._onChange);
264
- },
265
-
266
- componentWillUnmount: function() {
267
- TodoStore.removeChangeListener(this._onChange);
268
- },
269
-
242
+/** @jsx React.DOM */
243
+
244
+var Footer = require('./Footer.react');
245
+var Header = require('./Header.react');
246
+var MainSection = require('./MainSection.react');
247
+var React = require('react');
248
+var TodoStore = require('../stores/TodoStore');
249
+
250
+function getTodoState() {
251
+ return {
252
+ allTodos: TodoStore.getAll()
253
+ };
254
+}
255
+
256
+var TodoApp = React.createClass({
257
+
258
+ getInitialState: function() {
259
+ return getTodoState();
260
+ },
261
+
262
+ componentDidMount: function() {
263
+ TodoStore.addChangeListener(this._onChange);
264
+ },
265
+
266
+ componentWillUnmount: function() {
267
+ TodoStore.removeChangeListener(this._onChange);
268
+ },
269
+
270
/**
271
* @return {object}
272
*/
@@ -281,189 +281,189 @@ var TodoApp = React.createClass({
281
<Footer allTodos={this.state.allTodos} />
282
</div>
283
);
284
- },
285
-
286
- _onChange: function() {
287
- this.setState(getTodoState());
288
- }
289
-
290
-});
291
-
292
-module.exports = TodoApp;
293
-```
284
+ },
285
295
-Now we're in our familiar React territory, utilizing React's lifecycle methods. We set up the initial state of this controller-view in getInitialState(), register an event listener in componentDidMount(), and then clean up after ourselves within componentWillUnmount(). We render a containing div and pass down the collection of states we got from the TodoStore.
286
+ _onChange: function() {
287
+ this.setState(getTodoState());
288
+ }
289
+
290
+});
291
297
-The Header component contains the primary text input for the application, but it does not need to know the state of the store. The MainSection and Footer do need this data, so we pass it down to them.
292
+module.exports = TodoApp;
293
+```
294
299
-More Views
300
-----------
301
-At a high level, the React component hierarchy of the app looks like this:
295
+Now we're in our familiar React territory, utilizing React's lifecycle methods. We set up the initial state of this controller-view in getInitialState(), register an event listener in componentDidMount(), and then clean up after ourselves within componentWillUnmount(). We render a containing div and pass down the collection of states we got from the TodoStore.
296
+
297
+The Header component contains the primary text input for the application, but it does not need to know the state of the store. The MainSection and Footer do need this data, so we pass it down to them.
298
+
299
+More Views
300
+----------
301
+At a high level, the React component hierarchy of the app looks like this:
302
303
```javascript
304
-<TodoApp>
305
- <Header>
306
- <TodoTextInput />
307
-
308
- <MainSection>
309
- <ul>
310
- <TodoItem />
311
- </ul>
312
- </MainSection>
313
-
314
-</TodoApp>
315
-```
316
-If a TodoItem is in edit mode, it will also render a TodoTextInput as a child. Let's take a look at how some of these components display the data they receive as props, and how they communicate through actions with the dispatcher.
317
-The MainSection needs to iterate over the collection of to-do items it received from TodoApp to create the list of TodoItems. In the component's render() method, we can do that iteration like so:
304
+<TodoApp>
305
+ <Header>
306
+ <TodoTextInput />
307
+
308
+ <MainSection>
309
+ <ul>
310
+ <TodoItem />
311
+ </ul>
312
+ </MainSection>
313
+
314
+</TodoApp>
315
+```
316
+If a TodoItem is in edit mode, it will also render a TodoTextInput as a child. Let's take a look at how some of these components display the data they receive as props, and how they communicate through actions with the dispatcher.
317
+The MainSection needs to iterate over the collection of to-do items it received from TodoApp to create the list of TodoItems. In the component's render() method, we can do that iteration like so:
318
319
```javascript
320
-var allTodos = this.props.allTodos;
321
-
322
-for (var key in allTodos) {
323
- todos.push(<TodoItem key={key} todo={allTodos[key]} />);
324
-}
325
-
326
-return (
327
- <section id="main">
328
- <ul id="todo-list">{todos}</ul>
320
+var allTodos = this.props.allTodos;
321
+
322
+for (var key in allTodos) {
323
+ todos.push(<TodoItem key={key} todo={allTodos[key]} />);
324
+}
325
+
326
+return (
327
+ <section id="main">
328
+ <ul id="todo-list">{todos}</ul>
329
</section>
330
-);
331
-```
332
-Now each TodoItem can display its own text, and perform actions utilizing its own ID. Explaining all the different actions that a TodoItem can invoke in the TodoMVC example goes beyond the scope of this article, but let's just take a look at the action that deletes one of the to-do items. Here is an abbreviated version of the TodoItem:
330
+);
331
+```
332
+Now each TodoItem can display its own text, and perform actions utilizing its own ID. Explaining all the different actions that a TodoItem can invoke in the TodoMVC example goes beyond the scope of this article, but let's just take a look at the action that deletes one of the to-do items. Here is an abbreviated version of the TodoItem:
333
334
```javascript
335
-/** @jsx React.DOM */
336
-
337
-var React = require('react');
338
-var TodoActions = require('../actions/TodoActions');
339
-var TodoTextInput = require('./TodoTextInput.react');
340
-
341
-var TodoItem = React.createClass({
342
-
343
- propTypes: {
344
- todo: React.PropTypes.object.isRequired
345
- },
346
-
347
- render: function() {
348
- var todo = this.props.todo;
349
-
350
- return (
351
- <li
352
- key={todo.id}>
353
- <label>
354
- {todo.text}
355
- </label>
356
- <button className="destroy" onClick={this._onDestroyClick} />
357
- </li>
358
- );
359
- },
360
-
361
- _onDestroyClick: function() {
362
- TodoActions.destroy(this.props.todo.id);
363
- }
364
-
365
-});
366
-
367
-module.exports = TodoItem;
368
-```
369
-
370
-With a destroy action available in our library of TodoActions, and a store ready to handle it, connecting the user's interaction with application state changes could not be simpler. We just wrap our onClick handler around the destroy action, provide it with the id, and we're done. Now the user can click the destroy button and kick off the Flux cycle to update the rest of the application.
371
-
372
-Text input, on the other hand, is just a bit more complicated because we need to hang on to the state of the text input within the React component itself. Let's take a look at how TodoTextInput works.
373
-
374
-As you'll see below, with every change to the input, React expects us to update the state of the component. So when we are finally ready to save the text inside the input, we will put the value held in the component's state in the action's payload. This is UI state, rather than application state, and keeping that distinction in mind is a good guide for where state should live. All application state should live in the store, while components occasionally hold on to UI state. Ideally, React components preserve as little state as possible.
375
-
376
-Because TodoTextInput is being used in multiple places within our application, with different behaviors, we'll need to pass the onSave method in as a prop from the component's parent. This allows onSave to invoke different actions depending on where it is used.
335
+/** @jsx React.DOM */
336
+
337
+var React = require('react');
338
+var TodoActions = require('../actions/TodoActions');
339
+var TodoTextInput = require('./TodoTextInput.react');
340
+
341
+var TodoItem = React.createClass({
342
+
343
+ propTypes: {
344
+ todo: React.PropTypes.object.isRequired
345
+ },
346
+
347
+ render: function() {
348
+ var todo = this.props.todo;
349
+
350
+ return (
351
+ <li
352
+ key={todo.id}>
353
+ <label>
354
+ {todo.text}
355
+ </label>
356
+ <button className="destroy" onClick={this._onDestroyClick} />
357
+ </li>
358
+ );
359
+ },
360
+
361
+ _onDestroyClick: function() {
362
+ TodoActions.destroy(this.props.todo.id);
363
+ }
364
+
365
+});
366
+
367
+module.exports = TodoItem;
368
+```
369
+
370
+With a destroy action available in our library of TodoActions, and a store ready to handle it, connecting the user's interaction with application state changes could not be simpler. We just wrap our onClick handler around the destroy action, provide it with the id, and we're done. Now the user can click the destroy button and kick off the Flux cycle to update the rest of the application.
371
+
372
+Text input, on the other hand, is just a bit more complicated because we need to hang on to the state of the text input within the React component itself. Let's take a look at how TodoTextInput works.
373
+
374
+As you'll see below, with every change to the input, React expects us to update the state of the component. So when we are finally ready to save the text inside the input, we will put the value held in the component's state in the action's payload. This is UI state, rather than application state, and keeping that distinction in mind is a good guide for where state should live. All application state should live in the store, while components occasionally hold on to UI state. Ideally, React components preserve as little state as possible.
375
+
376
+Because TodoTextInput is being used in multiple places within our application, with different behaviors, we'll need to pass the onSave method in as a prop from the component's parent. This allows onSave to invoke different actions depending on where it is used.
377
378
```javascript
379
-/** @jsx React.DOM */
380
-
381
-var React = require('react');
382
-var ReactPropTypes = React.PropTypes;
383
-
384
-var ENTER_KEY_CODE = 13;
385
-
386
-var TodoTextInput = React.createClass({
387
-
388
- propTypes: {
389
- className: ReactPropTypes.string,
390
- id: ReactPropTypes.string,
391
- placeholder: ReactPropTypes.string,
392
- onSave: ReactPropTypes.func.isRequired,
393
- value: ReactPropTypes.string
394
- },
395
-
396
- getInitialState: function() {
397
- return {
398
- value: this.props.value || ''
399
- };
379
+/** @jsx React.DOM */
380
+
381
+var React = require('react');
382
+var ReactPropTypes = React.PropTypes;
383
+
384
+var ENTER_KEY_CODE = 13;
385
+
386
+var TodoTextInput = React.createClass({
387
+
388
+ propTypes: {
389
+ className: ReactPropTypes.string,
390
+ id: ReactPropTypes.string,
391
+ placeholder: ReactPropTypes.string,
392
+ onSave: ReactPropTypes.func.isRequired,
393
+ value: ReactPropTypes.string
394
+ },
395
+
396
+ getInitialState: function() {
397
+ return {
398
+ value: this.props.value || ''
399
+ };
400
},
401
-
402
- /**
403
- * @return {object}
404
- */
405
- render: function() /*object*/ {
406
- return (
407
- <input
408
- className={this.props.className}
409
- id={this.props.id}
410
- placeholder={this.props.placeholder}
411
- onBlur={this._save}
412
- onChange={this._onChange}
413
- onKeyDown={this._onKeyDown}
414
- value={this.state.value}
415
- autoFocus={true}
416
- />
417
- );
418
- },
419
-
420
- /**
421
- * Invokes the callback passed in as onSave, allowing this component to be
422
- * used in different ways.
423
- */
424
- _save: function() {
425
- this.props.onSave(this.state.value);
426
- this.setState({
427
- value: ''
428
- });
429
- },
430
-
431
- /**
432
- * @param {object} event
433
- */
434
- _onChange: function(/*object*/ event) {
435
- this.setState({
436
- value: event.target.value
437
- });
438
- },
439
-
440
- /**
441
- * @param {object} event
442
- */
443
-
444
- _onKeyDown: function(event) {
445
- if (event.keyCode === ENTER_KEY_CODE) {
446
- this._save();
447
- }
448
- }
449
-
450
-});
451
-
452
-module.exports = TodoTextInput;
453
-```
454
-
455
-The Header passes in the onSave method as a prop to allow the TodoTextInput to create new
456
-to-do items:
401
+
402
+ /**
403
+ * @return {object}
404
+ */
405
+ render: function() /*object*/ {
406
+ return (
407
+ <input
408
+ className={this.props.className}
409
+ id={this.props.id}
410
+ placeholder={this.props.placeholder}
411
+ onBlur={this._save}
412
+ onChange={this._onChange}
413
+ onKeyDown={this._onKeyDown}
414
+ value={this.state.value}
415
+ autoFocus={true}
416
+ />
417
+ );
418
+ },
419
+
420
+ /**
421
+ * Invokes the callback passed in as onSave, allowing this component to be
422
+ * used in different ways.
423
+ */
424
+ _save: function() {
425
+ this.props.onSave(this.state.value);
426
+ this.setState({
427
+ value: ''
428
+ });
429
+ },
430
+
431
+ /**
432
+ * @param {object} event
433
+ */
434
+ _onChange: function(/*object*/ event) {
435
+ this.setState({
436
+ value: event.target.value
437
+ });
438
+ },
439
+
440
+ /**
441
+ * @param {object} event
442
+ */
443
+
444
+ _onKeyDown: function(event) {
445
+ if (event.keyCode === ENTER_KEY_CODE) {
446
+ this._save();
447
+ }
448
+ }
449
+
450
+});
451
+
452
+module.exports = TodoTextInput;
453
+```
454
+
455
+The Header passes in the onSave method as a prop to allow the TodoTextInput to create new
456
+to-do items:
457
458
```javascript
459
-/** @jsx React.DOM */
460
-
461
-var React = require('react');
462
-var TodoActions = require('../actions/TodoActions');
463
-var TodoTextInput = require('./TodoTextInput.react');
464
-
465
-var Header = React.createClass({
466
-
459
+/** @jsx React.DOM */
460
+
461
+var React = require('react');
462
+var TodoActions = require('../actions/TodoActions');
463
+var TodoTextInput = require('./TodoTextInput.react');
464
+
465
+var Header = React.createClass({
466
+
467
/**
468
* @return {object}
469
*/
@@ -478,8 +478,8 @@ var Header = React.createClass({
478
/>
479
</header>
480
);
481
- },
482
-
481
+ },
482
+
483
/**
484
* Event handler called within TodoTextInput.
485
* Defining this here allows TodoTextInput to be used in multiple places
@@ -488,26 +488,26 @@ var Header = React.createClass({
488
*/
489
_onSave: function(text) {
490
TodoActions.create(text);
491
- }
492
-
493
-});
494
-
495
-module.exports = Header;
496
-```
491
+ }
492
498
-In a different context, such as in editing mode for an existing to-do item, we might pass an onSave callback that invokes `TodoActions.update(text)` instead.
493
+});
494
495
+module.exports = Header;
496
+```
497
501
-Creating Semantic Actions
502
--------------------------
498
+In a different context, such as in editing mode for an existing to-do item, we might pass an onSave callback that invokes `TodoActions.update(text)` instead.
499
504
-Here is the basic code for the two actions we used above in our views:
500
506
-```javascript
507
-/**
508
- * TodoActions
509
- */
510
-
501
+Creating Semantic Actions
502
+-------------------------
503
+
504
+Here is the basic code for the two actions we used above in our views:
505
+
506
+```javascript
507
+/**
508
+ * TodoActions
509
+ */
510
+
511
var AppDispatcher = require('../dispatcher/AppDispatcher');
512
var TodoConstants = require('../constants/TodoConstants');
513
@@ -521,8 +521,8 @@ var TodoActions = {
521
actionType: TodoConstants.TODO_CREATE,
522
text: text
523
});
524
- },
525
-
524
+ },
525
+
526
/**
527
* @param {string} id
528
*/
@@ -531,51 +531,51 @@ var TodoActions = {
531
actionType: TodoConstants.TODO_DESTROY,
532
id: id
533
});
534
- },
535
-
536
-};
537
-
538
-module.exports = TodoActions;
539
-```
540
-
541
-As you can see, we really would not need to have the helpers AppDispatcher.handleViewAction() or TodoActions.create(). We could, in theory, call AppDispatcher.dispatch() directly and provide a payload. But as our application grows, having these helpers keeps the code clean and semantic. It's just a lot cleaner to write TodoActions.destroy(id) instead of writing a whole lot of things that our TodoItem shouldn't have to know about.
542
-
543
-The payload produced by the TodoActions.create() will look like:
544
-
545
-```javascript
546
-{
547
- source: 'VIEW_ACTION',
548
- action: {
549
- type: 'TODO_CREATE',
550
- text: 'Write blog post about Flux'
551
- }
552
-}
553
-```
554
-
555
-This payload is provided to the TodoStore through its registered callback. The TodoStore then broadcasts the 'change' event, and the MainSection responds by fetching the new collection of to-do items from the TodoStore and changing its state. This change in state causes the TodoApp component to call its own render() method, and the render() method of all of its descendents.
556
-
557
-Start Me Up
534
+ },
535
+
536
+};
537
+
538
+module.exports = TodoActions;
539
+```
540
+
541
+As you can see, we really would not need to have the helpers AppDispatcher.handleViewAction() or TodoActions.create(). We could, in theory, call AppDispatcher.dispatch() directly and provide a payload. But as our application grows, having these helpers keeps the code clean and semantic. It's just a lot cleaner to write TodoActions.destroy(id) instead of writing a whole lot of things that our TodoItem shouldn't have to know about.
542
+
543
+The payload produced by the TodoActions.create() will look like:
544
+
545
+```javascript
546
+{
547
+ source: 'VIEW_ACTION',
548
+ action: {
549
+ type: 'TODO_CREATE',
550
+ text: 'Write blog post about Flux'
551
+ }
552
+}
553
+```
554
+
555
+This payload is provided to the TodoStore through its registered callback. The TodoStore then broadcasts the 'change' event, and the MainSection responds by fetching the new collection of to-do items from the TodoStore and changing its state. This change in state causes the TodoApp component to call its own render() method, and the render() method of all of its descendents.
556
+
557
+Start Me Up
558
-----------
559
560
-The bootstrap file of our application is app.js. It simply takes the TodoApp component and renders it in the root element of the application.
561
-
560
+The bootstrap file of our application is app.js. It simply takes the TodoApp component and renders it in the root element of the application.
561
+
562
```javascript
563
-/** @jsx React.DOM */
564
-
565
-var React = require('react');
566
-
567
-var TodoApp = require('./components/TodoApp.react');
568
-
569
-React.renderComponent(
570
- <TodoApp />,
571
- document.getElementById('todoapp')
572
-);
573
-```
574
-
575
-Adding Dependency Management to the Dispatcher
563
+/** @jsx React.DOM */
564
+
565
+var React = require('react');
566
+
567
+var TodoApp = require('./components/TodoApp.react');
568
+
569
+React.renderComponent(
570
+ <TodoApp />,
571
+ document.getElementById('todoapp')
572
+);
573
+```
574
+
575
+Adding Dependency Management to the Dispatcher
576
----------------------------------------------
577
578
-As I said previously, our Dispatcher implementation is a bit naive. It's pretty good, but it will not suffice for most applications. We need a way to be able to manage dependencies between Stores. Let's add that functionality with a waitFor() method within the main body of the Dispatcher class.
578
+As I said previously, our Dispatcher implementation is a bit naive. It's pretty good, but it will not suffice for most applications. We need a way to be able to manage dependencies between Stores. Let's add that functionality with a waitFor() method within the main body of the Dispatcher class.
579
580
We'll need another public method, waitFor(). Note that it returns a Promise that can in turn be returned from the Store callback.
581
@@ -590,12 +590,12 @@ We'll need another public method, waitFor(). Note that it returns a Promise that
590
});
591
return Promise.all(selectedPromises).then(callback);
592
}
593
-```
593
+```
594
595
Now within the TodoStore callback we can explicitly wait for any dependencies to first update before moving forward. However, if Store A waits for Store B, and B waits for A, then a circular dependency will occur. A more robust dispatcher is required to flag this scenario with warnings in the console.
596
597
-The Future of Flux
598
-------------------
597
+The Future of Flux
598
+------------------
599
600
A lot of people ask if Facebook will release Flux as an open source framework. Really, Flux is just an architecture, not a framework. But perhaps a Flux boilerplate project might make sense, if there is enough interest. Please let us know if you'd like to see us do this.
601
examples/todomvc-flux/.gitignore
deleted
-2
@@ -1,2 +0,0 @@
1
-/js/bundle.js
2
-/js/bundle.min.js
examples/todomvc-flux/README.md
deleted
-177
@@ -1,177 +0,0 @@
1
-# Flux TodoMVC Example
2
-
3
-> An application architecture for React utilizing a unidirectional data flow.
4
-
5
-
6
-## Learning Flux
7
-
8
-The [React website](http://facebook.github.io/react) is a great resource for getting started.
9
-
10
-Read the blog post announcing Flux: ["An Application Architecture for React"](http://facebook.github.io/react/blog/2014/05/06/flux.html), then read more about the [Flux architecture](http://facebook.github.io/react/docs/flux-overview.html) and a [TodoMVC tutorial](http://facebook.github.io/react/docs/flux-todo-list.html) explaining the structure of the files in this folder.
11
-
12
-
13
-## Overview
14
-
15
-Flux applications have three major parts: the ___dispatcher___, the ___stores___, and the ___views___ (React components). These should not be confused with Model-View-Controller. Controllers do exist in a Flux application, but they are ___controller-views___ -- views often found at the top of the hierarchy that retrieve data from the stores and pass this data down to their children. Additionally, ___actions___ — dispatcher helper methods — are often used to support a semantic dispatcher API. It can be useful to think of them as a fourth part of the Flux update cycle.
16
-
17
-Flux eschews MVC in favor of a unidirectional data flow. When a user interacts with a React ___view___, the view propagates an ___action___ through a central ___dispatcher___, to the various ___stores___ that hold the application's data and business logic, which updates all of the views that are affected. This works especially well with React's declarative programming style, which allows the store to send updates without specifying how to transition views between states.
18
-
19
-We originally set out to deal correctly with derived data: for example, we wanted to show an unread count for message threads while another view showed a list of threads, with the unread ones highlighted. This was difficult to handle with MVC — marking a single thread as read would update the thread model, and then also need to update the unread count model. These dependencies and cascading updates often occur in a large MVC application, leading to a tangled weave of data flow and unpredictable results.
20
-
21
-Control is inverted with ___stores___: the stores accept updates and reconcile them as appropriate, rather than depending on something external to update its data in a consistent way. Nothing outside the store has any insight into how it manages the data for its domain, helping to keep a clear separation of concerns. This also makes stores more testable than models, especially since stores have no direct setter methods like `setAsRead()`, but instead have only an input point for the payload, which is delivered through the ___dispatcher___ and originates with ___actions___.
22
-
23
-
24
-## Structure and Data Flow
25
-
26
-Data in a Flux application flows in a single direction, in a cycle:
27
-
28
-<pre>
29
-Views ---> (actions) ----> Dispatcher ---> (registered callback) ---> Stores -------+
30
-Ʌ |
31
-| V
32
-+-- (Controller-Views "change" event handlers) ---- (Stores emit "change" events) --+
33
-</pre>
34
-
35
-A unidirectional data flow is central to the Flux pattern, and in fact Flux takes its name from the Latin word for flow. In the above diagram, the ___dispatcher___, ___stores___ and ___views___ are independent nodes with distinct inputs and outputs. The ___actions___ are simply discrete, semantic helper functions that facilitate passing data to the ___dispatcher___.
36
-
37
-All data flows through the ___dispatcher___ as a central hub. ___Actions___ most often originate from user interactions with the ___views___, and are nothing more than a call into the ___dispatcher___. The ___dispatcher___ then invokes the callbacks that the ___stores___ have registered with it, effectively dispatching the data payload contained in the ___actions___ to all ___stores___. Within their registered callbacks, ___stores___ determine which ___actions___ they are interested in, and respond accordingly. The ___stores___ then emit a "change" event to alert the ___controller-views___ that a change to the data layer has occurred. ___Controller-views___ listen for these events and retrieve data from the ___stores___ in an event handler. The ___controller-views___ call their own `render()` method via `setState()` or `forceUpdate()`, updating themselves and all of their children.
38
-
39
-This structure allows us to reason easily about our application in a way that is reminiscent of functional reactive programming, or more specifically data-flow programming or flow-based programming, where data flows through the application in a single direction — there are no two-way bindings. Application state is maintained only in the ___stores___, allowing the different parts of the application to remain highly decoupled. Where dependencies do occur between ___stores___, they are kept in a strict hierarchy, with synchronous updates managed by the ___dispatcher___.
40
-
41
-We found that two-way data bindings led to cascading updates, where changing one object led to another object changing, which could also trigger more updates. As applications grew, these cascading updates made it very difficult to predict what would change as the result of one user interaction. When updates can only change data within a single round, the system as a whole becomes more predictable.
42
-
43
-Let's look at the various parts of the Flux update cycle up close. A good place to start is the dispatcher.
44
-
45
-
46
-### A Single Dispatcher
47
-
48
-The dispatcher is the central hub that manages all data flow in a Flux application. It is essentially a registry of callbacks into the stores. Each store registers itself and provides a callback. When the dispatcher responds to an action, all stores in the application are sent the data payload provided by the action via the callbacks in the registry.
49
-
50
-As an application grows, the dispatcher becomes more vital, as it can manage dependencies between stores by invoking the registered callbacks in a specific order. Stores can declaratively wait for other stores to finish updating, and then update themselves accordingly.
51
-
52
-
53
-### Stores
54
-
55
-Stores contain the application state and logic. Their role is somewhat similar to a model in a traditional MVC, but they manage the state of many objects — they are not instances of one object. Nor are they the same as Backbone's collections. More than simply managing a collection of ORM-style objects, stores manage the application state for a particular __domain__ within the application.
56
-
57
-For example, Facebook's [Lookback Video Editor](https://facebook.com/lookback/edit) utilized a TimeStore that kept track of the playback time position and the playback state. On the other hand, the same application's ImageStore kept track of a collection of images. The TodoStore in our TodoMVC example is similar in that it manages a collection of to-do items. A store exhibits characteristics of both a collection of models and a singleton model of a logical domain.
58
-
59
-As mentioned above, a store registers itself with the dispatcher and provides it with a callback. This callback receives the action's data payload as a parameter. The payload contains a type attribute, identifying the action's type. Within the store's registered callback, a switch statement based on the action's type is used to interpret the payload and to provide the proper hooks into the store's internal methods. This allows an action to result in an update to the state of the store, via the dispatcher. After the stores are updated, they broadcast an event declaring that their state has changed, so the views may query the new state and update themselves.
60
-
61
-
62
-### Views and Controller-Views
63
-
64
-React provides the kind of composable views we need for the view layer. Close to the top of the nested view hierarchy, a special kind of view listens for events that are broadcast by the stores that it depends on. One could call this a ___controller-view___, as it provides the glue code to get the data from the stores and to pass this data down the chain of its descendants. We might have one of these controller-views governing any significant section of the page.
65
-
66
-When it receives the event from the store, it first requests the new data it needs via the stores' public getter methods. It then calls its own `setState()` or `forceUpdate()` methods, causing its `render()` method and the `render()` method of all its descendants to run.
67
-
68
-We often pass the entire state of the store down the chain of views in a single object, allowing different descendants to use what they need. In addition to keeping the controller-like behavior at the top of the hierarchy, and thus keeping our descendant views as functionally pure as possible, passing down the entire state of the store in a single object also has the effect of reducing the number of props we need to manage.
69
-
70
-Occasionally we may need to add additional controller-views deeper in the hierarchy to keep components simple. This might help us to better encapsulate a section of the hierarchy related to a specific data domain. Be aware, however, that controller-views deeper in the hierarchy can violate the singular flow of data by introducing a new, potentially conflicting entry point for the data flow. In making the decision of whether to add a deep controller-view, balance the gain of simpler components against the complexity of multiple data updates flowing into the hierarchy at different points. These multiple data updates can lead to odd effects, with React's render method getting invoked repeatedly by updates from different controller-views, potentially increasing the difficulty of debugging.
71
-
72
-
73
-### Actions
74
-
75
-The dispatcher exposes a method that allows a view to trigger a dispatch to the stores, and to include a payload of data, or an action. The action construction may be wrapped into a semantic helper method which sends the payload to the dispatcher. For example, we may want to change the text of a to-do item in a to-do list application. We would create an action with a function signature like `updateText(todoId, newText)` in our `TodoActions` module. This method may be invoked from within our views' event handlers, so we can call it in response to a user action. This action method also adds the action type to the payload, so that when the payload is interpreted in the store, it can respond appropriately to a payload with a particular action type. In our example, this type might be named something like `TODO_UPDATE_TEXT`.
76
-
77
-Actions may also come from other places, such as the server. This happens, for example, during data initialization. It may also happen when the server returns an error code or when the server has updates to provide to the application. We'll talk more about server actions in a future article. In this post we're only concerned with the basics of the data flow.
78
-
79
-
80
-### What About that Dispatcher?
81
-
82
-As mentioned earlier, the dispatcher is also able to manage dependencies between stores. This functionality is available through the `waitFor()` method within the Dispatcher class. The TodoMVC application is extremely simple, so we did not need to use this method, but we have included it here as an example of what a dispatcher should be able to do in a larger, more complex application.
83
-
84
-Within the TodoStore's registered callback we can explicitly wait for any dependencies to first update before moving forward:
85
-
86
-```
87
-case 'TODO_CREATE':
88
- Dispatcher.waitFor([
89
- PrependedTextStore.dispatcherIndex,
90
- YetAnotherStore.dispatcherIndex
91
- ], function() {
92
- TodoStore.create(PrependedTextStore.getText() + ' ' + action.text);
93
- TodoStore.emit('change');
94
- });
95
- break;
96
-```
97
-
98
-The arguments for `waitFor()` are an array of dipatcher registry indexes, and a final callback to invoke after the callbacks at the given indexes have completed. Thus the store that is invoking `waitFor()` can depend on the state of another store to inform how it should update its own state.
99
-
100
-A problem arises if we create circular dependencies. If Store A waits for Store B, and B waits for A, then we'll have a very bad situation on our hands. We'll need a more robust dispatcher that flags these circular dependencies with console errors, and this is not easily accomplished with promises. Unfortunately, that's a bit beyond the scope of this documentation. In future blog posts, we hope to cover how to build a more robust dispatcher and how to initialize, update, and save the state of the application with persistent data, like a web service API.
101
-
102
-
103
-## TodoMVC Example Implementation
104
-
105
-In this TodoMVC example application, we can see the elements of Flux in our directory structure. Views here are referred to as "components" as they are React components.
106
-
107
-<pre>
108
-./
109
- index.html
110
- js/
111
- actions/
112
- TodoActions.js
113
- app.js
114
- bundle.js
115
- dispatcher/
116
- AppDispatcher.js
117
- Dispatcher.js
118
- components/
119
- Footer.react.js
120
- Header.react.js
121
- MainSection.react.js
122
- TodoApp.react.js
123
- TodoItem.react.js
124
- TodoTextInput.react.js
125
- stores/
126
- TodoStore.js
127
-</pre>
128
-
129
-The primary entry point into the application is app.js. This file bootstraps the React rendering inside of index.html. TodoApp.js is our controller-view and it passes all data down into its child React components.
130
-
131
-TodoActions.js is a collection of actions that views may call from within their event handlers, in response to user interactions. They are nothing more than helpers that call into the AppDispatcher.
132
-
133
-Dispatcher.js is a base class for AppDispatcher.js which extends it with a small amount of application-specific code. This dispatcher is a naive implementation based on promises, but much more robust implementations are possible.
134
-
135
-TodoStore.js is our only store. It provides all of the application logic and in-memory storage. Based on EventEmitter from Node.js, it emits "change" events after responding to actions in the callback it registers with the dispatcher.
136
-
137
-The bundle.js file is automatically genenerated by the build process, explained below.
138
-
139
-
140
-## Running
141
-
142
-You must have [npm](https://www.npmjs.org/) installed on your computer.
143
-From the root project directory run these commands from the command line:
144
-
145
- npm install
146
-
147
-This will install all dependencies.
148
-
149
-To build the project, first run this command:
150
-
151
- npm start
152
-
153
-This will perform an initial build and start a watcher process that will update build.js with any changes you wish to make. This watcher is based on [Browserify](http://browserify.org/) and [Watchify](https://github.com/substack/watchify), and it transforms React's JSX syntax into standard JavaScript with [Reactify](https://github.com/andreypopp/reactify).
154
-
155
-To run the app, spin up an HTTP server and visit http://localhost/.../todomvc-flux/.
156
-
157
-
158
-## Credit
159
-
160
-This TodoMVC application was created by [Bill Fisher](https://www.facebook.com/bill.fisher.771). This README document was written by Bill Fisher and the principal creator of Flux, [Jing Chen](https://www.facebook.com/jing).
161
-
162
-
163
-## License
164
-
165
-> Copyright 2013-2014 Facebook, Inc.
166
->
167
-> Licensed under the Apache License, Version 2.0 (the "License");
168
-> you may not use this file except in compliance with the License.
169
-> You may obtain a copy of the License at
170
->
171
-> http://www.apache.org/licenses/LICENSE-2.0
172
->
173
-> Unless required by applicable law or agreed to in writing, software
174
-> distributed under the License is distributed on an "AS IS" BASIS,
175
-> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
176
-> See the License for the specific language governing permissions and
177
-> limitations under the License.
examples/todomvc-flux/css/app.css
deleted
-25
@@ -1,25 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * base.css overrides
17
- */
18
-
19
-/**
20
- * We are not changing from display:none, but rather re-rendering instead.
21
- * Therefore this needs to be displayed normally by default.
22
- */
23
-#todo-list li .edit {
24
- display: inline;
25
-}
\ No newline at end of file
examples/todomvc-flux/index.html
deleted
-18
@@ -1,18 +0,0 @@
1
-<!doctype html>
2
-<html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <title>Flux • TodoMVC</title>
6
- <link rel="stylesheet" href="todomvc-common/base.css">
7
- <link rel="stylesheet" href="css/app.css">
8
- </head>
9
- <body>
10
- <section id="todoapp"></section>
11
- <footer id="info">
12
- <p>Double-click to edit a todo</p>
13
- <p>Created by <a href="http://facebook.com/bill.fisher.771">Bill Fisher</a></p>
14
- <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
15
- </footer>
16
- <script src="js/bundle.js"></script>
17
- </body>
18
-</html>
examples/todomvc-flux/js/actions/TodoActions.js
deleted
-95
@@ -1,95 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * TodoActions
17
- */
18
-
19
-var AppDispatcher = require('../dispatcher/AppDispatcher');
20
-var TodoConstants = require('../constants/TodoConstants');
21
-
22
-var TodoActions = {
23
-
24
- /**
25
- * @param {string} text
26
- */
27
- create: function(text) {
28
- AppDispatcher.handleViewAction({
29
- actionType: TodoConstants.TODO_CREATE,
30
- text: text
31
- });
32
- },
33
-
34
- /**
35
- * @param {string} id The ID of the ToDo item
36
- * @param {string} text
37
- */
38
- updateText: function(id, text) {
39
- AppDispatcher.handleViewAction({
40
- actionType: TodoConstants.TODO_UPDATE_TEXT,
41
- id: id,
42
- text: text
43
- });
44
- },
45
-
46
- /**
47
- * Toggle whether a single ToDo is complete
48
- * @param {object} todo
49
- */
50
- toggleComplete: function(todo) {
51
- var id = todo.id;
52
- if (todo.complete) {
53
- AppDispatcher.handleViewAction({
54
- actionType: TodoConstants.TODO_UNDO_COMPLETE,
55
- id: id
56
- });
57
- } else {
58
- AppDispatcher.handleViewAction({
59
- actionType: TodoConstants.TODO_COMPLETE,
60
- id: id
61
- });
62
- }
63
- },
64
-
65
- /**
66
- * Mark all ToDos as complete
67
- */
68
- toggleCompleteAll: function() {
69
- AppDispatcher.handleViewAction({
70
- actionType: TodoConstants.TODO_TOGGLE_COMPLETE_ALL
71
- });
72
- },
73
-
74
- /**
75
- * @param {string} id
76
- */
77
- destroy: function(id) {
78
- AppDispatcher.handleViewAction({
79
- actionType: TodoConstants.TODO_DESTROY,
80
- id: id
81
- });
82
- },
83
-
84
- /**
85
- * Delete all the completed ToDos
86
- */
87
- destroyCompleted: function() {
88
- AppDispatcher.handleViewAction({
89
- actionType: TodoConstants.TODO_DESTROY_COMPLETED
90
- });
91
- }
92
-
93
-};
94
-
95
-module.exports = TodoActions;
examples/todomvc-flux/js/app.js
deleted
-26
@@ -1,26 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * @jsx React.DOM
17
- */
18
-
19
-var React = require('react');
20
-
21
-var TodoApp = require('./components/TodoApp.react');
22
-
23
-React.renderComponent(
24
- <TodoApp />,
25
- document.getElementById('todoapp')
26
-);
\ No newline at end of file
examples/todomvc-flux/js/components/Footer.react.js
deleted
-84
@@ -1,84 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * @jsx React.DOM
17
- */
18
-
19
-var React = require('react');
20
-var ReactPropTypes = React.PropTypes;
21
-var TodoActions = require('../actions/TodoActions');
22
-
23
-var Footer = React.createClass({
24
-
25
- propTypes: {
26
- allTodos: ReactPropTypes.object.isRequired
27
- },
28
-
29
- /**
30
- * @return {object}
31
- */
32
- render: function() {
33
- var allTodos = this.props.allTodos;
34
- var total = Object.keys(allTodos).length;
35
-
36
- if (total === 0) {
37
- return null;
38
- }
39
-
40
- var completed = 0;
41
- for (var key in allTodos) {
42
- if (allTodos[key].complete) {
43
- completed++;
44
- }
45
- }
46
-
47
- var itemsLeft = total - completed;
48
- var itemsLeftPhrase = itemsLeft === 1 ? ' item ' : ' items ';
49
- itemsLeftPhrase += 'left';
50
-
51
- // Undefined and thus not rendered if no completed items are left.
52
- var clearCompletedButton;
53
- if (completed) {
54
- clearCompletedButton =
55
- <button
56
- id="clear-completed"
57
- onClick={this._onClearCompletedClick}>
58
- Clear completed ({completed})
59
- </button>;
60
- }
61
-
62
- return (
63
- <footer id="footer">
64
- <span id="todo-count">
65
- <strong>
66
- {itemsLeft}
67
- </strong>
68
- {itemsLeftPhrase}
69
- </span>
70
- {clearCompletedButton}
71
- </footer>
72
- );
73
- },
74
-
75
- /**
76
- * Event handler to delete all completed TODOs
77
- */
78
- _onClearCompletedClick: function() {
79
- TodoActions.destroyCompleted();
80
- }
81
-
82
-});
83
-
84
-module.exports = Footer;
examples/todomvc-flux/js/components/Header.react.js
deleted
-56
@@ -1,56 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * @jsx React.DOM
17
- */
18
-
19
-var React = require('react');
20
-var TodoActions = require('../actions/TodoActions');
21
-var TodoTextInput = require('./TodoTextInput.react');
22
-
23
-var Header = React.createClass({
24
-
25
- /**
26
- * @return {object}
27
- */
28
- render: function() {
29
- return (
30
- <header id="header">
31
- <h1>todos</h1>
32
- <TodoTextInput
33
- id="new-todo"
34
- placeholder="What needs to be done?"
35
- onSave={this._onSave}
36
- />
37
- </header>
38
- );
39
- },
40
-
41
- /**
42
- * Event handler called within TodoTextInput.
43
- * Defining this here allows TodoTextInput to be used in multiple places
44
- * in different ways.
45
- * @param {string} text
46
- */
47
- _onSave: function(text) {
48
- if (text.trim()){
49
- TodoActions.create(text);
50
- }
51
-
52
- }
53
-
54
-});
55
-
56
-module.exports = Header;
examples/todomvc-flux/js/components/MainSection.react.js
deleted
-71
@@ -1,71 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * @jsx React.DOM
17
- */
18
-
19
-var React = require('react');
20
-var ReactPropTypes = React.PropTypes;
21
-var TodoActions = require('../actions/TodoActions');
22
-var TodoItem = require('./TodoItem.react');
23
-
24
-var MainSection = React.createClass({
25
-
26
- propTypes: {
27
- allTodos: ReactPropTypes.object.isRequired,
28
- areAllComplete: ReactPropTypes.bool.isRequired
29
- },
30
-
31
- /**
32
- * @return {object}
33
- */
34
- render: function() {
35
- // This section should be hidden by default
36
- // and shown when there are todos.
37
- if (Object.keys(this.props.allTodos).length < 1) {
38
- return null;
39
- }
40
-
41
- var allTodos = this.props.allTodos;
42
- var todos = [];
43
-
44
- for (var key in allTodos) {
45
- todos.push(<TodoItem key={key} todo={allTodos[key]} />);
46
- }
47
-
48
- return (
49
- <section id="main">
50
- <input
51
- id="toggle-all"
52
- type="checkbox"
53
- onChange={this._onToggleCompleteAll}
54
- checked={this.props.areAllComplete ? 'checked' : ''}
55
- />
56
- <label htmlFor="toggle-all">Mark all as complete</label>
57
- <ul id="todo-list">{todos}</ul>
58
- </section>
59
- );
60
- },
61
-
62
- /**
63
- * Event handler to mark all TODOs as complete
64
- */
65
- _onToggleCompleteAll: function() {
66
- TodoActions.toggleCompleteAll();
67
- }
68
-
69
-});
70
-
71
-module.exports = MainSection;
examples/todomvc-flux/js/components/TodoApp.react.js
deleted
-79
@@ -1,79 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * @jsx React.DOM
17
- */
18
-
19
-/**
20
- * This component operates as a "Controller-View". It listens for changes in
21
- * the TodoStore and passes the new data to its children.
22
- */
23
-
24
-var Footer = require('./Footer.react');
25
-var Header = require('./Header.react');
26
-var MainSection = require('./MainSection.react');
27
-var React = require('react');
28
-var TodoStore = require('../stores/TodoStore');
29
-
30
-/**
31
- * Retrieve the current TODO data from the TodoStore
32
- */
33
-function getTodoState() {
34
- return {
35
- allTodos: TodoStore.getAll(),
36
- areAllComplete: TodoStore.areAllComplete()
37
- };
38
-}
39
-
40
-var TodoApp = React.createClass({
41
-
42
- getInitialState: function() {
43
- return getTodoState();
44
- },
45
-
46
- componentDidMount: function() {
47
- TodoStore.addChangeListener(this._onChange);
48
- },
49
-
50
- componentWillUnmount: function() {
51
- TodoStore.removeChangeListener(this._onChange);
52
- },
53
-
54
- /**
55
- * @return {object}
56
- */
57
- render: function() {
58
- return (
59
- <div>
60
- <Header />
61
- <MainSection
62
- allTodos={this.state.allTodos}
63
- areAllComplete={this.state.areAllComplete}
64
- />
65
- <Footer allTodos={this.state.allTodos} />
66
- </div>
67
- );
68
- },
69
-
70
- /**
71
- * Event handler for 'change' events coming from the TodoStore
72
- */
73
- _onChange: function() {
74
- this.setState(getTodoState());
75
- }
76
-
77
-});
78
-
79
-module.exports = TodoApp;
examples/todomvc-flux/js/components/TodoItem.react.js
deleted
-108
@@ -1,108 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * @jsx React.DOM
17
- */
18
-
19
-var React = require('react');
20
-var ReactPropTypes = React.PropTypes;
21
-var TodoActions = require('../actions/TodoActions');
22
-var TodoTextInput = require('./TodoTextInput.react');
23
-
24
-var cx = require('react/lib/cx');
25
-
26
-var TodoItem = React.createClass({
27
-
28
- propTypes: {
29
- todo: ReactPropTypes.object.isRequired
30
- },
31
-
32
- getInitialState: function() {
33
- return {
34
- isEditing: false
35
- };
36
- },
37
-
38
- /**
39
- * @return {object}
40
- */
41
- render: function() {
42
- var todo = this.props.todo;
43
-
44
- var input;
45
- if (this.state.isEditing) {
46
- input =
47
- <TodoTextInput
48
- className="edit"
49
- onSave={this._onSave}
50
- value={todo.text}
51
- />;
52
- }
53
-
54
- // List items should get the class 'editing' when editing
55
- // and 'completed' when marked as completed.
56
- // Note that 'completed' is a classification while 'complete' is a state.
57
- // This differentiation between classification and state becomes important
58
- // in the naming of view actions toggleComplete() vs. destroyCompleted().
59
- return (
60
- <li
61
- className={cx({
62
- 'completed': todo.complete,
63
- 'editing': this.state.isEditing
64
- })}
65
- key={todo.id}>
66
- <div className="view">
67
- <input
68
- className="toggle"
69
- type="checkbox"
70
- checked={todo.complete}
71
- onChange={this._onToggleComplete}
72
- />
73
- <label onDoubleClick={this._onDoubleClick}>
74
- {todo.text}
75
- </label>
76
- <button className="destroy" onClick={this._onDestroyClick} />
77
- </div>
78
- {input}
79
- </li>
80
- );
81
- },
82
-
83
- _onToggleComplete: function() {
84
- TodoActions.toggleComplete(this.props.todo);
85
- },
86
-
87
- _onDoubleClick: function() {
88
- this.setState({isEditing: true});
89
- },
90
-
91
- /**
92
- * Event handler called within TodoTextInput.
93
- * Defining this here allows TodoTextInput to be used in multiple places
94
- * in different ways.
95
- * @param {string} text
96
- */
97
- _onSave: function(text) {
98
- TodoActions.updateText(this.props.todo.id, text);
99
- this.setState({isEditing: false});
100
- },
101
-
102
- _onDestroyClick: function() {
103
- TodoActions.destroy(this.props.todo.id);
104
- }
105
-
106
-});
107
-
108
-module.exports = TodoItem;
examples/todomvc-flux/js/components/TodoTextInput.react.js
deleted
-89
@@ -1,89 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * @jsx React.DOM
17
- */
18
-
19
-var React = require('react');
20
-var ReactPropTypes = React.PropTypes;
21
-
22
-var ENTER_KEY_CODE = 13;
23
-
24
-var TodoTextInput = React.createClass({
25
-
26
- propTypes: {
27
- className: ReactPropTypes.string,
28
- id: ReactPropTypes.string,
29
- placeholder: ReactPropTypes.string,
30
- onSave: ReactPropTypes.func.isRequired,
31
- value: ReactPropTypes.string
32
- },
33
-
34
- getInitialState: function() {
35
- return {
36
- value: this.props.value || ''
37
- };
38
- },
39
-
40
- /**
41
- * @return {object}
42
- */
43
- render: function() /*object*/ {
44
- return (
45
- <input
46
- className={this.props.className}
47
- id={this.props.id}
48
- placeholder={this.props.placeholder}
49
- onBlur={this._save}
50
- onChange={this._onChange}
51
- onKeyDown={this._onKeyDown}
52
- value={this.state.value}
53
- autoFocus={true}
54
- />
55
- );
56
- },
57
-
58
- /**
59
- * Invokes the callback passed in as onSave, allowing this component to be
60
- * used in different ways.
61
- */
62
- _save: function() {
63
- this.props.onSave(this.state.value);
64
- this.setState({
65
- value: ''
66
- });
67
- },
68
-
69
- /**
70
- * @param {object} event
71
- */
72
- _onChange: function(/*object*/ event) {
73
- this.setState({
74
- value: event.target.value
75
- });
76
- },
77
-
78
- /**
79
- * @param {object} event
80
- */
81
- _onKeyDown: function(event) {
82
- if (event.keyCode === ENTER_KEY_CODE) {
83
- this._save();
84
- }
85
- }
86
-
87
-});
88
-
89
-module.exports = TodoTextInput;
examples/todomvc-flux/js/constants/TodoConstants.js
deleted
-29
@@ -1,29 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * TodoConstants
17
- */
18
-
19
-var keyMirror = require('react/lib/keyMirror');
20
-
21
-module.exports = keyMirror({
22
- TODO_CREATE: null,
23
- TODO_COMPLETE: null,
24
- TODO_DESTROY: null,
25
- TODO_DESTROY_COMPLETED: null,
26
- TODO_TOGGLE_COMPLETE_ALL: null,
27
- TODO_UNDO_COMPLETE: null,
28
- TODO_UPDATE_TEXT: null
29
-});
examples/todomvc-flux/js/dispatcher/AppDispatcher.js
deleted
-41
@@ -1,41 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * AppDispatcher
17
- *
18
- * A singleton that operates as the central hub for application updates.
19
- */
20
-
21
-var Dispatcher = require('./Dispatcher');
22
-
23
-var merge = require('react/lib/merge');
24
-
25
-var AppDispatcher = merge(Dispatcher.prototype, {
26
-
27
- /**
28
- * A bridge function between the views and the dispatcher, marking the action
29
- * as a view action. Another variant here could be handleServerAction.
30
- * @param {object} action The data coming from the view.
31
- */
32
- handleViewAction: function(action) {
33
- this.dispatch({
34
- source: 'VIEW_ACTION',
35
- action: action
36
- });
37
- }
38
-
39
-});
40
-
41
-module.exports = AppDispatcher;
examples/todomvc-flux/js/dispatcher/Dispatcher.js
deleted
-113
@@ -1,113 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * Dispatcher
17
- *
18
- * The Dispatcher is capable of registering callbacks and invoking them.
19
- * More robust implementations than this would include a way to order the
20
- * callbacks for dependent Stores, and to guarantee that no two stores
21
- * created circular dependencies.
22
- */
23
-
24
-var Promise = require('es6-promise').Promise;
25
-var merge = require('react/lib/merge');
26
-
27
-var _callbacks = [];
28
-var _promises = [];
29
-
30
-var Dispatcher = function() {};
31
-Dispatcher.prototype = merge(Dispatcher.prototype, {
32
-
33
- /**
34
- * Register a Store's callback so that it may be invoked by an action.
35
- * @param {function} callback The callback to be registered.
36
- * @return {number} The index of the callback within the _callbacks array.
37
- */
38
- register: function(callback) {
39
- _callbacks.push(callback);
40
- return _callbacks.length - 1; // index
41
- },
42
-
43
- /**
44
- * dispatch
45
- * @param {object} payload The data from the action.
46
- */
47
- dispatch: function(payload) {
48
- // First create array of promises for callbacks to reference.
49
- var resolves = [];
50
- var rejects = [];
51
- _promises = _callbacks.map(function(_, i) {
52
- return new Promise(function(resolve, reject) {
53
- resolves[i] = resolve;
54
- rejects[i] = reject;
55
- });
56
- });
57
- // Dispatch to callbacks and resolve/reject promises.
58
- _callbacks.forEach(function(callback, i) {
59
- // Callback can return an obj, to resolve, or a promise, to chain.
60
- // See waitFor() for why this might be useful.
61
- Promise.resolve(callback(payload)).then(function() {
62
- resolves[i](payload);
63
- }, function() {
64
- rejects[i](new Error('Dispatcher callback unsuccessful'));
65
- });
66
- });
67
- _promises = [];
68
- },
69
-
70
- /**
71
- * Allows a store to wait for the registered callbacks of other stores
72
- * to get invoked before its own does.
73
- * This function is not used by this TodoMVC example application, but
74
- * it is very useful in a larger, more complex application.
75
- *
76
- * Example usage where StoreB waits for StoreA:
77
- *
78
- * var StoreA = merge(EventEmitter.prototype, {
79
- * // other methods omitted
80
- *
81
- * dispatchIndex: Dispatcher.register(function(payload) {
82
- * // switch statement with lots of cases
83
- * })
84
- * }
85
- *
86
- * var StoreB = merge(EventEmitter.prototype, {
87
- * // other methods omitted
88
- *
89
- * dispatchIndex: Dispatcher.register(function(payload) {
90
- * switch(payload.action.actionType) {
91
- *
92
- * case MyConstants.FOO_ACTION:
93
- * Dispatcher.waitFor([StoreA.dispatchIndex], function() {
94
- * // Do stuff only after StoreA's callback returns.
95
- * });
96
- * }
97
- * })
98
- * }
99
- *
100
- * It should be noted that if StoreB waits for StoreA, and StoreA waits for
101
- * StoreB, a circular dependency will occur, but no error will be thrown.
102
- * A more robust Dispatcher would issue a warning in this scenario.
103
- */
104
- waitFor: function(/*array*/ promiseIndexes, /*function*/ callback) {
105
- var selectedPromises = promiseIndexes.map(function(index) {
106
- return _promises[index];
107
- });
108
- return Promise.all(selectedPromises).then(callback);
109
- }
110
-
111
-});
112
-
113
-module.exports = Dispatcher;
examples/todomvc-flux/js/dispatcher/__tests__/AppDispatcher-test.js
deleted
-76
@@ -1,76 +0,0 @@
1
-"use strict";
2
-
3
-jest.autoMockOff();
4
-
5
-describe('AppDispatcher', function() {
6
- var AppDispatcher;
7
-
8
- beforeEach(function() {
9
- AppDispatcher = require('../AppDispatcher.js');
10
- });
11
-
12
- it('sends actions to subscribers', function() {
13
- var listener = jest.genMockFunction();
14
- AppDispatcher.register(listener);
15
-
16
- var payload = {};
17
- AppDispatcher.dispatch(payload);
18
- expect(listener.mock.calls.length).toBe(1);
19
- expect(listener.mock.calls[0][0]).toBe(payload);
20
- });
21
-
22
- it('waits with chained dependencies properly', function() {
23
- var payload = {};
24
-
25
- var listener1Done = false;
26
- var listener1 = function(pl) {
27
- return AppDispatcher.waitFor([index2, index4], function() {
28
- // Second, third, and fourth listeners should have now been called
29
- expect(listener2Done).toBe(true);
30
- expect(listener3Done).toBe(true);
31
- expect(listener4Done).toBe(true);
32
- listener1Done = true;
33
- });
34
- };
35
- var index1 = AppDispatcher.register(listener1);
36
-
37
- var listener2Done = false;
38
- var listener2 = function(pl) {
39
- return AppDispatcher.waitFor([index3], function() {
40
- expect(listener3Done).toBe(true);
41
- listener2Done = true;
42
- });
43
- };
44
- var index2 = AppDispatcher.register(listener2);
45
-
46
- var listener3Done = false;
47
- var listener3 = function(pl) {
48
- listener3Done = true;
49
- return true;
50
- };
51
- var index3 = AppDispatcher.register(listener3);
52
-
53
- var listener4Done = false;
54
- var listener4 = function(pl) {
55
- return AppDispatcher.waitFor([index3], function() {
56
- expect(listener3Done).toBe(true);
57
- listener4Done = true;
58
- });
59
- };
60
- var index4 = AppDispatcher.register(listener4);
61
-
62
- runs(function() {
63
- AppDispatcher.dispatch(payload);
64
- });
65
-
66
- waitsFor(function() {
67
- return listener1Done;
68
- }, "Not all subscribers were properly called", 500);
69
-
70
- runs(function() {
71
- expect(listener1Done).toBe(true);
72
- expect(listener2Done).toBe(true);
73
- expect(listener3Done).toBe(true);
74
- });
75
- });
76
-});
examples/todomvc-flux/js/stores/TodoStore.js
deleted
-186
@@ -1,186 +0,0 @@
1
-/**
2
- * Copyright 2013-2014 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * TodoStore
17
- */
18
-
19
-var AppDispatcher = require('../dispatcher/AppDispatcher');
20
-var EventEmitter = require('events').EventEmitter;
21
-var TodoConstants = require('../constants/TodoConstants');
22
-var merge = require('react/lib/merge');
23
-
24
-var CHANGE_EVENT = 'change';
25
-
26
-var _todos = {};
27
-
28
-/**
29
- * Create a TODO item.
30
- * @param {string} text The content of the TODO
31
- */
32
-function create(text) {
33
- // Hand waving here -- not showing how this interacts with XHR or persistent
34
- // server-side storage.
35
- // Using the current timestamp in place of a real id.
36
- var id = Date.now();
37
- _todos[id] = {
38
- id: id,
39
- complete: false,
40
- text: text
41
- };
42
-}
43
-
44
-/**
45
- * Update a TODO item.
46
- * @param {string} id
47
- * @param {object} updates An object literal containing only the data to be
48
- * updated.
49
- */
50
-function update(id, updates) {
51
- _todos[id] = merge(_todos[id], updates);
52
-}
53
-
54
-/**
55
- * Update all of the TODO items with the same object.
56
- * the data to be updated. Used to mark all TODOs as completed.
57
- * @param {object} updates An object literal containing only the data to be
58
- * updated.
59
-
60
- */
61
-function updateAll(updates) {
62
- for (var id in _todos) {
63
- update(id, updates);
64
- }
65
-}
66
-
67
-/**
68
- * Delete a TODO item.
69
- * @param {string} id
70
- */
71
-function destroy(id) {
72
- delete _todos[id];
73
-}
74
-
75
-/**
76
- * Delete all the completed TODO items.
77
- */
78
-function destroyCompleted() {
79
- for (var id in _todos) {
80
- if (_todos[id].complete) {
81
- destroy(id);
82
- }
83
- }
84
-}
85
-
86
-var TodoStore = merge(EventEmitter.prototype, {
87
-
88
- /**
89
- * Tests whether all the remaining TODO items are marked as completed.
90
- * @return {boolean}
91
- */
92
- areAllComplete: function() {
93
- for (id in _todos) {
94
- if (!_todos[id].complete) {
95
- return false;
96
- break;
97
- }
98
- }
99
- return true;
100
- },
101
-
102
- /**
103
- * Get the entire collection of TODOs.
104
- * @return {object}
105
- */
106
- getAll: function() {
107
- return _todos;
108
- },
109
-
110
- emitChange: function() {
111
- this.emit(CHANGE_EVENT);
112
- },
113
-
114
- /**
115
- * @param {function} callback
116
- */
117
- addChangeListener: function(callback) {
118
- this.on(CHANGE_EVENT, callback);
119
- },
120
-
121
- /**
122
- * @param {function} callback
123
- */
124
- removeChangeListener: function(callback) {
125
- this.removeListener(CHANGE_EVENT, callback);
126
- }
127
-});
128
-
129
-// Register to handle all updates
130
-AppDispatcher.register(function(payload) {
131
- var action = payload.action;
132
- var text;
133
-
134
- switch(action.actionType) {
135
- case TodoConstants.TODO_CREATE:
136
- text = action.text.trim();
137
- if (text !== '') {
138
- create(text);
139
- }
140
- break;
141
-
142
- case TodoConstants.TODO_TOGGLE_COMPLETE_ALL:
143
- if (TodoStore.areAllComplete()) {
144
- updateAll({complete: false});
145
- } else {
146
- updateAll({complete: true});
147
- }
148
- break;
149
-
150
- case TodoConstants.TODO_UNDO_COMPLETE:
151
- update(action.id, {complete: false});
152
- break;
153
-
154
- case TodoConstants.TODO_COMPLETE:
155
- update(action.id, {complete: true});
156
- break;
157
-
158
- case TodoConstants.TODO_UPDATE_TEXT:
159
- text = action.text.trim();
160
- if (text !== '') {
161
- update(action.id, {text: text});
162
- }
163
- break;
164
-
165
- case TodoConstants.TODO_DESTROY:
166
- destroy(action.id);
167
- break;
168
-
169
- case TodoConstants.TODO_DESTROY_COMPLETED:
170
- destroyCompleted();
171
- break;
172
-
173
- default:
174
- return true;
175
- }
176
-
177
- // This often goes in each case that should trigger a UI change. This store
178
- // needs to trigger a UI change after every view action, so we can make the
179
- // code less repetitive by putting it here. We need the default case,
180
- // however, to make sure this only gets called after one of the cases above.
181
- TodoStore.emitChange();
182
-
183
- return true; // No errors. Needed by promise in Dispatcher.
184
-});
185
-
186
-module.exports = TodoStore;
examples/todomvc-flux/package.json
deleted
-36
@@ -1,36 +0,0 @@
1
-{
2
- "name": "todomvc-flux",
3
- "version": "0.0.1",
4
- "description": "Example Flux architecture.",
5
- "main": "js/app.js",
6
- "dependencies": {
7
- "es6-promise": "~0.1.1",
8
- "react": "~0.11.0"
9
- },
10
- "devDependencies": {
11
- "browserify": "~2.36.0",
12
- "envify": "~1.2.0",
13
- "reactify": "~0.4.0",
14
- "statics": "~0.1.0",
15
- "uglify-js": "~2.4.13",
16
- "watchify": "~0.4.1",
17
- "jest-cli": "~0.1.5"
18
- },
19
- "scripts": {
20
- "start": "STATIC_ROOT=./static watchify -o js/bundle.js -v -d .",
21
- "build": "STATIC_ROOT=./static NODE_ENV=production browserify . | uglifyjs -cm > js/bundle.min.js",
22
- "collect-static": "collect-static . ./static",
23
- "test": "jest"
24
- },
25
- "author": "Bill Fisher",
26
- "license": "Apache 2",
27
- "browserify": {
28
- "transform": [
29
- "reactify",
30
- "envify"
31
- ]
32
- },
33
- "jest": {
34
- "rootDir": "./js"
35
- }
36
-}
examples/todomvc-flux/todomvc-common/.bower.json
deleted
-14
@@ -1,14 +0,0 @@
1
-{
2
- "name": "todomvc-common",
3
- "version": "0.1.9",
4
- "homepage": "https://github.com/tastejs/todomvc-common",
5
- "_release": "0.1.9",
6
- "_resolution": {
7
- "type": "version",
8
- "tag": "v0.1.9",
9
- "commit": "7dd61b0ebf56c020e719a69444442cc7ae7242ff"
10
- },
11
- "_source": "git://github.com/tastejs/todomvc-common.git",
12
- "_target": "~0.1.4",
13
- "_originalSource": "todomvc-common"
14
-}
\ No newline at end of file
examples/todomvc-flux/todomvc-common/base.css
deleted
-556
@@ -1,556 +0,0 @@
1
-html,
2
-body {
3
- margin: 0;
4
- padding: 0;
5
-}
6
-
7
-button {
8
- margin: 0;
9
- padding: 0;
10
- border: 0;
11
- background: none;
12
- font-size: 100%;
13
- vertical-align: baseline;
14
- font-family: inherit;
15
- color: inherit;
16
- -webkit-appearance: none;
17
- -ms-appearance: none;
18
- -o-appearance: none;
19
- appearance: none;
20
-}
21
-
22
-body {
23
- font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
24
- line-height: 1.4em;
25
- background: #eaeaea url('bg.png');
26
- color: #4d4d4d;
27
- width: 550px;
28
- margin: 0 auto;
29
- -webkit-font-smoothing: antialiased;
30
- -moz-font-smoothing: antialiased;
31
- -ms-font-smoothing: antialiased;
32
- -o-font-smoothing: antialiased;
33
- font-smoothing: antialiased;
34
-}
35
-
36
-button,
37
-input[type="checkbox"] {
38
- outline: none;
39
-}
40
-
41
-#todoapp {
42
- background: #fff;
43
- background: rgba(255, 255, 255, 0.9);
44
- margin: 130px 0 40px 0;
45
- border: 1px solid #ccc;
46
- position: relative;
47
- border-top-left-radius: 2px;
48
- border-top-right-radius: 2px;
49
- box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
50
- 0 25px 50px 0 rgba(0, 0, 0, 0.15);
51
-}
52
-
53
-#todoapp:before {
54
- content: '';
55
- border-left: 1px solid #f5d6d6;
56
- border-right: 1px solid #f5d6d6;
57
- width: 2px;
58
- position: absolute;
59
- top: 0;
60
- left: 40px;
61
- height: 100%;
62
-}
63
-
64
-#todoapp input::-webkit-input-placeholder {
65
- font-style: italic;
66
-}
67
-
68
-#todoapp input::-moz-placeholder {
69
- font-style: italic;
70
- color: #a9a9a9;
71
-}
72
-
73
-#todoapp h1 {
74
- position: absolute;
75
- top: -120px;
76
- width: 100%;
77
- font-size: 70px;
78
- font-weight: bold;
79
- text-align: center;
80
- color: #b3b3b3;
81
- color: rgba(255, 255, 255, 0.3);
82
- text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
83
- -webkit-text-rendering: optimizeLegibility;
84
- -moz-text-rendering: optimizeLegibility;
85
- -ms-text-rendering: optimizeLegibility;
86
- -o-text-rendering: optimizeLegibility;
87
- text-rendering: optimizeLegibility;
88
-}
89
-
90
-#header {
91
- padding-top: 15px;
92
- border-radius: inherit;
93
-}
94
-
95
-#header:before {
96
- content: '';
97
- position: absolute;
98
- top: 0;
99
- right: 0;
100
- left: 0;
101
- height: 15px;
102
- z-index: 2;
103
- border-bottom: 1px solid #6c615c;
104
- background: #8d7d77;
105
- background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
106
- background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
107
- background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
108
- filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
109
- border-top-left-radius: 1px;
110
- border-top-right-radius: 1px;
111
-}
112
-
113
-#new-todo,
114
-.edit {
115
- position: relative;
116
- margin: 0;
117
- width: 100%;
118
- font-size: 24px;
119
- font-family: inherit;
120
- line-height: 1.4em;
121
- border: 0;
122
- outline: none;
123
- color: inherit;
124
- padding: 6px;
125
- border: 1px solid #999;
126
- box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
127
- -moz-box-sizing: border-box;
128
- -ms-box-sizing: border-box;
129
- -o-box-sizing: border-box;
130
- box-sizing: border-box;
131
- -webkit-font-smoothing: antialiased;
132
- -moz-font-smoothing: antialiased;
133
- -ms-font-smoothing: antialiased;
134
- -o-font-smoothing: antialiased;
135
- font-smoothing: antialiased;
136
-}
137
-
138
-#new-todo {
139
- padding: 16px 16px 16px 60px;
140
- border: none;
141
- background: rgba(0, 0, 0, 0.02);
142
- z-index: 2;
143
- box-shadow: none;
144
-}
145
-
146
-#main {
147
- position: relative;
148
- z-index: 2;
149
- border-top: 1px dotted #adadad;
150
-}
151
-
152
-label[for='toggle-all'] {
153
- display: none;
154
-}
155
-
156
-#toggle-all {
157
- position: absolute;
158
- top: -42px;
159
- left: -4px;
160
- width: 40px;
161
- text-align: center;
162
- /* Mobile Safari */
163
- border: none;
164
-}
165
-
166
-#toggle-all:before {
167
- content: '»';
168
- font-size: 28px;
169
- color: #d9d9d9;
170
- padding: 0 25px 7px;
171
-}
172
-
173
-#toggle-all:checked:before {
174
- color: #737373;
175
-}
176
-
177
-#todo-list {
178
- margin: 0;
179
- padding: 0;
180
- list-style: none;
181
-}
182
-
183
-#todo-list li {
184
- position: relative;
185
- font-size: 24px;
186
- border-bottom: 1px dotted #ccc;
187
-}
188
-
189
-#todo-list li:last-child {
190
- border-bottom: none;
191
-}
192
-
193
-#todo-list li.editing {
194
- border-bottom: none;
195
- padding: 0;
196
-}
197
-
198
-#todo-list li.editing .edit {
199
- display: block;
200
- width: 506px;
201
- padding: 13px 17px 12px 17px;
202
- margin: 0 0 0 43px;
203
-}
204
-
205
-#todo-list li.editing .view {
206
- display: none;
207
-}
208
-
209
-#todo-list li .toggle {
210
- text-align: center;
211
- width: 40px;
212
- /* auto, since non-WebKit browsers doesn't support input styling */
213
- height: auto;
214
- position: absolute;
215
- top: 0;
216
- bottom: 0;
217
- margin: auto 0;
218
- /* Mobile Safari */
219
- border: none;
220
- -webkit-appearance: none;
221
- -ms-appearance: none;
222
- -o-appearance: none;
223
- appearance: none;
224
-}
225
-
226
-#todo-list li .toggle:after {
227
- content: '✔';
228
- /* 40 + a couple of pixels visual adjustment */
229
- line-height: 43px;
230
- font-size: 20px;
231
- color: #d9d9d9;
232
- text-shadow: 0 -1px 0 #bfbfbf;
233
-}
234
-
235
-#todo-list li .toggle:checked:after {
236
- color: #85ada7;
237
- text-shadow: 0 1px 0 #669991;
238
- bottom: 1px;
239
- position: relative;
240
-}
241
-
242
-#todo-list li label {
243
- white-space: pre;
244
- word-break: break-word;
245
- padding: 15px 60px 15px 15px;
246
- margin-left: 45px;
247
- display: block;
248
- line-height: 1.2;
249
- -webkit-transition: color 0.4s;
250
- transition: color 0.4s;
251
-}
252
-
253
-#todo-list li.completed label {
254
- color: #a9a9a9;
255
- text-decoration: line-through;
256
-}
257
-
258
-#todo-list li .destroy {
259
- display: none;
260
- position: absolute;
261
- top: 0;
262
- right: 10px;
263
- bottom: 0;
264
- width: 40px;
265
- height: 40px;
266
- margin: auto 0;
267
- font-size: 22px;
268
- color: #a88a8a;
269
- -webkit-transition: all 0.2s;
270
- transition: all 0.2s;
271
-}
272
-
273
-#todo-list li .destroy:hover {
274
- text-shadow: 0 0 1px #000,
275
- 0 0 10px rgba(199, 107, 107, 0.8);
276
- -webkit-transform: scale(1.3);
277
- -ms-transform: scale(1.3);
278
- transform: scale(1.3);
279
-}
280
-
281
-#todo-list li .destroy:after {
282
- content: '✖';
283
-}
284
-
285
-#todo-list li:hover .destroy {
286
- display: block;
287
-}
288
-
289
-#todo-list li .edit {
290
- display: none;
291
-}
292
-
293
-#todo-list li.editing:last-child {
294
- margin-bottom: -1px;
295
-}
296
-
297
-#footer {
298
- color: #777;
299
- padding: 0 15px;
300
- position: absolute;
301
- right: 0;
302
- bottom: -31px;
303
- left: 0;
304
- height: 20px;
305
- z-index: 1;
306
- text-align: center;
307
-}
308
-
309
-#footer:before {
310
- content: '';
311
- position: absolute;
312
- right: 0;
313
- bottom: 31px;
314
- left: 0;
315
- height: 50px;
316
- z-index: -1;
317
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
318
- 0 6px 0 -3px rgba(255, 255, 255, 0.8),
319
- 0 7px 1px -3px rgba(0, 0, 0, 0.3),
320
- 0 43px 0 -6px rgba(255, 255, 255, 0.8),
321
- 0 44px 2px -6px rgba(0, 0, 0, 0.2);
322
-}
323
-
324
-#todo-count {
325
- float: left;
326
- text-align: left;
327
-}
328
-
329
-#filters {
330
- margin: 0;
331
- padding: 0;
332
- list-style: none;
333
- position: absolute;
334
- right: 0;
335
- left: 0;
336
-}
337
-
338
-#filters li {
339
- display: inline;
340
-}
341
-
342
-#filters li a {
343
- color: #83756f;
344
- margin: 2px;
345
- text-decoration: none;
346
-}
347
-
348
-#filters li a.selected {
349
- font-weight: bold;
350
-}
351
-
352
-#clear-completed {
353
- float: right;
354
- position: relative;
355
- line-height: 20px;
356
- text-decoration: none;
357
- background: rgba(0, 0, 0, 0.1);
358
- font-size: 11px;
359
- padding: 0 10px;
360
- border-radius: 3px;
361
- box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
362
-}
363
-
364
-#clear-completed:hover {
365
- background: rgba(0, 0, 0, 0.15);
366
- box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
367
-}
368
-
369
-#info {
370
- margin: 65px auto 0;
371
- color: #a6a6a6;
372
- font-size: 12px;
373
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
374
- text-align: center;
375
-}
376
-
377
-#info a {
378
- color: inherit;
379
-}
380
-
381
-/*
382
- Hack to remove background from Mobile Safari.
383
- Can't use it globally since it destroys checkboxes in Firefox and Opera
384
-*/
385
-
386
-@media screen and (-webkit-min-device-pixel-ratio:0) {
387
- #toggle-all,
388
- #todo-list li .toggle {
389
- background: none;
390
- }
391
-
392
- #todo-list li .toggle {
393
- height: 40px;
394
- }
395
-
396
- #toggle-all {
397
- top: -56px;
398
- left: -15px;
399
- width: 65px;
400
- height: 41px;
401
- -webkit-transform: rotate(90deg);
402
- -ms-transform: rotate(90deg);
403
- transform: rotate(90deg);
404
- -webkit-appearance: none;
405
- appearance: none;
406
- }
407
-}
408
-
409
-.hidden {
410
- display: none;
411
-}
412
-
413
-hr {
414
- margin: 20px 0;
415
- border: 0;
416
- border-top: 1px dashed #C5C5C5;
417
- border-bottom: 1px dashed #F7F7F7;
418
-}
419
-
420
-.learn a {
421
- font-weight: normal;
422
- text-decoration: none;
423
- color: #b83f45;
424
-}
425
-
426
-.learn a:hover {
427
- text-decoration: underline;
428
- color: #787e7e;
429
-}
430
-
431
-.learn h3,
432
-.learn h4,
433
-.learn h5 {
434
- margin: 10px 0;
435
- font-weight: 500;
436
- line-height: 1.2;
437
- color: #000;
438
-}
439
-
440
-.learn h3 {
441
- font-size: 24px;
442
-}
443
-
444
-.learn h4 {
445
- font-size: 18px;
446
-}
447
-
448
-.learn h5 {
449
- margin-bottom: 0;
450
- font-size: 14px;
451
-}
452
-
453
-.learn ul {
454
- padding: 0;
455
- margin: 0 0 30px 25px;
456
-}
457
-
458
-.learn li {
459
- line-height: 20px;
460
-}
461
-
462
-.learn p {
463
- font-size: 15px;
464
- font-weight: 300;
465
- line-height: 1.3;
466
- margin-top: 0;
467
- margin-bottom: 0;
468
-}
469
-
470
-.quote {
471
- border: none;
472
- margin: 20px 0 60px 0;
473
-}
474
-
475
-.quote p {
476
- font-style: italic;
477
-}
478
-
479
-.quote p:before {
480
- content: '“';
481
- font-size: 50px;
482
- opacity: .15;
483
- position: absolute;
484
- top: -20px;
485
- left: 3px;
486
-}
487
-
488
-.quote p:after {
489
- content: '”';
490
- font-size: 50px;
491
- opacity: .15;
492
- position: absolute;
493
- bottom: -42px;
494
- right: 3px;
495
-}
496
-
497
-.quote footer {
498
- position: absolute;
499
- bottom: -40px;
500
- right: 0;
501
-}
502
-
503
-.quote footer img {
504
- border-radius: 3px;
505
-}
506
-
507
-.quote footer a {
508
- margin-left: 5px;
509
- vertical-align: middle;
510
-}
511
-
512
-.speech-bubble {
513
- position: relative;
514
- padding: 10px;
515
- background: rgba(0, 0, 0, .04);
516
- border-radius: 5px;
517
-}
518
-
519
-.speech-bubble:after {
520
- content: '';
521
- position: absolute;
522
- top: 100%;
523
- right: 30px;
524
- border: 13px solid transparent;
525
- border-top-color: rgba(0, 0, 0, .04);
526
-}
527
-
528
-.learn-bar > .learn {
529
- position: absolute;
530
- width: 272px;
531
- top: 8px;
532
- left: -300px;
533
- padding: 10px;
534
- border-radius: 5px;
535
- background-color: rgba(255, 255, 255, .6);
536
- -webkit-transition-property: left;
537
- transition-property: left;
538
- -webkit-transition-duration: 500ms;
539
- transition-duration: 500ms;
540
-}
541
-
542
-@media (min-width: 899px) {
543
- .learn-bar {
544
- width: auto;
545
- margin: 0 0 0 300px;
546
- }
547
-
548
- .learn-bar > .learn {
549
- left: 8px;
550
- }
551
-
552
- .learn-bar #todoapp {
553
- width: 550px;
554
- margin: 130px auto 40px auto;
555
- }
556
-}
examples/todomvc-flux/todomvc-common/bg.png
Binary files a/examples/todomvc-flux/todomvc-common/bg.png and /dev/null differ
examples/todomvc-flux/todomvc-common/bower.json
deleted
-4
@@ -1,4 +0,0 @@
1
-{
2
- "name": "todomvc-common",
3
- "version": "0.1.9"
4
-}
examples/todomvc-flux/todomvc-common/readme.md
deleted
-8
@@ -1,8 +0,0 @@
1
-# todomvc-common
2
-
3
-> Bower component for some common utilities we use in every app
4
-
5
-
6
-## License
7
-
8
-MIT
grunt/tasks/release.js
+1
-16
@@ -15,22 +15,7 @@ var GH_PAGES_PATH = '../react-gh-pages/';
15
var GH_PAGES_GLOB = [GH_PAGES_PATH + '*'];
16
17
var EXAMPLES_PATH = 'examples/';
18
-var EXAMPLES_GLOB = [
19
- // todomvc-flux has too many other dependencies, so we'll leave it out
20
- EXAMPLES_PATH + '/README.md',
21
- EXAMPLES_PATH + '/ballmer-peak/**/*.*',
22
- EXAMPLES_PATH + '/basic/**/*.*',
23
- EXAMPLES_PATH + '/basic-commonjs/**/*.*',
24
- EXAMPLES_PATH + '/basic-jsx/**/*.*',
25
- EXAMPLES_PATH + '/basic-jsx-external/**/*.*',
26
- EXAMPLES_PATH + '/basic-jsx-harmony/**/*.*',
27
- EXAMPLES_PATH + '/basic-jsx-precompile/**/*.*',
28
- EXAMPLES_PATH + '/jquery-bootstrap/**/*.*',
29
- EXAMPLES_PATH + '/jquery-mobile/**/*.*',
30
- EXAMPLES_PATH + '/server-rendering/**/*.*',
31
- EXAMPLES_PATH + '/shared/**/*.*',
32
- EXAMPLES_PATH + '/transitions/**/*.*'
33
-];
18
+var EXAMPLES_GLOB = [EXAMPLES_PATH + '**/*.*'];
19
20
var STARTER_PATH = 'starter/';
21
var STARTER_GLOB = [STARTER_PATH + '/**/*.*'];