Commit f3e41f70 authored by Pascal Hartig's avatar Pascal Hartig

Merge pull request #782 from petehunt/react-model2

Make React TodoMVC more MVC-ish
parents 06fe35ab a656e4af
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
<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 type="text/jsx" src="js/utils.jsx"></script>
<script type="text/javascript" src="js/todoModel.js"></script>
<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,8 +5,8 @@ ...@@ -5,8 +5,8 @@
/*jshint white:false */ /*jshint white:false */
/*jshint trailing:false */ /*jshint trailing:false */
/*jshint newcap:false */ /*jshint newcap:false */
/*global Utils, ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS, /*global Utils, TodoModel, ALL_TODOS, ACTIVE_TODOS,
TodoItem, TodoFooter, React, Router*/ COMPLETED_TODOS, TodoItem, TodoFooter, React, Router*/
(function (window, React) { (function (window, React) {
'use strict'; 'use strict';
...@@ -19,9 +19,7 @@ ...@@ -19,9 +19,7 @@
var TodoApp = React.createClass({ var TodoApp = React.createClass({
getInitialState: function () { getInitialState: function () {
var todos = Utils.store('react-todos');
return { return {
todos: todos,
nowShowing: ALL_TODOS, nowShowing: ALL_TODOS,
editing: null editing: null
}; };
...@@ -34,6 +32,7 @@ ...@@ -34,6 +32,7 @@
'/completed': this.setState.bind(this, {nowShowing: COMPLETED_TODOS}) '/completed': this.setState.bind(this, {nowShowing: COMPLETED_TODOS})
}); });
router.init(); router.init();
this.refs.newField.getDOMNode().focus(); this.refs.newField.getDOMNode().focus();
}, },
...@@ -43,15 +42,9 @@ ...@@ -43,15 +42,9 @@
} }
var val = this.refs.newField.getDOMNode().value.trim(); var val = this.refs.newField.getDOMNode().value.trim();
var newTodo;
if (val) { if (val) {
newTodo = { this.props.model.addTodo(val);
id: Utils.uuid(),
title: val,
completed: false
};
this.setState({todos: this.state.todos.concat([newTodo])});
this.refs.newField.getDOMNode().value = ''; this.refs.newField.getDOMNode().value = '';
} }
...@@ -60,31 +53,15 @@ ...@@ -60,31 +53,15 @@
toggleAll: function (event) { toggleAll: function (event) {
var checked = event.target.checked; var checked = event.target.checked;
this.props.model.toggleAll(checked);
// Note: it's usually better to use immutable data structures since they're easier to
// reason about and React works very well with them. That's why we use map() and filter()
// everywhere instead of mutating the array or todo items themselves.
var newTodos = this.state.todos.map(function (todo) {
return Utils.extend({}, todo, {completed: checked});
});
this.setState({todos: newTodos});
}, },
toggle: function (todoToToggle) { toggle: function (todoToToggle) {
var newTodos = this.state.todos.map(function (todo) { this.props.model.toggle(todoToToggle);
return todo !== todoToToggle ? todo : Utils.extend({}, todo, {completed: !todo.completed});
});
this.setState({todos: newTodos});
}, },
destroy: function (todo) { destroy: function (todo) {
var newTodos = this.state.todos.filter(function (candidate) { this.props.model.destroy(todo);
return candidate.id !== todo.id;
});
this.setState({todos: newTodos});
}, },
edit: function (todo, callback) { edit: function (todo, callback) {
...@@ -96,11 +73,8 @@ ...@@ -96,11 +73,8 @@
}, },
save: function (todoToSave, text) { save: function (todoToSave, text) {
var newTodos = this.state.todos.map(function (todo) { this.props.model.save(todoToSave, text);
return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text}); this.setState({editing: null});
});
this.setState({todos: newTodos, editing: null});
}, },
cancel: function () { cancel: function () {
...@@ -108,22 +82,14 @@ ...@@ -108,22 +82,14 @@
}, },
clearCompleted: function () { clearCompleted: function () {
var newTodos = this.state.todos.filter(function (todo) { this.props.model.clearCompleted();
return !todo.completed;
});
this.setState({todos: newTodos});
},
componentDidUpdate: function () {
Utils.store('react-todos', this.state.todos);
}, },
render: function () { render: function () {
var footer = null; var footer = null;
var main = null; var main = null;
var shownTodos = this.state.todos.filter(function (todo) { var shownTodos = this.props.model.todos.filter(function (todo) {
switch (this.state.nowShowing) { switch (this.state.nowShowing) {
case ACTIVE_TODOS: case ACTIVE_TODOS:
return !todo.completed; return !todo.completed;
...@@ -149,11 +115,11 @@ ...@@ -149,11 +115,11 @@
); );
}, this); }, this);
var activeTodoCount = this.state.todos.reduce(function(accum, todo) { var activeTodoCount = this.props.model.todos.reduce(function(accum, todo) {
return todo.completed ? accum : accum + 1; return todo.completed ? accum : accum + 1;
}, 0); }, 0);
var completedCount = this.state.todos.length - activeTodoCount; var completedCount = this.props.model.todos.length - activeTodoCount;
if (activeTodoCount || completedCount) { if (activeTodoCount || completedCount) {
footer = footer =
...@@ -165,7 +131,7 @@ ...@@ -165,7 +131,7 @@
/>; />;
} }
if (this.state.todos.length) { if (this.props.model.todos.length) {
main = ( main = (
<section id="main"> <section id="main">
<input <input
...@@ -199,7 +165,18 @@ ...@@ -199,7 +165,18 @@
} }
}); });
React.renderComponent(<TodoApp />, document.getElementById('todoapp')); var model = new TodoModel('react-todos');
function render() {
React.renderComponent(
<TodoApp model={model}/>,
document.getElementById('todoapp')
);
}
model.subscribe(render);
render();
React.renderComponent( React.renderComponent(
<div> <div>
<p>Double-click to edit a todo</p> <p>Double-click to edit a todo</p>
......
/**
* @jsx React.DOM
*/
/*jshint quotmark:false */
/*jshint white:false */
/*jshint trailing:false */
/*jshint newcap:false */
/*global Utils */
(function (window) {
'use strict';
// Generic "model" object. You can use whatever
// framework you want. For this application it
// may not even be worth separating this logic
// out, but we do this to demonstrate one way to
// separate out parts of your application.
window.TodoModel = function (key) {
this.key = key;
this.todos = Utils.store(key);
this.onChanges = [];
};
window.TodoModel.prototype.subscribe = function (onChange) {
this.onChanges.push(onChange);
};
window.TodoModel.prototype.inform = function () {
Utils.store(this.key, this.todos);
this.onChanges.forEach(function (cb) { cb(); });
};
window.TodoModel.prototype.addTodo = function (title) {
this.todos = this.todos.concat({
id: Utils.uuid(),
title: title,
completed: false
});
this.inform();
};
window.TodoModel.prototype.toggleAll = function (checked) {
// Note: it's usually better to use immutable data structures since they're easier to
// reason about and React works very well with them. That's why we use map() and filter()
// everywhere instead of mutating the array or todo items themselves.
this.todos = this.todos.map(function (todo) {
return Utils.extend({}, todo, {completed: checked});
});
this.inform();
};
window.TodoModel.prototype.toggle = function (todoToToggle) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToToggle ? todo : Utils.extend({}, todo, {completed: !todo.completed});
});
this.inform();
};
window.TodoModel.prototype.destroy = function (todo) {
this.todos = this.todos.filter(function (candidate) {
return candidate.id !== todo.id;
});
this.inform();
};
window.TodoModel.prototype.save = function (todoToSave, text) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text});
});
this.inform();
};
window.TodoModel.prototype.clearCompleted = function () {
this.todos = this.todos.filter(function (todo) {
return !todo.completed;
});
this.inform();
};
})(this);
\ No newline at end of file
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