UNPKG

bauhaus

Version:

A library of functional, list, pagination, iteration methods for arrays for Client and Nodejs.

1,476 lines (1,314 loc) 38.5 kB
"use strict"; (function() { // #indexOf and #lastIndexOf are attached to #LIST_M collection. // var ARRAY_M = [ "all", // alias #every "any", // alias #some "collect", // alias #map "each", // alias #forEach "every", "filter", "foldl", // alias #reduce "foldr", // alias #reduceRight "forEach", "indexOf", "lastIndexOf", "map", "reduce", "reduceRight", "select", // alias #filter "some" ]; var LIST_M = [ "bump", "commit", "compact", "compose", "contains", "diff", "findFirst", "findLast", "first", "firstx", "flatten", "get", "group", "insert", "insertBefore", "insertAfter", "intersect", "invoke", "iterator", "last", "lastx", "max", "min", "$original", "page", "paginate", "pluck", "range", "reject", "remove", "reset", "sequence", "set", "shuffle", "search", "size", "sortBy", "sortedIndex", "totalPages", "union", "unique", "unset", "$value", "$without", "zip" ]; var ORIGINAL = []; var ACTIVE = []; var PAGINATION = 0; var CUR_PAGE = 1; var OP_TO_STRING = Object.prototype.toString; var AP_SLICE = Array.prototype.slice; var FUNC_PROTO = Function.prototype; var M_MIN = Math.min; var M_MAX = Math.max; var UNIQUE_ID = 1; // Object representing the public interface, assigned to #bauhaus bound to either // `module` or `window` context, extended with methods in #ARRAY_M and #LIST_M. // var $$ = { // #after // // Returns a function whose callback will only execute once the function has // been called n times. // // @argumentList // 0 : {Number} The call instance on which the callback fires. // 1 : {Function} The callback to fire // [2] : {Object} A context to fire callback in. // // @example var confirm = after(notes.length, function() { alert("All notes saved"); }) // each(notes, function(note) { // note.asyncSave({callback: confirm}); // }); // after : function(count, cb, ctxt) { return !count ? cb : function() { if(!--count) { return cb.apply(ctxt, arguments); } } }, // ##argsToArray // // Converts an arguments object to an array. // // @param {Arguments} a // @param {Number} [offset] Index to begin plucking arguments. Default 0. // @param {Number} [end] Index to stop plucking arguments. Default to end. // argsToArray : function(a, offset, end) { return AP_SLICE.call(a, offset || 0, end); }, // ##arrayToObject // // Converts an array to an object // // @param {Array} a The array to convert. // // @example ["a","b","c"] -> {a: 0, b: 1, c: 2} // arrayToObject : function(a) { var len = a.length; var ob = {}; while(len--) { ob[a[len]] = len; } return ob; }, // ##bind // // Ensure the execution context of a function. // bind : function(f, c) { var a = $$.argsToArray(arguments, 2); return function() { return f.apply(c, a.concat($$.argsToArray(arguments))); } }, // ##bindAll // // Ensures that the execution context of all (or some) of the methods // in an object remains the object itself, regardless of the ultimate context // within which all (or some) of the object methods are called. // // @argumentList // 0 : {Object} The object containing methods to bind. // [1..n] : {String} Any number of object method names. If none sent, all // object methods are bound. // bindAll : function(obj) {; var a = $.argsToArray(arguments, 1); var f; $$.each(a.length ? a : $$.objectToArray(obj), function(e, i) { f = obj[e]; $$.is(Function, f) && (obj[e] = $$.bind(f, obj)); }, obj); }, // ##compiledFunction // // Returns a function F which will execute #fbody within the context F is called. // // @param {String} fbody The body of the function to be created. // // @example: var f = scopedFunction("console.log(foo)"); // f.apply/call({ foo: "bar" }); // `bar` // compiledFunction : function(fbody) { return Function( "with(this) { return (function(){" + fbody + "})(); };" ) }, // ##copy // // Copies an object. // // @param {Mixed} o The copy candidate. // @param {Boolean} [deep] Whether do a deep copy. // copy : function(o, deep) { var cp = function(ob) { var fin; var p; if(typeof ob !== "object" || ob === null || ob.$n) { return ob; } if(!deep) { return ob.slice(0); } try { fin = new ob.constructor; } catch(e) { return ob; } for(p in ob) { fin[p] = cp(ob[p], deep); } return fin; } return cp(o); }, // ##escape // // Escapes HTML text, making it suitable for insertion into page flow. // // @param {String} text The text to escape. // // @see http://davidchambersdesign.com/escaping-html-in-javascript/ // escape : function(text) { return text.replace(/[&<>"'`]/g, function(chr) { return '&#' + chr.charCodeAt(0) + ';'; }); }, // ##extend // // Extends #bauhaus. // extend : function(m) { $$[m] = function() { // Methods have varying functional signatures. // var a = $$.argsToArray(arguments); // #LIST_ACCESSOR expects method name as first argument. // a.unshift(m); return LIST_ACCESSOR.apply($$, a); }; }, // ##is // // Whether #val is of #type. For most objects the constructor is // matched. // // @param {Mixed} type An object type. // @param {Mixed} val The value to check. // @type {Boolean} // // @example var inst = new SomeFunc(); // $$.is(SomeFunc, inst) // true // is : function(type, val) { if(!type || val === void 0) { return false; } var p; switch(type) { case Array: return OP_TO_STRING.call(val) === '[object Array]'; break; case Object: return OP_TO_STRING.call(val) === '[object Object]'; break; case "numeric": return !isNaN(parseFloat(val)) && isFinite(val); break; case "element": return val.nodeType === 1; break; case "empty": for(p in val) { return false; } return true; break; default: return val.constructor === type; break; } }, // ##memoize // // @param {Function} f The function to memoize. // @param {Object} [scp] The scope to execute the function within. // memoize : function(f, scp) { var m = {}; return function() { var a = $$.argsToArray(arguments); // Key joins arguments on escape character as delimiter which should be safe. // var k = a.join("\x1B"); return m.hasOwnProperty(k) || (m[k] = f.apply(scp, a)); }; }, // ##noop // // A benign anonymous function. // noop : function(){}, // ##objectToArray // // Converts an object to an array. // // @param {Object} o The object to convert. // @param {Boolean} [vals] Whether to use object value as its index in new array. // // @example : var obj = {a:1,b:2,c:3} // Without #vals : ["a","b","c"] // With #vals : [undefined,"a","b","c"] // objectToArray : function(o, vals) { var p; var r = []; for(p in o) { if(vals) { r[o[p]] = p; } else { r[r.length] = p; } } return r; }, // ##uniqueId // // Simply an incremented number + a prefix. You're safe until about +/- 9007199254740992. // // @param {String} [pref] A prefix for unique id. Defaults to "_"(underscore) // uniqueId : function(pref) { ++UNIQUE_ID; return (pref || "_") + UNIQUE_ID; } }; // ##ITERATOR // // Returns accumulator as modified by passed selective function. // Note that no checking of target is done, expecting that you send either an // array or an object. Error otherwise. Also see notes on @acc, below. // // @param {Function} fn The selective function. // @param {Object} [targ] The object to work against. // @param {Mixed} [acc] An accumulator, which is set to result of selective // function on each interation through target. If this // is undefined (void 0) this method returns the last // index reached. // @param {Object} [ctxt] A context to run the iterator in. // // @see #ARRAY_METHOD // var ITERATOR = function(fn, targ, acc, ctxt) { var c = targ.length; var n = 0; var idx = acc === void 0 ? true : false; if($$.is(Array, targ)) { while(n < c) { acc = ctxt ? fn.call(ctxt, targ[n], n, targ, acc) : fn(targ[n], n, targ, acc); if(acc === true) { break; } n++; } } else { for(n in targ) { acc = ctxt ? fn.call(ctxt, targ[n], n, targ, acc) : fn(targ[n], n, targ, acc); if(acc === true) { break; } } } return idx ? n : acc; }; // ##ARRAY_METHOD // // "Normalize" several array manipulation methods, such as #each and #map. // // @param {String} meth The array method. // @param {Function} fn The selective function. // @param {Object} [targ] The object to work against. If a string is sent, // try to fetch a list by that name. // @param {Mixed} [arg2] Usually the scope in which to execute the method, but // in the case of #reduce this is an [initialValue]. // @param {Mixed} [arg3] If #reduce, scope in which to execute the method. // var ARRAY_METHOD = function(meth, targ, fn, arg2, arg3) { // If the target is a string, assume a list being requested by name. // targ = typeof targ === "string" ? $$.range(targ, 0) : targ; // Can only operate on objects. // if(typeof targ !== "object") { return null; } var scope = arg3 || (arg2 || $$); // Will use native method if we're working with an array, and the method exists on // that array. Passing an object, IOW, uses custom method. // var nat = $$.is(Array, targ) && targ[meth]; nat = false; switch(meth) { case "each": case "forEach": return nat ? targ.forEach(fn, scope) : ITERATOR(function(elem, idx, targ) { fn.call(scope, elem, idx, targ); }, targ); break; case "collect": case "map": return nat ? targ.map(fn, scope) : ITERATOR(function(elem, idx, targ, acc) { acc[idx] = fn.call(scope, elem, idx, targ); return acc; }, targ, []); break; case "select": case "filter": return nat ? targ.filter(fn, scope) : ITERATOR(function(elem, idx, targ, acc) { fn.call(scope, elem, idx, targ) && acc.push(elem); return acc; }, targ, []); break; case "all": case "every": return nat ? targ.every(fn, scope) : ITERATOR(function(elem, idx, targ) { return fn.call(scope, elem, idx, targ) === false ? true : false; }, targ, []) === false; break; case "any": case "some": return nat ? targ.some(fn, scope) : ITERATOR(function(elem, idx, targ) { return fn.call(scope, elem, idx, targ) === true ? true : false; }, targ, []) === true; break; case "indexOf": if(nat) { return targ.indexOf(fn, arg2 === void 0 ? 0 : arg2); }; var off = arg2 || 0; var len = targ.length; while(off < len) { if(targ[off] === fn) { return off; } ++off; } return -1; break; case "lastIndexOf": return nat ? targ.lastIndexOf(fn, arg2 === void 0 ? targ.length : arg2) : $$.indexOf($$.copy(targ), fn, arg2); break; // Note how the #reduce methods change the argument order passed to the // selective function. // // All others : (element, index, target) // #reduce : (accumulator, element, index, target) // (or) : (initial or previous value, current value, index, target) // // @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Reduce // case "foldl": case "reduce": var offset = !arg2 ? 1 : 0; return nat ? targ.reduce(fn, arg2 === void 0 ? false : arg2) : ITERATOR(function(elem, idx, targ, acc) { return targ[idx + offset] ? fn.call(scope, acc, targ[idx + offset], idx + offset, targ) : acc; }, targ, arg2 || targ[0]); break; case "foldr": case "reduceRight": return $$.reduce($$.copy(targ).reverse(), fn, arg2); break; } }; // ##PROC_ARR_ARGS // // Ensures that sent array members are each arrays. For each member: // If array, do nothing; // If string, check if exists ACTIVE[string], use if so; // Else, empty array ([]). // // Note that the sent array reference itself is modified, so there is no return value. // // @see #union // @see #intersect // @see #diff // var PROC_ARR_ARGS = function(a) { var len = a.length; var c; while(len--) { c = a[len]; a[len] = $$.is(Array, c) ? c : typeof c === "string" && ACTIVE[c] ? ACTIVE[c] : []; } }; // ##SORTER // // Creates reusable sort functions. // // @see #sortBy // @href http://stackoverflow.com/questions/979256/how-to-sort-an-array-of-javascript-objects // var SORTER = $$.memoize(function(field, primer, descending) { var key = function(x) { return primer ? primer(x[field]) : x[field]; }; return function(a,b) { var left = key(a); var right = key(b); // [-1,1][+!!reverse] : Cast truth value of #descending(!!) to a number(+) which // will be (f || t) <- (0 || 1) <- [-1,1]. This function is intended as an // Array.sort argument, which sorts based on what its sort method returns: // -1 : left < right // 1 : left > right // 0 : left == right // Normally sorted ascending. By passing #descending, we multiply by -1, // inverting above table. // return ((left < right) ? -1 : (left > right) ? 1 : 0) * [1,-1][+!!descending]; } }); // ##LIST_METHOD // // These list methods only return a value if their operation is non-destructive. // The current operating list is passed by reference, and as such does not need // to be returned. // var LIST_METHOD = { // ##bump // // Replace #original at #key with a new list. // Returns bumped #original. // bump : function(cur, a, len, key) { var o = ORIGINAL[key]; if(ORIGINAL[key] && $$.is(Array, cur)) { ORIGINAL[key] = cur; } return o; }, // ##compact // // Removes all falsy values from list: false, null, 0, "", undefined, NaN. // compact : function(cur, a, len) { while(len--) { if(!cur[len]) { cur.splice(len, 1); } } return cur; }, // ##commit // // Will replace the #original list with the current #active list. // Returns previous #original. // commit : function(cur, a, len, key) { var o = ORIGINAL[key]; ORIGINAL[key] = $$.copy(ACTIVE[key]); return a; }, // ##compose // // Returns the composed result of a list of functions processed tail to head. // Expects a single argument, which is the value to pass to the tail function. // NOTE that no checking is done of list member types. If any member is not a // function this will error. // compose : function(cur, a, len) { var last = a; while(len--) { last = cur[len].apply(this, last); } return last; }, // ##contains // // Whether sent value exists in list // contains : function(cur, a, len) { return $$.search(cur, a[0]) !== -1; }, // ##diff // // Difference between first list (#prime) and subsequent lists (members of #prime // list which do not exist in any other list). NOTE: all duplicate entries in the // #prime array are reduced to single entries, regardless of differences. // // @see #LIST_ACCESSOR // @return {Array} // diff : function(cur, a, len) { var prime = $$.arrayToObject(cur); var si; var m; PROC_ARR_ARGS(a); while(m = a.shift()) { si = m.length; while(si--) { if(prime[m[si]] !== void 0) { delete prime[m[si]]; } } } return $$.objectToArray(prime); }, // ##findFirst // // Returns the first value matching iterator. // findFirst : function(cur, a) { return cur[ITERATOR(a[0], cur)]; }, // ##findLast // // Returns the last value matching iterator. // findLast : function(cur, a) { var c = $$.copy(cur).reverse(); return c[ITERATOR(a[0], c)]; }, // ##first // // Regarding the first item, either fetch it by sending zero arguments, // or set it by sending a single item value. If the list does not // exist, a new list will be created. // first : function(cur, a) { var c = a.length; if(c) { while(c--) { cur.unshift(a[c]); } } else { return cur.slice(0,1); } return cur; }, // ##firstx // // Regarding the first item, either fetch it by sending zero arguments, // or set it by sending a single item value. // // Unlike #first, #firstx will *not* create a list if one does not exist. // firstx : function(cur, a) { return cur ? this.first(cur, a) : null; }, // ##flatten // // Reduces an array of nested arrays to a single depth. // // @example : #flatten(['frank', ['bob', 'lisa'], ['jill', ['tom', 'sally']]]) // : ["sally", "tom", "jill", "lisa", "bob", "frank"] // flatten : function(cur, a, len, key) { var r = []; var c; var f = function(a) { var i = a.length; while(i--) { if(typeof a[i] === "object") { f(a[i]) } else { r[r.length] = a[i]; } } return r; } c = f(cur); if(key) { ACTIVE[key] = c; } return c; }, // ##get // // Return item at sent index. // // Indexes can be positive or negative, and are zero based. // `0` is first item, `1` is second, `-1` is last, `-2` is penultimate. // // @argumentList // [0]: {Number} The index. // get : function(cur, a, len) { return cur[a[0] >= 0 ? a[0] : len + a[0]]; }, // ##group // // Returns a grouping of list members based on results of passed iterator method. // // @argumentList // 0 : {Mixed} Either an iterator function, or a string property. // [1] : {Object} A scope to execute iterator within. // // @example #group([1.3, 2.1, 2.4], function(num){ return Math.floor(num); })) // { 1: [1.3], 2: [2.1, 2.4]} // #group(['one', 'two', 'three'], "length") // { 3: ["one", "two"], 5: ["three"] } // group : function(cur, a, len) { var i = a[0] var f = $$.is(String, i) ? function(elem) { return elem[i]; } : i; return ITERATOR(function(elem, idx, targ, acc) { var r = f.call(this, elem, idx, targ); (acc[r] || (acc[r] = [])).push(elem); return acc; }, cur, {}, a[1]); }, // ##insert // // Insert item before or after another item value. // // @argumentList // 0: {String} One of "before" or "after". // 1: {Mixed} The pivot value. // n: {Mixed} Any number of arguments (items) to be inserted // before or after given pivot value. // insert : function(cur, a, len) { var ins = a.slice(2); var piv = 1; while(len--) { if(cur[len] === a[1]) { if(a[0] == "before") { piv = -piv; } [].splice.apply(cur, [len + piv, 0].concat(ins)); break; } } return cur; }, // ##insertBefore // // Shortcut to #insert("before", ...); // insertBefore : function(cur, a, len) { return $$.insert(cur, "before", a[0]); }, // ##insertAfter // // Shortcut to #insert("after", ...); // insertAfter : function(cur, a, len) { return $$.insert(cur, "after", a[0]); }, // ##intersect // // Intersection of all lists (members which exist in all lists). // // @see #LIST_ACCESSOR // @return {Array} // intersect : function(cur, a, len) { var aLength = a.push(cur); var map = {}; var res = []; var m; var si; var c; PROC_ARR_ARGS(a); while(m = a.shift()) { si = m.length; while(si--) { c = map[m[si]]; ((map[m[si]] = c ? c += 1 : 1) === aLength) && (res[res.length] = m[si]); } } return res; }, // ##invoke // // Executes named method on list, passing named method any additional arguments // passed to #invoke. // invoke : function(cur, a, len) { var m = $$[a.shift()]; while(len--) { cur[len] = m.apply($$, [cur[len]].concat(a)); } return cur; }, // ##iterator // // Create a basic iterator for a list. // // To fetch iterator for #ACTIVE list, send no arguments. // To fetch iterator for #ORIGINAL list, send argument {String} "original". // To fetch iterator for any given array, send nothing, not even a key. // iterator : function(cur, a, len, key) { return new function() { var list = $$.copy( $$.is(Array, key) ? key : a[0] === "original" ? ORIGINAL[key] : ACTIVE[key], 1); this.idx = -1; this.moveIdx = function(d, circ) { var i = this.idx + d; var len = list.length -1; // If the operation exceeds list bound and the #circ(ular) directive // was not sent, the result of the operation is `undefined`. // if(!circ && (d === -1 ? i < 0 : i > len)) { return void 0; } this.idx = i < 0 ? len : i > len ? 0 : i; return this.idx; }; this.next = function(circ) { return list[this.moveIdx(1, circ)]; }; this.prev = function(circ) { return list[this.moveIdx(-1, circ)]; }; this.nextIndex = function() { return this.moveIdx(1); }; this.prevIndex = function() { return this.moveIdx(-1); }; this.hasNext = function() { return !((this.idx + 1) === list.length) }; this.hasPrev = function() { return !((this.idx - 1) === 0) }; this.remove = function() { var s = list.splice(this.idx, 1); if(list.length === this.idx) { this.prev(); } return s; }; this.set = function() { list[this.idx] = key; return a[0]; } }; }, // ##last // // Regarding the last item, either fetch it by sending zero arguments, // or set it by sending a single item value. If the list does not // exist, a new list will be created. // last : function(cur, a, len) { var c = a.length; var i = 0; if(c) { for(; i < c; i++) { cur[cur.length] = a[i]; } } else { return cur.slice(len -2, 1); } return cur; }, // ##lastx // // Regarding the last item, either fetch it by sending zero arguments, // or set it by sending a single item value. If the list does not // exist, a new list will be created. // // Unlike #last, #lastx will *not* create a list if one does not exist. // lastx : function(cur, a) { return cur ? this.last(cur, a) : null; }, // ##max // // Returns the maximum value in list. // // Arguments: // 0: {Function} [it] An iterator // 1: {Object} [scp] Scope to run iterator in. //(fn, targ, acc, ctxt) max : function(cur, a) { return a[0] ? ITERATOR(a[0], cur, -Infinity, a[1]) : M_MAX.apply(Math, cur); }, // ##min // // Returns the minimum value in list. // // Arguments: // 0: {Function} [it] An iterator // 1: {Object} [scp] Scope to run iterator in. // min : function(cur, a) { return a[0] ? ITERATOR(a[0], cur, Infinity, a[1]) : M_MIN.apply(Math, cur); }, // ##paginate // // Set the page width. // // @see #page // @see #totalPages // paginate : function(cur, a, len) { PAGINATION = M_MAX(0, a[0]); }, // ##page // // Returns the requested page array if #a is set, or the current page # if not. // // @argumentList // [0] : {Number} Page index. Returns page at page index. If undefined, returns // the current page index. // // @example #page(3) // ["foo","bar","baz"] // #page() // 3 // // @see #paginate // @see #totalPages // page : function(cur, a, len) { var p = a[0]; var pages = Math.ceil((len -1) / PAGINATION); var start; var end; // If #p is positive or negative the request is for a page array so run block. // If undefined, requesting #CUR_PAGE. Skip. // If 0(zero), treat like undefined. // if(p) { if(p < 0) { p = M_MAX(pages + p +1, 1); } else { p = M_MIN(p, pages); } start = M_MIN((p-1) * PAGINATION, len); end = M_MIN(start + PAGINATION, len); CUR_PAGE = p; return cur.slice(start, end); } return CUR_PAGE; }, // ##pluck // // Given a list of objects, will return a list of the values at given // index in each object. // // @example .pluck([ {user: 'tom', state: 'NY'}, // {user: 'dick', state: 'NJ'}, // {user: 'harry', state: 'ND'}], "state"); // // ['NY','NJ','ND'] // pluck : function(cur, a, len) { var p = a[0]; var r = []; var i = 0; var c; while(i < len) { c = cur[i]; if(typeof c === "object") { r[r.length] = c[p]; } i++ } return r; }, // ##range // // Returns a range of elements. // // Indexes can be positive or negative, and are zero based. // `0` is first item, `1` is second, `-1` is last, `-2` is penultimate. // NOTE: There is no range checking done. // // @argumentList // [0]: {Number} Start index. If no arguments are sent, the entire // array is returned. // [1]: {Number} End index. If none sent, range is start->end. // range : function(cur, a, len) { var v = [ a[0] >= 0 ? a[0] : len + a[0] , a[1] ? a[1] >= 0 ? a[1] : len + a[1] : len].sort(); return cur.slice(v[0], v[1]); }, // ##reject // // Return array of values which do *not* match iterator. Opposite of #filter. // reject : function(cur, a, len) { var fn = a[0]; return ITERATOR(function(elem, idx, targ, acc) { !fn.call(this, elem, idx, targ) && acc.push(elem); return acc; }, cur, [], a[1]); }, // ##remove // // Removes #c(ount) members of list === #v(alue). // #c > 0: moving from head to tail. // #c < 0: moving from tail to head. // #c = 0: Remove all. // // Arguments // 0: {Number} #c The number of instances of #v to remove // 1: {Mixed} #v The value to remove // remove : function(cur, a, len) { var max = a[0] === 0 ? len : Math.abs(a[0]); var pos = a[0] > 0; var hits = []; var hLen = 0; var cLen = len; while(cLen--) { if(cur[cLen] === a[1]) { hLen = hits.push(cLen); if(!pos && hLen === max) { break; } } } // Sort and truncate. If negative (from right), there will be no effect, // as the length of hits is controlled in above loop. If positive (or zero) // will be sorted smaller->larger, and truncated to #max length. // hits.sort(); hLen = M_MIN(hits.length, max); hits.length = hLen; while(hLen--) { cur.splice(hits[hLen], 1); } return cur; }, // ##reset // // Will replace the #active list with the #original list. // Returns last #active. // reset : function(cur, a, len, key) { var a = ACTIVE[key]; ACTIVE[key] = $$.copy(ORIGINAL[key]); return a; }, // ##sequence // // Returns the composed result of a list of functions processed head to tail. // Expects a single argument, which is the value to pass to the head function. // NOTE that no checking is done of list member types. If any member is not a // function this will error. // sequence : function(cur, a, len) { var last = a; var i = 0 while(i < len) { last = cur[len].apply(this, last); i++; } return last; }, // ##set // // Fetch item at sent index. // // Indexes can be positive or negative, and are zero based. // `0` is first item, `1` is second, `-1` is last, `-2` is penultimate. // NOTE: There is no range checking done. // // @argumentList // 0: {Number} The index. // 1: {Mixed} The value to set // set : function(cur, a, len) { return (cur[a[0] >= 0 ? a[0] : len + a[0]] = a[1]); }, // ##size // // Returns the length of the list // size : function(cur, a, len) { return len; }, // ##shuffle // // Shuffle a list randomly. // // Implements Fisher-Yates algorithm. // @see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle // shuffle : function(cur, a, len) { var s; var r; while(len) { // Select one of the remaining and swap with current. // r = Math.floor(Math.random() * (len - 1)); s = cur[--len]; cur[len] = cur[r]; cur[r] = s; } return cur; }, // ##search // // Binary search on a sorted array (NOTE: *sorted*). // Use to fetch either the index of a value, or the index where the value should be // inserted in order to keep the array ordered. // // @argumentList // 0 : {Mixed} The value to search for. You may also pass an iterator function, // whose return value will then determine sort ranking. // [1] : {Boolean} Whether to return the sorted index. // // @href http://jsfromhell.com/array/search // search : function(cur, a, len) { var low = -1; var v = a[0]; var i = a[1]; var mid; while(len - low > 1) { if(cur[mid = len + low >> 1] < v) { low = mid; } else { len = mid; } } return cur[len] != v ? i ? len : -1 : len; }, // ##sortBy // // Sorts an array // // @argumentsList // 0 : field // 1 : primer // 2 : descending // sortBy : function(cur, a) { if($$.is(Function, a[1])) { return cur.sort(SORTER(a[0], a[1], a[2])); } return cur.sort(SORTER(a[0], false, a[1])); }, // ##sortedIndex // // Shortcut for #search(val, true), which returns sorted index. // sortedIndex : function(cur, a, len) { return $$.search(cur, a[0], true); }, // ##totalPages // // The total number of pages in the current list. // // @see #paginate // totalPages : function(cur, a, len) { return Math.ceil(len / PAGINATION); }, // ##unique // // Unique-ifys the #active list. // unique : function(cur, a, len) { var map = {}; var c; while(len--) { c = map[cur[len]]; ((map[cur[len]] = c ? c += 1 : 1) > 1) && cur.splice(len, 1); } return cur; }, // ##union // // Union of all lists (add unique members of all lists). // // @see #LIST_ACCESSOR // @return {Array} // union : function(cur, a, len) { var map = {}; var si; var m; a[a.length] = cur; PROC_ARR_ARGS(a); while(m = a.shift()) { si = m.length; while(si--) { map[m[si]] = 1; } } return $$.objectToArray(map); }, // ##unset // // Will remove a key (a list) entirely, returning an object containing // list's last #active and last #original // unset : function(cur, a, len, key) { var r = { active : ACTIVE[key], original : ORIGINAL[key] } delete ACTIVE[key]; delete ORIGINAL[key]; return r; }, // ##zip // // Take any number of input arrays and returns an array where corresponding elements // in input arrays are merged. The length of the returned array equals the maximum length // found in the input arrays. // // @argumentList: // 0..n : {Array} Any number of arrays. // // @example .zip( ["Barack", "James", "John"], // ["Hussein", "Earl", "Fitzgerald"], // ["Obama", "Carter", "Kennedy"] ); // [ ["Barack", "Hussein", "Obama"], // ["James", "Earl", "Carter", "Bush"], <-- note useless member(4) // ["John", "Fitzgerald", "Kennedy"], // [undefined, undefined, undefined, "Bush"]]; // zip : function(cur, a, len) { a.unshift(cur); var cL = $$.max($$.pluck(a, "length")); var r = []; while(cL--) { r[cL] = $$.pluck(a, cL); } return r; } } // Prefilter for list calls, mainly to validate arguments and set up boilerplate for // the list accessors to use. // // Ensures that execution terminates if requested method cannot work with the // current list, determines current list values, filters argument list, and calls method. // var LIST_ACCESSOR = function(m, key) { var a = $$.argsToArray(arguments, 2); var cur = ACTIVE[key]; var initial = false; var result; // There is no value set. // if(!cur) { if($$.is(Array, key)) { cur = key; key = null; } else if(a[0] !== void 0 && (m == "first" || m == "last")) { cur = ACTIVE[key] = []; initial = true; } else if(m === "iterator") { cur = []; } else { return null; } } // Run transformation on #active list. // result = LIST_METHOD[m](cur, a, cur.length, key); // If first list operation on this key, store *copy* of #active. NOTE that the // copy is shallow, which means that objects are not copied. Being in a list // doesn't insulate list members from external access which may, or may not, be // what you want. // if(initial) { ORIGINAL[key] = $$.copy(cur,1); } // Return any truthy values. // Return `false` or `0` as those are valid results. // If otherwise falsy, return null. // return (result === false || result === 0) ? result : result || null; }; // Attach core array methods. // while(ARRAY_M.length) { (function(m) { $$[m] = function(targ, fn, scope, a3) { return ARRAY_METHOD(m, targ, fn, scope, a3); } })(ARRAY_M.pop()); } // Attach list methods. // while(LIST_M.length) { $$.extend(LIST_M.pop()); } // For Node we'd be leveraging the CommonJS system. Otherwise, attach to Window. // if(typeof exports == 'object' && exports) { exports.bauhaus = $$; } else { window.bauhaus = $$; } })();