Source code

Revision control

Copy as Markdown

Other Tools

var app=app||{};(function(){"use strict";app.ALL_TODOS="all";app.ACTIVE_TODOS="active";app.COMPLETED_TODOS="completed";app.Utils={uuid:function(){var i,random;var uuid="";for(i=0;i<32;i++){random=Math.random()*16|0;if(i===8||i===12||i===16||i===20){uuid+="-"}uuid+=(i===12?4:i===16?random&3|8:random).toString(16)}return uuid},pluralize:function(count,word){return count===1?word:word+"s"},extend:function(){var newObj={};for(var i=0;i<arguments.length;i++){var obj=arguments[i];for(var key in obj){if(obj.hasOwnProperty(key)){newObj[key]=obj[key]}}}return newObj}};var Utils=app.Utils;app.TodoModel=function(key){this.key=key;this.todos=[];this.onChanges=[]};app.TodoModel.prototype.subscribe=function(onChange){this.onChanges.push(onChange)};app.TodoModel.prototype.inform=function(){this.onChanges.forEach(function(cb){cb()})};app.TodoModel.prototype.addTodo=function(title){this.todos=this.todos.concat({id:Utils.uuid(),title:title,completed:false});this.inform()};app.TodoModel.prototype.toggleAll=function(checked){this.todos=this.todos.map(function(todo){return Utils.extend({},todo,{completed:checked})});this.inform()};app.TodoModel.prototype.toggle=function(todoToToggle){this.todos=this.todos.map(function(todo){return todo!==todoToToggle?todo:Utils.extend({},todo,{completed:!todo.completed})});this.inform()};app.TodoModel.prototype.destroy=function(todo){this.todos=this.todos.filter(function(candidate){return candidate!==todo});this.inform()};app.TodoModel.prototype.save=function(todoToSave,text){this.todos=this.todos.map(function(todo){return todo!==todoToSave?todo:Utils.extend({},todo,{title:text})});this.inform()};app.TodoModel.prototype.clearCompleted=function(){this.todos=this.todos.filter(function(todo){return!todo.completed});this.inform()};var TodoFooter=React.createClass({render:function(){var activeTodoWord=app.Utils.pluralize(this.props.count,"item");var clearButton=null;if(this.props.completedCount>0){clearButton=React.createElement("button",{className:"clear-completed",onClick:this.props.onClearCompleted},"Clear completed")}var nowShowing=this.props.nowShowing;return React.createElement("footer",{className:"footer"},React.createElement("span",{className:"todo-count"},React.createElement("strong",null,this.props.count)," ",activeTodoWord," left"),React.createElement("ul",{className:"filters"},React.createElement("li",null,React.createElement("a",{href:"#/",className:classNames({selected:nowShowing===app.ALL_TODOS})},"All"))," ",React.createElement("li",null,React.createElement("a",{href:"#/active",className:classNames({selected:nowShowing===app.ACTIVE_TODOS})},"Active"))," ",React.createElement("li",null,React.createElement("a",{href:"#/completed",className:classNames({selected:nowShowing===app.COMPLETED_TODOS})},"Completed"))),clearButton)}});var ESCAPE_KEY=27;var ENTER_KEY=13;var TodoItem=React.createClass({handleSubmit:function(event){var val=this.state.editText.trim();if(val){this.props.onSave(val);this.setState({editText:val})}else{this.props.onDestroy()}},handleEdit:function(){this.props.onEdit();this.setState({editText:this.props.todo.title})},handleKeyDown:function(event){if(event.which===ESCAPE_KEY){this.setState({editText:this.props.todo.title});this.props.onCancel(event)}else if(event.which===ENTER_KEY){this.handleSubmit(event)}},handleChange:function(event){if(this.props.editing){this.setState({editText:event.target.value})}},getInitialState:function(){return{editText:this.props.todo.title}},shouldComponentUpdate:function(nextProps,nextState){return nextProps.todo!==this.props.todo||nextProps.editing!==this.props.editing||nextState.editText!==this.state.editText},componentDidUpdate:function(prevProps){if(!prevProps.editing&&this.props.editing){var node=React.findDOMNode(this.refs.editField);node.focus();node.setSelectionRange(node.value.length,node.value.length)}},render:function(){return React.createElement("li",{className:classNames({completed:this.props.todo.completed,editing:this.props.editing})},React.createElement("div",{className:"view"},React.createElement("input",{className:"toggle",type:"checkbox",checked:this.props.todo.completed,onChange:this.props.onToggle}),React.createElement("label",{onDoubleClick:this.handleEdit},this.props.todo.title),React.createElement("button",{className:"destroy",onClick:this.props.onDestroy})),React.createElement("input",{ref:"editField",className:"edit",value:this.state.editText,onBlur:this.handleSubmit,onChange:this.handleChange,onKeyDown:this.handleKeyDown}))}});var ENTER_KEY=13;var TodoApp=React.createClass({getInitialState:function(){return{nowShowing:app.ALL_TODOS,editing:null,newTodo:""}},componentDidMount:function(){var setState=this.setState;var router=Router({"/":setState.bind(this,{nowShowing:app.ALL_TODOS}),"/active":setState.bind(this,{nowShowing:app.ACTIVE_TODOS}),"/completed":setState.bind(this,{nowShowing:app.COMPLETED_TODOS})});router.init("/")},handleChange:function(event){this.setState({newTodo:event.target.value})},handleNewTodoKeyDown:function(event){if(event.keyCode!==ENTER_KEY){return}event.preventDefault();var val=this.state.newTodo.trim();if(val){this.props.model.addTodo(val);this.setState({newTodo:""})}},toggleAll:function(event){var checked=event.target.checked;this.props.model.toggleAll(checked)},toggle:function(todoToToggle){this.props.model.toggle(todoToToggle)},destroy:function(todo){this.props.model.destroy(todo)},edit:function(todo){this.setState({editing:todo.id})},save:function(todoToSave,text){this.props.model.save(todoToSave,text);this.setState({editing:null})},cancel:function(){this.setState({editing:null})},clearCompleted:function(){this.props.model.clearCompleted()},render:function(){var footer;var main;var todos=this.props.model.todos;var shownTodos=todos.filter(function(todo){switch(this.state.nowShowing){case app.ACTIVE_TODOS:return!todo.completed;case app.COMPLETED_TODOS:return todo.completed;default:return true}},this);var todoItems=shownTodos.map(function(todo){return React.createElement(TodoItem,{key:todo.id,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),onCancel:this.cancel})},this);var activeTodoCount=todos.reduce(function(accum,todo){return todo.completed?accum:accum+1},0);var completedCount=todos.length-activeTodoCount;if(activeTodoCount||completedCount){footer=React.createElement(TodoFooter,{count:activeTodoCount,completedCount:completedCount,nowShowing:this.state.nowShowing,onClearCompleted:this.clearCompleted})}if(todos.length){main=React.createElement("section",{className:"main"},React.createElement("input",{className:"toggle-all",type:"checkbox",onChange:this.toggleAll,checked:activeTodoCount===0}),React.createElement("ul",{className:"todo-list"},todoItems))}return React.createElement("div",null,React.createElement("header",{className:"header"},React.createElement("h1",null,"todos"),React.createElement("input",{className:"new-todo",placeholder:"What needs to be done?",value:this.state.newTodo,onKeyDown:this.handleNewTodoKeyDown,onChange:this.handleChange,autoFocus:true})),main,footer)}});var model=new app.TodoModel("react-todos");function render(){ReactDOM.render(React.createElement(TodoApp,{model:model}),document.getElementsByClassName("todoapp")[0])}model.subscribe(render);render()})();