Commit 06c248c3 authored by James Thomas's avatar James Thomas

Finished up re-factoring to use application module

parent 2da31519
...@@ -57,10 +57,10 @@ body { ...@@ -57,10 +57,10 @@ body {
line-height: 1; line-height: 1;
} }
#create-todo { .create-todo {
position: relative; position: relative;
} }
#create-todo input { .create-todo input {
width: 466px; width: 466px;
font-size: 24px; font-size: 24px;
font-family: inherit; font-family: inherit;
...@@ -75,7 +75,7 @@ body { ...@@ -75,7 +75,7 @@ body {
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 span { .create-todo span {
position: absolute; position: absolute;
z-index: 999; z-index: 999;
width: 170px; width: 170px;
...@@ -83,17 +83,17 @@ body { ...@@ -83,17 +83,17 @@ body {
margin-left: -85px; margin-left: -85px;
} }
#todo-list { .todo-list {
margin-top: 10px; margin-top: 10px;
} }
#todo-list li { .todo-list li {
padding: 12px 20px 11px 0; padding: 12px 20px 11px 0;
position: relative; position: relative;
font-size: 24px; font-size: 24px;
line-height: 1.1em; line-height: 1.1em;
border-bottom: 1px solid #cccccc; border-bottom: 1px solid #cccccc;
} }
#todo-list li:after { .todo-list li:after {
content: "\0020"; content: "\0020";
display: block; display: block;
height: 0; height: 0;
...@@ -101,18 +101,18 @@ body { ...@@ -101,18 +101,18 @@ body {
overflow: hidden; overflow: hidden;
visibility: hidden; visibility: hidden;
} }
#todo-list li.editing { .todo-list li.editing {
padding: 0; padding: 0;
border-bottom: 0; border-bottom: 0;
} }
#todo-list .editing .display, .todo-list .editing .display,
#todo-list .edit { .todo-list .edit {
display: none; display: none;
} }
#todo-list .editing .edit { .todo-list .editing .edit {
display: block; display: block;
} }
#todo-list .editing input { .todo-list .editing input {
width: 444px; width: 444px;
font-size: 24px; font-size: 24px;
font-family: inherit; font-family: inherit;
...@@ -127,17 +127,17 @@ body { ...@@ -127,17 +127,17 @@ body {
-o-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; box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
} }
#todo-list .check { .todo-list .check {
position: relative; position: relative;
top: 11px; top: 11px;
margin: 0 10px 0 7px; margin: 0 10px 0 7px;
float: left; float: left;
} }
#todo-list .done .todo-content { .todo-list .done .todo-content {
text-decoration: line-through; text-decoration: line-through;
color: #777777; color: #777777;
} }
#todo-list .todo-destroy { .todo-list .todo-destroy {
position: absolute; position: absolute;
right: 5px; right: 5px;
top: 14px; top: 14px;
...@@ -147,25 +147,25 @@ body { ...@@ -147,25 +147,25 @@ body {
height: 20px; height: 20px;
background: url(destroy.png) no-repeat 0 0; background: url(destroy.png) no-repeat 0 0;
} }
#todo-list li:hover .todo-destroy { .todo-list li:hover .todo-destroy {
display: block; display: block;
} }
#todo-list .todo-destroy:hover { .todo-list .todo-destroy:hover {
background-position: 0 -20px; background-position: 0 -20px;
} }
#todo-stats { .todo-stats {
*zoom: 1; *zoom: 1;
margin-top: 10px; margin-top: 10px;
color: #777777; color: #777777;
display: none; display: none;
} }
#todo-stats.items_present { .todo-stats.items_present {
display: block; display: block;
} }
#todo-stats:after { .todo-stats:after {
content: "\0020"; content: "\0020";
display: block; display: block;
height: 0; height: 0;
...@@ -173,31 +173,31 @@ body { ...@@ -173,31 +173,31 @@ body {
overflow: hidden; overflow: hidden;
visibility: hidden; visibility: hidden;
} }
#todo-stats .todo-count { .todo-stats .todo-count {
float: left; float: left;
} }
#todo-stats .todo-count .number { .todo-stats .todo-count .number {
font-weight: bold; font-weight: bold;
color: #333333; color: #333333;
} }
#todo-stats .todo-clear { .todo-stats .todo-clear {
float: right; float: right;
display: none display: none
} }
#todo-stats.items_selected .todo-clear { .todo-stats.items_selected .todo-clear {
display: inline; display: inline;
} }
#todo-stats .todo-clear a { .todo-stats .todo-clear a {
color: #777777; color: #777777;
font-size: 12px; font-size: 12px;
} }
#todo-stats .todo-clear a:visited { .todo-stats .todo-clear a:visited {
color: #777777; color: #777777;
} }
#todo-stats .todo-clear a:hover { .todo-stats .todo-clear a:hover {
color: #336699; color: #336699;
} }
...@@ -352,11 +352,11 @@ body { ...@@ -352,11 +352,11 @@ body {
/* line 32 */ /* line 32 */
#todoapp .content #create-todo { #todoapp .content .create-todo {
position: relative; position: relative;
} }
/* line 34 */ /* line 34 */
#todoapp .content #create-todo input { #todoapp .content .create-todo input {
font-size: 24px; font-size: 24px;
font-family: inherit; font-family: inherit;
line-height: 1.4em; line-height: 1.4em;
...@@ -371,7 +371,7 @@ body { ...@@ -371,7 +371,7 @@ body {
} }
/* line 47 */ /* line 47 */
#todoapp .content #create-todo span { #todoapp .content .create-todo span {
position: absolute; position: absolute;
z-index: 999; z-index: 999;
width: 170px; width: 170px;
...@@ -380,11 +380,11 @@ body { ...@@ -380,11 +380,11 @@ body {
opacity: 0; opacity: 0;
} }
/* line 55 */ /* line 55 */
#todoapp .content ul#todo-list { #todoapp .content ul.todo-list {
margin-top: 10px; margin-top: 10px;
} }
/* line 57 */ /* line 57 */
#todoapp .content ul#todo-list li { #todoapp .content ul.todo-list li {
padding: 15px 20px 15px 0; padding: 15px 20px 15px 0;
position: relative; position: relative;
font-size: 24px; font-size: 24px;
...@@ -393,7 +393,7 @@ body { ...@@ -393,7 +393,7 @@ body {
cursor: move; cursor: move;
} }
/* line 22, /opt/ree/lib/ruby/gems/1.8/gems/compass-0.10.5/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */ /* line 22, /opt/ree/lib/ruby/gems/1.8/gems/compass-0.10.5/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */
#todoapp .content ul#todo-list li:after { #todoapp .content ul.todo-list li:after {
content: "\0020"; content: "\0020";
display: block; display: block;
height: 0; height: 0;
...@@ -402,12 +402,12 @@ body { ...@@ -402,12 +402,12 @@ body {
visibility: hidden; visibility: hidden;
} }
/* line 64 */ /* line 64 */
#todoapp .content ul#todo-list li.editing { #todoapp .content ul.todo-list li.editing {
padding: 0; padding: 0;
border-bottom: 0; border-bottom: 0;
} }
/* line 67 */ /* line 67 */
#todoapp .content ul#todo-list li.editing .todo-input { #todoapp .content ul.todo-list li.editing .todo-input {
display: block; display: block;
width: 466px; width: 466px;
font-size: 24px; font-size: 24px;
...@@ -423,35 +423,35 @@ body { ...@@ -423,35 +423,35 @@ body {
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;
} }
/* line 79 */ /* line 79 */
#todoapp .content ul#todo-list li.editing .todo-content { #todoapp .content ul.todo-list li.editing .todo-content {
display: none; display: none;
} }
/* line 81 */ /* line 81 */
#todoapp .content ul#todo-list li.editing .todo-check { #todoapp .content ul.todo-list li.editing .todo-check {
display: none; display: none;
} }
/* line 83 */ /* line 83 */
#todoapp .content ul#todo-list li.editing .todo-destroy { #todoapp .content ul.todo-list li.editing .todo-destroy {
display: none !important; display: none !important;
} }
/* line 85 */ /* line 85 */
#todoapp .content ul#todo-list li .todo-input { #todoapp .content ul.todo-list li .todo-input {
display: none; display: none;
} }
/* line 87 */ /* line 87 */
#todoapp .content ul#todo-list li .todo-check { #todoapp .content ul.todo-list li .todo-check {
position: relative; position: relative;
top: 6px; top: 6px;
margin: 0 10px 0 7px; margin: 0 10px 0 7px;
float: left; float: left;
} }
/* line 93 */ /* line 93 */
#todoapp .content ul#todo-list li.done .todo-content { #todoapp .content ul.todo-list li.done .todo-content {
text-decoration: line-through; text-decoration: line-through;
color: #777777; color: #777777;
} }
/* line 96 */ /* line 96 */
#todoapp .content ul#todo-list li .todo-destroy { #todoapp .content ul.todo-list li .todo-destroy {
position: absolute; position: absolute;
right: 0px; right: 0px;
top: 22px; top: 22px;
...@@ -461,11 +461,11 @@ body { ...@@ -461,11 +461,11 @@ body {
height: 20px; height: 20px;
} }
/* line 106 */ /* line 106 */
#todoapp .content ul#todo-list li:hover .todo-destroy { #todoapp .content ul.todo-list li:hover .todo-destroy {
display: block; display: block;
} }
/* line 109 */ /* line 109 */
#todoapp #todo-stats { #todoapp .todo-stats {
*zoom: 1; *zoom: 1;
margin-top: 10px; margin-top: 10px;
color: #555555; color: #555555;
...@@ -487,7 +487,7 @@ body { ...@@ -487,7 +487,7 @@ body {
line-height: 36px; 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 */ /* line 22, /opt/ree/lib/ruby/gems/1.8/gems/compass-0.10.5/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */
#todoapp #todo-stats:after { #todoapp .todo-stats:after {
content: "\0020"; content: "\0020";
display: block; display: block;
height: 0; height: 0;
...@@ -496,20 +496,20 @@ body { ...@@ -496,20 +496,20 @@ body {
visibility: hidden; visibility: hidden;
} }
/* line 118 */ /* line 118 */
#todoapp #todo-stats .todo-count { #todoapp .todo-stats .todo-count {
float: left; float: left;
} }
/* line 120 */ /* line 120 */
#todoapp #todo-stats .todo-count .number { #todoapp .todo-stats .todo-count .number {
font-weight: bold; font-weight: bold;
color: #555555; color: #555555;
} }
/* line 123 */ /* line 123 */
#todoapp #todo-stats .todo-clear { #todoapp .todo-stats .todo-clear {
float: right; float: right;
} }
/* line 125 */ /* line 125 */
#todoapp #todo-stats .todo-clear a { #todoapp .todo-stats .todo-clear a {
display: block; display: block;
line-height: 20px; line-height: 20px;
text-decoration: none; text-decoration: none;
...@@ -530,7 +530,7 @@ body { ...@@ -530,7 +530,7 @@ body {
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 */ /* line 136 */
#todoapp #todo-stats .todo-clear a:hover { #todoapp .todo-stats .todo-clear a:hover {
background: rgba(0, 0, 0, 0.15); background: rgba(0, 0, 0, 0.15);
-moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; -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; -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
...@@ -538,7 +538,7 @@ body { ...@@ -538,7 +538,7 @@ body {
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 */ /* line 139 */
#todoapp #todo-stats .todo-clear a:active { #todoapp .todo-stats .todo-clear a:active {
position: relative; position: relative;
top: 1px; top: 1px;
} }
...@@ -557,8 +557,6 @@ body { ...@@ -557,8 +557,6 @@ body {
.claro .dijitCheckBox, .claro .dijitCheckBoxIcon { .claro .dijitCheckBox, .claro .dijitCheckBoxIcon {
background-image: none; background-image: none;
width: 14px;
height: 14px;
} }
.dijitCheckBoxInput { .dijitCheckBoxInput {
......
...@@ -7,171 +7,10 @@ ...@@ -7,171 +7,10 @@
@import "./css/claro.css"; @import "./css/claro.css";
</style> </style>
<link href="css/todos.css" media="all" rel="stylesheet" type="text/css"/> <link href="css/todos.css" media="all" rel="stylesheet" type="text/css"/>
<script data-dojo-config="async:true, parseOnLoad:true, paths:{'todo':'../../todo'}" src="./js/dtk/dojo/dojo.js"></script> <!-- <script data-dojo-config="async:true, parseOnLoad:true, paths:{'todo':'../../todo'}" src="./js/dtk/dojo/dojo.js"></script> -->
<script data-dojo-config="async:true, parseOnLoad:true, paths:{'todo':'/code/todomvc/todo-example/dojo/js/todo'}" src="/code/DTK/dojotoolkit/dojo/dojo.js"></script>
<script> <script>
require(["dojo/parser", "dojo/dom-class", "dojo/_base/array", "dojox/mvc", "todo/store/LocalStorage", require(["dojo/parser", "todo/app"])
"dojox/mvc/Group", "dojox/mvc/Repeat", "dojox/mvc/Output", "dijit/InlineEditBox", "todo/form/CheckBox"],
function (parser, domClass, array, mvc, localStorage) {
/**
* Initialise our custom dojo store, backed by localStorage. This will be
* used to read the initial items, if available, and persist the current items
* when the application finishes.
*/
var store = new localStorage();
/**
* Attempt to read todo items from localStorage,
* returning default value the first time the application
* is loaded... The "id" parameter is used as the unique
* localStorage key for this object.
*/
var getLocalStorageItems = function () {
var initial = {
id: "local_storage_todos",
todos : [],
incomplete: 0,
complete: 0
};
return store.get(initial.id) || initial;
};
/**
* Update the model's "incomplete" value with the
* total number of items not finished. This will automatically
* cause the bound "complete" value to be updated as well.
*/
var updateTotalItemsLeft = function () {
var remaining = 0;
array.forEach(model.todos, function (item) {
item && !item.isDone.value && remaining++;
});
model.incomplete.set("value", remaining);
};
/**
* Add new a new todo item as the last element
* in the parent model.
* Set up bindings to the checkbox value, changes
* should automatically update total items left.
*/
var addToModel = function (content, isDone) {
var insert = mvc.newStatefulModel({
data: {content: content, isDone: isDone}
});
bindIsDone(insert);
model.todos.add(model.todos.length, insert);
}
/**
* Set up binding on a todo item, so that when the
* item's checked attribute changes, we re-calculate
* the composite model attribute's value, "complete".
*/
var bindIsDone = function (item) {
mvc.bindInputs([item.isDone], updateTotalItemsLeft);
}
/**
* Create new application Model class, this will be used to bind
* the UI elements to the data store items. Pre-populate model with
* items from localStorage if they exist...
*/
window.model = mvc.newStatefulModel({ data : getLocalStorageItems() });
/**
* Set up a composite model attribute, "complete", that is automatically
* calculated whenever the "incomplete" source value is modified. The "complete"
* attribute is bound to a view widget, displaying the number of items that can
* be cleared using the link.
*/
mvc.bind(model.incomplete, "value", model.complete, "value", function (value) {
return this.model.todos.get("length") - value;
});
/**
* The methods below set up a function binding to the composite (complete & incomplete)
* model attributes. These values are used to append CSS classes to dynamically show & hide
* the "stats" elements when the model is non-empty and has some completed items. Whenever
* the values below are updated, the function will be executed.
*/
mvc.bindInputs([model.incomplete], function () {
domClass[model.todos.get("length") ? "add" : "remove" ]("todo-stats", "items_present");
});
mvc.bindInputs([model.complete], function () {
domClass[model.complete.value > 0 ? "add" : "remove" ]("todo-stats", "items_selected");
});
/**
* Bind all pre-populated todo items to update the
* total item values when the "isDone" attribute is changed.
*/
array.forEach(model.todos, bindIsDone);
/**
* Whenever the "todos" array is modified, an element is added
* or deleted, we need to recompute the model composite values.
* Binding attributes changes to the "updateTotalItemsLeft" function
* using "watch", an attribute on dojo.Stateful objects.
*/
model.todos.watch(updateTotalItemsLeft);
/**
* Update the stats panel for the pre-populated
* items that may have been retrieved from localStorage
*/
updateTotalItemsLeft();
/**
* Global event handlers, used to process user events
* (add item, remove item) generated by the views.
*/
window.addTodoItem = function (input, event) {
if (event.keyCode !== 13) return;
addToModel(input.value, false);
input.value = "";
};
/**
* Remove all items that have been completed from
* model. We have to individually check each todo
* item, removing if true.
*/
window.removeCompletedItems = function () {
var len = model.todos.length, idx = 0;
/**
* Removing array indices from a Dojo MVC Model
* array left-shifts the remaining items. When
* we find an item to remove, don't increment the
* index and, instead, decrement the total item count.
*/
while (idx < len) {
if (model.todos[idx].isDone.value) {
model.todos.remove(idx);
len--;
continue;
}
idx++;
};
};
/**
* Hook into unload event to trigger persisting
* of the current model contents into the localStorage
* backed data store.
*/
window.onbeforeunload = function () {
model.commit(store);
};
});
</script> </script>
</head> </head>
...@@ -185,53 +24,14 @@ ...@@ -185,53 +24,14 @@
<h1>Todos</h1> <h1>Todos</h1>
</div> </div>
<div class="content"> <div class="content" data-dojo-type="todo.app"></div>
<div id="create-todo">
<input id="new-todo" placeholder="What needs to be done?" type="text" onkeypress="addTodoItem(this, event)"/>
<span class="ui-tooltip-top" style="display:none;">Press Enter to save this task</span>
</div>
<div id="todos">
<ul id="todo-list" data-dojo-type="dojox.mvc.Repeat" data-dojo-props="ref: 'model.todos'">
<li data-dojo-type="dojox.mvc.Group" data-dojo-props="ref: '${this.index}'">
<div class="todo">
<input class="check" data-dojo-type="todo.form.CheckBox" data-dojo-props='ref: "isDone"' style="display:inline-block" >
</input>
<div class="todo-content dijitInline" data-dojo-type="dijit.InlineEditBox"
data-dojo-props='ref: "content", editor:"dijit.form.TextBox", autosave:true, width:"420px", style:"width:420px;"'></div>
<span class="todo-destroy" data-model-id="${this.index}" onclick="model.todos.remove(this.dataset.modelId)">
</span>
</div>
</li>
</ul>
</div>
<div id="todo-stats">
<span class="todo-count">
<span data-dojo-type="dojox.mvc.Output" data-dojo-props="ref: model.incomplete" class="number"></span>
<span class="word">items</span> left.
</span>
<span class="todo-clear">
<a href="#" onclick="removeCompletedItems()">
Clear
<span class="number-done" data-dojo-type="dojox.mvc.Output" data-dojo-props="ref: model.complete" ></span>
completed items
</a>
</span>
</div>
</div>
</div>
<div id="credits"> <div id="credits">
Created by Created by
<br /> <br />
<a href="http://jamesthom.as/">James Thomas</a> and <a href="https://github.com/edchat">Ed Chatelain</a>. <a href="http://jamesthom.as/">James Thomas</a> and <a href="https://github.com/edchat">Ed Chatelain</a>.
</div> </div>
</body> </div>
</body>
</html> </html>
This source diff could not be displayed because it is too large. You can view the blob instead.
<div>
<div class="create-todo">
<input class="new-todo" data-dojo-attach-event="onkeypress:onKeyPress" placeholder="What needs to be done?" type="text"/>
<span class="ui-tooltip-top" style="display:none;">Press Enter to save this task</span>
</div>
<div class="todos">
<ul class="todo-list" data-dojo-attach-point="todo_list" data-dojo-type="dojox.mvc.Repeat" data-dojo-props="ref: this.model.todos, exprchar: '#'">
<li data-dojo-type="dojox.mvc.Group" data-dojo-props="ref: '#{this.index}'">
<div class="todo">
<input class="check" data-dojo-type="todo.form.CheckBox" data-dojo-props='ref: "isDone"' style="display:inline-block" >
</input>
<div class="todo-content dijitInline" data-dojo-type="dijit.InlineEditBox"
data-dojo-props='ref: "content", editor:"dijit.form.TextBox", autosave:true, width:"420px", style:"width:420px;"'></div>
<span class="todo-destroy" data-model-id="#{this.index}">
</span>
</div>
</li>
</ul>
</div>
<div class="todo-stats" data-dojo-attach-point="todo_stats">
<span class="todo-count">
<span data-dojo-type="dojox.mvc.Output" data-dojo-props="ref: this.model.incomplete" class="number"></span>
<span class="word">items</span> left.
</span>
<span class="todo-clear">
<a href="#" data-dojo-attach-event="onclick:removeCompletedItems">
Clear
<span class="number-done" data-dojo-type="dojox.mvc.Output" data-dojo-props="ref: this.model.complete" ></span>
completed items
</a>
</span>
</div>
</div>
/**
* Original source from https://gist.github.com/880822
* Converted to AMD-baseless format
*/
define(["dojo/_base/declare",
// Parent classes
"dijit/_WidgetBase", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin",
// General application modules
"dojo/_base/lang", "dojo/on", "dojo/dom-class", "dojox/mvc", "todo/model/TodoModel",
// Widget template
"dojo/text!./app.html",
// Template Widgets
"dijit/InlineEditBox", "todo/form/CheckBox", "dojox/mvc/Group", "dojox/mvc/Repeat", "dojox/mvc/Output"],
function(declare, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, lang, on, domClass, mvc, TodoModel, template) {
return declare("todo.app", [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {
templateString: template,
constructor: function () {
/**
* Create new application Model class, this will be used to bind
* the UI elements to the data store items. Pre-populate model with
* items from localStorage if they exist...
*/
this.model = new TodoModel();
/**
* The method below set up a function binding to the composite (complete & incomplete)
* model attributes. These values are used to append CSS classes to dynamically show & hide
* the "stats" elements when the model is non-empty and has some completed items. Whenever
* the values below are updated, the function will be executed.
*/
mvc.bindInputs([this.model.complete, this.model.incomplete], lang.hitch(this, "onItemStatusUpdate"));
/**
* Hook into unload event to trigger persisting
* of the current model contents into the localStorage
* backed data store.
*/
window.onbeforeunload = lang.hitch(this, function () {
this.model.commit();
});
},
/**
* Listen for item remove events from the using event delegation,
* we don't have to attach to each item. Also, ensure todo-stats
* have the correct initial CSS classes given the starting model
* contents.
*/
postCreate: function () {
on(this.domNode, ".todo-destroy:click", lang.hitch(this, "onRemove"));
this.onItemStatusUpdate();
},
/**
* Remove all items that have been completed from
* model. We have to individually check each todo
* item, removing if true.
*/
removeCompletedItems: function () {
var len = this.model.todos.length, idx = 0;
/**
* Removing array indices from a Dojo MVC Model
* array left-shifts the remaining items. When
* we find an item to remove, don't increment the
* index and, instead, decrement the total item count.
*/
while (idx < len) {
if (this.model.todos[idx].isDone.value) {
this.model.todos.remove(idx);
len--;
continue;
}
idx++;
}
},
/**
* Add new a new todo item as the last element
* in the parent model.
*/
addToModel: function (content, isDone) {
var insert = mvc.newStatefulModel({
data: {content: content, isDone: isDone}
});
this.model.todos.add(this.model.todos.length, insert);
},
/**
* Adjust CSS classes on todo-stats element based upon whether
* we a number of completed and incomplete todo items.
*/
onItemStatusUpdate: function () {
domClass[this.model.complete.value > 0 ? "add" : "remove" ](this.todo_stats, "items_selected");
domClass[this.model.todos.get("length") ? "add" : "remove" ](this.todo_stats, "items_present");
},
/**
* Handle key press events for the todo input
* field. If user has pressed enter, add current
* text value as new todo item in the model.
*/
onKeyPress: function (event) {
if (event.keyCode !== 13) return;
this.addToModel(event.target.value, false);
event.target.value = "";
},
/**
* Event handler when user has clicked to
* remove a todo item, just remove it from the
* model using the item identifier.
**/
onRemove: function (event) {
this.model.todos.remove(event.target.dataset.modelId);
}
});
});
define(["dojo/_base/declare", "dojox/mvc/StatefulModel", "todo/store/LocalStorage", "dojox/mvc", "dojo/_base/lang", "dojo/_base/array"],
function(declare, StatefulModel, LocalStorage, mvc, lang, array) {
return declare([StatefulModel], {
/**
* Default model structure, overriden by any
* items found in localStorage.
*/
data: {
id: "local_storage_todos",
todos : [],
incomplete: 0,
complete: 0
},
/**
* Initialise our custom dojo store, backed by localStorage. This will be
* used to read the initial items, if available, and persist the current items
* when the application finishes.
*/
store: new LocalStorage(),
constructor: function () {
/**
* Attempt to read todo items from localStorage,
* returning default value the first time the application
* is loaded... The "id" parameter is used as the unique
* localStorage key for this object.
*/
var data = this.store.get(this.data.id) || this.data;
this._createModel(data);
this.setUpModelBinding();
this.updateTotalItemsLeft();
},
setUpModelBinding: function () {
/**
* Set up a composite model attribute, "complete", that is automatically
* calculated whenever the "incomplete" source value is modified. The "complete"
* attribute is bound to a view widget, displaying the number of items that can
* be cleared using the link.
*/
mvc.bind(this.incomplete, "value", this.complete, "value", lang.hitch(this, function (value) {
return this.todos.get("length") - value;
}));
/**
* Bind all pre-populated todo items to update the
* total item values when the "isDone" attribute is changed.
*/
array.forEach(this.todos, lang.hitch(this, "bindIsDone"));
/**
* Whenever the "todos" array is modified, an element is added
* or deleted, we need to recompute the model composite values.
* Binding attributes changes to the "onTodosModelChange" function
* using "watch", an attribute on dojo.Stateful objects.
*/
this.todos.watch(lang.hitch(this, "onTodosModelChange"));
},
/**
* Set up binding on a todo item, so that when the
* item's checked attribute changes, we re-calculate
* the composite model attribute's value, "complete".
*/
bindIsDone: function (item) {
mvc.bindInputs([item.isDone], lang.hitch(this, "updateTotalItemsLeft"));
},
/**
* When todos array is modified, we need to update the composite
* value attributes. If the modification was an addition, ensure the
* "isDone" attribute is being watched for updates.
*/
onTodosModelChange: function (prop, oldValue, newValue) {
this.updateTotalItemsLeft();
// Adding to an array indicated by prop being indices and no previous value existing
if (typeof prop === "number" && !oldValue && newValue) {
this.bindIsDone(newValue);
}
},
/**
* Update the model's "incomplete" value with the
* total number of items not finished. This will automatically
* cause the bound "complete" value to be updated as well.
*/
updateTotalItemsLeft: function () {
var remaining = 0;
array.forEach(this.todos, function (item) {
item && !item.isDone.value && remaining++;
});
this.incomplete.set("value", remaining);
}
});
});
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