Commit 0d07bb3c authored by Sindre Sorhus's avatar Sindre Sorhus

YUI app update. Fixes #71

Updated it to the latest template and changed and fixed a lot of other stuff.
parent 1c04c6b5
#main,
#footer {
display: none;
}
\ No newline at end of file
#todo-app {
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-moz-border-radius: 0 0 5px 5px;
-o-border-radius: 0 0 5px 5px;
-webkit-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
#todo-list,
#todo-stats {
margin: 1em auto;
text-align: left;
width: 450px;
}
#todo-list {
list-style: none;
padding: 0;
}
#todo-stats,
.todo-clear { color: #777; }
.todo-clear { float: right; }
.todo-done .todo-content {
color: #666;
text-decoration: line-through;
}
.todo-edit,
.editing .todo-view { display: none; }
.editing .todo-edit { display: block; }
.todo-input {
width: 466px;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
.todo-item {
border-bottom: 1px dotted #cfcfcf;
font-size: 20px;
padding: 6px;
position: relative;
}
.todo-label {
color: #444;
font-size: 20px;
font-weight: bold;
text-align: center;
}
.todo-remaining {
color: #333;
font-weight: bold;
}
.todo-remove {
position: absolute;
right: 0;
top: 12px;
}
.todo-remove-icon {
background: url(../img/destroy.png) no-repeat;
display: block;
height: 20px;
opacity: 0.6;
visibility: hidden;
width: 20px;
}
.todo-remove:hover .todo-remove-icon { opacity: 1.0; }
.todo-hover .todo-remove-icon,
.todo-remove:focus .todo-remove-icon { visibility: visible; }
.editing .todo-remove-icon { visibility: hidden; }
.todo-clear {
display: block;
float: right;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
color: #555555;
font-size: 11px;
margin-top: 8px;
padding: 0 10px 1px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
-o-border-radius: 12px;
border-radius: 12px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
cursor: pointer;
}
/************************************************/
body { margin: 0; padding: 0; font-family: Helvetica; color: #444; }
#surface { position: relative; margin: 0 auto; width: 600px; }
h1 { position: relative; top: 18px; z-index: 2; margin: 0; padding: 0; width: 100%; height: 1.2em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 25px; font-weight: normal; }
#lists { position: absolute; top: 50px; left: 400px; z-index: 2; }
dl { margin: 0; padding: 0; color: #555; }
dt { margin: 0 0 5px 0; padding: 0; font-size: 20px; font-weight: normal; }
dd { margin: 0 0 5px 15px; padding: 0; font-size: 17px; cursor: pointer; }
#page { float: left; position: relative; z-index: 1; width: 400px; height: 600px; }
#page { }
h2 { position: absolute; top: 56px; left: 77px; margin: 0; font-size: 30px; width: 275px; height: 1.2em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: normal; }
/*ul { position: absolute; top: 116px; left: 80px; margin: 0; padding: 0; width: 275px; list-style: none; }*/
li { position: relative; margin: 0 0 0 25px; padding: 0; font-size: 20px; line-height: 27px; }
p { position: absolute; top: 116px; left: 80px; margin: 0; padding: 0; width: 275px; font-size: 20px; line-height: 27px; }
[contenteditable]:hover { outline: 1px dotted #999; }
.new { opacity: .25; cursor: pointer; }
.new:hover { opacity: .75; }
.trashcan { position: absolute; top: 63px; left: 57px; width: 25px; height: 25px; cursor: pointer; background: url(assets/destroy.png) no-repeat; }
.trashcan:hover { opacity: .75; }
li .trashcan { float: right;
left: 95%;
top: 15px; }
#page:hover .trashcan { display: inline-block; }
/*todos*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-weight: inherit;
font-style: inherit;
font-size: 100%;
font-family: inherit;
vertical-align: baseline;
}
body {
line-height: 1;
color: black;
background: white;
}
ol, ul {
list-style: none;
}
a img {
border: none;
}
html {
background: #eeeeee;
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.4em;
background: #eeeeee;
color: #333333;
}
#todo-app {
width: 480px;
margin: 0 auto 40px;
background: white;
padding: 20px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
}
#todo-app h1 {
font-size: 36px;
font-weight: bold;
text-align: center;
padding: 20px 0 30px 0;
line-height: 1;
}
#create-todo {
position: relative;
}
#create-todo input {
width: 466px;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
#create-todo input::-webkit-input-placeholder {
font-style: italic;
}
#create-todo span {
position: absolute;
z-index: 999;
width: 170px;
left: 50%;
margin-left: -85px;
}
#todo-list {
margin-top: 10px;
}
#todo-list li {
padding: 12px 20px 11px 0;
position: relative;
font-size: 24px;
line-height: 1.1em;
border-bottom: 1px solid #cccccc;
}
#todo-list li:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
#todo-list li.editing {
padding: 0;
border-bottom: 0;
}
#todo-list .editing .display,
#todo-list .edit {
display: none;
}
#todo-list .editing .edit {
display: block;
}
#todo-list .editing input {
width: 444px;
font-size: 24px;
font-family: inherit;
margin: 0;
line-height: 1.6em;
border: 0;
outline: none;
padding: 10px 7px 0px 27px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
#todo-list .check {
position: relative;
top: 9px;
margin: 0 10px 0 7px;
float: left;
}
#todo-list .done .todo-content {
text-decoration: line-through;
color: #777777;
}
#todo-list .todo-destroy {
position: absolute;
right: 5px;
top: 14px;
display: none;
cursor: pointer;
width: 20px;
height: 20px;
background: url(destroy.png) no-repeat 0 0;
}
#todo-list li:hover .todo-destroy {
display: block;
}
#todo-list .todo-destroy:hover {
background-position: 0 -20px;
}
#todo-stats {
*zoom: 1;
margin-top: 10px;
color: #777777;
}
#todo-stats:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
#todo-stats .todo-count {
float: left;
}
#todo-stats .todo-count .number {
font-weight: bold;
color: #333333;
}
#todo-stats .todo-clear {
float: right;
}
#todo-stats a.todo-clear {
color: #777777;
font-size: 12px;
}
#todo-stats a.todo-clear {
color: #777777;
}
#todo-stats a.todo-clear:hover, #todo-stats a.todo-clear:focus {
color: #336699;
}
/* line 125 */
#todo-stats a.todo-clear {
display: block;
line-height: 20px;
text-decoration: none;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
-o-border-radius: 12px;
-ms-border-radius: 12px;
-khtml-border-radius: 12px;
border-radius: 12px;
background: rgba(0, 0, 0, 0.1);
color: #555555;
font-size: 11px;
margin-top: 8px;
padding: 0 10px 1px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
}
/* line 136 */
#todo-stats a.todo-clear:hover, #todo-stats a.todo-clear:focus {
background: rgba(0, 0, 0, 0.15);
-moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
}
/* line 139 */
#todoapp #todo-stats a.todo-clear:active {
position: relative;
top: 1px;
}
#instructions {
width: 520px;
margin: 10px auto;
color: #777777;
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
text-align: center;
}
#instructions a {
color: #336699;
}
#credits {
width: 520px;
margin: 30px auto;
color: #999;
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
text-align: center;
}
#credits a {
color: #888;
}
/*
* François 'cahnory' Germain
*/
.ui-tooltip, .ui-tooltip-top, .ui-tooltip-right, .ui-tooltip-bottom, .ui-tooltip-left {
color:#ffffff;
cursor:normal;
display:-moz-inline-stack;
display:inline-block;
font-size:12px;
font-family:arial;
padding:.5em 1em;
position:relative;
text-align:center;
text-shadow:0 -1px 1px #111111;
-webkit-border-top-left-radius:4px ;
-webkit-border-top-right-radius:4px ;
-webkit-border-bottom-right-radius:4px ;
-webkit-border-bottom-left-radius:4px ;
-khtml-border-top-left-radius:4px ;
-khtml-border-top-right-radius:4px ;
-khtml-border-bottom-right-radius:4px ;
-khtml-border-bottom-left-radius:4px ;
-moz-border-radius-topleft:4px ;
-moz-border-radius-topright:4px ;
-moz-border-radius-bottomright:4px ;
-moz-border-radius-bottomleft:4px ;
border-top-left-radius:4px ;
border-top-right-radius:4px ;
border-bottom-right-radius:4px ;
border-bottom-left-radius:4px ;
-o-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
-moz-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
-khtml-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
-webkit-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
background-color:#3b3b3b;
background-image:-moz-linear-gradient(top,#555555,#222222);
background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#555555),color-stop(1,#222222));
filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222);
-ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222);
}
.ui-tooltip:after, .ui-tooltip-top:after, .ui-tooltip-right:after, .ui-tooltip-bottom:after, .ui-tooltip-left:after {
content:"\25B8";
display:block;
font-size:2em;
height:0;
line-height:0;
position:absolute;
}
.ui-tooltip:after, .ui-tooltip-bottom:after {
color:#2a2a2a;
bottom:0;
left:1px;
text-align:center;
text-shadow:1px 0 2px #000000;
-o-transform:rotate(90deg);
-moz-transform:rotate(90deg);
-khtml-transform:rotate(90deg);
-webkit-transform:rotate(90deg);
width:100%;
}
.ui-tooltip-top:after {
bottom:auto;
color:#4f4f4f;
left:-2px;
top:0;
text-align:center;
text-shadow:none;
-o-transform:rotate(-90deg);
-moz-transform:rotate(-90deg);
-khtml-transform:rotate(-90deg);
-webkit-transform:rotate(-90deg);
width:100%;
}
.ui-tooltip-right:after {
color:#222222;
right:-0.375em;
top:50%;
margin-top:-.05em;
text-shadow:0 1px 2px #000000;
-o-transform:rotate(0);
-moz-transform:rotate(0);
-khtml-transform:rotate(0);
-webkit-transform:rotate(0);
}
.ui-tooltip-left:after {
color:#222222;
left:-0.375em;
top:50%;
margin-top:.1em;
text-shadow:0 -1px 2px #000000;
-o-transform:rotate(180deg);
-moz-transform:rotate(180deg);
-khtml-transform:rotate(180deg);
-webkit-transform:rotate(180deg);
}
/* new additions - cleanup required*/
/* line 109 */
#todo-app #todo-stats {
*zoom: 1;
margin-top: 10px;
color: #555555;
-moz-border-radius-bottomleft: 5px;
-webkit-border-bottom-left-radius: 5px;
-o-border-bottom-left-radius: 5px;
-ms-border-bottom-left-radius: 5px;
-khtml-border-bottom-left-radius: 5px;
border-bottom-left-radius: 5px;
-moz-border-radius-bottomright: 5px;
-webkit-border-bottom-right-radius: 5px;
-o-border-bottom-right-radius: 5px;
-ms-border-bottom-right-radius: 5px;
-khtml-border-bottom-right-radius: 5px;
border-bottom-right-radius: 5px;
background: #f4fce8;
border-top: 1px solid #ededed;
padding: 0 20px;
line-height: 36px;
}
/* line 22, /opt/ree/lib/ruby/gems/1.8/gems/compass-0.10.5/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */
#todo-app #todo-stats:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
/* line 118 */
#todo-app #todo-stats .todo-done {
float: left;
}
/* line 120 */
#todo-app #todo-stats .todo-done .number {
font-weight: bold;
color: #555555;
}
/* line 123 */
#todos { display:block}
...@@ -4,53 +4,41 @@ ...@@ -4,53 +4,41 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>YUI • TodoMVC</title> <title>YUI • TodoMVC</title>
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" href="css/app.css">
<!--[if IE]> <!--[if IE]>
<script src="../../assets/ie.js"></script> <script src="../../assets/ie.js"></script>
<![endif]--> <![endif]-->
</head> </head>
<body> <body>
<div id="todo-app"> <section id="todoapp">
<div class="title"> <header id="header">
<h1>Todos</h1> <h1>todos</h1>
</div> </header>
<label class="todo-label" for="new-todo"></label> <input id="new-todo" placeholder="What needs to be done?" autofocus>
<input type="text" id="new-todo" class="todo-input" placeholder="What needs to be done?"> <section id="main">
<div id="todos"> <input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul> <ul id="todo-list"></ul>
</section>
<footer id="footer"></footer>
</section>
<footer id="info">
<p>Based on code by the YUILibrary team</p>
<p>Created by <a href="https://github.com/addyosmani">Addy Osmani</a></p>
<p>Rewrite by <a href="https://github.com/sindresorhus">Sindre Sorhus</a></p>
</footer>
<script type="text/x-template" id="todo-template">
<div class="view">
<input class="toggle" type="checkbox" {completed}>
<label>{title}</label>
<button class="destroy"></button>
</div> </div>
<div id="todo-stats"></div> <input class="edit" value="{title}">
</div>
<div id="credits">
This version by
<br />
<a href="http://twitter.com/addyosmani">Addy Osmani</a>
<br />
based on code by the YUILibrary team.
</div>
<script type="text/x-template" id="todo-item-template">
<div class="todo-view">
<input type="checkbox" class="todo-checkbox" {checked}>
<span class="todo-content" tabindex="0">{text}</span>
</div>
<div class="todo-edit">
<input type="text" class="todo-input" value="{text}">
</div>
<a href="#" class="todo-remove" title="Remove this task">
<span class="todo-remove-icon"></span>
</a>
</script> </script>
<script type="text/x-template" id="todo-stats-template"> <script type="text/x-template" id="footer-template">
<span class="todo-count"> <span id="todo-count"><strong>{numRemaining}</strong> {remainingLabel} left</span>
<span class="todo-remaining">{numRemaining}</span> <button id="clear-completed">Clear completed ({numCompleted})</button>
<span class="todo-remaining-label">{remainingLabel}</span> left.
</span>
<a href="#" class="todo-clear">
Clear {numDone} completed <span class="todo-done-label">{doneLabel}</span>
</a>
</script> </script>
<script src="../../assets/base.js"></script> <script src="../../assets/base.js"></script>
<script src="js/yui-3.4.0.min.js"></script> <script src="js/yui-3.4.0.min.js"></script>
......
/*global YUI */
YUI().use( 'event-focus', 'json', 'model', 'model-list', 'view', function( Y ) {
YUI().use('event-focus', 'json', 'model', 'model-list', 'view', function (Y) { var TodoAppView, TodoList, TodoModel, TodoView, localStorageName = 'todos-yui',
ENTER_KEY = 13;
var TodoAppView, TodoList, TodoModel, TodoView, localStorageName = 'todos-yuilibrary';
// -- Model -------------------------------------------------------------------- // -- Model --------------------------------------------------------------------
...@@ -9,23 +10,27 @@ var TodoAppView, TodoList, TodoModel, TodoView, localStorageName = 'todos-yuilib ...@@ -9,23 +10,27 @@ var TodoAppView, TodoList, TodoModel, TodoView, localStorageName = 'todos-yuilib
// sync provider (the source for that is further below) and to provide // sync provider (the source for that is further below) and to provide
// attributes and methods useful for todo items. // attributes and methods useful for todo items.
TodoModel = Y.TodoModel = Y.Base.create('todoModel', Y.Model, [], { TodoModel = Y.TodoModel = Y.Base.create( 'todoModel', Y.Model, [], {
// This tells the Model to use a localStorage sync provider (which we'll // This tells the Model to use a localStorage sync provider (which we'll
// create below) to save and load information about a todo item. // create below) to save and load information about a todo item.
sync: LocalStorageSync(localStorageName), sync: localStorageSync(localStorageName),
// This method will toggle the `done` attribute from `true` to `false`, or // This method will toggle the `completed` attribute from `true` to `false`, or
// vice versa. // vice versa.
toggleDone: function () { toggle: function() {
this.set('done', !this.get('done')).save(); this.set( 'completed', !this.get('completed') ).save();
} }
}, { }, {
ATTRS: { ATTRS: {
// Indicates whether or not this todo item has been completed. // Indicates whether or not this todo item has been completed.
done: {value: false}, completed: {
value: false
},
// Contains the text of the todo item. // Contains the text of the todo item.
text: {value: ''} title: {
value: ''
}
} }
}); });
...@@ -35,29 +40,43 @@ TodoModel = Y.TodoModel = Y.Base.create('todoModel', Y.Model, [], { ...@@ -35,29 +40,43 @@ TodoModel = Y.TodoModel = Y.Base.create('todoModel', Y.Model, [], {
// TodoModel instances, and to provide some convenience methods for getting // TodoModel instances, and to provide some convenience methods for getting
// information about the todo items in the list. // information about the todo items in the list.
TodoList = Y.TodoList = Y.Base.create('todoList', Y.ModelList, [], { TodoList = Y.TodoList = Y.Base.create( 'todoList', Y.ModelList, [], {
// This tells the list that it will hold instances of the TodoModel class. // This tells the list that it will hold instances of the TodoModel class.
model: TodoModel, model: TodoModel,
// This tells the list to use a localStorage sync provider (which we'll // This tells the list to use a localStorage sync provider (which we'll
// create below) to load the list of todo items. // create below) to load the list of todo items.
sync : LocalStorageSync(localStorageName), sync : localStorageSync( localStorageName ),
// Returns an array of all models in this list with the `done` attribute // Returns an array of all models in this list with the `completed` attribute
// set to `true`. // set to `true`.
done: function () { completed: function() {
return Y.Array.filter(this.toArray(), function (model) { return Y.Array.filter( this.toArray(), function( model) {
return model.get('done'); return model.get('completed');
}); });
}, },
// Returns an array of all models in this list with the `done` attribute // Returns an array of all models in this list with the `completed` attribute
// set to `false`. // set to `false`.
remaining: function () { remaining: function() {
return Y.Array.filter(this.toArray(), function (model) { return Y.Array.filter( this.toArray(), function( model ) {
return !model.get('done'); return !model.get('completed');
}); });
} },
toggleAll: function( toggle ) {
Y.Array.each( this.toArray(), function( model ) {
model.set( 'completed', toggle );
});
},
clearCompleted: function() {
Y.Array.each( this.completed(), function( model ) {
model.destroy({
'delete': true
});
});
},
}); });
// -- Todo App View ------------------------------------------------------------ // -- Todo App View ------------------------------------------------------------
...@@ -72,9 +91,9 @@ TodoList = Y.TodoList = Y.Base.create('todoList', Y.ModelList, [], { ...@@ -72,9 +91,9 @@ TodoList = Y.TodoList = Y.Base.create('todoList', Y.ModelList, [], {
TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], { TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], {
// The container node is the wrapper for this view. All the view's events // The container node is the wrapper for this view. All the view's events
// will be delegated from the container. In this case, the #todo-app // will be delegated from the container. In this case, the #todoapp
// node already exists on the page, so we don't need to create it. // node already exists on the page, so we don't need to create it.
container: Y.one('#todo-app'), container: Y.one('#todoapp'),
// This is a custom property that we'll use to hold a reference to the // This is a custom property that we'll use to hold a reference to the
// "new todo" input field. // "new todo" input field.
...@@ -82,43 +101,49 @@ TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], { ...@@ -82,43 +101,49 @@ TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], {
// The `template` property is a convenience property for holding a template // The `template` property is a convenience property for holding a template
// for this view. In this case, we'll use it to store the contents of the // for this view. In this case, we'll use it to store the contents of the
// #todo-stats-template element, which will serve as the template for the // #footer-template element, which will serve as the template for the
// statistics displayed at the bottom of the list. // statistics displayed at the bottom of the list.
template: Y.one('#todo-stats-template').getContent(), template: Y.one('#footer-template').getContent(),
// This is where we attach DOM events for the view. The `events` object is a // This is where we attach DOM events for the view. The `events` object is a
// mapping of selectors to an object containing one or more events to attach // mapping of selectors to an object containing one or more events to attach
// to the node(s) matching each selector. // to the node(s) matching each selector.
events: { events: {
// Handle <enter> keypresses on the "new todo" input field. // Handle <enter> keypresses on the "new todo" input field.
'#new-todo': {keypress: 'createTodo'}, '#new-todo': {
keypress: 'createTodo'
},
'#toggle-all': {
change: 'toggleAll'
},
// Clear all completed items from the list when the "Clear" link is // Clear all completed items from the list when the "Clear" link is
// clicked. // clicked.
'.todo-clear': {click: 'clearDone'}, '#clear-completed': {
click: 'clearCompleted'
// Add and remove hover states on todo items.
'.todo-item': {
mouseover: 'hoverOn',
mouseout : 'hoverOff'
} }
}, },
// The initializer runs when a TodoAppView instance is created, and gives // The initializer runs when a TodoAppView instance is created, and gives
// us an opportunity to set up the view. // us an opportunity to set up the view.
initializer: function () { initializer: function() {
// Create a new TodoList instance to hold the todo items. // Create a new TodoList instance to hold the todo items.
var list = this.todoList = new TodoList(); var list = this.todoList = new TodoList();
// Update the display when a new item is added to the list, or when the // Update the display when a new item is added to the list, or when the
// entire list is reset. // entire list is reset.
list.after('add', this.add, this); list.after( 'add', this.add, this );
list.after('reset', this.reset, this); list.after( 'reset', this.reset, this );
// Re-render the stats in the footer whenever an item is added, removed // Re-render the stats in the footer whenever an item is added, removed
// or changed, or when the entire list is reset. // or changed, or when the entire list is reset.
list.after(['add', 'reset', 'remove', 'todoModel:doneChange'], list.after([
this.render, this); 'add',
'reset',
'remove',
'todoModel:completedChange'
], this.render, this );
// Load saved items from localStorage, if available. // Load saved items from localStorage, if available.
list.load(); list.load();
...@@ -127,33 +152,41 @@ TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], { ...@@ -127,33 +152,41 @@ TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], {
// The render function is called whenever a todo item is added, removed, or // The render function is called whenever a todo item is added, removed, or
// changed, thanks to the list event handler we attached in the initializer // changed, thanks to the list event handler we attached in the initializer
// above. // above.
render: function () { render: function() {
var todoList = this.todoList, var numRemaining, numCompleted,
stats = this.container.one('#todo-stats'), todoList = this.todoList,
numRemaining, numDone; main = this.container.one('#main'),
footer = this.container.one('#footer');
// Check the toggleAll checkbox when all todos are checked
this.container.one('#toggle-all').set( 'checked', !todoList.remaining().length );
// If there are no todo items, then clear the stats. // If there are no todo items, then clear the stats.
if (todoList.isEmpty()) { // Ugly, but for some reason `main.hide()` doesn't work
stats.empty(); if ( todoList.isEmpty() ) {
main._node.style.display = 'none';
footer._node.style.display = 'none';
return this; return this;
} else {
main._node.style.display = 'block';
footer._node.style.display = 'block';
} }
// Figure out how many todo items are completed and how many remain. // Figure out how many todo items are completed and how many remain.
numDone = todoList.done().length; numCompleted = todoList.completed().length;
numRemaining = todoList.remaining().length; numRemaining = todoList.remaining().length;
// Update the statistics. // Update the statistics.
stats.setContent(Y.Lang.sub(this.template, { footer.setContent(Y.Lang.sub( this.template, {
numDone : numDone, numCompleted: numCompleted,
numRemaining : numRemaining, numRemaining: numRemaining,
doneLabel : numDone === 1 ? 'task' : 'tasks', remainingLabel: numRemaining === 1 ? 'item' : 'items'
remainingLabel: numRemaining === 1 ? 'task' : 'tasks'
})); }));
// If there are no completed todo items, don't show the "Clear // If there are no completed todo items, don't show the "Clear
// completed items" link. // completed items" link.
if (!numDone) { if ( !numCompleted ) {
stats.one('.todo-clear').remove(); footer.one('#clear-completed').remove();
} }
return this; return this;
...@@ -163,72 +196,58 @@ TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], { ...@@ -163,72 +196,58 @@ TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], {
// Creates a new TodoView instance and renders it into the list whenever a // Creates a new TodoView instance and renders it into the list whenever a
// todo item is added to the list. // todo item is added to the list.
add: function (e) { add: function ( e ) {
var view = new TodoView({model: e.model}); var view = new TodoView({
this.container.one('#todo-list').append(view.render().container); model: e.model
});
this.container.one('#todo-list').append( view.render().container );
}, },
// Removes all finished todo items from the list. // Creates and renders views for every todo item in the list when the entire
clearDone: function (e) { // list is reset.
var done = this.todoList.done(); reset: function( e ) {
var fragment = Y.one( Y.config.doc.createDocumentFragment() );
e.preventDefault();
Y.Array.each( e.models, function ( model ) {
// Remove all finished items from the list, but do it silently so as not var view = new TodoView({
// to re-render the app view after each item is removed. model: model
this.todoList.remove(done, {silent: true}); });
fragment.append( view.render().container );
// Destroy each removed TodoModel instance.
Y.Array.each(done, function (todo) {
// Passing {'delete': true} to the todo model's `destroy()` method
// tells it to delete itself from localStorage as well.
todo.destroy({'delete': true});
}); });
// Finally, re-render the app view. this.container.one('#todo-list').setContent( fragment );
this.render();
}, },
// Creates a new todo item when the enter key is pressed in the new todo // Creates a new todo item when the enter key is pressed in the new todo
// input field. // input field.
createTodo: function (e) { createTodo: function( e ) {
var value; var value;
if (e.keyCode === 13) { // enter key if ( e.keyCode === ENTER_KEY ) {
value = Y.Lang.trim(this.inputNode.get('value')); value = Y.Lang.trim( this.inputNode.get('value') );
if (!value) { return; } if ( !value ) {
return;
}
// This tells the list to create a new TodoModel instance with the // This tells the list to create a new TodoModel instance with the
// specified text and automatically save it to localStorage in a // specified text and automatically save it to localStorage in a
// single step. // single step.
this.todoList.create({text: value}); this.todoList.create({
title: value
});
this.inputNode.set('value', ''); this.inputNode.set( 'value', '' );
} }
}, },
// Turns off the hover state on a todo item. toggleAll: function( e ) {
hoverOff: function (e) { this.todoList.toggleAll( e.target._node.checked );
e.currentTarget.removeClass('todo-hover');
},
// Turns on the hover state on a todo item.
hoverOn: function (e) {
e.currentTarget.addClass('todo-hover');
}, },
// Creates and renders views for every todo item in the list when the entire // Removes all finished todo items from the list.
// list is reset. clearCompleted: function( e ) {
reset: function (e) { this.todoList.clearCompleted();
var fragment = Y.one(Y.config.doc.createDocumentFragment());
Y.Array.each(e.models, function (model) {
var view = new TodoView({model: model});
fragment.append(view.render().container);
});
this.container.one('#todo-list').setContent(fragment);
} }
}); });
...@@ -238,63 +257,66 @@ TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], { ...@@ -238,63 +257,66 @@ TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], {
// of a single todo item in the list. It also handles DOM events on the item to // of a single todo item in the list. It also handles DOM events on the item to
// allow it to be edited and removed from the list. // allow it to be edited and removed from the list.
TodoView = Y.TodoView = Y.Base.create('todoView', Y.View, [], { TodoView = Y.TodoView = Y.Base.create( 'todoView', Y.View, [], {
// Specifying an HTML string as this view's container element causes that // Specifying an HTML string as this view's container element causes that
// HTML to be automatically converted into an unattached Y.Node instance. // HTML to be automatically converted into an unattached Y.Node instance.
// The TodoAppView (above) will take care of appending it to the list. // The TodoAppView (above) will take care of appending it to the list.
container: '<li class="todo-item"/>', container: '<li>',
// The template property holds the contents of the #todo-item-template // The template property holds the contents of the #todo-template
// element, which will be used as the HTML template for each todo item. // element, which will be used as the HTML template for each todo item.
template: Y.one('#todo-item-template').getContent(), template: Y.one('#todo-template').getContent(),
// Delegated DOM events to handle this view's interactions. // Delegated DOM events to handle this view's interactions.
events: { events: {
// Toggle the "done" state of this todo item when the checkbox is // Toggle the "completed" state of this todo item when the checkbox is
// clicked. // clicked.
'.todo-checkbox': {click: 'toggleDone'}, '.toggle': {
click: 'toggle'
},
// When the text of this todo item is clicked or focused, switch to edit // When the text of this todo item is clicked or focused, switch to edit
// mode to allow editing. // mode to allow editing.
'.todo-content': { '.view': {
click: 'edit', dblclick: 'edit'
focus: 'edit'
}, },
// On the edit field, when enter is pressed or the field loses focus, // On the edit field, when enter is pressed or the field loses focus,
// save the current value and switch out of edit mode. // save the current value and switch out of edit mode.
'.todo-input' : { '.edit': {
blur : 'save', blur: 'save',
keypress: 'enter' keypress: 'enter'
}, },
// When the remove icon is clicked, delete this todo item. // When the remove icon is clicked, delete this todo item.
'.todo-remove': {click: 'remove'} '.destroy': {
click: 'remove'
}
}, },
initializer: function () { initializer: function() {
// The model property is set to a TodoModel instance by TodoAppView when // The model property is set to a TodoModel instance by TodoAppView when
// it instantiates this TodoView. // it instantiates this TodoView.
var model = this.model; var model = this.model;
// Re-render this view when the model changes, and destroy this view // Re-render this view when the model changes, and destroy this view
// when the model is destroyed. // when the model is destroyed.
model.after('change', this.render, this); model.after( 'change', this.render, this );
model.after('destroy', this.destroy, this); model.after( 'destroy', this.destroy, this );
}, },
render: function () { render: function () {
var container = this.container, var container = this.container,
model = this.model, model = this.model,
done = model.get('done'); completed = model.get('completed');
container.setContent(Y.Lang.sub(this.template, { container.setContent( Y.Lang.sub( this.template, {
checked: done ? 'checked' : '', completed: completed ? 'checked' : '',
text : model.getAsHTML('text') title: model.get('title')
})); }));
container[done ? 'addClass' : 'removeClass']('todo-done'); container[ completed ? 'addClass' : 'removeClass' ]('completed');
this.inputNode = container.one('.todo-input'); this.inputNode = container.one('.edit');
return this; return this;
}, },
...@@ -302,37 +324,46 @@ TodoView = Y.TodoView = Y.Base.create('todoView', Y.View, [], { ...@@ -302,37 +324,46 @@ TodoView = Y.TodoView = Y.Base.create('todoView', Y.View, [], {
// -- Event Handlers ------------------------------------------------------- // -- Event Handlers -------------------------------------------------------
// Toggles this item into edit mode. // Toggles this item into edit mode.
edit: function () { edit: function() {
this.container.addClass('editing'); this.container.addClass('editing');
this.inputNode.focus(); this.inputNode.select();
}, },
// When the enter key is pressed, focus the new todo input field. This // When the enter key is pressed, focus the new todo input field. This
// causes a blur event on the current edit field, which calls the save() // causes a blur event on the current edit field, which calls the save()
// handler below. // handler below.
enter: function (e) { enter: function( e ) {
if (e.keyCode === 13) { // enter key if ( e.keyCode === ENTER_KEY ) {
Y.one('#new-todo').focus(); Y.one('#new-todo').focus();
} }
}, },
// Removes this item from the list. // Removes this item from the list.
remove: function (e) { remove: function( e ) {
e.preventDefault(); this.constructor.superclass.remove.call( this );
this.model.destroy({
this.constructor.superclass.remove.call(this); 'delete': true
this.model.destroy({'delete': true}); });
}, },
// Toggles this item out of edit mode and saves it. // Toggles this item out of edit mode and saves it.
save: function () { save: function() {
var val = Y.Lang.trim( this.inputNode.get('value') );
this.container.removeClass('editing'); this.container.removeClass('editing');
this.model.set('text', this.inputNode.get('value')).save();
if ( val ) {
this.model.set( 'title', val ).save();
} else {
this.model.destroy({
'delete': true
});
}
}, },
// Toggles the `done` state on this item's model. // Toggles the `completed` state on this item's model.
toggleDone: function () { toggle: function() {
this.model.toggleDone(); this.model.toggle();
} }
}); });
...@@ -342,27 +373,27 @@ TodoView = Y.TodoView = Y.Base.create('todoView', Y.View, [], { ...@@ -342,27 +373,27 @@ TodoView = Y.TodoView = Y.Base.create('todoView', Y.View, [], {
// be used as a sync layer for either a Model or a ModelList instance. The // be used as a sync layer for either a Model or a ModelList instance. The
// TodoModel and TodoList instances above use it to save and load items. // TodoModel and TodoList instances above use it to save and load items.
function LocalStorageSync(key) { function localStorageSync( key ) {
var localStorage; var localStorage;
if (!key) { if ( !key ) {
Y.error('No storage key specified.'); Y.error('No storage key specified.');
} }
if (Y.config.win.localStorage) { if ( Y.config.win.localStorage ) {
localStorage = Y.config.win.localStorage; localStorage = Y.config.win.localStorage;
} }
// Try to retrieve existing data from localStorage, if there is any. // Try to retrieve existing data from localStorage, if there is any.
// Otherwise, initialize `data` to an empty object. // Otherwise, initialize `data` to an empty object.
var data = Y.JSON.parse((localStorage && localStorage.getItem(key)) || '{}'); var data = Y.JSON.parse( ( localStorage && localStorage.getItem( key ) ) || '{}' );
// Delete a model with the specified id. // Delete a model with the specified id.
function destroy(id) { function destroy( id ) {
var modelHash; var modelHash;
if ((modelHash = data[id])) { if ( ( modelHash = data[ id ] ) ) {
delete data[id]; delete data[ id ];
save(); save();
} }
...@@ -374,8 +405,8 @@ function LocalStorageSync(key) { ...@@ -374,8 +405,8 @@ function LocalStorageSync(key) {
var id = '', var id = '',
i = 4; i = 4;
while (i--) { while ( i-- ) {
id += (((1 + Math.random()) * 0x10000) | 0) id += ( ( ( 1 + Math.random()) * 0x10000) | 0 )
.toString(16).substring(1); .toString(16).substring(1);
} }
...@@ -389,26 +420,26 @@ function LocalStorageSync(key) { ...@@ -389,26 +420,26 @@ function LocalStorageSync(key) {
// If an id is specified, then it loads a single model. If no id is // If an id is specified, then it loads a single model. If no id is
// specified then it loads an array of all models. This allows the same sync // specified then it loads an array of all models. This allows the same sync
// layer to be used for both the TodoModel and TodoList classes. // layer to be used for both the TodoModel and TodoList classes.
function get(id) { function get( id ) {
return id ? data[id] : Y.Object.values(data); return id ? data[ id ] : Y.Object.values( data );
} }
// Saves the entire `data` object to localStorage. // Saves the entire `data` object to localStorage.
function save() { function save() {
localStorage && localStorage.setItem(key, Y.JSON.stringify(data)); localStorage && localStorage.setItem( key, Y.JSON.stringify( data ) );
} }
// Sets the id attribute of the specified model (generating a new id if // Sets the id attribute of the specified model (generating a new id if
// necessary), then saves it to localStorage. // necessary), then saves it to localStorage.
function set(model) { function set( model ) {
var hash = model.toJSON(), var hash = model.toJSON(),
idAttribute = model.idAttribute; idAttribute = model.idAttribute;
if (!Y.Lang.isValue(hash[idAttribute])) { if ( !Y.Lang.isValue(hash[ idAttribute ] ) ) {
hash[idAttribute] = generateId(); hash[ idAttribute ] = generateId();
} }
data[hash[idAttribute]] = hash; data[ hash[ idAttribute ] ] = hash;
save(); save();
return hash; return hash;
...@@ -416,23 +447,23 @@ function LocalStorageSync(key) { ...@@ -416,23 +447,23 @@ function LocalStorageSync(key) {
// Returns a `sync()` function that can be used with either a Model or a // Returns a `sync()` function that can be used with either a Model or a
// ModelList instance. // ModelList instance.
return function (action, options, callback) { return function( action, options, callback ) {
// `this` refers to the Model or ModelList instance to which this sync // `this` refers to the Model or ModelList instance to which this sync
// method is attached. // method is attached.
var isModel = Y.Model && this instanceof Y.Model; var isModel = Y.Model && this instanceof Y.Model;
switch (action) { switch ( action ) {
case 'create': // intentional fallthru case 'create': // intentional fallthru
case 'update': case 'update':
callback(null, set(this)); callback( null, set( this ) );
return; return;
case 'read': case 'read':
callback(null, get(isModel && this.get('id'))); callback( null, get( isModel && this.get('id') ) );
return; return;
case 'delete': case 'delete':
callback(null, destroy(isModel && this.get('id'))); callback( null, destroy( isModel && this.get('id') ) );
return; return;
} }
}; };
......
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