Skip to content

Instantly share code, notes, and snippets.

@togakangaroo
Last active August 29, 2015 13:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save togakangaroo/8939329 to your computer and use it in GitHub Desktop.
Save togakangaroo/8939329 to your computer and use it in GitHub Desktop.
define(
['_underscore', 'jquery'],
function underscoreExtensions(_, $){
//Object and collection helpers
_.mixin({
// Create object from a callback that returns [name, value] pairs. We use this combination a lot
// so might as well bake it into the framework
mapObject: _.compose(_.object, _.map)
// Create a new object by removing keys from object1 which also exist in object2
,differenceObject: function(obj1, obj2) {
return _.pick(obj1, _.difference(_.keys(obj1), _.keys(obj2)) );
}
// Shortcut similar to _.find(collection, function(){ return x.SlideId == val})
// or _.where(collection, {SlideId: val})[0]
// Usage:
// var slide3 = _.findBy(entities, 3, 'SlideId');
,findBy: function (collection, val, prop) {
return _.find(collection, function findBy(x) { return x&&(x[prop] === val) });
}
// Similar to find but returns the index of the matching item
,findIndex: function(collection, iterator) {
for(var i=0,len=collection.length;i<len; i++)
if(iterator(collection[i], i, collection))
return i;
return -1;
}
// Pluck values out of an array of objects.
// Usage:
// arr = [ {a:'a1', b: 'b1', c: 'c1'}
// ,{a:'a2', b: 'b2', c: 'c2'} ];
// _.pluckValues(arr, 'b', 'c')
// or _.pluckValues(arr, ['b', 'c']); =>
// [ {b: 'b1', c: 'c1'}
// ,{b: 'b2', c: 'c2'} ];
,pluckValues: function(obj, properties) {
properties = _.flatten(_.tail(arguments));
if(_.isArray(obj))
return _.map(obj, function(val){ return _.pick(val, properties)});
return _.mapObject(obj, function(val, key){
return [key, _.pick(val, properties)];
});
}
// Take a collection and return an object with a key for each property of each object
// note that this is not optimized for large collections or large objects.
// Usage:
// arr = [ {a:'a1', b: 'b1'}
// ,{a:'a2', c: 'c2'} ];
// or _.pivotAllProperties(arr); =>
// { a: ['a1', 'a2'], b: ['b1', undefined], c: [undefined, 'c2'] };
,pivotAllProperties: function(collection) {
var allKeys = _.uniq(_.flatten(_.map(collection, _.keys)))
;
return _.reduce(allKeys, function(obj, k) {
obj[k] = _.pluck(collection, k);
return obj;
}, {});
}
// Take all array properties of an object and turn them to an array of objects with properties corresponding to
// that data.
,unPivot: function(obj) {
var arrayProps = _.chain(obj).map(function(v,k){ return _.isArray(v) && k} ).reject(_.isUndefined).value()
,maxLength = Math.max.apply(Math, _.pluck(_.pick(obj, arrayProps), 'length') ) //of those properties that are arrays
;
return _.map(_.range(maxLength), function(i){
return _.reduce(arrayProps, function(memo, p){
memo[p] = obj[p][i];
return memo;
}, {});
})
}
// return the array itself but with any functions replaced by the results of invoking them
,resultAll: function(collection) {
var args = _.tail(arguments)
return _.map(collection, function(v) {
return _.isFunction(v) ? v.apply(undefined, _.tail(args)) : v
})
}
//tap each item in the collection returning the collection itself
,tapEach: function(collection, fn, bindTo) {
_.each(collection, fn, bindTo);
return collection;
}
//Throw an error when the given keys do not belong to the object.
//Useful for explicitly stating and checking non-optional keys in options objects
//Usage:
// _.ensureHasKeys(this.options, 'mode', 'language');
,ensureHasKeys: function(obj) {
if(!obj) {
console.error("Expected object but recieved", obj);
throw new Error("Expected object but no object was recieved");
}
var keys = _.tail(arguments);
_.each(keys, function(key) {
if (!_.has(obj, key)) {
console.error("Expected object ", obj, "to contain", key);
throw new Error("Expected object to contain " + key);
}
});
}
//More meaningful than $.extend(true, {}, obj) - but that's what it is
,deepClone: function(obj) {
return $.extend(true, {}, obj);
}
//union while flattening through any nested arrays
,unionAll: _.compose(_.flatten, _.union)
//A very rough implementation of a random on a gaussian distribution.
//Inspired by http://www.protonfish.com/random.shtml
,pseudoGaussianRandom: function(mean, stdev) {
var threeStdDevs = (Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1);
return threeStdDevs*stdev+mean;
}
});
// Functional helpers
_.mixin({
//A simplistic caching function.
//Execute the given function with a given set of parameters no more frequently than
//every [duration] milliseconds. Any invocations within that window will simply return
//the last stored value (for the given parameter set). Useful for functions that
//should be memoized for a period of time such as repeated ajax lookups of data that
//changes infrequently. Takes an optional third parameter [hasher] function to determine
//a unique hash for a given set of parameters. If none is provided, default equality for
//the first parameter is used.
memoizeForDuration: function(func, duration, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function memoizedFn() {
var key = hasher.apply(this, arguments);
if(_.has(memo, key))
return memo[key];
_.delay(function() { delete memo[key] }, duration);
return memo[key] = func.apply(this, arguments);
}
}
//Shift all arguments one position to the left, dropping the leftmost. This is useful for situations where
//you do not care about the first argument (for example a jquery callback's event object)
,leftShiftArgs: function(fn) {
return function() {
fn.apply( this, _.toArray(arguments).slice(1) );
};
}
//Start polling and execute a callback. Returns a promise that becomes resolved when
//polling concludes
,poll: function(op) {
_.defaults(op, {
until: function(){return true}, every: 200, timeout: 1500, success: $.noop, onTimeout: $.noop
});
var deferred = $.Deferred();
deferred.done(op.success).fail(op.onTimeout);
var startTime = new Date().getTime();
check();
return deferred.promise();
function check() {
if(op.until())
return deferred.resolve();
if(isTimedOut())
return deferred.reject("Timeout");
_.delay(check, op.every);
}
function isTimedOut() {
return new Date().getTime() - startTime;
}
}
//Automatically call bindAll on all functions in the object. To lock down 'this' to the
//object itself
,bindAllFunctions: function(obj) {
return _.bindAll.apply(_, _.union([obj], _.functions(obj)) );
}
//Takes a filterFunction and an execution function. Returns a new method that when
//run will only trigger the exection function if the filter function returns truthy.
,runWhen: function(fnFilter, fn) {
return function runWhen(){
if(fnFilter.apply(this, arguments))
return fn.apply(this, arguments);
}
}
//Simply run the given function. Useful as a default for determinging whether somethign should be defered, debounced, etc
,run: function(fn) { return fn.call(this) }
//Execute a metnod now and return it. Useful when you want to subscribe to an event with a callback AND execute it immediately eg
// paramContext.events.selectionsChanged.add( _.immediate(recalculateAvalableSlideSets) )
,immediate: function(fn) {
fn.apply(null, _.tail(arguments));
return fn;
}
});
//GM - allow interpolation of text like this "hello {{name}}"
_.templateSettings.interpolate = /\{\{(.+?)\}\}/g;
//Hierarchy helpers
_.mixin({
//Visitor pattern for navigating tree-like structures assumes a default children property
//named 'children', otherwise, a string or function can be provided
visit: function(root, callback, childProperty) {
if(null == root) return;
var getChildren = getChildrenFn(childProperty);
callback(root)
_.each(getChildren(root), function(child){
_.visit(child, callback, getChildren);
})
}
//Visit all nodes of an object graph and for each property invoke a callback. Then descend into any object properties
//and repeat. Will not visit the same node twice.
// _.visitObject(ancestryTree, function(val, propertyName, parent) { ... })
,visitObject: function(root, callback, name, visited) {
visited || (visited = [])
if(_.contains(visited, root))
return;
_.each(root, function(val, propName) { callback(val, propName, root) })
visited.push(root);
_.chain(root).filter(isNavigable).each(function(val, propName) { _.visitObject(val, callback, propName, visited) });
}
//Visits all nodes in a tree-line structure and returns all nodes that match a filter test function
,collectTree: function(root, test, childProperty) {
var result = [];
function testNode(node) {
test(node) && result.push(node);
}
_.visit(root, testNode, childProperty);
return result;
}
//Returns all nodes from a tree-like structure
,collectTreeNodes: function(root, childProperty) {
return _.collectTree(root, function(){ return true }, childProperty)
}
});
return _;
function getChildrenFn(prop) {
return _.isFunction(prop) ? prop :
(function getChildrenFn(x){ return x[prop|| 'children']});
}
function isNavigable(root) {
return !( _.isNull(root) || _.isUndefined(root) || _.isFunction(root) || _.isNumber(root) || _.isString(root) );
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment