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 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<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]>
<script src="../../assets/ie.js"></script>
<![endif]-->
</head>
<body>
<div id="todo-app">
<div class="title">
<h1>Todos</h1>
</div>
<label class="todo-label" for="new-todo"></label>
<input type="text" id="new-todo" class="todo-input" placeholder="What needs to be done?">
<div id="todos">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
</header>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
<section id="main">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<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 id="todo-stats"></div>
</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>
<input class="edit" value="{title}">
</script>
<script type="text/x-template" id="todo-stats-template">
<span class="todo-count">
<span class="todo-remaining">{numRemaining}</span>
<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 type="text/x-template" id="footer-template">
<span id="todo-count"><strong>{numRemaining}</strong> {remainingLabel} left</span>
<button id="clear-completed">Clear completed ({numCompleted})</button>
</script>
<script src="../../assets/base.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-yuilibrary';
var TodoAppView, TodoList, TodoModel, TodoView, localStorageName = 'todos-yui',
ENTER_KEY = 13;
// -- Model --------------------------------------------------------------------
......@@ -9,23 +10,27 @@ var TodoAppView, TodoList, TodoModel, TodoView, localStorageName = 'todos-yuilib
// sync provider (the source for that is further below) and to provide
// 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
// 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.
toggleDone: function () {
this.set('done', !this.get('done')).save();
toggle: function() {
this.set( 'completed', !this.get('completed') ).save();
}
}, {
ATTRS: {
// Indicates whether or not this todo item has been completed.
done: {value: false},
completed: {
value: false
},
// Contains the text of the todo item.
text: {value: ''}
title: {
value: ''
}
}
});
......@@ -35,29 +40,43 @@ TodoModel = Y.TodoModel = Y.Base.create('todoModel', Y.Model, [], {
// TodoModel instances, and to provide some convenience methods for getting
// 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.
model: TodoModel,
// This tells the list to use a localStorage sync provider (which we'll
// 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`.
done: function () {
return Y.Array.filter(this.toArray(), function (model) {
return model.get('done');
completed: function() {
return Y.Array.filter( this.toArray(), function( model) {
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`.
remaining: function () {
return Y.Array.filter(this.toArray(), function (model) {
return !model.get('done');
remaining: function() {
return Y.Array.filter( this.toArray(), function( model ) {
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 ------------------------------------------------------------
......@@ -72,9 +91,9 @@ TodoList = Y.TodoList = Y.Base.create('todoList', Y.ModelList, [], {
TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], {
// 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.
container: Y.one('#todo-app'),
container: Y.one('#todoapp'),
// This is a custom property that we'll use to hold a reference to the
// "new todo" input field.
......@@ -82,43 +101,49 @@ TodoAppView = Y.TodoAppView = Y.Base.create('todoAppView', Y.View, [], {
// 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
// #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.
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
// mapping of selectors to an object containing one or more events to attach
// to the node(s) matching each selector.
events: {
// 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
// clicked.
'.todo-clear': {click: 'clearDone'},
// Add and remove hover states on todo items.
'.todo-item': {
mouseover: 'hoverOn',
mouseout : 'hoverOff'
'#clear-completed': {
click: 'clearCompleted'
}
},
// The initializer runs when a TodoAppView instance is created, and gives
// us an opportunity to set up the view.
initializer: function () {
initializer: function() {
// Create a new TodoList instance to hold the todo items.
var list = this.todoList = new TodoList();
// Update the display when a new item is added to the list, or when the
// entire list is reset.
list.after('add', this.add, this);
list.after('reset', this.reset, this);
list.after( 'add', this.add, this );
list.after( 'reset', this.reset, this );
// Re-render the stats in the footer whenever an item is added, removed
// or changed, or when the entire list is reset.
list.after(['add', 'reset', 'remove', 'todoModel:doneChange'],
this.render, this);
list.after([
'add',
'reset',
'remove',
'todoModel:completedChange'
], this.render, this );
// Load saved items from localStorage, if available.
list.load();
......@@ -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
// changed, thanks to the list event handler we attached in the initializer
// above.
render: function () {
var todoList = this.todoList,
stats = this.container.one('#todo-stats'),
numRemaining, numDone;
render: function() {
var numRemaining, numCompleted,
todoList = this.todoList,
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 (todoList.isEmpty()) {
stats.empty();
// Ugly, but for some reason `main.hide()` doesn't work
if ( todoList.isEmpty() ) {
main._node.style.display = 'none';
footer._node.style.display = 'none';
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.
numDone = todoList.done().length;
numCompleted = todoList.completed().length;
numRemaining = todoList.remaining().length;
// Update the statistics.
stats.setContent(Y.Lang.sub(this.template, {
numDone : numDone,
numRemaining : numRemaining,
doneLabel : numDone === 1 ? 'task' : 'tasks',
remainingLabel: numRemaining === 1 ? 'task' : 'tasks'
footer.setContent(Y.Lang.sub( this.template, {
numCompleted: numCompleted,
numRemaining: numRemaining,
remainingLabel: numRemaining === 1 ? 'item' : 'items'
}));
// If there are no completed todo items, don't show the "Clear
// completed items" link.
if (!numDone) {
stats.one('.todo-clear').remove();
if ( !numCompleted ) {
footer.one('#clear-completed').remove();
}
return this;
......@@ -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
// todo item is added to the list.
add: function (e) {
var view = new TodoView({model: e.model});
this.container.one('#todo-list').append(view.render().container);
add: function ( e ) {
var view = new TodoView({
model: e.model
});
this.container.one('#todo-list').append( view.render().container );
},
// Removes all finished todo items from the list.
clearDone: function (e) {
var done = this.todoList.done();
e.preventDefault();
// Remove all finished items from the list, but do it silently so as not
// to re-render the app view after each item is removed.
this.todoList.remove(done, {silent: true});
// 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});
// Creates and renders views for every todo item in the list when the entire
// list is reset.
reset: function( e ) {
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 );
});
// Finally, re-render the app view.
this.render();
this.container.one('#todo-list').setContent( fragment );
},
// Creates a new todo item when the enter key is pressed in the new todo
// input field.
createTodo: function (e) {
createTodo: function( e ) {
var value;
if (e.keyCode === 13) { // enter key
value = Y.Lang.trim(this.inputNode.get('value'));
if ( e.keyCode === ENTER_KEY ) {
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
// specified text and automatically save it to localStorage in a
// 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.
hoverOff: function (e) {
e.currentTarget.removeClass('todo-hover');
},
// Turns on the hover state on a todo item.
hoverOn: function (e) {
e.currentTarget.addClass('todo-hover');
toggleAll: function( e ) {
this.todoList.toggleAll( e.target._node.checked );
},
// Creates and renders views for every todo item in the list when the entire
// list is reset.
reset: function (e) {
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);
// Removes all finished todo items from the list.
clearCompleted: function( e ) {
this.todoList.clearCompleted();
}
});
......@@ -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
// 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
// HTML to be automatically converted into an unattached Y.Node instance.
// 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.
template: Y.one('#todo-item-template').getContent(),
template: Y.one('#todo-template').getContent(),
// Delegated DOM events to handle this view's interactions.
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.
'.todo-checkbox': {click: 'toggleDone'},
'.toggle': {
click: 'toggle'
},
// When the text of this todo item is clicked or focused, switch to edit
// mode to allow editing.
'.todo-content': {
click: 'edit',
focus: 'edit'
'.view': {
dblclick: 'edit'
},
// On the edit field, when enter is pressed or the field loses focus,
// save the current value and switch out of edit mode.
'.todo-input' : {
blur : 'save',
'.edit': {
blur: 'save',
keypress: 'enter'
},
// 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
// it instantiates this TodoView.
var model = this.model;
// Re-render this view when the model changes, and destroy this view
// when the model is destroyed.
model.after('change', this.render, this);
model.after('destroy', this.destroy, this);
model.after( 'change', this.render, this );
model.after( 'destroy', this.destroy, this );
},
render: function () {
var container = this.container,
model = this.model,
done = model.get('done');
model = this.model,
completed = model.get('completed');
container.setContent(Y.Lang.sub(this.template, {
checked: done ? 'checked' : '',
text : model.getAsHTML('text')
container.setContent( Y.Lang.sub( this.template, {
completed: completed ? 'checked' : '',
title: model.get('title')
}));
container[done ? 'addClass' : 'removeClass']('todo-done');
this.inputNode = container.one('.todo-input');
container[ completed ? 'addClass' : 'removeClass' ]('completed');
this.inputNode = container.one('.edit');
return this;
},
......@@ -302,37 +324,46 @@ TodoView = Y.TodoView = Y.Base.create('todoView', Y.View, [], {
// -- Event Handlers -------------------------------------------------------
// Toggles this item into edit mode.
edit: function () {
edit: function() {
this.container.addClass('editing');
this.inputNode.focus();
this.inputNode.select();
},
// 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()
// handler below.
enter: function (e) {
if (e.keyCode === 13) { // enter key
enter: function( e ) {
if ( e.keyCode === ENTER_KEY ) {
Y.one('#new-todo').focus();
}
},
// Removes this item from the list.
remove: function (e) {
e.preventDefault();
this.constructor.superclass.remove.call(this);
this.model.destroy({'delete': true});
remove: function( e ) {
this.constructor.superclass.remove.call( this );
this.model.destroy({
'delete': true
});
},
// 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.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.
toggleDone: function () {
this.model.toggleDone();
// Toggles the `completed` state on this item's model.
toggle: function() {
this.model.toggle();
}
});
......@@ -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
// TodoModel and TodoList instances above use it to save and load items.
function LocalStorageSync(key) {
function localStorageSync( key ) {
var localStorage;
if (!key) {
if ( !key ) {
Y.error('No storage key specified.');
}
if (Y.config.win.localStorage) {
if ( Y.config.win.localStorage ) {
localStorage = Y.config.win.localStorage;
}
// Try to retrieve existing data from localStorage, if there is any.
// 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.
function destroy(id) {
function destroy( id ) {
var modelHash;
if ((modelHash = data[id])) {
delete data[id];
if ( ( modelHash = data[ id ] ) ) {
delete data[ id ];
save();
}
......@@ -374,8 +405,8 @@ function LocalStorageSync(key) {
var id = '',
i = 4;
while (i--) {
id += (((1 + Math.random()) * 0x10000) | 0)
while ( i-- ) {
id += ( ( ( 1 + Math.random()) * 0x10000) | 0 )
.toString(16).substring(1);
}
......@@ -389,26 +420,26 @@ function LocalStorageSync(key) {
// 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
// layer to be used for both the TodoModel and TodoList classes.
function get(id) {
return id ? data[id] : Y.Object.values(data);
function get( id ) {
return id ? data[ id ] : Y.Object.values( data );
}
// Saves the entire `data` object to localStorage.
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
// necessary), then saves it to localStorage.
function set(model) {
var hash = model.toJSON(),
function set( model ) {
var hash = model.toJSON(),
idAttribute = model.idAttribute;
if (!Y.Lang.isValue(hash[idAttribute])) {
hash[idAttribute] = generateId();
if ( !Y.Lang.isValue(hash[ idAttribute ] ) ) {
hash[ idAttribute ] = generateId();
}
data[hash[idAttribute]] = hash;
data[ hash[ idAttribute ] ] = hash;
save();
return hash;
......@@ -416,23 +447,23 @@ function LocalStorageSync(key) {
// Returns a `sync()` function that can be used with either a Model or a
// ModelList instance.
return function (action, options, callback) {
return function( action, options, callback ) {
// `this` refers to the Model or ModelList instance to which this sync
// method is attached.
var isModel = Y.Model && this instanceof Y.Model;
switch (action) {
switch ( action ) {
case 'create': // intentional fallthru
case 'update':
callback(null, set(this));
callback( null, set( this ) );
return;
case 'read':
callback(null, get(isModel && this.get('id')));
callback( null, get( isModel && this.get('id') ) );
return;
case 'delete':
callback(null, destroy(isModel && this.get('id')));
callback( null, destroy( isModel && this.get('id') ) );
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