diff --git a/examples/todomvc-flux/css/app.css b/examples/todomvc-flux/css/app.css
new file mode 100644
index 0000000000..2baee13e9a
--- /dev/null
+++ b/examples/todomvc-flux/css/app.css
@@ -0,0 +1,25 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * base.css overrides
+ */
+
+/**
+ * We are not changing from display:none, but rather re-rendering instead.
+ * Therefore this needs to be displayed normally by default.
+ */
+#todo-list li .edit {
+ display: inline;
+}
\ No newline at end of file
diff --git a/examples/todomvc-flux/index.html b/examples/todomvc-flux/index.html
new file mode 100644
index 0000000000..e7660c5c82
--- /dev/null
+++ b/examples/todomvc-flux/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+ Flux • TodoMVC
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/todomvc-flux/js/actions/TodoActions.js b/examples/todomvc-flux/js/actions/TodoActions.js
new file mode 100644
index 0000000000..ae86061a5e
--- /dev/null
+++ b/examples/todomvc-flux/js/actions/TodoActions.js
@@ -0,0 +1,95 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * TodoActions
+ */
+
+var AppDispatcher = require('../dispatcher/AppDispatcher');
+var TodoConstants = require('../constants/TodoConstants');
+
+var TodoActions = {
+
+ /**
+ * @param {string} text
+ */
+ create: function(text) {
+ AppDispatcher.handleViewAction({
+ actionType: TodoConstants.TODO_CREATE,
+ text: text
+ });
+ },
+
+ /**
+ * @param {string} id The ID of the ToDo item
+ * @param {string} text
+ */
+ updateText: function(id, text) {
+ AppDispatcher.handleViewAction({
+ actionType: TodoConstants.TODO_UPDATE_TEXT,
+ id: id,
+ text: text
+ });
+ },
+
+ /**
+ * Toggle whether a single ToDo is complete
+ * @param {object} todo
+ */
+ toggleComplete: function(todo) {
+ var id = todo.id;
+ if (todo.complete) {
+ AppDispatcher.handleViewAction({
+ actionType: TodoConstants.TODO_UNDO_COMPLETE,
+ id: id
+ });
+ } else {
+ AppDispatcher.handleViewAction({
+ actionType: TodoConstants.TODO_COMPLETE,
+ id: id
+ });
+ }
+ },
+
+ /**
+ * Mark all ToDos as complete
+ */
+ toggleCompleteAll: function() {
+ AppDispatcher.handleViewAction({
+ actionType: TodoConstants.TODO_TOGGLE_COMPLETE_ALL
+ });
+ },
+
+ /**
+ * @param {string} id
+ */
+ destroy: function(id) {
+ AppDispatcher.handleViewAction({
+ actionType: TodoConstants.TODO_DESTROY,
+ id: id
+ });
+ },
+
+ /**
+ * Delete all the completed ToDos
+ */
+ destroyCompleted: function() {
+ AppDispatcher.handleViewAction({
+ actionType: TodoConstants.TODO_DESTROY_COMPLETED
+ });
+ }
+
+};
+
+module.exports = TodoActions;
diff --git a/examples/todomvc-flux/js/app.js b/examples/todomvc-flux/js/app.js
new file mode 100644
index 0000000000..a2c793a22e
--- /dev/null
+++ b/examples/todomvc-flux/js/app.js
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @jsx React.DOM
+ */
+
+var React = require('react');
+
+var TodoApp = require('./components/TodoApp.react');
+
+React.renderComponent(
+ ,
+ document.getElementById('todoapp')
+);
\ No newline at end of file
diff --git a/examples/todomvc-flux/js/components/Footer.react.js b/examples/todomvc-flux/js/components/Footer.react.js
new file mode 100644
index 0000000000..ba985b42e5
--- /dev/null
+++ b/examples/todomvc-flux/js/components/Footer.react.js
@@ -0,0 +1,84 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @jsx React.DOM
+ */
+
+var React = require('react');
+var ReactPropTypes = React.PropTypes;
+var TodoActions = require('../actions/TodoActions');
+
+var Footer = React.createClass({
+
+ propTypes: {
+ allTodos: ReactPropTypes.object.isRequired
+ },
+
+ /**
+ * @return {object}
+ */
+ render: function() {
+ var allTodos = this.props.allTodos;
+ var total = Object.keys(allTodos).length;
+
+ if (total === 0) {
+ return ;
+ }
+
+ var completed = 0;
+ for (var key in allTodos) {
+ if (allTodos[key].complete) {
+ completed++;
+ }
+ }
+
+ var itemsLeft = total - completed;
+ var itemsLeftPhrase = itemsLeft === 1 ? ' item ' : ' items ';
+ itemsLeftPhrase += 'left';
+
+ // Undefined and thus not rendered if no completed items are left.
+ var clearCompletedButton;
+ if (completed) {
+ clearCompletedButton =
+ ;
+ }
+
+ return (
+
+ );
+ },
+
+ /**
+ * Event handler to delete all completed TODOs
+ */
+ _onClearCompletedClick: function() {
+ TodoActions.destroyCompleted();
+ }
+
+});
+
+module.exports = Footer;
diff --git a/examples/todomvc-flux/js/components/Header.react.js b/examples/todomvc-flux/js/components/Header.react.js
new file mode 100644
index 0000000000..cbf7b6a32a
--- /dev/null
+++ b/examples/todomvc-flux/js/components/Header.react.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @jsx React.DOM
+ */
+
+var React = require('react');
+var TodoActions = require('../actions/TodoActions');
+var TodoTextInput = require('./TodoTextInput.react');
+
+var Header = React.createClass({
+
+ /**
+ * @return {object}
+ */
+ render: function() {
+ return (
+
+
todos
+
+
+ );
+ },
+
+ /**
+ * Event handler called within TodoTextInput.
+ * Defining this here allows TodoTextInput to be used in multiple places
+ * in different ways.
+ * @param {string} text
+ */
+ _onSave: function(text) {
+ TodoActions.create(text);
+ }
+
+});
+
+module.exports = Header;
diff --git a/examples/todomvc-flux/js/components/MainSection.react.js b/examples/todomvc-flux/js/components/MainSection.react.js
new file mode 100644
index 0000000000..0b7efb0f76
--- /dev/null
+++ b/examples/todomvc-flux/js/components/MainSection.react.js
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @jsx React.DOM
+ */
+
+var React = require('react');
+var ReactPropTypes = React.PropTypes;
+var TodoActions = require('../actions/TodoActions');
+var TodoItem = require('./TodoItem.react');
+
+var MainSection = React.createClass({
+
+ propTypes: {
+ allTodos: ReactPropTypes.object.isRequired,
+ areAllComplete: ReactPropTypes.bool.isRequired
+ },
+
+ /**
+ * @return {object}
+ */
+ render: function() {
+ // This section should be hidden by default
+ // and shown when there are todos.
+ if (Object.keys(this.props.allTodos).length < 1) {
+ return ;
+ }
+
+ var allTodos = this.props.allTodos;
+ var todos = [];
+
+ for (var key in allTodos) {
+ todos.push();
+ }
+
+ return (
+
+
+
+
{todos}
+
+ );
+ },
+
+ /**
+ * Event handler to mark all TODOs as complete
+ */
+ _onToggleCompleteAll: function() {
+ TodoActions.toggleCompleteAll();
+ }
+
+});
+
+module.exports = MainSection;
diff --git a/examples/todomvc-flux/js/components/TodoApp.react.js b/examples/todomvc-flux/js/components/TodoApp.react.js
new file mode 100644
index 0000000000..803476685f
--- /dev/null
+++ b/examples/todomvc-flux/js/components/TodoApp.react.js
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @jsx React.DOM
+ */
+
+/**
+ * This component operates as a "Controller-View". It listens for changes in
+ * the TodoStore and passes the new data to its children.
+ */
+
+var Footer = require('./Footer.react');
+var Header = require('./Header.react');
+var MainSection = require('./MainSection.react');
+var React = require('react');
+var TodoStore = require('../stores/TodoStore');
+
+/**
+ * Retrieve the current TODO data from the TodoStore
+ */
+function getTodoState() {
+ return {
+ allTodos: TodoStore.getAll(),
+ areAllComplete: TodoStore.areAllComplete()
+ };
+}
+
+var TodoApp = React.createClass({
+
+ getInitialState: function() {
+ return getTodoState();
+ },
+
+ componentDidMount: function() {
+ TodoStore.addChangeListener(this._onChange);
+ },
+
+ componentWillUnmount: function() {
+ TodoStore.removeChangeListener(this._onChange);
+ },
+
+ /**
+ * @return {object}
+ */
+ render: function() {
+ return (
+
+
+
+
+
+ );
+ },
+
+ /**
+ * Event handler for 'change' events coming from the TodoStore
+ */
+ _onChange: function() {
+ this.setState(getTodoState());
+ }
+
+});
+
+module.exports = TodoApp;
diff --git a/examples/todomvc-flux/js/components/TodoItem.react.js b/examples/todomvc-flux/js/components/TodoItem.react.js
new file mode 100644
index 0000000000..6dd9ae78a8
--- /dev/null
+++ b/examples/todomvc-flux/js/components/TodoItem.react.js
@@ -0,0 +1,108 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @jsx React.DOM
+ */
+
+var React = require('react');
+var ReactPropTypes = React.PropTypes;
+var TodoActions = require('../actions/TodoActions');
+var TodoTextInput = require('./TodoTextInput.react');
+
+var cx = require('react/lib/cx');
+
+var TodoItem = React.createClass({
+
+ propTypes: {
+ todo: ReactPropTypes.object.isRequired
+ },
+
+ getInitialState: function() {
+ return {
+ isEditing: false
+ };
+ },
+
+ /**
+ * @return {object}
+ */
+ render: function() {
+ var todo = this.props.todo;
+
+ var input;
+ if (this.state.isEditing) {
+ input =
+ ;
+ }
+
+ // List items should get the class 'editing' when editing
+ // and 'completed' when marked as completed.
+ // Note that 'completed' is a classification while 'complete' is a state.
+ // This differentiation between classification and state becomes important
+ // in the naming of view actions toggleComplete() vs. destroyCompleted().
+ return (
+
+
+
+
+
+
+ {input}
+
+ );
+ },
+
+ _onToggleComplete: function() {
+ TodoActions.toggleComplete(this.props.todo);
+ },
+
+ _onDoubleClick: function() {
+ this.setState({isEditing: true});
+ },
+
+ /**
+ * Event handler called within TodoTextInput.
+ * Defining this here allows TodoTextInput to be used in multiple places
+ * in different ways.
+ * @param {string} text
+ */
+ _onSave: function(text) {
+ TodoActions.updateText(this.props.todo.id, text);
+ this.setState({isEditing: false});
+ },
+
+ _onDestroyClick: function() {
+ TodoActions.destroy(this.props.todo.id);
+ }
+
+});
+
+module.exports = TodoItem;
diff --git a/examples/todomvc-flux/js/components/TodoTextInput.react.js b/examples/todomvc-flux/js/components/TodoTextInput.react.js
new file mode 100644
index 0000000000..2253fa50c8
--- /dev/null
+++ b/examples/todomvc-flux/js/components/TodoTextInput.react.js
@@ -0,0 +1,89 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @jsx React.DOM
+ */
+
+var React = require('react');
+var ReactPropTypes = React.PropTypes;
+
+var ENTER_KEY_CODE = 13;
+
+var TodoTextInput = React.createClass({
+
+ propTypes: {
+ className: ReactPropTypes.string,
+ id: ReactPropTypes.string,
+ placeholder: ReactPropTypes.string,
+ onSave: ReactPropTypes.func.isRequired,
+ value: ReactPropTypes.string
+ },
+
+ getInitialState: function() {
+ return {
+ value: this.props.value || ''
+ };
+ },
+
+ /**
+ * @return {object}
+ */
+ render: function() /*object*/ {
+ return (
+
+ );
+ },
+
+ /**
+ * Invokes the callback passed in as onSave, allowing this component to be
+ * used in different ways.
+ */
+ _save: function() {
+ this.props.onSave(this.state.value);
+ this.setState({
+ value: ''
+ });
+ },
+
+ /**
+ * @param {object} event
+ */
+ _onChange: function(/*object*/ event) {
+ this.setState({
+ value: event.target.value
+ });
+ },
+
+ /**
+ * @param {object} event
+ */
+ _onKeyDown: function(event) {
+ if (event.keyCode === ENTER_KEY_CODE) {
+ this._save();
+ }
+ }
+
+});
+
+module.exports = TodoTextInput;
diff --git a/examples/todomvc-flux/js/constants/TodoConstants.js b/examples/todomvc-flux/js/constants/TodoConstants.js
new file mode 100644
index 0000000000..73127c504c
--- /dev/null
+++ b/examples/todomvc-flux/js/constants/TodoConstants.js
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * TodoConstants
+ */
+
+var keyMirror = require('react/lib/keyMirror');
+
+module.exports = keyMirror({
+ TODO_CREATE: null,
+ TODO_COMPLETE: null,
+ TODO_DESTROY: null,
+ TODO_DESTROY_COMPLETED: null,
+ TODO_TOGGLE_COMPLETE_ALL: null,
+ TODO_UNDO_COMPLETE: null,
+ TODO_UPDATE_TEXT: null
+});
diff --git a/examples/todomvc-flux/js/dispatcher/AppDispatcher.js b/examples/todomvc-flux/js/dispatcher/AppDispatcher.js
new file mode 100644
index 0000000000..7e3b32babe
--- /dev/null
+++ b/examples/todomvc-flux/js/dispatcher/AppDispatcher.js
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * AppDispatcher
+ *
+ * A singleton that operates as the central hub for application updates.
+ */
+
+var Dispatcher = require('./Dispatcher');
+
+var merge = require('react/lib/merge');
+
+var AppDispatcher = merge(Dispatcher.prototype, {
+
+ /**
+ * A bridge function between the views and the dispatcher, marking the action
+ * as a view action. Another variant here could be handleServerAction.
+ * @param {object} action The data coming from the view.
+ */
+ handleViewAction: function(action) {
+ this.dispatch({
+ source: 'VIEW_ACTION',
+ action: action
+ });
+ }
+
+});
+
+module.exports = AppDispatcher;
diff --git a/examples/todomvc-flux/js/dispatcher/Dispatcher.js b/examples/todomvc-flux/js/dispatcher/Dispatcher.js
new file mode 100644
index 0000000000..f3b4f9838b
--- /dev/null
+++ b/examples/todomvc-flux/js/dispatcher/Dispatcher.js
@@ -0,0 +1,125 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Dispatcher
+ *
+ * The Dispatcher is capable of registering callbacks and invoking them.
+ * More robust implementations than this would include a way to order the
+ * callbacks for dependent Stores, and to guarantee that no two stores
+ * created circular dependencies.
+ */
+
+var Promise = require('es6-promise').Promise;
+var merge = require('react/lib/merge');
+
+var _callbacks = [];
+var _promises = [];
+
+/**
+ * Add a promise to the queue of callback invocation promises.
+ * @param {function} callback The Store's registered callback.
+ * @param {object} payload The data from the Action.
+ */
+var _addPromise = function(callback, payload) {
+ _promises.push(new Promise(function(resolve, reject) {
+ if (callback(payload)) {
+ resolve(payload);
+ } else {
+ reject(new Error('Dispatcher callback unsuccessful'));
+ }
+ }));
+};
+
+/**
+ * Empty the queue of callback invocation promises.
+ */
+var _clearPromises = function() {
+ _promises = [];
+};
+
+/**
+ * Used below in waitFor().
+ * @param {number} index The index within the _promises array
+ */
+var _getPromise = function(index) {
+ return _promises[index];
+};
+
+var Dispatcher = function() {};
+Dispatcher.prototype = merge(Dispatcher.prototype, {
+
+ /**
+ * Register a Store's callback so that it may be invoked by an action.
+ * @param {function} callback The callback to be registered.
+ * @return {number} The index of the callback within the _callbacks array.
+ */
+ register: function(callback) {
+ _callbacks.push(callback);
+ return _callbacks.length - 1; // index
+ },
+
+ /**
+ * dispatch
+ * @param {object} payload The data from the action.
+ */
+ dispatch: function(payload) {
+ _callbacks.forEach(function(callback) {
+ _addPromise(callback, payload);
+ });
+ Promise.all(_promises).then(_clearPromises);
+ },
+
+ /**
+ * Allows a store to wait for the registered callbacks of other stores
+ * to get invoked before its own does.
+ * This function is not used by this TodoMVC example application, but
+ * it is very useful in a larger, more complex application.
+ *
+ * Example usage where StoreB waits for StoreA:
+ *
+ * var StoreA = merge(EventEmitter.prototype, {
+ * // other methods omitted
+ *
+ * dispatchIndex: Dispatcher.register(function(payload) {
+ * // switch statement with lots of cases
+ * })
+ * }
+ *
+ * var StoreB = merge(EventEmitter.prototype, {
+ * // other methods omitted
+ *
+ * dispatchIndex: Dispatcher.register(function(payload) {
+ * switch(payload.action.actionType) {
+ *
+ * case MyConstants.FOO_ACTION:
+ * Dispatcher.waitFor([StoreA.dispatchIndex], function() {
+ * // Do stuff only after StoreA's callback returns.
+ * });
+ * }
+ * })
+ * }
+ *
+ * It should be noted that if StoreB waits for StoreA, and StoreA waits for
+ * StoreB, a circular dependency will occur, but no error will be thrown.
+ * A more robust Dispatcher would issue a warning in this scenario.
+ */
+ waitFor: function(/*array*/ promiseIndexes, /*function*/ callback) {
+ var selectedPromises = promiseIndexes.filter(_getPromise);
+ Promise.all(selectedPromises).then(callback);
+ }
+
+});
+
+module.exports = Dispatcher;
diff --git a/examples/todomvc-flux/js/stores/TodoStore.js b/examples/todomvc-flux/js/stores/TodoStore.js
new file mode 100644
index 0000000000..38c13b7d68
--- /dev/null
+++ b/examples/todomvc-flux/js/stores/TodoStore.js
@@ -0,0 +1,186 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * TodoStore
+ */
+
+var AppDispatcher = require('../dispatcher/AppDispatcher');
+var EventEmitter = require('events').EventEmitter;
+var TodoConstants = require('../constants/TodoConstants');
+var merge = require('react/lib/merge');
+
+var CHANGE_EVENT = 'change';
+
+var _todos = {};
+
+/**
+ * Create a TODO item.
+ * @param {string} text The content of the TODO
+ */
+function create(text) {
+ // Hand waving here -- not showing how this interacts with XHR or persistent
+ // server-side storage.
+ // Using the current timestamp in place of a real id.
+ var id = Date.now();
+ _todos[id] = {
+ id: id,
+ complete: false,
+ text: text
+ };
+}
+
+/**
+ * Update a TODO item.
+ * @param {string} id
+ * @param {object} updates An object literal containing only the data to be
+ * updated.
+ */
+function update(id, updates) {
+ _todos[id] = merge(_todos[id], updates);
+}
+
+/**
+ * Update all of the TODO items with the same object.
+ * the data to be updated. Used to mark all TODOs as completed.
+ * @param {object} updates An object literal containing only the data to be
+ * updated.
+
+ */
+function updateAll(updates) {
+ for (var id in _todos) {
+ update(id, updates);
+ }
+}
+
+/**
+ * Delete a TODO item.
+ * @param {string} id
+ */
+function destroy(id) {
+ delete _todos[id];
+}
+
+/**
+ * Delete all the completed TODO items.
+ */
+function destroyCompleted() {
+ for (var id in _todos) {
+ if (_todos[id].complete) {
+ destroy(id);
+ }
+ }
+}
+
+var TodoStore = merge(EventEmitter.prototype, {
+
+ /**
+ * Tests whether all the remaining TODO items are marked as completed.
+ * @return {booleam}
+ */
+ areAllComplete: function() {
+ for (id in _todos) {
+ if (!_todos[id].complete) {
+ return false;
+ break;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Get the entire collection of TODOs.
+ * @return {object}
+ */
+ getAll: function() {
+ return _todos;
+ },
+
+ emitChange: function() {
+ this.emit(CHANGE_EVENT);
+ },
+
+ /**
+ * @param {function} callback
+ */
+ addChangeListener: function(callback) {
+ this.on(CHANGE_EVENT, callback);
+ },
+
+ /**
+ * @param {function} callback
+ */
+ removeChangeListener: function(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ }
+});
+
+// Register to handle all updates
+AppDispatcher.register(function(payload) {
+ var action = payload.action;
+ var text;
+
+ switch(action.actionType) {
+ case TodoConstants.TODO_CREATE:
+ text = action.text.trim();
+ if (text !== '') {
+ create(text);
+ }
+ break;
+
+ case TodoConstants.TODO_TOGGLE_COMPLETE_ALL:
+ if (TodoStore.areAllComplete()) {
+ updateAll({complete: false});
+ } else {
+ updateAll({complete: true});
+ }
+ break;
+
+ case TodoConstants.TODO_UNDO_COMPLETE:
+ update(action.id, {complete: false});
+ break;
+
+ case TodoConstants.TODO_COMPLETE:
+ update(action.id, {complete: true});
+ break;
+
+ case TodoConstants.TODO_UPDATE_TEXT:
+ text = action.text.trim();
+ if (text !== '') {
+ update(action.id, {text: text});
+ }
+ break;
+
+ case TodoConstants.TODO_DESTROY:
+ destroy(action.id);
+ break;
+
+ case TodoConstants.TODO_DESTROY_COMPLETED:
+ destroyCompleted();
+ break;
+
+ default:
+ return true;
+ }
+
+ // This often goes in each case that should trigger a UI change. This store
+ // needs to trigger a UI change after every view action, so we can make the
+ // code less repetitive by putting it here. We need the default case,
+ // however, to make sure this only gets called after one of the cases above.
+ TodoStore.emitChange();
+
+ return true; // No errors. Needed by promise in Dispatcher.
+})
+
+module.exports = TodoStore;
diff --git a/examples/todomvc-flux/package.json b/examples/todomvc-flux/package.json
new file mode 100644
index 0000000000..548676d0df
--- /dev/null
+++ b/examples/todomvc-flux/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "todomvc-flux",
+ "version": "0.0.1",
+ "description": "Example Flux architecture.",
+ "main": "js/app.js",
+ "dependencies": {
+ "es6-promise": "~0.1.1",
+ "react": "~0.9"
+ },
+ "devDependencies": {
+ "browserify": "~2.36.0",
+ "envify": "~1.2.0",
+ "reactify": "~0.4.0",
+ "statics": "~0.1.0",
+ "uglifyjs": "~2.3.6",
+ "watchify": "~0.4.1"
+ },
+ "scripts": {
+ "start": "STATIC_ROOT=./static watchify -o js/bundle.js -v -d .",
+ "build": "STATIC_ROOT=./static NODE_ENV=production browserify . | uglifyjs -cm > js/bundle.min.js",
+ "collect-static": "collect-static . ./static",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Bill Fisher",
+ "license": "Apache 2",
+ "browserify": {
+ "transform": [
+ "reactify",
+ "envify"
+ ]
+ }
+}
diff --git a/examples/todomvc-flux/readme.md b/examples/todomvc-flux/readme.md
new file mode 100644
index 0000000000..2389d49967
--- /dev/null
+++ b/examples/todomvc-flux/readme.md
@@ -0,0 +1,100 @@
+# Flux TodoMVC Example
+
+> An application architecture for React utilizing a unidirectional data flow.
+
+
+## Learning Flux
+
+The [React website](http://facebook.github.io/react) is a great resource for getting started.
+
+A post on the [React Blog](http://facebook.github.io/react/blog/) is forthcoming to describe the Flux architecture in more detail.
+
+
+## Implementation
+
+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 -- top level views that retrieve data from the Stores and pass this data down to their children.
+
+Data in a Flux application flows in a single direction, in a cycle:
+
+
+
+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 calls the callbacks that the Stores have registered with it, effectively dispatching the data 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 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 View-Controllers call their own render() method via setState() or forceUpdate(), updating themselves and all of their children.
+
+In this TodoMVC example application, we can see these elements in our directory structure. Views here are referred to as "components" as they are React components.
+
+