Skip to content

Instantly share code, notes, and snippets.

@mpereira
Created January 31, 2014 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mpereira/d407618d78fb52ccf94e to your computer and use it in GitHub Desktop.
Save mpereira/d407618d78fb52ccf94e to your computer and use it in GitHub Desktop.
/** @jsx React.DOM */
'use strict';
var TodoApplication = React.createClass({
taskStatuses: {
all: 'all',
todo: 'todo',
done: 'done'
},
getInitialState: function() {
return({
tasks: (this.props.tasks || []),
toggleTaskStatusesChecked: false,
showing: this.taskStatuses.all
});
},
createTask: function(task) {
this.setState({ tasks: this.state.tasks.concat(task) });
},
updateTask: function(i, updatedTask) {
var tasks = _.clone(this.state.tasks);
tasks[i] = _(tasks[i]).extend(updatedTask);
this.setState({ tasks: tasks });
},
deleteTask: function(i) {
var tasks = _(_.clone(this.state.tasks)).filter(_(function(task, index) {
return(i !== index);
}).bind(this));
this.setState({ tasks: tasks });
},
deleteTasks: function(tasks) {
this.setState({ tasks: _(_.clone(this.state.tasks)).difference(tasks) });
},
toggleDoneStates: function() {
var tasks = _(_.clone(this.state.tasks)).map(_(function(task) {
task.done = !this.state.toggleTaskStatusesChecked;
return(task)
}).bind(this));
this.setState({
tasks: tasks,
toggleTaskStatusesChecked: !this.state.toggleTaskStatusesChecked
});
},
taskStatusNames: function() {
return(_.values(this.taskStatuses));
},
showTasksWithStatus: function(status) {
if (_(this.taskStatusNames()).contains(status)) {
this.setState({ showing: status });
}
},
tasksWithStatus: function(status) {
return(_(this.state.tasks).filter(_(function(task) {
if (status === this.taskStatuses.all) {
return(true);
} else if (status === this.taskStatuses.todo) {
return(!task.done);
} else if (status === this.taskStatuses.done) {
return(task.done);
}
}).bind(this)));
},
tasksFilteredByShowing: function() {
return(_(this.state.tasks).filter(_(function(task) {
if (this.state.showing === this.taskStatuses.all) {
return(true);
} else if (this.state.showing === this.taskStatuses.todo) {
return(!task.done);
} else if (this.state.showing === this.taskStatuses.done) {
return(task.done);
}
}).bind(this)));
},
render: function() {
return(
<div className='todo-application'>
<h1 className='title'>Todo</h1>
<TaskTextForm onSubmit={this.createTask}
input={{
placeholder: 'What needs to be done?',
classSet: { 'new-task-text-input': true },
autofocus: true
}}/>
<input onChange={this.toggleDoneStates}
className='toggle-all-tasks-input'
type='checkbox'
name='tasks[done]'
defaultChecked={this.state.toggle} />
<ul className='tasks'>
{this.tasksFilteredByShowing().map(function(task, i) {
return(<Task key={i}
task={task}
onUpdate={this.updateTask.bind(this, i)}
onDelete={this.deleteTask.bind(this, i)}/>);
}.bind(this))}
</ul>
<span className='todo-tasks-count'>
{this.tasksWithStatus(this.taskStatuses.todo).length} tasks left
</span>
<span className='show-all-tasks'
onClick={this.showTasksWithStatus.bind(this, this.taskStatuses.all)}>
All
</span>
<span className='show-todo-tasks'
onClick={this.showTasksWithStatus.bind(this, this.taskStatuses.todo)}>
Active
</span>
<span className='show-done-tasks'
onClick={this.showTasksWithStatus.bind(this, this.taskStatuses.done)}>
Completed
</span>
<span className='clear-completed-tasks'
onClick={this.deleteTasks.bind(
this,
this.tasksWithStatus(this.taskStatuses.done)
)}>
Clear completed
({this.tasksWithStatus(this.taskStatuses.done).length})
</span>
</div>
);
}
});
var TaskTextForm = React.createClass({
onSubmit: function(event, el) {
event.preventDefault();
if (!this.props.onSubmit) { return; };
var input = this.refs.taskText.getDOMNode();
var task = { text: input.value.trim() };
input.value = '';
this.props.onSubmit(task);
},
inputClassSet: function() {
return(React.addons.classSet(_({
'task-text-input': true
}).extend(this.props.input && this.props.input.classSet)))
},
render: function() {
return(
<form className='task-form' onSubmit={this.onSubmit}>
<input ref='taskText'
className={this.inputClassSet()}
type='text'
name='task[text]'
autoComplete='off'
placeholder={this.props.input.placeholder}
defaultValue={this.props.input.defaultValue} />
</form>
);
}
});
var Task = React.createClass({
getInitialState: function() {
return({ isBeingEdited: false });
},
setIsBeingEditedToTrue: function(event, el) {
this.setState({ isBeingEdited: true });
},
updateIsBeingEditedAndText: function(task) {
this.setState({ isBeingEdited: false });
this.props.onUpdate({ text: task.text });
},
updateDoneState: function(event, el) {
this.props.onUpdate({ done: event.target.checked });
},
delete: function() {
this.props.onDelete();
},
textClassSet: function() {
return(React.addons.classSet(_({
'text': true,
'done': this.props.task.done
}).extend(this.props.text && this.props.text.classSet)))
},
render: function() {
var doneInput = <input onChange={this.updateDoneState}
className='task-done-input'
type='checkbox'
name='task[done]'
checked={this.props.task.done} />;
if (this.state.isBeingEdited) {
return(
<li className='task'>
{doneInput}
<TaskTextForm onSubmit={this.updateIsBeingEditedAndText}
input={{
placeholder: 'What needs to be done?',
defaultValue: this.props.task.text,
autofocus: true
}} />
</li>
);
} else {
return(
<li className='task'>
{doneInput}
<span onDoubleClick={this.setIsBeingEditedToTrue}
className={this.textClassSet()}>
{this.props.task.text}
</span>
<i className='glyphicon glyphicon-remove delete'
onClick={this.delete}/>
</li>
);
}
}
});
var tasks = [{
text: 'buy milk', done: false
}, {
text: 'pay bills', done: true
}];
React.renderComponent(
<TodoApplication tasks={tasks} />,
document.getElementById('application')
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment