Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Created applitude build destination.

  • Loading branch information...
commit b0be3610a8ba21d4493c1a2262f823bb8ddf6b36 1 parent 6c0458c
@ericelliott authored
View
1  .gitignore
@@ -0,0 +1 @@
+node_modules
View
18 README.markdown
@@ -6,18 +6,16 @@ Collision-resistant client-side UID generator safe for element IDs and server-si
## Example
-wz5347t5lh1ttmg
+ch6sgpt5s5lh1ttmg
### Broken down
-** w - z5347t - [5l-h1] - ttmg **
+** c - h6sgpt5s - 5l - h1 - ttmg **
-The first char is a random a-z char to make it HTML ID friendly.
+The groups, in order, are:
-The next group is a timestamp.
-
-After that is a fingerprint where the first chars are obtained from the user agent string (which is fairly unique), and the supported mimeTypes (which is also fairly unique, except for IE, which always returns 0).
-
-The final bit of browser fingerprint is a count of global variables (which varies a lot).
-
-The rest of the ID is just a random number.
+* 'c' - identifies this as a cuid, and allows you to use it in html entity ids.
+* timestamp
+* Browser fingerprint. The first chars are obtained from the user agent string (which is fairly unique), and the supported mimeTypes (which is also fairly unique, except for IE, which always returns 0).
+* a count of global variables.
+* Math.random().
View
74 dist/applitude.uid.js
@@ -0,0 +1,74 @@
+(function () {
+ 'use strict';
+
+/**
+ * cuid.js
+ * Collision-resistant client-side UID generator safe
+ * for element IDs and server-side lookups.
+ *
+ * Extracted from CLCTR
+ *
+ * Copyright (c) Eric Elliott 2012
+ * MIT License
+ */
+
+/*global window, navigator, document */
+(function () {
+ 'use strict';
+ var c = 0,
+ globalCount = (function () {
+ var i,
+ count = 0;
+ for (i in window) {
+ count++;
+ }
+ return count;
+ }()),
+
+ pad = function pad(num, size) {
+ var s = "000000000" + num;
+ return s.substr(s.length-size);
+ },
+
+ cuid = function cuid() {
+ // Starting with a lowercase letter Makes
+ // it HTML element ID friendly.
+ var letter = 'c', // fixed = allows for sequential access
+
+ // timestamp
+ // warning: this exposes the exact date and time
+ // that the uid was created.
+ timestamp = (new Date().getTime()).toString(36),
+
+ // Without this, 1 in 600,000 IDs collide.
+ counter,
+
+ // A few chars to generate distinct ids for different
+ // browser / page combos (so different computers are far less
+ // likely to generate the same id)
+ fingerprint = pad((navigator.mimeTypes.length +
+ navigator.userAgent.length).toString(36) +
+ globalCount.toString(36), 4),
+
+ // Grab some more chars from browser's .random() method
+ random = ("0000" + (Math.random() *
+ Math.pow(36, 4) << 0)
+ .toString(36)).substr(-4);
+
+ c = (c < 1679615) ? c : 0;
+ counter = pad(c.toString(36), 4);
+
+ c++; // this is not subliminal
+
+ return (letter + timestamp + counter + fingerprint + random);
+ };
+
+ window.cuid = cuid;
+}());
+
+ (function (app) {
+ var namespace = 'uid';
+ app.register(namespace, cuid);
+ }(applitude));
+
+}());
View
60 dist/cuid.js
@@ -12,8 +12,7 @@
/*global window, navigator, document */
(function () {
'use strict';
- var alpha = 'abcdefghijklmnopqrstuvwxyz',
- c = 0,
+ var c = 0,
globalCount = (function () {
var i,
count = 0;
@@ -23,63 +22,42 @@
return count;
}()),
- /**
- * cuid returns a short random string prepended with
- * the current time and a little browser/page fingerprint
- * to prevent collisions from different clients.
- *
- * Sample: t6koe7ta5lf4cuzq4apz
- *
- * Broken down: u - 6koe7ta - 5l-f4 - cuzq - 4apz
- *
- * The first char is a random a-z char to make it HTML
- * ID friendly.
- *
- * The next group is a timestamp.
- *
- * After that is a fingerprint where the first chars
- * are obtained from the user agent string (which
- * is fairly unique), and the supported mimeTypes
- * (which is also fairly unique, except for IE, which
- * always returns 0).
- *
- * The next part of the browser fingerprint is a count of
- * global variables (which varies a lot).
- *
- * The next part is a counter to hammer out the last
- * same-machine collisions (was ~1 in 600,000).
- *
- * The final part is from Math.random().
- **/
+ pad = function pad(num, size) {
+ var s = "000000000" + num;
+ return s.substr(s.length-size);
+ },
+
cuid = function cuid() {
// Starting with a lowercase letter Makes
// it HTML element ID friendly.
- var letter = alpha.charAt(Math.floor(Math.random()*26)),
+ var letter = 'c', // fixed = allows for sequential access
+
+ // timestamp
+ // warning: this exposes the exact date and time
+ // that the uid was created.
+ timestamp = (new Date().getTime()).toString(36),
- // timestamp with least useful digit shaved
- timestamp = (new Date().getTime()).toString(36).slice(1),
+ // Without this, 1 in 600,000 IDs collide.
+ counter,
// A few chars to generate distinct ids for different
// browser / page combos (so different computers are far less
// likely to generate the same id)
- fingerprint = (navigator.mimeTypes.length +
+ fingerprint = pad((navigator.mimeTypes.length +
navigator.userAgent.length).toString(36) +
- globalCount.toString(36),
-
- // Without this, 1 in 600,000 IDs collide.
- counter,
+ globalCount.toString(36), 4),
// Grab some more chars from browser's .random() method
random = ("0000" + (Math.random() *
Math.pow(36, 4) << 0)
.toString(36)).substr(-4);
- c = (c < 1000000) ? c : 0;
- counter = c.toString(36);
+ c = (c < 1679615) ? c : 0;
+ counter = pad(c.toString(36), 4);
c++; // this is not subliminal
- return (letter + timestamp + fingerprint + counter + random);
+ return (letter + timestamp + counter + fingerprint + random);
};
window.cuid = cuid;
View
6 grunt.js
@@ -25,7 +25,11 @@ module.exports = function(grunt) {
}
},
concat: {
- applitude: {
+ applitudeCopy: {
+ src: ['node_modules/applitude/dist/applitude.bundle.js'],
+ dest: 'lib/applitude.bundle.js'
+ },
+ applitudeBuild: {
src: ['src/applitude.head.js', 'src/cuid.js', 'src/applitude.foot.js'],
dest: 'dist/applitude.uid.js'
},
View
938 lib/applitude.bundle.js
@@ -0,0 +1,938 @@
+;!function(exports, undefined) {
+
+ var isArray = Array.isArray ? Array.isArray : function _isArray(obj) {
+ return Object.prototype.toString.call(obj) === "[object Array]";
+ };
+ var defaultMaxListeners = 10;
+
+ function init() {
+ this._events = new Object;
+ }
+
+ function configure(conf) {
+ if (conf) {
+ conf.delimiter && (this.delimiter = conf.delimiter);
+ conf.wildcard && (this.wildcard = conf.wildcard);
+ if (this.wildcard) {
+ this.listenerTree = new Object;
+ }
+ }
+ }
+
+ function EventEmitter(conf) {
+ this._events = new Object;
+ configure.call(this, conf);
+ }
+
+ //
+ // Attention, function return type now is array, always !
+ // It has zero elements if no any matches found and one or more
+ // elements (leafs) if there are matches
+ //
+ function searchListenerTree(handlers, type, tree, i) {
+ if (!tree) {
+ return [];
+ }
+ var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached,
+ typeLength = type.length, currentType = type[i], nextType = type[i+1];
+ if (i === typeLength && tree._listeners) {
+ //
+ // If at the end of the event(s) list and the tree has listeners
+ // invoke those listeners.
+ //
+ if (typeof tree._listeners === 'function') {
+ handlers && handlers.push(tree._listeners);
+ return [tree];
+ } else {
+ for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) {
+ handlers && handlers.push(tree._listeners[leaf]);
+ }
+ return [tree];
+ }
+ }
+
+ if ((currentType === '*' || currentType === '**') || tree[currentType]) {
+ //
+ // If the event emitted is '*' at this part
+ // or there is a concrete match at this patch
+ //
+ if (currentType === '*') {
+ for (branch in tree) {
+ if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1));
+ }
+ }
+ return listeners;
+ } else if(currentType === '**') {
+ endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*'));
+ if(endReached && tree._listeners) {
+ // The next element has a _listeners, add it to the handlers.
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength));
+ }
+
+ for (branch in tree) {
+ if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
+ if(branch === '*' || branch === '**') {
+ if(tree[branch]._listeners && !endReached) {
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength));
+ }
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
+ } else if(branch === nextType) {
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2));
+ } else {
+ // No match on this one, shift into the tree but not in the type array.
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
+ }
+ }
+ }
+ return listeners;
+ }
+
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1));
+ }
+
+ xTree = tree['*'];
+ if (xTree) {
+ //
+ // If the listener tree will allow any match for this part,
+ // then recursively explore all branches of the tree
+ //
+ searchListenerTree(handlers, type, xTree, i+1);
+ }
+
+ xxTree = tree['**'];
+ if(xxTree) {
+ if(i < typeLength) {
+ if(xxTree._listeners) {
+ // If we have a listener on a '**', it will catch all, so add its handler.
+ searchListenerTree(handlers, type, xxTree, typeLength);
+ }
+
+ // Build arrays of matching next branches and others.
+ for(branch in xxTree) {
+ if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) {
+ if(branch === nextType) {
+ // We know the next element will match, so jump twice.
+ searchListenerTree(handlers, type, xxTree[branch], i+2);
+ } else if(branch === currentType) {
+ // Current node matches, move into the tree.
+ searchListenerTree(handlers, type, xxTree[branch], i+1);
+ } else {
+ isolatedBranch = {};
+ isolatedBranch[branch] = xxTree[branch];
+ searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1);
+ }
+ }
+ }
+ } else if(xxTree._listeners) {
+ // We have reached the end and still on a '**'
+ searchListenerTree(handlers, type, xxTree, typeLength);
+ } else if(xxTree['*'] && xxTree['*']._listeners) {
+ searchListenerTree(handlers, type, xxTree['*'], typeLength);
+ }
+ }
+
+ return listeners;
+ }
+
+ function growListenerTree(type, listener) {
+
+ type = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+
+ //
+ // Looks for two consecutive '**', if so, don't add the event at all.
+ //
+ for(var i = 0, len = type.length; i+1 < len; i++) {
+ if(type[i] === '**' && type[i+1] === '**') {
+ return;
+ }
+ }
+
+ var tree = this.listenerTree;
+ var name = type.shift();
+
+ while (name) {
+
+ if (!tree[name]) {
+ tree[name] = new Object;
+ }
+
+ tree = tree[name];
+
+ if (type.length === 0) {
+
+ if (!tree._listeners) {
+ tree._listeners = listener;
+ }
+ else if(typeof tree._listeners === 'function') {
+ tree._listeners = [tree._listeners, listener];
+ }
+ else if (isArray(tree._listeners)) {
+
+ tree._listeners.push(listener);
+
+ if (!tree._listeners.warned) {
+
+ var m = defaultMaxListeners;
+
+ if (typeof this._events.maxListeners !== 'undefined') {
+ m = this._events.maxListeners;
+ }
+
+ if (m > 0 && tree._listeners.length > m) {
+
+ tree._listeners.warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ tree._listeners.length);
+ console.trace();
+ }
+ }
+ }
+ return true;
+ }
+ name = type.shift();
+ }
+ return true;
+ };
+
+ // By default EventEmitters will print a warning if more than
+ // 10 listeners are added to it. This is a useful default which
+ // helps finding memory leaks.
+ //
+ // Obviously not all Emitters should be limited to 10. This function allows
+ // that to be increased. Set to zero for unlimited.
+
+ EventEmitter.prototype.delimiter = '.';
+
+ EventEmitter.prototype.setMaxListeners = function(n) {
+ this._events || init.call(this);
+ this._events.maxListeners = n;
+ };
+
+ EventEmitter.prototype.event = '';
+
+ EventEmitter.prototype.once = function(event, fn) {
+ this.many(event, 1, fn);
+ return this;
+ };
+
+ EventEmitter.prototype.many = function(event, ttl, fn) {
+ var self = this;
+
+ if (typeof fn !== 'function') {
+ throw new Error('many only accepts instances of Function');
+ }
+
+ function listener() {
+ if (--ttl === 0) {
+ self.off(event, listener);
+ }
+ fn.apply(this, arguments);
+ };
+
+ listener._origin = fn;
+
+ this.on(event, listener);
+
+ return self;
+ };
+
+ EventEmitter.prototype.emit = function() {
+ this._events || init.call(this);
+
+ var type = arguments[0];
+
+ if (type === 'newListener') {
+ if (!this._events.newListener) { return false; }
+ }
+
+ // Loop through the *_all* functions and invoke them.
+ if (this._all) {
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+ for (i = 0, l = this._all.length; i < l; i++) {
+ this.event = type;
+ this._all[i].apply(this, args);
+ }
+ }
+
+ // If there is no 'error' event listener then throw.
+ if (type === 'error') {
+
+ if (!this._all &&
+ !this._events.error &&
+ !(this.wildcard && this.listenerTree.error)) {
+
+ if (arguments[1] instanceof Error) {
+ throw arguments[1]; // Unhandled 'error' event
+ } else {
+ throw new Error("Uncaught, unspecified 'error' event.");
+ }
+ return false;
+ }
+ }
+
+ var handler;
+
+ if(this.wildcard) {
+ handler = [];
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ searchListenerTree.call(this, handler, ns, this.listenerTree, 0);
+ }
+ else {
+ handler = this._events[type];
+ }
+
+ if (typeof handler === 'function') {
+ this.event = type;
+ if (arguments.length === 1) {
+ handler.call(this);
+ }
+ else if (arguments.length > 1)
+ switch (arguments.length) {
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+ // slower
+ default:
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+ handler.apply(this, args);
+ }
+ return true;
+ }
+ else if (handler) {
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+
+ var listeners = handler.slice();
+ for (var i = 0, l = listeners.length; i < l; i++) {
+ this.event = type;
+ listeners[i].apply(this, args);
+ }
+ return (listeners.length > 0) || this._all;
+ }
+ else {
+ return this._all;
+ }
+
+ };
+
+ EventEmitter.prototype.on = function(type, listener) {
+
+ if (typeof type === 'function') {
+ this.onAny(type);
+ return this;
+ }
+
+ if (typeof listener !== 'function') {
+ throw new Error('on only accepts instances of Function');
+ }
+ this._events || init.call(this);
+
+ // To avoid recursion in the case that type == "newListeners"! Before
+ // adding it to the listeners, first emit "newListeners".
+ this.emit('newListener', type, listener);
+
+ if(this.wildcard) {
+ growListenerTree.call(this, type, listener);
+ return this;
+ }
+
+ if (!this._events[type]) {
+ // Optimize the case of one listener. Don't need the extra array object.
+ this._events[type] = listener;
+ }
+ else if(typeof this._events[type] === 'function') {
+ // Adding the second element, need to change to array.
+ this._events[type] = [this._events[type], listener];
+ }
+ else if (isArray(this._events[type])) {
+ // If we've already got an array, just append.
+ this._events[type].push(listener);
+
+ // Check for listener leak
+ if (!this._events[type].warned) {
+
+ var m = defaultMaxListeners;
+
+ if (typeof this._events.maxListeners !== 'undefined') {
+ m = this._events.maxListeners;
+ }
+
+ if (m > 0 && this._events[type].length > m) {
+
+ this._events[type].warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ this._events[type].length);
+ console.trace();
+ }
+ }
+ }
+ return this;
+ };
+
+ EventEmitter.prototype.onAny = function(fn) {
+
+ if(!this._all) {
+ this._all = [];
+ }
+
+ if (typeof fn !== 'function') {
+ throw new Error('onAny only accepts instances of Function');
+ }
+
+ // Add the function to the event listener collection.
+ this._all.push(fn);
+ return this;
+ };
+
+ EventEmitter.prototype.addListener = EventEmitter.prototype.on;
+
+ EventEmitter.prototype.off = function(type, listener) {
+ if (typeof listener !== 'function') {
+ throw new Error('removeListener only takes instances of Function');
+ }
+
+ var handlers,leafs=[];
+
+ if(this.wildcard) {
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
+ }
+ else {
+ // does not use listeners(), so no side effect of creating _events[type]
+ if (!this._events[type]) return this;
+ handlers = this._events[type];
+ leafs.push({_listeners:handlers});
+ }
+
+ for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
+ var leaf = leafs[iLeaf];
+ handlers = leaf._listeners;
+ if (isArray(handlers)) {
+
+ var position = -1;
+
+ for (var i = 0, length = handlers.length; i < length; i++) {
+ if (handlers[i] === listener ||
+ (handlers[i].listener && handlers[i].listener === listener) ||
+ (handlers[i]._origin && handlers[i]._origin === listener)) {
+ position = i;
+ break;
+ }
+ }
+
+ if (position < 0) {
+ return this;
+ }
+
+ if(this.wildcard) {
+ leaf._listeners.splice(position, 1)
+ }
+ else {
+ this._events[type].splice(position, 1);
+ }
+
+ if (handlers.length === 0) {
+ if(this.wildcard) {
+ delete leaf._listeners;
+ }
+ else {
+ delete this._events[type];
+ }
+ }
+ }
+ else if (handlers === listener ||
+ (handlers.listener && handlers.listener === listener) ||
+ (handlers._origin && handlers._origin === listener)) {
+ if(this.wildcard) {
+ delete leaf._listeners;
+ }
+ else {
+ delete this._events[type];
+ }
+ }
+ }
+
+ return this;
+ };
+
+ EventEmitter.prototype.offAny = function(fn) {
+ var i = 0, l = 0, fns;
+ if (fn && this._all && this._all.length > 0) {
+ fns = this._all;
+ for(i = 0, l = fns.length; i < l; i++) {
+ if(fn === fns[i]) {
+ fns.splice(i, 1);
+ return this;
+ }
+ }
+ } else {
+ this._all = [];
+ }
+ return this;
+ };
+
+ EventEmitter.prototype.removeListener = EventEmitter.prototype.off;
+
+ EventEmitter.prototype.removeAllListeners = function(type) {
+ if (arguments.length === 0) {
+ !this._events || init.call(this);
+ return this;
+ }
+
+ if(this.wildcard) {
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
+
+ for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
+ var leaf = leafs[iLeaf];
+ leaf._listeners = null;
+ }
+ }
+ else {
+ if (!this._events[type]) return this;
+ this._events[type] = null;
+ }
+ return this;
+ };
+
+ EventEmitter.prototype.listeners = function(type) {
+ if(this.wildcard) {
+ var handlers = [];
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ searchListenerTree.call(this, handlers, ns, this.listenerTree, 0);
+ return handlers;
+ }
+
+ this._events || init.call(this);
+
+ if (!this._events[type]) this._events[type] = [];
+ if (!isArray(this._events[type])) {
+ this._events[type] = [this._events[type]];
+ }
+ return this._events[type];
+ };
+
+ EventEmitter.prototype.listenersAny = function() {
+
+ if(this._all) {
+ return this._all;
+ }
+ else {
+ return [];
+ }
+
+ };
+
+ if (typeof define === 'function' && define.amd) {
+ define(function() {
+ return EventEmitter;
+ });
+ } else {
+ exports.EventEmitter2 = EventEmitter;
+ }
+
+}(typeof process !== 'undefined' && typeof process.title !== 'undefined' && typeof exports !== 'undefined' ? exports : window);
+
+/**
+ * odotjs - Prototypal OO made easy.
+ *
+ * Copyright (c) Eric Elliott 2012
+ * MIT License
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+
+/*global exports */
+// Shim .forEach()
+if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function (fn, scope) {
+ var i,
+ length = this.length;
+ for (i = 0, length; i < length; ++i) {
+ fn.call(scope || this, this[i], i, this);
+ }
+ };
+}
+
+// Shim Object.create()
+if (!Object.create) {
+ Object.create = function (o) {
+ if (arguments.length > 1) {
+ throw new Error('Object.create implementation only accepts the first parameter.');
+ }
+ function F() {}
+ F.prototype = o;
+ return new F();
+ };
+}
+
+// Shim String.prototype.trim()
+if(!String.prototype.trim) {
+ String.prototype.trim = function () {
+ return this.replace(/^\s+|\s+$/g,'');
+ };
+}
+
+(function (exports) {
+ 'use strict';
+ var namespace = 'odotjs',
+
+ // Adapted from Underscore.
+ extend = function extend(obj) {
+ var args = [].slice.call(arguments, 1);
+ args.forEach(function (source) {
+ var prop;
+ for (prop in source) {
+ obj[prop] = source[prop];
+ }
+ });
+ return obj;
+ },
+
+ plugins = {},
+
+ // Add to the global plugin collection.
+ addPlugins = function (newPlugins) {
+ extend(plugins, newPlugins);
+ },
+
+ // Add to the current object prototype.
+ share = function share(name, prop) {
+ this.proto[name] = prop;
+ },
+
+ // Pass the global plugins to the object
+ // prototype.
+ bless = function bless(proto) {
+ proto.share = share;
+
+ extend(proto, plugins);
+
+ return proto;
+ },
+ o,
+ api,
+ defaultInit = function init() {
+ return this;
+ },
+
+ /**
+ * The user can pass in the formal parameters, or a named
+ * parameters. Either way, we need to initialize the
+ * variables to the expected values.
+ *
+ * @param {String} optionNames Parameter names.
+ *
+ * @return {object} New configuration object.
+ */
+ mapOptions = function mapOptions(optionNames) {
+ var config = {}, // New config object
+ // Comma separated string to Array
+ names = optionNames.split(','),
+ // Turn arguments into array, starting at index 1
+ args = [].slice.call(arguments, 1);
+
+ names.forEach(function (optionName, index) {
+ // Strip whitespace
+ optionName = optionName.trim();
+
+ // Use first argument as params object...
+ config[optionName] = (args[0] && args[0][optionName]) ||
+ args[index]; // or grab the formal parameter.
+ });
+
+ return config;
+ };
+
+ /**
+ * Create a new, blessed object with public properties,
+ * shared properties (on prototype), and support for
+ * privacy (via initFunction).
+ *
+ * @param {object} sharedProperties Prototype
+ * @param {object} instanceProperties Instance safe
+ * @param {function} initFunction Init and privacy
+ *
+ * @return {object}
+ */
+ o = function o(sharedProperties, instanceProperties,
+ initFunction) {
+ var optionNames = 'sharedProperties, instanceProperties,' +
+ ' initFunction',
+ config,
+ proto,
+ obj;
+
+ config = mapOptions(optionNames, sharedProperties,
+ instanceProperties, initFunction);
+ config.initFunction = config.initFunction || defaultInit;
+ proto = config.sharedProperties || {};
+
+ bless(proto);
+
+ obj = extend(Object.create(proto), {proto: proto},
+ config.instanceProperties);
+
+ return config.initFunction.call(obj);
+ };
+
+ bless(o);
+
+ extend(o, {
+ /**
+ * Returns an object factory that stamps out objects
+ * using a specified shared prototype and init.
+ *
+ * @param {object} sharedProperties Prototype
+ * @param {object} defaultProperties Instance safe
+ * @param {function} initFunction Init and privacy
+ *
+ * @return {function} A new object factory.
+ */
+ factory: function factory(sharedProperties, defaultProperties,
+ instanceInit, factoryInit) {
+ var optionNames = 'sharedProperties, defaultProperties,' +
+ ' instanceInit, factoryInit',
+ config,
+ initObj = o();
+
+ config = mapOptions(optionNames, sharedProperties,
+ defaultProperties, instanceInit, factoryInit);
+ config.instanceInit = config.instanceInit || defaultInit;
+
+ // factoryInit can be used to initialize shared private state.
+ if (typeof config.factoryInit === 'function') {
+ config.factoryInit.call(initObj);
+ }
+
+ return bless(function (options) {
+ var defaultProperties = config.defaultProperties || {},
+ sharedProperties = extend(config.sharedProperties || {}, initObj),
+ instance = extend(defaultProperties, options),
+ obj = extend(o(sharedProperties, instance)),
+ init = config.instanceInit;
+ return ((typeof init === 'function') ?
+ init.call(obj)
+ : obj);
+ });
+ },
+ addPlugins: addPlugins,
+ extend: extend,
+ mapOptions: mapOptions,
+ getConfig: mapOptions
+ });
+
+ api = o;
+
+ exports[namespace] = api;
+}((typeof exports === 'undefined') ?
+ this
+ : exports));
+
+/**
+ * Applitude - Application namespacing and module management.
+ *
+ * Depends on jQuery, EventEmitter2, and odotjs
+ *
+ * Copyright (c) Eric Elliott 2012
+ * MIT License
+ * http://opensource.org/licenses/MIT
+ */
+
+/*global jQuery, EventEmitter2, odotjs, window, setTimeout,
+console, exports, navigator */
+(function (root, $, o, events) {
+ 'use strict';
+ var namespace = 'applitude',
+ debugLog = [],
+ loadErrors = {},
+
+ /**
+ * Deferred utilities
+ */
+ deferred = $.Deferred,
+ when = $.when,
+ resolved = deferred().resolve().promise(),
+ rejected = deferred().reject().promise(),
+ app,
+ register,
+ stringToArray,
+ addMixins,
+ whenRenderReady = deferred(),
+ setModule;
+
+ setModule = function val(cursor, location, value) {
+ var tree = location.split('.'),
+ key = tree.shift(),
+ returnValue;
+
+ while (tree.length) {
+ if (cursor[key] !== undefined) {
+ cursor = cursor[key];
+ } else {
+ cursor = cursor[key] = {};
+ }
+ key = tree.shift();
+ }
+
+ if (cursor[key] === undefined) {
+ cursor[key] = value;
+ returnValue = true;
+ } else {
+ returnValue = false;
+ }
+ return returnValue;
+ };
+
+ stringToArray = function stringToArray(input, pattern) {
+ var result;
+ pattern = pattern || /\s*\,\s*/;
+
+ result = (typeof input !== 'string') ?
+ result = [] :
+ result = input.trim().split(pattern);
+
+ return result;
+ };
+
+ addMixins = function addMixins(module) {
+ var mixins = stringToArray(module.mixins),
+ backup = o.extend({}, module);
+ mixins.forEach(function (mixin) {
+ if (app[mixin]) {
+ o.extend(module, app[mixin]);
+ }
+ });
+ return o.extend(module, backup);
+ };
+
+ app = function applitudeFunction(appNs, environment, options) {
+ var whenPageLoaded = deferred(),
+ beforeRenderOption = (options && options.beforeRender) || [],
+ beforeRender = [whenPageLoaded].concat(beforeRenderOption),
+ tryRender;
+
+ whenRenderReady = when.apply(null, beforeRender);
+
+ tryRender = function tryRender(module) {
+ if (typeof module.render === 'function') {
+ whenRenderReady.then(module.render, module.render);
+ }
+ };
+
+ register = function register(ns, module) {
+ var whenLoaded,
+ newModule;
+
+ module.moduleNamespace = ns;
+
+ newModule = setModule(app, ns, module, function () {
+ app.events.trigger('module_added' + app.appNamespace, ns);
+ });
+
+ if (newModule) {
+
+ if (module.mixins) {
+ addMixins(module);
+ }
+
+ // If load exists, try to load
+ if (typeof module.load === 'function') {
+ try {
+ // If a promise is returned, wait for load to finish.
+ whenLoaded = module.load();
+ if (whenLoaded && whenLoaded.done) {
+ whenLoaded.done(function () {
+ tryRender(module);
+ });
+ } else {
+ tryRender(module);
+ }
+ } catch (loadError) {
+ loadErrors[ns] = loadError;
+ app.log('Error loading module: ', ns, loadError);
+ }
+ } else if (!loadErrors[ns]) {
+ // if .render() exists, try to render
+ tryRender(module);
+ }
+
+ } else {
+ app.log('Error: Module already registered: ', ns);
+ }
+
+ return app;
+ };
+
+ $(function () {
+ whenPageLoaded.resolve();
+ });
+
+ // aliases
+ events.trigger = events.emit;
+
+ o.extend(app, {
+ register: register,
+ environment: environment,
+ appNamespace: appNs,
+ options: options
+ });
+
+ return app;
+ };
+
+ function on() {
+ app.events.on.apply(app.events, arguments);
+ }
+
+ function trigger() {
+ app.events.trigger.apply(app.events, arguments);
+ }
+
+ o.extend(app, {
+ deferred: deferred,
+ resolved: resolved,
+ rejected: rejected,
+ when: when,
+ o: o,
+ $: $,
+ get: $.get,
+ stringToArray: stringToArray,
+ isArray: $.isArray,
+ events: events,
+ on: on,
+ trigger: trigger,
+ debugLog: debugLog
+ });
+
+ app.log = function log() {
+ var debug = app.environment && app.environment.debug,
+ hasConsole = (window.console !== undefined) && console.log;
+ if (debug && hasConsole) {
+ console.log.apply(console, [].slice.call(arguments, 0));
+ } else {
+ debugLog.push(arguments);
+ }
+ };
+
+ root[namespace] = app;
+
+}((typeof exports !== 'undefined') ?
+ exports : window,
+ jQuery,
+ odotjs,
+ new EventEmitter2({
+ wildcard: true
+ })));
View
938 lib/applitude.js
@@ -0,0 +1,938 @@
+;!function(exports, undefined) {
+
+ var isArray = Array.isArray ? Array.isArray : function _isArray(obj) {
+ return Object.prototype.toString.call(obj) === "[object Array]";
+ };
+ var defaultMaxListeners = 10;
+
+ function init() {
+ this._events = new Object;
+ }
+
+ function configure(conf) {
+ if (conf) {
+ conf.delimiter && (this.delimiter = conf.delimiter);
+ conf.wildcard && (this.wildcard = conf.wildcard);
+ if (this.wildcard) {
+ this.listenerTree = new Object;
+ }
+ }
+ }
+
+ function EventEmitter(conf) {
+ this._events = new Object;
+ configure.call(this, conf);
+ }
+
+ //
+ // Attention, function return type now is array, always !
+ // It has zero elements if no any matches found and one or more
+ // elements (leafs) if there are matches
+ //
+ function searchListenerTree(handlers, type, tree, i) {
+ if (!tree) {
+ return [];
+ }
+ var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached,
+ typeLength = type.length, currentType = type[i], nextType = type[i+1];
+ if (i === typeLength && tree._listeners) {
+ //
+ // If at the end of the event(s) list and the tree has listeners
+ // invoke those listeners.
+ //
+ if (typeof tree._listeners === 'function') {
+ handlers && handlers.push(tree._listeners);
+ return [tree];
+ } else {
+ for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) {
+ handlers && handlers.push(tree._listeners[leaf]);
+ }
+ return [tree];
+ }
+ }
+
+ if ((currentType === '*' || currentType === '**') || tree[currentType]) {
+ //
+ // If the event emitted is '*' at this part
+ // or there is a concrete match at this patch
+ //
+ if (currentType === '*') {
+ for (branch in tree) {
+ if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1));
+ }
+ }
+ return listeners;
+ } else if(currentType === '**') {
+ endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*'));
+ if(endReached && tree._listeners) {
+ // The next element has a _listeners, add it to the handlers.
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength));
+ }
+
+ for (branch in tree) {
+ if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
+ if(branch === '*' || branch === '**') {
+ if(tree[branch]._listeners && !endReached) {
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength));
+ }
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
+ } else if(branch === nextType) {
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2));
+ } else {
+ // No match on this one, shift into the tree but not in the type array.
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
+ }
+ }
+ }
+ return listeners;
+ }
+
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1));
+ }
+
+ xTree = tree['*'];
+ if (xTree) {
+ //
+ // If the listener tree will allow any match for this part,
+ // then recursively explore all branches of the tree
+ //
+ searchListenerTree(handlers, type, xTree, i+1);
+ }
+
+ xxTree = tree['**'];
+ if(xxTree) {
+ if(i < typeLength) {
+ if(xxTree._listeners) {
+ // If we have a listener on a '**', it will catch all, so add its handler.
+ searchListenerTree(handlers, type, xxTree, typeLength);
+ }
+
+ // Build arrays of matching next branches and others.
+ for(branch in xxTree) {
+ if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) {
+ if(branch === nextType) {
+ // We know the next element will match, so jump twice.
+ searchListenerTree(handlers, type, xxTree[branch], i+2);
+ } else if(branch === currentType) {
+ // Current node matches, move into the tree.
+ searchListenerTree(handlers, type, xxTree[branch], i+1);
+ } else {
+ isolatedBranch = {};
+ isolatedBranch[branch] = xxTree[branch];
+ searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1);
+ }
+ }
+ }
+ } else if(xxTree._listeners) {
+ // We have reached the end and still on a '**'
+ searchListenerTree(handlers, type, xxTree, typeLength);
+ } else if(xxTree['*'] && xxTree['*']._listeners) {
+ searchListenerTree(handlers, type, xxTree['*'], typeLength);
+ }
+ }
+
+ return listeners;
+ }
+
+ function growListenerTree(type, listener) {
+
+ type = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+
+ //
+ // Looks for two consecutive '**', if so, don't add the event at all.
+ //
+ for(var i = 0, len = type.length; i+1 < len; i++) {
+ if(type[i] === '**' && type[i+1] === '**') {
+ return;
+ }
+ }
+
+ var tree = this.listenerTree;
+ var name = type.shift();
+
+ while (name) {
+
+ if (!tree[name]) {
+ tree[name] = new Object;
+ }
+
+ tree = tree[name];
+
+ if (type.length === 0) {
+
+ if (!tree._listeners) {
+ tree._listeners = listener;
+ }
+ else if(typeof tree._listeners === 'function') {
+ tree._listeners = [tree._listeners, listener];
+ }
+ else if (isArray(tree._listeners)) {
+
+ tree._listeners.push(listener);
+
+ if (!tree._listeners.warned) {
+
+ var m = defaultMaxListeners;
+
+ if (typeof this._events.maxListeners !== 'undefined') {
+ m = this._events.maxListeners;
+ }
+
+ if (m > 0 && tree._listeners.length > m) {
+
+ tree._listeners.warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ tree._listeners.length);
+ console.trace();
+ }
+ }
+ }
+ return true;
+ }
+ name = type.shift();
+ }
+ return true;
+ };
+
+ // By default EventEmitters will print a warning if more than
+ // 10 listeners are added to it. This is a useful default which
+ // helps finding memory leaks.
+ //
+ // Obviously not all Emitters should be limited to 10. This function allows
+ // that to be increased. Set to zero for unlimited.
+
+ EventEmitter.prototype.delimiter = '.';
+
+ EventEmitter.prototype.setMaxListeners = function(n) {
+ this._events || init.call(this);
+ this._events.maxListeners = n;
+ };
+
+ EventEmitter.prototype.event = '';
+
+ EventEmitter.prototype.once = function(event, fn) {
+ this.many(event, 1, fn);
+ return this;
+ };
+
+ EventEmitter.prototype.many = function(event, ttl, fn) {
+ var self = this;
+
+ if (typeof fn !== 'function') {
+ throw new Error('many only accepts instances of Function');
+ }
+
+ function listener() {
+ if (--ttl === 0) {
+ self.off(event, listener);
+ }
+ fn.apply(this, arguments);
+ };
+
+ listener._origin = fn;
+
+ this.on(event, listener);
+
+ return self;
+ };
+
+ EventEmitter.prototype.emit = function() {
+ this._events || init.call(this);
+
+ var type = arguments[0];
+
+ if (type === 'newListener') {
+ if (!this._events.newListener) { return false; }
+ }
+
+ // Loop through the *_all* functions and invoke them.
+ if (this._all) {
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+ for (i = 0, l = this._all.length; i < l; i++) {
+ this.event = type;
+ this._all[i].apply(this, args);
+ }
+ }
+
+ // If there is no 'error' event listener then throw.
+ if (type === 'error') {
+
+ if (!this._all &&
+ !this._events.error &&
+ !(this.wildcard && this.listenerTree.error)) {
+
+ if (arguments[1] instanceof Error) {
+ throw arguments[1]; // Unhandled 'error' event
+ } else {
+ throw new Error("Uncaught, unspecified 'error' event.");
+ }
+ return false;
+ }
+ }
+
+ var handler;
+
+ if(this.wildcard) {
+ handler = [];
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ searchListenerTree.call(this, handler, ns, this.listenerTree, 0);
+ }
+ else {
+ handler = this._events[type];
+ }
+
+ if (typeof handler === 'function') {
+ this.event = type;
+ if (arguments.length === 1) {
+ handler.call(this);
+ }
+ else if (arguments.length > 1)
+ switch (arguments.length) {
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+ // slower
+ default:
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+ handler.apply(this, args);
+ }
+ return true;
+ }
+ else if (handler) {
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+
+ var listeners = handler.slice();
+ for (var i = 0, l = listeners.length; i < l; i++) {
+ this.event = type;
+ listeners[i].apply(this, args);
+ }
+ return (listeners.length > 0) || this._all;
+ }
+ else {
+ return this._all;
+ }
+
+ };
+
+ EventEmitter.prototype.on = function(type, listener) {
+
+ if (typeof type === 'function') {
+ this.onAny(type);
+ return this;
+ }
+
+ if (typeof listener !== 'function') {
+ throw new Error('on only accepts instances of Function');
+ }
+ this._events || init.call(this);
+
+ // To avoid recursion in the case that type == "newListeners"! Before
+ // adding it to the listeners, first emit "newListeners".
+ this.emit('newListener', type, listener);
+
+ if(this.wildcard) {
+ growListenerTree.call(this, type, listener);
+ return this;
+ }
+
+ if (!this._events[type]) {
+ // Optimize the case of one listener. Don't need the extra array object.
+ this._events[type] = listener;
+ }
+ else if(typeof this._events[type] === 'function') {
+ // Adding the second element, need to change to array.
+ this._events[type] = [this._events[type], listener];
+ }
+ else if (isArray(this._events[type])) {
+ // If we've already got an array, just append.
+ this._events[type].push(listener);
+
+ // Check for listener leak
+ if (!this._events[type].warned) {
+
+ var m = defaultMaxListeners;
+
+ if (typeof this._events.maxListeners !== 'undefined') {
+ m = this._events.maxListeners;
+ }
+
+ if (m > 0 && this._events[type].length > m) {
+
+ this._events[type].warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ this._events[type].length);
+ console.trace();
+ }
+ }
+ }
+ return this;
+ };
+
+ EventEmitter.prototype.onAny = function(fn) {
+
+ if(!this._all) {
+ this._all = [];
+ }
+
+ if (typeof fn !== 'function') {
+ throw new Error('onAny only accepts instances of Function');
+ }
+
+ // Add the function to the event listener collection.
+ this._all.push(fn);
+ return this;
+ };
+
+ EventEmitter.prototype.addListener = EventEmitter.prototype.on;
+
+ EventEmitter.prototype.off = function(type, listener) {
+ if (typeof listener !== 'function') {
+ throw new Error('removeListener only takes instances of Function');
+ }
+
+ var handlers,leafs=[];
+
+ if(this.wildcard) {
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
+ }
+ else {
+ // does not use listeners(), so no side effect of creating _events[type]
+ if (!this._events[type]) return this;
+ handlers = this._events[type];
+ leafs.push({_listeners:handlers});
+ }
+
+ for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
+ var leaf = leafs[iLeaf];
+ handlers = leaf._listeners;
+ if (isArray(handlers)) {
+
+ var position = -1;
+
+ for (var i = 0, length = handlers.length; i < length; i++) {
+ if (handlers[i] === listener ||
+ (handlers[i].listener && handlers[i].listener === listener) ||
+ (handlers[i]._origin && handlers[i]._origin === listener)) {
+ position = i;
+ break;
+ }
+ }
+
+ if (position < 0) {
+ return this;
+ }
+
+ if(this.wildcard) {
+ leaf._listeners.splice(position, 1)
+ }
+ else {
+ this._events[type].splice(position, 1);
+ }
+
+ if (handlers.length === 0) {
+ if(this.wildcard) {
+ delete leaf._listeners;
+ }
+ else {
+ delete this._events[type];
+ }
+ }
+ }
+ else if (handlers === listener ||
+ (handlers.listener && handlers.listener === listener) ||
+ (handlers._origin && handlers._origin === listener)) {
+ if(this.wildcard) {
+ delete leaf._listeners;
+ }
+ else {
+ delete this._events[type];
+ }
+ }
+ }
+
+ return this;
+ };
+
+ EventEmitter.prototype.offAny = function(fn) {
+ var i = 0, l = 0, fns;
+ if (fn && this._all && this._all.length > 0) {
+ fns = this._all;
+ for(i = 0, l = fns.length; i < l; i++) {
+ if(fn === fns[i]) {
+ fns.splice(i, 1);
+ return this;
+ }
+ }
+ } else {
+ this._all = [];
+ }
+ return this;
+ };
+
+ EventEmitter.prototype.removeListener = EventEmitter.prototype.off;
+
+ EventEmitter.prototype.removeAllListeners = function(type) {
+ if (arguments.length === 0) {
+ !this._events || init.call(this);
+ return this;
+ }
+
+ if(this.wildcard) {
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
+
+ for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
+ var leaf = leafs[iLeaf];
+ leaf._listeners = null;
+ }
+ }
+ else {
+ if (!this._events[type]) return this;
+ this._events[type] = null;
+ }
+ return this;
+ };
+
+ EventEmitter.prototype.listeners = function(type) {
+ if(this.wildcard) {
+ var handlers = [];
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ searchListenerTree.call(this, handlers, ns, this.listenerTree, 0);
+ return handlers;
+ }
+
+ this._events || init.call(this);
+
+ if (!this._events[type]) this._events[type] = [];
+ if (!isArray(this._events[type])) {
+ this._events[type] = [this._events[type]];
+ }
+ return this._events[type];
+ };
+
+ EventEmitter.prototype.listenersAny = function() {
+
+ if(this._all) {
+ return this._all;
+ }
+ else {
+ return [];
+ }
+
+ };
+
+ if (typeof define === 'function' && define.amd) {
+ define(function() {
+ return EventEmitter;
+ });
+ } else {
+ exports.EventEmitter2 = EventEmitter;
+ }
+
+}(typeof process !== 'undefined' && typeof process.title !== 'undefined' && typeof exports !== 'undefined' ? exports : window);
+
+/**
+ * odotjs - Prototypal OO made easy.
+ *
+ * Copyright (c) Eric Elliott 2012
+ * MIT License
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+
+/*global exports */
+// Shim .forEach()
+if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function (fn, scope) {
+ var i,
+ length = this.length;
+ for (i = 0, length; i < length; ++i) {
+ fn.call(scope || this, this[i], i, this);
+ }
+ };
+}
+
+// Shim Object.create()
+if (!Object.create) {
+ Object.create = function (o) {
+ if (arguments.length > 1) {
+ throw new Error('Object.create implementation only accepts the first parameter.');
+ }
+ function F() {}
+ F.prototype = o;
+ return new F();
+ };
+}
+
+// Shim String.prototype.trim()
+if(!String.prototype.trim) {
+ String.prototype.trim = function () {
+ return this.replace(/^\s+|\s+$/g,'');
+ };
+}
+
+(function (exports) {
+ 'use strict';
+ var namespace = 'odotjs',
+
+ // Adapted from Underscore.
+ extend = function extend(obj) {
+ var args = [].slice.call(arguments, 1);
+ args.forEach(function (source) {
+ var prop;
+ for (prop in source) {
+ obj[prop] = source[prop];
+ }
+ });
+ return obj;
+ },
+
+ plugins = {},
+
+ // Add to the global plugin collection.
+ addPlugins = function (newPlugins) {
+ extend(plugins, newPlugins);
+ },
+
+ // Add to the current object prototype.
+ share = function share(name, prop) {
+ this.proto[name] = prop;
+ },
+
+ // Pass the global plugins to the object
+ // prototype.
+ bless = function bless(proto) {
+ proto.share = share;
+
+ extend(proto, plugins);
+
+ return proto;
+ },
+ o,
+ api,
+ defaultInit = function init() {
+ return this;
+ },
+
+ /**
+ * The user can pass in the formal parameters, or a named
+ * parameters. Either way, we need to initialize the
+ * variables to the expected values.
+ *
+ * @param {String} optionNames Parameter names.
+ *
+ * @return {object} New configuration object.
+ */
+ mapOptions = function mapOptions(optionNames) {
+ var config = {}, // New config object
+ // Comma separated string to Array
+ names = optionNames.split(','),
+ // Turn arguments into array, starting at index 1
+ args = [].slice.call(arguments, 1);
+
+ names.forEach(function (optionName, index) {
+ // Strip whitespace
+ optionName = optionName.trim();
+
+ // Use first argument as params object...
+ config[optionName] = (args[0] && args[0][optionName]) ||
+ args[index]; // or grab the formal parameter.
+ });
+
+ return config;
+ };
+
+ /**
+ * Create a new, blessed object with public properties,
+ * shared properties (on prototype), and support for
+ * privacy (via initFunction).
+ *
+ * @param {object} sharedProperties Prototype
+ * @param {object} instanceProperties Instance safe
+ * @param {function} initFunction Init and privacy
+ *
+ * @return {object}
+ */
+ o = function o(sharedProperties, instanceProperties,
+ initFunction) {
+ var optionNames = 'sharedProperties, instanceProperties,' +
+ ' initFunction',
+ config,
+ proto,
+ obj;
+
+ config = mapOptions(optionNames, sharedProperties,
+ instanceProperties, initFunction);
+ config.initFunction = config.initFunction || defaultInit;
+ proto = config.sharedProperties || {};
+
+ bless(proto);
+
+ obj = extend(Object.create(proto), {proto: proto},
+ config.instanceProperties);
+
+ return config.initFunction.call(obj);
+ };
+
+ bless(o);
+
+ extend(o, {
+ /**
+ * Returns an object factory that stamps out objects
+ * using a specified shared prototype and init.
+ *
+ * @param {object} sharedProperties Prototype
+ * @param {object} defaultProperties Instance safe
+ * @param {function} initFunction Init and privacy
+ *
+ * @return {function} A new object factory.
+ */
+ factory: function factory(sharedProperties, defaultProperties,
+ instanceInit, factoryInit) {
+ var optionNames = 'sharedProperties, defaultProperties,' +
+ ' instanceInit, factoryInit',
+ config,
+ initObj = o();
+
+ config = mapOptions(optionNames, sharedProperties,
+ defaultProperties, instanceInit, factoryInit);
+ config.instanceInit = config.instanceInit || defaultInit;
+
+ // factoryInit can be used to initialize shared private state.
+ if (typeof config.factoryInit === 'function') {
+ config.factoryInit.call(initObj);
+ }
+
+ return bless(function (options) {
+ var defaultProperties = config.defaultProperties || {},
+ sharedProperties = extend(config.sharedProperties || {}, initObj),
+ instance = extend(defaultProperties, options),
+ obj = extend(o(sharedProperties, instance)),
+ init = config.instanceInit;
+ return ((typeof init === 'function') ?
+ init.call(obj)
+ : obj);
+ });
+ },
+ addPlugins: addPlugins,
+ extend: extend,
+ mapOptions: mapOptions,
+ getConfig: mapOptions
+ });
+
+ api = o;
+
+ exports[namespace] = api;
+}((typeof exports === 'undefined') ?
+ this
+ : exports));
+
+/**
+ * Applitude - Application namespacing and module management.
+ *
+ * Depends on jQuery, EventEmitter2, and odotjs
+ *
+ * Copyright (c) Eric Elliott 2012
+ * MIT License
+ * http://opensource.org/licenses/MIT
+ */
+
+/*global jQuery, EventEmitter2, odotjs, window, setTimeout,
+console, exports, navigator */
+(function (root, $, o, events) {
+ 'use strict';
+ var namespace = 'applitude',
+ debugLog = [],
+ loadErrors = {},
+
+ /**
+ * Deferred utilities
+ */
+ deferred = $.Deferred,
+ when = $.when,
+ resolved = deferred().resolve().promise(),
+ rejected = deferred().reject().promise(),
+ app,
+ register,
+ stringToArray,
+ addMixins,
+ whenRenderReady = deferred(),
+ setModule;
+
+ setModule = function val(cursor, location, value) {
+ var tree = location.split('.'),
+ key = tree.shift(),
+ returnValue;
+
+ while (tree.length) {
+ if (cursor[key] !== undefined) {
+ cursor = cursor[key];
+ } else {
+ cursor = cursor[key] = {};
+ }
+ key = tree.shift();
+ }
+
+ if (cursor[key] === undefined) {
+ cursor[key] = value;
+ returnValue = true;
+ } else {
+ returnValue = false;
+ }
+ return returnValue;
+ };
+
+ stringToArray = function stringToArray(input, pattern) {
+ var result;
+ pattern = pattern || /\s*\,\s*/;
+
+ result = (typeof input !== 'string') ?
+ result = [] :
+ result = input.trim().split(pattern);
+
+ return result;
+ };
+
+ addMixins = function addMixins(module) {
+ var mixins = stringToArray(module.mixins),
+ backup = o.extend({}, module);
+ mixins.forEach(function (mixin) {
+ if (app[mixin]) {
+ o.extend(module, app[mixin]);
+ }
+ });
+ return o.extend(module, backup);
+ };
+
+ app = function applitudeFunction(appNs, environment, options) {
+ var whenPageLoaded = deferred(),
+ beforeRenderOption = (options && options.beforeRender) || [],
+ beforeRender = [whenPageLoaded].concat(beforeRenderOption),
+ tryRender;
+
+ whenRenderReady = when.apply(null, beforeRender);
+
+ tryRender = function tryRender(module) {
+ if (typeof module.render === 'function') {
+ whenRenderReady.then(module.render, module.render);
+ }
+ };
+
+ register = function register(ns, module) {
+ var whenLoaded,
+ newModule;
+
+ module.moduleNamespace = ns;
+
+ newModule = setModule(app, ns, module, function () {
+ app.events.trigger('module_added' + app.appNamespace, ns);
+ });
+
+ if (newModule) {
+
+ if (module.mixins) {
+ addMixins(module);
+ }
+
+ // If load exists, try to load
+ if (typeof module.load === 'function') {
+ try {
+ // If a promise is returned, wait for load to finish.
+ whenLoaded = module.load();
+ if (whenLoaded && whenLoaded.done) {
+ whenLoaded.done(function () {
+ tryRender(module);
+ });
+ } else {
+ tryRender(module);
+ }
+ } catch (loadError) {
+ loadErrors[ns] = loadError;
+ app.log('Error loading module: ', ns, loadError);
+ }
+ } else if (!loadErrors[ns]) {
+ // if .render() exists, try to render
+ tryRender(module);
+ }
+
+ } else {
+ app.log('Error: Module already registered: ', ns);
+ }
+
+ return app;
+ };
+
+ $(function () {
+ whenPageLoaded.resolve();
+ });
+
+ // aliases
+ events.trigger = events.emit;
+
+ o.extend(app, {
+ register: register,
+ environment: environment,
+ appNamespace: appNs,
+ options: options
+ });
+
+ return app;
+ };
+
+ function on() {
+ app.events.on.apply(app.events, arguments);
+ }
+
+ function trigger() {
+ app.events.trigger.apply(app.events, arguments);
+ }
+
+ o.extend(app, {
+ deferred: deferred,
+ resolved: resolved,
+ rejected: rejected,
+ when: when,
+ o: o,
+ $: $,
+ get: $.get,
+ stringToArray: stringToArray,
+ isArray: $.isArray,
+ events: events,
+ on: on,
+ trigger: trigger,
+ debugLog: debugLog
+ });
+
+ app.log = function log() {
+ var debug = app.environment && app.environment.debug,
+ hasConsole = (window.console !== undefined) && console.log;
+ if (debug && hasConsole) {
+ console.log.apply(console, [].slice.call(arguments, 0));
+ } else {
+ debugLog.push(arguments);
+ }
+ };
+
+ root[namespace] = app;
+
+}((typeof exports !== 'undefined') ?
+ exports : window,
+ jQuery,
+ odotjs,
+ new EventEmitter2({
+ wildcard: true
+ })));
View
9,227 lib/jquery.js
9,227 additions, 0 deletions not shown
View
3  package.json
@@ -1,6 +1,6 @@
{
"name": "cuid",
- "version": "0.1.0",
+ "version": "1.0.0",
"description": "Client-side unique ids",
"author": {
"name": "Eric Elliott",
@@ -26,6 +26,7 @@
"test": "grunt"
},
"dependencies": {
+ "applitude": "~0.x.x",
"grunt": "~0.3.x"
},
"devDependencies": {
View
6 src/applitude.foot.js
@@ -0,0 +1,6 @@
+ (function (app) {
+ var namespace = 'uid';
+ app.register(namespace, cuid);
+ }(applitude));
+
+}());
View
2  src/applitude.head.js
@@ -0,0 +1,2 @@
+(function () {
+ 'use strict';
View
60 src/cuid.js
@@ -12,8 +12,7 @@
/*global window, navigator, document */
(function () {
'use strict';
- var alpha = 'abcdefghijklmnopqrstuvwxyz',
- c = 0,
+ var c = 0,
globalCount = (function () {
var i,
count = 0;
@@ -23,63 +22,42 @@
return count;
}()),
- /**
- * cuid returns a short random string prepended with
- * the current time and a little browser/page fingerprint
- * to prevent collisions from different clients.
- *
- * Sample: t6koe7ta5lf4cuzq4apz
- *
- * Broken down: t - 6koe7ta - 5l-f4 - cuzq - 4apz
- *
- * The first char is a random a-z char to make it HTML
- * ID friendly.
- *
- * The next group is a timestamp.
- *
- * After that is a fingerprint where the first chars
- * are obtained from the user agent string (which
- * is fairly unique), and the supported mimeTypes
- * (which is also fairly unique, except for IE, which
- * always returns 0).
- *
- * The next part of the browser fingerprint is a count of
- * global variables (which varies a lot).
- *
- * The next part is a counter to hammer out the last
- * same-machine collisions (was ~1 in 600,000).
- *
- * The final part is from Math.random().
- **/
+ pad = function pad(num, size) {
+ var s = "000000000" + num;
+ return s.substr(s.length-size);
+ },
+
cuid = function cuid() {
// Starting with a lowercase letter Makes
// it HTML element ID friendly.
- var letter = alpha.charAt(Math.floor(Math.random()*26)),
+ var letter = 'c', // fixed = allows for sequential access
+
+ // timestamp
+ // warning: this exposes the exact date and time
+ // that the uid was created.
+ timestamp = (new Date().getTime()).toString(36),
- // timestamp with least useful digit shaved
- timestamp = (new Date().getTime()).toString(36).slice(1),
+ // Without this, 1 in 600,000 IDs collide.
+ counter,
// A few chars to generate distinct ids for different
// browser / page combos (so different computers are far less
// likely to generate the same id)
- fingerprint = (navigator.mimeTypes.length +
+ fingerprint = pad((navigator.mimeTypes.length +
navigator.userAgent.length).toString(36) +
- globalCount.toString(36),
-
- // Without this, 1 in 600,000 IDs collide.
- counter,
+ globalCount.toString(36), 4),
// Grab some more chars from browser's .random() method
random = ("0000" + (Math.random() *
Math.pow(36, 4) << 0)
.toString(36)).substr(-4);
- c = (c < 1000000) ? c : 0;
- counter = c.toString(36);
+ c = (c < 1679615) ? c : 0;
+ counter = pad(c.toString(36), 4);
c++; // this is not subliminal
- return (letter + timestamp + fingerprint + counter + random);
+ return (letter + timestamp + counter + fingerprint + random);
};
window.cuid = cuid;
View
7 test/index.html
@@ -11,7 +11,12 @@ <h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</section>
- <script src="../dist/cuid.js"></script>
+ <script src="../lib/jquery.js"></script>
+ <script src="../lib/applitude.bundle.js"></script>
+ <script>
+ applitude({debug: true});
+ </script>
+ <script src="../dist/applitude.uid.js"></script>
<script src="qunit.js"></script>
<script src="test.js"></script>
View
11 test/test.js
@@ -1,4 +1,4 @@
-/*global test, ok, cuid, equal, stop, start*/
+/*global window, applitude, test, ok, cuid, equal, stop, start*/
(function () {
'use strict';
var collision = false;
@@ -6,7 +6,7 @@
test('Collision test', function () {
stop();
(function () {
- var ids = {},
+ var ids = window.ids = {},
i,
id;
for (i = 0; i < 600000; i++) {
@@ -24,5 +24,12 @@
start();
}());
+ test('Applitude build', function () {
+ equal(typeof applitude.uid(), 'string',
+ 'applitude.uid() is registered and returns a string.');
+ ok(applitude.uid() !== applitude.uid(),
+ '.uid() should generate unique strings.');
+ });
+
});
}());
Please sign in to comment.
Something went wrong with that request. Please try again.