Commit 2f4933c8 authored by Sindre Sorhus's avatar Sindre Sorhus

Merge pull request #442 from mkuklis/gh-pages

Added todo app built in Twitter Flight
parents 20f31f00 a93df88f
......@@ -196,6 +196,9 @@
<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>
</li>
<li class="routing labs">
<a href="labs/dependency-examples/flight/" data-source="http://twitter.github.com/flight" data-content="Flight is a lightweight, component-based JavaScript framework that maps behavior to DOM nodes. Twitter uses it for their web applications.">Flight</a>
</li>
</ul>
<hr>
<h2>Real-time</h2>
......
'use strict';
define(
[
'./data/todos',
'./data/stats',
'./ui/new_item',
'./ui/todo_list',
'./ui/stats',
'./ui/main_selector',
'./ui/toggle_all'
],
function (
TodosData,
StatsData,
NewItemUI,
TodoListUI,
StatsUI,
MainSelectorUI,
ToggleAllUI) {
var initialize = function () {
StatsData.attachTo(document);
TodosData.attachTo(document);
NewItemUI.attachTo('#new-todo');
MainSelectorUI.attachTo("#main");
StatsUI.attachTo('#footer');
ToggleAllUI.attachTo('#toggle-all');
TodoListUI.attachTo('#todo-list');
}
return {
initialize: initialize
};
}
);
'use strict';
define(
[
'flight/component',
'../store'
],
function (defineComponent, dataStore) {
return defineComponent(stats);
function stats() {
this.recount = function () {
var todos = dataStore.all();
var all = todos.length;
var remaining = todos.reduce(function (memo, each) {
return memo += (each.completed) ? 0 : 1;
}, 0);
this.trigger('dataStatsCounted', {
all: all,
remaining: remaining,
completed: all - remaining,
filter: localStorage.getItem('filter') || ''
});
}
this.after('initialize', function () {
this.on(document, 'dataTodosLoaded', this.recount);
this.on(document, 'dataTodoAdded', this.recount);
this.on(document, 'dataTodoRemoved', this.recount);
this.on(document, 'dataTodoToggled', this.recount);
this.on(document, 'dataClearedCompleted', this.recount);
this.on(document, 'dataTodoToggledAll', this.recount);
});
}
}
);
'use strict';
define(
[
'flight/component',
'../store'
],
function (defineComponent, dataStore) {
return defineComponent(todos);
function todos() {
var filter;
this.add = function (e, data) {
var todo = dataStore.save({
title: data.title,
completed: false
});
this.trigger('dataTodoAdded', { todo: todo, filter: filter });
}
this.remove = function (e, data) {
var todo = dataStore.destroy(data.id);
this.trigger('dataTodoRemoved', todo);
}
this.load = function (e, data) {
var todos;
filter = localStorage.getItem('filter');
todos = this.find();
this.trigger('dataTodosLoaded', { todos: todos });
}
this.update = function (e, data) {
dataStore.save(data);
}
this.toggleCompleted = function (e, data) {
var eventType;
var todo = dataStore.get(data.id);
todo.completed = !todo.completed;
dataStore.save(todo);
eventType = (filter) ? 'dataTodoRemoved' : 'dataTodoToggled';
this.trigger(eventType, todo);
}
this.toggleAllCompleted = function (e, data) {
dataStore.updateAll({ completed: data.completed });
this.trigger('dataTodoToggledAll', { todos: this.find(filter) });
}
this.filter = function (e, data) {
var todos;
localStorage.setItem('filter', data.filter);
filter = data.filter;
todos = this.find();
this.trigger('dataTodosFiltered', { todos: todos });
}
this.find = function () {
var todos;
if (filter) {
todos = dataStore.find(function (each) {
return (typeof each[filter] !== 'undefined') ? each.completed : !each.completed;
});
}
else {
todos = dataStore.all();
}
return todos;
}
this.clearCompleted = function () {
var todos;
dataStore.destroyAll({ completed: true });
todos = dataStore.all();
this.trigger('dataClearedCompleted', { todos: todos });
}
this.after('initialize', function () {
this.on(document, 'uiAddRequested', this.add);
this.on(document, 'uiUpdateRequested', this.update);
this.on(document, 'uiRemoveRequested', this.remove);
this.on(document, 'uiLoadRequested', this.load);
this.on(document, 'uiToggleRequested', this.toggleCompleted);
this.on(document, 'uiToggleAllRequested', this.toggleAllCompleted);
this.on(document, 'uiClearRequested', this.clearCompleted);
this.on(document, 'uiFilterRequested', this.filter);
});
}
}
);
'use strict';
require.config({
baseUrl: './',
paths: {
jquery: 'components/jquery/jquery',
es5shim: 'components/es5-shim/es5-shim',
es5sham: 'components/es5-shim/es5-sham',
text: 'components/requirejs/plugins/text'
},
map: {
'*': {
'flight/component': 'components/flight/lib/component',
'depot': 'components/depot/depot'
}
},
shim: {
'components/flight/lib/index': {
deps: ['jquery', 'es5shim', 'es5sham']
},
'app/js/app': {
deps: ['components/flight/lib/index']
}
}
});
require(['app/js/app'], function (App) {
App.initialize();
});
'use strict';
define(
[
'depot'
],
function (depot) {
return depot('todos', { idAttribute: 'id' });
}
);
'use strict';
define(
[
'flight/component'
],
function (defineComponent) {
return defineComponent(mainSelector);
function mainSelector() {
this.toggle = function (e, data) {
var toggle = data.all > 0;
this.$node.toggle(toggle);
}
this.after('initialize', function () {
this.$node.hide();
this.on(document, 'dataStatsCounted', this.toggle);
});
}
}
);
'use strict';
define(
[
'flight/component'
],
function (defineComponent) {
return defineComponent(newItem);
function newItem() {
var ENTER_KEY = 13;
this.createOnEnter = function (e) {
if (e.which !== ENTER_KEY ||
!this.$node.val().trim()) {
return;
}
this.trigger('uiAddRequested', {
title: this.$node.val().trim()
});
this.$node.val('');
}
this.after('initialize', function () {
this.on('keydown', this.createOnEnter);
});
}
}
);
'use strict';
define(
[
'flight/component',
'./with_filters',
'text!app/templates/stats.html',
'../utils'
],
function (defineComponent, withFilters, statsTmpl, utils) {
return defineComponent(stats, withFilters);
function stats() {
var template = utils.tmpl(statsTmpl);
this.defaultAttrs({
clearCompletedSelector: '#clear-completed'
});
this.render = function (e, data) {
var toggle = data.all > 0;
this.$node.html(template(data));
this.$node.toggle(toggle);
this.markSelected(data.filter);
}
this.clearCompleted = function (e, data) {
this.trigger('uiClearRequested');
}
this.after('initialize', function () {
this.$node.hide();
this.on(document, 'dataStatsCounted', this.render);
this.on('click', { 'clearCompletedSelector': this.clearCompleted });
});
}
}
);
'use strict';
define(
[
'flight/component',
'text!app/templates/todo.html',
'../utils'
],
function (defineComponent, todoTmpl, utils) {
return defineComponent(todoList);
function todoList() {
var ENTER_KEY = 13;
var template = utils.tmpl(todoTmpl);
this.defaultAttrs({
destroySelector: 'button.destroy',
toggleSelector: 'input.toggle',
labelSelector: 'label',
editSelector: '.edit'
});
this.renderAll = function (e, data) {
this.$node.html('');
data.todos.forEach(function (each) {
this.render(e, { todo: each });
}, this);
}
this.render = function (e, data) {
if (e.type == 'dataTodoAdded'
&& data.filter == 'completed') {
return;
}
this.$node.append(template(data.todo));
}
this.edit = function (e, data) {
var $todoEl = $(data.el).parents('li');
$todoEl.addClass('editing');
this.select('editSelector').focus();
}
this.requestUpdate = function (e, data) {
var $inputEl = $(e.currentTarget);
var $todoEl = $inputEl.parents('li');
var value = $inputEl.val().trim();
var id = $todoEl.attr('id');
if (!$todoEl.hasClass('editing')) {
return;
}
!$todoEl.removeClass('editing');
if (value) {
$todoEl.find('label').html(value);
this.trigger('uiUpdateRequested', { id: id, title: value });
} else {
this.trigger('uiRemoveRequested', { id: id });
}
},
this.requestUpdateOnEnter = function (e, data) {
if (e.which === ENTER_KEY) {
this.requestUpdate(e, data);
}
},
this.requestRemove = function (e, data) {
var id = $(data.el).attr('id').split('_')[1];
this.trigger('uiRemoveRequested', { id: id });
}
this.remove = function (e, data) {
var $todoEl = this.$node.find('#' + data.id);
$todoEl.remove();
}
this.toggle = function (e, data) {
var $todoEl = $(data.el).parents('li');
$todoEl.toggleClass('completed');
this.trigger('uiToggleRequested', { id: $todoEl.attr('id') });
}
this.after('initialize', function () {
this.on(document, 'dataTodoAdded', this.render);
this.on(document, 'dataTodosLoaded', this.renderAll);
this.on(document, 'dataTodosFiltered', this.renderAll);
this.on(document, 'dataClearedCompleted', this.renderAll);
this.on(document, 'dataTodoToggledAll', this.renderAll);
this.on(document, 'dataTodoRemoved', this.remove);
this.on('click', { 'destroySelector': this.requestRemove });
this.on('click', { 'toggleSelector': this.toggle });
this.on('dblclick', { 'labelSelector': this.edit });
this.$node.on('blur', '.edit', this.bind(this.requestUpdate));
this.$node.on('keydown', '.edit', this.bind(this.requestUpdateOnEnter));
// these don't work
// this.on(this.attr.editSelector, 'blur', this.requestUpdate);
// this.on('blur', { 'editSelector': this.requestUpdate });
this.trigger('uiLoadRequested');
});
}
}
);
'use strict';
define(
[
'flight/component'
],
function (defineComponent) {
return defineComponent(toggleAll);
function toggleAll() {
this.toggleAllComplete = function () {
this.trigger('uiToggleAllRequested', {
completed: this.$node.is(':checked')
});
}
this.toggleCheckbox = function (e, data) {
this.$node[0].checked = !data.remaining;
}
this.after('initialize', function () {
this.on('click', this.toggleAllComplete);
this.on(document, 'dataStatsCounted', this.toggleCheckbox);
});
}
}
);
'use strict';
define(
function() {
return withFilters;
function withFilters() {
this.defaultAttrs({
filterSelector: '#filters a'
});
this.chooseFilter = function (e, data) {
var filter = data.el.hash.slice(2);
this.select('filterSelector').removeClass('selected');
$(data.el).addClass('selected');
this.trigger('uiFilterRequested', { filter: filter });
}
this.markSelected = function (filter) {
this.$node.find('[href="#/' + filter + '"]').addClass('selected');
}
this.after('initialize', function() {
this.on('click', { filterSelector: this.chooseFilter });
});
}
}
);
'use strict';
// tmpl function scooped from underscore.
// http://documentcloud.github.com/underscore/#template
define(function () {
var _ = {};
// List of HTML entities for escaping.
var entityMap = {
escape: {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;'
}
};
var escapeKeys = '&<>"\'/';
var unescapeKeys = '&amp;|&lt;|&gt;|&quot;|&#x27;|&#x2F;';
// Regexes containing the keys and values listed immediately above.
var entityRegexes = {
escape: new RegExp('[' + escapeKeys + ']', 'g'),
unescape: new RegExp('(' + unescapeKeys + ')', 'g')
};
// Functions for escaping and unescaping strings to/from HTML interpolation.
['escape', 'unescape'].forEach(function(method) {
_[method] = function(string) {
if (string == null) return '';
return ('' + string).replace(entityRegexes[method], function(match) {
return entityMap[method][match];
});
};
});
var settings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
var noMatch = /(.)^/;
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
var template = function(text, data) {
var render;
// Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
}
if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
return match;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n";
try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
return template;
};
return { tmpl: template };
});
<span id="todo-count"><strong><%= remaining %></strong> <%= remaining == 1 ? 'item' : 'items' %> left</span>
<ul id="filters">
<li>
<a href="#/">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<% if (completed) { %>
<button id="clear-completed">Clear completed (<%= completed %>)</button>
<% } %>
<li id="<%= id %>" class="<%= completed ? 'completed' : '' %>">
<div class="view">
<input class="toggle" type="checkbox" <%= completed ? 'checked' : '' %>>
<label><%- title %></label>
<button id="destroy_<%= id %>" class="destroy"></button>
</div>
<input class="edit" value="<%- title %>">
</li>
/* ----------------------------------
* depot.js v0.1.0
* Licensed under The MIT License
* http://opensource.org/licenses/MIT
* ---------------------------------- */
// commonjs, amd, global
(function (name, root, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
root[name] = factory();
}
}("depot", this, function () {
"use strict";
// depot api
var api = {
save: function (record) {
var id;
if (!record[this.idAttribute]) {
record[this.idAttribute] = guid();
}
id = record[this.idAttribute];
if (this.ids.indexOf(id) >= 0) {
record = extend(this.get(id), record);
}
else {
this.ids.push(id);
localStorage.setItem(this.name, this.ids.join(","));
}
localStorage.setItem(getKey(this.name, id), JSON.stringify(record));
return record;
},
updateAll: function (data) {
var records = this.all();
records.forEach(function (record) {
record = extend(record, data);
this.save(record);
}, this);
return records;
},
find: function (criteria) {
var key, match, record;
var name = this.name;
if (!criteria) return this.all();
return this.ids.reduce(function (memo, id) {
record = jsonData(localStorage.getItem(getKey(name, id)));
match = findMatch(criteria, record);
if (match) {
memo.push(record);
}
return memo;
}, []);
},
get: function (id) {
return jsonData(localStorage.getItem(getKey(this.name, id)));
},
all: function () {
var record, name = this.name;
return this.ids.reduce(function (memo, id) {
record = localStorage.getItem(getKey(name, id));
if (record) {
memo.push(jsonData(record));
}
return memo;
}, []);
},
destroy: function (record) {
var index;
var id = (record[this.idAttribute]) ? record[this.idAttribute] : record;
var key = getKey(this.name, id);
record = jsonData(localStorage.getItem(key));
localStorage.removeItem(key);
index = this.ids.indexOf(id);
if (index != -1) this.ids.splice(index, 1);
localStorage.setItem(this.name, this.ids.join(","));
return record;
},
destroyAll: function (criteria) {
var attr, id, match, record, key;
for (var i = this.ids.length - 1; i >= 0; i--) {
id = this.ids[i];
key = getKey(this.name, id);
if (criteria) {
record = jsonData(localStorage.getItem(key));
match = findMatch(criteria, record);
if (match) {
localStorage.removeItem(key);
this.ids.splice(i, 1);
}
}
else {
localStorage.removeItem(key);
}
}
if (criteria) {
localStorage.setItem(this.name, this.ids.join(","));
}
else {
localStorage.removeItem(this.name);
this.ids = [];
}
}
};
// helpers
function jsonData(data) {
return data && JSON.parse(data);
}
function getKey(name, id) {
return name + "-" + id;
}
function findMatch(criteria, record) {
var match, attr;
if (typeof criteria == 'function') {
match = criteria(record);
}
else {
match = true;
for (attr in criteria) {
match &= (criteria[attr] === record[attr]);
}
}
return match;
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16).substring(1);
}
function guid() {
return s4() + s4() + '-' + s4() + '-' + s4() +
'-' +s4() + '-' + s4() + s4() + s4();
}
function extend(dest, source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
dest[key] = source[key];
}
}
return dest;
}
function depot(name, options) {
var store, ids;
if (!localStorage) throw new Error("localStorage not found");
store = localStorage.getItem(name);
ids = (store && store.split(",")) || [];
options = options || {};
return Object.create(api, {
name: { value: name },
store: { value: store },
ids: { value: ids, writable: true },
idAttribute: { value: options.idAttribute || '_id' }
});
}
return depot;
}));
// ==========================================
// Copyright 2013 Twitter, Inc
// Licensed under The MIT License
// http://opensource.org/licenses/MIT
// ==========================================
"use strict";
define(
[
'./utils',
'./compose'
],
function (util, compose) {
var advice = {
around: function(base, wrapped) {
return function() {
var args = util.toArray(arguments);
return wrapped.apply(this, [base.bind(this)].concat(args));
}
},
before: function(base, before) {
return this.around(base, function() {
var args = util.toArray(arguments),
orig = args.shift(),
beforeFn;
beforeFn = (typeof before == 'function') ? before : before.obj[before.fnName];
beforeFn.apply(this, args);
return (orig).apply(this, args);
});
},
after: function(base, after) {
return this.around(base, function() {
var args = util.toArray(arguments),
orig = args.shift(),
afterFn;
// this is a separate statement for debugging purposes.
var res = (orig.unbound || orig).apply(this, args);
afterFn = (typeof after == 'function') ? after : after.obj[after.fnName];
afterFn.apply(this, args);
return res;
});
},
// a mixin that allows other mixins to augment existing functions by adding additional
// code before, after or around.
withAdvice: function() {
['before', 'after', 'around'].forEach(function(m) {
this[m] = function(method, fn) {
compose.unlockProperty(this, method, function() {
if (typeof this[method] == 'function') {
return this[method] = advice[m](this[method], fn);
} else {
return this[method] = fn;
}
});
};
}, this);
}
};
return advice;
}
);
// ==========================================
// Copyright 2013 Twitter, Inc
// Licensed under The MIT License
// http://opensource.org/licenses/MIT
// ==========================================
"use strict";
define(
[
'./advice',
'./utils',
'./compose',
'./registry'
],
function(advice, utils, compose, registry) {
var functionNameRegEx = /function (.*?)\s?\(/;
var spaceCommaRegEx = /\s\,/g;
function teardownInstance(instanceInfo){
instanceInfo.events.slice().forEach(function(event) {
var args = [event.type];
event.element && args.unshift(event.element);
(typeof event.callback == 'function') && args.push(event.callback);
this.off.apply(this, args);
}, instanceInfo.instance);
}
function teardown() {
this.trigger("componentTearDown");
teardownInstance(registry.findInstanceInfo(this));
}
//teardown for all instances of this constructor
function teardownAll() {
var componentInfo = registry.findComponentInfo(this);
componentInfo && componentInfo.instances.slice().forEach(function(info) {
info.instance.teardown();
});
}
//common mixin allocates basic functionality - used by all component prototypes
//callback context is bound to component
function withBaseComponent() {
// delegate trigger, bind and unbind to an element
// if $element not supplied, use component's node
// other arguments are passed on
this.trigger = function() {
var $element, type, data;
var args = utils.toArray(arguments);
if (typeof args[args.length - 1] != "string") {
data = args.pop();
}
$element = (args.length == 2) ? $(args.shift()) : this.$node;
type = args[0];
if (window.DEBUG && window.postMessage) {
try {
window.postMessage(data, '*');
} catch(e) {
console.log('unserializable data for event',type,':',data);
throw new Error(
["The event", event.type, "on component", this.describe, "was triggered with non-serializable data"].join(" ")
);
}
}
if (typeof this.attr.eventData === 'object') {
data = $.extend(true, {}, this.attr.eventData, data);
}
return $element.trigger(type, data);
};
this.on = function() {
var $element, type, callback, originalCb;
var args = utils.toArray(arguments);
if (typeof args[args.length - 1] == "object") {
//delegate callback
originalCb = utils.delegate(
this.resolveDelegateRules(args.pop())
);
} else {
originalCb = args.pop();
}
$element = (args.length == 2) ? $(args.shift()) : this.$node;
type = args[0];
if (typeof originalCb != 'function' && typeof originalCb != 'object') {
throw new Error("Unable to bind to '" + type + "' because the given callback is not a function or an object");
}
callback = originalCb.bind(this);
callback.target = originalCb;
// if the original callback is already branded by jQuery's guid, copy it to the context-bound version
if (originalCb.guid) {
callback.guid = originalCb.guid;
}
$element.on(type, callback);
// get jquery's guid from our bound fn, so unbinding will work
originalCb.guid = callback.guid;
return callback;
};
this.off = function() {
var $element, type, callback;
var args = utils.toArray(arguments);
if (typeof args[args.length - 1] == "function") {
callback = args.pop();
}
$element = (args.length == 2) ? $(args.shift()) : this.$node;
type = args[0];
return $element.off(type, callback);
};
this.resolveDelegateRules = function(ruleInfo) {
var rules = {};
Object.keys(ruleInfo).forEach(
function(r) {
if (!this.attr.hasOwnProperty(r)) {
throw new Error('Component "' + this.describe + '" wants to listen on "' + r + '" but no such attribute was defined.');
}
rules[this.attr[r]] = ruleInfo[r];
},
this
);
return rules;
};
this.defaultAttrs = function(defaults) {
utils.push(this.defaults, defaults, true) || (this.defaults = defaults);
};
this.select = function(attributeKey) {
return this.$node.find(this.attr[attributeKey]);
};
this.initialize = $.noop;
this.teardown = teardown;
}
function attachTo(selector/*, options args */) {
if (!selector) {
throw new Error("Component needs to be attachTo'd a jQuery object, native node or selector string");
}
var options = utils.merge.apply(utils, utils.toArray(arguments, 1));
$(selector).each(function(i, node) {
new this(node, options);
}.bind(this));
}
// define the constructor for a custom component type
// takes an unlimited number of mixin functions as arguments
// typical api call with 3 mixins: define(timeline, withTweetCapability, withScrollCapability);
function define(/*mixins*/) {
var mixins = utils.toArray(arguments);
Component.toString = function() {
var prettyPrintMixins = mixins.map(function(mixin) {
if ($.browser.msie) {
var m = mixin.toString().match(functionNameRegEx);
return (m && m[1]) ? m[1] : "";
} else {
return mixin.name;
}
}).join(', ').replace(spaceCommaRegEx,'');//weed out no-named mixins
return prettyPrintMixins;
};
Component.describe = Component.toString();
//'options' is optional hash to be merged with 'defaults' in the component definition
function Component(node, options) {
var fnCache = {}, uuid = 0;
if (!node) {
throw new Error("Component needs a node");
}
if (node.jquery) {
this.node = node[0];
this.$node = node;
} else {
this.node = node;
this.$node = $(node);
}
this.describe = this.constructor.describe;
this.bind = function(func) {
var bound;
if (func.uuid && (bound = fnCache[func.uuid])) {
return bound;
}
var bindArgs = utils.toArray(arguments, 1);
bindArgs.unshift(this); //prepend context
bound = func.bind.apply(func, bindArgs);
bound.target = func;
func.uuid = uuid++;
fnCache[func.uuid] = bound;
return bound;
};
//merge defaults with supplied options
this.attr = utils.merge(this.defaults, options);
this.defaults && Object.keys(this.defaults).forEach(function(key) {
if (this.defaults[key] === null && this.attr[key] === null) {
throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.describe + '".');
}
}, this);
this.initialize.call(this, options || {});
this.trigger('componentInitialized');
}
Component.attachTo = attachTo;
Component.teardownAll = teardownAll;
// prepend common mixins to supplied list, then mixin all flavors
mixins.unshift(withBaseComponent, advice.withAdvice, registry.withRegistration);
compose.mixin(Component.prototype, mixins);
return Component;
}
define.teardownAll = function() {
registry.components.slice().forEach(function(c) {
c.component.teardownAll();
});
registry.reset();
};
return define;
}
);
// ==========================================
// Copyright 2013 Twitter, Inc
// Licensed under The MIT License
// http://opensource.org/licenses/MIT
// ==========================================
"use strict";
define(
[
'./utils',
'../tools/debug/debug'
],
function(util, debug) {
//enumerables are shims - getOwnPropertyDescriptor shim doesn't work
var canWriteProtect = debug.enabled && !util.isEnumerable(Object, 'getOwnPropertyDescriptor');
//whitelist of unlockable property names
var dontLock = ['mixedIn'];
if (canWriteProtect) {
//IE8 getOwnPropertyDescriptor is built-in but throws exeption on non DOM objects
try {
Object.getOwnPropertyDescriptor(Object, 'keys');
} catch(e) {
canWriteProtect = false;
}
}
function setPropertyWritability(obj, isWritable) {
if (!canWriteProtect) {
return;
}
var props = Object.create(null);
Object.keys(obj).forEach(
function (key) {
if (dontLock.indexOf(key) < 0) {
var desc = Object.getOwnPropertyDescriptor(obj, key);
desc.writable = isWritable;
props[key] = desc;
}
}
);
Object.defineProperties(obj, props);
}
function unlockProperty(obj, prop, op) {
var writable;
if (!canWriteProtect || !obj.hasOwnProperty(prop)) {
op.call(obj);
return;
}
writable = Object.getOwnPropertyDescriptor(obj, prop).writable;
Object.defineProperty(obj, prop, { writable: true });
op.call(obj);
Object.defineProperty(obj, prop, { writable: writable });
}
function mixin(base, mixins) {
base.mixedIn = base.hasOwnProperty('mixedIn') ? base.mixedIn : [];
mixins.forEach(function(mixin) {
if (base.mixedIn.indexOf(mixin) == -1) {
setPropertyWritability(base, false);
mixin.call(base);
base.mixedIn.push(mixin);
}
});
setPropertyWritability(base, true);
}
return {
mixin: mixin,
unlockProperty: unlockProperty
};
}
);
// ==========================================
// Copyright 2013 Twitter, Inc
// Licensed under The MIT License
// http://opensource.org/licenses/MIT
// ==========================================
define(
[
'./advice',
'./component',
'./compose',
'./logger',
'./registry',
'./utils'
],
function (advice, component, compose, logger, registry, utils) {
return {
advice: advice,
component: component,
compose: compose,
logger: logger,
registry: registry,
utils: utils
};
}
);
// ==========================================
// Copyright 2013 Twitter, Inc
// Licensed under The MIT License
// http://opensource.org/licenses/MIT
// ==========================================
"use strict";
define(
[
'./compose',
'./utils'
],
function (compose, util) {
var actionSymbols = {
on:'<-',
trigger: '->',
off: 'x '
};
function elemToString(elem) {
var tagStr = elem.tagName ? elem.tagName.toLowerCase() : elem.toString();
var classStr = elem.className ? "." + (elem.className) : "";
var result = tagStr + classStr;
return elem.tagName ? ['\'', '\''].join(result) : result;
}
function log(action, component, eventArgs) {
var name, elem, fn, fnName, logFilter, toRegExp, actionLoggable, nameLoggable;
if (typeof eventArgs[eventArgs.length-1] == 'function') {
fn = eventArgs.pop();
fn = fn.unbound || fn; //use unbound version if any (better info)
}
if (typeof eventArgs[eventArgs.length - 1] == 'object') {
eventArgs.pop(); //trigger data arg - not logged right now
}
if (eventArgs.length == 2) {
elem = eventArgs[0];
name = eventArgs[1];
} else {
elem = component.$node[0];
name = eventArgs[0];
}
if (window.DEBUG) {
logFilter = DEBUG.events.logFilter;
// no regex for you, actions...
actionLoggable = logFilter.actions=="all" || (logFilter.actions.indexOf(action) > -1);
// event name filter allow wildcards or regex...
toRegExp = function(expr) {
return expr.test ? expr : new RegExp("^" + expr.replace(/\*/g, ".*") + "$");
};
nameLoggable =
logFilter.eventNames=="all" ||
logFilter.eventNames.some(function(e) {return toRegExp(e).test(name)});
if (actionLoggable && nameLoggable) {
console.info(
actionSymbols[action],
action,
'[' + name + ']',
elemToString(elem),
component.constructor.describe,
fn && (fnName = fn.name || fn.displayName) && '-> ' + fnName
);
}
}
}
function withLogging() {
this.before('trigger', function() {
log('trigger', this, util.toArray(arguments));
});
this.before('on', function() {
log('on', this, util.toArray(arguments));
});
this.before('off', function(eventArgs) {
log('off', this, util.toArray(arguments));
});
}
return withLogging;
}
);
// ==========================================
// Copyright 2013 Twitter, Inc
// Licensed under The MIT License
// http://opensource.org/licenses/MIT
// ==========================================
"use strict";
define(
[
'./utils'
],
function (util) {
function parseEventArgs(instance, args) {
var element, type, callback;
args = util.toArray(args);
if (typeof args[args.length-1] === 'function') {
callback = args.pop();
}
if (typeof args[args.length-1] === 'object') {
args.pop();
}
if (args.length == 2) {
element = args[0];
type = args[1];
} else {
element = instance.node;
type = args[0];
}
return {
element: element,
type: type,
callback: callback
};
}
function matchEvent(a, b) {
return (
(a.element == b.element) &&
(a.type == b.type) &&
(b.callback == null || (a.callback == b.callback))
);
}
function Registry() {
var registry = this;
(this.reset = function() {
this.components = [];
this.allInstances = [];
this.events = [];
}).call(this);
function ComponentInfo(component) {
this.component = component;
this.instances = [];
this.addInstance = function(instance) {
this.throwIfInstanceExistsOnNode(instance);
var instanceInfo = new InstanceInfo(instance);
this.instances.push(instanceInfo);
return instanceInfo;
}
this.throwIfInstanceExistsOnNode = function(instance) {
this.instances.forEach(function (instanceInfo) {
if (instanceInfo.instance.$node[0] === instance.$node[0]) {
throw new Error('Instance of ' + instance.constructor + ' already exists on node ' + instance.$node[0]);
}
});
}
this.removeInstance = function(instance) {
var instanceInfo = this.instances.filter(function(instanceInfo) {
return instanceInfo.instance == instance;
})[0];
var index = this.instances.indexOf(instanceInfo);
(index > -1) && this.instances.splice(index, 1);
if (!this.instances.length) {
//if I hold no more instances remove me from registry
registry.removeComponentInfo(this);
}
}
}
function InstanceInfo(instance) {
this.instance = instance;
this.events = [];
this.addTrigger = function() {};
this.addBind = function(event) {
this.events.push(event);
registry.events.push(event);
};
this.removeBind = function(event) {
for (var i = 0, e; e = this.events[i]; i++) {
if (matchEvent(e, event)) {
this.events.splice(i, 1);
}
}
}
}
this.addInstance = function(instance) {
var component = this.findComponentInfo(instance);
if (!component) {
component = new ComponentInfo(instance.constructor);
this.components.push(component);
}
var inst = component.addInstance(instance);
this.allInstances.push(inst);
return component;
};
this.removeInstance = function(instance) {
var index, instInfo = this.findInstanceInfo(instance);
//remove from component info
var componentInfo = this.findComponentInfo(instance);
componentInfo.removeInstance(instance);
//remove from registry
var index = this.allInstances.indexOf(instInfo);
(index > -1) && this.allInstances.splice(index, 1);
};
this.removeComponentInfo = function(componentInfo) {
var index = this.components.indexOf(componentInfo);
(index > -1) && this.components.splice(index, 1);
};
this.findComponentInfo = function(which) {
var component = which.attachTo ? which : which.constructor;
for (var i = 0, c; c = this.components[i]; i++) {
if (c.component === component) {
return c;
}
}
return null;
};
this.findInstanceInfo = function(which) {
var testFn;
if (which.node) {
//by instance (returns matched instance)
testFn = function(inst) {return inst.instance === which};
} else {
//by node (returns array of matches)
testFn = function(inst) {return inst.instance.node === which};
}
var matches = this.allInstances.filter(testFn);
if (!matches.length) {
return which.node ? null : [];
}
return which.node ? matches[0] : matches;
};
this.trigger = function() {
var event = parseEventArgs(this, arguments),
instance = registry.findInstanceInfo(this);
if (instance) {
instance.addTrigger(event);
}
};
this.on = function(componentOn) {
var otherArgs = util.toArray(arguments, 1);
var instance = registry.findInstanceInfo(this);
var boundCallback;
if (instance) {
boundCallback = componentOn.apply(null, otherArgs);
if(boundCallback) {
otherArgs[otherArgs.length-1] = boundCallback;
}
var event = parseEventArgs(this, otherArgs);
instance.addBind(event);
}
};
this.off = function(el, type, callback) {
var event = parseEventArgs(this, arguments),
instance = registry.findInstanceInfo(this);
if (instance) {
instance.removeBind(event);
}
};
this.teardown = function() {
registry.removeInstance(this);
};
this.withRegistration = function() {
this.before('initialize', function() {
registry.addInstance(this);
});
this.after('trigger', registry.trigger);
this.around('on', registry.on);
this.after('off', registry.off);
this.after('teardown', {obj:registry, fnName:'teardown'});
};
}
return new Registry;
}
);
// ==========================================
// Copyright 2013 Twitter, Inc
// Licensed under The MIT License
// http://opensource.org/licenses/MIT
// ==========================================
"use strict";
define(
[],
function () {
var arry = [];
var DEFAULT_INTERVAL = 100;
var utils = {
isDomObj: function(obj) {
return !!(obj.nodeType || (obj === window));
},
toArray: function(obj, from) {
return arry.slice.call(obj, from);
},
// returns new object representing multiple objects merged together
// optional final argument is boolean which specifies if merge is recursive
// original objects are unmodified
//
// usage:
// var base = {a:2, b:6};
// var extra = {b:3, c:4};
// merge(base, extra); //{a:2, b:3, c:4}
// base; //{a:2, b:6}
//
// var base = {a:2, b:6};
// var extra = {b:3, c:4};
// var extraExtra = {a:4, d:9};
// merge(base, extra, extraExtra); //{a:4, b:3, c:4. d: 9}
// base; //{a:2, b:6}
//
// var base = {a:2, b:{bb:4, cc:5}};
// var extra = {a:4, b:{cc:7, dd:1}};
// merge(base, extra, true); //{a:4, b:{bb:4, cc:7, dd:1}}
// base; //{a:2, b:6}
merge: function(/*obj1, obj2,....deepCopy*/) {
var args = this.toArray(arguments);
//start with empty object so a copy is created
args.unshift({});
if (args[args.length - 1] === true) {
//jquery extend requires deep copy as first arg
args.pop();
args.unshift(true);
}
return $.extend.apply(undefined, args);
},
// updates base in place by copying properties of extra to it
// optionally clobber protected
// usage:
// var base = {a:2, b:6};
// var extra = {c:4};
// push(base, extra); //{a:2, b:6, c:4}
// base; //{a:2, b:6, c:4}
//
// var base = {a:2, b:6};
// var extra = {b: 4 c:4};
// push(base, extra, true); //Error ("utils.push attempted to overwrite 'b' while running in protected mode")
// base; //{a:2, b:6}
//
// objects with the same key will merge recursively when protect is false
// eg:
// var base = {a:16, b:{bb:4, cc:10}};
// var extra = {b:{cc:25, dd:19}, c:5};
// push(base, extra); //{a:16, {bb:4, cc:25, dd:19}, c:5}
//
push: function(base, extra, protect) {
if (base) {
Object.keys(extra || {}).forEach(function(key) {
if (base[key] && protect) {
throw Error("utils.push attempted to overwrite '" + key + "' while running in protected mode");
}
if (typeof base[key] == "object" && typeof extra[key] == "object") {
//recurse
this.push(base[key], extra[key]);
} else {
//no protect, so extra wins
base[key] = extra[key];
}
}, this);
}
return base;
},
isEnumerable: function(obj, property) {
return Object.keys(obj).indexOf(property) > -1;
},
//build a function from other function(s)
//util.compose(a,b,c) -> a(b(c()));
//implementation lifted from underscore.js (c) 2009-2012 Jeremy Ashkenas
compose: function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length-1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
},
// Can only unique arrays of homogeneous primitives, e.g. an array of only strings, an array of only booleans, or an array of only numerics
uniqueArray: function(array) {
var u = {}, a = [];
for (var i = 0, l = array.length; i < l; ++i) {
if (u.hasOwnProperty(array[i])) {
continue;
}
a.push(array[i]);
u[array[i]] = 1;
}
return a;
},
debounce: function(func, wait, immediate) {
if (typeof wait != 'number') {
wait = DEFAULT_INTERVAL;
}
var timeout, result;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
}
return result;
};
},
throttle: function(func, wait) {
if (typeof wait != 'number') {
wait = DEFAULT_INTERVAL;
}
var context, args, timeout, throttling, more, result;
var whenDone = this.debounce(function(){
more = throttling = false;
}, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) {
result = func.apply(context, args);
}
whenDone();
};
if (!timeout) {
timeout = setTimeout(later, wait);
}
if (throttling) {
more = true;
} else {
throttling = true;
result = func.apply(context, args);
}
whenDone();
return result;
};
},
countThen: function(num, base) {
return function() {
if (!--num) { return base.apply(this, arguments); }
};
},
delegate: function(rules) {
return function(e, data) {
var target = $(e.target), parent;
Object.keys(rules).forEach(function(selector) {
if ((parent = target.closest(selector)).length) {
data = data || {};
data.el = parent[0];
return rules[selector].apply(this, [e, data]);
}
}, this);
};
}
};
return utils;
}
);
"use strict";
define(
[
'../../lib/registry',
'../../lib/utils'
],
function(registry, utils) {
var logFilter;
//******************************************************************************************
// Search object model
//******************************************************************************************
function traverse(util, searchTerm, options) {
var options = options || {};
var obj = options.obj || window;
var path = options.path || ((obj==window) ? "window" : "");
var props = Object.keys(obj);
props.forEach(function(prop) {
if ((tests[util] || util)(searchTerm, obj, prop)){
console.log([path, ".", prop].join(""), "->",["(", typeof obj[prop], ")"].join(""), obj[prop]);
}
if(Object.prototype.toString.call(obj[prop])=="[object Object]" && (obj[prop] != obj) && path.split(".").indexOf(prop) == -1) {
traverse(util, searchTerm, {obj: obj[prop], path: [path,prop].join(".")});
}
});
}
function search(util, expected, searchTerm, options) {
if (!expected || typeof searchTerm == expected) {
traverse(util, searchTerm, options);
} else {
console.error([searchTerm, 'must be', expected].join(' '))
}
}
var tests = {
'name': function(searchTerm, obj, prop) {return searchTerm == prop},
'nameContains': function(searchTerm, obj, prop) {return prop.indexOf(searchTerm)>-1},
'type': function(searchTerm, obj, prop) {return obj[prop] instanceof searchTerm},
'value': function(searchTerm, obj, prop) {return obj[prop] === searchTerm},
'valueCoerced': function(searchTerm, obj, prop) {return obj[prop] == searchTerm}
}
function byName(searchTerm, options) {search('name', 'string', searchTerm, options);};
function byNameContains(searchTerm, options) {search('nameContains', 'string', searchTerm, options);};
function byType(searchTerm, options) {search('type', 'function', searchTerm, options);};
function byValue(searchTerm, options) {search('value', null, searchTerm, options);};
function byValueCoerced(searchTerm, options) {search('valueCoerced', null, searchTerm, options);};
function custom(fn, options) {traverse(fn, null, options);};
//******************************************************************************************
// Event logging
//******************************************************************************************
var logLevel = 'all';
logFilter = {actions: logLevel, eventNames: logLevel}; //no filter by default
function filterEventLogsByAction(/*actions*/) {
var actions = [].slice.call(arguments, 0);
logFilter.eventNames.length || (logFilter.eventNames = 'all');
logFilter.actions = actions.length ? actions : 'all';
}
function filterEventLogsByName(/*eventNames*/) {
var eventNames = [].slice.call(arguments, 0);
logFilter.actions.length || (logFilter.actions = 'all');
logFilter.eventNames = eventNames.length ? eventNames : 'all';
}
function hideAllEventLogs() {
logFilter.actions = [];
logFilter.eventNames = [];
}
function showAllEventLogs() {
logFilter.actions = 'all';
logFilter.eventNames = 'all';
}
return {
enable: function(enable) {
this.enabled = !!enable;
if (enable && window.console) {
console.info('Booting in DEBUG mode');
console.info('You can filter event logging with DEBUG.events.logAll/logNone/logByName/logByAction');
}
window.DEBUG = this;
},
find: {
byName: byName,
byNameContains: byNameContains,
byType: byType,
byValue: byValue,
byValueCoerced: byValueCoerced,
custom: custom
},
events: {
logFilter: logFilter,
// Accepts any number of action args
// e.g. DEBUG.events.logByAction("on", "off")
logByAction: filterEventLogsByAction,
// Accepts any number of event name args (inc. regex or wildcards)
// e.g. DEBUG.events.logByName(/ui.*/, "*Thread*");
logByName: filterEventLogsByName,
logAll: showAllEventLogs,
logNone: hideAllEventLogs
}
};
}
);
This diff is collapsed.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Flight • Todo</title>
<link rel="stylesheet" href="../../../assets/base.css">
</head>
<body>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
</header>
<section id="main">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul>
</section>
<footer id="footer"></footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://github.com/mkuklis">Michal Kuklis</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script data-main="app/js/main" src="components/requirejs/requirejs.js"></script>
</body>
</html>
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