Commit 0103a8e4 authored by Addy Osmani's avatar Addy Osmani

Merge pull request #290 from jthomas/master

Updating example to use Dojo 1.8 and new MVC features
parents 6828eaac 19cf5e30
......@@ -86,3 +86,10 @@
.inline_edit[style~='0;'] ~ .toggle {
visibility: hidden !important;
}
.dijitOffScreen { /* For 1.8 in-line edit box */
position: absolute !important;
left: 50% !important;
top: -10000px !important;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Dojo • TodoMVC</title>
<link rel="stylesheet" href="../../assets/base.css">
<!-- CSS overrides - remove if you don't need it -->
<link rel="stylesheet" href="css/app.css">
<!--[if IE]>
<script src="../../assets/ie.js"></script>
<![endif]-->
</head>
<body>
<section id="todoapp" data-dojo-type="todo.app"></section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://jamesthom.as/">James Thomas</a> and <a href="https://github.com/edchat">Ed Chatelain</a></p>
</footer>
<script src="../../assets/base.js"></script>
<script data-dojo-config="async:true, parseOnLoad:true, locale:'en', paths:{'todo':'../todo/'}, deps:['dojo/parser', 'todo/app']" src="js/lib/dojo/dojo.js"></script>
</body>
</html>
<!doctype html>
<!--
This is a Dojo version of TodoMVC using uncompressed Dojo source.
To use this, place todomvc directory at the directory containing dojo/dijit/dojox.
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Dojo • TodoMVC</title>
<link rel="stylesheet" href="../../assets/base.css">
<!-- CSS overrides - remove if you don't need it -->
<link rel="stylesheet" href="css/app.css">
<!--[if IE]>
<script>
nativeDate = Date;
</script>
<script src="../../assets/ie.js"></script>
<script>
(function(nativeDate){
// It appears that the modification to Date constructor done by ie.js negatively affects new Date("X"), ending up a JS error.
// Using the native Date constructor for that case.
var origDate = Date;
Date = function(){
return (arguments.length == 1 && typeof arguments[0] == "string" ? nativeDate : origDate).apply(this, [].slice.call(arguments, 0));
};
Date.prototype = new origDate();
})(nativeDate);
</script>
<![endif]-->
</head>
<body>
<section id="todoapp" data-dojo-type="todo/app18"></section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://jamesthom.as/">James Thomas</a> and <a href="https://github.com/edchat">Ed Chatelain</a></p>
</footer>
<script src="../../assets/base.js"></script>
<script>
require = {
async: true,
parseOnLoad: true,
locale: "en",
paths: {
"todo": "../todomvc/architecture-examples/dojo/js/todo"
},
deps: ["dojo/parser", "dojo/domReady!"],
mvc: {debugBindings: true}
};
</script>
<script src="../../../dojo/dojo.js"></script>
</body>
</html>
......@@ -8,17 +8,43 @@
<!-- CSS overrides - remove if you don't need it -->
<link rel="stylesheet" href="css/app.css">
<!--[if IE]>
<script>
nativeDate = Date;
</script>
<script src="../../assets/ie.js"></script>
<script>
(function(nativeDate){
// It appears that the modification to Date constructor done by ie.js negatively affects new Date("X"), ending up a JS error.
// Using the native Date constructor for that case.
var origDate = Date;
Date = function(){
return (arguments.length == 1 && typeof arguments[0] == "string" ? nativeDate : origDate).apply(this, [].slice.call(arguments, 0));
};
Date.prototype = new origDate();
})(nativeDate);
</script>
<![endif]-->
</head>
<body>
<section id="todoapp" data-dojo-type="todo.app"></section>
<section id="todoapp" data-dojo-type="todo/app18"></section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://jamesthom.as/">James Thomas</a> and <a href="https://github.com/edchat">Ed Chatelain</a></p>
</footer>
<script src="../../assets/base.js"></script>
<script data-dojo-config="async:true, parseOnLoad:true, locale:'en', paths:{'todo':'../todo/'}, deps:['dojo/parser', 'todo/app']" src="js/lib/dojo/dojo.js"></script>
<script>
require = {
async: true,
parseOnLoad: true,
locale: "en",
paths: {
"dijit": "../dijit-1.8"
},
deps: ["dojo/parser", "dojo/domReady!"],
mvc: {debugBindings: true}
};
</script>
<script src="js/lib/dojo-1.8/dojo.js"></script>
</body>
</html>
define("dijit/nls/common", { root:
//begin v1.x content
({
buttonOk: "OK",
buttonCancel: "Cancel",
buttonSave: "Save",
itemClose: "Close"
})
//end v1.x content
,
"zh": true,
"zh-tw": true,
"tr": true,
"th": true,
"sv": true,
"sl": true,
"sk": true,
"ru": true,
"ro": true,
"pt": true,
"pt-pt": true,
"pl": true,
"nl": true,
"nb": true,
"ko": true,
"kk": true,
"ja": true,
"it": true,
"hu": true,
"hr": true,
"he": true,
"fr": true,
"fi": true,
"es": true,
"el": true,
"de": true,
"da": true,
"cs": true,
"ca": true,
"az": true,
"ar": true
});
define(
//begin v1.x content
{
"decimal": ".",
"group": ",",
"list": ";",
"percentSign": "%",
"plusSign": "+",
"minusSign": "-",
"exponential": "E",
"perMille": "",
"infinity": "",
"nan": "NaN",
"decimalFormat": "#,##0.###",
"decimalFormat-short": "000T",
"scientificFormat": "#E0",
"percentFormat": "#,##0%",
"currencyFormat": "¤#,##0.00;(¤#,##0.00)"
}
//end v1.x content
);
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
define('dojo/nls/dojo_ROOT',{
'dijit/nls/common':{"buttonOk":"OK","buttonCancel":"Cancel","buttonSave":"Save","itemClose":"Close"}
});
\ No newline at end of file
define([
"dojo/_base/array",
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/dom-class",
"dijit/_WidgetBase"
], function(array, declare, lang, domClass, _WidgetBase){
return declare(_WidgetBase, {
// summary:
// Widget supporting widget attributes with classExists type.
// classExists type allows boolean value of an attribute to reflect existence of a CSS class in a DOM node in the widget.
// example:
// In this example, the text will be bold when the check box is checked.
// | <html>
// | <head>
// | <script src="/path/to/dojo-toolkit/dojo/dojo.js" type="text/javascript"
// | data-dojo-config="parseOnLoad: 1,
// | packages: [{name: 'todo', location: '/path/to/todo-package'}],
// | deps: ['dojo/parser', 'dojo/domReady!']"></script>
// | <style type="text/css">
// | .boldText {
// | font-weight: bold;
// | }
// | </style>
// | </head>
// | <body>
// | <script type="dojo/require">at: "dojox/mvc/at"</script>
// | <input id="checkbox" data-dojo-type="dijit/form/CheckBox">
// | <div data-dojo-type="todo/CssToggleWidget"
// | data-dojo-props="_setBoldAttr: {type: 'classExists', className: 'boldText'},
// | bold: at('widget:checkbox', 'checked')">This text will be bold when above check box is checked.</div>
// | </body>
// | </html>
_attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){
// summary:
// Handle widget attribute with classExists type.
// See dijit/_WidgetBase._attrToDom() for more details.
var callee = arguments.callee;
array.forEach((function(){ return lang.isArray(commands) ? commands.slice(0) : [commands]; })(arguments.length >= 3 ? commands : this.attributeMap[attr]), function(command){
command.type != "classExists" ?
this.inherited("_attrToDom", lang.mixin([attr, value, command], {callee: callee})) :
domClass.toggle(this[command.node || "domNode"], command.className || attr, value);
}, this);
}
});
});
define([
"dojo/_base/declare",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"dojox/mvc/WidgetList",
"dojox/mvc/_InlineTemplateMixin",
"todo/CssToggleWidget",
"todo/ctrl/_HashCompletedMixin"
], function(declare, _TemplatedMixin, _WidgetsInTemplateMixin, WidgetList, _InlineTemplateMixin, CssToggleWidget, _HashCompletedMixin){
return declare([WidgetList, _InlineTemplateMixin], {
childClz: declare([CssToggleWidget, _TemplatedMixin, _WidgetsInTemplateMixin, _HashCompletedMixin], {
_setCompletedAttr: {type: "classExists", className: "completed"},
_setHiddenAttr: {type: "classExists", className: "hidden"},
onRemoveClick: function(){
this.parent.listCtrl.removeItem(this.uniqueId);
},
onEditBoxChange: function(){
if(!this.editBox.value){
this.parent.listCtrl.removeItem(this.uniqueId);
}
}
})
});
});
<section>
<script type="dojo/require">
at: "dojox/mvc/at",
SimpleTodoModel: "todo/model/SimpleTodoModel",
HashSelectedConverter: "todo/misc/HashSelectedConverter",
LessThanOrEqualToConverter: "todo/misc/LessThanOrEqualToConverter"
</script>
<span data-dojo-id="${id}_routeCtl" data-dojo-type="todo/ctrl/RouteController"></span>
<span data-dojo-id="${id}_localStorage" data-dojo-type="todo/store/LocalStorage"></span>
<span data-dojo-id="${id}_ctrl"
data-dojo-type="todo/ctrl/TodoRefController"
data-dojo-props="defaultId: 'todos-dojo', store: ${id}_localStorage, modelClass: SimpleTodoModel, complete: at('widget:${id}', 'complete')"></span>
<span data-dojo-id="${id}_listCtrl"
data-dojo-type="todo/ctrl/TodoListRefController"
data-dojo-props="model: at(${id}_ctrl, 'todos'), length: at('widget:${id}', 'present').direction(at.to)"></span>
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" type="text" autofocus
data-dojo-attach-event="onkeypress: onKeyPressNewItem">
<span class="ui-tooltip-top" style="display:none;">Press Enter to save this task</span>
</header>
<section id="main">
<input id="toggle-all" type="checkbox"
data-dojo-type="dijit/form/CheckBox"
data-dojo-props="checked: at(${id}_ctrl, 'incomplete').transform(LessThanOrEqualToConverter),
onClick: function(e){ ${id}_listCtrl.markAll(e.target.checked); }">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"
data-dojo-type="todo/TodoList"
data-dojo-props="children: at(${id}_listCtrl, 'model'), partialRebuild: true, listCtrl: ${id}_listCtrl"
data-mvc-child-props="uniqueId: at(this.target, 'uniqueId'), completed: at(this.target, 'completed'), hash: at(${id}_routeCtl, 'hash')">
<script type="dojox/mvc/InlineTemplate">
<li>
<div class="view">
<label class="dijitInline inline_edit"
data-dojo-attach-point="editBox"
data-dojo-attach-event="onChange: onEditBoxChange"
data-dojo-type="todo/form/InlineEditBox"
data-dojo-props="value: at('rel:', 'title'), editor: 'dijit/form/TextBox', autosave: true"></label>
<input class="toggle" type="checkbox"
data-dojo-type="dijit/form/CheckBox"
data-dojo-props="checked: at('rel:', 'completed')">
<button class="destroy" data-dojo-attach-event="onclick: onRemoveClick"></button>
</div>
</li>
</script>
</ul>
</section>
<footer id="footer">
<span id="todo-count">
<strong>
<span class="number"
data-dojo-type="dijit/_WidgetBase"
data-dojo-props="_setValueAttr: {type: 'innerText', node: 'domNode'}, value: at(${id}_ctrl, 'incomplete')"></span>
</strong>
<span class="word">item<!--
--><span data-dojo-type="todo/CssToggleWidget"
data-dojo-props="_setSingleAttr: {type: 'classExists', className: 'plural'},
constraints: {lessThanOrEqualTo: 1},
single: at(${id}_ctrl, 'incomplete').transform(LessThanOrEqualToConverter)">s</span></span>
left.
</span>
<!-- Remove this if you don't implement routing -->
<ul id="filters">
<li>
<a href="#/"
data-dojo-type="todo/CssToggleWidget"
data-dojo-props="_setSelectedAttr: {type: 'classExists', className: 'selected'},
selected: at(${id}_routeCtl, 'hash').transform(HashSelectedConverter)">All</a>
</li>
<li>
<a href="#/active"
data-dojo-type="todo/CssToggleWidget"
data-dojo-props="_setSelectedAttr: {type: 'classExists', className: 'selected'},
selected: at(${id}_routeCtl, 'hash').transform(HashSelectedConverter)">Active</a>
</li>
<li>
<a href="#/completed"
data-dojo-type="todo/CssToggleWidget"
data-dojo-props="_setSelectedAttr: {type: 'classExists', className: 'selected'},
selected: at(${id}_routeCtl, 'hash').transform(HashSelectedConverter)">Completed</a>
</li>
</ul>
<button id="clear-completed" onclick="${id}_listCtrl.removeCompletedItems();">
Clear completed (
<span class="number-done"
data-dojo-type="dijit/_WidgetBase"
data-dojo-props="_setValueAttr: {type: 'innerText', node: 'domNode'}, value: at(${id}_ctrl, 'complete')"></span>
)
</button>
</footer>
</section>
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/_base/unload",
"dojo/keys",
"dojo/string",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"todo/CssToggleWidget",
"dojo/text!./app18.html",
// Below modules are referred in template.
// dijit/_WidgetsInTemplateMixin requires all modules referred in template to have been loaded before it's instantiated.
"dijit/form/CheckBox",
"dijit/form/TextBox",
"dojox/mvc/at",
"todo/TodoList",
"todo/ctrl/RouteController",
"todo/ctrl/TodoListRefController",
"todo/ctrl/TodoRefController",
"todo/form/InlineEditBox",
"todo/misc/HashSelectedConverter",
"todo/misc/LessThanOrEqualToConverter",
"todo/model/SimpleTodoModel",
"todo/store/LocalStorage"
], function(declare, lang, unload, keys, string, _TemplatedMixin, _WidgetsInTemplateMixin, CssToggleWidget, template){
return declare([CssToggleWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
// summary:
// A widgets-in-template widget that composes the application UI of TodoMVC (Dojo 1.8 version).
// Also, this class inherits todo/CssToggleWidget so that it can react to change in "present"/"complete" attributes and add/remove CSS class to the root DOM node of this widget.
// templateString: String
// The HTML of widget template.
templateString: template,
// _setPresentAttr: Object
// A object used by todo/CssToggleWidget to reflect true/false state of "present" attribute to existence of "todos_present" CSS class in this widget's root DOM.
_setPresentAttr: {type: "classExists", className: "todos_present"},
// _setCompleteAttr: Object
// A object used by todo/CssToggleWidget to reflect true/false state of "complete" attribute to existence of "todos_selected" CSS class in this widget's root DOM.
_setCompleteAttr: {type: "classExists", className: "todos_selected"},
startup: function(){
// summary:
// A function called after the DOM fragment declaring this controller is added to the document.
// See documentation for dijit/_WidgetBase.startup() for more details.
var _self = this;
this.inherited(arguments);
unload.addOnUnload(function(){
_self.destroy(); // When this page is being unloaded, call destroy callbacks of inner-widgets to let them clean up
});
},
onKeyPressNewItem: function(/*DOMEvent*/ event){
// summary:
// Handle key press events for the input field for new todo.
// description:
// If user has pressed enter, add current text value as new todo item in the model.
if(event.keyCode !== keys.ENTER || !string.trim(event.target.value).length){
return; // If the key is not Enter, or the input field is empty, bail
}
lang.getObject(this.id + "_listCtrl").addItem(event.target.value); // Add a todo item with the given text
event.target.value = ""; // Clear the input field
event.preventDefault();
event.stopPropagation();
}
});
});
define([
"dojo/_base/declare",
"dojo/router",
"dijit/Destroyable",
"dojox/mvc/_Controller"
], function(declare, router, Destroyable, _Controller){
return declare([_Controller, Destroyable], {
// summary:
// A controller that maintains hash attribute in sync with location.hash.
// example:
// In this example, the text box is in sync with the URL hash.
// (The change in URL hash is reflected to the text box, and the edit in text box will be reflected to the URL hash)
// | <html>
// | <head>
// | <script src="/path/to/dojo-toolkit/dojo/dojo.js" type="text/javascript"
// | data-dojo-config="parseOnLoad: 1,
// | packages: [{name: 'todo', location: '/path/to/todo-package'}],
// | deps: ['dojo/parser', 'dojo/domReady!']"></script>
// | </head>
// | <body>
// | <script type="dojo/require">at: "dojox/mvc/at"</script>
// | <span id="routeCtrl" data-dojo-type="todo/ctrl/RouteController"></span>
// | <input data-dojo-type="dijit/form/TextBox"
// | data-dojo-props="value: at('widget:routeCtrl', 'hash')">
// | </body>
// | </html>
postscript: function(/*Object?*/ params, /*DOMNode?*/ srcNodeRef){
// summary:
// Kicks off instantiation of this controller, in a similar manner as dijit/_WidgetBase.postscript().
// params: Object?
// The optional parameters for this controller.
// srcNodeRef: DOMNode?
// The DOM node declaring this controller. Set if this controller is created via Dojo parser.
this.inherited(arguments);
srcNodeRef && srcNodeRef.setAttribute("widgetId", this.id); // If this is created via Dojo parser, set widgetId attribute so that destroyDescendants() of parent widget works
},
startup: function(){
// summary:
// A function called after the DOM fragment declaring this controller is added to the document, in a similar manner as dijit/_WidgetBase.startup().
var _self = this;
this.own(router.register(/.*/, function(e){ // Register a route handling callback for any route, make sure it's cleaned up upon this controller being destroyed
_self._set("hash", e.newPath); // Update hash property
}));
router.startup(); // Activate dojo/router
this.set("hash", router._currentPath); // Set the inital value of hash property
},
_setHashAttr: function(value){
// summary:
// Handler for calls to set("hash", val).
// description:
// If the new value is different from location.hash, updates location.hash.
if(this.hash != value){
router.go(value); // If the new value is different from location.hash, updates location.hash
}
this._set("hash", value); // Assign the new value to the property
}
});
})
define([
"dojo/_base/array",
"dojo/_base/declare",
"dojo/Stateful",
"dojox/mvc/ModelRefController"
], function(array, declare, Stateful, ModelRefController){
return declare(ModelRefController, {
// summary:
// Our custom controller that does:
//
// - Handle actions like adding/removing/marking
// - Provide references to the todo list in data model, whose data comes from above Dojo Object Store
//
// description:
// The todo list in the data model, which is based on dojox/mvc/StatefulArray, can be referred via this[this._refModelProp].
// Actions are implemented in the manner of manipulating array.
// The change will automatically be reflected to the UI via the notification system of dojox/mvc/StatefulArray.
addItem: function(/*String*/ title){
// summary:
// Adds a todo item with the given title.
// title: String
// The title of todo item.
this[this._refModelProp].push(new Stateful({title: title, completed: false}));
},
removeItem: function(/*String*/ uniqueId){
// summary:
// Removes a todo item having the given unique ID.
// uniqueId: String
// The unique ID of the todo item to be removed.
var model = this[this._refModelProp],
indices = array.filter(array.map(model, function(item, idx){ return item.uniqueId == uniqueId ? idx : -1; }), function(idx){ return idx >= 0; }); // The array index of the todo item to bd removed
if(indices.length > 0){
model.splice(indices[0], 1);
}
},
removeCompletedItems: function(){
// summary:
// Removes todo items that have been marked as complete.
var model = this[this._refModelProp];
for(var i = model.length - 1; i >= 0; --i){
if(model[i].get("completed")){
model.splice(i, 1);
}
}
},
markAll: function(/*Boolean*/ markComplete){
// summary:
// Mark all todo items as complete or incomplete.
// markComplete: Boolean
// True to mark all todo items as complete. Otherwise to mark all todo items as incomplete.
array.forEach(this[this._refModelProp], function(item){
item.set("completed", markComplete);
});
}
});
});
define([
"dojo/_base/array",
"dojo/_base/declare",
"dojox/mvc/EditStoreRefController"
], function(array, declare, EditStoreRefController){
return declare(EditStoreRefController, {
// summary:
// Our custom controller that does:
//
// - Load and save data using a Dojo Object Store
// - Provide references to the data model, whose data comes from above Dojo Object Store
// defaultId: String
// The default ID to fetch data as this controller starts up.
defaultId: "",
// store: dojo/store
// Our custom Dojo Object 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: null,
// modelClass: todo/model/SimpleTodoModel
// The class of our data model, based on dojo/Stateful and dojox/mvc/StatefulArray.
// This will be used to automatically keep various Todo properties (e.g. number of complete/incomplete items) consistent, and to auto-generate properties (e.g. default unique IDs).
modelClass: null,
getStatefulOptions: {
// summary:
// An object that specifies how plain object from Dojo Object Store should be converted to a data model based on dojo/Stateful and dojox/mvc/StatefulArray.
getType: function(/*Object*/ v){
return "specifiedModel"; // We are converting given object at once using modelClass here, instead of using per-data-item based data conversion callbacks
},
getStatefulSpecifiedModel: function(/*Object*/ o){
return new (this.parent.modelClass)(o); // Create an instance of modelClass given the plain object from Dojo Object Store
}
},
getPlainValueOptions: {
// summary:
// An object that specifies how a data model based on dojo/Stateful and dojox/mvc/StatefulArray should be converted to a plain object, to be saved to Dojo Object Store.
getType: function(/*todo/model/SimpleTodoModel*/ o){
return "specifiedModel"; // We are converting given data model at once, instead of using per-data-item based data conversion callbacks
},
getPlainSpecifiedModel: function(/*todo/model/SimpleTodoModel*/ o){
return { // Pick up only meaningful data here
id: o.id,
todos: array.map(o.todos, function(item){ return {title: item.title, completed: item.completed }; }),
incomplete: o.incomplete,
complete: o.complete
};
}
},
postscript: function(/*Object?*/ params, /*DOMNode?*/ srcNodeRef){
// summary:
// Kicks off instantiation of this controller, in a similar manner as dijit/_WidgetBase.postscript().
// params: Object?
// The optional parameters for this controller.
// srcNodeRef: DOMNode?
// The DOM node declaring this controller. Set if this controller is created via Dojo parser.
this.getStatefulOptions.parent = this; // For getStatefulOptions object to have a reference to this object
this.inherited(arguments);
srcNodeRef && srcNodeRef.setAttribute("widgetId", this.id); // If this is created via Dojo parser, set widgetId attribute so that destroyDescendants() of parent widget works
},
startup: function(){
// summary:
// A function called after the DOM fragment declaring this controller is added to the document, in a similar manner as dijit/_WidgetBase.startup().
this.inherited(arguments);
this.getStore(this.defaultId); // Obtain data from Dojo Object Store as soon as this starts up
},
set: function(/*String*/ name, /*Anything*/ value){
// summary:
// A function called when a property value is set to this controller.
if(name == this._refSourceModelProp && this[this._refSourceModelProp] != value && (this[this._refSourceModelProp] || {}).destroy){
this[this._refSourceModelProp].destroy(); // If we have a data model and it's being replaced, make sure it's cleaned up
}
this.inherited(arguments);
},
destroy: function(){
// summary:
// A function called when this controller is being destroyed, in a similar manner as dijit/_WidgetBase.destroy().
this.commit(); // Save the data to Dojo Object Store when this is destroyed
if((this[this._refSourceModelProp] || {}).destroy){
this[this._refSourceModelProp].destroy(); // If we have a data model, make sure it's cleaned up
}
this.inherited(arguments);
}
});
});
define(["dojo/_base/declare"], function(declare){
var ACTIVE = "/active",
COMPLETED = "/completed";
function getHiddenState(/*Object*/ props){
// summary:
// Returns the new hidden state of todo item, given the URL hash and the completed state of todo item.
// props: Object
// An object containing the URL hash and the completed state of todo item.
return props.hash == ACTIVE ? props.completed :
props.hash == COMPLETED ? !props.completed :
false;
}
return declare(null, {
// summary:
// A mix-in class for widgets-in-template for todo item, that looks at URL hash and completed state of todo item, and updates the hidden state.
// description:
// A todo item should be hidden if:
//
// - URL hash is "/active" and the todo item is complete -OR-
// - URL hash is "/copleted" and the todo item is incomplete
_setHashAttr: function(/*String*/ value){
// summary:
// Handler for calls to set("hash", val), to update hidden state given the new value and the completed state.
this.set("hidden", getHiddenState({hash: value, completed: this.completed})); // Update hidden state given the new value and the completed state
this._set("hash", value); // Assign the new value to the attribute
},
_setCompletedAttr: function(/*Boolean*/ value){
// summary:
// Handler for calls to set("completed", val), to update hidden state given the new value and the hash.
this.set("hidden", getHiddenState({hash: this.hash, completed: value})); // Update hidden state given the new value and the hash
this._set("completed", value); // Assign the new value to the attribute
}
});
});
define({
// summary:
// A dojox/mvc data converter, that runs between todo/ctrl/HashController and a widget having <a> tag as its DOM node.
// It does one-way conversion from URL hash to boolean state of whether the URL hash matches href attribute of the widget's DOM node.
format: function(/*String*/ value){
// summary:
// Returns whether given value matches href attribute of the widget's DOM node.
return this.target.domNode.getAttribute("href").substr(1) == (value || "/");
},
parse: function(/*Boolean*/ value){
// summary:
// This functions throws an error so that the new value won't be reflected.
throw new Error();
}
});
define({
// summary:
// A dojox/mvc data converter, that does one-way conversion that returns whether we have less than n todo items in a specific state, where n is the given number in data converter options.
// Data converter options can be specified by setting constraints property in one of data binding endpoints.
// See data converter section of dojox/mvc/sync library's documentation for more details.
format: function(/*Number*/ value, /*Object*/ constraints){
// summary:
// Returns whether given value is less than or equal to the given number in data converter options (default zero).
return value <= (constraints.lessThanOrEqualTo || 0);
},
parse: function(/*Boolean*/ value){
// summary:
// This functions throws an error so that the new value won't be reflected.
throw new Error();
}
});
define([
"dojo/_base/array",
"dojo/_base/lang",
"dojox/mvc/getStateful"
], function(array, lang, getStateful){
var defaultData = {
// summary:
// Data used when there is no saved data.
// The same structure is used to save data to localStrage (when the page is unloaded).
// id: String
// The ID of saved data, used in Dojo Object Store as well as in localStorage that the Dojo Object Store uses.
id: "todos-dojo",
// todos: Object[]
// The todo items.
todos : [],
// incomplete: Number
// The count of incomplete todo items.
incomplete: 0,
// complete: Number
// The count of complete todo items.
complete: 0
};
var seq = 0; // The sequence number to generate unique IDs of todo items
return function(/*Object*/ data){
// summary:
// The data model for TodoMVC.
// description:
// This data model does:
//
// - Keep complete/incomplete properties up to date
// - Add unique ID to new todo items
//
// data: Object
// The plain data, coming from Dojo Object Store.
function assignPropertiesToNewItem(/*dojo/Stateful*/ item){
// summary:
// Add additional properties to a todo item.
// description:
// This function does:
//
// - Add unique ID to the given todo item
// - Add setter function for completed property to the given todo item
// item: dojo/Stateful
// The todo item.
lang.mixin(item, {
_completedSetter: function(/*Boolean*/ value){
// summary
// The setter function for completed property, which keeps complete/incomplete properties of the data model up to date.
// value: Boolean
// True means this todo item is completed. Otherwise means this todo item is incomplete.
if(this.completed ^ value){ // Update is done only if there is a flip in value
// If the todo item is turned to completed state, increase the count of completed items.
// If the todo item is turned to incomplete state, decrease the count of completed items.
statefulData.set("complete", statefulData.get("complete") + (value ? 1 : -1));
}
this.completed = value; // Assign the new value
},
// uniqueId: String
// The unique ID of the todo item.
uniqueId: "TODO-" + seq++
});
}
var statefulData = getStateful(data || defaultData); // Convert a plain object to dojo/Stateful, hierarchically. Use the default data if (lastly saved) data is not there
array.forEach(statefulData.todos, assignPropertiesToNewItem); // Add additional properties to todo items
return lang.mixin(statefulData, {
_elementWatchHandle: statefulData.todos.watchElements(function(/*Number*/ idx, /*dojo/Stateful[]*/ removals, /*dojo/Stateful[]*/adds){
// summary:
// The dojox/mvc/StatefulArray watch callback for adds/removals of todo items.
// description:
// This callback does:
//
// - Keep complete/incomplete properties up to date, upon adds/removals of todo items
// - Add unique ID to new todo items
// - Add setter function for completed property to new todo items
//
// idx: Number
// The array index where adds/removals happened at.
// removals: dojo/Stateful[]
// The removed todo items.
// adds: dojo/Stateful[]
// The added todo items.
var complete = statefulData.get("complete");
array.forEach(removals, function(item){ complete -= !!item.completed; }); // If completed items are removed, decrease the count of completed items
array.forEach(adds, function(item){ complete += !!item.completed; }); // If completed items are added, increase the count of completed items
statefulData.set("complete", complete);
statefulData.set("incomplete", statefulData.todos.get("length") - complete);
array.forEach(adds, assignPropertiesToNewItem); // Add additional properties to newly added todo items
}),
_incompleteSetter: function(/*Number*/ value){
// summary:
// dojo/Stateful setter function for incomplete property.
// description:
// This setter function keeps complete property up to date.
// value: Number
// The new count of incomplete todo items.
var changed = this.incomplete != value;
this.incomplete = value; // Assign the new value
if(changed){ // Update is done only if there is a change in value
this.set("complete", this.todos.get("length") - value); // Make the count of complete items reflect the new count of incomplete items
}
},
_completeSetter: function(/*Number*/ value){
// summary:
// dojo/Stateful setter function for complete property.
// description:
// This setter function keeps incomplete property up to date.
// value: Number
// The new count of complete todo items.
var changed = this.complete != value;
this.complete = value; // Assign the new value
if(changed){ // Update is done only if there is a change in value
this.set("incomplete", this.todos.get("length") - value); // Make the count of incomplete items reflect the new count of complete items
}
},
destroy: function(){
this._elementWatchHandle.remove(); // Stop watching for adds/removals of todo items
}
});
};
});
// This is a build profile for Dojo version of TodoMVC.
// To use this, place todomvc directory at the directory containing dojo/dijit/dojox/util (and follow the procedure of building Dojo).
// Refer to todomvc/architecture-examples/dojo/js/lib/dojo-1.8 and todomvc/architecture-examples/dojo/js/lib/dijit-1.8 to see what built files need to be copied.
dependencies = {
stripConsole: "normal",
selectorEngine:"acme",
layers: [
{
name: "dojo.js",
dependencies: [
"dojo.domReady",
"dojo.parser",
"todo.app18"
]
}
],
prefixes: [
[ "dijit", "../dijit" ],
[ "dojox", "../dojox" ],
[ "doh", "../util/doh" ],
[ "todo", "../todomvc/architecture-examples/dojo/js/todo" ]
]
}
# jQuery TodoMVC app
## Credit
Created by [Sindre Sorhus](https://github.com/sindresorhus)
# rAppid.js TodoMVC app
> rAppid.js is a declarative JavaScript web application for rapid web application development. It uses XML to define the structure of applications, modules, components and views and JavaScript for the business logic of the application. The XML (xaml) gets translated to javascript components during runtime which will render itself as HTML5 DOM elements. This enables a rapid development of applications.
This example app demonstrates the features and abilities of [rAppid.js](http://www.rappidjs.com)
## Documentation
### index.html
In the index.html the app is bootstrapped by defining our main app file (in this case Todo.xml). Because rAppid.js is a RIA framework, the whole rendering is done by JavaScript.
If you are now thinking "yeah fine, but what about SEO", don't worry, rAppid.js also support Node-Rendering, which can be used for things like SEO.
### Todo.xml
The main view of the app is declarated in Todo.xml. The first tag of the Todo.xml defines the super class of our app and the namespaces used inside the app description.
```xml
<?xml version="1.0"?>
<app:TodoClass xmlns="http://www.w3.org/1999/xhtml" xmlns:js="js.core" xmlns:ui="js.ui" xmlns:app="app" xmlns:view="app.view" xmlns:conf="js.conf">
...
</app:TodoClass>
```
As you can see, the default namespace is `"http://www.w3.org/1999/xhtml"` which allows us to use plain HTML elements to describe our view. The other namespaces are used for custom components.
One example of a custom component is the Router configuration.
```xml
<js:Router cid="router">
<conf:Route name="default" route="^$" onexec="showAll"/>
<conf:Route name="active" route="^active$" onexec="showActive"/>
<conf:Route name="completed" route="^completed$" onexec="showCompleted"/>
</js:Router>
```
All routes of the app are defined inside this declaration. The **route** attribute expects a regular expression, which matches the route. The **onexec** attribute defines the function which will be called, if the route is triggered.
The rest of the markup defines the UI of our app. To connect the view with our app model we use bindings. For example the header:
```html
<header id="header">
<h1>{i18n.translate('title')}</h1>
<input id="new-todo" placeholder="{i18n.translate('placeholder')}" onkeyup="addNewTodo" value="{{newTodo.title}}" autofocus="autofocus"/>
</header>
```
The bindings tell the app to hold view and model in sync. If you're interested in more details, checkout the [rAppid.js wiki](http://www.rappidjs.com/#/wiki/Home).
### TodoClass.js
TodoClass.js is the code behind Todo.xml. It initializes the attributes used in this app and it defines the event handlers for routing and UI events. So there is a clean seperation between app code and UI declaration.
In the initialize method inside TodoClass all bound models are created and set as attributes of the app. This is important for resolving the bindings used in the view declaration.
### Todo Model (app/model/Todo.js)
The default attributes for an instance and some methods used inside the app are defined in this model.
It also marks the functions `hasTitle` and `status` as bindable.
```javascript
status: function() {
return this.$.completed ? 'completed' : '';
}.onChange('completed'),
```
Calling the `onChange()` function tells the app that the binding value of this methods has to be refreshed every time the attributes change. See app/view/TodoView.xml for usage.
### Todo List (app/collection/TodoList.js)
The Todo List is a bindable List which encapsulates some app logic for manipulating the todo instances. It also declares bindable functions, which are used inside the view.
### Todo View (app/view/TodoView.xml)
The Todo view is a custom view for displaying and editing Todo instances. Here we define view logic and view declaration in one file.
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