Fix backbone todo example bugs.
Fixed: - New todo not submitting correctly (page refreshes. `preventDefault` wasn't there. - Old checked todo being removed will leave the checkmark on the next todo replacing its position. - Cannot change todo (`value`'s now a controlled field). - `autofocus` (should be `autoFocus`, how ironic given the current situation) on new todo input isn't working. Switched to manual `focus()` in `componentDidMount` for now. - More consistent breathing space between lines. - Gutter at 80. Added: - Use todomvc-common base.css. The old one had to change ids to classes. No longer necessary. - Give `cx` a better name and move it in `Utils`. - Trim input upon finishing edit. - Remove todo if the new edited value is empty. - Submit edited todo value on input blur. - README to explain the existence of this example. Being able to maintain a non-compilant version allows nice deviations from the todomvc specs, such as animations, in the future.
This commit is contained in:
parent
3cf14e8f9b
commit
78d305eb16
|
@ -0,0 +1,3 @@
|
|||
# TodoMVC-Backbone
|
||||
|
||||
This is a lightweight version of TodoMVC. Its primary purpose is to demo the Backbone integration rather than being feature-complete (refer to `todomvc-director` for a full TodoMVC-compilant app).
|
|
@ -34,7 +34,7 @@ body {
|
|||
font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.todoapp {
|
||||
#todoapp {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
margin: 130px 0 40px 0;
|
||||
|
@ -46,7 +46,7 @@ body {
|
|||
0 25px 50px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.todoapp:before {
|
||||
#todoapp:before {
|
||||
content: '';
|
||||
border-left: 1px solid #f5d6d6;
|
||||
border-right: 1px solid #f5d6d6;
|
||||
|
@ -57,16 +57,16 @@ body {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.todoapp input::-webkit-input-placeholder {
|
||||
#todoapp input::-webkit-input-placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.todoapp input:-moz-placeholder {
|
||||
#todoapp input::-moz-placeholder {
|
||||
font-style: italic;
|
||||
color: #a9a9a9;
|
||||
}
|
||||
|
||||
.todoapp h1 {
|
||||
#todoapp h1 {
|
||||
position: absolute;
|
||||
top: -120px;
|
||||
width: 100%;
|
||||
|
@ -83,12 +83,12 @@ body {
|
|||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.header {
|
||||
#header {
|
||||
padding-top: 15px;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.header:before {
|
||||
#header:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -109,7 +109,7 @@ body {
|
|||
border-top-right-radius: 1px;
|
||||
}
|
||||
|
||||
.new-todo,
|
||||
#new-todo,
|
||||
.edit {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
|
@ -135,7 +135,7 @@ body {
|
|||
font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.new-todo {
|
||||
#new-todo {
|
||||
padding: 16px 16px 16px 60px;
|
||||
border: none;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
|
@ -143,17 +143,17 @@ body {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.main {
|
||||
#main {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
border-top: 1px dotted #adadad;
|
||||
}
|
||||
|
||||
.toggle-all-label {
|
||||
label[for='toggle-all'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
#toggle-all {
|
||||
position: absolute;
|
||||
top: -42px;
|
||||
left: -4px;
|
||||
|
@ -162,50 +162,50 @@ body {
|
|||
border: none; /* Mobile Safari */
|
||||
}
|
||||
|
||||
.toggle-all:before {
|
||||
#toggle-all:before {
|
||||
content: '»';
|
||||
font-size: 28px;
|
||||
color: #d9d9d9;
|
||||
padding: 0 25px 7px;
|
||||
}
|
||||
|
||||
.toggle-all:checked:before {
|
||||
#toggle-all:checked:before {
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.todo-list {
|
||||
#todo-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.todo-list li {
|
||||
#todo-list li {
|
||||
position: relative;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px dotted #ccc;
|
||||
}
|
||||
|
||||
.todo-list li:last-child {
|
||||
#todo-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing {
|
||||
#todo-list li.editing {
|
||||
border-bottom: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.todo-list li.editing .edit {
|
||||
#todo-list li.editing .edit {
|
||||
display: block;
|
||||
width: 506px;
|
||||
padding: 13px 17px 12px 17px;
|
||||
margin: 0 0 0 43px;
|
||||
}
|
||||
|
||||
.todo-list li.editing .view {
|
||||
#todo-list li.editing .view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
#todo-list li .toggle {
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||
|
@ -222,7 +222,7 @@ body {
|
|||
appearance: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle:after {
|
||||
#todo-list li .toggle:after {
|
||||
content: '✔';
|
||||
line-height: 43px; /* 40 + a couple of pixels visual adjustment */
|
||||
font-size: 20px;
|
||||
|
@ -230,16 +230,17 @@ body {
|
|||
text-shadow: 0 -1px 0 #bfbfbf;
|
||||
}
|
||||
|
||||
.todo-list li .toggle:checked:after {
|
||||
#todo-list li .toggle:checked:after {
|
||||
color: #85ada7;
|
||||
text-shadow: 0 1px 0 #669991;
|
||||
bottom: 1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.todo-list li label {
|
||||
#todo-list li label {
|
||||
white-space: pre;
|
||||
word-break: break-word;
|
||||
padding: 15px;
|
||||
padding: 15px 60px 15px 15px;
|
||||
margin-left: 45px;
|
||||
display: block;
|
||||
line-height: 1.2;
|
||||
|
@ -250,12 +251,12 @@ body {
|
|||
transition: color 0.4s;
|
||||
}
|
||||
|
||||
.todo-list li.completed label {
|
||||
#todo-list li.completed label {
|
||||
color: #a9a9a9;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.todo-list li .destroy {
|
||||
#todo-list li .destroy {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -273,7 +274,7 @@ body {
|
|||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:hover {
|
||||
#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);
|
||||
|
@ -283,23 +284,23 @@ body {
|
|||
transform: scale(1.3);
|
||||
}
|
||||
|
||||
.todo-list li .destroy:after {
|
||||
#todo-list li .destroy:after {
|
||||
content: '✖';
|
||||
}
|
||||
|
||||
.todo-list li:hover .destroy {
|
||||
#todo-list li:hover .destroy {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.todo-list li .edit {
|
||||
#todo-list li .edit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing:last-child {
|
||||
#todo-list li.editing:last-child {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
#footer {
|
||||
color: #777;
|
||||
padding: 0 15px;
|
||||
position: absolute;
|
||||
|
@ -311,7 +312,7 @@ body {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.footer:before {
|
||||
#footer:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
@ -326,12 +327,12 @@ body {
|
|||
0 44px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.todo-count {
|
||||
#todo-count {
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.filters {
|
||||
#filters {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
@ -340,21 +341,21 @@ body {
|
|||
left: 0;
|
||||
}
|
||||
|
||||
.filters li {
|
||||
#filters li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.filters li a {
|
||||
#filters li a {
|
||||
color: #83756f;
|
||||
margin: 2px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.filters li a.selected {
|
||||
#filters li a.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.clear-completed {
|
||||
#clear-completed {
|
||||
float: right;
|
||||
position: relative;
|
||||
line-height: 20px;
|
||||
|
@ -366,12 +367,12 @@ body {
|
|||
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.clear-completed:hover {
|
||||
#clear-completed:hover {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.info {
|
||||
#info {
|
||||
margin: 65px auto 0;
|
||||
color: #a6a6a6;
|
||||
font-size: 12px;
|
||||
|
@ -379,29 +380,25 @@ body {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.info a {
|
||||
#info a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
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 {
|
||||
#toggle-all,
|
||||
#todo-list li .toggle {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
#todo-list li .toggle {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
#toggle-all {
|
||||
top: -56px;
|
||||
left: -15px;
|
||||
width: 65px;
|
||||
|
@ -413,6 +410,147 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.hidden{
|
||||
display:none;
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 20px 0;
|
||||
border: 0;
|
||||
border-top: 1px dashed #C5C5C5;
|
||||
border-bottom: 1px dashed #F7F7F7;
|
||||
}
|
||||
|
||||
.learn a {
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
color: #b83f45;
|
||||
}
|
||||
|
||||
.learn a:hover {
|
||||
text-decoration: underline;
|
||||
color: #787e7e;
|
||||
}
|
||||
|
||||
.learn h3,
|
||||
.learn h4,
|
||||
.learn h5 {
|
||||
margin: 10px 0;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.learn h3 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.learn h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.learn h5 {
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.learn ul {
|
||||
padding: 0;
|
||||
margin: 0 0 30px 25px;
|
||||
}
|
||||
|
||||
.learn li {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.learn p {
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
line-height: 1.3;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.quote {
|
||||
border: none;
|
||||
margin: 20px 0 60px 0;
|
||||
}
|
||||
|
||||
.quote p {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.quote p:before {
|
||||
content: '“';
|
||||
font-size: 50px;
|
||||
opacity: .15;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.quote p:after {
|
||||
content: '”';
|
||||
font-size: 50px;
|
||||
opacity: .15;
|
||||
position: absolute;
|
||||
bottom: -42px;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.quote footer {
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.quote footer img {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.quote footer a {
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.speech-bubble {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, .04);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.speech-bubble:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 30px;
|
||||
border: 13px solid transparent;
|
||||
border-top-color: rgba(0, 0, 0, .04);
|
||||
}
|
||||
|
||||
/**body*/.learn-bar > .learn {
|
||||
position: absolute;
|
||||
width: 272px;
|
||||
top: 8px;
|
||||
left: -300px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: rgba(255, 255, 255, .6);
|
||||
transition-property: left;
|
||||
transition-duration: 500ms;
|
||||
}
|
||||
|
||||
@media (min-width: 899px) {
|
||||
/**body*/.learn-bar {
|
||||
width: auto;
|
||||
margin: 0 0 0 300px;
|
||||
}
|
||||
/**body*/.learn-bar > .learn {
|
||||
left: 8px;
|
||||
}
|
||||
/**body*/.learn-bar #todoapp {
|
||||
width: 550px;
|
||||
margin: 130px auto 40px auto;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="todoapp"></div>
|
||||
<div id="container"></div>
|
||||
<script src="../../build/react.js"></script>
|
||||
<script src="../../build/JSXTransformer.js"></script>
|
||||
<script type="text/javascript" src="../shared/thirdparty/jquery.min.js" charset="utf-8"></script>
|
||||
|
|
|
@ -1,20 +1,6 @@
|
|||
/** @jsx React.DOM */
|
||||
|
||||
function cx(obj) {
|
||||
var s = '';
|
||||
for (var key in obj) {
|
||||
if (!obj.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
if (obj[key]) {
|
||||
s += key + ' ';
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
var Todo = Backbone.Model.extend({
|
||||
|
||||
// Default attributes for the todo
|
||||
// and ensure that each todo created has `title` and `completed` keys.
|
||||
defaults: {
|
||||
|
@ -28,7 +14,6 @@ var Todo = Backbone.Model.extend({
|
|||
completed: !this.get('completed')
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var TodoList = Backbone.Collection.extend({
|
||||
|
@ -68,6 +53,19 @@ var TodoList = Backbone.Collection.extend({
|
|||
var Utils = {
|
||||
pluralize: function( count, word ) {
|
||||
return count === 1 ? word : word + 's';
|
||||
},
|
||||
|
||||
stringifyObjKeys: function(obj) {
|
||||
var s = '';
|
||||
for (var key in obj) {
|
||||
if (!obj.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
if (obj[key]) {
|
||||
s += key + ' ';
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -75,31 +73,47 @@ var Utils = {
|
|||
|
||||
var TodoItem = React.createClass({
|
||||
handleSubmit: function(event) {
|
||||
var val = this.refs.editField.getDOMNode().value;
|
||||
var val = this.refs.editField.getDOMNode().value.trim();
|
||||
if (val) {
|
||||
this.props.onSave(val);
|
||||
} else {
|
||||
this.props.onDestroy();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
onEdit: function() {
|
||||
this.props.onEdit();
|
||||
this.refs.editField.getDOMNode().focus();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var classes = Utils.stringifyObjKeys({
|
||||
completed: this.props.todo.get('completed'), editing: this.props.editing
|
||||
});
|
||||
return (
|
||||
<li class={cx({completed: this.props.todo.get('completed'), editing: this.props.editing})}>
|
||||
<li class={classes}>
|
||||
<div class="view">
|
||||
<input
|
||||
class="toggle"
|
||||
type="checkbox"
|
||||
checked={this.props.todo.get('completed') ? 'checked' : null}
|
||||
checked={this.props.todo.get('completed')}
|
||||
onChange={this.props.onToggle}
|
||||
key={this.props.key}
|
||||
/>
|
||||
<label onDoubleClick={this.onEdit}>{this.props.todo.get('title')}</label>
|
||||
<label onDoubleClick={this.onEdit}>
|
||||
{this.props.todo.get('title')}
|
||||
</label>
|
||||
<button class="destroy" onClick={this.props.onDestroy} />
|
||||
</div>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<input ref="editField" class="edit" value={this.props.todo.get('title')} />
|
||||
<input
|
||||
ref="editField"
|
||||
class="edit"
|
||||
defaultValue={this.props.todo.get('title')}
|
||||
onBlur={this.handleSubmit}
|
||||
autoFocus="autofocus"
|
||||
/>
|
||||
</form>
|
||||
</li>
|
||||
);
|
||||
|
@ -113,37 +127,46 @@ var TodoFooter = React.createClass({
|
|||
|
||||
if (this.props.completedCount > 0) {
|
||||
clearButton = (
|
||||
<button class="clear-completed" onClick={this.props.onClearCompleted}>Clear completed ({this.props.completedCount})</button>
|
||||
<button id="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>
|
||||
<footer id="footer">
|
||||
<span id="todo-count">
|
||||
<strong>{this.props.count}</strong>{' '}
|
||||
{activeTodoWord}{' '}left
|
||||
</span>
|
||||
{clearButton}
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// An example generic Mixin that you can add to any component that should react to changes in a Backbone component.
|
||||
// The use cases we've identified thus far are for Collections -- since they trigger a change event whenever
|
||||
// any of their constituent items are changed there's no need to reconcile for regular models.
|
||||
// One caveat: this relies on getBackboneModels() to always return the same model instances throughout the
|
||||
// lifecycle of the component. If you're using this mixin correctly (it should be near the top of your
|
||||
// component hierarchy) this should not be an issue.
|
||||
// An example generic Mixin that you can add to any component that should react
|
||||
// to changes in a Backbone component. The use cases we've identified thus far
|
||||
// are for Collections -- since they trigger a change event whenever any of
|
||||
// their constituent items are changed there's no need to reconcile for regular
|
||||
// models. One caveat: this relies on getBackboneModels() to always return the
|
||||
// same model instances throughout the lifecycle of the component. If you're
|
||||
// using this mixin correctly (it should be near the top of your component
|
||||
// hierarchy) this should not be an issue.
|
||||
var BackboneMixin = {
|
||||
componentDidMount: function() {
|
||||
// Whenever there may be a change in the Backbone data, trigger a reconcile.
|
||||
this.getBackboneModels().map(function(model) {
|
||||
model.on('add change remove', this.forceUpdate, this);
|
||||
}.bind(this));
|
||||
this.getBackboneModels().forEach(function(model) {
|
||||
model.on('add change remove', this.forceUpdate.bind(this, null), this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
// Ensure that we clean up any dangling references when the component is destroyed.
|
||||
this.getBackboneModels().map(function(model) {
|
||||
// Ensure that we clean up any dangling references when the component is
|
||||
// destroyed.
|
||||
this.getBackboneModels().forEach(function(model) {
|
||||
model.off(null, null, this);
|
||||
}.bind(this));
|
||||
}, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -152,21 +175,28 @@ var TodoApp = React.createClass({
|
|||
getInitialState: function() {
|
||||
return {editing: null};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// Additional functionality for todomvc: fetch() the collection on init
|
||||
this.props.todos.fetch();
|
||||
this.refs.newField.getDOMNode().focus();
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
// If saving were expensive we'd listen for mutation events on Backbone and do this manually.
|
||||
// however, since saving isn't expensive this is an elegant way to keep it reactively up-to-date.
|
||||
this.props.todos.map(function(todo) {
|
||||
// If saving were expensive we'd listen for mutation events on Backbone and
|
||||
// do this manually. however, since saving isn't expensive this is an
|
||||
// elegant way to keep it reactively up-to-date.
|
||||
this.props.todos.forEach(function(todo) {
|
||||
todo.save();
|
||||
});
|
||||
},
|
||||
|
||||
getBackboneModels: function() {
|
||||
return [this.props.todos];
|
||||
},
|
||||
handleSubmit: function() {
|
||||
|
||||
handleSubmit: function(event) {
|
||||
event.preventDefault();
|
||||
var val = this.refs.newField.getDOMNode().value.trim();
|
||||
if (val) {
|
||||
this.props.todos.create({
|
||||
|
@ -176,48 +206,63 @@ var TodoApp = React.createClass({
|
|||
});
|
||||
this.refs.newField.getDOMNode().value = '';
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
toggleAll: function(event) {
|
||||
var checked = event.nativeEvent.target.checked;
|
||||
this.props.todos.map(function(todo) {
|
||||
this.props.todos.forEach(function(todo) {
|
||||
todo.set('completed', checked);
|
||||
});
|
||||
},
|
||||
destroy: function(todo) {
|
||||
this.props.todos.remove(todo);
|
||||
},
|
||||
|
||||
edit: function(todo) {
|
||||
this.setState({editing: todo.get('id')});
|
||||
},
|
||||
|
||||
save: function(todo, text) {
|
||||
todo.set('title', text);
|
||||
this.setState({editing: null});
|
||||
},
|
||||
|
||||
clearCompleted: function() {
|
||||
this.props.todos.completed().map(function(todo) {
|
||||
this.props.todos.completed().forEach(function(todo) {
|
||||
todo.destroy();
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var footer = null;
|
||||
var main = null;
|
||||
var todoItems = this.props.todos.map(function(todo) {
|
||||
return <TodoItem todo={todo} onToggle={todo.toggle.bind(todo)} onDestroy={this.destroy.bind(this, todo)} onEdit={this.edit.bind(this, todo)} editing={this.state.editing === todo.get('id')} onSave={this.save.bind(this, todo)} />;
|
||||
}.bind(this));
|
||||
return (
|
||||
<TodoItem
|
||||
key={Math.random()}
|
||||
todo={todo}
|
||||
onToggle={todo.toggle.bind(todo)}
|
||||
onDestroy={todo.destroy.bind(todo)}
|
||||
onEdit={this.edit.bind(this, todo)}
|
||||
editing={this.state.editing === todo.get('id')}
|
||||
onSave={this.save.bind(this, todo)}
|
||||
/>
|
||||
);
|
||||
}, this);
|
||||
|
||||
var activeTodoCount = this.props.todos.remaining().length;
|
||||
var completedCount = todoItems.length - activeTodoCount;
|
||||
if (activeTodoCount || completedCount) {
|
||||
footer = <TodoFooter count={activeTodoCount} completedCount={completedCount} onClearCompleted={this.clearCompleted} />;
|
||||
if (activeTodoCount || completedCount) {
|
||||
footer =
|
||||
<TodoFooter
|
||||
count={activeTodoCount}
|
||||
completedCount={completedCount}
|
||||
onClearCompleted={this.clearCompleted}
|
||||
/>;
|
||||
}
|
||||
|
||||
if (todoItems.length) {
|
||||
main = (
|
||||
<section class="main">
|
||||
<input class="toggle-all" type="checkbox" onChange={this.toggleAll} />
|
||||
<label class="toggle-all-label">Mark all as complete</label>
|
||||
<ul class="todo-list">
|
||||
<section id="main">
|
||||
<input id="toggle-all" type="checkbox" onChange={this.toggleAll} />
|
||||
<ul id="todo-list">
|
||||
{todoItems}
|
||||
</ul>
|
||||
</section>
|
||||
|
@ -226,23 +271,33 @@ var TodoApp = React.createClass({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<section id="todoapp">
|
||||
<header id="header">
|
||||
<h1>todos</h1>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<input ref="newField" class="new-todo" placeholder="What needs to be done?" autofocus="autofocus" />
|
||||
<input
|
||||
ref="newField"
|
||||
id="new-todo"
|
||||
placeholder="What needs to be done?"
|
||||
/>
|
||||
</form>
|
||||
</header>
|
||||
{main}
|
||||
{footer}
|
||||
</section>
|
||||
<footer class="info">
|
||||
<footer id="info">
|
||||
<p>Double-click to edit a todo</p>
|
||||
<p>Created by{' '}<a href="http://github.com/petehunt/">petehunt</a></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 todos={new TodoList()} />, document.getElementById('todoapp'));
|
||||
|
||||
React.renderComponent(
|
||||
<TodoApp todos={new TodoList()} />, document.getElementById('container')
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue