1 /*jshint quotmark:false */
2 /*jshint white:false */
3 /*jshint trailing:false */
4 /*jshint newcap:false */
5 /*global React, Router*/
11 app.ALL_TODOS = 'all';
12 app.ACTIVE_TODOS = 'active';
13 app.COMPLETED_TODOS = 'completed';
17 /*jshint bitwise:false */
21 for (i = 0; i < 32; i++) {
22 random = Math.random() * 16 | 0;
23 if (i === 8 || i === 12 || i === 16 || i === 20) {
26 uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
33 pluralize: function (count, word) {
34 return count === 1 ? word : word + 's';
39 for (var i = 0; i < arguments.length; i++) {
40 var obj = arguments[i];
41 for (var key in obj) {
42 if (obj.hasOwnProperty(key)) {
43 newObj[key] = obj[key];
51 var Utils = app.Utils;
52 // Generic "model" object. You can use whatever
53 // framework you want. For this application it
54 // may not even be worth separating this logic
55 // out, but we do this to demonstrate one way to
56 // separate out parts of your application.
57 app.TodoModel = function (key) {
63 app.TodoModel.prototype.subscribe = function (onChange) {
64 this.onChanges.push(onChange);
67 app.TodoModel.prototype.inform = function () {
68 this.onChanges.forEach(function (cb) { cb(); });
71 app.TodoModel.prototype.addTodo = function (title) {
72 this.todos = this.todos.concat({
81 app.TodoModel.prototype.toggleAll = function (checked) {
82 // Note: it's usually better to use immutable data structures since they're
83 // easier to reason about and React works very well with them. That's why
84 // we use map() and filter() everywhere instead of mutating the array or
85 // todo items themselves.
86 this.todos = this.todos.map(function (todo) {
87 return Utils.extend({}, todo, {completed: checked});
93 app.TodoModel.prototype.toggle = function (todoToToggle) {
94 this.todos = this.todos.map(function (todo) {
95 return todo !== todoToToggle ?
97 Utils.extend({}, todo, {completed: !todo.completed});
103 app.TodoModel.prototype.destroy = function (todo) {
104 this.todos = this.todos.filter(function (candidate) {
105 return candidate !== todo;
111 app.TodoModel.prototype.save = function (todoToSave, text) {
112 this.todos = this.todos.map(function (todo) {
113 return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text});
119 app.TodoModel.prototype.clearCompleted = function () {
120 this.todos = this.todos.filter(function (todo) {
121 return !todo.completed;
128 var TodoFooter = React.createClass({
129 render: function () {
130 var activeTodoWord = app.Utils.pluralize(this.props.count, 'item');
131 var clearButton = null;
133 if (this.props.completedCount > 0) {
136 className="clear-completed"
137 onClick={this.props.onClearCompleted}>
143 var nowShowing = this.props.nowShowing;
145 <footer className="footer">
146 <span className="todo-count">
147 <strong>{this.props.count}</strong> {activeTodoWord} left
149 <ul className="filters">
153 className={classNames({selected: nowShowing === app.ALL_TODOS})}>
161 className={classNames({selected: nowShowing === app.ACTIVE_TODOS})}>
169 className={classNames({selected: nowShowing === app.COMPLETED_TODOS})}>
183 var TodoItem = React.createClass({
184 handleSubmit: function (event) {
185 var val = this.state.editText.trim();
187 this.props.onSave(val);
188 this.setState({editText: val});
190 this.props.onDestroy();
194 handleEdit: function () {
196 this.setState({editText: this.props.todo.title});
199 handleKeyDown: function (event) {
200 if (event.which === ESCAPE_KEY) {
201 this.setState({editText: this.props.todo.title});
202 this.props.onCancel(event);
203 } else if (event.which === ENTER_KEY) {
204 this.handleSubmit(event);
208 handleChange: function (event) {
209 if (this.props.editing) {
210 this.setState({editText: event.target.value});
214 getInitialState: function () {
215 return {editText: this.props.todo.title};
219 * This is a completely optional performance enhancement that you can
220 * implement on any React component. If you were to delete this method
221 * the app would still work correctly (and still be very performant!), we
222 * just use it as an example of how little code it takes to get an order
223 * of magnitude performance improvement.
225 shouldComponentUpdate: function (nextProps, nextState) {
227 nextProps.todo !== this.props.todo ||
228 nextProps.editing !== this.props.editing ||
229 nextState.editText !== this.state.editText
234 * Safely manipulate the DOM after updating the state when invoking
235 * `this.props.onEdit()` in the `handleEdit` method above.
236 * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate
237 * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
239 componentDidUpdate: function (prevProps) {
240 if (!prevProps.editing && this.props.editing) {
241 var node = React.findDOMNode(this.refs.editField);
243 node.setSelectionRange(node.value.length, node.value.length);
247 render: function () {
249 <li className={classNames({
250 completed: this.props.todo.completed,
251 editing: this.props.editing
253 <div className="view">
257 checked={this.props.todo.completed}
258 onChange={this.props.onToggle}
260 <label onDoubleClick={this.handleEdit}>
261 {this.props.todo.title}
263 <button className="destroy" onClick={this.props.onDestroy} />
268 value={this.state.editText}
269 onBlur={this.handleSubmit}
270 onChange={this.handleChange}
271 onKeyDown={this.handleKeyDown}
280 var TodoApp = React.createClass({
281 getInitialState: function () {
283 nowShowing: app.ALL_TODOS,
289 componentDidMount: function () {
290 var setState = this.setState;
291 var router = Router({
292 '/': setState.bind(this, {nowShowing: app.ALL_TODOS}),
293 '/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}),
294 '/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS})
299 handleChange: function (event) {
300 this.setState({newTodo: event.target.value});
303 handleNewTodoKeyDown: function (event) {
304 if (event.keyCode !== ENTER_KEY) {
308 event.preventDefault();
310 var val = this.state.newTodo.trim();
313 this.props.model.addTodo(val);
314 this.setState({newTodo: ''});
318 toggleAll: function (event) {
319 var checked = event.target.checked;
320 this.props.model.toggleAll(checked);
323 toggle: function (todoToToggle) {
324 this.props.model.toggle(todoToToggle);
327 destroy: function (todo) {
328 this.props.model.destroy(todo);
331 edit: function (todo) {
332 this.setState({editing: todo.id});
335 save: function (todoToSave, text) {
336 this.props.model.save(todoToSave, text);
337 this.setState({editing: null});
340 cancel: function () {
341 this.setState({editing: null});
344 clearCompleted: function () {
345 this.props.model.clearCompleted();
348 render: function () {
351 var todos = this.props.model.todos;
353 var shownTodos = todos.filter(function (todo) {
354 switch (this.state.nowShowing) {
355 case app.ACTIVE_TODOS:
356 return !todo.completed;
357 case app.COMPLETED_TODOS:
358 return todo.completed;
364 var todoItems = shownTodos.map(function (todo) {
369 onToggle={this.toggle.bind(this, todo)}
370 onDestroy={this.destroy.bind(this, todo)}
371 onEdit={this.edit.bind(this, todo)}
372 editing={this.state.editing === todo.id}
373 onSave={this.save.bind(this, todo)}
374 onCancel={this.cancel}
379 var activeTodoCount = todos.reduce(function (accum, todo) {
380 return todo.completed ? accum : accum + 1;
383 var completedCount = todos.length - activeTodoCount;
385 if (activeTodoCount || completedCount) {
388 count={activeTodoCount}
389 completedCount={completedCount}
390 nowShowing={this.state.nowShowing}
391 onClearCompleted={this.clearCompleted}
397 <section className="main">
399 className="toggle-all"
401 onChange={this.toggleAll}
402 checked={activeTodoCount === 0}
404 <ul className="todo-list">
413 <header className="header">
417 placeholder="What needs to be done?"
418 value={this.state.newTodo}
419 onKeyDown={this.handleNewTodoKeyDown}
420 onChange={this.handleChange}
431 var model = new app.TodoModel('react-todos');
435 <TodoApp model={model}/>,
436 document.getElementsByClassName('todoapp')[0]
440 model.subscribe(render);