app/assets/javascripts/ultimate/underscore/underscore.js in ultimate-base-0.2.3.2 vs app/assets/javascripts/ultimate/underscore/underscore.js in ultimate-base-0.2.4

- old
+ new

@@ -1,8 +1,8 @@ // Underscore.js 1.3.3 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. +// Underscore may be freely distributed under the MIT license. // Portions of Underscore are inspired or borrowed from Prototype, // Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore @@ -22,11 +22,12 @@ // Save bytes in the minified (but not gzipped) version: var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. - var slice = ArrayProto.slice, + var push = ArrayProto.push, + slice = ArrayProto.slice, unshift = ArrayProto.unshift, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use @@ -74,11 +75,11 @@ if (obj == null) return; if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { - if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; + if (iterator.call(context, obj[i], i, obj) === breaker) return; } } else { for (var key in obj) { if (_.has(obj, key)) { if (iterator.call(context, obj[key], key, obj) === breaker) return; @@ -94,11 +95,10 @@ if (obj == null) return results; if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); each(obj, function(value, index, list) { results[results.length] = iterator.call(context, value, index, list); }); - if (obj.length === +obj.length) results.length = obj.length; return results; }; // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. @@ -211,22 +211,26 @@ // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); return _.map(obj, function(value) { - return (_.isFunction(method) ? method || value : value[method]).apply(value, args); + return (_.isFunction(method) ? method : value[method]).apply(value, args); }); }; // Convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { return _.map(obj, function(value){ return value[key]; }); }; // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See: https://bugs.webkit.org/show_bug.cgi?id=80797 _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj); + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } if (!iterator && _.isEmpty(obj)) return -Infinity; var result = {computed : -Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed >= result.computed && (result = {value : value, computed : computed}); @@ -234,11 +238,13 @@ return result.value; }; // Return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj); + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } if (!iterator && _.isEmpty(obj)) return Infinity; var result = {computed : Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed < result.computed && (result = {value : value, computed : computed}); @@ -246,22 +252,24 @@ return result.value; }; // Shuffle an array. _.shuffle = function(obj) { - var shuffled = [], rand; - each(obj, function(value, index, list) { - rand = Math.floor(Math.random() * (index + 1)); - shuffled[index] = shuffled[rand]; + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = Math.floor(Math.random() * ++index); + shuffled[index - 1] = shuffled[rand]; shuffled[rand] = value; }); return shuffled; }; // Sort the object's values by a criterion produced by an iterator. _.sortBy = function(obj, val, context) { - var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; + var iterator = lookupIterator(obj, val); return _.pluck(_.map(obj, function(value, index, list) { return { value : value, criteria : iterator.call(context, value, index, list) }; @@ -271,30 +279,53 @@ if (b === void 0) return -1; return a < b ? -1 : a > b ? 1 : 0; }), 'value'); }; - // Groups the object's values by a criterion. Pass either a string attribute - // to group by, or a function that returns the criterion. - _.groupBy = function(obj, val) { + // An internal function to generate lookup iterators. + var lookupIterator = function(obj, val) { + return _.isFunction(val) ? val : function(obj) { return obj[val]; }; + }; + + // An internal function used for aggregate "group by" operations. + var group = function(obj, val, behavior) { var result = {}; - var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; + var iterator = lookupIterator(obj, val); each(obj, function(value, index) { var key = iterator(value, index); - (result[key] || (result[key] = [])).push(value); + behavior(result, key, value); }); return result; }; - // Use a comparator function to figure out at what index an object should - // be inserted so as to maintain order. Uses binary search. + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, val) { + return group(obj, val, function(result, key, value) { + (result[key] || (result[key] = [])).push(value); + }); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function(obj, val) { + return group(obj, val, function(result, key, value) { + result[key] || (result[key] = 0); + result[key]++; + }); + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator) { iterator || (iterator = _.identity); + var value = iterator(obj); var low = 0, high = array.length; while (low < high) { var mid = (low + high) >> 1; - iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; + iterator(array[mid]) < value ? low = mid + 1 : high = mid; } return low; }; // Safely convert anything iterable into a real, live array. @@ -319,11 +350,11 @@ // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; }; - // Returns everything but the last entry of the array. Especcialy useful on + // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. The **guard** check allows it to work with // `_.map`. _.initial = function(array, n, guard) { return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); @@ -350,17 +381,25 @@ // Trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, function(value){ return !!value; }); }; + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, output) { + each(input, function(value) { + if (_.isArray(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + // Return a completely flattened version of an array. _.flatten = function(array, shallow) { - return _.reduce(array, function(memo, value) { - if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); - memo[memo.length] = value; - return memo; - }, []); + return flatten(array, shallow, []); }; // Return a version of the array that does not contain the specified value(s). _.without = function(array) { return _.difference(array, slice.call(arguments, 1)); @@ -370,14 +409,12 @@ // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. _.uniq = _.unique = function(array, isSorted, iterator) { var initial = iterator ? _.map(array, iterator) : array; var results = []; - // The `isSorted` flag is irrelevant if the array only contains two elements. - if (array.length < 3) isSorted = true; - _.reduce(initial, function (memo, value, index) { - if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) { + _.reduce(initial, function(memo, value, index) { + if (isSorted ? (_.last(memo) !== value || !memo.length) : !_.include(memo, value)) { memo.push(value); results.push(array[index]); } return memo; }, []); @@ -385,16 +422,16 @@ }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { - return _.uniq(_.flatten(arguments, true)); + return _.uniq(flatten(arguments, true, [])); }; // Produce an array that contains every item shared between all the - // passed-in arrays. (Aliased as "intersect" for back-compat.) - _.intersection = _.intersect = function(array) { + // passed-in arrays. + _.intersection = function(array) { var rest = slice.call(arguments, 1); return _.filter(_.uniq(array), function(item) { return _.every(rest, function(other) { return _.indexOf(other, item) >= 0; }); @@ -402,24 +439,36 @@ }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { - var rest = _.flatten(slice.call(arguments, 1), true); + var rest = flatten(slice.call(arguments, 1), true, []); return _.filter(array, function(value){ return !_.include(rest, value); }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { var args = slice.call(arguments); var length = _.max(_.pluck(args, 'length')); var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } return results; }; + // Zip together two arrays -- an array of keys and an array of values -- into + // a single object. + _.zipObject = function(keys, values) { + var result = {}; + for (var i = 0, l = keys.length; i < l; i++) { + result[keys[i]] = values[i]; + } + return result; + }; + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), // we need this function. Return the position of the first occurrence of an // item in an array, or -1 if the item is not included in the array. // Delegates to **ECMAScript 5**'s native `indexOf` if available. // If the array is large and already in sort order, pass `true` @@ -430,20 +479,20 @@ if (isSorted) { i = _.sortedIndex(array, item); return array[i] === item ? i : -1; } if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); - for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i; + for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; return -1; }; // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. _.lastIndexOf = function(array, item) { if (array == null) return -1; if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); var i = array.length; - while (i--) if (i in array && array[i] === item) return i; + while (i--) if (array[i] === item) return i; return -1; }; // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See @@ -538,14 +587,14 @@ }; if (!timeout) timeout = setTimeout(later, wait); if (throttling) { more = true; } else { + throttling = true; result = func.apply(context, args); } whenDone(); - throttling = true; return result; }; }; // Returns a function, that, as long as it continues to be invoked, will not @@ -558,13 +607,14 @@ var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) func.apply(context, args); }; - if (immediate && !timeout) func.apply(context, args); + var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); }; }; // Returns a function that will be executed at most one time, no matter how // often you call it. Useful for lazy initialization. @@ -602,11 +652,13 @@ // Returns a function that will only be executed after being called N times. _.after = function(times, func) { if (times <= 0) return func(); return function() { - if (--times < 1) { return func.apply(this, arguments); } + if (--times < 1) { + return func.apply(this, arguments); + } }; }; // Object Functions // ---------------- @@ -646,11 +698,11 @@ }; // Return a copy of the object only containing the whitelisted properties. _.pick = function(obj) { var result = {}; - each(_.flatten(slice.call(arguments, 1)), function(key) { + each(flatten(slice.call(arguments, 1), true, []), function(key) { if (key in obj) result[key] = obj[key]; }); return result; }; @@ -676,12 +728,12 @@ _.tap = function(obj, interceptor) { interceptor(obj); return obj; }; - // Internal recursive comparison function. - function eq(a, b, stack) { + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, stack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; @@ -762,11 +814,11 @@ } } // Remove the first object from the stack of traversed objects. stack.pop(); return result; - } + }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { return eq(a, b, []); }; @@ -794,35 +846,25 @@ // Is a given variable an object? _.isObject = function(obj) { return obj === Object(obj); }; - // Is a given variable an arguments object? - _.isArguments = function(obj) { - return toString.call(obj) == '[object Arguments]'; - }; + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { return !!(obj && _.has(obj, 'callee')); }; } - // Is a given value a function? - _.isFunction = function(obj) { - return toString.call(obj) == '[object Function]'; - }; - - // Is a given value a string? - _.isString = function(obj) { - return toString.call(obj) == '[object String]'; - }; - - // Is a given value a number? - _.isNumber = function(obj) { - return toString.call(obj) == '[object Number]'; - }; - // Is a given object a finite number? _.isFinite = function(obj) { return _.isNumber(obj) && isFinite(obj); }; @@ -835,31 +877,22 @@ // Is a given value a boolean? _.isBoolean = function(obj) { return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; }; - // Is a given value a date? - _.isDate = function(obj) { - return toString.call(obj) == '[object Date]'; - }; - - // Is the given value a regular expression? - _.isRegExp = function(obj) { - return toString.call(obj) == '[object RegExp]'; - }; - // Is a given value equal to null? _.isNull = function(obj) { return obj === null; }; // Is a given variable undefined? _.isUndefined = function(obj) { return obj === void 0; }; - // Has own property? + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). _.has = function(obj, key) { return hasOwnProperty.call(obj, key); }; // Utility Functions @@ -876,17 +909,32 @@ _.identity = function(value) { return value; }; // Run a function **n** times. - _.times = function (n, iterator, context) { + _.times = function(n, iterator, context) { for (var i = 0; i < n; i++) iterator.call(context, i); }; + // List of HTML entities for escaping. + var htmlEscapes = { + '&': '&amp;', + '<': '&lt;', + '>': '&gt;', + '"': '&quot;', + "'": '&#x27;', + '/': '&#x2F;' + }; + + // Regex containing the keys listed immediately above. + var htmlEscaper = /[&<>"'\/]/g; + // Escape a string for HTML interpolation. _.escape = function(string) { - return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;'); + return ('' + string).replace(htmlEscaper, function(match) { + return htmlEscapes[match]; + }); }; // If the value of the named property is a function then invoke it; // otherwise, return it. _.result = function(object, property) { @@ -925,20 +973,20 @@ var noMatch = /.^/; // Certain characters need to be escaped so that they can be put into a // string literal. var escapes = { - '\\': '\\', - "'": "'", - 'r': '\r', - 'n': '\n', - 't': '\t', - 'u2028': '\u2028', - 'u2029': '\u2029' + '\\': '\\', + "'": "'", + r: '\r', + n: '\n', + t: '\t', + u2028: '\u2028', + u2029: '\u2029' }; - for (var p in escapes) escapes[escapes[p]] = p; + for (var key in escapes) escapes[escapes[key]] = key; var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g; // Within an interpolation, evaluation, or escaping, remove HTML escaping // that had been previously added. @@ -960,36 +1008,34 @@ var source = "__p+='" + text .replace(escaper, function(match) { return '\\' + escapes[match]; }) .replace(settings.escape || noMatch, function(match, code) { - return "'+\n_.escape(" + unescape(code) + ")+\n'"; + return "'+\n((__t=(" + unescape(code) + "))==null?'':_.escape(__t))+\n'"; }) .replace(settings.interpolate || noMatch, function(match, code) { - return "'+\n(" + unescape(code) + ")+\n'"; + return "'+\n((__t=(" + unescape(code) + "))==null?'':__t)+\n'"; }) .replace(settings.evaluate || noMatch, function(match, code) { - return "';\n" + unescape(code) + "\n;__p+='"; + return "';\n" + unescape(code) + "\n__p+='"; }) + "';\n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; - source = "var __p='';" + - "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'')};\n" + source + "return __p;\n"; var render = new Function(settings.variable || 'obj', '_', source); if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); }; - // Provide the compiled function source as a convenience for build time - // precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + - source + '}'; + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; return template; }; // Add a "chain" function, which will delegate to the wrapper. @@ -1027,14 +1073,13 @@ // Add all mutator Array functions to the wrapper. each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; wrapper.prototype[name] = function() { - var wrapped = this._wrapped; - method.apply(wrapped, arguments); - var length = wrapped.length; - if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0]; - return result(wrapped, this._chain); + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result(obj, this._chain); }; }); // Add all accessor Array functions to the wrapper. each(['concat', 'join', 'slice'], function(name) {