@samitouri / QOS-React-2 / commits / 85339bfdae

TodoMVC Flux Example

fisherwebdev committed Mar 14, 2014 at 10:37 UTC 85339bfdae5cd622fb97dc8c681376ba24e847da
22 files changed +1953
examples/todomvc-flux/css/app.css new
+25
@@ -0,0 +1,25 @@
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 new
+19
@@ -0,0 +1,19 @@
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" />
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="todomvc-common/base.js"></script>
17 + <script src="js/bundle.js"></script>
18 + </body>
19 +</html>
examples/todomvc-flux/js/actions/TodoActions.js new
+95
@@ -0,0 +1,95 @@
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 new
+26
@@ -0,0 +1,26 @@
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 new
+84
@@ -0,0 +1,84 @@
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 <noscript />;
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 new
+53
@@ -0,0 +1,53 @@
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 + TodoActions.create(text);
49 + }
50 +
51 +});
52 +
53 +module.exports = Header;
examples/todomvc-flux/js/components/MainSection.react.js new
+71
@@ -0,0 +1,71 @@
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 <noscript />;
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 new
+79
@@ -0,0 +1,79 @@
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 new
+108
@@ -0,0 +1,108 @@
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 new
+89
@@ -0,0 +1,89 @@
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 new
+29
@@ -0,0 +1,29 @@
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 new
+41
@@ -0,0 +1,41 @@
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 new
+125
@@ -0,0 +1,125 @@
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 +/**
31 + * Add a promise to the queue of callback invocation promises.
32 + * @param {function} callback The Store's registered callback.
33 + * @param {object} payload The data from the Action.
34 + */
35 +var _addPromise = function(callback, payload) {
36 + _promises.push(new Promise(function(resolve, reject) {
37 + if (callback(payload)) {
38 + resolve(payload);
39 + } else {
40 + reject(new Error('Dispatcher callback unsuccessful'));
41 + }
42 + }));
43 +};
44 +
45 +/**
46 + * Empty the queue of callback invocation promises.
47 + */
48 +var _clearPromises = function() {
49 + _promises = [];
50 +};
51 +
52 +/**
53 + * Used below in waitFor().
54 + * @param {number} index The index within the _promises array
55 + */
56 +var _getPromise = function(index) {
57 + return _promises[index];
58 +};
59 +
60 +var Dispatcher = function() {};
61 +Dispatcher.prototype = merge(Dispatcher.prototype, {
62 +
63 + /**
64 + * Register a Store's callback so that it may be invoked by an action.
65 + * @param {function} callback The callback to be registered.
66 + * @return {number} The index of the callback within the _callbacks array.
67 + */
68 + register: function(callback) {
69 + _callbacks.push(callback);
70 + return _callbacks.length - 1; // index
71 + },
72 +
73 + /**
74 + * dispatch
75 + * @param {object} payload The data from the action.
76 + */
77 + dispatch: function(payload) {
78 + _callbacks.forEach(function(callback) {
79 + _addPromise(callback, payload);
80 + });
81 + Promise.all(_promises).then(_clearPromises);
82 + },
83 +
84 + /**
85 + * Allows a store to wait for the registered callbacks of other stores
86 + * to get invoked before its own does.
87 + * This function is not used by this TodoMVC example application, but
88 + * it is very useful in a larger, more complex application.
89 + *
90 + * Example usage where StoreB waits for StoreA:
91 + *
92 + * var StoreA = merge(EventEmitter.prototype, {
93 + * // other methods omitted
94 + *
95 + * dispatchIndex: Dispatcher.register(function(payload) {
96 + * // switch statement with lots of cases
97 + * })
98 + * }
99 + *
100 + * var StoreB = merge(EventEmitter.prototype, {
101 + * // other methods omitted
102 + *
103 + * dispatchIndex: Dispatcher.register(function(payload) {
104 + * switch(payload.action.actionType) {
105 + *
106 + * case MyConstants.FOO_ACTION:
107 + * Dispatcher.waitFor([StoreA.dispatchIndex], function() {
108 + * // Do stuff only after StoreA's callback returns.
109 + * });
110 + * }
111 + * })
112 + * }
113 + *
114 + * It should be noted that if StoreB waits for StoreA, and StoreA waits for
115 + * StoreB, a circular dependency will occur, but no error will be thrown.
116 + * A more robust Dispatcher would issue a warning in this scenario.
117 + */
118 + waitFor: function(/*array*/ promiseIndexes, /*function*/ callback) {
119 + var selectedPromises = promiseIndexes.filter(_getPromise);
120 + Promise.all(selectedPromises).then(callback);
121 + }
122 +
123 +});
124 +
125 +module.exports = Dispatcher;
examples/todomvc-flux/js/stores/TodoStore.js new
+186
@@ -0,0 +1,186 @@
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 {booleam}
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 new
+32
@@ -0,0 +1,32 @@
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.9"
9 + },
10 + "devDependencies": {
11 + "browserify": "~2.36.0",
12 + "envify": "~1.2.0",
13 + "reactify": "~0.4.0",
14 + "statics": "~0.1.0",
15 + "uglifyjs": "~2.3.6",
16 + "watchify": "~0.4.1"
17 + },
18 + "scripts": {
19 + "start": "STATIC_ROOT=./static watchify -o js/bundle.js -v -d .",
20 + "build": "STATIC_ROOT=./static NODE_ENV=production browserify . | uglifyjs -cm > js/bundle.min.js",
21 + "collect-static": "collect-static . ./static",
22 + "test": "echo \"Error: no test specified\" && exit 1"
23 + },
24 + "author": "Bill Fisher",
25 + "license": "Apache 2",
26 + "browserify": {
27 + "transform": [
28 + "reactify",
29 + "envify"
30 + ]
31 + }
32 +}
examples/todomvc-flux/readme.md new
+100
@@ -0,0 +1,100 @@
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 +A post on the [React Blog](http://facebook.github.io/react/blog/) is forthcoming to describe the Flux architecture in more detail.
11 +
12 +
13 +## Implementation
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 -- top level views that retrieve data from the Stores and pass this data down to their children.
16 +
17 +Data in a Flux application flows in a single direction, in a cycle:
18 +
19 +<pre>
20 +Views ---> (actions) ----> Dispatcher ---> (registerd callback) ---> Stores --------+
21 +Ʌ |
22 +| V
23 ++-- (Controller-Views "change" event handlers) ---- (Stores emit "change" events) --+
24 +</pre>
25 +
26 +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.
27 +
28 +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.
29 +
30 +<pre>
31 +./
32 + index.html
33 + js/
34 + actions/
35 + TodoActions.js
36 + app.js
37 + bundle.js
38 + dispatcher/
39 + AppDispatcher.js
40 + Dispatcher.js
41 + components/
42 + Footer.react.js
43 + Header.react.js
44 + MainSection.react.js
45 + TodoApp.react.js
46 + TodoItem.react.js
47 + TodoTextInput.react.js
48 + stores/
49 + TodoStore.js
50 +</pre>
51 +
52 +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.
53 +
54 +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.
55 +
56 +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.
57 +
58 +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.
59 +
60 +The bundle.js file is automatically genenerated by the build process, explained below.
61 +
62 +
63 +## Running
64 +
65 +You must have [npm](https://www.npmjs.org/) installed on your computer.
66 +From the root project directory run these commands from the command line:
67 +
68 + npm install
69 +
70 +This will install all dependencies.
71 +
72 +To build the project, first run this command:
73 +
74 + npm start
75 +
76 +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).
77 +
78 +To run the app, spin up an HTTP server and visit http://localhost/.../todomvc-flux/.
79 +
80 +
81 +## Credit
82 +
83 +This TodoMVC application was created by [Bill Fisher](https://www.facebook.com/bill.fisher.771).
84 +
85 +
86 +## License
87 +
88 +> Copyright 2013-2014 Facebook, Inc.
89 +>
90 +> Licensed under the Apache License, Version 2.0 (the "License");
91 +> you may not use this file except in compliance with the License.
92 +> You may obtain a copy of the License at
93 +>
94 +> http://www.apache.org/licenses/LICENSE-2.0
95 +>
96 +> Unless required by applicable law or agreed to in writing, software
97 +> distributed under the License is distributed on an "AS IS" BASIS,
98 +> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
99 +> See the License for the specific language governing permissions and
100 +> limitations under the License.
examples/todomvc-flux/todomvc-common/.bower.json new
+14
@@ -0,0 +1,14 @@
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 new
+556
@@ -0,0 +1,556 @@
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/base.js new
+209
@@ -0,0 +1,209 @@
1 +(function () {
2 + 'use strict';
3 +
4 + // Underscore's Template Module
5 + // Courtesy of underscorejs.org
6 + var _ = (function (_) {
7 + _.defaults = function (object) {
8 + if (!object) {
9 + return object;
10 + }
11 + for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
12 + var iterable = arguments[argsIndex];
13 + if (iterable) {
14 + for (var key in iterable) {
15 + if (object[key] == null) {
16 + object[key] = iterable[key];
17 + }
18 + }
19 + }
20 + }
21 + return object;
22 + }
23 +
24 + // By default, Underscore uses ERB-style template delimiters, change the
25 + // following template settings to use alternative delimiters.
26 + _.templateSettings = {
27 + evaluate : /<%([\s\S]+?)%>/g,
28 + interpolate : /<%=([\s\S]+?)%>/g,
29 + escape : /<%-([\s\S]+?)%>/g
30 + };
31 +
32 + // When customizing `templateSettings`, if you don't want to define an
33 + // interpolation, evaluation or escaping regex, we need one that is
34 + // guaranteed not to match.
35 + var noMatch = /(.)^/;
36 +
37 + // Certain characters need to be escaped so that they can be put into a
38 + // string literal.
39 + var escapes = {
40 + "'": "'",
41 + '\\': '\\',
42 + '\r': 'r',
43 + '\n': 'n',
44 + '\t': 't',
45 + '\u2028': 'u2028',
46 + '\u2029': 'u2029'
47 + };
48 +
49 + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
50 +
51 + // JavaScript micro-templating, similar to John Resig's implementation.
52 + // Underscore templating handles arbitrary delimiters, preserves whitespace,
53 + // and correctly escapes quotes within interpolated code.
54 + _.template = function(text, data, settings) {
55 + var render;
56 + settings = _.defaults({}, settings, _.templateSettings);
57 +
58 + // Combine delimiters into one regular expression via alternation.
59 + var matcher = new RegExp([
60 + (settings.escape || noMatch).source,
61 + (settings.interpolate || noMatch).source,
62 + (settings.evaluate || noMatch).source
63 + ].join('|') + '|$', 'g');
64 +
65 + // Compile the template source, escaping string literals appropriately.
66 + var index = 0;
67 + var source = "__p+='";
68 + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
69 + source += text.slice(index, offset)
70 + .replace(escaper, function(match) { return '\\' + escapes[match]; });
71 +
72 + if (escape) {
73 + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
74 + }
75 + if (interpolate) {
76 + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
77 + }
78 + if (evaluate) {
79 + source += "';\n" + evaluate + "\n__p+='";
80 + }
81 + index = offset + match.length;
82 + return match;
83 + });
84 + source += "';\n";
85 +
86 + // If a variable is not specified, place data values in local scope.
87 + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
88 +
89 + source = "var __t,__p='',__j=Array.prototype.join," +
90 + "print=function(){__p+=__j.call(arguments,'');};\n" +
91 + source + "return __p;\n";
92 +
93 + try {
94 + render = new Function(settings.variable || 'obj', '_', source);
95 + } catch (e) {
96 + e.source = source;
97 + throw e;
98 + }
99 +
100 + if (data) return render(data, _);
101 + var template = function(data) {
102 + return render.call(this, data, _);
103 + };
104 +
105 + // Provide the compiled function source as a convenience for precompilation.
106 + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
107 +
108 + return template;
109 + };
110 +
111 + return _;
112 + })({});
113 +
114 + if (location.hostname === 'todomvc.com') {
115 + window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
116 + }
117 +
118 + function redirect() {
119 + if (location.hostname === 'tastejs.github.io') {
120 + location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
121 + }
122 + }
123 +
124 + function findRoot() {
125 + var base;
126 +
127 + [/labs/, /\w*-examples/].forEach(function (href) {
128 + var match = location.href.match(href);
129 +
130 + if (!base && match) {
131 + base = location.href.indexOf(match);
132 + }
133 + });
134 +
135 + return location.href.substr(0, base);
136 + }
137 +
138 + function getFile(file, callback) {
139 + if (!location.host) {
140 + return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
141 + }
142 +
143 + var xhr = new XMLHttpRequest();
144 +
145 + xhr.open('GET', findRoot() + file, true);
146 + xhr.send();
147 +
148 + xhr.onload = function () {
149 + if (xhr.status === 200 && callback) {
150 + callback(xhr.responseText);
151 + }
152 + };
153 + }
154 +
155 + function Learn(learnJSON, config) {
156 + if (!(this instanceof Learn)) {
157 + return new Learn(learnJSON, config);
158 + }
159 +
160 + var template, framework;
161 +
162 + if (typeof learnJSON !== 'object') {
163 + try {
164 + learnJSON = JSON.parse(learnJSON);
165 + } catch (e) {
166 + return;
167 + }
168 + }
169 +
170 + if (config) {
171 + template = config.template;
172 + framework = config.framework;
173 + }
174 +
175 + if (!template && learnJSON.templates) {
176 + template = learnJSON.templates.todomvc;
177 + }
178 +
179 + if (!framework && document.querySelector('[data-framework]')) {
180 + framework = document.querySelector('[data-framework]').getAttribute('data-framework');
181 + }
182 +
183 +
184 + if (template && learnJSON[framework]) {
185 + this.frameworkJSON = learnJSON[framework];
186 + this.template = template;
187 +
188 + this.append();
189 + }
190 + }
191 +
192 + Learn.prototype.append = function () {
193 + var aside = document.createElement('aside');
194 + aside.innerHTML = _.template(this.template, this.frameworkJSON);
195 + aside.className = 'learn';
196 +
197 + // Localize demo links
198 + var demoLinks = aside.querySelectorAll('.demo-link');
199 + Array.prototype.forEach.call(demoLinks, function (demoLink) {
200 + demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
201 + });
202 +
203 + document.body.className = (document.body.className + ' learn-bar').trim();
204 + document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
205 + };
206 +
207 + redirect();
208 + getFile('learn.json', Learn);
209 +})();
examples/todomvc-flux/todomvc-common/bg.png
Binary files /dev/null and b/examples/todomvc-flux/todomvc-common/bg.png differ
examples/todomvc-flux/todomvc-common/bower.json new
+4
@@ -0,0 +1,4 @@
1 +{
2 + "name": "todomvc-common",
3 + "version": "0.1.9"
4 +}
examples/todomvc-flux/todomvc-common/readme.md new
+8
@@ -0,0 +1,8 @@
1 +# todomvc-common
2 +
3 +> Bower component for some common utilities we use in every app
4 +
5 +
6 +## License
7 +
8 +MIT