Files
react/examples/todomvc/js/app.js
T
2013-06-03 00:44:20 -07:00

256 lines
7.1 KiB
JavaScript

/**
* @jsx React.DOM
*/
var Utils = {
// https://gist.github.com/1308368
uuid: function(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b},
pluralize: function( count, word ) {
return count === 1 ? word : word + 's';
},
store: function( namespace, data ) {
if ( arguments.length > 1 ) {
return localStorage.setItem( namespace, JSON.stringify( data ) );
} else {
var store = localStorage.getItem( namespace );
return ( store && JSON.parse( store ) ) || [];
}
}
};
function cx(obj) {
var s = '';
for (var key in obj) {
if (!obj.hasOwnProperty(key)) {
continue;
}
if (obj[key]) {
s += key + ' ';
}
}
return s;
}
var TodoItem = React.createClass({
handleSubmit: React.autoBind(function() {
var val = this.state.editText;
if (val) {
this.props.onSave(val);
this.setState({editField: ''});
}
return false;
}),
handleEdit: React.autoBind(function() {
this.props.onEdit();
this.refs.editField.getDOMNode().focus();
}),
handleKey: React.autoBind(function(event) {
if (event.nativeEvent.keyCode === 27) {
this.handleSubmit();
}
this.setState({editText: event.target.value});
}),
getInitialState: function() {
return {editText: this.props.todo.title};
},
componentWillReceiveProps: function(nextProps) {
if (nextProps.todo.title !== this.props.todo.title) {
this.setState(this.getInitialState());
}
},
render: function() {
return (
<li class={cx({completed: this.props.todo.completed, editing: this.props.editing})}>
<div class="view">
<input
class="toggle"
type="checkbox"
checked={this.props.todo.completed ? 'checked' : null}
onChange={this.props.onToggle}
/>
<label onDoubleClick={this.handleEdit}>{this.props.todo.title}</label>
<button class="destroy" onClick={this.props.onDestroy} />
</div>
<form onSubmit={this.handleSubmit}>
<input
ref="editField"
class="edit"
value={this.state.editText}
onBlur={this.handleSubmit}
onKeyUp={this.handleKey}
/>
<input type="submit" class="submitButton" />
</form>
</li>
);
}
});
var TodoFooter = React.createClass({
render: function() {
var activeTodoWord = Utils.pluralize(this.props.count, 'todo');
var clearButton = null;
if (this.props.completedCount > 0) {
clearButton = (
<button class="clear-completed" onClick={this.props.onClearCompleted}>Clear completed ({this.props.completedCount})</button>
);
}
return (
<footer class="footer">
<span class="todo-count"><strong>{this.props.count}</strong>{' '}{activeTodoWord}{' '}left</span>
{clearButton}
</footer>
);
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {
todos: Utils.store('react-todos'),
editing: {}
};
},
handleSubmit: React.autoBind(function() {
var val = this.refs.newField.getDOMNode().value.trim();
if (val) {
var todos = this.state.todos;
var newTodo = {
id: Utils.uuid(),
title: val,
completed: false
};
this.setState({todos: todos.concat([newTodo])});
this.refs.newField.getDOMNode().value = '';
}
return false;
}),
toggleAll: function(event) {
var checked = event.nativeEvent.target.checked;
this.state.todos.map(function(todo) {
todo.completed = checked;
});
this.setState({todos: this.state.todos});
},
toggle: function(todo) {
todo.completed = !todo.completed;
this.setState({todos: this.state.todos});
},
destroy: function(todo) {
var newTodos = this.state.todos.filter(function(candidate) {
return candidate.id !== todo.id;
});
this.setState({todos: newTodos});
},
edit: function(todo) {
this.state.todos.map(function(todo) {
this.state.editing[todo.id] = false;
}.bind(this));
this.state.editing[todo.id] = true;
this.setState({editing: this.state.editing});
},
save: function(todo, text) {
todo.title = text;
this.state.editing[todo.id] = false;
this.setState({todos: this.state.todos, editing: this.state.editing});
},
clearCompleted: function() {
var newTodos = this.state.todos.filter(function(todo) {
return !todo.completed;
});
this.setState({todos: newTodos});
},
render: function() {
Utils.store('react-todos', this.state.todos);
var footer = null;
var main = null;
var todoItems = this.state.todos.map(function(todo) {
return (
<TodoItem
todo={todo}
onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)}
onEdit={this.edit.bind(this, todo)}
editing={this.state.editing[todo.id]}
onSave={this.save.bind(this, todo)}
/>
);
}.bind(this));
var activeTodoCount = this.state.todos.filter(function(todo) {
return !todo.completed;
}).length;
var completedCount = todoItems.length - activeTodoCount;
if (activeTodoCount || completedCount) {
footer =
<TodoFooter
count={activeTodoCount}
completedCount={completedCount}
onClearCompleted={this.clearCompleted.bind(this)}
/>;
}
if (todoItems.length) {
main = (
<section class="main">
<input class="toggle-all" type="checkbox" onChange={this.toggleAll.bind(this)} />
<label class="toggle-all-label">Mark all as complete</label>
<ul class="todo-list">
{todoItems}
</ul>
</section>
);
}
return (
<div>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<form onSubmit={this.handleSubmit}>
<input
ref="newField"
class="new-todo"
placeholder="What needs to be done?"
autofocus="autofocus"
/>
<input type="submit" class="submitButton" />
</form>
</header>
{main}
{footer}
</section>
<footer class="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>
</div>
);
}
});
React.renderComponent(<TodoApp />, document.getElementById('todoapp'));
// Some benchmarking that requires either a custom build of React or more
// modules exposed from React.*
// var initTime = ReactMount.totalInstantiationTime + ReactMount.totalInjectionTime;
// var benchmark = document.getElementById('benchmark');
// setInterval(function() {
// benchmark.innerHTML = (
// 'Init render time = ' + initTime + 'ms' +
// '<br />' +
// 'Post-init render time = ' + (ReactMount.totalInstantiationTime + ReactMount.totalInjectionTime - initTime) + 'ms'
// );
// }, 1000);