Commit 156d0859 authored by Cheng Lou's avatar Cheng Lou

[React] New main todomvc with fixes and React version bump

Fixes all the stuff from #785 too.
parent 162f06ea
...@@ -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"
} }
} }
...@@ -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