'use strict'; /*@ Ox.avg Returns the average of an array's values, or an object's properties (collection) -> Average value collection <[n]|o> Array or object with numerical values > Ox.avg([-1, 0, 1]) 0 > Ox.avg({a: 1, b: 2, c: 3}) 2 > Ox.avg('avg is 0.1') 0.1 @*/ Ox.avg = function(collection) { return Ox.sum(collection) / Ox.len(collection); }; /*@ Ox.clone Returns a (shallow or deep) copy of an array or object > (function() { var a = ['v'], b = Ox.clone(a); a[0] = null; return b[0]; }()) 'v' > (function() { var a = {k: 'v'}, b = Ox.clone(a); a.k = null; return b.k; }()) 'v' > Ox.clone(0) 0 > (function() { var a = [[0, 1]], b = Ox.clone(a); a[0][0] = null; return b[0]; }()) [null, 1] > (function() { var a = [[0, 1]], b = Ox.clone(a, true); a[0][0] = null; return b[0]; }()) [0, 1] @*/ Ox.clone = Ox.copy = function(collection, deep) { // fixme: copy or clone? var ret, type = Ox.typeOf(collection); if (deep) { ret = type == 'array' ? [] : {}; Ox.forEach(collection, function(value, key) { type = Ox.typeOf(value); ret[key] = type == 'array' || type == 'object' ? Ox.clone(value, true) : value; }); } else { ret = type == 'array' ? collection.slice() : type == 'object' ? Ox.extend({}, collection) : collection; } return ret; }; /*@ Ox.contains Tests if a collection contains a value (collection, value) -> If true, the collection contains the value collection Collection value <*> Any value > Ox.contains(['foo', 'bar'], 'foo') true > Ox.contains({foo: 'bar'}, 'bar') true > Ox.contains({foo: 'bar'}, 'foo') false > Ox.contains('foobar', 'bar') true @*/ // FIXME: a shorter name would be nice (but IE8 doesn't like 'in') Ox.contains = function(collection, value) { return ( Ox.isObject(collection) ? Ox.values(collection) : collection ).indexOf(value) > -1; }; /*@ Ox.count Counts the occurences of values in a collection (collection) -> Number of occurrences per value (collection, value) -> Number of occurrences of the given value collection Collection value <*> Any value > Ox.count(['f', 'o', 'o']) {f: 1, o: 2} > Ox.count({a: 'f', b: 'o', c: 'o'}) {f: 1, o: 2} > Ox.count('foo') {f: 1, o: 2} > Ox.count('foo', 'f') 1 > Ox.count('foo', 'x') 0 @*/ Ox.count = function(collection, value) { var count = {}; Ox.forEach(collection, function(value) { count[value] = (count[value] || 0) + 1; }); return value ? count[value] || 0 : count; }; /*@ Ox.every Tests if every element of a collection satisfies a given condition Unlike `Array.prototype.every`, `Ox.every` works for arrays, objects and strings. (collection, iterator) -> True if every element passes the test collection Collection iterator Iterator value <*> Value key Index or key collection The collection > Ox.every([0, 1, 2], function(v, i) { return v == i; }) true > Ox.every({a: 1, b: 2, c: 3}, function(v) { return v == 1; }) false > Ox.every("foo", function(v) { return v == 'f'; }) false > Ox.every([true, true, true]) true @*/ Ox.every = function(collection, iterator) { return Ox.filter( Ox.values(collection), iterator || Ox.identity ).length == Ox.len(collection); }; /*@ Ox.filter Filters a collection by a given condition Unlike `Array.prototype.filter`, `Ox.filter` works for arrays, objects and strings. > Ox.filter([2, 1, 0], function(v, i) { return v == i; }) [1] > Ox.filter({a: 'c', b: 'b', c: 'a'}, function(v, k) { return v == k; }) {b: 'b'} > Ox.filter(' foo bar ', function(v) { return v != ' '; }) 'foobar' @*/ Ox.filter = function(collection, iterator, that) { var ret, type = Ox.typeOf(collection); iterator = iterator || Ox.identity; if (type == 'object') { ret = {}; Ox.forEach(collection, function(value, key) { if (iterator.call(that, value, key, collection)) { ret[value] = key; } }); } else { ret = Ox.toArray(collection).filter(iterator, that); if (type == 'string') { ret = ret.join(''); } } return ret; }; /*@ Ox.forEach forEach loop `Ox.forEach` loops over arrays, objects and strings. Calling `Ox.Break` inside the iterator or returning `false` from the iterator acts like a `break` statement. Unlike `Array.prototype.forEach`, which leaks its counter variable to the outer scope, `Ox.forEach` returns it. (collection, iterator[, that]) -> Next index collection Collection iterator Iterator value <*> Value key Index or key collection The collection that The iterator's `this` binding > Ox.test.string "012abcfoo" > Ox.forEach({a: 'f', b: 'o', c: 'o'}, function(v, k) { return v != 'o' }); 1 @*/ Ox.forEach = function(collection, iterator, that) { var i = 0, key, type = Ox.typeOf(collection); if (type != 'array' && type != 'object') { collection = Ox.toArray(collection); } try { if (type == 'object') { for (key in collection) { if (Ox.hasOwn(collection, key)) { // iterator.call(that, collection[key], key, collection); if (iterator.call(that, collection[key], key, collection) === false) { console.warn('Returning false in Ox.forEach is deprecated.'); break; } } i++; } } else { for (i = 0; i < collection.length; i++) { if (i in collection) { // iterator.call(that, collection[i], i, collection); if (iterator.call(that, collection[i], i, collection) === false) { console.warn('Returning false in Ox.forEach is deprecated.'); break; } } } } } catch (error) { if (error !== Ox.BreakError) { throw error; } } return i; }; /*@ Ox.indexOf Returns the first index of a collection element that passes a test > Ox.indexOf([1, 2, 3], function(val) { return val % 2 == 0; }) 1 > Ox.indexOf({a: 1, b: 2, c: 3}, function(val) { return val % 2 == 0; }) 'b' > Ox.indexOf('FooBar', function(val) { return val == val.toUpperCase(); }) 0 > Ox.indexOf([1, 2, 3], function(val) { return val == 0; }) -1 @*/ Ox.indexOf = function(collection, test) { var index = Ox.forEach(collection, function(value) { test(value) && Ox.Break(); }); return Ox.isObject(collection) ? Object.keys(collection)[index] || null : index == collection.length ? -1 : index; }; /*@ Ox.indicesOf Returns all indices of collection elements that pass a test > Ox.indicesOf([1, 2, 3], function(val) { return val % 2 == 1; }) [0, 2] > Ox.indicesOf({a: 1, b: 2, c: 3}, function(val) { return val % 2 == 1; }) ['a', 'c'] > Ox.indicesOf('FooBar', function(val) { return val == val.toUpperCase(); }) [0, 3] > Ox.indicesOf([1, 2, 3], function(val) { return val == 0; }) [] @*/ Ox.indicesOf = function(collection, test) { var ret = []; Ox.forEach(collection, function(value, index) { test(value) && ret.push(index); }); return ret; }; /*@ Ox.isEmpty Tests if a value is an empty array, object or string (value) -> True if the value is an empty array, object or string value <*> Any value > Ox.isEmpty([]) true > Ox.isEmpty({}) true > Ox.isEmpty('') true > Ox.isEmpty(function() {}) false > Ox.isEmpty(false) false > Ox.isEmpty(null) false > Ox.isEmpty(0) false > Ox.isEmpty() false @*/ Ox.isEmpty = function(value) { return Ox.len(value) === 0; }; /*@ Ox.len Returns the length of an array, node list, object or string Not to be confused with `Ox.length`, which is the `length` property of the `Ox` function (`1`). > Ox.len((function() { return arguments; }(1, 2, 3))) 3 > Ox.len([1, 2, 3]) 3 > Ox.len([,]) 1 > Ox.typeOf(Ox.len(document.getElementsByTagName('a'))) 'number' > Ox.len({a: 1, b: 2, c: 3}) 3 > Ox.len('abc') 3 > Ox.len(function(a, b, c) {}) undefined @*/ // FIXME: Ox.size() ? Ox.len = function(collection) { var ret, type = Ox.typeOf(collection); if ( type == 'arguments' || type == 'array' || type == 'nodelist' || type == 'string' ) { ret = collection.length; } else if (type == 'object') { ret = Object.keys(collection).length; } return ret; }; /*@ Ox.map Transforms the values of an array, object or string Unlike `Array.prototype.map`, `Ox.map` works for arrays, objects and strings. > Ox.map([2, 1, 0], function(v, i) { return v == i; }) [false, true, false] > Ox.map({a: 'b', b: 'b', c: 'b'}, function(v, k) { return v == k; }) {a: false, b: true, c: false} > Ox.map('foo', function(v) { return v.toUpperCase(); }) 'FOO' > Ox.map([,], function(v, i) { return i; }) [,] @*/ Ox.map = function(collection, iterator, that) { var ret, type = Ox.typeOf(collection); if (type == 'object') { ret = {}; Ox.forEach(collection, function(value, key) { ret[key] = iterator.call(that, value, key, collection); }); } else { ret = Ox.toArray(collection).map(iterator); if (type == 'string') { ret = ret.join(''); } } return ret; }; /*@ Ox.max Returns the maximum value of a collection > Ox.max([1, 2, 3]) 3 > Ox.max({a: 1, b: 2, c: 3}) 3 > Ox.max('123') 3 @*/ Ox.max = function(collection) { var ret, values = Ox.values(collection); if (values.length < Ox.STACK_LENGTH) { ret = Math.max.apply(null, values) } else { ret = values.reduce(function(previousValue, currentValue) { return Math.max(previousValue, currentValue); }, -Infinity); } return ret; }; /*@ Ox.min Returns the minimum value of a collection > Ox.min([1, 2, 3]) 1 > Ox.min({a: 1, b: 2, c: 3}) 1 > Ox.min('123') 1 @*/ Ox.min = function(collection) { var ret, values = Ox.values(collection); if (values.length < Ox.STACK_LENGTH) { ret = Math.min.apply(null, values) } else { ret = values.reduce(function(previousValue, currentValue) { return Math.min(previousValue, currentValue); }, Infinity); } return ret; }; /*@ Ox.reverse Reverses an array or string > Ox.reverse([1, 2, 3]) [3, 2, 1] > Ox.reverse('foobar') 'raboof' @*/ Ox.reverse = function(collection) { return Ox.isArray(collection) ? Ox.clone(collection).reverse() : collection.toString().split('').reverse().join(''); }; /*@ Ox.shuffle Randomizes the order of values within a collection > Ox.shuffle([1, 2, 3]).length 3 > Ox.len(Ox.shuffle({a: 1, b: 2, c: 3})) 3 > Ox.shuffle('123').split('').sort().join('') '123' @*/ Ox.shuffle = function(collection) { var keys, ret, type = Ox.typeOf(collection), values; if (type == 'object') { keys = Object.keys(collection); values = Ox.shuffle(Ox.values(collection)); ret = {}; keys.forEach(function(key, index) { ret[key] = values[index]; }); } else { ret = []; Ox.toArray(collection).forEach(function(value, index) { var random = Math.floor(Math.random() * (index + 1)); ret[index] = ret[random]; ret[random] = value; }); if (type == 'string') { ret = ret.join(''); } } return ret; }; /*@ Ox.slice Alias for `Array.prototype.slice.call` > (function() { return Ox.slice(arguments, 1, -1); }(1, 2, 3)) [2] > (function() { return Ox.slice(arguments, 1); }(1, 2, 3)) [2, 3] @*/ Ox.slice = function(value, start, stop) { return Array.prototype.slice.call(value, start, stop); }; // IE8 returns an empty array if undefined is passed as stop // and an array of null values if a string is passed as value. // Firefox 3.6 returns an array of undefined values // if a string is passed as value. if ( Ox.slice([0]).length == 0 || Ox.slice('0')[0] === null || Ox.slice('0')[0] === void 0 ) { Ox.slice = function(value, start, stop) { if (Ox.typeOf(value) == 'string') { value = value.split(''); } return stop === void 0 ? Array.prototype.slice.call(value, start) : Array.prototype.slice.call(value, start, stop); }; } /*@ Ox.some Tests if one or more elements of a collection meet a given condition Unlike `Array.prototype.some`, `Ox.some` works for arrays, objects and strings. > Ox.some([2, 1, 0], function(i, v) { return i == v; }) true > Ox.some({a: 1, b: 2, c: 3}, function(v) { return v == 1; }) true > Ox.some("foo", function(v) { return v == 'f'; }) true > Ox.some([false, null, 0, '', void 0]) false @*/ Ox.some = function(collection, iterator) { return Ox.filter(Ox.values(collection), iterator || Ox.identity).length > 0; }; /*@ Ox.sum Returns the sum of the values of a collection > Ox.sum(1, 2, 3) 6 > Ox.sum([1, 2, 3]) 6 > Ox.sum({a: 1, b: 2, c: 3}) 6 > Ox.sum('123') 6 > Ox.sum('123foo') 6 > Ox.sum('08', -2, 'foo') 6 @*/ Ox.sum = function(collection) { var ret = 0; collection = arguments.length > 1 ? Ox.toArray(arguments) : collection; Ox.forEach(collection, function(value) { value = +value; ret += isFinite(value) ? value : 0; }); return ret; }; /* FIXME: do we need this kind of zip functionality? Ox.arrayToObject = function(array, key) { var ret = {}; array.forEach(function(v) { ret[v[key]] = v; }); return ret; }; Ox.objectToArray = function(object, key) { var ret = []; Ox.forEach(object, function(v, k) { ret.push(Ox.extend(v, key, k)); }); return ret }; */ /*@ Ox.values Returns the values of a collection (collection) -> Array of values collection Collection > Ox.values([1, 2, 3]) [1, 2, 3] > Ox.values({a: 1, b: 2, c: 3}) [1, 2, 3] > Ox.values('abc') ['a', 'b', 'c'] > Ox.values([1,,3]) [1,,3] @*/ Ox.values = function(collection) { var ret, type = Ox.typeOf(collection); if (type == 'array') { ret = Ox.clone(collection); } else if (type == 'object') { ret = []; Ox.forEach(collection, function(value) { ret.push(value); }); } else if (type == 'string') { ret = collection.split(''); } return ret; }; /*@ Ox.walk Iterates over a nested data structure (collection, iterator[, that]) -> undefined collection Collection iterator Iterator value <*> Value keys Array of keys collection The collection that The iterator's `this` binding > Ox.test.number 6 > Ox.test.array [['a'], ['b', 'c'], ['b', 'd']] @*/ Ox.walk = function(collection, iterator, that, keys) { keys = keys || []; Ox.forEach(collection, function(value, key) { var keys_ = keys.concat(key); iterator.call(that, value, keys_, collection); Ox.walk(collection[key], iterator, that, keys_); }); };