Commit 1b16a19d authored by Cheng Lou's avatar Cheng Lou

Upgrade to React 0.4, fully compilant

- Compilant with coding style.
- Removed app.css. No need anymore since we freed id.
- Render info footer separately (specs).
- Tweak footer to make it pass jshint when compiled:
  - {''} after 'left'.
  - {''} around `clear completed`.
  - Breaking style for footer clearButton, `>` on the same line.
- Rename cx and put it in Utils.
- Routing with director.
- Change some keyUp to keyDown (specs).
- Strip form tags (specs? Referred to the stable ones).
- Trim after editing item.
- Manually autofocus (attribute not supported in ie9).
- Input focus now triggers as a callback (because of batch rendering).
- Controlled input.
- Gutter at 80.
- Class `completed` trim whitespace.
- Remove bind.
parent 26bdbb2d
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"todomvc-common": "~0.1.7", "todomvc-common": "~0.1.7",
"react": "~0.3.2" "react": "~0.4.0"
} }
} }
{
"name": "react",
"version": "0.4.0",
"main": "react.js",
"homepage": "https://github.com/facebook/react-bower",
"_release": "0.4.0",
"_resolution": {
"type": "version",
"tag": "v0.4.0",
"commit": "54334ad626d26dff4c214d308cefd30ad80fb8e9"
},
"_source": "git://github.com/facebook/react-bower.git",
"_target": "~0.4.0"
}
\ No newline at end of file
{
"name": "react",
"version": "0.4.0",
"main": "react.js"
}
\ No newline at end of file
{
"name": "todomvc-common",
"version": "0.1.7",
"homepage": "https://github.com/tastejs/todomvc-common",
"_release": "0.1.7",
"_resolution": {
"type": "version",
"tag": "v0.1.7",
"commit": "e5b3251c95f29d872636b761e32a2296dc97c3e0"
},
"_source": "git://github.com/tastejs/todomvc-common.git",
"_target": "~0.1.7"
}
\ No newline at end of file
...@@ -136,10 +136,6 @@ ...@@ -136,10 +136,6 @@
} }
function getFile(file, callback) { function getFile(file, callback) {
if (!location.host) {
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
}
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('GET', findRoot() + file, true); xhr.open('GET', findRoot() + file, true);
......
/** Reset base.css #todoapp */
#todoapp {
background: none;
margin: auto;
border: 0;
position: static;
box-shadow: none;
}
#todoapp:before {
display: none;
left: 100%;
}
.todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
.todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
.header {
padding-top: 15px;
border-radius: inherit;
}
.header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
.new-todo {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
.main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
.toggle-all-label {
display: none;
}
.toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
border: none; /* Mobile Safari */
}
.toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
.toggle-all:checked:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
/*-moz-appearance: none;*/
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
.todo-list li .toggle:after {
content: '✔';
line-height: 43px; /* 40 + a couple of pixels visual adjustment */
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
.todo-list li .toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
.todo-list li label {
word-break: break-word;
padding: 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
-moz-transition: color 0.4s;
-ms-transition: color 0.4s;
-o-transition: color 0.4s;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #a9a9a9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
-ms-transition: all 0.2s;
-o-transition: all 0.2s;
transition: all 0.2s;
}
.todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-moz-transform: scale(1.3);
-ms-transform: scale(1.3);
-o-transform: scale(1.3);
transform: scale(1.3);
}
.todo-list li .destroy:after {
content: '✖';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
z-index: 1;
text-align: center;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: #83756f;
margin: 2px;
text-decoration: none;
}
.filters li a.selected {
font-weight: bold;
}
.clear-completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}
.clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
.info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
.info a {
color: inherit;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
.toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
@media (min-width: 899px) {
/**body*/.learn-bar .todoapp {
width: 550px;
margin: 130px auto 40px auto;
}
}
#benchmark {
position: absolute;
left: 0;
top: 0;
padding: 10px;
}
.submitButton {
display: none;
}
...@@ -5,19 +5,20 @@ ...@@ -5,19 +5,20 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>React • TodoMVC</title> <title>React • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> <link rel="stylesheet" href="bower_components/todomvc-common/base.css">
<link rel="stylesheet" href="css/app.css">
</head> </head>
<body> <body>
<div id="todoapp"></div> <section id="todoapp"></section>
<footer id="info"></footer>
<div id="benchmark"></div> <div id="benchmark"></div>
<script src="bower_components/todomvc-common/base.js"></script> <script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/react/react.js"></script> <script src="bower_components/react/react.js"></script>
<script src="bower_components/react/JSXTransformer.js"></script> <script src="bower_components/react/JSXTransformer.js"></script>
<script src="../../../assets/director.min.js"></script>
<script type="text/jsx" src="js/utils.js"></script> <script type="text/jsx" src="js/utils.jsx"></script>
<script type="text/jsx" src="js/todoItem.js"></script> <script type="text/jsx" src="js/todoItem.jsx"></script>
<script type="text/jsx" src="js/footer.js"></script> <script type="text/jsx" src="js/footer.jsx"></script>
<script type="text/jsx" src="js/app.js"></script> <script type="text/jsx" src="js/app.jsx"></script>
</body> </body>
</html> </html>
/** /**
* @jsx React.DOM * @jsx React.DOM
*/ */
/*jshint quotmark:false */
/*jshint white:false */
/*jshint trailing:false */
/*jshint newcap:false */
/*global Utils, ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS,
TodoItem, TodoFooter, React, Router*/
(function (window, React) { (function (window, React) {
'use strict'; 'use strict';
window.ALL_TODOS = 'all';
window.ACTIVE_TODOS = 'active';
window.COMPLETED_TODOS = 'completed';
var ENTER_KEY = 13;
var TodoApp = React.createClass({ var TodoApp = React.createClass({
getInitialState: function () { getInitialState: function () {
var todos = Utils.store('react-todos');
return { return {
todos: Utils.store('react-todos'), todos: todos,
nowShowing: ALL_TODOS,
editing: null editing: null
}; };
}, },
handleSubmit: React.autoBind(function () { componentDidMount: function () {
var router = Router({
'/': this.setState.bind(this, {nowShowing: ALL_TODOS}),
'/active': this.setState.bind(this, {nowShowing: ACTIVE_TODOS}),
'/completed': this.setState.bind(this, {nowShowing: COMPLETED_TODOS})
});
router.init();
this.refs.newField.getDOMNode().focus();
},
handleNewTodoKeyDown: function (event) {
if (event.which !== ENTER_KEY) {
return;
}
var val = this.refs.newField.getDOMNode().value.trim(); var val = this.refs.newField.getDOMNode().value.trim();
var todos; var todos;
var newTodo; var newTodo;
...@@ -24,26 +53,26 @@ ...@@ -24,26 +53,26 @@
title: val, title: val,
completed: false completed: false
}; };
this.setState({ todos: todos.concat([newTodo]) }); this.setState({todos: todos.concat([newTodo])});
this.refs.newField.getDOMNode().value = ''; this.refs.newField.getDOMNode().value = '';
} }
return false; return false;
}), },
toggleAll: function (event) { toggleAll: function (event) {
var checked = event.nativeEvent.target.checked; var checked = event.target.checked;
this.state.todos.map(function (todo) { this.state.todos.forEach(function (todo) {
todo.completed = checked; todo.completed = checked;
}); });
this.setState({ todos: this.state.todos }); this.setState({todos: this.state.todos});
}, },
toggle: function (todo) { toggle: function (todo) {
todo.completed = !todo.completed; todo.completed = !todo.completed;
this.setState({ todos: this.state.todos }); this.setState({todos: this.state.todos});
}, },
destroy: function (todo) { destroy: function (todo) {
...@@ -51,11 +80,15 @@ ...@@ -51,11 +80,15 @@
return candidate.id !== todo.id; return candidate.id !== todo.id;
}); });
this.setState({ todos: newTodos }); this.setState({todos: newTodos});
}, },
edit: function (todo) { edit: function (todo, callback) {
this.setState({ editing: todo.id }); // refer to todoItem.js `handleEdit` for the reasoning behind the
// callback
this.setState({editing: todo.id}, function () {
callback();
});
}, },
save: function (todo, text) { save: function (todo, text) {
...@@ -63,9 +96,9 @@ ...@@ -63,9 +96,9 @@
this.setState({todos: this.state.todos, editing: null}); this.setState({todos: this.state.todos, editing: null});
}, },
cancel: React.autoBind(function () { cancel: function () {
this.setState({editing: null}); this.setState({editing: null});
}), },
clearCompleted: function () { clearCompleted: function () {
var newTodos = this.state.todos.filter(function (todo) { var newTodos = this.state.todos.filter(function (todo) {
...@@ -86,16 +119,27 @@ ...@@ -86,16 +119,27 @@
var activeTodoCount; var activeTodoCount;
var completedCount; var completedCount;
this.state.todos.map(function (todo) { var shownTodos = this.state.todos.filter(function (todo) {
switch (this.state.nowShowing) {
case ACTIVE_TODOS:
return !todo.completed;
case COMPLETED_TODOS:
return todo.completed;
default:
return true;
}
}.bind(this));
shownTodos.forEach(function (todo) {
todoItems[todo.id] = ( todoItems[todo.id] = (
<TodoItem <TodoItem
todo={ todo } todo={todo}
onToggle={ this.toggle.bind(this, todo) } onToggle={this.toggle.bind(this, todo)}
onDestroy={ this.destroy.bind(this, todo) } onDestroy={this.destroy.bind(this, todo)}
onEdit={ this.edit.bind(this, todo) } onEdit={this.edit.bind(this, todo)}
editing={ this.state.editing === todo.id } editing={this.state.editing === todo.id}
onSave={ this.save.bind(this, todo) } onSave={this.save.bind(this, todo)}
onCancel={ this.cancel } onCancel={this.cancel}
/> />
); );
}.bind(this)); }.bind(this));
...@@ -109,19 +153,24 @@ ...@@ -109,19 +153,24 @@
if (activeTodoCount || completedCount) { if (activeTodoCount || completedCount) {
footer = footer =
<TodoFooter <TodoFooter
count={ activeTodoCount } count={activeTodoCount}
completedCount={ completedCount } completedCount={completedCount}
onClearCompleted={ this.clearCompleted.bind(this) } nowShowing={this.state.nowShowing}
onClearCompleted={this.clearCompleted}
/>; />;
} }
if (this.state.todos.length) { if (this.state.todos.length) {
main = ( main = (
<section class="main"> <section id="main">
<input class="toggle-all" type="checkbox" onChange={ this.toggleAll.bind(this) } checked={activeTodoCount === 0} /> <input
<label class="toggle-all-label">Mark all as complete</label> id="toggle-all"
<ul class="todo-list"> type="checkbox"
{ todoItems } onChange={this.toggleAll}
checked={activeTodoCount === 0}
/>
<ul id="todo-list">
{todoItems}
</ul> </ul>
</section> </section>
); );
...@@ -129,31 +178,30 @@ ...@@ -129,31 +178,30 @@
return ( return (
<div> <div>
<section class="todoapp"> <header id="header">
<header class="header">
<h1>todos</h1> <h1>todos</h1>
<form onSubmit={ this.handleSubmit }>
<input <input
ref="newField" ref="newField"
class="new-todo" id="new-todo"
placeholder="What needs to be done?" placeholder="What needs to be done?"
autofocus="autofocus" onKeyDown={this.handleNewTodoKeyDown}
/> />
<input type="submit" class="submitButton" />
</form>
</header> </header>
{main} {main}
{footer} {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> </div>
); );
} }
}); });
React.renderComponent(<TodoApp />, document.getElementById('todoapp')); React.renderComponent(<TodoApp />, document.getElementById('todoapp'));
React.renderComponent(
<div>
<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>
</div>,
document.getElementById('info'));
})(window, React); })(window, React);
/** /**
* @jsx React.DOM * @jsx React.DOM
*/ */
/*jshint quotmark:false */
/*jshint white:false */
/*jshint trailing:false */
/*jshint newcap:false */
/*global React, ALL_TODOS, ACTIVE_TODOS, Utils, COMPLETED_TODOS */
(function (window) { (function (window) {
'use strict'; 'use strict';
...@@ -12,18 +17,39 @@ ...@@ -12,18 +17,39 @@
if (this.props.completedCount > 0) { if (this.props.completedCount > 0) {
clearButton = ( clearButton = (
<button <button
class="clear-completed" id="clear-completed"
onClick={ this.props.onClearCompleted }> onClick={this.props.onClearCompleted}>
Clear completed ({ this.props.completedCount }) {''}Clear completed ({this.props.completedCount}){''}
</button> </button>
); );
} }
var show = {
ALL_TODOS: '',
ACTIVE_TODOS: '',
COMPLETED_TODOS: ''
};
show[this.props.nowShowing] = 'selected';
return ( return (
<footer class="footer"> <footer id="footer">
<span class="todo-count"> <span id="todo-count">
<strong>{this.props.count}</strong>{' '}{ activeTodoWord }{' '}left <strong>{this.props.count}</strong>
{' '}{activeTodoWord}{' '}left{''}
</span> </span>
<ul id="filters">
<li>
<a href="#/" class={show[ALL_TODOS]}>All</a>
</li>
{' '}
<li>
<a href="#/active" class={show[ACTIVE_TODOS]}>Active</a>
</li>
{' '}
<li>
<a href="#/completed" class={show[COMPLETED_TODOS]}>Completed</a>
</li>
</ul>
{clearButton} {clearButton}
</footer> </footer>
); );
......
/** /**
* @jsx React.DOM * @jsx React.DOM
*/ */
/*jshint quotmark: false */
/*jshint white: false */
/*jshint trailing: false */
/*jshint newcap: false */
/*global React, Utils */
(function (window) { (function (window) {
'use strict'; 'use strict';
var ESCAPE_KEY = 27; var ESCAPE_KEY = 27;
var ENTER_KEY = 13;
window.TodoItem = React.createClass({ window.TodoItem = React.createClass({
handleSubmit: React.autoBind(function () { handleSubmit: function () {
var val = this.state.editText.trim(); var val = this.state.editText.trim();
if (val) { if (val) {
this.props.onSave(val); this.props.onSave(val);
this.setState({ editText: val }); this.setState({editText: val});
} else { } else {
this.props.onDestroy(); this.props.onDestroy();
} }
return false; return false;
}), },
handleEdit: function () {
handleEdit: React.autoBind(function () { // react optimizes renders by batching them. This means you can't call
this.props.onEdit(); // parent's `onEdit` (which in this case triggeres a re-render), and
// immediately manipulate the DOM as if the rendering's over. Put it as a
// callback. Refer to app.js' `edit` method
this.props.onEdit(function () {
var node = this.refs.editField.getDOMNode(); var node = this.refs.editField.getDOMNode();
node.focus(); node.focus();
node.setSelectionRange(node.value.length, node.value.length); node.setSelectionRange(node.value.length, node.value.length);
}), }.bind(this));
},
handleKey: React.autoBind(function (event) { handleKeyDown: function (event) {
if (event.nativeEvent.keyCode === ESCAPE_KEY) { if (event.keyCode === ESCAPE_KEY) {
this.setState({ editText: this.props.todo.title }); this.setState({editText: this.props.todo.title});
this.props.onCancel(); this.props.onCancel();
} else if (event.keyCode === ENTER_KEY) {
this.handleSubmit();
} else { } else {
this.setState({ editText: event.target.value }); this.setState({editText: event.target.value});
} }
}), },
handleChange: function (event) {
this.setState({editText: event.target.value});
},
getInitialState: function () { getInitialState: function () {
return { editText: this.props.todo.title }; return {editText: this.props.todo.title};
}, },
componentWillReceiveProps: function (nextProps) { componentWillReceiveProps: function (nextProps) {
...@@ -47,7 +62,7 @@ ...@@ -47,7 +62,7 @@
render: function () { render: function () {
return ( return (
<li class={cx({ <li class={Utils.stringifyObjKeys({
completed: this.props.todo.completed, completed: this.props.todo.completed,
editing: this.props.editing editing: this.props.editing
})}> })}>
...@@ -55,22 +70,22 @@ ...@@ -55,22 +70,22 @@
<input <input
class="toggle" class="toggle"
type="checkbox" type="checkbox"
checked={ this.props.todo.completed ? 'checked' : null } checked={this.props.todo.completed ? 'checked' : null}
onChange={ this.props.onToggle } onChange={this.props.onToggle}
/> />
<label onDoubleClick={ this.handleEdit }>{ this.props.todo.title }</label> <label onDoubleClick={this.handleEdit}>
<button class='destroy' onClick={ this.props.onDestroy } /> {this.props.todo.title}
</label>
<button class='destroy' onClick={this.props.onDestroy} />
</div> </div>
<form onSubmit={this.handleSubmit}>
<input <input
ref="editField" ref="editField"
class="edit" class="edit"
value={ this.state.editText } value={this.state.editText}
onBlur={ this.handleSubmit } onBlur={this.handleSubmit}
onKeyUp={ this.handleKey } onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
/> />
<input type="submit" class="submitButton" />
</form>
</li> </li>
); );
} }
......
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
if (i === 8 || i === 12 || i === 16 || i === 20) { if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-'; uuid += '-';
} }
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16); uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
.toString(16);
} }
return uuid; return uuid;
...@@ -29,10 +30,9 @@ ...@@ -29,10 +30,9 @@
var store = localStorage.getItem(namespace); var store = localStorage.getItem(namespace);
return (store && JSON.parse(store)) || []; return (store && JSON.parse(store)) || [];
} },
};
window.cx = function (obj) { stringifyObjKeys: function (obj) {
var s = ''; var s = '';
var key; var key;
...@@ -42,6 +42,8 @@ ...@@ -42,6 +42,8 @@
} }
} }
return s; return s.trim();
}
}; };
})(window); })(window);
...@@ -11,9 +11,28 @@ The [React getting started documentation](http://facebook.github.io/react/docs/g ...@@ -11,9 +11,28 @@ The [React getting started documentation](http://facebook.github.io/react/docs/g
Here are some links you may find helpful: Here are some links you may find helpful:
* [Tutorial](http://facebook.github.io/react/docs/tutorial.html) * [Documentation](http://facebook.github.io/react/docs/getting-started.html)
* [Philosophy](http://www.quora.com/Pete-Hunt/Posts/React-Under-the-Hood) * [API Reference](http://facebook.github.io/react/docs/reference.html)
* [Common Questions](http://facebook.github.io/react/docs/common-questions.html) * [Blog](http://facebook.github.io/react/blog/)
* [React on GitHub](https://github.com/facebook/react)
* [Support](http://facebook.github.io/react/support.html) * [Support](http://facebook.github.io/react/support.html)
Articles and guides from the community:
* [Philosophy](http://www.quora.com/Pete-Hunt/Posts/React-Under-the-Hood)
* [How is Facebook's React JavaScript library](http://www.quora.com/React-JS-Library/How-is-Facebooks-React-JavaScript-library)
* [React: Under the hood](http://www.quora.com/Pete-Hunt/Posts/React-Under-the-Hood)
Get help from other React users:
* [React on StackOverflow](http://stackoverflow.com/questions/tagged/reactjs)
* [Mailing list on Google Groups](https://groups.google.com/forum/#!forum/reactjs)
*
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ _If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
## Running
The app is built with [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html) and compiled at runtime for a lighter and more fun code reading experience. As stated in the link, JSX is not mandatory.
To run the app, spin up an HTTP server (e.g. `python -m SimpleHTTPServer`) and visit http://localhost/.../myexample/.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment