mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
256 lines
7.1 KiB
JavaScript
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);
|