Commit c2abe8ee authored by runawaygo's avatar runawaygo

Merge branch 'master' of git://github.com/addyosmani/todomvc

parents 3dd6ba51 2aef4b5d
/*this doesn't seem to be used in the jquery example at all. Its getting in the way */
#todo-count span {
font-weight: inherit;
}
\ No newline at end of file
html,
body {
margin: 0;
padding: 0;
}
body {
font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eeeeee;
color: #333333;
width: 520px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
}
#todoapp {
background: #fff;
padding: 20px;
margin-bottom: 40px;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-ms-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;
-webkit-border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-ms-border-radius: 0 0 5px 5px;
-o-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
#todoapp h1 {
font-size: 36px;
font-weight: bold;
text-align: center;
padding: 0 0 10px 0;
}
#todoapp input[type="text"] {
width: 466px;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-ms-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;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todo-list {
margin: 10px 0;
padding: 0;
list-style: none;
}
#todo-list li {
padding: 18px 20px 18px 0;
position: relative;
font-size: 24px;
border-bottom: 1px solid #cccccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.done label {
color: #777777;
text-decoration: line-through;
}
#todo-list li .destroy {
display: none;
position: absolute;
top: 20px;
right: 10px;
cursor: pointer;
width: 20px;
height: 20px;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUBAMAAAB/pwA+AAAABGdBTUEAALGPC/xhBQAAACdQTFRFzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMAAAA////zMzMhnu0WAAAAAt0Uk5T5u3pqtV3jFQEKAC0bVelAAAAfUlEQVQI12NYtWpFsc8R865VqxhWrZpyBgg8QcylZ8AgCsjMgTCPrWJYfgYKqhjWwJgaDDVnzpw+c2bPmTPHGWzOnNm95/TuM2cOM/AARXfvBooeZAAp270bRCIz4QoOIGtDMqwJZoUEQzvCYrhzuhhWtUKYEahOX7UK6iEA3A6NUGwCTZIAAAAASUVORK5CYII=') no-repeat center center;
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list li.editing {
border-bottom: none;
margin-top: -1px;
padding: 0;
}
#todo-list li.editing:last-child {
margin-bottom: -1px;
}
#todo-list li.editing .edit {
display: block;
width: 444px;
padding: 13px 15px 14px 20px;
margin: 0;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li .view label {
word-break: break-word;
}
#todo-list li .edit {
display: none;
}
#todoapp footer {
margin: 0 -20px -20px -20px;
overflow: hidden;
color: #555555;
background: #f4fce8;
border-top: 1px solid #ededed;
padding: 0 20px;
line-height: 37px;
-webkit-border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-ms-border-radius: 0 0 5px 5px;
-o-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
#clear-completed {
float: right;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
color: #555555;
font-size: 11px;
margin-top: 8px;
margin-bottom: 8px;
padding: 0 10px 1px;
cursor: pointer;
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
-ms-border-radius: 12px;
-o-border-radius: 12px;
border-radius: 12px;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-ms-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;
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
-webkit-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;
-ms-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;
}
#clear-completed:active {
position: relative;
top: 1px;
}
#todo-count span {
font-weight: bold;
}
#instructions {
margin: 10px auto;
color: #777777;
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
text-align: center;
}
#instructions a {
color: #336699;
}
#credits {
margin: 30px auto;
color: #999;
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
text-align: center;
}
#credits a {
color: #888;
}
\ No newline at end of file
<!doctype html>
<html ng-app="todomvc">
<html lang="en" ng-app="todomvc">
<head>
<meta charset="utf-8">
<title>AngularJS - TodoMVC</title>
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/app.css">
<!--[if IE]>
<script src="../../assets/ie.js"></script>
<![endif]-->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>AngularJS - TodoMVC</title>
<link rel="stylesheet" href="../../assets/base.css">
<!--[if IE]>
<script src="../../assets/ie.js"></script>
<![endif]-->
</head>
<body>
<div ng-controller="TodoController" id="todoapp">
<header>
<h1>Todos</h1>
<form id="todo-form" ng-submit="addTodo()">
<input type="text" id="new-todo" name="newTodo" ng-model="newTodo" placeholder="What needs to be done?">
</form>
</header>
<section id="main" ng-show="todos.length">
<input type="checkbox" id="toggle-all" ng-click="markAllDone()" ng-checked="remainingTodos().length == 0">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<li ng-repeat="todo in todos" ng-dblclick="editTodo(todo)" ng-class="{done: todo.done, editing: todo.editing}">
<div class="view">
<input type="checkbox" class="toggle" name="todo.done" ng-model="todo.done">
<label ng-bind="todo.title">Loading...</label>
<a class="destroy" ng-click="removeTodo(todo)"></a>
</div>
<form ng-submit="finishEditing(todo)">
<input type="text" class="edit" name="todo.title" ng-model="todo.title" my:blur="finishEditing(todo)">
</form>
</li>
</ul>
</section>
<footer ng-show="todos.length">
<a id="clear-completed" ng-click="clearDoneTodos()" ng-show="doneTodos().length" ng-bind-template="Clear {{doneTodos().length}} items"></a>
<div id="todo-count">
<ng-pluralize count="remainingTodos().length" when="todoForms"></ng-pluralize>
</div>
</footer>
</div>
<div id="instructions">
Double-click to edit a todo.
</div>
<div id="credits">
Credits: <a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>, <a href="http://ericbidelman.com">Eric Bidelman</a>
</div>
<script src="../../assets/base.js"></script>
<script src="js/libs/angular/angular.min.js"></script>
<script src="js/controllers.js"></script>
<section id="todoapp" ng-controller="TodoController">
<header id="header">
<h1>todos</h1>
<form id="todo-form" ng-submit="addTodo()">
<input id="new-todo" placeholder="What needs to be done?" type="text" name="newTodo" ng-model="newTodo">
</form>
</header>
<section id="main" ng-show="todos.length">
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-change="markAllDone()" ng-checked="remainingTodos().length == 0">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<li ng-repeat="todo in todos | filter:statusFilter" ng-dblclick="editTodo(todo)" ng-class="{completed: todo.completed, editing: todo.editing}">
<div class="view">
<input class="toggle" type="checkbox" name="todo.completed" ng-model="todo.completed">
<label ng-bind="todo.title">Loading...</label>
<button class="destroy" ng-click="removeTodo(todo)"></button>
</div>
<form ng-submit="finishEditing(todo)">
<input class="edit" type="text" name="todo.title" ng-model="todo.title" todo-blur="finishEditing(todo)" todo-focus="todo.editing">
</form>
</li>
</ul>
</section>
<footer id="footer" ng-show="todos.length">
<span id="todo-count"><strong>{{remainingTodos().length}}</strong> <ng-pluralize count="remainingTodos().length" when="todoForms"></ng-pluralize></span>
<ul id="filters">
<li>
<a ng-class="{selected: location.path() == '/'} " href="#/">All</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/active'}" href="#/active">Active</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/completed'}" href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed" ng-click="clearDoneTodos()" ng-show="completedTodos().length" ng-bind-template="Clear completed ({{completedTodos().length}})"></button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo.</p>
<p>Credits: <a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>, <a href="http://ericbidelman.com">Eric Bidelman</a>, <a href="http://jacobmumm.com">Jacob Mumm</a></p>
</footer>
<script src="../../assets/base.js"></script>
<script src="js/libs/angular/angular.min.js"></script>
<script src="js/controllers/todo.js"></script>
<script src="js/directives/todo-directives.js"></script>
</body>
</html>
\ No newline at end of file
</html>
/* App Controllers */
var todomvc = angular.module('todomvc', []);
todomvc.controller('TodoController',['$scope',function ($scope) {
$scope.todos = retrieveStore();
// Call updateStore() whenever the todos array changes.
$scope.$watch('todos', updateStore, true);
$scope.todoForms = {
0: "You're done!",
one: '{} item left',
other: '{} items left'
};
function retrieveStore() {
var store = localStorage.getItem('todo-angularjs');
return (store && JSON.parse(store)) || [];
};
function updateStore() {
var isEditing = $scope.todos.filter(function(val) {
return val.editing;
}).length;
if (!isEditing) {
localStorage.setItem('todo-angularjs', JSON.stringify($scope.todos));
}
};
$scope.addTodo = function() {
if (this.newTodo.trim().length === 0) {
return;
}
$scope.todos.push({
title: this.newTodo,
done: false,
editing: false
});
this.newTodo = '';
};
$scope.editTodo = function(todo) {
//cancel any active editing operation
$scope.todos.forEach(function(val) {
val.editing = false;
});
todo.editing = true;
};
$scope.finishEditing = function(todo) {
if (todo.title.trim().length === 0) {
$scope.removeTodo(todo);
} else {
todo.editing = false;
}
};
$scope.removeTodo = function(todo) {
for (var i = 0, len = $scope.todos.length; i < len; ++i) {
if (todo === $scope.todos[i]) {
$scope.todos.splice(i, 1);
}
}
};
$scope.remainingTodos = function() {
return $scope.todos.filter(function(val) {
return !val.done;
});
};
$scope.doneTodos = function() {
return $scope.todos.filter(function(val) {
return val.done;
});
}
$scope.clearDoneTodos = function() {
$scope.todos = $scope.remainingTodos();
};
$scope.markAllDone = function() {
var markDone = true;
if (!$scope.remainingTodos().length) {
markDone = false;
}
$scope.todos.forEach(function(todo) {
todo.done = markDone;
});
};
}]);
/* App Controllers */
var todomvc = angular.module('todomvc', []);
function TodoController($scope, $location) {
$scope.todos = retrieveStore();
$scope.newTodo = "";
if($location.path()=='') $location.path('/');
$scope.location = $location;
// Call updateStore() whenever the todos array changes.
$scope.$watch('todos', updateStore, true);
$scope.$watch(function() {return $location.path(); }, function(path) {
$scope.statusFilter = path == '/active' ?
{ completed: false } : path == '/completed' ?
{ completed: true } : null;
});
$scope.todoForms = {
one: 'item left',
other: 'items left'
};
function retrieveStore() {
var store = localStorage.getItem('todos-angularjs');
return (store && JSON.parse(store)) || [];
};
function updateStore() {
var isEditing = $scope.todos.filter(function(val) {
return val.editing;
}).length;
if (!isEditing) {
localStorage.setItem('todos-angularjs', JSON.stringify($scope.todos));
}
};
$scope.addTodo = function() {
if (this.newTodo.trim().length === 0) {
return;
}
$scope.todos.push({
title: this.newTodo,
completed: false,
editing: false
});
this.newTodo = '';
};
$scope.editTodo = function(todo) {
//cancel any active editing operation
$scope.todos.forEach(function(val) {
val.editing = false;
});
todo.editing = true;
};
$scope.finishEditing = function(todo) {
if (todo.title.trim().length === 0) {
$scope.removeTodo(todo);
} else {
todo.editing = false;
}
};
$scope.removeTodo = function(todo) {
for (var i = 0, len = $scope.todos.length; i < len; ++i) {
if (todo === $scope.todos[i]) {
$scope.todos.splice(i, 1);
}
}
};
$scope.remainingTodos = function() {
return $scope.todos.filter(function(val) {
return !val.completed;
});
};
$scope.completedTodos = function() {
return $scope.todos.filter(function(val) {
return val.completed;
});
}
$scope.clearDoneTodos = function() {
$scope.todos = $scope.remainingTodos();
};
$scope.markAllDone = function() {
var markDone = true;
if (!$scope.remainingTodos().length) {
markDone = false;
}
$scope.todos.forEach(function(todo) {
todo.completed = markDone;
});
};
};
todomvc.directive('todoBlur', function() {
return function( scope, elem, attrs ) {
elem.bind('blur', function() {
scope.$apply( attrs.todoBlur );
});
};
});
todomvc.directive('todoFocus', function( $timeout ) {
return function( scope, elem, attrs ) {
scope.$watch( attrs.todoFocus, function( newval ) {
if ( newval ) {
$timeout(function() {
elem[0].focus();
elem[0].select();
}, 0 );
}
});
};
});
This source diff could not be displayed because it is too large. You can view the blob instead.
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;
}
.clickable{
cursor:pointer;
}
#todoapp {
background: none repeat scroll 0 0 white;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2);
background: none repeat scroll 0 0 white;
margin: 0 auto 40px;
padding: 20px;
width: 480px;
}
#todoapp {
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;
}
#todoapp h1 {
font-size: 36px;
font-weight: bold;
text-align: center;
padding: 20px 0 30px 0;
line-height: 1;
}
#create-todo {
position: relative;
}
#todo-form 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-true {
padding: 0;
border-bottom: 0;
}
#todo-list .editing-true .display,
#todo-list .edit {
display: none;
}
#todo-list .editing-true .edit {
display: block;
}
#todo-list .editing-true 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-true .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(../img/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;
background: none repeat scroll 0 0 #F4FCE8;
border-radius: 0 0 5px 5px;
border-top: 1px solid #EDEDED;
color: #555555;
display: block;
line-height: 36px;
margin: 20px -20px -20px;
overflow: hidden;
padding: 0 20px;
}
#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;
}
#todoapp #todo-stats {
background: none repeat scroll 0 0 #F4FCE8;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
border-top: 1px solid #EDEDED;
color: #555555;
line-height: 36px;
margin-top: 10px;
padding: 0 20px;
}
#todoapp #todo-stats .todo-clear a {
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 */
#todoapp #todo-stats .todo-clear a:hover, #todoapp #todo-stats .todo-clear a: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;
}
#todo-stats .todo-clear a {
color: #777777;
font-size: 12px;
}
#todo-stats .todo-clear a:visited {
color: #777777;
}
#todo-stats .todo-clear a:hover, #todo-stats .todo-clear a:focus {
color: #336699;
}
#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);
}
<!doctype html>
<html xmlns:ng="http://angularjs.org/" xmlns:my="http://rx.org">
<html lang="en" ng-app="todomvc">
<head>
<meta charset="utf-8">
<title>AngularJS with PersistenceJS Storage Todo App</title>
<link rel="stylesheet" href="css/app.css">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>AngularJS - TodoMVC</title>
<link rel="stylesheet" href="../../assets/base.css">
<!--[if IE]>
<script src="../../assets/ie.js"></script>
<![endif]-->
</head>
<body>
<div ng:controller="App.Controllers.TodoController" id="todoapp">
<div class="title">
<h1>
Todos
</h1>
</div>
<div class="content">
<div id="todo-form">
</div>
<form id="todo-form" ng:submit="addTodo()">
<input id="new-todo" name="newTodo" my:blur="addTodo()" placeholder="What needs to be done?" type="text">
<span class="ui-tooltip-top" ng:show="showHitEnterHint">
Press Enter to save this task
</span>
</form>
<div id="todos">
<ul id="todo-list">
<li class="todo" ng:class="'editing-' + todo.editing + ' done-' + todo.done" ng:repeat="todo in todos">
<div class="display">
<input ng:change="changeStatus(todo)" class="check" type="checkbox" name="todo.done" / >
<div ng:click="editTodo(todo)" class="todo-content"> {{ todo.content }} </div>
<span class="todo-destroy" ng:click="removeTodo(todo)"></span>
</div>
<div class="edit">
<form ng:submit="finishEditing(todo)">
<input class="todo-input" my:focus="todo.editing" my:blur="finishEditing(todo)" name="todo.content" type="text">
</form>
</div>
</li>
</ul>
</div>
<div id="todo-stats">
<span class="todo-count" ng:show="hasTodos()">
<ng:pluralize count="remainingTodos()" when="{'0' : 'No items left.', '1': '1 item left.', 'other' : '{} items left.' }">
</ng:pluralize>
</span>
<span class="todo-clear" ng:show="hasFinishedTodos()">
<a ng:click="clearCompletedItems()">
Clear <ng:pluralize count="finishedTodos()" when="{'1': '1 completed item', 'other' : '{} completed items' }">
</ng:pluralize>
</a>
</span>
</div>
</div>
</div>
<ul id="instructions">
<li>Click to edit a todo.</li>
</ul>
<div id="credits">
<p>
Originally Created by
<br>
<a href="http://jgn.me/">Jérôme Gravel-Niquet</a>
</p>
<p>
Rewritten to use <a href="http://angularjs.org">AngularJS </a> by
<br>
<a href="http://cburgdorf.wordpress.com/">Christoph Burgdorf</a>
<br>Cleanup, edits: <a href="http://www.linkedin.com/pub/dan-doyon/2/1b0/a83">Dan Doyon</a>
</p>
<p>
Extended for persistent WebSQL storage by <br/>
<a href="http://jacobmumm.com">Jacob Mumm</a><br/>
Using <a href="http://persistencejs.org">PersistenceJS</a>
</p>
</div>
<section id="todoapp" ng-controller="TodoController">
<header id="header">
<h1>todos</h1>
<form id="todo-form" ng-submit="addTodo()">
<input id="new-todo" placeholder="What needs to be done?" type="text" name="newTodo" ng-model="newTodo">
</form>
</header>
<section id="main" ng-show="todos.length">
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-change="markAllDone()" ng-checked="remainingTodos().length == 0">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<li ng-repeat="todo in todos | filter:statusFilter" ng-dblclick="editTodo(todo)" ng-class="{done: todo.completed, editing: todo.editing}">
<div class="view">
<input class="toggle" type="checkbox" name="todo.completed" ng-model="todo.completed" ng-change="toggleDone(todo)">
<label ng-bind="todo.title">Loading...</label>
<button class="destroy" ng-click="removeTodo(todo)"></button>
</div>
<form ng-submit="finishEditing(todo)">
<input class="edit" type="text" name="todo.title" ng-model="todo.title" todo-blur="finishEditing(todo)" todo-focus="todo.editing">
</form>
</li>
</ul>
</section>
<footer id="footer" ng-show="todos.length">
<span id="todo-count"><strong>{{remainingTodos().length}}</strong> <ng-pluralize count="remainingTodos().length" when="todoForms"></ng-pluralize></span>
<ul id="filters">
<li>
<a ng-class="{selected: location.path() == '/'} " href="#/">All</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/active'}" href="#/active">Active</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/completed'}" href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed" ng-click="clearDoneTodos()" ng-show="doneTodos().length" ng-bind-template="Clear completed ({{doneTodos().length}})"></button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo.</p>
<p>Created by <a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>.</p>
<p>Extended to use PersistenceJS for local storage by <a href="http://jacobmumm.com">Jacob Mumm</a>.</p>
</footer>
<script src="../../assets/base.js"></script>
<script src="js/booter.js"></script>
<script src="lib/angular/angular.min.js" ng:autobind></script>
<script src="lib/rx/rx.js"></script>
<script src="lib/rx/rx.angular.js"></script>
<script src="lib/persistence/persistence.js"></script>
<script src="lib/persistence/persistence.store.sql.js"></script>
<script src="lib/persistence/persistence.store.websql.js"></script>
<script src="js/controllers.js"></script>
<script src="js/directive.js"></script>
<script src="js/services.js"></script>
<script src="js/libs/angular/angular.min.js"></script>
<script src="js/libs/persistencejs/persistence.js"></script>
<script src="js/libs/persistencejs/persistence.store.sql.js"></script>
<script src="js/libs/persistencejs/persistence.store.websql.js"></script>
<script src="js/controllers/todo.js"></script>
<script src="js/modules/persistence-service.js"></script>
<script src="js/directives/todo-directives.js"></script>
</body>
</html>
\ No newline at end of file
</html>
/* App Controllers */
App.Controllers.TodoController = function (persistencejs) {
var self = this;
self.newTodo = "";
self.editTodoStartContent = "";
self.addTodo = function() {
if (self.newTodo.length === 0) return;
self.todos.push({
content: self.newTodo,
done: false,
editing: false
});
persistencejs.add(self.newTodo);
self.newTodo = "";
};
self.editTodo = function(todo) {
angular.forEach(self.todos, function(value) {
value.editing = false;
});
todo.editing = true;
self.editTodoStartContent = todo.content;
};
self.changeStatus = function(todo){
persistencejs.changeStatus(todo);
};
self.finishEditing = function(todo) {
todo.editing = false;
persistencejs.edit(self.editTodoStartContent, todo.content);
};
self.removeTodo = function(todo) {
angular.Array.remove(self.todos, todo);
persistencejs.remove(todo);
};
self.todos = [];
var countTodos = function(done) {
return function() {
return angular.Array.count(self.todos, function(x) {
return x.done === (done === "done");
});
}
};
self.remainingTodos = countTodos("undone");
self.finishedTodos = countTodos("done");
self.clearCompletedItems = function() {
var oldTodos = self.todos;
self.todos = [];
angular.forEach(oldTodos, function(todo) {
if (!todo.done) self.todos.push(todo);
});
persistencejs.clearCompletedItems();
};
self.hasFinishedTodos = function() {
return self.finishedTodos() > 0;
};
self.hasTodos = function() {
return self.todos.length > 0;
};
self.loadTodos = function(){
persistencejs.fetchAll(self);
}
self.refresh = function(){ self.$apply(); }
self.loadTodos();
/*
The following code deals with hiding the hint *while* you are typing,
showing it once you did *finish* typing (aka 500 ms since you hit the last key)
*in case* the result is a non empty string
*/
Rx.Observable.FromAngularScope(self, "newTodo")
.Do(function() {
self.showHitEnterHint = false;
})
.Throttle(500)
.Select(function(x) {
return x.length > 0;
})
.ToOutputProperty(self, "showHitEnterHint");
};
App.Controllers.TodoController.$inject = ['persistencejs'];
\ No newline at end of file
/* App Controllers */
var todomvc = angular.module('todomvc', []);
function TodoController($scope, $location, persistencejs) {
$scope.todos = []; loadTodos();
$scope.newTodo = "";
$scope.editTodoStartContent = "";
if($location.path()=='') $location.path('/');
$scope.location = $location;
$scope.$watch(function() {return $location.path(); }, function(path) {
$scope.statusFilter = path == '/active' ?
{ completed: false } : path == '/completed' ?
{ completed: true } : null;
});
function loadTodos() {
persistencejs.fetchAll($scope);
};
$scope.refresh = function(){ $scope.$apply(); }
$scope.todoForms = {
one: 'item left',
other: 'items left'
};
$scope.addTodo = function() {
if (this.newTodo.trim().length === 0) {
return;
}
$scope.todos.push({
title: this.newTodo,
completed: false,
editing: false
});
persistencejs.add(this.newTodo);
this.newTodo = '';
};
$scope.editTodo = function(todo) {
$scope.todos.forEach(function(val) {
val.editing = false;
});
todo.editing = true;
$scope.editTodoStartContent = todo.title;
};
$scope.finishEditing = function(todo) {
if (todo.title.trim().length === 0) {
$scope.removeTodo(todo);
persistencejs.remove(todo);
} else {
todo.editing = false;
persistencejs.edit($scope.editTodoStartContent, todo.title);
}
};
$scope.removeTodo = function(todo) {
for (var i = 0, len = $scope.todos.length; i < len; ++i) {
if (todo === $scope.todos[i]) {
$scope.todos.splice(i, 1);
}
}
persistencejs.remove(todo);
};
$scope.remainingTodos = function() {
return $scope.todos.filter(function(val) {
return !val.completed;
});
};
$scope.completedTodos = function() {
return $scope.todos.filter(function(val) {
return val.completed;
});
}
$scope.clearDoneTodos = function() {
$scope.todos = $scope.remainingTodos();
persistencejs.clearCompletedItems();
};
$scope.toggleDone = function(todo){
persistencejs.changeStatus(todo);
}
$scope.markAllDone = function() {
var markDone = true;
if (!$scope.remainingTodos().length) {
markDone = false;
}
$scope.todos.forEach(function(todo) {
if(todo.completed !== markDone){
persistencejs.changeStatus(todo);
}
todo.completed = markDone;
});
};
};
angular.directive('my:blur', function(expression, compiledElement) {
var compiler = this;
return function(linkElement) {
var scope = this;
linkElement.bind('blur', function(event) {
scope.$apply(expression, linkElement);
event.stopPropagation();
});
};
});
angular.directive("my:focus", function(expression, compiledElement){
return function(element){
this.$watch(expression, function(){
if(angular.formatter.boolean.parse(expression)){
element[0].focus();
}
}, element);
};
});
todomvc.directive('todoBlur', function() {
return function( scope, elem, attrs ) {
elem.bind('blur', function() {
scope.$apply( attrs.todoBlur );
});
};
});
todomvc.directive('todoFocus', function( $timeout ) {
return function( scope, elem, attrs ) {
scope.$watch( attrs.todoFocus, function( newval ) {
if ( newval ) {
$timeout(function() {
elem[0].focus();
elem[0].select();
}, 0 );
}
});
};
});
......@@ -459,7 +459,7 @@ persistence.get = function(arg1, arg2) {
that._data_obj[ref] = session.trackedObjects[that._data[ref]];
return that._data_obj[ref];
} else {
throw new Error("Property '" + ref + "' with id: " + that._data[ref] + " not fetched, either prefetch it or fetch it manually.");
throw new Error("Property '" + ref + "' of '" + meta.name + "' with id: " + that._data[ref] + " not fetched, either prefetch it or fetch it manually.");
}
});
}());
......@@ -1016,7 +1016,12 @@ persistence.get = function(arg1, arg2) {
switch(type) {
case 'DATE':
if(typeof value === 'number') {
return new Date(value * 1000);
if (value > 1000000000000) {
// it's in milliseconds
return new Date(value);
} else {
return new Date(value * 1000);
}
} else {
return null;
}
......@@ -1281,8 +1286,10 @@ persistence.get = function(arg1, arg2) {
var el = ar[i];
if(el.equals && el.equals(item)) {
ar.splice(i, 1);
return;
} else if(el === item) {
ar.splice(i, 1);
return;
}
}
}
......@@ -1627,7 +1634,7 @@ persistence.get = function(arg1, arg2) {
s += '|Order:';
for(var i = 0; i < this._orderColumns.length; i++) {
var col = this._orderColumns[i];
s += col[0] + ", " + col[1];
s += col[0] + ", " + col[1] + ", " + col[2];
}
s += '|Prefetch:';
for(var i = 0; i < this._prefetchFields.length; i++) {
......@@ -1714,12 +1721,16 @@ persistence.get = function(arg1, arg2) {
* Returns a new query collection with an ordering imposed on the collection
* @param property the property to sort on
* @param ascending should the order be ascending (= true) or descending (= false)
* @param caseSensitive should the order be case sensitive (= true) or case insensitive (= false)
* note: using case insensitive ordering for anything other than TEXT fields yields
* undefinded behavior
* @return the query collection with imposed ordering
*/
QueryCollection.prototype.order = function (property, ascending) {
QueryCollection.prototype.order = function (property, ascending, caseSensitive) {
ascending = ascending === undefined ? true : ascending;
caseSensitive = caseSensitive === undefined ? true : caseSensitive;
var c = this.clone();
c._orderColumns.push( [ property, ascending ]);
c._orderColumns.push( [ property, ascending, caseSensitive ]);
return this._session.uniqueQueryCollection(c);
};
......@@ -2055,8 +2066,13 @@ persistence.get = function(arg1, arg2) {
for(var i = 0; i < that._orderColumns.length; i++) {
var col = that._orderColumns[i][0];
var asc = that._orderColumns[i][1];
var sens = that._orderColumns[i][2];
var aVal = persistence.get(a, col);
var bVal = persistence.get(b, col);
if (!sens) {
aVal = aVal.toLowerCase();
bVal = bVal.toLowerCase();
}
if(aVal < bVal) {
return asc ? -1 : 1;
} else if(aVal > bVal) {
......
......@@ -6,7 +6,7 @@ var defaultTypeMapper = {
* SQL type for ids
*/
idType: "VARCHAR(32)",
/**
* SQL type for class names (used by mixins)
*/
......@@ -44,7 +44,12 @@ var defaultTypeMapper = {
switch (type) {
case 'DATE':
// SQL is in seconds and JS in miliseconds
return new Date(parseInt(val, 10) * 1000);
if (val > 1000000000000) {
// usually in seconds, but sometimes it's milliseconds
return new Date(parseInt(val, 10));
} else {
return new Date(parseInt(val, 10) * 1000);
}
case 'BOOL':
return val === 1 || val === '1';
break;
......@@ -124,7 +129,7 @@ function config(persistence, dialect) {
/**
* Synchronize the data model with the database, creates table that had not
* been defined before
*
*
* @param tx
* transaction object to use (optional)
* @param callback
......@@ -147,7 +152,7 @@ function config(persistence, dialect) {
return;
}
var queries = [], meta, colDefs, otherMeta, tableName;
var tm = persistence.typeMapper;
var entityMeta = persistence.getEntityMeta();
for (var entityName in entityMeta) {
......@@ -178,10 +183,10 @@ function config(persistence, dialect) {
var otherMeta = meta.hasMany[rel].type.meta;
var inv = meta.hasMany[rel].inverseProperty;
// following test ensures that mixin mtm tables get created with the mixin itself
// it seems superfluous because mixin will be processed before entitites that use it
// it seems superfluous because mixin will be processed before entitites that use it
// but better be safe than sorry.
if (otherMeta.hasMany[inv].type.meta != meta)
continue;
continue;
var p1 = meta.name + "_" + rel;
var p2 = otherMeta.name + "_" + inv;
queries.push([dialect.createIndex(tableName, [p1]), null]);
......@@ -219,7 +224,7 @@ function config(persistence, dialect) {
/**
* Persists all changes to the database transaction
*
*
* @param tx
* transaction to use
* @param callback
......@@ -276,7 +281,7 @@ function config(persistence, dialect) {
}
});
};
/**
* Remove all tables in the database (as defined by the model)
*/
......@@ -316,7 +321,7 @@ function config(persistence, dialect) {
} else {
cb();
}
function cb(result, err) {
session.clean();
persistence.generatedTables = {};
......@@ -372,7 +377,7 @@ function config(persistence, dialect) {
obj._dirtyProperties[p] = true;
}
}
}
}
for ( var p in obj._dirtyProperties) {
if (obj._dirtyProperties.hasOwnProperty(p)) {
properties.push("`" + p + "`");
......@@ -389,7 +394,7 @@ function config(persistence, dialect) {
}
}
executeQueriesSeq(tx, additionalQueries, function() {
if (properties.length === 0) { // Nothing changed
if (!obj._new && properties.length === 0) { // Nothing changed and not new
if(callback) callback();
return;
}
......@@ -455,7 +460,7 @@ function config(persistence, dialect) {
/////////////////////////// QueryCollection patches to work in SQL environment
/**
* Function called when session is flushed, returns list of SQL queries to execute
* Function called when session is flushed, returns list of SQL queries to execute
* (as [query, arg] tuples)
*/
persistence.QueryCollection.prototype.persistQueries = function() { return []; };
......@@ -568,7 +573,7 @@ function config(persistence, dialect) {
/**
* Asynchronous call to actually fetch the items in the collection
* @param tx transaction to use
* @param callback function to be called taking an array with
* @param callback function to be called taking an array with
* result objects as argument
*/
persistence.DbQueryCollection.prototype.list = function (tx, callback) {
......@@ -590,7 +595,7 @@ function config(persistence, dialect) {
var entityName = this._entityName;
var meta = persistence.getMeta(entityName);
var tm = persistence.typeMapper;
// handles mixin case -- this logic is generic and could be in persistence.
if (meta.isMixin) {
var result = [];
......@@ -609,7 +614,7 @@ function config(persistence, dialect) {
query.list(null, callback);
});
return;
}
}
function selectAll (meta, tableAlias, prefix) {
var selectFields = [ tm.inIdVar("`" + tableAlias + "`.id") + " AS " + prefix + "id" ];
......@@ -670,7 +675,7 @@ function config(persistence, dialect) {
sql += " ORDER BY "
+ this._orderColumns.map(
function (c) {
return "`" + mainPrefix + c[0] + "` "
return (c[2] ? "`" : "LOWER(`") + mainPrefix + c[0] + (c[2] ? "` " : "`) ")
+ (c[1] ? "ASC" : "DESC");
}).join(", ");
}
......@@ -705,7 +710,7 @@ function config(persistence, dialect) {
};
/**
* Asynchronous call to remove all the items in the collection.
* Asynchronous call to remove all the items in the collection.
* Note: does not only remove the items from the collection, but
* the items themselves.
* @param tx transaction to use
......@@ -726,7 +731,7 @@ function config(persistence, dialect) {
that.destroyAll(tx, callback);
});
return;
}
}
var entityName = this._entityName;
var meta = persistence.getMeta(entityName);
var tm = persistence.typeMapper;
......@@ -739,7 +744,7 @@ function config(persistence, dialect) {
query.destroyAll(tx, callback);
}, callback);
return;
}
}
var joinSql = '';
var additionalWhereSqls = this._additionalWhereSqls.slice(0);
......@@ -789,13 +794,13 @@ function config(persistence, dialect) {
if(tx && !tx.executeSql) { // provided callback as first argument
callback = tx;
tx = null;
}
}
if(!tx) { // no transaction supplied
session.transaction(function(tx) {
that.count(tx, callback);
});
return;
}
}
var entityName = this._entityName;
var meta = persistence.getMeta(entityName);
var tm = persistence.typeMapper;
......@@ -814,7 +819,7 @@ function config(persistence, dialect) {
callback(result);
});
return;
}
}
var joinSql = '';
var additionalWhereSqls = this._additionalWhereSqls.slice(0);
......@@ -862,14 +867,14 @@ function config(persistence, dialect) {
vars.push("?");
args.push(inverseMeta.name);
}
queries.push(["INSERT INTO " + rel.tableName +
queries.push(["INSERT INTO " + rel.tableName +
" (`" + columns.join("`, `") + "`) VALUES (" + vars.join(",") + ")", args]);
}
this._localAdded = [];
// Removed
for(var i = 0; i < this._localRemoved.length; i++) {
queries.push(["DELETE FROM " + rel.tableName +
" WHERE `" + direct + "_" + this._coll + "` = " + tm.outIdVar("?") + " AND `" +
queries.push(["DELETE FROM " + rel.tableName +
" WHERE `" + direct + "_" + this._coll + "` = " + tm.outIdVar("?") + " AND `" +
inverse + '_' + rel.inverseProperty +
"` = " + tm.outIdVar("?"), [tm.entityIdToDbId(this._obj.id), tm.entityIdToDbId(this._localRemoved[i].id)]]);
}
......
angular.service('persistencejs', function() {
persistence.store.websql.config(persistence, 'todo-angular-persistence', 'todo database', 5*1024*1024);
todomvc.factory('persistencejs', function(){
persistence.store.websql.config(persistence, 'todos-angularjs', 'todo database', 5*1024*1024);
var Todo = persistence.define('todo', {
content: 'TEXT',
done: 'BOOL'
title: 'TEXT',
completed: 'BOOL'
});
persistence.schemaSync();
return {
add: function(item){
var t = new Todo();
t.content = item;
t.done = false;
t.title = item;
t.completed = false;
persistence.add(t);
persistence.flush();
},
edit: function(startContent, endContent){
Todo.all().filter('content','=',startContent).one(function(item){
item.content = endContent;
persistence.flush();
edit: function(oldTitle, newTitle){
Todo.all().filter('title','=',oldTitle).one(function(item){
if(item){
item.title = newTitle;
persistence.flush();
}
});
},
changeStatus: function(item){
Todo.all().filter('content','=',item.content).one(function(todo){
todo.done = item.done;
Todo.all().filter('title','=',item.title).one(function(todo){
todo.completed = item.completed;
persistence.flush();
});
},
clearCompletedItems: function(){
Todo.all().filter('done','=',true).destroyAll();
Todo.all().filter('completed','=',true).destroyAll();
},
remove: function(item){
Todo.all().filter('content','=',item.content).destroyAll();
Todo.all().filter('title','=',item.title).destroyAll();
},
fetchAll: function(controller){
......@@ -42,8 +44,8 @@ angular.service('persistencejs', function() {
var todos = [];
items.forEach(function(item){
todos.push({
content: item.content,
done: item.done,
title: item.title,
completed: item.completed,
editing: false
});
if(--itemCount == 0){
......
(function () {
var global = this,
root = (typeof ProvideCustomRxRootObject == "undefined") ? global.Rx : ProvideCustomRxRootObject();
var observable = root.Observable;
var observableCreate = observable.Create;
observable.FromAngularScope = function (angularScope, propertyName) {
return observableCreate(function (observer) {
var unwatch = angularScope.$watch(function(){
return angularScope[propertyName];
},
function(){
observer.OnNext(angularScope[propertyName]);
});
return function () {
unwatch();
};
})
.Skip(1); //In AngularJS 0.10.x There is no way to avoid initial evaluation. So we take care about it!
};
observable.prototype.ToOutputProperty = function (scope, propertyName) {
var disposable = this.Subscribe(function (data) {
scope[propertyName] = data;
scope.$apply();
});
scope.$on('$destroy', function(event){
//we need to asure that we only dispose the observable when it's our scope that
//was destroyed.
//TODO: Figure out if thats enough to asure the above (e.g what happens when
//a child scope will be destroyed but ours won't be affected. Or the other way around,
//if a higher scope will be destroyed (and therefore ours as well) does it mean that $destroy()
//will be also called on our scope or will our scope get destroyed without actually
//calling $destroy() on it?
if (event.targetScope === scope){
disposable.Dispose();
}
});
};
})();
\ No newline at end of file
(The MIT License)
Copyright (c) 2012 M Krejpowicz <krebbl@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
# Simple Todo App build with rAppid.js
\ No newline at end of file
# Simple Todo App build with rAppid.js
This example app demonstrates the features and abilities of rAppid.js
## Documentation
### The index.html
In the index.html the application is bootStrapped by defining our main application 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.
### The application file Todo.xml
The main view of the application is declarated in Todo.xml. The first tag of the Todo.xml defines the super class of our application and the namespaces used inside the application 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>
```
Inside this declaration all routes of the application are defined. 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 application.
To connect the view with our application 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')}" type="text" onkeyup="addNewTodo"
value="{{newTodo.title}}" autofocus="autofocus"/>
</header>
```
The bindings tell the application to hold view and model in sync. If you're interested in more details, checkout the rAppid.js wiki.
### The code behind file TodoClass.js
The TodoClass.js is the code behind file for Todo.xml. It initializes the attributes used in this application and it defines the event handlers for routing and ui events.
So there is a clean seperation between application code and ui declaration.
In the initialize method inside TodoClass all binded models are created and set as attributes of the application. This is important for resolving the bindings used in the view declaration.
### The Todo Model (app/model/Todo.js)
In this model, the default attributes for an instance and some methods used inside the application are defined.
It also marks the functions `hasTitle` and `status` as bindable.
```javascript
status: function () {
return this.$.completed ? "done" : '';
}.onChange("completed"),
```
Calling the `onChange(...)` function tells the application that the binding value of this methods has to be refreshed everytime the attributes change.
See app/view/TodoView.xml for usage.
### The Todo List (app/collection/TodoList.js)
The Todo List is a bindable List which encapsulates some application logic for manipulating the todo instances.
It also declares bindable functions, which are used inside the view ...
### The 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.
<?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">
<js:Injection>
<js:I18n locale="en_EN"/>
</js:Injection>
<div class="container">
<div id="todoapp">
<div class="language">
<select items="{locales}" selectedItem="{{i18n.locale}}">
<js:Template name="item">
<option value="{$item}">{i18n.translate({$item})}</option>
</js:Template>
</select>
</div>
xmlns:js="js.core" xmlns:ui="js.ui" xmlns:app="app" xmlns:view="app.view" xmlns:conf="js.conf"
xmlns:data="js.data">
<!-- LOCAL STORAGE CONFIGURATION -->
<data:LocalStorageDataSource cid="dataSource" name="todo-rappidjs">
<conf:Configuration path="todos" modelClassName="app.model.Todo"
collectionClassName="app.collection.TodoList"/>
</data:LocalStorageDataSource>
<div class="title">
<h1>{i18n.translate(title)}</h1>
</div>
<div class="content">
<div id="create-todo">
<!-- HERE WE USE TWO WAY BINDING TO SET THE CONTENT OF THE NEW TODO -->
<input id="new-todo" placeholder="{i18n.translate(placeholder)}" type="text" onkeyup="addNewTodo"
value="{{newTodo.content}}"/>
<!-- EACH ELEMENT WITH A CID IS REGISTERED IN THE ROOT SCOPE -->
<span class="ui-tooltip-top" cid="hint" visible="false">{i18n.translate(hint)}</span>
</div>
<!-- ROUTER CONFIGURATION -->
<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>
<div id="todo-list">
<div visible="{todoList.hasItems()}">
<input class="check mark-all-done" type="checkbox" onclick="markAllComplete" id="check-all"
checked="{todoList.areAllComplete()}"/>
<label for="check-all">{i18n.translate(markAllAsComplete)}</label>
</div>
<!-- CUSTOM UI COMPONENT FOR RENDERING A LIST OF ITEMS -> TODOs -->
<ui:ItemsView list="{todoList}" tagName="ul" id="todoList">
<js:Template name="item">
<view:TodoView todo="{$item}" tagName="li" onremove="removeTodo"/>
</js:Template>
</ui:ItemsView>
</div>
<div id="todo-stats">
<span class="todo-count" data-num="{todoList.numOpenTodos()}">
<span class="number">{data-num}</span>
{i18n.translate({data-num},item)} {i18n.translate(left)}.
</span>
<span class="todo-sort" visible="{todoList.hasItems()}">
<a href="javascript: void 0;" onclick="sort">
{i18n.translate(sortTodos)}
</a>
</span>
<span class="todo-clear" visible="{todoList.hasCompletedTodos()}">
<!-- FOR THE ITEMS STRING WE USE A FUNCTION WHICH GETS A NUMBER OF ITEMS AS INPUT -->
<a href="javascript: void 0;" onclick="clearCompleted"
data-num="{todoList.numCompletedTodos()}">
{i18n.translate(clear)}
<span class="number-done">{data-num}</span>
{i18n.translate(completed)}
<span class="word-done">
{i18n.translate({data-num},item)}
</span>
</a>
</span>
</div>
</div>
</div>
<div id="credits">
{i18n.translate(createdBy)}
<a href="http://github.com/krebbl">Marcus Krejpowicz</a>
<br/>
{i18n.translate(with)}
<a href="http://rappidjs.com">&lt;rAppid:js /&gt;</a>.
<!-- HERE WE START TO DEFINE THE UI WITH HTML AND CUSTOM UI COMPONENTS -->
<section id="todoapp">
<header id="header">
<h1>Todos</h1>
<input id="new-todo" placeholder="What needs to be done?" type="text" onkeyup="addNewTodo" autofocus="autofocus"/>
</header>
<section id="main" visible="{todoList.hasItems()}">
<input id="toggle-all" type="checkbox" onclick="markAllComplete"
checked="{todoList.areAllComplete()}"/>
<label for="toggle-all">Mark all as complete</label>
<!-- CUSTOM UI COMPONENT FOR RENDERING A LIST OF ITEMS -> TODOs -->
<ui:ItemsView items="{filterList.list}" tagName="ul" id="todo-list" itemKey="todo">
<js:Template name="item">
<view:TodoView todo="{$todo}" tagName="li" onremove="removeTodo"/>
</js:Template>
</ui:ItemsView>
</section>
<footer id="footer" data-num="{todoList.numOpenTodos()}" visible="{todoList.hasItems()}">
<span id="todo-count">
<strong>{data-num}</strong> {translateItems({data-num})} left
</span>
<ul id="filters">
<li>
<a href="#/" class="{selectedClass('all',{filterList.filter})}">All</a>
</li>
<li>
<a href="#/active" class="{selectedClass('active',{filterList.filter})}">Active</a>
</li>
<li>
<a href="#/completed" class="{selectedClass('completed',{filterList.filter})}">Completed</a>
</li>
</ul>
<button id="clear-completed" onclick="clearCompleted" visible="{todoList.hasCompletedTodos()}">
Clear completed ({todoList.numCompletedTodos()})
</button>
</footer>
</section>
<footer id="info">
<p visible="{todoList.hasItems()}">Double - click to edit a todo</p>
<p>Template by <a href="http://github.com/sindresorhus">Sindre Sorhus</a></p>
<p>
Created by <a href="http://github.com/krebbl">Marcus Krejpowicz</a> with<a href="http://rappidjs.com">&lt; rAppid:js/&gt;</a>
<br/>
{i18n.translate(creditsTo)} <a href="http://github.com/it-ony">Tony Findeisen</a>.
</div>
</div>
Big credits to <a href="http://github.com/it-ony">Tony Findeisen</a>
</p>
<p>Part of
<a href="http://todomvc.com">TodoMVC</a>
</p>
</footer>
</app:TodoClass>
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
define(["js/core/Application", "js/core/I18n", "app/model/Todo", "app/collection/TodoList", "js/data/FilterDataView", "js/data/LocalStorageDataSource"],
function (Application, I18n, Todo, TodoList, FilterDataView, DataSource) {
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("app.TodoClass",
["js.core.Application", "js.core.I18n", "app.model.Todo", "app.collection.TodoList"],
function (Application, I18n, Todo, TodoList) {
var ENTER_KEY = 13;
return Application.inherit({
inject:{
i18n:I18n
},
/**
* Initializes the app
* In this method we set the initial models
*/
initialize:function () {
this.set("todoList", new TodoList());
this.set("newTodo", new Todo());
this.set("locales",["en_EN","de_DE"]);
},
/**
* The rest is just controller stuff
*/
addNewTodo:function (e) {
if (e.$.keyCode === 13) {
var tmp = this.get("newTodo");
if (tmp.hasContent()) {
var newTodo = new Todo({content:tmp.get("content")});
newTodo.setDone(false);
this.get("todoList").add(newTodo);
tmp.set("content", "");
}
this.get("hint").set("visible", false);
} else {
// some hint stuff
var hint = this.get("hint");
if (!hint.get("visible")) {
setTimeout(function () {
hint.set("visible", true);
return Application.inherit("app.TodoClass", {
/**
* Initializes the app
* In this method we set the initial models
*/
initialize: function () {
this.set("todoList", null);
this.set("filterList", null);
this.callBase();
},
/**
* Are triggered
*/
showAll: function () {
this.$.filterList.set("filter", 'all');
},
showActive: function () {
this.$.filterList.set("filter", "active");
},
showCompleted: function () {
this.$.filterList.set("filter", "completed");
},
/**
* The rest is just controller stuff
*/
addNewTodo: function (e) {
if (e.domEvent.keyCode === ENTER_KEY) {
var title = e.target.get("value").trim();
if (title) {
var newTodo = this.$.dataSource.createEntity(Todo);
newTodo.set({title: title, completed: false});
this.get("todoList").add(newTodo);
setTimeout(function () {
hint.set("visible", false);
}, 2000);
}, 400);
}
// save the new item
newTodo.save();
e.target.set('value','');
}
}
},
markAllComplete: function (e) {
this.get("todoList").markAll(e.target.$el.checked);
},
clearCompleted: function () {
this.get("todoList").clearCompleted();
},
removeTodo: function (e) {
var todo = e.$, self = this;
todo.remove(null, function(err){
if(!err){
self.get("todoList").remove(todo);
}
},
markAllComplete:function (e, input) {
this.get("todoList").markAll(input.get("checked"));
},
clearCompleted:function (e) {
this.get("todoList").clearCompleted();
},
removeTodo:function (e, el) {
this.get("todoList").remove(e.$);
},
sort:function () {
this.get("todoList").sort(function (t1, t2) {
if (t1.get("isDone") && t2.get("isDone")) {
return 0;
} else if (t1.get("isDone") === true && !t2.get("isDone")) {
return 1;
});
},
/**
* Start the application and render it to the body ...
*/
start: function (parameter, callback) {
this.set('todoList', this.$.dataSource.createCollection(TodoList));
// fetch all todos, can be done sync because we use localStorage
this.$.todoList.fetch();
this.set('filterList', new FilterDataView({
baseList: this.get("todoList"),
filter: 'all',
filterFnc: function (item) {
var filter = this.$.filter;
if (filter == "active") {
return !item.isCompleted();
} else if (filter == "completed") {
return item.isCompleted();
} else {
return -1;
return true;
}
});
},
/**
* Start the application and render it to the body ...
*/
start:function (parameter, callback) {
// false - disables autostart
this.callBase(parameter, false);
this.$.i18n.set("locale","en_EN",{silent: true});
this.$.i18n.loadLocale("en_EN", callback);
}
});
}
);
});
\ No newline at end of file
}})
);
// false - disables autostart
this.callBase();
},
translateItems: function(num){
return (num === 1) ? "item" : "items";
},
selectedClass: function (expected, current) {
return expected == current ? "selected" : "";
}
});
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
define(["js/data/Collection", "app/model/Todo", "flow"], function (Collection, Todo, flow) {
return Collection.inherit("app.collection.TodoList", {
$modelFactory: Todo,
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("app.collection.TodoList", ["js.core.List"], function (List) {
return List.inherit({
markAll:function (done) {
this.each(function (todo) {
todo.setDone(done);
});
},
areAllComplete:function () {
if(this.$items.length == 0) return false;
for (var i = 0; i < this.$items.length; i++) {
if (!this.$items[i].get("isDone")) {
return false;
}
markAll: function (done) {
this.each(function (todo) {
todo.setCompleted(done);
todo.save();
});
},
areAllComplete: function () {
if (this.$items.length === 0) {
return false;
}
for (var i = 0; i < this.$items.length; i++) {
if (!this.$items[i].isCompleted()) {
return false;
}
return true;
}.on('change','add','remove'),
clearCompleted:function () {
console.log("clear completed");
for (var i = this.$items.length-1; i >= 0; i--) {
if (this.$items[i].get("isDone")) {
this.removeAt(i);
}
}
return true;
}.on('change', 'add', 'remove'),
clearCompleted: function () {
var self = this;
// remove all completed todos in a sequence
flow().seqEach(this.$items,function (todo, cb) {
if (todo.isCompleted()) {
// remove the todo
todo.remove(null, function (err) {
if (!err) {
self.remove(todo);
}
cb(err);
});
} else {
cb();
}
},
numOpenTodos:function () {
var num = 0;
for (var i = 0; i < this.$items.length; i++) {
if (!this.$items[i].get("isDone")) {
num++;
}
}).exec();
},
numOpenTodos: function () {
var num = 0;
for (var i = 0; i < this.$items.length; i++) {
if (!this.$items[i].isCompleted()) {
num++;
}
return num;
}.on('change','add','remove'),
numCompletedTodos: function(){
var num = 0;
for (var i = 0; i < this.$items.length; i++) {
if (this.$items[i].get("isDone")) {
num++;
}
}
return num;
}.on('change', 'add', 'remove'),
numCompletedTodos: function () {
var num = 0;
for (var i = 0; i < this.$items.length; i++) {
if (this.$items[i].isCompleted()) {
num++;
}
return num;
}.on('change', 'add', 'remove'),
hasCompletedTodos: function(){
return this.numCompletedTodos() > 0;
}.on('change','add','remove')
});
}
return num;
}.on('change', 'add', 'remove'),
hasCompletedTodos: function () {
return this.numCompletedTodos() > 0;
}.on('change', 'add', 'remove')
});
});
\ No newline at end of file
{
"language" : "Sprache",
"title":"Aufgaben",
"placeholder":"Was soll erledigt werden?",
"hint":"Drücke ENTER, um die Aufgabe anzulegen",
"markAllAsComplete":"Markiere alle als erledigt",
"item":"Aufgabe",
"item_plural":"Aufgaben",
"left":"offen",
"clear":"Entferne",
"completed":"erledigte",
"sortTodos":"Sortieren",
"de_DE" : "Deutsch",
"en_EN" : "Englisch",
"createdBy":"Erstellt von",
"with":"mit",
"creditsTo":"Vielen Dank an"
}
\ No newline at end of file
{
"language" : "Language",
"title":"Todos",
"placeholder":"What needs to be done?",
"hint":"Press Enter to save this task",
"markAllAsComplete":"Mark all as complete",
"item":"item",
"item_plural":"items",
"left":"left",
"clear":"Clear",
"completed":"completed",
"sortTodos":"Sort todos",
"de_DE":"German",
"en_EN":"English",
"createdBy" : "Created by",
"with":"with",
"creditsTo" : "Big credits to"
}
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("app.model.Todo", ["js.core.Bindable"], function (Bindable) {
return Bindable.inherit({
defaults:{
content:"",
isDone: false
},
setDone:function (done) {
this.set("isDone", done);
},
status: function(){
if(this.$.isDone){
return "done";
}else{
return "";
}
}.onChange("isDone","isEditing"),
hasContent:function () {
return this.$.content.length > 0;
}.onChange("content")
});
define(["js/data/Model"], function (Model) {
return Model.inherit("app.model.Todo", {
defaults: {
title: "",
completed: false
},
setCompleted: function (completed) {
this.set("completed", completed);
},
isCompleted: function () {
return this.$.completed;
},
status: function () {
return this.$.completed ? "completed" : '';
}.onChange("completed"),
hasTitle: function () {
return this.$.title.trim().length;
}.onChange("title")
});
});
\ No newline at end of file
<ui:View xmlns="http://www.w3.org/1999/xhtml"
xmlns:js="js.core" xmlns:ui="js.ui">
xmlns:js="js.core" xmlns:ui="js.ui" componentClass="{todo.status()}">
<js:Script>
<![CDATA[
(function (Base, Bindable, Content) {
(function () {
var ENTER_KEY = 13;
var INPUT_BLUR = "blur";
return {
defaults:{
componentClass: "todo",
defaults: {
editing: false
},
events: [
"remove"
],
editTodo:function (e, div) {
var todo = div.find("$item");
this.set("editing",true);
e.$.stopPropagation();
// we don't use jquery, so we have to select this the old fashioned way
div.$el.parentNode.parentNode.getElementsByTagName("input")[1].select();
$classAttributes: ['todo', 'inputElement'],
events: ["remove"],
editTodo: function (e) {
this.set("editing", true);
e.preventDefault();
this.$.inputElement.$el.select();
return false;
},
checkTodo:function (e, input) {
checkTodo: function () {
var todo = this.get("todo");
todo.setDone(!todo.get("isDone"));
todo.setCompleted(!todo.isCompleted());
todo.save();
},
preventEditing: function(e){
e.stopPropagation();
},
updateTodo:function (e, input) {
updateTodo: function (e) {
var todo;
if (e.$.keyCode === 13) {
todo = this.get("todo");
this.set("editing",false);
} else if (e.$.keyCode === 27) {
todo = this.get("todo");
var content = todo.get("content");
input.$el.blur();
todo.set("content", content);
this.set("editing",false);
} else if (e.$.type === "blur") {
if (e.domEvent.keyCode === ENTER_KEY || e.domEvent.type === INPUT_BLUR) {
todo = this.get("todo");
this.set("editing",false);
if (!todo.hasTitle()) {
this.trigger("remove", todo);
} else {
this.set("editing", false);
todo.save();
}
}
},
triggerOnRemove: function(e, el){
this.trigger("onremove",this.get("todo"));
triggerOnRemove: function () {
this.trigger("remove", this.get("todo"));
},
_renderEditing: function(editing){
if(editing){
_renderEditing: function (editing) {
if (editing) {
this.addClass("editing");
}else{
} else {
this.removeClass("editing");
this.$.inputElement.$el.blur();
}
},
trim: function(title){
if(title){
return title.trim();
}
return "";
}
}
......@@ -57,19 +62,14 @@
]]>
</js:Script>
<js:Template name="layout">
<div class="{todo.status()}">
<div class="display">
<input class="check" type="checkbox" onchange="checkTodo"
checked="{todo.isDone}"/>
<label class="todo-content" ondblclick="editTodo">{todo.content}</label>
<span class="todo-destroy" onclick="triggerOnRemove"></span>
</div>
<div class="edit">
<input class="todo-input" type="text" value="{{todo.content}}"
onkeyup="updateTodo" onblur="updateTodo"/>
<div class="view" ondblclick="editTodo">
<input class="toggle" type="checkbox" onclick="checkTodo" ondblclick="preventEditing"
checked="{todo.completed}"/>
<label>{todo.title}</label>
<button class="destroy" onclick="triggerOnRemove"/>
</div>
</div>
<input class="edit" cid="inputElement" type="text" value="{{todo.title|trim()}}"
onkeyup="updateTodo" onblur="updateTodo" updateOnEvent="change"/>
</js:Template>
</ui:View>
{
"xamlClasses": ["js.ui.Link", "js.ui.MenuButton", "js.ui.SplitButton" , "js.ui.TabView", "app.view.TodoView"],
"xamlClasses": ["app/Todo", "app/view/TodoView", "js/ui/ButtonGroup", "js/ui/Link", "js/ui/List", "js/ui/MenuButton", "js/ui/Modal", "js/ui/ScrollView", "js/ui/SplitButton", "js/ui/TabView"],
"namespaceMap": null,
"rewriteMap": null
}
"rewriteMap": null,
"paths": {
"xaml": "js/plugins/xaml",
"json": "js/plugins/json",
"raw": "js/plugins/raw",
"flow": "js/lib/flow",
"inherit": "js/lib/inherit",
"underscore": "js/lib/underscore",
"JSON": "js/lib/json2.js"
}, "shim": {
"inherit": {
"exports": "inherit"
},
"flow": {
"exports": "flow"
},
"underscore": {
"exports": "_"
},
"js/lib/parser": {
"exports": "parser"
},
"JSON": {
"exports": "JSON"
}
}}
\ No newline at end of file
.hide {
display: none;
}
\ No newline at end of file
This diff is collapsed.
<!DOCTYPE HTML>
<html>
<head>
<title>Todo</title>
<link rel="stylesheet" href="css/todo.css"/>
<script type="text/javascript" src="lib/require.js"></script>
<script type="text/javascript" src="lib/inherit.js"></script>
<script type="text/javascript" src="lib/underscore-min.js"></script>
<script type="text/javascript" src="lib/flow.js"></script>
<script type="text/javascript" src="lib/rAppid.js"></script>
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<title>TodoMVC build with rAppid.js</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<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]-->
<script type="text/javascript" src="package/rappidjs.min.js"></script>
<script type="text/javascript">
rAppid.bootStrap("app/Todo.xml", "config.json",
function (err, systemManager, application) {
if (!err) {
application.start(null, function () {
application.render(document.body);
});
} else {
console.warn(err);
}
});
</script>
</head>
<body>
<script type="text/javascript">
rAppid.bootStrap("app/Todo.xml", "config.json",
function (err, systemManager, application) {
if (!err) {
application.start(null, function () {
application.render(document.body);
});
} else {
console.warn(err);
}
});
</script>
</body>
<body></body>
</html>
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.Application",
["js.core.UIComponent", "js.core.History"], function (UIComponent, History) {
return UIComponent.inherit({
ctor: function () {
this.history = new History();
this.callBase();
},
initialize: function () {
// set up application wide vars
this.callBase();
},
_inject: function() {
// overwrite and call inside start
},
_initializeDescriptors: function() {
this.callBase();
UIComponent.prototype._inject.call(this);
},
/**
* Method called, when application is initialized
*
* @param {Object} parameter
* @param {Function} callback
*/
start: function (parameter, callback) {
this.history.start();
if (callback) {
callback(null);
}
},
render: function (target) {
var dom = this.callBase(null);
if (target) {
target.appendChild(dom);
}
return dom;
},
toString: function () {
return "js.core.Application";
}
});
}
);
});
\ No newline at end of file
// defined under rAppid.js
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.Bindable", ["js.core.EventDispatcher"],
/**
* @export js.core.Bindable
*/
function (EventDispatcher) {
/**
* @class js.core.Bindable
* @extends js.core.EventDispatcher
*/
var Bindable = EventDispatcher.inherit({
ctor: function (attributes) {
// call the base class constructor
this.callBase(null);
this.$ = {};
rAppid._.extend(this._eventAttributes, this.base._eventAttributes || {});
attributes = attributes || {};
rAppid._.defaults(attributes, this._defaultAttributes());
this.$ = attributes;
this.$previousAttributes = rAppid._.clone(attributes);
var self = this, fnc;
var bind = function(key, targetKey, method){
self.bind('change:' + key, function () {
self.set(targetKey, method.call(self));
});
};
},
defaults: {
},
_defaultAttributes: function () {
return this._generateDefaultsChain("defaults");
},
_generateDefaultsChain: function (property) {
var ret = this[property],
base = this.base;
while (base) {
rAppid._.defaults(ret, base[property]);
base = base.base;
}
// clone all attributes
for(var k in ret){
if(ret.hasOwnProperty(k) && !rAppid._.isFunction(ret[k])){
ret[k] = rAppid._.clone(ret[k]);
}
}
return ret;
},
/**
*
* @param key
* @param value
* @param options
*/
set: function (key, value, options) {
var attributes = {};
if (rAppid._.isString(key)) {
// check for path
var path = key.split(".");
if (path.length > 1) {
var scope = this.get(path.shift());
if (scope && scope.set) {
scope.set(path.join("."), value, options);
return this;
}
}
attributes[key] = value;
} else {
options = value;
attributes = key;
}
options = options || {silent: false, unset: false};
// for unsetting attributes
if (options.unset) {
for (key in attributes) {
attributes[key] = void 0;
}
}
var changedAttributes = {},
equal = true,
now = this.$,
val;
for (key in attributes) {
if (attributes.hasOwnProperty(key)) {
// get the value
val = attributes[key];
// unset attribute or change it ...
if (options.unset === true) {
delete now[key];
} else {
if (!rAppid._.isEqual(now[key], attributes[key])) {
this.$previousAttributes[key] = now[key];
now[key] = attributes[key];
changedAttributes[key] = now[key];
}
}
// if attribute has changed and there is no async changing process in the background, fire the event
}
}
this._commitChangedAttributes(changedAttributes);
if (options.silent === false && rAppid._.size(changedAttributes) > 0) {
for (key in changedAttributes) {
if (changedAttributes.hasOwnProperty(key)) {
this.trigger('change:' + key, changedAttributes[key], this);
}
}
this.trigger('change', changedAttributes, this);
}
return this;
},
get: function (key) {
//var path = key.split(".");
return this.$[key];
/*
var key;
while (path.length > 0 && prop != null) {
key = path.shift();
if (prop instanceof Bindable) {
prop = prop.get(key);
} else if (typeof(prop[key]) !== "undefined") {
prop = prop[key];
} else {
return null;
}
} */
},
has: function(key){
return typeof(this.$[key]) !== "undefined" && this.$[key] !== null;
},
_commitChangedAttributes: function (attributes) {
},
unset: function (key, options) {
(options || (options = {})).unset = true;
return this.set(key, null, options);
},
clear: function () {
return this.set(this.$, {unset: true});
}
});
return Bindable;
});
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.Binding", ["js.core.Bindable", "js.core.EventDispatcher"], function (Bindable, EventDispatcher) {
var $splitAtReg = /\.?(?:\w+\([^(]*\)|[^.]+)/;
var splitPath = function(path){
var matches = [];
var match = path.match($splitAtReg);
while(match[0]){
matches.push(match[0]);
path = path.substr(match[0].length+1);
match = path.search($splitAtReg);
}
if(path.length > 0){
matches.push(path);
}
return matches;
};
var splitFirst = function(path){
var match = path.match($splitAtReg);
if(match){
return match[0];
}
return path;
};
var Binding = Bindable.inherit({
defaults: {
event: 'change',
path: null,
twoWay: false,
transform: function (val) {
return val;
},
transformBack: function (val) {
return val;
}
},
ctor: function () {
this.callBase();
this.initialize();
},
initialize: function () {
this._checkAttributes();
this.$parameters = [];
this.$subBinding = null;
if (!this.$.rootScope) {
this.$.rootScope = this;
}
var scope = this.$.scope;
var keys = splitPath(this.$.path);
// split up first key
this.$.key = keys.shift();
if($fncRegex.test(this.$.key)){
var matches = this.$.key.match($fncRegex);
var fncName = matches[1];
var fncParameterStr = matches[2];
if (rAppid._.isFunction(scope[fncName])) {
var fnc = scope[fncName];
var events = [];
if(fnc._events){
events = fnc._events;
}else{
events = ['change'];
}
for (var i = 0; i < events.length; i++) {
var event = events[i];
scope.bind(event, this._callback, this);
}
var self = this;
var cb = function(){
self.trigger();
};
// TODO: implement regex for , splitting
this.$parameters = fncParameterStr.split(",");
for (var j = 0; j < this.$parameters.length; j++) {
var para = this.$parameters[j];
if($bindingRegex.test(para)){
para = Binding.create(para,this.$.target,cb);
}
this.$parameters[j] = para;
}
this.$.fnc = fnc;
}
}else{
this.$.event = "change:" + this.$.key;
// on change of this key
scope.bind(this.$.event, this._callback, this);
if (this.$.twoWay === true) {
this.$.targetEvent = 'change:' + this.$.targetKey;
this.$.target.bind(this.$.targetEvent, this._revCallback, this);
}
}
this._createSubBinding();
},
_checkAttributes: function () {
// check infrastructur
if (!this.$.path) {
throw "No path defined!";
}
if (!this.$.scope) {
throw "No scope defined!"
}
if (this.$.twoWay) {
if (!this.$.target) {
throw "TwoWay binding, but no target defined!";
}
if (!this.$.target instanceof Bindable) {
throw "Target is not a Bindable!";
}
if (!this.$.targetKey) {
throw "TwoWay binding, but no target key defined!";
}
}
},
_createSubBinding: function () {
var keys = splitPath(this.$.path);
var k = keys.shift();
if (keys.length > 0) {
var nScope;
if(this.$.fnc){
nScope = this.getValue();
}else{
nScope = this.$.scope.$[k];
}
// if keys are left and has value && is bindable
// get value for first child
if (nScope && (nScope instanceof EventDispatcher)) {
// init new binding, which triggers this binding
this.$subBinding = new Binding({scope: nScope, path: keys.join("."), target: this.$.target, targetKey: this.$.targetKey, rootScope: this.$.rootScope, callback: this.$.callback});
}
}
},
_revCallback: function (e) {
this.$.scope.set(this.$.path, this.$.transformBack(e.$, this.$.target));
},
_callback: function () {
// remove subBindings!
if (this.$subBinding) {
this.$subBinding.destroy();
this.$subBinding = null;
}
// try to create subBinding
this._createSubBinding();
this.trigger();
},
destroy: function () {
this.$.scope.unbind(this.$.event, this._callback);
if (this.$.twoWay === true) {
this.$.target.unbind(this.$.targetEvent, this._revCallback);
}
if (this.$subBinding) {
this.$subBinding.destroy();
}
// destroy parameter bindings
for (var i = 0; i < this.$parameters.length; i++) {
var par = this.$parameters[i];
if(par instanceof Binding){
par.destroy();
}
}
},
getValue: function(){
if(this.$subBinding){
return this.$subBinding.getValue();
}else{
if(this.$.fnc){
var parameters = [];
for (var i = 0; i < this.$parameters.length; i++) {
var para = this.$parameters[i];
if(para instanceof Binding){
para = para.getValue();
}
parameters.push(para);
}
return this.$.fnc.apply(this.$.scope,parameters);
}else if(this.$.path == this.$.key){
return this.$.scope.get(this.$.key);
}else{
return null;
}
}
},
// trigger
trigger: function(){
// get value
var val = this.getValue();
if(this.$.targetKey){
this.$.target.set(this.$.targetKey, this.$.transform(val, this.$.rootScope));
}else if(this.$.callback){
this.$.callback.call(this.$.target,this.$.transform(val, this.$.rootScope));
}
}
});
var $bindingRegex= /^((?:\{{2}(.+)\}{2})|(?:\{(.+)\}))$/i;
var $twoWayBindingRegex= /^\{{2}([a-z_$\}][\(\)a-z0-9$\-_.,\{]*)\}{2}$/i;
var $fncRegex = /^([a-z$_]\w*)\((.*)\)$/i;
var $fncNameRegex = /^(\w+)\(.*\)$/;
Binding.matches = function(attrValue){
return $bindingRegex.test(attrValue);
};
Binding.create = function(bindingDef, targetScope, attrKey){
var cb;
if(rAppid._.isFunction(attrKey)){
cb = attrKey;
}
var match = bindingDef.match($bindingRegex);
var path = match[2] ? match[2] : match[3];
var scopeKey = splitFirst(path);
var scope;
if($fncRegex.test(scopeKey)){
match = scopeKey.match($fncNameRegex);
scope = targetScope.getScopeForFncName(match[1]);
}else{
scope = targetScope.getScopeForKey(scopeKey);
}
if(scope && (scope != targetScope || attrKey != scopeKey)){
var twoWay = !rAppid._.isUndefined(match[2]);
var options = {scope:scope, path:path, target:targetScope, twoWay: twoWay};
if(cb){
options['callback'] = cb;
}else{
options['targetKey'] = attrKey;
}
return new Binding(options);
}
return null;
};
return Binding;
});
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.Content", ["js.core.Component"], function (Component) {
return Component.inherit(({
// TODO
}));
});
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.Element",
["js.core.Bindable"], function (Bindable) {
var undef;
function stringToPrimitive(str) {
// if it's not a string
if (rAppid._.isString(str)) {
var n = parseFloat(str);
if (!rAppid._.isNaN(n)) {
return n;
}
if (str === "true") {
return true;
} else if (str === "false") {
return false;
}
}
return str;
}
return Bindable.inherit({
ctor: function (attributes, descriptor, applicationDomain, parentScope, rootScope) {
attributes = attributes || {};
if (!descriptor) {
// created from node
if (!rootScope) {
rootScope = this;
}
}
this.$descriptor = descriptor;
this.$applicationDomain = applicationDomain;
this.$parentScope = parentScope || null;
this.$rootScope = rootScope || null;
rAppid._.extend(attributes, this._getAttributesFromDescriptor(descriptor), this._getAttributesFromDescriptor(this._$descriptor));
this.callBase(attributes);
this._initializeAttributes(this.$);
// manually constructed
if (descriptor === undef || descriptor === null) {
this._initialize(this.$creationPolicy);
}
},
_getAttributesFromDescriptor: function (descriptor) {
var attributes = {};
if (descriptor && descriptor.attributes) {
var node;
for (var a = 0; a < descriptor.attributes.length; a++) {
node = descriptor.attributes[a];
attributes[node.nodeName] = stringToPrimitive(node.value);
}
}
return attributes;
},
defaults: {
creationPolicy: "auto"
},
_initializeAttributes: function (attributes) {
},
_initializeDescriptors: function () {
},
/**
*
* @param creationPolicy
* auto - do not overwrite (default),
* all - create all children
* TODO none?
*/
_initialize: function (creationPolicy, withBindings) {
if (this.$initialized) {
return;
}
this._preinitialize();
this.initialize();
this._initializeDescriptors();
if (this == this.$rootScope || withBindings) {
this._initializeBindings();
}
this._initializationComplete();
},
_initializeBindings: function () {
},
_initializeDescriptor: function (descriptor) {
},
initialize: function () {
},
find: function (key) {
var scope = this.getScopeForKey(key);
if (this == scope) {
return this.callBase();
} else if (scope != null) {
return scope.get(key);
} else {
return null;
}
},
getScopeForKey: function (key) {
// try to find value for first key
var value = this.$[key];
// if value was found
if (!rAppid._.isUndefined(value)) {
return this;
} else if (this.$parentScope) {
return this.$parentScope.getScopeForKey(key);
} else {
return null;
}
},
getScopeForFncName: function(fncName){
var fnc = this[fncName];
if(!rAppid._.isUndefined(fnc) && rAppid._.isFunction(fnc)){
return this;
} else if(this.$parentScope){
return this.$parentScope.getScopeForFncName(fncName);
} else {
return null;
}
},
_preinitialize: function () {
},
_initializationComplete: function () {
this.$initialized = true;
},
_getTextContentFromDescriptor:function (desc) {
return desc.textContent ? desc.textContent : desc.text;
}
});
}
);
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
var a = 0;
rAppid.defineClass("js.core.EventDispatcher",
["js.core.Base"],
/**
* Base class for trigger and listen to events
* @export js/core/EventDispatcher
*/
function (Base) {
Function.prototype.on = function () {
var events = Array.prototype.slice.call(arguments, 0);
this._events = [];
for (var i = 0; i < events.length; i++) {
var event = events[i];
this._events.push(event);
}
return this;
};
Function.prototype.onChange = function () {
var events = Array.prototype.slice.call(arguments, 0);
this._events = [];
for (var i = 0; i < events.length; i++) {
var event = events[i];
event = "change:" + event;
this._events.push(event);
}
return this;
};
var undefinedValue;
var EventDispatcher = Base.inherit({
ctor: function () {
this.callBase();
this._eventHandlers = {};
},
bind: function (eventType, callback, scope) {
scope = scope || this;
// get the list for the event
var list = this._eventHandlers[eventType] || (this._eventHandlers[eventType] = []);
// and push the callback function
list.push(new EventDispatcher.EventHandler(callback, scope));
return this;
},
/**
*
* @param {String} eventType
* @param {js.core.EventDispatcher.Event|Object} event
* @param caller
*/
trigger: function (eventType, event, caller) {
if (this._eventHandlers[eventType]) {
if (!(event instanceof EventDispatcher.Event)) {
event = new EventDispatcher.Event(event);
}
if (!caller) {
caller = arguments.callee.caller;
}
event.type = eventType;
var list = this._eventHandlers[eventType];
for (var i = 0; i < list.length; i++) {
if (list[i]) {
var result = list[i].trigger(event, caller);
if (result !== undefinedValue) {
ret = result;
if (result === false) {
event.preventDefault();
event.stopPropagation();
}
}
if (event.isImmediatePropagationStopped) {
break;
}
}
}
}
return event;
},
unbind: function (eventType, callback) {
if (!eventType) {
// remove all events
this._eventHandlers = {};
} else if (!callback) {
// remove all callbacks for these event
this._eventHandlers[eventType] = [];
} else if (this._eventHandlers[eventType]) {
var list = this._eventHandlers[eventType];
for (var i = list.length - 1; i >= 0; i--) {
if (list[i].$callback == callback) {
list.splice(i, 1); // delete callback
}
}
}
}
});
EventDispatcher.Event = Base.inherit({
ctor: function (attributes) {
this.$ = attributes;
this.isDefaultPrevented = false;
this.isPropagationStopped = false;
this.isImmediatePropagationStopped = false;
},
preventDefault: function () {
this.isDefaultPrevented = true;
var e = this.orginalEvent;
if (e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false; // IE
}
}
},
stopPropagation: function () {
this.isPropagationStopped = true;
var e = this.originalEvent;
if (e) {
if (e.stopPropagation) {
e.stopPropagation();
}
e.cancelBubble = true;
}
},
stopImmediatePropagation: function () {
this.isImmediatePropagationStopped = true;
this.stopPropagation();
}
});
EventDispatcher.EventHandler = Base.inherit({
ctor: function (callback, scope) {
this.scope = scope;
this.$callback = callback;
},
trigger: function (event, caller) {
this.$callback.call(this.scope, event, caller);
}
});
return EventDispatcher;
}
);
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.History", ["js.core.Bindable"], function (Bindable) {
var routeStripper = /^#\/?/,
undef;
return Bindable.inherit({
ctor: function () {
this.callBase();
this.$routers = [];
this.processUrl = true;
},
defaults: {
interval: 50
},
getFragment: function (fragment) {
fragment = decodeURIComponent(fragment || window.location.hash);
return fragment.replace(routeStripper, '');
},
start: function () {
var self = this;
this.$checkUrlFn = function () {
self.checkUrl.apply(self, arguments);
};
if ("onhashchange" in window) {
if (window.addEventListener) {
window.addEventListener('hashchange',
this.$checkUrlFn, false);
} else {
window.attachEvent('onhashchange', this.$checkUrlFn);
}
} else {
// polling
this.$checkUrlInterval = setInterval(this.$checkUrlFn, this.$.interval);
}
this.fragment = this.getFragment();
this.navigate(this.fragment);
},
stop: function () {
if ("onhashchange" in window) {
if (window.removeEventListener) {
window.removeEventListener('hashchange',
this.$checkUrlFn, false);
} else {
window.detachEvent('onhashchange', this.$checkUrlFn);
}
} else {
// polling
clearInterval(this.$checkUrlInterval);
}
},
addRouter: function (router) {
this.$routers.push(router);
},
checkUrl: function (e) {
if (this.processUrl) {
var currentFragment = this.getFragment();
if (currentFragment == this.fragment) {
return false;
}
}
this.processUrl = true;
},
triggerRoute: function (fragment) {
for (var i = 0; i < this.$routers.length; i++) {
if (this.$routers[i].executeRoute(fragment)) {
return true;
}
}
console.log("no route for '" + fragment + "' found.");
},
navigate: function (fragment, createHistoryEntry, triggerRoute) {
if (createHistoryEntry == undef || createHistoryEntry == null) {
createHistoryEntry = true;
}
if (triggerRoute == undef || triggerRoute == null) {
triggerRoute = true;
}
this.processUrl = false;
if (createHistoryEntry) {
window.location.hash = "/" + fragment;
} else {
// replace hash
window.location.replace("#/" + fragment);
}
this.fragment = fragment;
if (triggerRoute) {
return this.triggerRoute(fragment);
}
}
});
});
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.I18n", ["js.core.Component"], function(Component) {
return Component.inherit({
defaults: {
path: 'app/locale',
locale: null,
suffix: '.json',
translations: {}
},
initialize: function() {
this.callBase();
this.loadLocale(this.$.locale);
},
_commitChangedAttributes: function(attributes) {
if (attributes.locale) {
this.loadLocale(attributes.locale);
}
},
loadLocale: function(locale, callback) {
if (!locale) {
throw "locale not defined";
}
var self = this;
rAppid.require(['json!' + this.$.path + '/' + this.$.locale + this.$.suffix], function (translations) {
if (callback) {
callback();
}
self.set({
translations: translations
});
});
},
/**
* @param [num] for plural or singular
* @param key translation key
* @param - replacement for %0
* @param - replacement for %1 ...
*/
translate: function() {
var args = Array.prototype.slice.call(arguments);
var key = args.shift(), isPlural;
if(rAppid._.isNumber(key)){
isPlural = key !== 1;
key = args.shift();
}
if(isPlural){
key += "_plural";
}
var value = this.$.translations[key] || "";
for (var i = 0; i < args.length; i++) {
// replace, placeholder
value = value.split("%" + i).join(args[i]);
}
return value;
}.onChange("translations")
})
})
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.Imports", ["js.core.Element"], function (Component) {
return Component.inherit({
});
});
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.Injection", ["js.core.Component"], function (Component) {
var singleton;
function factoryInheritsFrom(factory, type) {
return factory == type || factory.prototype instanceof type;
}
return Component.inherit({
ctor: function () {
if (!singleton) {
this.callBase();
this.$singletonInstanceCache = [];
this.$factories = [];
singleton = this;
}
return singleton;
},
_childrenInitialized: function () {
this.callBase();
for (var c = 0; c < this.$configurations.length; c++) {
var config = this.$configurations[c];
if (config.className == "js.conf.Factory") {
this.addFactory(config.$);
}
}
},
getInstance: function (type) {
// TODO: add class hierarchy distance check
var instance;
// go to the singleton instance and look for requested instance
for (var i = 0; i < this.$singletonInstanceCache.length; i++) {
instance = this.$singletonInstanceCache[i];
if (instance instanceof type) {
return instance;
}
}
// instance not found -> go thought the factories
for (var f = 0; f < this.$factories.length; f++) {
var factory = this.$factories[f];
if (factoryInheritsFrom(factory.factory, type)) {
// create instance
instance = new factory.factory();
if (instance instanceof type) {
if (factory.singleton) {
this.addInstance(instance)
}
return instance;
}
}
}
throw "requested injection type not found";
},
addChild: function (child) {
this.addInstance(child);
},
addFactory: function (factory) {
if (factory instanceof Function) {
factory = {
factory: factory
}
}
rAppid._.defaults(factory, {
"type": null,
"factory": null,
"singleton": false
});
if (!factory.factory) {
// get factory from class
var fac = this.$applicationDomain.getDefinition(factory.type);
if (!fac) {
throw "factory for type '" + factory.type + "' not found";
}
factory.factory = fac;
}
this.$factories.push(factory);
},
addInstance: function (instance) {
if (instance instanceof Function) {
throw "got a factory instead of an instance"
}
this.$singletonInstanceCache.push(instance);
}
});
});
});
\ No newline at end of file
This diff is collapsed.
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.Module", ["js.core.UIComponent"], function (UIComponent) {
return UIComponent.inherit({
/**
* loads the
* @param callback
*/
start: function (callback) {
},
render: function (target) {
}
});
});
});
\ No newline at end of file
var requirejs = (typeof requirejs === "undefined" ? require("requirejs") : requirejs);
requirejs(["rAppid"], function (rAppid) {
rAppid.defineClass("js.core.Router", ["js.core.Component"],
function (Component) {
return Component.inherit({
ctor: function () {
this.$routes = [];
this.callBase();
},
initialize: function () {
this.callBase();
if (this.$.history) {
this.history = this.$.history;
} else {
this.history = rAppid.systemManager.application.history;
}
this.history.addRouter(this);
},
/**
*
* @param {Regexp|Object} route
* @param {Function} [fn]
*/
addRoute: function () {
var route;
if (arguments.length == 2) {
route = {
regex: arguments[0],
fn: arguments[1]
}
} else {
route = arguments[0];
}
rAppid._.defaults(route, {
name: null,
regex: null,
fn: null
});
if (!(route.fn && route.regex)) {
throw "fn and regex required"
}
this.$routes.push(route);
},
executeRoute: function (fragment) {
// Test routes and call callback
for (var i = 0; i < this.$routes.length; i++) {
var route = this.$routes[i];
var params = route.regex.exec(fragment);
if (params) {
params.shift();
route.fn.apply(this, params);
return true;
}
}
return false;
},
/**
* shortcut to history.navigate
* @param to
* @param createHistoryEntry
* @param triggerRoute
*/
navigate: function (to, createHistoryEntry, triggerRoute) {
return this.history.navigate(to, createHistoryEntry, triggerRoute);
}
});
});
});
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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