Commit 987bc94c authored by Sam Saccone's avatar Sam Saccone

Merge pull request #1337 from tenbits/atmajs-update

update (Atma.js) rewrite the app
parents 7be1d104 8651418c
......@@ -4,11 +4,6 @@ node_modules/atma-class/*
node_modules/includejs/*
!node_modules/includejs/lib/include.js
node_modules/jquery/*
!node_modules/jquery/dist
node_modules/jquery/dist/*
!node_modules/jquery/dist/jquery.js
node_modules/maskjs/*
!node_modules/maskjs/lib/mask.js
......
......@@ -3,52 +3,21 @@
<head>
<meta charset="utf-8">
<title>Atma.js • TodoMVC</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
</head>
<body>
<!--
TodoMVC Atma.js Application
1. Read readme.md - you will get basic information about the libraries
2. Hint: Viewing *.mask files enable javascript or less syntax highlighting in your IDE
The application structure guide:
- Controls
todo:input;
- Components
:app\
:filter;
:todoList\
:todoTask;
Scripts overview (sorted from the most atomic parts up to the application component):
js/
model/Todos.js
cntrl/input.js
filter/filter.js
todoList/
todoTask/todoTask.js
todoList.js
app.js
_If the controller loads a template, do not forget to review that._
-->
<script src="node_modules/todomvc-common/base.js"></script>
<script src="node_modules/jquery/dist/jquery.js"></script>
<!-- Loads App component from `js/App.mask` on start -->
<script type="text/mask" data-run="auto">
import from 'node_modules/todomvc-common/base.css';
import from 'node_modules/todomvc-app-css/index.css';
import from 'node_modules/todomvc-common/base.js';
import App from 'js/app';
App;
</script>
<script src="node_modules/includejs/lib/include.js"></script>
<script src="node_modules/atma-class/lib/class.js"></script>
<script src="node_modules/maskjs/lib/mask.js"></script>
<script src="node_modules/ruta/lib/ruta.js"></script>
<script src="js/app.js"></script>
</body>
</html>
/*
* Filter Presentation:
* - HASH-change listener
* - navigation(filter) buttons
*/
define Filter {
var filters = {
'' : 'All',
active: 'Active',
completed: 'Completed'
};
ul .filters {
for ((key, val) in filters) {
// compare with the scoped value `action`
li >
a .~[bind: action == key ? 'selected' ] href = '#~key' >
'~val'
}
}
}
/**
* Extend INPUT tag to edit a todo's title
* - format string
* - complete edit on ENTER
* - complete edit on BLUR
*
* Used as
* - the main application's input
* - single todo item editor
*
* Public Signals
* - cancel: input interrupted
* - submit: input formatted and completed
*/
define TodoInput as (input type='text') {
event blur (e) {
this.submit();
}
event press:esc (e) {
this.cancel();
}
event press:enter (e) {
this.submit();
// prevent IE from button click - `Clear Completed`
e.preventDefault();
}
function submit () {
var str = this.$.val().trim();
this.emitOut('submit', str);
this.afterEdit();
}
function cancel () {
this.emitOut('cancel');
this.afterEdit();
}
function afterEdit () {
this.$.val(this.attr.preserve ? this.model.title : '');
}
}
......@@ -31,10 +31,15 @@
})
.save();
},
removeCompleted: function () {
this.del(function (x) {
return x.completed === true;
});
},
status: {
count: 0,
todoCount: 0,
completedCount: 0,
completedCount: 0
},
Override: {
// Override mutators and recalculate status,
......@@ -58,7 +63,7 @@
calcStatus: function () {
var todos = 0;
var completed = 0;
this.forEach(function(todo){
this.forEach(function (todo) {
todo.completed && ++completed || ++todos;
});
......
import TodoTask from 'TodoTask';
define TodoList {
slot toggleAll (event) {
var completed = event.currentTarget.checked;
this.model.toggleAll(completed);
}
slot taskChanged () {
this.model.save();
}
slot taskRemoved (event, task) {
this.model.del(task);
}
input .toggle-all
type = checkbox
checked = '~[bind: status.todoCount == 0 ? "checked" ]'
x-tap = toggleAll
;
label for='toggle-all' > 'Mark all as complete'
ul .todo-list {
// bind todos collection
+each (.) > TodoTask;
}
}
......@@ -13,23 +13,17 @@
*
*/
include
.load('todoTask.mask::Template')
.done(function (response) {
(function () {
'use strict';
var STATE_VIEW = '';
var STATE_EDIT = 'editing';
mask.registerHandler(':todoTask', Compo({
include.exports = {
scope: {
state: STATE_VIEW
},
template: response.load.Template,
slots: {
inputCanceled: '_editEnd',
taskChanged: function () {
if (!this.model.title) {
......@@ -40,7 +34,7 @@ include
return false;
}
this._editEnd();
this.scope.state = STATE_VIEW;
},
taskRemoved: function () {
// remove component
......@@ -49,31 +43,33 @@ include
// add arguments to the signal
return [this.model];
},
cancel: function () {
this.scope.state = STATE_VIEW;
},
submit: function () {
// do not send the signal to the app
return false;
},
edit: function () {
this.scope.state = STATE_EDIT;
this.compos.input.focus();
this.compos.input.$.focus();
}
},
compos: {
input: 'compo: todo:input'
input: 'compo: TaskEdit'
},
//= Private Methods
_editEnd: function () {
this.scope.state = STATE_VIEW;
},
_isVisible: function (completed, action) {
if ('completed' === action && !completed) {
if (action === 'completed' && !completed) {
return false;
}
if ('active' === action && completed) {
if (action === 'active' && completed) {
return false;
}
return true;
}
}));
});
};
})();
import * as TodoTaskController from 'TodoTask.js';
import TodoInput from '../Controls/TodoInput';
define TodoTask extends TodoTaskController {
let TaskView as (.view) {
input.toggle type=checkbox {
dualbind
value = completed
// emit signal when INPUTs state changes via user input
x-signal = 'dom: taskChanged'
;
}
label > '~[bind:title]';
button.destroy x-tap = 'taskRemoved';
}
let TaskEdit as (input.edit preserve) extends TodoInput {
dualbind
value = 'title'
dom-slot = submit
// emit `taskChange` signal each time model is changed
// via user input
x-signal = 'dom: taskChanged'
;
}
/* `+visible` is same as `+if` with one difference:
* by falsy condition it still renders the nodes (with display:none)
* and `+if` first renders only when the condition becomes true.
*/
+visible ($._isVisible(completed, action)) {
li
.~[bind:completed ? 'completed']
.~[bind:state]
// emit `edit` on `dblclick` event
x-signal = 'dblclick: edit'
{
TaskView;
TaskEdit;
}
}
}
......@@ -4,58 +4,33 @@
'use strict';
/**
* Application Entry Point
*
* - load immediate dependecies
* - define and initialize Application Component
* Controller for the App Component
*
* - load model dependecies
*/
include
.js('Store/Todos.js')
.done(function (resp) {
// Global route namespaces, to simplify resource dependency loading
.routes({
model: 'model/{0}.js',
cntrl: 'cntrl/{0}.js',
compo: 'compo/{0}/{1}.js'
})
.cfg({
// Load `/foo.bar` from current _directory_ path, and not from domain's root
lockToFolder: true
})
.js({
model: 'Todos',
cntrl: 'input',
compo: ['todoList', 'filter']
})
.load('./app.mask::Template')
.ready(function (resp) {
mask.registerHandler(':app', Compo({
template: resp.load.Template,
include.exports = {
model: resp.Todos.fetch(),
scope: {
action: ''
},
slots: {
newTask: function (event, title) {
submit: function (event, title) {
if (title) {
this.model.create(title);
}
},
removeAllCompleted: function () {
this.model.del(function (x) {
return x.completed === true;
});
this.model.removeCompleted();
}
},
onRenderStart: function () {
// (RutaJS) Default router is the History API,
// but for this app spec enable hashes
// but for this app-spec we enable hashes
ruta
.setRouterType('hash')
.add('/?:action', this.applyFilter.bind(this))
......@@ -65,7 +40,5 @@ include
applyFilter: function (route, params) {
this.scope.action = params.action || '';
}
}));
Compo.initialize(':app', document.body);
});
\ No newline at end of file
};
});
section #todoapp {
header #header {
h1 > 'todos'
import * as AppController from 'app.js';
import TodoInput from 'Controls/TodoInput';
import TodoList from 'Todos/TodoList';
import Filter from 'Controls/Filter';
todo:input #new-todo
autofocus
placeholder = 'What needs to be done?'
x-signal = 'enter: newTask'
;
}
+if (status.count) {
section #main >
:todoList;
footer #footer {
span #todo-count {
strong > '~[bind: status.todoCount]'
span > ' item~[bind: status.todoCount != 1 ? "s"] left'
}
:filter;
+if (status.completedCount > 0) {
button #clear-completed x-signal = 'click: removeAllCompleted' >
'Clear completed'
define App extends AppController {
section .todoapp {
header {
h1 > 'todos'
TodoInput .new-todo
autofocus
placeholder = 'What needs to be done?'
;
}
+if (status.count) {
section .main >
TodoList;
footer .footer {
span .todo-count {
strong > '~[bind: status.todoCount]'
span > ' item~[bind: status.todoCount != 1 ? "s"] left'
}
Filter;
+if (status.completedCount > 0) {
button .clear-completed x-tap = 'removeAllCompleted' >
'Clear completed'
}
}
}
}
footer .info {
p { 'Double-click to edit a todo' }
p { 'Created by ' a href='http://github.com/tenbits' > 'Alex Kit' }
p { 'Part of ' a href='http://todomvc.com' > 'TodoMVC' }
}
}
footer #info {
p { 'Double-click to edit a todo' }
p { 'Created by ' a href='http://github.com/tenbits' > 'tenbits' }
p { 'Part of ' a href='http://todomvc.com' > 'TodoMVC' }
}
\ No newline at end of file
/*jshint newcap:false */
/*global mask, Compo */
/**
* Extend INPUT tag to edit a todo's title
* - format string
* - complete edit on ENTER
* - complete edit on BLUR
*
* Used as
* - a main application's input
* - single todo item editor
*
* Public Events
* - cancel: input interrupted
* - enter : input formatted and completed
*
*/
(function () {
'use strict';
var ENTER_KEY = 13;
var ESCAPE_KEY = 27;
mask.registerHandler('todo:input', Compo({
tagName: 'input',
attr: {
type: 'text',
value: '~[title]',
// Clear input after edit, `true` for main input, `false` for todo's edit.
preserve: false
},
events: {
'keydown': function (event) {
switch (event.which) {
case ENTER_KEY:
this.save();
// prevent IE from button click - `Clear Completed`
event.preventDefault();
break;
case ESCAPE_KEY:
this.cancel();
break;
}
},
'blur': 'save'
},
focus: function () {
this.$.focus();
},
cancel: function () {
this.$.trigger('cancel');
this.afterEdit();
},
save: function () {
var value = this.$.val().trim();
this.$.trigger('enter', value);
this.afterEdit();
},
afterEdit: function () {
this.$.val(this.attr.preserve ? this.model.title : '');
}
}));
}());
/*global include, mask, Compo, ruta */
/*
* Filter Presentation:
* - HASH-change listener
* - navigation(filter) buttons
*
*/
include
.load('filter.mask::Template')
.done(function (resp) {
'use strict';
mask.registerHandler(':filter', Compo.createClass({
template: resp.load.Template
}));
});
var filters = {
'' : 'All',
'active': 'Active',
'completed': 'Completed'
};
ul #filters >
for ((key, val) in filters) {
// compare with the scoped value `action`
li >
a .~[bind: action == key ? 'selected' ] href = '#~[key]' >
'~[val]'
}
\ No newline at end of file
/*jshint newcap:false */
/*global include, mask, Compo*/
/*
* Todos Collection Component
*
* Collection is passed as a model to this component
*/
include
.load('todoList.mask')
.js('todoTask/todoTask.js')
.done(function (response) {
'use strict';
mask.registerHandler(':todoList', Compo({
template: response.load.todoList,
slots: {
// Component's slots for the signals
toggleAll: function (event) {
var completed = event.currentTarget.checked;
this.model.toggleAll(completed);
},
taskChanged: function () {
this.model.save();
},
taskRemoved: function (event, task) {
this.model.del(task);
}
}
}));
});
input #toggle-all
type = checkbox
checked = '~[bind: status.todoCount == 0 ? "checked" ]'
x-signal = 'click: toggleAll'
;
label for='toggle-all' > 'Mark all as complete'
ul #todo-list {
// bind todos collection
+each (.) > :todoTask;
}
\ No newline at end of file
define :todoTask:view > .view{
input.toggle type=checkbox {
:dualbind
value = 'completed'
// emit signal when INPUTs state changes via user input
x-signal = 'dom: taskChanged'
;
}
label > '~[bind:title]';
button.destroy x-signal = 'click: taskRemoved';
}
define :todoTask:edit >
todo:input.edit
// do not clear input after edit
preserve
// emit signal on custom event `cancel`
x-signal = 'cancel: inputCanceled' {
:dualbind
value = 'title'
// change model on custom event `enter` defined in the control
change-event = 'enter'
// emit `taskChange` signal each time model is changed
// via user input
x-signal = 'dom: taskChanged'
;
}
+if($c._isVisible(completed, action)) >
li
.~[bind:completed ? 'completed']
.~[bind:state]
// emit `edit` on `dblclick` event
x-signal = 'dblclick: edit'
{
:todoTask:view;
:todoTask:edit;
}
\ No newline at end of file
// source /src/license.txt
/*!
* ClassJS v1.0.67
* ClassJS v1.0.68
* Part of the Atma.js Project
* http://atmajs.com/
*
* MIT license
* http://opensource.org/licenses/MIT
*
* (c) 2012, 2014 Atma.js and other contributors
* (c) 2012, 2015 Atma.js and other contributors
*/
// end:source /src/license.txt
// source /src/umd.js
......@@ -21,14 +21,11 @@
_exports
;
_exports = root || _global;
function construct(){
return factory(_global, _exports);
};
}
if (typeof define === 'function' && define.amd) {
......@@ -376,7 +373,6 @@
return;
}
var Static;
if (is_Function(mix))
Static = mix;
......@@ -407,17 +403,27 @@
}
if (_extends != null) {
arr_each(_extends, function(x){
x = proto_getProto(x);
if (is_rawObject(x[key]))
obj_defaults(protoValue, x[key]);
});
arr_each(
_extends,
proto_extendDefaultsDelegate(protoValue, key)
);
}
}
}
// PRIVATE
function proto_extendDefaultsDelegate(target, key) {
return function(source){
var proto = proto_getProto(source),
val = proto[key];
if (is_rawObject(val)) {
obj_defaults(target, val);
}
}
}
function proto_extend(proto, source) {
if (source == null)
return;
......@@ -430,6 +436,9 @@
var key, val;
for (key in source) {
if (key === 'constructor')
continue;
val = source[key];
if (val != null)
proto[key] = val;
......@@ -450,7 +459,6 @@
return fn_apply(super_, this, args);
}
} else{
proxy = fn_doNothing;
}
......@@ -491,6 +499,29 @@
_class.prototype = prototype;
}
function inherit_Object_create(_class, _base, _extends, original, _overrides, defaults) {
if (_base != null) {
_class.prototype = Object.create(_base.prototype);
obj_extendDescriptors(_class.prototype, original);
} else {
_class.prototype = Object.create(original);
}
_class.prototype.constructor = _class;
if (_extends != null) {
arr_each(_extends, function(x) {
obj_defaults(_class.prototype, x);
});
}
var proto = _class.prototype;
obj_defaults(proto, defaults);
for (var key in _overrides) {
proto[key] = proto_override(proto[key], _overrides[key]);
}
}
// browser that doesnt support __proto__
......@@ -608,13 +639,6 @@
json[asKey] = val.toJSON();
continue;
//@removed - serialize any if toJSON is implemented
//if (toJSON === json_proto_toJSON || toJSON === json_proto_arrayToJSON) {
// json[asKey] = val.toJSON();
// continue;
//}
break;
}
json[asKey] = val;
......@@ -806,7 +830,7 @@
define(target, key, descr);
}
return target;
};
}
}());
......@@ -1289,7 +1313,7 @@
_arguments[3],
_arguments[4]
);
};
}
return fn.apply(ctx, _arguments);
};
......@@ -1500,6 +1524,7 @@
p.done(e_PRAPAIR_DATA, '');
return p;
}
break;
default:
// @TODO notify not supported content type
// -> fallback to url encode
......@@ -2826,10 +2851,9 @@
};
}
function Remote(route){
function Remote(route){
return new XHRRemote(route);
};
}
Remote.onBefore = storageEvents_onBefore;
Remote.onAfter = storageEvents_onAfter;
......@@ -3083,9 +3107,11 @@
}
}
// eqeq to match by type diffs.
if (value != matcher)
/*jshint eqeqeq: false*/
if (value != matcher) {
return false;
}
/*jshint eqeqeq: true*/
}
return true;
......
......@@ -1076,7 +1076,7 @@
}
}
if (currentInclude != null){
if (currentInclude != null && currentInclude.type === 'js'){
global.include = currentInclude;
}
},
......@@ -1370,7 +1370,7 @@
return resource;
}
resource = new Resource();
resource = new Resource('package');
resource.state = 4;
resource.location = path_getDir(path_normalize(url));
resource.parent = parent;
......@@ -1468,7 +1468,7 @@
allDone: function(callback){
ScriptStack.complete(function(){
var pending = include.getPending('js'),
var pending = include.getPending(),
await = pending.length;
if (await === 0) {
callback();
......@@ -1478,7 +1478,7 @@
var i = -1,
imax = await;
while( ++i < imax ){
pending[i].on(4, check);
pending[i].on(4, check, null, 'push');
}
function check() {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -15,7 +15,7 @@
// source ../src/vars.js
var mask = global.mask || Mask;
var mask = global.mask || (typeof Mask !== 'undefined' ? Mask : null);
// settings
......@@ -27,6 +27,33 @@
var _cfg_isStrict = true,
_Array_slice = Array.prototype.slice;
// end:source ../src/vars.js
// source ../src/utils/obj.js
var obj_extend,
obj_create;
(function(){
obj_extend = function(a, b){
if (b == null)
return a || {};
if (a == null)
return obj_create(b);
for(var key in b){
a[key] = b[key];
}
return a;
};
obj_create = Object.create || function(x) {
var Ctor = function(){};
Ctor.prototype = x;
return new Ctor;
};
}());
// end:source ../src/utils/obj.js
// source ../src/utils/log.js
var log_error;
(function(){
......@@ -43,7 +70,9 @@
var path_normalize,
path_split,
path_join,
path_fromCLI
path_fromCLI,
path_getQuery,
path_setQuery
;
(function(){
......@@ -93,6 +122,26 @@
return parts_serialize(parts);
};
path_getQuery = function(path){
var i = path.indexOf('?');
if (i === -1)
return null;
var query = path.substring(i + 1);
return query_deserialize(query, '&');
};
path_setQuery = function(path, mix){
var query = typeof mix !== 'string'
? query_serialize(mix, '&')
: mix;
var i = path.indexOf('?');
if (i !== -1) {
path = path.substring(0, i);
}
return path + '?' + query;
};
// == private
......@@ -892,12 +941,29 @@
}
HistoryEmitter.prototype = {
navigate: function(url){
navigate: function(url, opts){
if (url == null) {
this.changed();
return;
}
if (opts != null && opts.extend === true) {
var query = path_getQuery(url),
current = path_getQuery(location.search);
if (current != null && query != null) {
for (var key in current) {
// strict compare
if (query[key] !== void 0 && query[key] === null) {
delete current[key];
}
}
query = obj_extend(current, query);
url = path_setQuery(url, query);
}
}
history.pushState({}, null, url);
this.initial = null;
this.changed();
......@@ -949,8 +1015,8 @@
route.value(route, current && current.params);
}
},
navigate: function(url){
this.emitter.navigate(url);
navigate: function(url, opts){
this.emitter.navigate(url, opts);
},
current: function(){
return this.collection.get(
......@@ -965,8 +1031,28 @@
return Location;
}());
// end:source ../src/emit/Location.js
// source ../src/ruta.js
// source ../src/api/utils.js
var ApiUtils = {
/*
* Format URI path from CLI command:
* some action -foo bar === /some/action?foo=bar
*/
pathFromCLI: path_fromCLI,
query: {
serialize: query_serialize,
deserialize: query_deserialize,
get: function(path_){
var path = path_ == null
? location.search
: path_;
return path_getQuery(path);
}
}
};
// end:source ../src/api/utils.js
// source ../src/ruta.js
var routes = new Routes(),
router;
......@@ -1001,8 +1087,12 @@
get: function(path){
return routes.get(path);
},
navigate: function(path){
router_ensure().navigate(path);
navigate: function(mix, opts){
var path = mix;
if (mix != null && typeof mix === 'object') {
path = '?' + query_serialize(mix, '&');
}
router_ensure().navigate(path, opts);
return this;
},
current: function(){
......@@ -1019,18 +1109,11 @@
parse: Routes.parse,
$utils: {
/*
* Format URI path from CLI command:
* some action -foo bar === /some/action?foo=bar
*/
pathFromCLI: path_fromCLI,
query: {
serialize: query_serialize,
deserialize: query_deserialize
}
}
/*
* @deprecated - use `_` instead
*/
$utils: ApiUtils,
_ : ApiUtils,
};
......@@ -1039,6 +1122,9 @@
// source ../src/mask/attr/anchor-dynamic.js
(function() {
if (mask == null) {
return;
}
mask.registerAttrHandler('x-dynamic', function(node, value, model, ctx, tag){
tag.onclick = navigate;
......
......@@ -44,7 +44,7 @@ input[type="checkbox"] {
display: none;
}
#todoapp {
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
......@@ -52,25 +52,25 @@ input[type="checkbox"] {
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
#todoapp input::-webkit-input-placeholder {
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#todoapp input::-moz-placeholder {
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#todoapp input::input-placeholder {
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#todoapp h1 {
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
......@@ -83,7 +83,7 @@ input[type="checkbox"] {
text-rendering: optimizeLegibility;
}
#new-todo,
.new-todo,
.edit {
position: relative;
margin: 0;
......@@ -104,14 +104,14 @@ input[type="checkbox"] {
font-smoothing: antialiased;
}
#new-todo {
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
#main {
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
......@@ -121,7 +121,7 @@ label[for='toggle-all'] {
display: none;
}
#toggle-all {
.toggle-all {
position: absolute;
top: -55px;
left: -12px;
......@@ -131,50 +131,50 @@ label[for='toggle-all'] {
border: none; /* Mobile Safari */
}
#toggle-all:before {
.toggle-all:before {
content: '❯';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
#toggle-all:checked:before {
.toggle-all:checked:before {
color: #737373;
}
#todo-list {
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
#todo-list li:last-child {
.todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
.todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
......@@ -188,15 +188,15 @@ label[for='toggle-all'] {
appearance: none;
}
#todo-list li .toggle:after {
.todo-list li .toggle:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
#todo-list li .toggle:checked:after {
.todo-list li .toggle:checked:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
#todo-list li label {
.todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
......@@ -206,12 +206,12 @@ label[for='toggle-all'] {
transition: color 0.4s;
}
#todo-list li.completed label {
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
#todo-list li .destroy {
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
......@@ -226,27 +226,27 @@ label[for='toggle-all'] {
transition: color 0.2s ease-out;
}
#todo-list li .destroy:hover {
.todo-list li .destroy:hover {
color: #af5b5e;
}
#todo-list li .destroy:after {
.todo-list li .destroy:after {
content: '×';
}
#todo-list li:hover .destroy {
.todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
.todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
......@@ -254,7 +254,7 @@ label[for='toggle-all'] {
border-top: 1px solid #e6e6e6;
}
#footer:before {
.footer:before {
content: '';
position: absolute;
right: 0;
......@@ -269,16 +269,16 @@ label[for='toggle-all'] {
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
.todo-count {
float: left;
text-align: left;
}
#todo-count strong {
.todo-count strong {
font-weight: 300;
}
#filters {
.filters {
margin: 0;
padding: 0;
list-style: none;
......@@ -287,11 +287,11 @@ label[for='toggle-all'] {
left: 0;
}
#filters li {
.filters li {
display: inline;
}
#filters li a {
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
......@@ -300,17 +300,17 @@ label[for='toggle-all'] {
border-radius: 3px;
}
#filters li a.selected,
#filters li a:hover {
.filters li a.selected,
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
#filters li a.selected {
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
#clear-completed,
html #clear-completed:active {
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
......@@ -319,11 +319,11 @@ html #clear-completed:active {
position: relative;
}
#clear-completed:hover {
.clear-completed:hover {
text-decoration: underline;
}
#info {
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
......@@ -331,17 +331,17 @@ html #clear-completed:active {
text-align: center;
}
#info p {
.info p {
line-height: 1;
}
#info a {
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
#info a:hover {
.info a:hover {
text-decoration: underline;
}
......@@ -350,16 +350,16 @@ html #clear-completed:active {
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
.toggle-all,
.todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
.todo-list li .toggle {
height: 40px;
}
#toggle-all {
.toggle-all {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
......@@ -368,11 +368,11 @@ html #clear-completed:active {
}
@media (max-width: 430px) {
#footer {
.footer {
height: 50px;
}
#filters {
.filters {
bottom: 10px;
}
}
......@@ -114,7 +114,12 @@
})({});
if (location.hostname === 'todomvc.com') {
window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-31081062-1', 'auto');
ga('send', 'pageview');
}
/* jshint ignore:end */
......@@ -228,7 +233,7 @@
xhr.onload = function (e) {
var parsedResponse = JSON.parse(e.target.responseText);
if (parsedResponse instanceof Array) {
var count = parsedResponse.length
var count = parsedResponse.length;
if (count !== 0) {
issueLink.innerHTML = 'This app has ' + count + ' open issues';
document.getElementById('issue-count').style.display = 'inline';
......
{
"private": true,
"dependencies": {
"atma-class": "^1.0.68",
"includejs": "^0.9.10",
"jquery": "^2.1.3",
"maskjs": "^0.10.1",
"ruta": "^0.1.12",
"todomvc-common": "^1.0.1",
"todomvc-app-css": "^1.0.0"
"atma-class": "^1.1.69",
"includejs": "^0.9.14",
"maskjs": "^0.52.0",
"ruta": "^0.1.16",
"todomvc-app-css": "^2.0.1",
"todomvc-common": "^1.0.2"
}
}
# Atma.js TodoMVC Example
> Fast, elegant and component oriented framework for desktops, mobiles or servers _(node.js)_
> Fast, elegant and component oriented framework for desktops, mobiles or servers _(Node.js)_
> _[Atma - atmajs.com](http://atmajs.com)_
The framework consists of several stand-alone libraries. This approach not only reduces barriers to entry, but also
......@@ -11,21 +11,22 @@ The goal of the framework is to deliver the component-based development and to p
## Learning Atma.js
#### ClassJS
[web-page](http://atmajs.com/class) [github](http://github.com/atmajs/ClassJS)
[web-page](http://atmajs.com/class) [GitHub](http://github.com/atmajs/ClassJS)
— is a class-model implementation. A business logic layer for applications. It accumulates best practices of the OOP and supports model de-/serialization with the persistence to localStorage, RESTful service or MongoDB. Any additional adapter can be created by the developer.
#### MaskJS
[web-page](http://atmajs.com/mask) [github](https://github.com/atmajs/MaskJS)
[web-page](http://atmajs.com/mask) [GitHub](https://github.com/atmajs/MaskJS)
— is the core library of the Atma.js framework. It brings HMVC engine into play and everything starts with the markup. Instead of HTML, more compact and component-oriented syntax is used, which is similar to LESS and Zen Coding. But not the syntax is the advantage of the mask markup, but the DOM creation approach. It allows very fast to parse the templates to tiny MaskDOM (_json with the tree structure_). And while traversing the MaskDOM, the builder creates DOM Elements and initializes components. As the MaskDom structure is extremely lightweight, each component can easily manipulate the MaskDOM. So the all dynamic behavior, like interpolation, 1-/2way-binding, component's nesting and many other things, are almost for free in sens of the performance. Beyond fast DOM creation there are other distinguishing features:
— is the core library of the Atma.js framework. It brings HMVC engine into play and everything starts with the markup. Along HTML, more compact and component-oriented syntax can be used, which is similar to LESS and Zen Coding. But not the syntax is the advantage of the mask markup, but the DOM creation approach. Mask or HTML templates are prarsed to the tiny MaskDOM AST. And while traversing the MaskDOM, the builder creates DOM Elements and initializes components. As the MaskDom structure is extremely lightweight, each component can easily manipulate the MaskDOM. So the all dynamic behavior, like interpolation, 1-/2way-binding, component's nesting and many other things, are almost for free in sens of the performance. Beyond fast DOM creation there are other distinguishing features:
- model agnostic
- components hierarchy
- better referencing via ```find/closest``` search _in a jquery way)_
- better referencing via `find/closest` search _in a jQuery way)_
- better communication via signals and slots. _Piped-signals are used to bind components, that are not in ascendant-descendant relation, but anywhere in an app_
- one-/two-way bindings with complex object observers, so even if deep, nested path to the property is used, any manipulations with the model preserve observers in place.
- one-/two-way bindings with complex object observers, so even if deep, nested path to the property is used, any manipulations with the model preserve observers in place
- modules. Can load: Mask, Html, Javascript, Css, Json
- custom attribute handlers
- designed to be used with other libraries. For example, with small wrappers we can encapsulate twitter bootstrap markups and widgets initializations
- high code reuse
......@@ -34,43 +35,48 @@ To mention is, that the templates and the components can be rendered on the serv
#### IncludeJS
[web-page](http://atmajs.com/include) [github](https://github.com/atmajs/IncludeJS)
[web-page](http://atmajs.com/include) [GitHub](https://github.com/atmajs/IncludeJS)
— is created to load component's resources and to work in browsers and node.js the same way.
— is created to load component's resources and to work in browsers and Node.js the same way.
Some key points of the library are:
- no requirements for the module definition, but supports several: CommonJS and ```include.exports```
- no requirements for the module definition, but supports several: CommonJS and `include.exports`
- in-place dependency declaration with nice namespaced routing
- custom loaders. _Already implemented ```coffee, less, yml, json```_
- custom loaders. _Already implemented `coffee, less, yml, json`_
- lazy modules
- better debugging: loads javascript in browsers via ```script src='x'```
- for production builder can combine and optimize all resources into single ```*.js``` and single ```*.css```. All the templates are embedded into main ```*.html```. _Developing a web page using Atma node application module, builder also creates additionally single js/css/html files per page from the components that are specific to a page_
- better debugging: loads javascript in browsers via `script src='x'`
- for production builder can combine and optimize all resources into single `*.js` and single `*.css`. All the templates are embedded into main `*.html`. _Developing a web page using Atma Node.js application module, builder also creates additionally single js/css/html files per page from the components that are specific to a page_
##### µTest
[github](https://github.com/atmajs/utest)
[GitHub](https://github.com/atmajs/utest)
— _inspired by Buster.JS_ Simplifies unit test creation and runs them in node.js or in browser-slave(s) environments. All the Atma.js libraries are tested using the µTest.
— Simplifies unit-test creation and runs them in Node.js or in browser-slave(s) environments. All the Atma.js libraries are tested using the µTest.
##### DomTest
[GitHub](https://github.com/atmajs/domtest)
The module is embedded into the µTest library and allows the developer to easily write test suites for the UI using MaskJS syntax.
##### Ruta
[github](https://github.com/atmajs/Ruta)
[GitHub](https://github.com/atmajs/Ruta)
— is not only an url routing via History API or ```hashchange```, but it implements a Route-Value Collection for adding/retrieving any object by the route.
— is not only an url routing via History API or `hashchange`, but it implements a Route-Value Collection for adding/retrieving any object by the route.
#### Atma.Toolkit
[github](https://github.com/atmajs/Atma.Toolkit)
[GitHub](https://github.com/atmajs/Atma.Toolkit)
— command-line tool, which runs unit tests, builds applications, runs node.js ```bash``` scripts, creates static file server with live reload feature, etc.
— command-line tool, which runs unit tests, builds applications, runs Node.js `bash` scripts, creates static file server with live reload feature, etc.
### Mask.Animation
[github](https://github.com/atmajs/mask-animation)
[GitHub](https://github.com/atmajs/mask-animation)
— CSS3 and sprite animations for MaskJS.
### Atma.js Server Application
[web-page](http://atmajs.com/atma-server) [github](https://github.com/atmajs/atma-server)
[web-page](http://atmajs.com/atma-server) [GitHub](https://github.com/atmajs/atma-server)
— a connect middle-ware. All the routes are declared in configuration files, and there are 3 types of endpoints:
......@@ -80,39 +86,39 @@ Some key points of the library are:
Pages benefits from component based approach. Each component's controller can define caching settings, so that the component renders only once. Developer can restrict any component for the server side rendering only, so that the controller itself and any sensitive data is not sent to the client. When a component is rendered, then only HTML is sent to the client, _where all bindings and event listeners are attached_. So it is extremely SEO friendly.
Here are some links you may find helpful:
- [Get Started](http://atmajs.com/get/github)
- [Mask Markup Live Test](http://atmajs.com/mask-try)
- Mask Syntax Plugins
- [Sublime](https://github.com/tenbits/sublime-mask)
- [Atom](https://github.com/tenbits/package-atom)
- [Atma.js on GitHub](https://github.com/atmajs)
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
### Implementation
The Application is split into components hierarchy, and so the application structure consists of a Component and an Application Layer. Any component has the resources (_that are component specific, like styles / templates and other nested components_) in the same folder and sub-. These makes it easer to _reuse_ them in other applications and makes it easer to develop and test them.
For the application globals dev build of Atma.js was used.
The Application is split into components hierarchy, and so the application structure consists of a component and a business logic Layer. Components have the resources (_that are component specific, like styles / templates and other nested components_) in the same or a sub-folder. These makes it easer to _reuse_ them in other applications and makes it easer to develop and test them. Atma.js is not a opinionated, but extremely flexible framework, so that the developer can choose the way he wants to structure the applications architecture. This demo application just demonstrates one possible way.
### Run
To run the main example, file access should be allowed in browser, as ```include``` loads templates with
```XMLHttpRequest```. But you can also start a built-in local server:
Open `index.html` as a file in browser or run a static server:
```bash
$ npm install -g atma # install atma.toolkit
# install atma.toolkit
$ npm install atma --global
$ atma server
```
navigate to ``` http://localhost:5777/ ```
navigate to `http://localhost:5777/`
### Build
To build the application for release, run ``` $ atma build --file index.html --output release/```. We provide also a compiled version in 'build/' directory, so you
can see how the application looks like for production.
To build the application for release, run `$ atma build --file index.html --output release/`.
## Contact
- [team@atmajs.com](mailto:team@atmajs.com)
- [Google Group QA](https://groups.google.com/forum/#!forum/atmajs)
\ No newline at end of file
- [Google Group QA](https://groups.google.com/forum/#!forum/atmajs)
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