Commit 904aa5a8 authored by Pascal Hartig's avatar Pascal Hartig

Merge pull request #855 from chenglou/new-react

[React] New main todomvc with fixes and React version bump
parents f0079582 156d0859
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"todomvc-common": "~0.1.7", "todomvc-common": "~0.1.7",
"react": "~0.8.0", "director": "~1.2.0",
"director": "~1.2.0" "react": "~0.9.0"
} }
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -8,15 +8,22 @@ ...@@ -8,15 +8,22 @@
<body> <body>
<section id="todoapp"></section> <section id="todoapp"></section>
<footer id="info"></footer> <footer id="info"></footer>
<div id="benchmark"></div> <footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://github.com/petehunt/">petehunt</a></p>
<p>Part of<a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script src="bower_components/todomvc-common/base.js"></script> <script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/react/react-with-addons.js"></script> <script src="bower_components/react/react-with-addons.js"></script>
<script src="bower_components/react/JSXTransformer.js"></script> <script src="bower_components/react/JSXTransformer.js"></script>
<script src="bower_components/director/build/director.js"></script> <script src="bower_components/director/build/director.js"></script>
<script type="text/jsx" src="js/utils.jsx"></script> <script src="js/utils.js"></script>
<script src="js/todoModel.js"></script> <script src="js/todoModel.js"></script>
<!-- jsx is an optional syntactic sugar that transforms methods in React's
`render` into an HTML-looking format. Since the two models above are
unrelated to React, we didn't need those transforms. -->
<script type="text/jsx" src="js/todoItem.jsx"></script> <script type="text/jsx" src="js/todoItem.jsx"></script>
<script type="text/jsx" src="js/footer.jsx"></script> <script type="text/jsx" src="js/footer.jsx"></script>
<script type="text/jsx" src="js/app.jsx"></script> <script type="text/jsx" src="js/app.jsx"></script>
......
...@@ -5,35 +5,36 @@ ...@@ -5,35 +5,36 @@
/*jshint white:false */ /*jshint white:false */
/*jshint trailing:false */ /*jshint trailing:false */
/*jshint newcap:false */ /*jshint newcap:false */
/*global Utils, TodoModel, ALL_TODOS, ACTIVE_TODOS, /*global React, Router*/
COMPLETED_TODOS, TodoItem, TodoFooter, React, Router*/ var app = app || {};
(function (window, React) { (function () {
'use strict'; 'use strict';
window.ALL_TODOS = 'all'; app.ALL_TODOS = 'all';
window.ACTIVE_TODOS = 'active'; app.ACTIVE_TODOS = 'active';
window.COMPLETED_TODOS = 'completed'; app.COMPLETED_TODOS = 'completed';
var TodoFooter = app.TodoFooter;
var TodoItem = app.TodoItem;
var ENTER_KEY = 13; var ENTER_KEY = 13;
var TodoApp = React.createClass({ var TodoApp = React.createClass({
getInitialState: function () { getInitialState: function () {
return { return {
nowShowing: ALL_TODOS, nowShowing: app.ALL_TODOS,
editing: null editing: null
}; };
}, },
componentDidMount: function () { componentDidMount: function () {
var setState = this.setState;
var router = Router({ var router = Router({
'/': this.setState.bind(this, {nowShowing: ALL_TODOS}), '/': setState.bind(this, {nowShowing: app.ALL_TODOS}),
'/active': this.setState.bind(this, {nowShowing: ACTIVE_TODOS}), '/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}),
'/completed': this.setState.bind(this, {nowShowing: COMPLETED_TODOS}) '/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS})
}); });
router.init(); router.init('/');
this.refs.newField.getDOMNode().focus();
}, },
handleNewTodoKeyDown: function (event) { handleNewTodoKeyDown: function (event) {
...@@ -86,14 +87,15 @@ ...@@ -86,14 +87,15 @@
}, },
render: function () { render: function () {
var footer = null; var footer;
var main = null; var main;
var todos = this.props.model.todos;
var shownTodos = this.props.model.todos.filter(function (todo) { var shownTodos = todos.filter(function (todo) {
switch (this.state.nowShowing) { switch (this.state.nowShowing) {
case ACTIVE_TODOS: case app.ACTIVE_TODOS:
return !todo.completed; return !todo.completed;
case COMPLETED_TODOS: case app.COMPLETED_TODOS:
return todo.completed; return todo.completed;
default: default:
return true; return true;
...@@ -115,11 +117,11 @@ ...@@ -115,11 +117,11 @@
); );
}, this); }, this);
var activeTodoCount = this.props.model.todos.reduce(function(accum, todo) { var activeTodoCount = todos.reduce(function (accum, todo) {
return todo.completed ? accum : accum + 1; return todo.completed ? accum : accum + 1;
}, 0); }, 0);
var completedCount = this.props.model.todos.length - activeTodoCount; var completedCount = todos.length - activeTodoCount;
if (activeTodoCount || completedCount) { if (activeTodoCount || completedCount) {
footer = footer =
...@@ -131,7 +133,7 @@ ...@@ -131,7 +133,7 @@
/>; />;
} }
if (this.props.model.todos.length) { if (todos.length) {
main = ( main = (
<section id="main"> <section id="main">
<input <input
...@@ -156,6 +158,7 @@ ...@@ -156,6 +158,7 @@
id="new-todo" id="new-todo"
placeholder="What needs to be done?" placeholder="What needs to be done?"
onKeyDown={this.handleNewTodoKeyDown} onKeyDown={this.handleNewTodoKeyDown}
autoFocus={true}
/> />
</header> </header>
{main} {main}
...@@ -165,7 +168,7 @@ ...@@ -165,7 +168,7 @@
} }
}); });
var model = new TodoModel('react-todos'); var model = new app.TodoModel('react-todos');
function render() { function render() {
React.renderComponent( React.renderComponent(
...@@ -176,14 +179,4 @@ ...@@ -176,14 +179,4 @@
model.subscribe(render); model.subscribe(render);
render(); render();
})();
React.renderComponent(
<div>
<p>Double-click to edit a todo</p>
<p>Created by{' '}
<a href="http://github.com/petehunt/">petehunt</a>
</p>
<p>Part of{' '}<a href="http://todomvc.com">TodoMVC</a></p>
</div>,
document.getElementById('info'));
})(window, React);
...@@ -5,13 +5,15 @@ ...@@ -5,13 +5,15 @@
/*jshint white:false */ /*jshint white:false */
/*jshint trailing:false */ /*jshint trailing:false */
/*jshint newcap:false */ /*jshint newcap:false */
/*global React, ALL_TODOS, ACTIVE_TODOS, Utils, COMPLETED_TODOS */ /*global React */
(function (window) { var app = app || {};
(function () {
'use strict'; 'use strict';
window.TodoFooter = React.createClass({ app.TodoFooter = React.createClass({
render: function () { render: function () {
var activeTodoWord = Utils.pluralize(this.props.count, 'item'); var activeTodoWord = app.Utils.pluralize(this.props.count, 'item');
var clearButton = null; var clearButton = null;
if (this.props.completedCount > 0) { if (this.props.completedCount > 0) {
...@@ -19,35 +21,42 @@ ...@@ -19,35 +21,42 @@
<button <button
id="clear-completed" id="clear-completed"
onClick={this.props.onClearCompleted}> onClick={this.props.onClearCompleted}>
{''}Clear completed ({this.props.completedCount}){''} Clear completed ({this.props.completedCount})
</button> </button>
); );
} }
var show = { // React idiom for shortcutting to `classSet` since it'll be used often
ALL_TODOS: '', var cx = React.addons.classSet;
ACTIVE_TODOS: '', var nowShowing = this.props.nowShowing;
COMPLETED_TODOS: ''
};
show[this.props.nowShowing] = 'selected';
return ( return (
<footer id="footer"> <footer id="footer">
<span id="todo-count"> <span id="todo-count">
<strong>{this.props.count}</strong> <strong>{this.props.count}</strong> {activeTodoWord} left
{' '}{activeTodoWord}{' '}left{''}
</span> </span>
<ul id="filters"> <ul id="filters">
<li> <li>
<a href="#/" className={show[ALL_TODOS]}>All</a> <a
href="#/"
className={cx({selected: nowShowing === app.ALL_TODOS})}>
All
</a>
</li> </li>
{' '} {' '}
<li> <li>
<a href="#/active" className={show[ACTIVE_TODOS]}>Active</a> <a
href="#/active"
className={cx({selected: nowShowing === app.ACTIVE_TODOS})}>
Active
</a>
</li> </li>
{' '} {' '}
<li> <li>
<a href="#/completed" className={show[COMPLETED_TODOS]}>Completed</a> <a
href="#/completed"
className={cx({selected: nowShowing === app.COMPLETED_TODOS})}>
Completed
</a>
</li> </li>
</ul> </ul>
{clearButton} {clearButton}
...@@ -55,4 +64,4 @@ ...@@ -55,4 +64,4 @@
); );
} }
}); });
})(window); })();
...@@ -5,14 +5,16 @@ ...@@ -5,14 +5,16 @@
/*jshint white: false */ /*jshint white: false */
/*jshint trailing: false */ /*jshint trailing: false */
/*jshint newcap: false */ /*jshint newcap: false */
/*global React, Utils */ /*global React */
(function (window) { var app = app || {};
(function () {
'use strict'; 'use strict';
var ESCAPE_KEY = 27; var ESCAPE_KEY = 27;
var ENTER_KEY = 13; var ENTER_KEY = 13;
window.TodoItem = React.createClass({ app.TodoItem = React.createClass({
handleSubmit: function () { handleSubmit: function () {
var val = this.state.editText.trim(); var val = this.state.editText.trim();
if (val) { if (val) {
...@@ -38,10 +40,10 @@ ...@@ -38,10 +40,10 @@
}, },
handleKeyDown: function (event) { handleKeyDown: function (event) {
if (event.keyCode === ESCAPE_KEY) { if (event.which === ESCAPE_KEY) {
this.setState({editText: this.props.todo.title}); this.setState({editText: this.props.todo.title});
this.props.onCancel(); this.props.onCancel();
} else if (event.keyCode === ENTER_KEY) { } else if (event.which === ENTER_KEY) {
this.handleSubmit(); this.handleSubmit();
} }
}, },
...@@ -98,4 +100,4 @@ ...@@ -98,4 +100,4 @@
); );
} }
}); });
})(window); })();
...@@ -5,32 +5,33 @@ ...@@ -5,32 +5,33 @@
/*jshint white:false */ /*jshint white:false */
/*jshint trailing:false */ /*jshint trailing:false */
/*jshint newcap:false */ /*jshint newcap:false */
/*global Utils */ var app = app || {};
(function (window) { (function () {
'use strict'; 'use strict';
var Utils = app.Utils;
// Generic "model" object. You can use whatever // Generic "model" object. You can use whatever
// framework you want. For this application it // framework you want. For this application it
// may not even be worth separating this logic // may not even be worth separating this logic
// out, but we do this to demonstrate one way to // out, but we do this to demonstrate one way to
// separate out parts of your application. // separate out parts of your application.
window.TodoModel = function (key) { app.TodoModel = function (key) {
this.key = key; this.key = key;
this.todos = Utils.store(key); this.todos = Utils.store(key);
this.onChanges = []; this.onChanges = [];
}; };
window.TodoModel.prototype.subscribe = function (onChange) { app.TodoModel.prototype.subscribe = function (onChange) {
this.onChanges.push(onChange); this.onChanges.push(onChange);
}; };
window.TodoModel.prototype.inform = function () { app.TodoModel.prototype.inform = function () {
Utils.store(this.key, this.todos); Utils.store(this.key, this.todos);
this.onChanges.forEach(function (cb) { cb(); }); this.onChanges.forEach(function (cb) { cb(); });
}; };
window.TodoModel.prototype.addTodo = function (title) { app.TodoModel.prototype.addTodo = function (title) {
this.todos = this.todos.concat({ this.todos = this.todos.concat({
id: Utils.uuid(), id: Utils.uuid(),
title: title, title: title,
...@@ -40,10 +41,11 @@ ...@@ -40,10 +41,11 @@
this.inform(); this.inform();
}; };
window.TodoModel.prototype.toggleAll = function (checked) { app.TodoModel.prototype.toggleAll = function (checked) {
// Note: it's usually better to use immutable data structures since they're easier to // Note: it's usually better to use immutable data structures since they're
// reason about and React works very well with them. That's why we use map() and filter() // easier to reason about and React works very well with them. That's why we
// everywhere instead of mutating the array or todo items themselves. // use map() and filter() everywhere instead of mutating the array or todo
// items themselves.
this.todos = this.todos.map(function (todo) { this.todos = this.todos.map(function (todo) {
return Utils.extend({}, todo, {completed: checked}); return Utils.extend({}, todo, {completed: checked});
}); });
...@@ -51,15 +53,17 @@ ...@@ -51,15 +53,17 @@
this.inform(); this.inform();
}; };
window.TodoModel.prototype.toggle = function (todoToToggle) { app.TodoModel.prototype.toggle = function (todoToToggle) {
this.todos = this.todos.map(function (todo) { this.todos = this.todos.map(function (todo) {
return todo !== todoToToggle ? todo : Utils.extend({}, todo, {completed: !todo.completed}); return todo !== todoToToggle ?
todo :
Utils.extend({}, todo, {completed: !todo.completed});
}); });
this.inform(); this.inform();
}; };
window.TodoModel.prototype.destroy = function (todo) { app.TodoModel.prototype.destroy = function (todo) {
this.todos = this.todos.filter(function (candidate) { this.todos = this.todos.filter(function (candidate) {
return candidate !== todo; return candidate !== todo;
}); });
...@@ -67,7 +71,7 @@ ...@@ -67,7 +71,7 @@
this.inform(); this.inform();
}; };
window.TodoModel.prototype.save = function (todoToSave, text) { app.TodoModel.prototype.save = function (todoToSave, text) {
this.todos = this.todos.map(function (todo) { this.todos = this.todos.map(function (todo) {
return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text}); return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text});
}); });
...@@ -75,7 +79,7 @@ ...@@ -75,7 +79,7 @@
this.inform(); this.inform();
}; };
window.TodoModel.prototype.clearCompleted = function () { app.TodoModel.prototype.clearCompleted = function () {
this.todos = this.todos.filter(function (todo) { this.todos = this.todos.filter(function (todo) {
return !todo.completed; return !todo.completed;
}); });
...@@ -83,4 +87,4 @@ ...@@ -83,4 +87,4 @@
this.inform(); this.inform();
}; };
})(this); })();
\ No newline at end of file
(function (window) { var app = app || {};
(function () {
'use strict'; 'use strict';
window.Utils = { app.Utils = {
uuid: function () { uuid: function () {
/*jshint bitwise:false */ /*jshint bitwise:false */
var i, random; var i, random;
...@@ -45,5 +47,4 @@ ...@@ -45,5 +47,4 @@
return newObj; return newObj;
} }
}; };
})();
})(window);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment