From 78d305eb167af7f5446c5d4812a160946f493234 Mon Sep 17 00:00:00 2001 From: Cheng Lou Date: Fri, 6 Sep 2013 16:40:35 -0400 Subject: [PATCH] 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. --- examples/todomvc-backbone/README.md | 3 + examples/todomvc-backbone/css/base.css | 244 +++++++++++++++++++------ examples/todomvc-backbone/index.html | 2 +- examples/todomvc-backbone/js/app.js | 173 ++++++++++++------ 4 files changed, 309 insertions(+), 113 deletions(-) create mode 100644 examples/todomvc-backbone/README.md diff --git a/examples/todomvc-backbone/README.md b/examples/todomvc-backbone/README.md new file mode 100644 index 0000000000000..b2e9210f5a442 --- /dev/null +++ b/examples/todomvc-backbone/README.md @@ -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). diff --git a/examples/todomvc-backbone/css/base.css b/examples/todomvc-backbone/css/base.css index 0baca436d368a..8c300228ade20 100644 --- a/examples/todomvc-backbone/css/base.css +++ b/examples/todomvc-backbone/css/base.css @@ -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; + } } diff --git a/examples/todomvc-backbone/index.html b/examples/todomvc-backbone/index.html index b1a6f2ad1e4e6..9ed9bd7418ed4 100644 --- a/examples/todomvc-backbone/index.html +++ b/examples/todomvc-backbone/index.html @@ -10,7 +10,7 @@ -
+
diff --git a/examples/todomvc-backbone/js/app.js b/examples/todomvc-backbone/js/app.js index 5eb8ab5843f41..66ee9c4b0b9ce 100644 --- a/examples/todomvc-backbone/js/app.js +++ b/examples/todomvc-backbone/js/app.js @@ -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 ( -
  • +
  • - +
    - +
  • ); @@ -113,37 +127,46 @@ var TodoFooter = React.createClass({ if (this.props.completedCount > 0) { clearButton = ( - + ); } return ( -