devoir
Version:
Lightweight Javascript library adding functionality used in everyday tasks
1,720 lines (1,463 loc) • 120 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// shim for using process in browser
var process = module.exports = {};
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
function drainQueue() {
if (draining) {
return;
}
var timeout = setTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while(len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
currentQueue[queueIndex].run();
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
clearTimeout(timeout);
}
process.nextTick = function (fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
setTimeout(drainQueue, 0);
}
};
// v8 likes predictible objects
function Item(fun, array) {
this.fun = fun;
this.array = array;
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
// TODO(shtylman)
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
},{}],2:[function(require,module,exports){
window.D = require('./index');
},{"./index":3}],3:[function(require,module,exports){
(function(factory) {
module.exports = factory({});
})(function(root) {
'use strict';
require('./lib/base')(root);
require('./lib/deferred')(root);
root.data = require('./lib/data')(undefined, root);
root.utils = require('./lib/utils')(undefined, root);
root.events = require('./lib/events')(undefined, root);
root.lang = require('./lib/language')(undefined, root);
require('./lib/formatters')(root.data, root);
require('./lib/tokenizer')(root.utils);
return root;
});
},{"./lib/base":4,"./lib/data":5,"./lib/deferred":6,"./lib/events":7,"./lib/formatters":8,"./lib/language":9,"./lib/tokenizer":10,"./lib/utils":11}],4:[function(require,module,exports){
(function (process){
(function(factory) {
module.exports = function(_root) {
var root = _root || {};
return factory(root);
};
})(function(root) {
'use strict';
/**
* Devoir Base Functionality
*
* @namespace devoir
**/
function uid() {
return 'U' + (uidCounter++);
};
function initMeta(node, namespace) {
var metaContext;
if (!node.hasOwnProperty('_meta')) {
var thisUID = uid();
metaContext = {'_UID': thisUID, '_aliases': {}};
Object.defineProperty(node, '_meta', {
configurable: false,
enumerable: false,
value: metaContext
});
} else {
metaContext = node._meta;
}
if (arguments.length > 1 && namespace) {
if (!node._meta.hasOwnProperty(namespace)) {
metaContext = {};
Object.defineProperty(node._meta, namespace, {
configurable: false,
enumerable: false,
value: metaContext
});
} else {
metaContext = metaContext[namespace];
}
}
return metaContext;
}
function initAudit(node) {
var timeCreated = getTimeNow();
Object.defineProperty(node, '_audit', {
configurable: false,
enumerable: false,
value: {
'base': {created: timeCreated, modified: timeCreated, updateCount: 0},
'_meta': {created: timeCreated, modified: timeCreated, updateCount: 0}
}
});
}
var uidCounter = 1;
root.uid = uid;
var getTimeNow = root.now = (function() {
if (typeof performance !== 'undefined' && performance.now)
return performance.now.bind(performance);
var nanosecondsInMilliseconds = 1000000;
return function() {
var hrTime = process.hrtime();
return (hrTime[0] * 1000) + (hrTime[1] / nanosecondsInMilliseconds);
};
})();
//This function is deliberately large and confusing in order to squeeze
//every ounce of speed out of it
function prop(cmd, _node, namespace) {
var node = _node;
if (!node || !(node instanceof Object))
return;
var GET = 0x01,
SET = 0x02,
REMOVE = 0x04,
c,
isMetaNS = false,
isMeta = false,
argStartIndex,
argStartIndexOne,
context,
op = ((c = cmd.charAt(0)) === 'g') ? GET : (c === 's') ? SET : REMOVE,
finalPath = [];
switch(cmd) {
case 'getMetaNS':
case 'setMetaNS':
case 'removeMetaNS':
isMetaNS = isMeta = true;
argStartIndex = 3;
argStartIndexOne = 4;
if (!node.hasOwnProperty('_meta') || !node._meta.hasOwnProperty(namespace))
context = initMeta(node, namespace);
else
context = node._meta[namespace];
finalPath = ['_meta', namespace];
break;
case 'getMeta':
case 'setMeta':
case 'removeMeta':
isMeta = true;
argStartIndex = 2;
argStartIndexOne = 3;
if (!node.hasOwnProperty('_meta'))
context = initMeta(node);
else
context = node._meta;
finalPath = ['_meta'];
break;
default:
argStartIndex = 2;
argStartIndexOne = 3;
context = node;
break;
}
var prop,
fullPath = '' + arguments[argStartIndex],
nextIsArray,
parts = [];
//No path
if (!fullPath) {
if (op & SET)
return '';
if (op & REMOVE)
return;
return arguments[argStartIndexOne];
}
//Are there any parts to handle?
if (fullPath.indexOf('.') > -1 || fullPath.indexOf('[') > -1) {
if (fullPath.indexOf('\\') > -1)
//If we have character escapes, take the long and slow route
parts = fullPath.replace(/([^\\])\[/g,'$1.[').replace(/([^\\])\./g,'$1..').replace(/\\([.\[])/g,'$1').split('..');
else
//Fast route
parts = fullPath.replace(/\[/g,'.[').split('.');
for (var i = 0, i2 = 1, il = parts.length; i < il; i++, i2++) {
var part = parts[i],
isLast = (i2 >= il),
isArrayIndex = (part.charAt(0) === '[');
//Is this an array index
if (isArrayIndex)
part = part.substring(1, part.length - 1);
//Get prop
prop = context[part];
if (op & REMOVE && isLast) {
//If this is the final part, and we are to remove the item...
if (arguments[argStartIndexOne] === true)
//ACTUALLY delete it if the user forces a delete
delete context[part];
else
//Otherwise do it the performant way by setting the value to undefined
context[part] = undefined;
//Return whatever the value was
return prop;
} else if (op & SET) {
//Are we setting the value?
//If this is the last part, or the value isn't set,
//or it is set but the path continues and it
//needs to be overwritten
if (isLast || (prop === undefined || prop === null || (!isLast && (!(prop instanceof Object) || prop instanceof Number || prop instanceof String || prop instanceof Boolean)))) {
//If the next part is an array, make sure to create an array
nextIsArray = (!isLast && parts[i2].charAt(0) === '[');
//What is our new value?
prop = (isLast) ? arguments[argStartIndexOne] : (nextIsArray) ? [] : {};
//Update context accordingly
if (context instanceof Array && !part) {
isArrayIndex = true;
part = '' + (context.push(prop) - 1);
context = prop;
} else if (part) {
context[part] = prop;
context = prop;
}
} else {
context = prop;
}
if (part)
finalPath.push((isArrayIndex) ? ('[' + part + ']') : part);
} else {
if (prop === undefined || prop === null || ((typeof prop === 'number' || prop instanceof Number) && (isNaN(prop) || !isFinite(prop))))
return arguments[argStartIndexOne];
context = prop;
}
}
} else {
if (op & REMOVE) {
prop = context[fullPath];
if (arguments[argStartIndexOne] === true)
//ACTUALLY delete it if the user forces a delete
delete context[part];
else
//Otherwise do it the performant way by setting the value to undefined
context[part] = undefined;
//Return whatever the value was
return prop;
} else if (op & SET) {
context[fullPath] = arguments[argStartIndexOne];
return fullPath;
}
prop = context[fullPath];
}
if (op & GET) {
//Do we need to return the default value?
if (prop === undefined || prop === null || ((typeof prop === 'number' || prop instanceof Number) && (isNaN(prop) || !isFinite(prop))))
return arguments[argStartIndexOne];
return prop;
}
if (!node.hasOwnProperty('_audit'))
initAudit(node);
var lastUpdated = getTimeNow();
if (isMeta) {
var m = node._audit.meta;
m.modified = lastUpdated;
m.updateCount++;
} else {
var b = node._audit.base;
b.modified = lastUpdated;
b.updateCount++;
}
return (op & SET) ? finalPath.join('.').replace(/\.\[/g, '[') : prop;
}
/**
* Get/set object id. By default every object will have a unique id. This id is stored in the objects meta properties
*
* @parent devoir
* @function id
* @param {Object} {obj} Object to get / set id from
* @param {String} {[set]} If specified set object id to this
* @return {String} Objects id
* @see devoir.getMeta
* @see devoir.setMeta
* @see devoir.get
* @see devoir.set
**/
function id(node, set) {
if (arguments.length === 0)
return;
if (!node.hasOwnProperty('_meta'))
initMeta(node);
if (arguments.length === 1)
return node._meta._UID;
if (!node.hasOwnProperty('_audit'))
initAudit(node);
node._meta._UID = set;
var m = node._audit.meta;
m.modified = getTimeNow();
m.updateCount++;
return set;
}
/**
* Get/set object aliases (from meta properties)
*
* @parent devoir
* @function aliases
* @param {Object} {obj} Object to get / set aliases from
* @param {Array|String} {[set]} If specified as an Array, set the entire aliases array to this. If specified as a string, add this alias to the list of aliases
* @return {Array} List of aliases
* @see devoir.getMeta
* @see devoir.setMeta
**/
function aliases(node, set) {
if (arguments.length === 0)
return;
if (!node.hasOwnProperty('_meta'))
initMeta(node);
if (arguments.length === 1)
return node._meta._aliases;
if (!set)
return;
if (!node.hasOwnProperty('_audit'))
initAudit(node);
if (set instanceof Array) {
node._meta._aliases = set;
} else if (node._meta._aliases.indexOf(set) < 0) {
node._meta._aliases.push(set);
}
var m = node._audit.meta;
m.modified = getTimeNow();
m.updateCount++;
return node._meta._aliases;
}
/**
* Get audit information on object
*
* @parent devoir
* @function audit
* @param {Object} {obj} Object to get audit information on
* @param {String} {[which]} 'meta' or 'base'. If 'meta', get audit information on meta property updates. If 'base', get audit information on base property updates. If neither is specified, get the most recently updated (meta or base, whichever is most recent)
* @return {Object} Meta information object, i.e {created: (timestamp), modified: (timestamp), updateCount: (count)}
**/
function audit(node, _which) {
if (arguments.length === 0)
return;
var which = _which || '*';
if (!node.hasOwnProperty('_audit'))
initAudit(node);
switch(which) {
case '*':
var m = node._audit.meta,
b = node._audit.base;
return (m.modified > b.modified) ? m : b;
case 'meta':
return node._audit.meta;
case 'base':
return node._audit.base;
}
}
/**
* Delete ALL deletable properties from an object. This is useful when
* you want to "empty" an object while retaining all references to this object.
*
* @parent devoir
* @function empty
* @param {Object} {obj} Object to "clear"
* @return {Object} Same object but with all properties removed
* @note This could possibly have huge performance implications
**/
function empty(obj) {
var keys = Object.keys(obj);
for (var i = 0, len = keys.length; i < len; i++) {
var k = keys[i];
if (k === '_meta' || k === '_audit')
continue;
delete obj[k];
}
if (obj._meta || obj._audit) {
if (!obj.hasOwnProperty('_audit'))
initAudit(obj);
var b = obj._audit.base;
b.modified = getTimeNow();
b.updateCount++;
}
};
function setProperty(writable, obj, name, val, set, get) {
var props = {
enumerable: false,
configurable: false
};
if (!get) {
props.value = val;
} else {
props.get = get;
}
if (set)
props.set = set;
if (!get && !set)
props.writable = writable;
Object.defineProperty(obj, name, props);
}
function ClassBase() {
}
ClassBase.prototype = {
constructor: ClassBase,
extend: function(props) {
var keys = Object.keys(props);
for (var i = 0, il = keys.length; i < il; i++) {
var key = keys[i];
this[key] = props[key];
}
}
};
function newClass(_konstructor, _parent) {
var konstructor = _konstructor,
parent = _parent;
if (!parent)
parent = ClassBase;
var sup = parent.prototype,
proto = Object.create(sup),
klass = konstructor.call(proto, sup);
if (!(klass instanceof Function))
throw new Error("Class builder must return a function");
proto.constructor = klass;
klass.prototype = proto;
klass.extend = function(konstructor, instantiator) {
return newClass(konstructor, klass, instantiator);
};
return klass;
}
/**
* Get a property from an object and all sub-objects by evaluating a dot-notation path into an object.
*
* @parent devoir
* @function get
* @param {Object} {obj} Object to get property from
* @param {String} {path} Dot notation path to evaluate
* @param {*} {[defaultValue=undefined]} Specify a default value to return if the requested property is not found, is null, undefined, NaN or !isFinite
* @return {*} Return property specified by path
* @see devoir.set
* @example {javascript}
var obj = {hello: {world: "!!!"}, arr:[[1,2],3,4,5]};
devoir.get(obj, 'hello.world'); //!!!
devoir.get(obj, "some.key.that.doesn't.exist", 'not found'); //not found
devoir.get(obj, "arr[0][0]"); //1
devoir.get(obj, "arr[1]"); //3
**/
root.get = prop.bind(root, 'get');
/**
* Set a property on an object by evaluating a dot-notation path into an object.
*
* @parent devoir
* @function set
* @param {Object} {obj} Object to set property on
* @param {String} {path} Dot notation path to evaluate
* @param {*} {value} Value to set
* @return {String} Return the actual final path (relative to the base object) where the property was set. This is useful if property was pushed into an array; the actual array index will be returned as part of the final path
* @see devoir.get
* @note With empty array notation in a specified path (i.e my.array.key[]) the value will be appended to the array specified
* @example {javascript}
var obj = {};
devoir.set(obj, 'hello.world', '!!!'); //hello.world
devoir.set(set, "arr[]", [1]); //arr[0]
devoir.set(obj, "arr[0][1]", 2); //arr[0][1]
devoir.set(obj, "arr[]", 3); //arr[1]
**/
root.set = prop.bind(root, 'set');
/**
* Get a meta property from an object and all sub-objects by evaluating a dot-notation path into an object. This is the same as @@devoir.get except it is used for object meta properties
*
* @parent devoir
* @function getMeta
* @param {Object} {obj} Object to get meta property from
* @param {String} {path} Dot notation path to evaluate
* @param {*} {[defaultValue=undefined]} Specify a default value to return if the requested meta property is not found, is null, undefined, NaN or !isFinite
* @return {*} Return property specified by path
* @see devoir.setMeta
**/
root.getMeta = prop.bind(root, 'getMeta');
/**
* Set a meta property on an object by evaluating a dot-notation path into an object. This is the same as @@devoir.set except it is used for object meta properties
*
* @parent devoir
* @function setMeta
* @param {Object} {obj} Object to set meta property on
* @param {String} {path} Dot notation path to evaluate
* @param {*} {value} Value to set
* @return {String} Return the actual final path (relative to the base object) where the meta property was set. This is useful if meta property was pushed into an array; the actual array index will be returned as part of the final path
* @see devoir.getMeta
**/
root.setMeta = prop.bind(root, 'setMeta');
/**
* Get a namespaced meta property from an object and all sub-objects by evaluating a dot-notation path into an object. This is the same as @@devoir.getMeta except that the value is retrieved from a namespace
*
* @parent devoir
* @function getMetaNS
* @param {Object} {obj} Object to get meta property from
* @param {String} {namespace} Namespace to store meta property in
* @param {String} {path} Dot notation path to evaluate
* @param {*} {[defaultValue=undefined]} Specify a default value to return if the requested meta property is not found, is null, undefined, NaN or !isFinite
* @return {*} Return property specified by path
* @see devoir.getMeta
* @see devoir.setMeta
**/
root.getMetaNS = prop.bind(root, 'getMetaNS');
/**
* Set a namespaced meta property on an object by evaluating a dot-notation path into an object. This is the same as @@devoir.setMeta except that the value is stored in a namespace
*
* @parent devoir
* @function setMetaNS
* @param {Object} {obj} Object to set meta property on
* @param {String} {namespace} Namespace to store meta property in
* @param {String} {path} Dot notation path to evaluate
* @param {*} {value} Value to set
* @return {String} Return the actual final path (relative to the base object) where the meta property was set. This is useful if meta property was pushed into an array; the actual array index will be returned as part of the final path
* @see devoir.getMeta
**/
root.setMetaNS = prop.bind(root, 'setMetaNS');
root.id = id;
root.aliases = aliases;
root.audit = audit;
root.setROProperty = setProperty.bind(root, false);
root.setRWProperty = setProperty.bind(root, true);
root.empty = empty;
root.newClass = newClass;
return root;
});
}).call(this,require('_process'))
},{"_process":1}],5:[function(require,module,exports){
// (c) 2016 Wyatt Greenway
// This code is licensed under MIT license (see LICENSE.txt for details)
// Helper Functions and utilities
(function(factory) {
module.exports = function(_root, _base) {
var root = _root || {},
base = _base;
if (!base)
base = require('./base');
return factory(root, base);
};
})(function(root, D) {
'use strict';
/**
* @namespace {devoir}
* @namespace {data} Devoir Data Functions
*/
function sortBySubKey(array, negate) {
var args = arguments;
return array.sort(function(a, b) {
var x, y;
for (var i = 2; i < args.length; i++) {
var key = args[i];
var isPath = (key.indexOf('.') > -1);
if (typeof a === 'object')
x = (isPath) ? D.get(a, key) : a[key];
else
x = a;
if (typeof b === 'object')
y = (isPath) ? D.get(b, key) : b[key];
else
y = b;
if (y !== undefined && x !== undefined)
break;
}
if (y === undefined || y === null || !('' + y).match(/\S/))
y = -1;
if (x === undefined || x === null || !('' + x).match(/\S/))
x = 1;
var result = ((x < y) ? -1 : ((x > y) ? 1 : 0));
return (negate) ? (result * -1) : result;
});
}
root.sortBySubKey = sortBySubKey;
/**
* @function {extend} Extend (copy) objects into base object. This should ALWAYS be used instead of jQuery.extend
because it is faster, and more importantly because it WILL NOT mangle instantiated
sub-objects.
* @param {[flags]}
* @type {Object} Will be considered the first of <b>args</b>
* @type {Boolean} If true this is a deep copy
* @type {Number} This is a bitwise combination of flags. Flags include: devoir.data.extend.DEEP, devoir.data.extend.NO_OVERWRITE, and devoir.data.extend.FILTER. If the 'FILTER' flag is specified the 2nd argument is expected to be a function to assist in which keys to copy
* @end
* @param {[args...]}
* @type {Object} Objects to copy into base object. First object in the argument list is the base object.
* @type {Function} If the second argument is a function and the bitwise flag "FILTER" is set then this function will be the callback used to filter the keys of objects during copy
@param {String} {key} Key of object(s) being copied
@param {*} {value} Value being copied
@param {Object} {src} Parent Object/Array where value is being copied from
@param {*} {dstValue} Value of same key at destination, if any
@param {Object} {dst} Parent Object/Array where value is being copied to
@end
* @end
* @return {Object} Base object with all other objects merged into it
*/
function extend() {
if (arguments.length === 0)
return;
if (arguments.length === 1)
return arguments[0];
var isDeep = false;
var allowOverwrite = true;
var onlyMatching = false;
var filterFunc;
var startIndex = 0;
var dst = arguments[0];
if (typeof dst === 'boolean') {
isDeep = dst;
startIndex++;
} else if (typeof dst === 'number') {
isDeep = (dst & extend.DEEP);
allowOverwrite = !(dst & extend.NO_OVERWRITE);
startIndex++;
filterFunc = (dst & extend.FILTER) ? arguments[startIndex++] : undefined;
}
//Destination object
dst = arguments[startIndex++];
if (!dst)
dst = {};
var val;
if (isDeep) {
for (var i = startIndex, len = arguments.length; i < len; i++) {
var thisArg = arguments[i];
if (!(thisArg instanceof Object))
continue;
var keys = Object.keys(thisArg);
for (var j = 0, jLen = keys.length; j < jLen; j++) {
var key = keys[j];
if (allowOverwrite !== true && dst.hasOwnProperty(key))
continue;
val = thisArg[key];
var dstVal = dst[key];
if (filterFunc && filterFunc(key, val, thisArg, dstVal, dst) === false)
continue;
if (val && typeof val === 'object' && !(val instanceof String) && !(val instanceof Number) &&
(val.constructor === Object.prototype.constructor || val.constructor === Array.prototype.constructor)) {
var isArray = (val instanceof Array);
if (!dstVal)
dstVal = (isArray) ? [] : {};
val = extend(true, (isArray) ? [] : {}, dstVal, val);
}
dst[key] = val;
}
}
} else {
for (var i = startIndex, len = arguments.length; i < len; i++) {
var thisArg = arguments[i];
if (!(thisArg instanceof Object))
continue;
var keys = Object.keys(thisArg);
for (var j = 0, jLen = keys.length; j < jLen; j++) {
var key = keys[j];
if (allowOverwrite !== true && dst.hasOwnProperty(key))
continue;
val = thisArg[key];
if (filterFunc) {
var dstVal = dst[key];
if (filterFunc(key, val, thisArg, dstVal, dst) === false)
continue;
}
dst[key] = val;
}
}
}
if (dst._audit) {
var b = dst._audit.base;
b.modified = D.now();
b.updateCount++;
}
return dst;
}
root.extend = extend;
(function extend_const(base) {
base.DEEP = 0x01;
base.NO_OVERWRITE = 0x02;
base.FILTER = 0x04;
})(extend);
function matching(deep) {
function buildPathMap(pathMap, obj, path) {
for (var key in thisArg) {
if (!thisArg.hasOwnProperty(key))
continue;
if (!pathMap.hasOwnProperty(key)) {
pathMap[key] = 1;
continue;
}
pathMap[key] = pathMap[key] + 1;
}
}
var isDeep = false;
var startIndex = 1;
var dst = {};
var pathMap = {};
if (typeof deep === 'boolean') {
isDeep = deep;
startIndex = 1;
} else {
startIndex = 0;
}
for (var i = startIndex, len = arguments.length; i < len; i++) {
var thisArg = arguments[i];
buildPathMap(pathMap, thisArg);
}
var objCount = arguments.length - startIndex;
var lastObj = arguments[arguments.length - 1];
for (var key in pathMap) {
if (!pathMap.hasOwnProperty(key))
continue;
var val = pathMap[key];
if (val >= objCount)
dst[key] = lastObj[key];
}
return dst;
}
root.matching = matching;
/**
* @function {extract} Extracts elements from an Array of Objects (not parts from a String). This is not the same as @@@function:devoir.utils.extract
* @param {String} {key} Key to extract from all objects
* @param {Object} {[args...]} Array(s) to extract from
* @return {Object} Array of extracted properties. If the property wasn't found the array element will be 'undefined'.
* @see function:devoir.data.toLookup
* @example {javascript}
var myParts = D.data.extract('id', [
{
id:'derp',
field: 'derp'
},
{
id:'dog',
field: 'dog'
},
{
id:'cat',
field: 'cat'
}
], [
{
id:'another',
field: 'another'
},
{
id:'field',
field: 'field'
}
]);
myParts === ['derp','dog','cat','another','field'];
*/
function extract(key) {
var thisArray = [];
for (var i = 1, len = arguments.length; i < len; i++) {
var args = arguments[i];
if (!args)
continue;
for (var j in args) {
if (!args.hasOwnProperty(j))
continue;
var val = D.get(args[j], key);
thisArray.push(val);
}
}
return thisArray;
}
root.extract = extract;
/**
* @function {toLookup} This takes an Array and returns a reference map for quick lookup.
* @param {String} {key} Key to match on all objects. If key is undefined or null, the index will be used instead.
* @param {Array} {data} Array to create map from
* @return {Object} Each key in the object will be the value in the Array specified by 'key'
* @see function:devoir.data.extract
* @example {javascript}
var myMap = D.data.toLookup('id', [
{
id:'derp',
field: 'derp'
},
{
id:'dog',
field: 'dog'
},
{
id:'cat',
field: 'cat'
}
]);
myMap === {
'derp': {
id:'derp',
field: 'derp'
},
'dog': {
id:'dog',
field: 'dog'
},
'cat': {
id:'cat',
field: 'cat'
}
};
*/
function toLookup(key, data) {
if (!data)
return {};
var obj = {};
for (var k in data) {
if (!data.hasOwnProperty(k))
continue;
var v = data[k];
if (key) {
var id = D.get(v, key);
if (!id)
continue;
} else {
id = v;
}
obj[id] = v;
}
return obj;
}
root.toLookup = toLookup;
/**
* @function {mergeKeys} Merge/Facet keys of all objects passed in
* @param {Object} {[objects...]} Object(s) to gather unique keys from
* @return {Object} An object where each key is a key in at least one of the objects passed in.
The value for each key is the number of times that key was encountered.
* @example {javascript}
var obj1 = {hello:'world',derp:'cool'}, obj2 = {hello:'dude',cool:'beans'};
$mn.data.mergeKeys(obj1, obj2); //= {hello:2,derp:1,cool:1}
*/
function mergeKeys() {
var obj = {};
for (var i = 0, il = arguments.length; i < il; i++) {
var data = arguments[i];
var keys = Object.keys(data);
for (var j = 0, jl = keys.length; j < jl; j++) {
var k = keys[j];
if (!obj.hasOwnProperty(k))
obj[k] = 0;
obj[k]++;
}
}
return obj;
}
root.mergeKeys = mergeKeys;
/**
* @function {toArray} Convert Object(s) to an Array/Array of keys
* @param {[keys]}
* @type {Boolean} Specifies if the result will be an array of keys (true), or an array of objects (false)
* @type {Object} The first object to convert
* @end
* @param {Object} {[args...]} Objects to add to Array
* @return {Array} If *keys* is false or an Object, than all array elements will be the properties of the supplied object(s).
If *keys* is true, than all array elements will be the keys of all the properties of the supplied object(s).
* @see {devoir.data.extract} {devoir.data.toLookup}
* @example {javascript}
var myArray = D.data.toArray(
{
id:'derp',
field: 'test',
caption: 'Hello world!'
});
myArray === ['derp','test','Hello World!'];
myArray = myArray = D.data.toArray(true,
{
id:'derp',
field: 'test',
caption: 'Hello world!'
});
myArray === ['id','field','caption'];
*/
function toArray(keys) {
var startIndex = 1;
var thisKeys = keys;
if (!D.utils.dataTypeEquals(thisKeys, 'boolean')) {
thisKeys = false;
startIndex = 0;
}
var thisArray = [];
for (var i = startIndex, len = arguments.length; i < len; i++) {
var args = arguments[i];
if (!args)
continue;
for (var j in args) {
if (!args.hasOwnProperty(j))
continue;
if (thisKeys === true && !(args instanceof Array))
thisArray.push(j);
else
thisArray.push(args[j]);
}
}
return thisArray;
}
root.toArray = toArray;
/**
* @function {walk} Walk an object, calling a callback for each property. If callback returns **false** the walk will be aborted.
* @param {Object} {data} Object to walk
* @param {callback} Callback to be called for every property on the object tree
* @type {Function}
* @param {String} {thisPath} full key path in dot notation (i.e. 'property.child.name')
* @param {Object} {value} Current property value
* @param {String} {key} Key name of current property
* @param {Object} {data} Current property parent object
* @return {Boolean} **false** to abort walking
* @end
* @end
* @return {Boolean|undefined} **false** if walk was canceled (from callback). Undefined otherwise.
*/
function walk(data, callback, path, parentData, originalData, parentContext, depth, visitedIDMap) {
if (!(callback instanceof Function))
return;
if (!data)
return;
if (typeof data !== 'object')
return;
if (!visitedIDMap)
visitedIDMap = {};
if (originalData === undefined)
originalData = data;
if (path === undefined)
path = '';
if (!depth)
depth = 0;
if (depth > 64)
throw new Error('Maximum walk depth exceeded');
var context = {
parent: parentContext
};
for (var k in data) {
if (!data.hasOwnProperty(k)) continue;
var thisPath;
if (data instanceof Array)
thisPath = path + '[' + k + ']';
else {
thisPath = (path) ? [path, k].join('.') : k;
}
var v = data[k];
if (v !== null && typeof v === 'object') {
var objectID = D.id(v);
if (visitedIDMap[objectID]) //No infinite recursion
continue;
visitedIDMap[objectID] = v;
if (root.walk(v, callback, thisPath, data, originalData, context, depth + 1, visitedIDMap) === false)
return false;
}
context.depth = depth;
context.scope = data;
context.parentScope = parentData;
context.originalData = originalData;
context.fullPath = thisPath;
if (callback.apply(context, [thisPath, v, k, data, parentData, originalData, depth]) === false)
return false;
}
}
root.walk = walk;
/**
* @function {flatten} Flatten an object where the returned object's keys are full path notation, i.e:
{
my: {},
my.path: {},
my.path.to: {},
my.path.to.key: [],
my.path.to.key[0]: 'Derp',
my.path.to.key[1]: 'Hello'
}
* @param {Object} {obj} Object to flatten
* @param {Number} {[maxDepth]} If specified, don't traverse any deeper than this number of levels
* @return {Object} Flattened object
*/
function flatten(obj, maxDepth) {
var ref = {};
root.walk(obj, function(path, v, k) {
if (maxDepth && this.depth <= maxDepth)
ref[path] = v;
else if (!maxDepth)
ref[path] = v;
});
return ref;
}
root.flatten = flatten;
/**
* @function {ifAny} See if any of the elements in the provided array match the comparison arguments provided
* @param {Array|Object} {testArray} Array/Object to iterate
* @param {*} {[args...]} Any object/value to test with strict comparison against elements of "testArray" (testArray[0]===arg[1]||testArray[0]===arg[2]...)
* @return {Boolean} **true** if ANY elements match ANY provided arguments, **false** otherwise
*/
function ifAny(testArray) {
if (!testArray)
return false;
if (root.instanceOf(testArray, 'string', 'number', 'boolean', 'function'))
return false;
if (!(testArray instanceof Array) && !(testArray instanceof Object))
return false;
var keys = Object.keys(testArray);
for (var i = 0, il = keys.length; i < il; i++) {
var key = keys[i],
elem = testArray[key];
for (var j = 1, jl = arguments.length; j < jl; j++) {
if (elem === arguments[j])
return true;
}
}
return false;
}
root.ifAny = ifAny;
return root;
});
},{"./base":4}],6:[function(require,module,exports){
(function (process){
(function(factory) {
module.exports = function(_root) {
var root = _root || {};
return factory(root);
};
})(function(root) {
'use strict';
/**
* @namespace devoir
* @class {Deferred} Devoir Deferreds
**/
/**
* @constructor Create a new Deferred. If a callback is provided it will be called as soon as the deferred is fully initialized (on nextTick), or immediately if immediate mode is true.
* This callback will be provided three arguments which are all functions: resolve, reject, and notify<br>
* * resolve: is called to resolve this deferred, optionally passing any arguments you wish to resolve the deferred with
* * reject: is called to reject this deferred, optionally passing any arguments you wish to reject the deferred with
* * notify: is called to update the progress of this deferred. Normally there would only be one numerical argument in the range 0-1, but any arguments are valid (progress will be set to arguments provided)
* @param {Function} {callback} Callback function. If specified call as soon as the deferred is created. If the "immediate" flag is specified, call the callback immediately upon construction, otherwise call on "nextTick"
* @param {[options]} Options object
* @type {Object}
* @property {raw=false} if true, deferred resolution arguments will be passed as a single array to any bound resolution / rejection listeners. By default resolution arguments will be passed directly to any bound listeners in the order provided to "resolve"
* @property {immediate=false} if true, this deferred will call its callback immediately, and also call any listeners immediately upon resolution / rejection. Default is to call callback and listeners on "nextTick"
* @return {Deferred} A new Deferred instance
* @example {javascript}
function loadFile(fileName) {
var fs = require('fs');
return new devoir.Deferred(function(resolve, reject) {
fs.loadFile(fileName, function(err, contents) {
if (err) {
reject(err);
return;
}
resolve(contents);
});
});
}
@example {javascript}
function longTask() {
var def = new devoir.Deferred();
setTimeout(function() {
def.resolve();
}, 6000);
return def.promise();
}
@example {javascript}
function loadFile(fileName) {
var fs = require('fs');
return new devoir.Deferred(function(resolve, reject) {
fs.loadFile(fileName, function(err, contents) {
if (err) {
reject(err);
return;
}
resolve(contents);
});
});
}
function loadMultipleFiles(fileNames) {
//Create an array of deferreds
var deferreds = [];
for (var i = 0, il = fileNames.length; i < il; i++) {
deferreds.push(loadFile(fileNames[i]));
}
//Create a new deferred that will resolve when all other deferreds have resolved
//If anyone of the deferreds fail the entire deferred chain will fail
return devoir.Deferred.all(deferreds);
}
**/
function Deferred(cb, _opts) {
function newROProperty(name, func, _parent) {
var parent = _parent || self;
Object.defineProperty(parent, name, {
writable: false,
enumerable: false,
configurable: false,
value: func
});
}
function doAllCallbacks(type, callbackObj, args) {
function doIt() {
var callbackFunc = callbackObj[type], ret;
if (callbackFunc instanceof Function)
ret = callbackFunc.apply(self, (raw) ? [args] : args);
if (ret instanceof Deferred) {
ret.proxy(callbackObj.deferred);
} else {
callbackObj.deferred[type](ret);
}
}
if (immediate)
doIt();
else
process.nextTick(doIt);
}
function then(resolve, reject, notify) {
var def = new Deferred(),
callbackObj = {
resolve: resolve,
reject: reject,
notify: notify,
deferred: def
};
if (state === 'pending') {
callbacks.push(callbackObj);
} else {
doAllCallbacks((state === 'rejected') ? 'reject' : 'resolve', callbackObj, result);
}
return def;
}
function statusUpdate(type, currentProgress) {
if (state !== 'pending')
return;
var args = [];
if (raw) {
args = currentProgress;
} else {
for (var i = 1, len = arguments.length; i < len; i++)
args.push(arguments[i]);
}
if (type === 'notify') {
progress = currentProgress;
} else {
result = args;
state = (type === 'resolve') ? 'fulfilled' : 'rejected';
}
for (var i = 0, len = callbacks.length; i < len; i++) {
doAllCallbacks.call(self, type, callbacks[i], args);
}
}
if (!(this instanceof Deferred)) {
var args = [Object.create(Deferred.prototype)];
for (var j = 0, l = arguments.length; j < l; j++)
args.push(arguments[j]);
return Deferred.bind.apply(Deferred, args);
}
var self = this,
opts = (_opts !== undefined) ? _opts : {
raw: false,
immediate: false
},
state = "pending",
immediate = opts.immediate,
raw = opts.raw,
progress = 0,
callbacks = [],
result;
/**
* @function {then} Call *successCallback* when deferred resolves (or *errorCallback* if deferred is rejected), passing arguments as provided to @@@devoir.Deferred.resolve (unless the "raw" option is set, and then all arguments will be provided in a single array)
* @param {Function} {successCallback} Callback function to call when deferred has resolved
* @param {Function} {[errorCallback]} Callback function to call when deferred is rejected
* @param {Function} {[notificationCallback]} Callback function to call when deferred gets a notification update
* @return {Deferred} Return a new deferred that can be used for chaining (if *successCallback* returns a deferred, this deferred wont resolve until the one returned by *successCallback* resolves)
**/
newROProperty('then', then);
/**
* @alias {done} function:devoir.Deferred.then
**/
newROProperty('done', then);
/**
* @function {fail} Call *errorCallback* if deferred is rejected
* @param {Function} {errorCallback} Callback function to call if deferred is rejected
* @return {Deferred} A new deferred that can be used for chaining. See @@@devoir.Deferred.then
**/
newROProperty('fail', then.bind(self, undefined));
/**
* @alias {catch} function:devoir.Deferred.fail
**/
newROProperty('catch', then.bind(self, undefined));
/**
* @function {notification} Call *notificationCallback* if deferred gets a notification event
* @param {Function} {notificationCallback} Callback function to call if deferred gets a progress notification event
* @return {Deferred} A new deferred that can be used for chaining. See @@@devoir.Deferred.then
**/
newROProperty('notification', then.bind(self, undefined, undefined));
/**
* @function {always} Call *callback* if deferred gets resolved or rejected. Call *notificationCallback* if deferred gets a progress notification
* @param {Function} {callback} Callback function to call if deferred gets resolved or rejected
* @param {Function} {[notificationCallback]} Callback function to call if deferred gets a progress notification event
* @return {Deferred} A new deferred that can be used for chaining. See @@@devoir.Deferred.then
**/
newROProperty('always', function(func, notify) {
return then.call(this, func, func, notify);
});
/**
* @function {resolve} Resolve this deferred with the arguments provided
* @param {*} {[args...]} Arguments to resolve this deferred with. These will be passed on to any bound resolve callbacks
* @return {undefined} None
**/
newROProperty('resolve', statusUpdate.bind(self, 'resolve'));
/**
* @function {reject} Reject this deferred with the arguments provided
* @param {*} {[args...]} Arguments to reject this deferred with. These will be passed on to any bound reject callbacks
* @return {undefined} None
**/
newROProperty('reject', statusUpdate.bind(self, 'reject'));
/**
* @function {notify} Notify this deferred with a progress update (usually a Number between 0 and 1, but is not required to be a number)
* @param {*} {[args...]} Arguments to notify this deferred with. These will be passed on to any bound notification callbacks
* @return {undefined} None
**/
newROProperty('notify', statusUpdate.bind(self, 'notify'));
/**
* @function {promise} Return a special clone deferred object that can be used as a normal "view" into this deferred, but can not be updated / modified (read only)
* @return {Deferred} A read-only clone of this deferred
**/
newROProperty('promise', function() {
function nullFunc(){}
var p = Object.create(self);
newROProperty('resolve', nullFunc, p);
newROProperty('reject', nullFunc, p);
newROProperty('notify', nullFunc, p);
return p;
});
/**
* @function {status} Get the status of this deferred
* @return {String} **"pending"** if the deferred is still pending, **"fulfilled"** if the deferred has been resolved, or **"rejected"** if the deferred has been rejected
**/
newROProperty('status', function() {
return state;
});
/**
* @alias {state} function:devoir.Deferred.status
**/
newROProperty('state', function() {
return state;
});
/**
* @function {progress} Get the progress for this deferred as set by notifications
* @return {*} The current progress (usually a Number) of this deferred
* @see function:devoir.Deferred.notify
**/
newROProperty('progress', function() {
return progress;
});
/**
* @function {proxy} Proxy the resolution / rejection / notifications of this deferred to the deferred specified by *deferred* argument
* @param {Deferred} {deferred} The deferred to proxy events to
* @return {Deferred} A new Deferred that can be used for chaining
* @see function:devoir.Deferred.resolve
* @see function:devoir.Deferred.reject
* @see function:devoir.Deferred.notify
**/
newROProperty('proxy', function(deferred) {
function proxyAction(type) {
var args = [];
for (var i = 1, len = arguments.length; i < len; i++)
args.push(arguments[i]);
return deferred[type].apply(deferred, args);
}
return self.then(function() {
return proxyAction.bind(self, 'resolve').apply(self, arguments);
}, function() {
return proxyAction.bind(self, 'reject').apply(self, arguments);
}, function() {
return proxyAction.bind(self, 'notify').apply(self, arguments);
});
});
/**
* @function {immediate} Get/Set this deferred's **"immediate"** mode. In **"immediate"** mode all callbacks are called immediately, instead of "nextTick", which is the default
* @param {Boolean} {[set]} If specified, set **"immediate"** mode according to boolean value. If not specified, return "immediate" mode state
* @return {Deferred} If *set* argument is specified, *this* is returned for chaining.
* @return {Boolean} If *set* is not specified, return the current **"immediate"** mode state
**/
newROProperty('immediate', function(set) {
if (arguments.length === 0)
return immediate;
immediate = set;
return self;
});
/**
* @function {raw} Get/Set this deferred's **"raw"** mode. In **"raw"** mode all callbacks are called with a single array of arguments as the argument to any callbacks instead of passing arguments as supplied to resolve/reject/notify
* @param {Boolean} {[set]} If specified, set "raw" mode according to boolean value. If not specified, return "raw" mode state
* @return {Deferred} If *set* argument is specified, *this* is returned for chaining.
* @return {Boolean} If *set* is not specified, return the current **"raw"** mode state
**/
newROProperty('raw', function(set) {
if (arguments.length === 0)
return raw;
raw = set;
return self;
});
if (cb instanceof Function) {
if (immediate) {
cb.call(self, self.resolve, self.reject, self.notify);
} else {
process.nextTick(function() {
cb.call(self, self.resolve, self.reject, self.notify);
});
}
} else if (cb instanceof Object) {
if (cb.hasOwnProperty('immediate'))
immediate = cb.immediate;
if (cb.hasOwnProperty('raw'))
raw = cb.raw;
}
return this;
}
/**
* @function {all} This will create a new deferred that waits on the status of ALL deferreds passed to it. If any one of the deferreds is rejected the entire chain is rejected
* @static
* @public
* @param {Array} {deferreds} Array if deferreds to wait on
* @return {Deferred} A new deferred wrapping all deferreds provided by the argument *deferreds*
**/
Deferred.all = function(promises, opts) {
var resolvedValues = new Array(promises.length),
resolvedCount = 0;
return new Deferred(function(resolve, reject, notify) {
var self = this;
for (var i = 0, len = promises.length; i < len; i++) {
(function(_promise, i) {
var promise = _promise;
if (!(promise instanceof Deferred))
promise = Deferred.resolve(promise);
promise.then(function(val) {
if (self.status() !== 'pending')
return;
var args = [];
for (var j = 0, l = arguments.length; j < l; j++)
args.push(arguments[j]);
resolvedValues[i] = (args.length > 1) ? args : args[0];
resolvedCount++;
notify(resolvedCount / len);
if (resolvedCount >= len)
resolve.apply(self, (self.raw()) ? [resolvedValues] : resolvedValues);
}, function() {
if (self.status() !== 'pending')
return;
notify(1);
reject.apply(self, arguments);
});
})(promises[i], i);
}
}, opts);
};
/**
* @function {race} This will create a new deferred that waits on the status of ALL deferreds passed to it. This deferred will be resolved/rejected as soon as any one of the deferreds is resolved or rejected
* @static
* @public
* @param {Array} {deferreds} Array if deferreds to wait on
* @return {Deferred} A new deferred wrapping all deferreds provided by the argument *deferreds*
**/
Deferred.race = function(promises, opts) {
return new Deferred(function(resolve, reject, notify) {
var self = this;
for (var i = 0, len = promises.length; i < len; i++) {
(function(_promise) {
var promise = _promise;
if (!(promise instanceof Deferred))
promise = Deferred.resolve(promise);
promise.then(function() {
if (self.status() !== 'pending')
return;
notify(1);
resolve.apply(self, arguments);
}, function() {
if (self.status() !== 'pending')
return;
notify(1);
reject.apply(self, arguments);
});
})(promises[i]);
}
}, opts);
};
/**
* @function {every} This will create a new deferred that waits on the status of ALL deferreds passed to it. This deferred will not resolve / reject until ALL deferreds have resolved / rejected. If anyone of the deferreds has been rejected than the whole chain is rejected
* @static
* @public
* @param {Array} {deferreds} Array if deferreds to wait on
* @return {Deferred} A new deferred wrapping all deferreds provided by the argument *deferreds*
**/
Deferred.every = function(promises, opts) {
var resolvedValues = new Array(promises.length),
resolvedCount = 0;
return new Deferred(function(resolve, reject, notify) {
var self = this;
function doCallback(resolvedValues) {
var isRejected = false;
for (var i = 0, len = promises.length; i < len; i++) {
if (promises[i].status() === 'rejected') {
isRejected = true;
break;
}
}
if (isRejected)
reject.apply(self, (self.raw()) ? [resolvedValues] : resolvedValues);
else
resolve.apply(self, (self.raw()) ? [resolvedValues] : resolvedValues);
}
for (var i = 0, len = promises.length; i < len; i++) {
(function(_promise, i) {
var promise = _promise;
if (!(promise instanceof Deferred))
promise = Deferred.resolve(promise);
promise.then(function() {
if (self.status() !== 'pending')
return;
var args = [];
for (var j = 0, l = arguments.length; j < l; j++)
args.push(arguments[j]);
resolvedValues[i] = (args.length > 1) ? args : args[0];
resolvedCount++;
notify(resolvedCount / len);
if (resolvedCount >= len)
doCallback(resolvedValues);
}, function() {
if (self.status() !== 'pending')
return;
var args = [];
for