Commit b9756473 authored by Mikael Karon's avatar Mikael Karon

Updated project to TroopJS 2.x

* Fix jshint errors.
* Clean bower_components.
* Update global, remove local learn.json.
* Only edit when dblclick on `.view label`.
* Update title from Template -> TroopJS.
* Use .toggle (to simplify reading the code).
* Add support for ESC_KEY during edit.
* Escape title in template.
* Use unminified versions of components.
* Remove comments from html.
* Remove `type` from script attribute.
* Remove `baseUrl` from requirejs config.
* Convert double to single quotes for javascript (in index.html).
* Move troopjs -> troopjs_require and update the main index.html file to reflect updates in the framework.
* Use `.focus()` instead of `.select()`.
* Use Array natives for iteration.
* Remove redundant package configuration.
* `else` and `else if` on the same row as bracket.
* No space before `:`.
* Break `define` into multiple rows.
* Simplify filter.
* Stay consistent with anonymous function usage.
* Add newlines.
* Apply the same fix as in tastejs/todomvc@dca5107.
* Don't quote keys.
* Remove self evident comments.
* Update storage code to _always_ restore UI on fail.
parent 609f6cc1
......@@ -202,7 +202,7 @@
<a href="labs/dependency-examples/canjs_require/" data-source="http://canjs.us" data-content="CanJS is a client-side, JavaScript framework that makes building rich web applications easy. The AMD version lets you use the framework in a fully modular fashion and will only what you actually need.">CanJS + RequireJS</a>
</li>
<li class="routing labs">
<a href="labs/dependency-examples/troopjs/" data-source="https://github.com/troopjs/" data-content="TroopJS attempts to package popular front-end technologies and bind them with minimal effort for the developer. It includes jQuery for DOM manipulation, ComposeJS for object composition, RequireJS for modularity and Has.js for feature detection. On top, it includes Pub/Sub support, templating, weaving (widgets to DOM) and auto-wiring.">TroopJS</a>
<a href="labs/dependency-examples/troopjs_require/" data-source="https://github.com/troopjs/" data-content="TroopJS attempts to package popular front-end technologies and bind them with minimal effort for the developer. It includes jQuery for DOM manipulation, When.js for promises, RequireJS for modularity and Has.js for feature detection. On top, it includes Pub/Sub support, templating, weaving (widgets to DOM) and auto-wiring.">TroopJS + RequireJS</a>
</li>
<li class="routing labs">
<a href="labs/dependency-examples/thorax_lumbar/public/" data-source="http://walmartlabs.github.com/lumbar" data-content="An opinionated, battle tested Backbone + Handlebars framework to build large scale web applications. This implementation uses Lumbar, a route based module loader.">Thorax + Lumbar</a>
......
{
"name": "todomvc-troopjs",
"version": "0.0.0",
"dependencies": {
"todomvc-common": "~0.1.4",
"requirejs": "~2.1.5",
"jquery": "<=1.8.2"
}
}
<!doctype html>
<html lang="en" data-framework="troopjs">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Troop.js • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus data-weave="widget/create">
</header>
<section id="main" data-weave="widget/display">
<input id="toggle-all" type="checkbox" data-weave="widget/mark">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-weave="widget/list"></ul>
</section>
<footer id="footer" data-weave="widget/display">
<span id="todo-count" data-weave="widget/count">
<strong>0</strong> items left
</span>
<ul id="filters" data-weave="widget/filters">
<li>
<a href="#/">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed" data-weave="widget/clear">Clear completed (0)</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://github.com/mikaelkaron">Mikael Karon</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script src="bower_components/todomvc-common/base.js"></script>
<script data-main="js/app.js" src="bower_components/requirejs/require.js"></script>
</body>
</html>
/*newcap false*/
/*jshint newcap:false*/
require({
paths: {
jquery: '../bower_components/jquery/jquery',
'troopjs-bundle': 'lib/troopjs-bundle'
}
}, [
'require',
'jquery',
'troopjs-bundle'
], function Deps(parentRequire, $) {
'use strict';
// Application and plug-ins
parentRequire([
'widget/application',
'troopjs-jquery/weave',
'troopjs-jquery/destroy',
'troopjs-jquery/hashchange',
'troopjs-jquery/action'
], function App(Application) {
// Hook ready
$(document).ready(function () {
Application($(this.body), 'app/todos').start();
});
});
});
/*global define*/
define({
store: 'todos-troopjs'
});
/*global define*/
/*jshint newcap:false*/
define([
'troopjs-core/widget/application',
'troopjs-core/route/router',
'jquery'
], function ApplicationModule(Application, Router, $) {
'use strict';
function Forward(signal, deferred) {
var services = $.map(this.services, function map(service) {
return $.Deferred(function deferredSignal(deferSignal) {
service.signal(signal, deferSignal);
});
});
if (deferred) {
$.when.apply($, services).then(deferred.resolve, deferred.reject);
}
}
return Application.extend({
'sig/initialize': Forward,
'sig/finalize': Forward,
'sig/start': Forward,
'sif/stop': Forward,
services: [Router($(window))]
});
});
/*global define*/
define([
'troopjs-core/component/widget',
'jquery'
], function DisplayModule(Widget, $) {
'use strict';
function filter(item) {
return item === null;
}
return Widget.extend({
'hub:memory/todos/change': function onChange(topic, items) {
var count = $.grep(items, filter, true).length;
this.$element.toggle(count > 0);
}
});
});
/*global define*/
/*jshint quotmark:false*/
define([
'jquery'
], function EscapeModule($) {
'use strict';
var invert = function (obj) {
var result = {};
var key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
result[obj[key]] = key;
}
}
return result;
};
var fallbackKeys = function (obj) {
var keys = [];
var key;
if (obj !== Object(obj)) {
throw new TypeError('Invalid object');
}
for (key in obj) {
if (obj.hasOwnProperty(key)) {
keys[keys.length] = key;
}
}
return keys;
};
var keys = Object.keys || fallbackKeys;
var entityMap = {};
entityMap.escape = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;'
};
entityMap.unescape = invert(entityMap.escape);
var entityRegexes = {
escape: new RegExp('[' + keys(entityMap.escape).join('') + ']', 'g'),
unescape: new RegExp('(' + keys(entityMap.unescape).join('|') + ')', 'g')
};
var exports = {};
$.each(['escape', 'unescape'], function (i, method) {
exports[method] = function (string) {
if (string === null) {
return '';
}
return ('' + string).replace(entityRegexes[method], function (match) {
return entityMap[method][match];
});
};
});
return exports;
});
<%
var i = data.i;
var item = data.item;
var title = data.itemTitle;
var completed = item.completed;
%>
<li class="<%= (completed ? 'completed' : 'active') %>">
<div class="view" data-action="prepare(<%= i %>)">
<input class="toggle" type="checkbox" <%= (completed ? 'checked' : '') %> data-action="status(<%= i %>)">
<label><%= title %></label>
<button class="destroy" data-action="delete(<%= i %>)"></button>
</div>
<input class="edit" data-action="commit(<%= i %>)">
</li>
/*global define*/
define([
'./escape',
'troopjs-core/component/widget',
'troopjs-core/store/local',
'jquery',
'troopjs-requirejs/template!./item.html'
], function ListModule(Escaper, Widget, store, $, template) {
'use strict';
var ENTER_KEY = 13;
var ESCAPE_KEY = 27;
var FILTER_ACTIVE = 'filter-active';
var FILTER_COMPLETED = 'filter-completed';
function filter(item) {
return item === null;
}
return Widget.extend(function ListWidget() {
var self = this;
// Defer initialization
$.Deferred(function deferredInit(deferInit) {
// Defer get
$.Deferred(function deferredGet(deferGet) {
store.get(self.config.store, deferGet);
})
.done(function doneGet(items) {
// Set items (empty or compacted) - then resolve
store.set(self.config.store, items === null ? [] : $.grep(items, filter, true), deferInit);
});
})
.done(function doneInit(items) {
// Iterate each item
$.each(items, function itemIterator(i, item) {
// Append to self
self.append(template, {
i: i,
item: item,
itemTitle: Escaper.escape(item.title)
});
});
})
.done(function doneInit(items) {
self.publish('todos/change', items);
});
}, {
'hub/todos/add': function onAdd(topic, title) {
var self = this;
// Defer set
$.Deferred(function deferredSet(deferSet) {
// Defer get
$.Deferred(function deferredGet(deferGet) {
store.get(self.config.store, deferGet);
})
.done(function doneGet(items) {
// Get the next index
var i = items.length;
// Create new item, store in items
var item = items[i] = {
completed: false,
title: title
};
// Append new item to self
self.append(template, {
i: i,
item: item,
itemTitle: Escaper.escape(item.title)
});
// Set items and resolve set
store.set(self.config.store, items, deferSet);
});
})
.done(function doneSet(items) {
self.publish('todos/change', items);
});
},
'hub/todos/mark': function onMark(topic, value) {
this.$element.find(':checkbox').prop('checked', value).change();
},
'hub/todos/clear': function onClear() {
this.$element.find('.completed .destroy').click();
},
'hub:memory/todos/filter': function onFilter(topic, filter) {
var $element = this.$element;
switch (filter) {
case '/completed':
$element
.removeClass(FILTER_ACTIVE)
.addClass(FILTER_COMPLETED);
break;
case '/active':
$element
.removeClass(FILTER_COMPLETED)
.addClass(FILTER_ACTIVE);
break;
default:
$element.removeClass(FILTER_ACTIVE + ' ' + FILTER_COMPLETED);
}
},
'dom/action.change.click.dblclick.focusout.keyup': $.noop,
'dom/action/status.change': function onStatus(topic, $event, index) {
var self = this;
var $target = $($event.target);
var completed = $target.prop('checked');
// Update UI
$target
.closest('li')
.toggleClass('completed', completed)
.toggleClass('active', !completed);
// Defer set
$.Deferred(function deferredSet(deferSet) {
// Defer get
$.Deferred(function deferredGet(deferGet) {
store.get(self.config.store, deferGet);
})
.done(function doneGet(items) {
// Update completed
items[index].completed = completed;
// Set items and resolve set
store.set(self.config.store, items, deferSet);
});
})
.done(function doneSet(items) {
self.publish('todos/change', items);
});
},
'dom/action/delete.click': function onDelete(topic, $event, index) {
var self = this;
// Update UI
$($event.target)
.closest('li')
.remove();
// Defer set
$.Deferred(function deferredSet(deferSet) {
// Defer get
$.Deferred(function deferredGet(deferGet) {
// Get the items
store.get(self.config.store, deferGet);
})
.done(function doneGet(items) {
// Delete item
items[index] = null;
// Set items and resolve set
store.set(self.config.store, items, deferSet);
});
})
.done(function doneSet(items) {
self.publish('todos/change', items);
});
},
'dom/action/prepare.dblclick': function onPrepare(topic, $event, index) {
var self = this;
// Get LI and update
var $li = $($event.target)
.closest('li')
.addClass('editing');
// Get INPUT and disable
var $input = $li
.find('input')
.prop('disabled', true);
// Defer get
$.Deferred(function deferredGet(deferGet) {
// Get items
store.get(self.config.store, deferGet);
})
.done(function doneGet(items) {
// Update input value, enable and select
$input
.val(items[index].title)
.removeProp('disabled')
.focus();
})
.fail(function failGet() {
$li.removeClass('editing');
});
},
'dom/action/commit.keyup': function onCommitKeyUp(topic, $event) {
var $target = $($event.target);
var keyCode = $event.originalEvent.keyCode;
if (keyCode === ENTER_KEY) {
$target.focusout();
}
if (keyCode === ESCAPE_KEY) {
$target
.closest('li.editing')
.removeClass('editing');
$target.val(store.get(this.config.store));
}
},
'dom/action/commit.focusout': function onCommitFocusOut(topic, $event, index) {
var self = this;
var $target = $($event.target);
var title = $target.val().trim();
if (title === '') {
$target
.closest('li.editing')
.removeClass('editing')
.find('.destroy')
.click();
} else {
// Defer set
$.Deferred(function deferredSet(deferSet) {
// Disable
$target.prop('disabled', true);
// Defer get
$.Deferred(function deferredGet(deferGet) {
// Get items
store.get(self.config.store, deferGet);
})
.done(function doneGet(items) {
// Update text
items[index].title = title;
// Set items and resolve set
store.set(self.config.store, items, deferSet);
});
})
.done(function doneSet(items) {
// Update UI
$target
.closest('li')
.removeClass('editing')
.find('label')
.text(title);
self.publish('todos/change', items);
})
.always(function alwaysSet() {
// Enable
$target.removeProp('disabled');
});
}
}
});
});
{
"name" : "todomvc-troopjs",
"version" : "2.0.0-SNAPSHOT",
"main" : "index.html",
"dependencies" : {
"todomvc-common" : "*",
"requirejs" : "~2.1",
"jquery" : "~2.0",
"troopjs-bundle" : "https://github.com/troopjs/troopjs-bundle/archive/build/2.x.zip"
}
}
/** vim: et:ts=4:sw=4:sts=4
* @license RequireJS 2.1.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* @license RequireJS 2.1.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details
*/
......@@ -12,7 +12,7 @@ var requirejs, require, define;
(function (global) {
var req, s, head, baseElement, dataMain, src,
interactiveScript, currentlyAddingScript, mainScript, subPath,
version = '2.1.5',
version = '2.1.6',
commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
jsSuffixRegExp = /\.js$/,
......@@ -22,7 +22,7 @@ var requirejs, require, define;
hasOwn = op.hasOwnProperty,
ap = Array.prototype,
apsp = ap.splice,
isBrowser = !!(typeof window !== 'undefined' && navigator && document),
isBrowser = !!(typeof window !== 'undefined' && navigator && window.document),
isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
//PS3 indicates loaded and complete, but need to wait for complete
//specifically. Sequence is 'loading', 'loaded', execution,
......@@ -134,6 +134,10 @@ var requirejs, require, define;
return document.getElementsByTagName('script');
}
function defaultOnError(err) {
throw err;
}
//Allow getting a global that expressed in
//dot notation, like 'a.b.c'.
function getGlobal(value) {
......@@ -500,7 +504,12 @@ var requirejs, require, define;
fn(defined[id]);
}
} else {
getModule(depMap).on(name, fn);
mod = getModule(depMap);
if (mod.error && name === 'error') {
fn(mod.error);
} else {
mod.on(name, fn);
}
}
}
......@@ -571,7 +580,13 @@ var requirejs, require, define;
id: mod.map.id,
uri: mod.map.url,
config: function () {
return (config.config && getOwn(config.config, mod.map.id)) || {};
var c,
pkg = getOwn(config.pkgs, mod.map.id);
// For packages, only support config targeted
// at the main module.
c = pkg ? getOwn(config.config, mod.map.id + '/' + pkg.main) :
getOwn(config.config, mod.map.id);
return c || {};
},
exports: defined[mod.map.id]
});
......@@ -840,8 +855,13 @@ var requirejs, require, define;
if (this.depCount < 1 && !this.defined) {
if (isFunction(factory)) {
//If there is an error listener, favor passing
//to that instead of throwing an error.
if (this.events.error) {
//to that instead of throwing an error. However,
//only do it for define()'d modules. require
//errbacks should not be called for failures in
//their callbacks (#699). However if a global
//onError is set, use that.
if ((this.events.error && this.map.isDefine) ||
req.onError !== defaultOnError) {
try {
exports = context.execCb(id, factory, depExports, exports);
} catch (e) {
......@@ -869,8 +889,8 @@ var requirejs, require, define;
if (err) {
err.requireMap = this.map;
err.requireModules = [this.map.id];
err.requireType = 'define';
err.requireModules = this.map.isDefine ? [this.map.id] : null;
err.requireType = this.map.isDefine ? 'define' : 'require';
return onError((this.error = err));
}
......@@ -1093,7 +1113,7 @@ var requirejs, require, define;
}));
if (this.errback) {
on(depMap, 'error', this.errback);
on(depMap, 'error', bind(this, this.errback));
}
}
......@@ -1605,7 +1625,7 @@ var requirejs, require, define;
},
/**
* Executes a module callack function. Broken out as a separate function
* Executes a module callback function. Broken out as a separate function
* solely to allow the build system to sequence the files in the built
* layer in the right sequence.
*
......@@ -1643,7 +1663,7 @@ var requirejs, require, define;
onScriptError: function (evt) {
var data = getScriptData(evt);
if (!hasPathFallback(data.id)) {
return onError(makeError('scripterror', 'Script error', evt, [data.id]));
return onError(makeError('scripterror', 'Script error for: ' + data.id, evt, [data.id]));
}
}
};
......@@ -1772,9 +1792,7 @@ var requirejs, require, define;
* function. Intercept/override it if you want custom error handling.
* @param {Error} err the error object.
*/
req.onError = function (err) {
throw err;
};
req.onError = defaultOnError;
/**
* Does the request to load a module for the browser case.
......@@ -1906,24 +1924,31 @@ var requirejs, require, define;
//baseUrl, if it is not already set.
dataMain = script.getAttribute('data-main');
if (dataMain) {
//Preserve dataMain in case it is a path (i.e. contains '?')
mainScript = dataMain;
//Set final baseUrl if there is not already an explicit one.
if (!cfg.baseUrl) {
//Pull off the directory of data-main for use as the
//baseUrl.
src = dataMain.split('/');
src = mainScript.split('/');
mainScript = src.pop();
subPath = src.length ? src.join('/') + '/' : './';
cfg.baseUrl = subPath;
dataMain = mainScript;
}
//Strip off any trailing .js since dataMain is now
//Strip off any trailing .js since mainScript is now
//like a module name.
dataMain = dataMain.replace(jsSuffixRegExp, '');
mainScript = mainScript.replace(jsSuffixRegExp, '');
//If mainScript is still a path, fall back to dataMain
if (req.jsExtRegExp.test(mainScript)) {
mainScript = dataMain;
}
//Put the data-main script in the files to load.
cfg.deps = cfg.deps ? cfg.deps.concat(dataMain) : [dataMain];
cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];
return true;
}
......@@ -1951,12 +1976,13 @@ var requirejs, require, define;
//This module may not have dependencies
if (!isArray(deps)) {
callback = deps;
deps = [];
deps = null;
}
//If no name, and callback is a function, then figure out if it a
//CommonJS thing with dependencies.
if (!deps.length && isFunction(callback)) {
if (!deps && isFunction(callback)) {
deps = [];
//Remove comments from the callback string,
//look for require calls, and pull them into the dependencies,
//but only if there are function args.
......
/* base.css overrides */
#footer, #main {
display: none;
}
.filter-active .completed,
.filter-completed .active {
display: none;
......
<!doctype html>
<html lang="en" data-framework="troopjs">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>TroopJS • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus data-weave="troopjs-todos/widget/create">
</header>
<section id="main" data-weave="troopjs-todos/widget/display">
<input id="toggle-all" type="checkbox" data-weave="troopjs-todos/widget/mark">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-weave="troopjs-todos/widget/list"></ul>
</section>
<footer id="footer" data-weave="troopjs-todos/widget/display">
<span id="todo-count" data-weave="troopjs-todos/widget/count"><strong>1</strong> item left</span>
<ul id="filters" data-weave="troopjs-todos/widget/filters">
<li>
<a class="selected" href="#/">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed" data-weave="troopjs-todos/widget/clear">Clear completed (1)</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="https://github.com/mikaelkaron">Mikael Karon</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script src="bower_components/todomvc-common/base.js"></script>
<script>
'use strict';
var require = {
packages: [{
name: 'jquery',
location: 'bower_components/jquery',
main: 'jquery'
}, {
name: 'troopjs-bundle',
location: 'bower_components/troopjs-bundle',
main: 'maxi'
}, {
name: 'troopjs-todos',
location: 'js'
}],
map: {
'*': {
template: 'troopjs-requirejs/template'
}
},
deps: [ 'require', 'jquery', 'troopjs-bundle' ],
callback: function Boot (contextRequire, jQuery) {
contextRequire([ 'troopjs-browser/application/widget', 'troopjs-browser/route/widget' ], function Strap (Application, RouteWidget) {
jQuery(function ($) {
Application($('html'), 'bootstrap', RouteWidget($(window), 'route')).start();
});
});
}
};
</script>
<script src="bower_components/requirejs/require.js" async="async"></script>
</body>
</html>
/*global define*/
/*global define:false */
define([
'troopjs-core/component/widget',
'jquery'
], function ClearModule(Widget, $) {
'troopjs-browser/component/widget',
'poly/array'
], function ClearModule(Widget) {
'use strict';
function filter(item) {
return item === null || !item.completed;
return item !== null && item.completed;
}
return Widget.extend({
'hub:memory/todos/change': function onChange(topic, items) {
var count = $.grep(items, filter, true).length;
'hub:memory/todos/change': function onChange(items) {
var count = items.filter(filter).length;
this.$element.text('Clear completed (' + count + ')').toggle(count > 0);
},
......
/*global define*/
/*global define:false */
define([
'troopjs-core/component/widget',
'jquery'
], function CountModule(Widget, $) {
'troopjs-browser/component/widget',
'poly/array'
], function CountModule(Widget) {
'use strict';
function filter(item) {
return item === null || item.completed;
return item !== null && !item.completed;
}
return Widget.extend({
'hub:memory/todos/change': function onChange(topic, items) {
var count = $.grep(items, filter, true).length;
'hub:memory/todos/change': function onChange(items) {
var count = items.filter(filter).length;
this.$element.html('<strong>' + count + '</strong> ' + (count === 1 ? 'item' : 'items') + ' left');
}
......
/*global define*/
define([
'troopjs-core/component/widget'
], function CreateModule(Widget) {
/*global define:false */
define([ 'troopjs-browser/component/widget' ], function CreateModule(Widget) {
'use strict';
var ENTER_KEY = 13;
return Widget.extend({
'dom/keyup': function onKeyUp(topic, $event) {
var $element = this.$element;
'dom/keyup': function onKeyUp($event) {
var me = this;
var $element = me.$element;
var value;
if ($event.keyCode === ENTER_KEY) {
value = $element.val().trim();
if (value !== '') {
this.publish('todos/add', value);
$element.val('');
me.publish('todos/add', value)
.then(function () {
$element.val('');
});
}
}
}
......
/*global define:false */
define([
'troopjs-browser/component/widget',
'poly/array'
], function DisplayModule(Widget) {
'use strict';
function filter(item) {
return item !== null;
}
return Widget.extend({
'hub:memory/todos/change': function onChange(items) {
this.$element.toggle(items.some(filter));
}
});
});
/*global define*/
/*global define:false */
define([
'troopjs-core/component/widget',
'troopjs-browser/component/widget',
'jquery'
], function FiltersModule(Widget, $) {
'use strict';
return Widget.extend({
'hub:memory/route': function onRoute(topic, uri) {
'hub:memory/route': function onRoute(uri) {
this.publish('todos/filter', uri.source);
},
'hub:memory/todos/filter': function onFilter(topic, filter) {
filter = filter || '/';
// Update UI
'hub:memory/todos/filter': function onFilter(filter) {
$('a[href^="#"]')
.removeClass('selected')
.filter('[href="#' + filter + '"]')
.filter('[href="#' + (filter || '/') + '"]')
.addClass('selected');
}
});
......
<%
var i = data.i;
var item = data.item;
var completed = item.completed;
function htmlEscape(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
%>
<li class="<%= (completed ? 'completed' : 'active') %>" data-index="<%= i %>">
<div class='view'>
<input class="toggle" type="checkbox" <%= (completed ? 'checked' : '') %>>
<label><%= htmlEscape(item.title) %></label>
<button class="destroy"></button>
</div>
<input class="edit">
</li>
/*global define:false */
define([
'troopjs-browser/component/widget',
'troopjs-data/store/component',
'troopjs-browser/store/adapter/local',
'jquery',
'template!./item.html',
'poly/array'
], function ListModule(Widget, Store, Adapter, $, template) {
/*jshint newcap:false*/
'use strict';
var ARRAY_SLICE = Array.prototype.slice;
var ENTER_KEY = 13;
var ESC_KEY = 27;
var FILTER_ACTIVE = 'filter-active';
var FILTER_COMPLETED = 'filter-completed';
var KEY = 'todos-troopjs';
var STORE = 'store';
function filter(item) {
return item !== null;
}
return Widget.extend(function ListWidget() {
this[STORE] = Store(Adapter());
}, {
'sig/start': function () {
var me = this;
var store = me[STORE];
return store.ready(function () {
return store.get(KEY, function (getItems) {
return store.set(KEY, getItems && getItems.filter(filter) || [], function (setItems) {
setItems.forEach(function (item, i) {
me.append(template, {
i: i,
item: item
});
});
me.publish('todos/change', setItems);
});
});
});
},
'hub/todos/add': function onAdd(title) {
var me = this;
var store = me[STORE];
return store.ready(function () {
return store.get(KEY, function (getItems) {
var i = getItems.length;
var item = getItems[i] = {
completed: false,
title: title
};
me.append(template, {
i: i,
item: item
});
return store.set(KEY, getItems, function (setItems) {
me.publish('todos/change', setItems);
});
});
})
.yield(ARRAY_SLICE.call(arguments));
},
'hub/todos/mark': function onMark(value) {
this.$element.find(':checkbox').prop('checked', value).change();
},
'hub/todos/clear': function onClear() {
this.$element.find('.completed .destroy').click();
},
'hub:memory/todos/filter': function onFilter(filter) {
var $element = this.$element;
switch (filter) {
case '/completed':
$element
.removeClass(FILTER_ACTIVE)
.addClass(FILTER_COMPLETED);
break;
case '/active':
$element
.removeClass(FILTER_COMPLETED)
.addClass(FILTER_ACTIVE);
break;
default:
$element.removeClass([FILTER_ACTIVE, FILTER_COMPLETED].join(' '));
}
},
'dom:.toggle/change': function onToggleChange($event) {
var me = this;
var store = me[STORE];
var $target = $($event.currentTarget);
var completed = $target.prop('checked');
var $li = $target.closest('li');
var index = $li.data('index');
$li
.toggleClass('completed', completed)
.toggleClass('active', !completed);
store.ready(function () {
return store.get(KEY, function (getItems) {
getItems[index].completed = completed;
return store.set(KEY, getItems, function (setItems) {
me.publish('todos/change', setItems);
});
});
});
},
'dom:.destroy/click': function onDestroyClick($event) {
var me = this;
var store = me[STORE];
var $li = $($event.currentTarget).closest('li');
var index = $li.data('index');
$li.remove();
store.ready(function () {
return store.get(KEY, function (getItems) {
getItems[index] = null;
return store.set(KEY, getItems, function (setItems) {
me.publish('todos/change', setItems);
});
});
});
},
'dom:.view label/dblclick': function onViewDblClick($event) {
var me = this;
var store = me[STORE];
var $li = $($event.currentTarget).closest('li');
var index = $li.data('index');
var $input = $li.find('input');
$li.addClass('editing');
$input.prop('disabled', true);
store.ready(function () {
return store.get(KEY, function (items) {
$input
.val(items[index].title)
.prop('disabled', false)
.focus();
});
}, function () {
$input.prop('disabled', false);
$li.removeClass('editing');
});
},
'dom:.edit/keyup': function onEditKeyUp($event) {
var $li = $($event.currentTarget).closest('li');
switch ($event.keyCode) {
case ENTER_KEY:
$li
.find('input')
.focusout();
break;
case ESC_KEY:
$li
.find('input')
.val($li.find('label').text())
.focusout();
break;
}
},
'dom:.edit/focusout': function onEditFocusOut($event) {
var me = this;
var store = me[STORE];
var $target = $($event.currentTarget);
var title = $target.val().trim();
if (title === '') {
$target
.closest('li.editing')
.removeClass('editing')
.find('.destroy')
.click();
} else {
$target.prop('disabled', true);
store.ready(function () {
return store.get(KEY, function (getItems) {
var $li = $target.closest('li');
var index = $li.data('index');
getItems[index].title = title;
return store.set(KEY, getItems, function (setItems) {
$li
.removeClass('editing')
.find('label')
.text(title);
me.publish('todos/change', setItems);
});
})
.ensure(function () {
$target.prop('disabled', false);
});
});
}
}
});
});
/*global define*/
/*global define:false */
define([
'troopjs-browser/component/widget',
'jquery',
'troopjs-core/component/widget'
], function MarkModule($, Widget) {
'poly/array'
], function MarkModule(Widget, $) {
'use strict';
return Widget.extend({
'hub:memory/todos/change': function onChange(topic, items) {
'hub:memory/todos/change': function onChange(items) {
var total = 0;
var count = 0;
var completed = 0;
var $element = this.$element;
$.each(items, function iterator(i, item) {
items.forEach(function (item) {
if (item === null) {
return;
}
if (item.completed) {
count++;
completed++;
}
total++;
});
$element
.prop('indeterminate', count !== 0 && count !== total)
.prop('checked', count === total);
if (completed === 0) {
$element
.prop('indeterminate', false)
.prop('checked', false);
} else if (completed === total) {
$element
.prop('indeterminate', false)
.prop('checked', true);
} else {
$element
.prop('indeterminate', true)
.prop('checked', false);
}
},
'dom/change': function onMark(topic, $event) {
'dom/change': function onMark($event) {
this.publish('todos/mark', $($event.target).prop('checked'));
}
});
......
......@@ -1694,11 +1694,14 @@
"homepage": "troopjs.com",
"examples": [{
"name": "Dependency Example",
"url": "labs/dependency-examples/troopjs"
"url": "labs/dependency-examples/troopjs_require"
}],
"link_groups": [{
"heading": "Official Resources",
"links": [{
"name": "TODOs Application (latest)",
"url": "https://github.com/troopjs/troopjs-todos"
}, {
"name": "TroopJS on GitHub",
"url": "https://github.com/troopjs"
}]
......
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