UNPKG

abaaso

Version:

abaaso is a modern, lightweight Enterprise class RESTful JavaScript application framework.

2,115 lines (1,840 loc) 276 kB
/** * abaaso * * @author Jason Mulligan <jason.mulligan@avoidwork.com> * @copyright 2014 Jason Mulligan * @license BSD-3 <https://raw.github.com/avoidwork/abaaso/master/LICENSE> * @link http://abaaso.com * @module abaaso * @version 3.11.12 */ ( function ( global ) { var document = global.document, location = global.location, navigator = global.navigator, server = typeof exports !== "undefined", VERSIONS = 100, $, abaaso, http, https, url; if ( global.abaaso !== undefined ) { return; } if ( server ) { url = require( "url" ); http = require( "http" ); https = require( "https" ); mongodb = require( "mongodb" ).MongoClient; format = require( "util" ).format; if ( typeof Storage === "undefined" ) { localStorage = require( "localStorage" ); } if ( typeof XMLHttpRequest === "undefined" ) { XMLHttpRequest = null; } } /** * abaaso * * @namespace */ abaaso = ( function () { "use strict"; var bootstrap, external, has, slice; /** * Regex patterns used through abaaso * * `url` was authored by Diego Perini * * @type {Object} */ var regex = { after_space : /\s+.*/, android : /android/i, allow : /^allow$/i, allow_cors : /^access-control-allow-methods$/i, alphanum : /^[a-zA-Z0-9]+$/, and : /^&/, asc : /\s+asc$/ig, auth : /\/\/(.*)\@/, blackberry : /blackberry/i, "boolean" : /^(true|false)?$/, boolean_number_string : /boolean|number|string/, cdata : /\&|<|>|\"|\'|\t|\r|\n|\@|\$/, checked_disabled : /checked|disabled/i, chrome : /chrome/i, complete_loaded : /^(complete|loaded)$/i, csv_quote : /^\s|\"|\n|,|\s$/, del : /^del/, decimal : /^\d+.(\d+)/, desc : /\s+desc$/i, domain : /^[\w.-_]+\.[A-Za-z]{2,}$/, double_slash : /\/\//, down : /down/, down_up : /down|up/, email : /^[a-zA-Z0-9.!#$%&'*+\/=?\^_`{|}~\-]+@[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,253}[a-zA-Z0-9])?)*$/, endslash : /\/$/, element_update : /innerHTML|innerText|textContent|type|src/, firefox : /firefox/i, get_headers : /^(head|get|options)$/, get_remove_set : /get|remove|set/, hash : /^\#/, hash_bang : /^\#\!?/, header_replace : /:.*/, header_value_replace : /.*:\s+/, html : /^<.*>$/, http_body : /200|201|202|203|206/, http_ports : /80|443/, ie : /msie|ie/i, input_button : /button|submit|reset/, integer : /(^-?\d\d*$)/, ip : /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, is_xml : /^<\?xml.*\?>/, ios : /ipad|iphone/i, json_maybe : /json|plain|javascript/, json_wrap : /^[\[\{]/, jsonp_wrap : /([a-zA-Z0-9\.]+\()(.*)(\))$/, klass : /^\./, linux : /linux|bsd|unix/i, no : /no-store|no-cache/i, not_endpoint : /.*\//, notEmpty : /\w{1,}/, number : /(^-?\d\d*\.\d*$)|(^-?\d\d*$)|(^-?\.\d\d*$)|number/, number_format_1 : /.*\./, number_format_2 : /\..*/, number_present : /\d{1,}/, number_string : /number|string/i, number_string_object : /number|object|string/i, null_undefined : /null|undefined/, observer_allowed : /click|error|key|mousedown|mouseup|submit/i, observer_globals : /body|document|window/i, object_type : /\[object Object\]/, object_undefined : /object|undefined/, opera : /opera/i, osx : /macintosh/i, patch : /^patch$/i, phone : /^([0-9\(\)\/\+ \-\.]+)$/, playbook : /playbook/i, plural : /s$/, primitive : /^(boolean|function|number|string)$/, priv : /private/, put_post : /^(post|put)$/i, radio_checkbox : /^(radio|checkbox)$/i, reflect : /function\s+\w*\s*\((.*?)\)/, root : /^\/[^\/]/, route_nget : /^(head|options)$/i, route_methods : /^(all|delete|get|put|post|head|options)$/i, safari : /safari/i, scheme : /.*\/\//, select : /select/i, selector_is : /^:/, selector_many : /\:|\.|\+|\~|\[/, selector_complex : /\s+|\>|\+|\~|\:|\[/, selector_split : /\s+|\>|\+|\~/, set_del : /^(set|del|delete)$/, sort_needle : /^.*:::/, sort_value : /:::.*$/, space_hyphen : /\s|-/, string_boolean : /^(true|false)$/i, string_object : /string|object/i, string_true : /^true$/i, svg : /svg/, top_bottom : /top|bottom/i, true_undefined : /true|undefined/i, url : /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/i, webos : /webos/i, windows : /windows/i, word : /^\w+$/, xml : /xml/i }; /** @namespace array */ var array = { /** * Adds 'arg' to 'obj' if it is not found * * @method add * @param {Array} obj Array to receive 'arg' * @param {Mixed} arg Argument to set in 'obj' * @return {Array} Array that was queried */ add : function ( obj, arg ) { if ( !array.contains( obj, arg ) ) { obj.push( arg ); } return obj; }, /** * Preforms a binary search on a sorted Array * * @method binIndex * @param {Array} obj Array to search * @param {Mixed} arg Value to find index of * @return {Number} Index of `arg` within `obj` */ binIndex : function ( obj, arg ) { var min = 0, max = obj.length - 1, idx, val; while ( min <= max ) { idx = Math.floor( ( min + max ) / 2 ); val = obj[idx]; if ( val < arg ) { min = idx + 1; } else if ( val > arg ) { max = idx - 1; } else { return idx; } } return -1; }, /** * Returns an Object ( NodeList, etc. ) as an Array * * @method cast * @param {Object} obj Object to cast * @param {Boolean} key [Optional] Returns key or value, only applies to Objects without a length property * @return {Array} Object as an Array */ cast : function () { if ( server || ( !client.ie || client.version > 8 ) ) { return function ( obj, key ) { key = ( key === true ); var o = []; if ( !isNaN( obj.length ) ) { o = slice.call( obj ); } else if ( key ) { o = array.keys( obj ); } else { utility.iterate( obj, function ( i ) { o.push( i ); }); } return o; }; } else { return function ( obj, key ) { key = ( key === true ); var o = []; if ( !isNaN( obj.length ) ) { try { o = slice.call( obj ); } catch ( e ) { utility.iterate( obj, function ( i, idx ) { if ( idx !== "length" ) { o.push( i ); } }); } } else if ( key ) { o = array.keys( obj ); } else { utility.iterate( obj, function ( i ) { o.push( i ); }); } return o; }; } }, /** * Transforms an Array to a 2D Array of chunks * * @method chunk * @param {Array} obj Array to parse * @param {Number} size Chunk size ( integer ) * @return {Array} Chunked Array */ chunk : function ( obj, size ) { var result = [], nth = number.round( ( obj.length / size ), "up" ), start = 0, i = -1; while ( ++i < nth ) { start = i * size; result.push( array.limit( obj, start, size ) ); } return result; }, /** * Clears an Array without destroying it * * @method clear * @param {Array} obj Array to clear * @return {Array} Cleared Array */ clear : function ( obj ) { return obj.length > 0 ? array.remove( obj, 0, obj.length ) : obj; }, /** * Clones an Array * * @method clone * @param {Array} obj Array to clone * @return {Array} Clone of Array */ clone : function ( obj ) { return obj.slice(); }, /** * Determines if obj contains arg * * @method contains * @param {Array} obj Array to search * @param {Mixed} arg Value to look for * @return {Boolean} True if found, false if not */ contains : function ( obj, arg ) { return ( array.index( obj, arg ) > -1 ); }, /** * Creates a new Array of the result of `fn` executed against every index of `obj` * * @method collect * @param {Array} obj Array to iterate * @param {Function} fn Function to execute against indices * @return {Array} New Array */ collect : function ( obj, fn ) { var result = []; array.each( obj, function ( i ) { result.push( fn( i ) ); }); return result; }, /** * Compacts a Array by removing `null` or `undefined` indices * * @method compact * @param {Array} obj Array to compact * @param {Boolean} diff Indicates to return resulting Array only if there's a difference * @return {Array} Compacted copy of `obj`, or null ( if `diff` is passed & no diff is found ) */ compact : function ( obj, diff ) { var result = []; result = obj.filter( function ( i ) { return !regex.null_undefined.test( i ); }); return !diff ? result : ( result.length < obj.length ? result : null ); }, /** * Counts `value` in `obj` * * @method count * @param {Array} obj Array to search * @param {Mixed} value Value to compare * @return {Array} Array of counts */ count : function ( obj, value ) { return obj.filter( function ( i ) { return ( i === value ); }).length; }, /** * Finds the difference between array1 and array2 * * @method diff * @param {Array} array1 Source Array * @param {Array} array2 Comparison Array * @return {Array} Array of the differences */ diff : function ( array1, array2 ) { var result = []; array.each( array1, function ( i ) { if ( !array.contains( array2, i ) ) { array.add( result, i ); } }); array.each( array2, function ( i ) { if ( !array.contains( array1, i ) ) { array.add( result, i ); } }); return result; }, /** * Iterates obj and executes fn * * Parameters for fn are 'value', 'index' * * @method each * @param {Array} obj Array to iterate * @param {Function} fn Function to execute on index values * @param {Boolean} async [Optional] Asynchronous iteration * @param {Number} size [Optional] Batch size for async iteration, default is 10 * @return {Array} Array */ each : function ( obj, fn, async, size ) { var nth = obj.length, i, offset; if ( async !== true ) { for ( i = 0; i < nth; i++ ) { if ( fn.call( obj, obj[i], i ) === false ) { break; } } } else { size = size || 10; offset = 0; if ( size > nth ) { size = nth; } utility.repeat( function () { var i = 0, idx; for ( i = 0; i < size; i++ ) { idx = i + offset; if ( idx === nth || fn.call( obj, obj[idx], idx ) === false ) { return false; } } offset += size; if ( offset >= nth ) { return false; } }, undefined, undefined, false ); } return obj; }, /** * Determines if an Array is empty * * @method empty * @param {Array} obj Array to inspect * @return {Boolean} `true` if there's no indices */ empty : function ( obj ) { return ( obj.length === 0 ); }, /** * Determines if `a` is equal to `b` * * @method equal * @param {Array} a Array to compare * @param {Array} b Array to compare * @return {Boolean} `true` if the Arrays match */ equal : function ( a, b ) { return ( json.encode( a ) === json.encode( b ) ); }, /** * Fibonacci generator * * @method fib * @param {Number} arg [Optional] Amount of numbers to generate, default is 100 * @return {Array} Array of numbers */ fib : function ( arg ) { var result = [1, 1], first = result[0], second = result[1], sum; // Subtracting 1 to account for `first` & `second` arg = ( arg || 100 ) - 1; if ( isNaN( arg ) || arg < 2 ) { throw new Error( label.error.invalidArguments ); } while ( --arg ) { sum = first + second; first = second; second = sum; result.push( sum ); } return result; }, /** * Fills `obj` with the evalution of `arg`, optionally from `start` to `offset` * * @method fill * @param {Array} obj Array to fill * @param {Mixed} arg String, Number of Function to fill with * @param {Number} start [Optional] Index to begin filling at * @param {Number} end [Optional] Offset from start to stop filling at * @return {Array} Filled Array */ fill : function ( obj, arg, start, offset ) { var fn = typeof arg === "function", l = obj.length, i = !isNaN( start ) ? start : 0, nth = !isNaN( offset ) ? i + offset : l - 1; if ( nth > ( l - 1) ) { nth = l - 1; } while ( i <= nth ) { obj[i] = fn ? arg( obj[i] ) : arg; i++; } return obj; }, /** * Returns the first Array node * * @method first * @param {Array} obj The array * @return {Mixed} The first node of the array */ first : function ( obj ) { return obj[0]; }, /** * Flattens a 2D Array * * @method flat * @param {Array} obj 2D Array to flatten * @return {Array} Flatten Array */ flat : function ( obj ) { var result = []; result = obj.reduce( function ( a, b ) { return a.concat( b ); }, result ); return result; }, /** * Creates a 2D Array from an Object * * @method fromObject * @param {Object} obj Object to convert * @return {Array} 2D Array */ fromObject : function ( obj ) { return array.mingle( array.keys( obj ), array.cast( obj ) ); }, /** * Facade to indexOf for shorter syntax * * @method index * @param {Array} obj Array to search * @param {Mixed} arg Value to find index of * @return {Number} The position of arg in instance */ index : function ( obj, arg ) { return obj.indexOf( arg ); }, /** * Returns an Associative Array as an Indexed Array * * @method indexed * @param {Array} obj Array to index * @return {Array} Indexed Array */ indexed : function ( obj ) { var indexed = []; utility.iterate( obj, function ( v ) { indexed.push( v ); }); return indexed; }, /** * Finds the intersections between array1 and array2 * * @method intersect * @param {Array} array1 Source Array * @param {Array} array2 Comparison Array * @return {Array} Array of the intersections */ intersect : function ( array1, array2 ) { var a = array1.length > array2.length ? array1 : array2, b = ( a === array1 ? array2 : array1 ); return a.filter( function ( key ) { return array.contains( b, key ); }); }, /** * Keeps every element of `obj` for which `fn` evaluates to true * * @method keepIf * @param {Array} obj Array to iterate * @param {Function} fn Function to test indices against * @return {Array} Array */ keepIf : function ( obj, fn ) { if ( typeof fn !== "function" ) { throw new Error( label.error.invalidArguments ); } var result = [], remove = []; result = obj.filter( fn ); remove = array.diff( obj, result ); array.each( remove, function ( i ) { array.remove( obj, array.index( obj, i ) ); }); return obj; }, /** * Sorts an Array based on key values, like an SQL ORDER BY clause * * @method sort * @param {Array} obj Array to sort * @param {String} query Sort query, e.g. "name, age desc, country" * @param {String} sub [Optional] Key which holds data, e.g. "{data: {}}" = "data" * @return {Array} Sorted Array */ keySort : function ( obj, query, sub ) { query = query.replace( /\s*asc/ig, "" ).replace( /\s*desc/ig, " desc" ); var queries = string.explode( query ).map( function ( i ) { return i.split( " " ); }), sorts = []; if ( sub && sub !== "" ) { sub = "." + sub; } else { sub = ""; } array.each( queries, function ( i ) { var desc = i[1] === "desc"; if ( !desc ) { sorts.push( "if ( a" + sub + "[\"" + i[0] + "\"] < b" + sub + "[\"" + i[0] + "\"] ) return -1;" ); sorts.push( "if ( a" + sub + "[\"" + i[0] + "\"] > b" + sub + "[\"" + i[0] + "\"] ) return 1;" ); } else { sorts.push( "if ( a" + sub + "[\"" + i[0] + "\"] < b" + sub + "[\"" + i[0] + "\"] ) return 1;" ); sorts.push( "if ( a" + sub + "[\"" + i[0] + "\"] > b" + sub + "[\"" + i[0] + "\"] ) return -1;" ); } }); sorts.push( "else return 0;" ); return obj.sort( new Function( "a", "b", sorts.join( "\n" ) ) ); }, /** * Returns the keys in an "Associative Array" * * @method keys * @param {Mixed} obj Array or Object to extract keys from * @return {Array} Array of the keys */ keys : function () { if ( typeof Object.keys === "function" ) { return function ( obj ) { return Object.keys( obj ); }; } else { return function ( obj ) { var keys = []; utility.iterate( obj, function ( v, k ) { keys.push( k ); }); return keys; }; } }(), /** * Returns the last index of the Array * * @method last * @param {Array} obj Array * @param {Number} arg [Optional] Negative offset from last index to return * @return {Mixed} Last index( s ) of Array */ last : function ( obj, arg ) { var n = obj.length - 1; if ( arg >= ( n + 1 ) ) { return obj; } else if ( isNaN( arg ) || arg === 1 ) { return obj[n]; } else { return array.limit( obj, ( n - ( --arg ) ), n ); } }, /** * Returns a limited range of indices from the Array * * @method limit * @param {Array} obj Array to iterate * @param {Number} start Starting index * @param {Number} offset Number of indices to return * @return {Array} Array of indices */ limit : function ( obj, start, offset ) { var result = [], i = start - 1, nth = start + offset, max = obj.length; if ( max > 0 ) { while ( ++i < nth && i < max ) { result.push( obj[i] ); } } return result; }, /** * Finds the maximum value in an Array * * @method max * @param {Array} obj Array to parse * @return {Mixed} Number, String, etc. */ max : function ( obj ) { return array.last( obj.sort( array.sort ) ); }, /** * Finds the mean of an Array ( of numbers ) * * @method mean * @param {Array} obj Array to parse * @return {Number} Mean of the Array ( float or integer ) */ mean : function ( obj ) { return obj.length > 0 ? ( array.sum( obj ) / obj.length ) : undefined; }, /** * Finds the median value of an Array ( of numbers ) * * @method median * @param {Array} obj Array to parse * @return {Number} Median number of the Array ( float or integer ) */ median : function ( obj ) { var nth = obj.length, mid = number.round( nth / 2, "down" ), sorted = obj.sort( array.sort ); return number.odd( nth ) ? sorted[mid] : ( ( sorted[mid - 1] + sorted[mid] ) / 2 ); }, /** * Merges `arg` into `obj`, excluding duplicate indices * * @method merge * @param {Array} obj Array to receive indices * @param {Array} arg Array to merge * @return {Array} obj */ merge : function ( obj, arg ) { array.each( arg, function ( i ) { array.add( obj, i ); }); return obj; }, /** * Finds the minimum value in an Array * * @method min * @param {Array} obj Array to parse * @return {Mixed} Number, String, etc. */ min : function ( obj ) { return obj.sort( array.sort )[0]; }, /** * Mingles Arrays and returns a 2D Array * * @method mingle * @param {Array} obj1 Array to mingle * @param {Array} obj2 Array to mingle * @return {Array} 2D Array */ mingle : function ( obj1, obj2 ) { var result; result = obj1.map( function ( i, idx ) { return [i, obj2[idx]]; }); return result; }, /** * Finds the mode value of an Array * * @method mode * @param {Array} obj Array to parse * @return {Mixed} Mode value of the Array */ mode : function ( obj ) { var values = {}, count = 0, nth = 0, mode = [], result; // Counting values array.each( obj, function ( i ) { if ( !isNaN( values[i] ) ) { values[i]++; } else { values[i] = 1; } }); // Finding the highest occurring count count = array.max( array.cast( values ) ); // Finding values that match the count utility.iterate( values, function ( v, k ) { if ( v === count ) { mode.push( number.parse( k ) ); } }); // Determining the result nth = mode.length; if ( nth > 0 ) { result = nth === 1 ? mode[0] : mode; } return result; }, /** * Creates an Array of percentages from an Array of Numbers (ints/floats) * * @method percents * @param {Array} obj Array to iterate * @param {Number} precision [Optional] Rounding precision * @param {Number} total [Optional] Value to compare against * @return {Array} Array of percents */ percents : function ( obj, precision, total ) { var result = [], custom = false, last, padding, sum; precision = precision || 0; if ( total === undefined ) { total = array.sum( obj ); } else { custom = true; } array.each( obj, function ( i ) { result.push( number.parse( ( ( i / total ) * 100 ).toFixed( precision ) ) ); } ); // Dealing with the awesomeness of JavaScript "integers" if ( !custom ) { sum = array.sum( result ); if ( sum < 100 ) { padding = number.parse( number.diff( sum, 100 ).toFixed( precision ) ); last = array.last( result ) + padding; result[result.length - 1] = last; } else if ( sum > 100 ) { padding = number.parse( number.diff( sum, 100 ).toFixed( precision ) ); last = number.parse( ( array.last( result ) - padding ).toFixed( precision ) ); result[result.length - 1] = last; } } return result; }, /** * Finds the range of the Array ( of numbers ) values * * @method range * @param {Array} obj Array to parse * @return {Number} Range of the array ( float or integer ) */ range : function ( obj ) { return array.max( obj ) - array.min( obj ); }, /** * Searches a 2D Array `obj` for the first match of `arg` as a second index * * @method rassoc * @param {Array} obj 2D Array to search * @param {Mixed} arg Primitive to find * @return {Mixed} Array or undefined */ rassoc : function ( obj, arg ) { var result; array.each( obj, function ( i, idx ) { if ( i[1] === arg ) { result = obj[idx]; return false; } }); return result; }, /** * Returns Array containing the items in `obj` for which `fn()` is not true * * @method reject * @param {Array} obj Array to iterate * @param {Function} fn Function to execute against `obj` indices * @return {Array} Array of indices which fn() is not true */ reject : function ( obj, fn ) { return array.diff( obj, obj.filter( fn ) ); }, /** * Replaces the contents of `obj` with `arg` * * @method replace * @param {Array} obj Array to modify * @param {Array} arg Array to become `obj` * @return {Array} New version of `obj` */ replace : function ( obj, arg ) { array.remove( obj, 0, obj.length ); array.each( arg, function ( i ) { obj.push( i ); }); return obj; }, /** * Removes indices from an Array without recreating it * * @method remove * @param {Array} obj Array to remove from * @param {Mixed} start Starting index, or value to find within obj * @param {Number} end [Optional] Ending index * @return {Array} Modified Array */ remove : function ( obj, start, end ) { if ( isNaN( start ) ) { start = obj.index( start ); if ( start === -1 ) { return obj; } } else { start = start || 0; } var length = obj.length, remaining = obj.slice( ( end || start ) + 1 || length ); obj.length = start < 0 ? ( length + start ) : start; obj.push.apply( obj, remaining ); return obj; }, /** * Deletes every element of `obj` for which `fn` evaluates to true * * @method removeIf * @param {Array} obj Array to iterate * @param {Function} fn Function to test indices against * @return {Array} Array */ removeIf : function ( obj, fn ) { var remove; if ( typeof fn !== "function" ) { throw new Error( label.error.invalidArguments ); } remove = obj.filter( fn ); array.each( remove, function ( i ) { array.remove( obj, array.index ( obj, i ) ); }); return obj; }, /** * Deletes elements of `obj` until `fn` evaluates to false * * @method removeWhile * @param {Array} obj Array to iterate * @param {Function} fn Function to test indices against * @return {Array} Array */ removeWhile : function ( obj, fn ) { if ( typeof fn !== "function" ) { throw new Error( label.error.invalidArguments ); } var remove = []; array.each( obj, function ( i ) { if ( fn( i ) !== false ) { remove.push( i ); } else { return false; } }); array.each( remove, function ( i ) { array.remove( obj, array.index( obj, i) ); }); return obj; }, /** * Returns the "rest" of `obj` from `arg` * * @method rest * @param {Array} obj Array to parse * @param {Number} arg [Optional] Start position of subset of `obj` ( positive number only ) * @return {Array} Array of a subset of `obj` */ rest : function ( obj, arg ) { arg = arg || 1; if ( arg < 1 ) { arg = 1; } return array.limit( obj, arg, obj.length ); }, /** * Finds the last index of `arg` in `obj` * * @method rindex * @param {Array} obj Array to search * @param {Mixed} arg Primitive to find * @return {Mixed} Index or undefined */ rindex : function ( obj, arg ) { var result = -1; array.each( obj, function ( i, idx ) { if ( i === arg ) { result = idx; } }); return result; }, /** * Returns new Array with `arg` moved to the first index * * @method rotate * @param {Array} obj Array to rotate * @param {Number} arg Index to become the first index, if negative the rotation is in the opposite direction * @return {Array} Newly rotated Array */ rotate : function ( obj, arg ) { var nth = obj.length, result; if ( arg === 0 ) { result = obj; } else { if ( arg < 0 ) { arg += nth; } else { arg--; } result = array.limit( obj, arg, nth ); result = result.concat( array.limit( obj, 0, arg ) ); } return result; }, /** * Generates a series Array * * @method series * @param {Number} start Start value the series * @param {Number} end [Optional] The end of the series * @param {Number} offset [Optional] Offset for indices, default is 1 * @return {Array} Array of new series */ series : function ( start, end, offset ) { start = start || 0; end = end || start; offset = offset || 1; var result = [], n = -1, nth = Math.max( 0, Math.ceil( ( end - start ) / offset ) ); while ( ++n < nth ) { result[n] = start; start += offset; } return result; }, /** * Splits an Array by divisor * * @method split * @param {Array} obj Array to parse * @param {Number} divisor Integer to divide the Array by * @return {Array} Split Array */ split : function ( obj, divisor ) { var result = [], total = obj.length, nth = Math.ceil( total / divisor ), low = Math.floor( total / divisor ), lower = Math.ceil( total / nth ), lowered = false, start = 0, i = -1; // Finding the fold if ( number.diff( total, ( divisor * nth ) ) > nth ) { lower = total - ( low * divisor ) + low - 1; } else if ( total % divisor > 0 && lower * nth >= total ) { lower--; } while ( ++i < divisor ) { if ( i > 0 ) { start = start + nth; } if ( !lowered && lower < divisor && i === lower ) { --nth; lowered = true; } result.push( array.limit( obj, start, nth ) ); } return result; }, /** * Sorts the Array by parsing values * * @method sort * @param {Mixed} a Argument to compare * @param {Mixed} b Argument to compare * @return {Number} Number indicating sort order */ sort : function ( a, b ) { var types = {a: typeof a, b: typeof b}, c, d, result; if ( types.a === "number" && types.b === "number" ) { result = a - b; } else { c = a.toString(); d = b.toString(); if ( c < d ) { result = -1; } else if ( c > d ) { result = 1; } else if ( types.a === types.b ) { result = 0; } else if ( types.a === "boolean" ) { result = -1; } else { result = 1; } } return result; }, /** * Sorts `obj` using `array.sort` * * @method sorted * @param {Array} obj Array to sort * @return {Array} Sorted Array */ sorted : function ( obj ) { return obj.sort( array.sort ); }, /** * Finds the standard deviation of an Array ( of numbers ) * * @method stddev * @param {Array} obj Array to parse * @return {Number} Standard deviation of the Array ( float or integer ) */ stddev : function ( obj ) { return Math.sqrt( array.variance( obj ) ); }, /** * Gets the summation of an Array of numbers * * @method sum * @param {Array} obj Array to sum * @return {Number} Summation of Array */ sum : function ( obj ) { var result = 0; if ( obj.length > 0 ) { result = obj.reduce( function ( prev, cur ) { return prev + cur; }); } return result; }, /** * Takes the first `arg` indices from `obj` * * @method take * @param {Array} obj Array to parse * @param {Number} arg Offset from 0 to return * @return {Array} Subset of `obj` */ take : function ( obj, arg ) { return array.limit( obj, 0, arg ); }, /** * Gets the total keys in an Array * * @method total * @param {Array} obj Array to find the length of * @return {Number} Number of keys in Array */ total : function ( obj ) { return array.indexed( obj ).length; }, /** * Casts an Array to Object * * @method toObject * @param {Array} ar Array to transform * @return {Object} New object */ toObject : function ( ar ) { var obj = {}, i = ar.length; while ( i-- ) { obj[i.toString()] = ar[i]; } return obj; }, /** * Returns an Array of unique indices of `obj` * * @method unique * @param {Array} obj Array to parse * @return {Array} Array of unique indices */ unique : function ( obj ) { var result = []; array.each( obj, function ( i ) { array.add( result, i ); }); return result; }, /** * Finds the variance of an Array ( of numbers ) * * @method variance * @param {Array} obj Array to parse * @return {Number} Variance of the Array ( float or integer ) */ variance : function ( obj ) { var nth = obj.length, n = 0, mean; if ( nth > 0 ) { mean = array.mean( obj ); array.each( obj, function ( i ) { n += math.sqr( i - mean ); } ); return n / nth; } else { return n; } }, /** * Converts any arguments to Arrays, then merges elements of `obj` with corresponding elements from each argument * * @method zip * @param {Array} obj Array to transform * @param {Mixed} args Argument instance or Array to merge * @return {Array} Array */ zip : function ( obj, args ) { var result = []; // Preparing args if ( !(args instanceof Array) ) { args = typeof args === "object" ? array.cast( args ) : [args]; } array.each( args, function ( i, idx ) { if ( !( i instanceof Array ) ) { this[idx] = [i]; } }); // Building result Array array.each( obj, function ( i, idx ) { result[idx] = [i]; array.each( args, function ( x ) { result[idx].push( x[idx] || null ); }); }); return result; } }; /** @namespace cache */ var cache = { // Collection URIs items : {}, /** * Garbage collector for the cached items * * @method clean * @private * @return {Undefined} undefined */ clean : function () { return utility.iterate( cache.items, function ( v, k ) { if ( cache.expired( k ) ) { cache.expire( k, true ); } }); }, /** * Expires a URI from the local cache * * Events: expire Fires when the URI expires * * @method expire * @private * @param {String} uri URI of the local representation * @param {Boolean} silent [Optional] If 'true', the event will not fire * @return {Undefined} undefined */ expire : function ( uri, silent ) { silent = ( silent === true ); if ( cache.items[uri] !== undefined ) { delete cache.items[uri]; if ( !silent ) { observer.fire( uri, "beforeExpire, expire, afterExpire" ); } return true; } else { return false; } }, /** * Determines if a URI has expired * * @method expired * @private * @param {Object} uri Cached URI object * @return {Boolean} True if the URI has expired */ expired : function ( uri ) { var item = cache.items[uri]; return item !== undefined && item.expires !== undefined && item.expires < new Date(); }, /** * Returns the cached object {headers, response} of the URI or false * * @method get * @private * @param {String} uri URI/Identifier for the resource to retrieve from cache * @param {Boolean} expire [Optional] If 'false' the URI will not expire * @param {Boolean} silent [Optional] If 'true', the event will not fire * @return {Mixed} URI Object {headers, response} or False */ get : function ( uri, expire ) { uri = utility.parse( uri ).href; expire = ( expire !== false ); if ( cache.items[uri] === undefined ) { return false; } if ( expire && cache.expired( uri ) ) { cache.expire( uri ); return false; } return utility.clone( cache.items[uri], true ); }, /** * Sets, or updates an item in cache.items * * @method set * @private * @param {String} uri URI to set or update * @param {String} property Property of the cached URI to set * @param {Mixed} value Value to set * @return {Mixed} URI Object {headers, response} or undefined */ set : function ( uri, property, value ) { uri = utility.parse( uri ).href; if ( cache.items[uri] === undefined ) { cache.items[uri] = {}; cache.items[uri].permission = 0; } if ( property === "permission" ) { cache.items[uri].permission |= value; } else if ( property === "!permission" ) { cache.items[uri].permission &= ~value; } else { cache.items[uri][property] = value; } return cache.items[uri]; } }; /** * Channel factory * * @method channel * @return {Object} Channel instance */ var channel = function () { return new Channel(); }; /** * Channel * * @constructor * @return {Object} Channel instance */ function Channel () { this.queue = []; } // Setting constructor loop Channel.prototype.constructor = Channel; /** * Puts an item into the Channel * * @method put * @param {Mixed} arg Item * @return {Object} Deferred instance */ Channel.prototype.put = function ( arg ) { var defer = deferred(); if ( this.queue.length === 0 ) { this.queue.push( arg ); defer.resolve( ["continue", null] ); } else { defer.resolve( ["pause", null] ); } return defer; }; /** * Takes an item from the Channel * * @method take * @return {Object} Deferred instance */ Channel.prototype.take = function () { var defer = deferred(); if ( this.queue.length === 0 ) { defer.resolve( ["pause", null] ); } else { defer.resolve( ["continue", this.queue.pop()] ); } return defer; }; /** @namespace client */ var client = { /** * ActiveX support * * @type {Boolean} */ activex : function () { var result = false, obj; if ( typeof ActiveXObject !== "undefined" ) { try { obj = new ActiveXObject( "Microsoft.XMLHTTP" ); result = true; } catch ( e ) {} } return result; }(), /** * Android platform * * @type {Boolean} */ android : function () { return !server && regex.android.test( navigator.userAgent ); }(), /** * Blackberry platform * * @type {Boolean} */ blackberry : function () { return !server && regex.blackberry.test( navigator.userAgent ); }(), /** * Chrome browser * * @type {Boolean} */ chrome : function () { return !server && regex.chrome.test( navigator.userAgent ); }(), /** * Firefox browser * * @type {Boolean} */ firefox : function () { return !server && regex.firefox.test( navigator.userAgent ); }(), /** * Internet Explorer browser * * @type {Boolean} */ ie : function () { return !server && regex.ie.test( navigator.userAgent ); }(), /** * iOS platform * * @type {Boolean} */ ios : function () { return !server && regex.ios.test( navigator.userAgent ); }(), /** * Linux Platform * * @type {Boolean} */ linux : function () { return !server && regex.linux.test( navigator.userAgent ); }(), /** * Mobile platform * * @type {Boolean} */ mobile : function () { var size; if ( server ) { return false; } else { size = client.size(); return ( /blackberry|iphone|webos/i.test( navigator.userAgent ) || ( regex.android.test( navigator.userAgent ) && ( size[0] < 720 || size[1] < 720 ) ) ); } }, /** * Playbook platform * * @type {Boolean} */ playbook: function () { return !server && regex.playbook.test( navigator.userAgent ); }(), /** * Opera browser * * @type {Boolean} */ opera : function () { return !server && regex.opera.test( navigator.userAgent ); }(), /** * OSX platform * * @type {Boolean} */ osx : function () { return !server && regex.osx.test( navigator.userAgent ); }(), /** * Safari browser * * @type {Boolean} */ safari : function () { return !server && regex.safari.test( navigator.userAgent.replace(/chrome.*/i, "") ); }(), /** * Tablet platform * * Modern smartphone resolution makes this a hit/miss scenario * * @type {Boolean} */ tablet : function () { var size; if ( server ) { return false; } else { size = client.size(); return ( /ipad|playbook|webos/i.test( navigator.userAgent ) || ( regex.android.test( navigator.userAgent ) && ( size[0] >= 720 || size[1] >= 720 ) ) ); } }, /** * WebOS platform * * @type {Boolean} */ webos : function () { return !server && regex.webos.test( navigator.userAgent ); }(), /** * Windows platform * * @type {Boolean} */ windows : function () { return !server && regex.windows.test( navigator.userAgent ); }(), /** * Client version * * @type {Boolean} */ version : function () { var version = 0; if ( this.chrome ) { version = navigator.userAgent.replace( /(.*chrome\/|safari.*)/gi, "" ); } else if ( this.firefox ) { version = navigator.userAgent.replace( /(.*firefox\/)/gi, "" ); } else if ( this.ie ) { version = navigator.userAgent.replace(/(.*msie|;.*)/gi, ""); } else if ( this.opera ) { version = navigator.userAgent.replace( /(.*version\/|\(.*)/gi, "" ); } else if ( this.safari ) { version = navigator.userAgent.replace( /(.*version\/|safari.*)/gi, "" ); } else { version = navigator.appVersion || "0"; } version = number.parse( string.trim( version ) ); if ( isNaN( version ) ) { version = 0; } if ( this.ie && document.documentMode && document.documentMode < version ) { version = document.documentMode; } return version; }, /** * Quick way to see if a URI allows a specific verb * * @method allows * @param {String} uri URI to query * @param {String} verb HTTP verb * @return {Boolean} `true` if the verb is allowed, undefined if unknown */ allows : function ( uri, verb ) { if ( string.isEmpty( uri ) || string.isEmpty( verb ) ) { throw new Error( label.error.invalidArguments ); } uri = utility.parse( uri ).href; verb = verb.toLowerCase(); var result = false, bit = 0; if ( !cache.get( uri, false ) ) { result = undefined; } else { if ( regex.del.test( verb ) ) { bit = 1; } else if ( regex.get_headers.test( verb ) ) { bit = 4; } else if ( regex.put_post.test( verb ) ) { bit = 2; } else if ( regex.patch.test( verb ) ) { bit = 8; } result = Boolean( client.permissions( uri, verb ).bit & bit ); } return result; }, /** * Gets bit value based on args * * @method bit * @param {Array} args Array of commands the URI accepts * @return {Number} To be set as a bit */ bit : function ( args ) { var result = 0; array.each( args, function ( verb ) { verb = verb.toLowerCase(); if ( regex.get_headers.test( verb ) ) { result |= 4; } else if ( regex.put_post.test( verb ) ) { result |= 2; } else if ( regex.patch.test( verb ) ) { result |= 8; } else if ( regex.del.test( verb ) ) { result |= 1; } }); return result; }, /** * Determines if a URI is a CORS end point * * @method cors * @param {String} uri URI to parse * @return {Boolean} True if CORS */ cors : function ( uri ) { return ( !server && uri.indexOf( "//" ) > -1 && uri.indexOf( "//" + location.host ) === -1 ); }, /** * Caches the headers from the XHR response * * @method headers * @param {Object} xhr XMLHttpRequest Object * @param {String} uri URI to request * @param {String} type Type of request * @return {Object} Cached URI representation */ headers : function ( xhr, uri, type ) { var headers = string.trim( xhr.getAllResponseHeaders() ).split( "\n" ), items = {}, o = {}, allow = null, expires = new Date(), cors = client.cors( uri ); array.each( headers, function ( i ) { var header, value; value = i.replace( regex.header_value_replace, "" ); header = i.replace( regex.header_replace, "" ); header = string.unhyphenate( header, true ).replace( /\s+/g, "-" ); items[header] = value; if ( allow === null ) { if ( ( !cors && regex.allow.test( header) ) || ( cors && regex.allow_cors.test( header) ) ) { allow = value; } } }); if ( regex.no.test( items["Cache-Control"] ) ) { // Do nothing } else if ( items["Cache-Control"] !== undefined && regex.number_present.test( items["Cache-Control"] ) ) { expires = expires.setSeconds( expires.getSeconds() + number.parse( regex.number_present.exec( items["Cache-Control"] )[0], 10 ) ); } else if ( items.Expires !== undefined ) { expires = new Date( items.Expires ); } else { expires = expires.setSeconds( expires.getSeconds() + $.expires ); } o.expires = expires; o.headers = items; o.permission = client.bit( allow !== null ? string.explode( allow ) : [type] ); if ( type === "get" ) { cache.set( uri, "expires", o.expires ); cache.set( uri, "headers", o.headers ); cache.set( uri, "permission", o.permission ); } return o; }, /** * Parses an XHR response * * @method parse * @param {Object} xhr XHR Object * @param {String} type [Optional] Content-Type header value * @return {Mixed} Array, Boolean, Document, Number, Object or String */ parse : function ( xhr, type ) { type = type || ""; var result, obj; if ( ( regex.json_maybe.test( type ) || string.isEmpty( type ) ) && ( regex.json_wrap.test( xhr.responseText ) && Boolean( obj = json.decode( xhr.responseText, true ) ) ) ) { result = obj; } else if ( regex.xml.test( type ) ) { if ( type !== "text/xml" ) { xhr.overrideMimeType( "text/xml" ); } result = xhr.responseXML; } else if ( type === "text/plain" && regex.is_xml.test( xhr.responseText) && xml.valid( xhr.responseText ) ) { result = xml.decode( xhr.responseText ); } else { result = xhr.responseText; } return result; }, /** * Returns the permission of the cached URI * * @method permissions * @param {String} uri URI to query * @return {Object} Contains an Array of available commands, the permission bit and a map */ permissions : function ( uri ) { var cached = cache.get( uri, false ), bit = !cached ? 0 : cached.permission, result = {allows: [], bit: bit, map: {partial: 8, read: 4, write: 2, "delete": 1, unknown: 0}}; if ( bit & 1) { result.allows.push( "DELETE" ); } if ( bit & 2) { result.allows.push( "POST" ); result.allows.push( "PUT" ); } if ( bit & 4) { result.allows.push( "GET" ); } if ( bit & 8) { result.allows.push( "PATCH" ); } return result; }, /** * Creates a JSONP request * * @method jsonp * @param {String} uri URI to request * @param {Function} success A handler function to execute when an appropriate response been received * @param {Function} failure [Optional] A handler function to execute on error * @param {Mixed} args Custom JSONP handler parameter name, default is "callback"; or custom headers for GET request ( CORS ) * @return {Object} Deferred */ jsonp : function ( uri, success, failure, args ) { var defer = deferred(), callback = "callback", cbid, s; if ( external === undefined ) { if ( global.abaaso === undefined ) { utility.define( "abaaso.callback", {}, global ); } external = "abaaso"; } if ( args instanceof Object && args.callback !== undefined ) { callback = args.callback; } defer.then( function (arg ) { if ( typeof success === "function") { success( arg ); } }, function ( e ) { if ( typeof failure === "function") { failure( e ); } throw e; }); do { cbid = utility.genId().slice( 0, 10 ); } while ( global.abaaso.callback[cbid] !== undefined ); uri = uri.replace( callback + "=?", callback + "=" + external + ".callback." + cbid ); global.abaaso.callback[cbid] = function ( arg ) { clearTimeout( utility.timer[cbid] ); delete utility.timer[cbid]; delete global.abaaso.callback[cbid]; defer.resolve( arg ); element.destroy( s ); }; s = element.create( "script", {src: uri, type: "text/javascript"}, utility.$( "head" )[0] ); utility.defer( function () { defer.reject( undefined ); }, 30000, cbid ); return defer; }, /** * Creates an XmlHttpRequest to a URI ( aliased to multiple methods ) * * The returned Deferred will have an .xhr property decorated * * Events: before[type] Fires before the XmlHttpRequest is made, type specific * failed[type] Fires on error * progress[type] Fires on progress * progressUpload[type] Fires on upload progress * received[type] Fires on XHR readystate 2 * timeout[type] Fires when XmlHttpRequest times out * * @method request * @param {String} uri URI to query * @param {String} type Type of request ( DELETE/GET/POST/PUT/HEAD ) * @param {Function} success A handler function to execute when an appropriate response been received * @param {Function} failure [Optional] A handler function to execute on error * @param {Mixed} args [Optional] Data to send with the request * @param {Object} headers [Optional] Custom request headers ( can be used to set withCredentials ) * @param {Number} timeout [Optional] Timeout in milliseconds, default is 30000 * @return {Object} Deferred */ request : function ( uri, type, success, failure, args, headers, timeout ) { timeout = timeout || 30000; var cors, xhr, payload, cached, typed, contentType, doc, ab, blob, defer; if ( ( regex.put_post.test( type ) || regex.patch.test( type ) ) && args === undefined ) { throw new Error( label.error.invalidArguments ); } uri = utility.parse( uri ).href; type = type.toLowerCase(); headers = headers instanceof Object ? headers : null; cors = client.cors( uri ); xhr = ( client.ie && client.version < 10 && cors ) ? new XDomainRequest() : ( !client.ie || ( client.version > 8 || type !== "patch") ? new XMLHttpRequest() : new ActiveXObject( "Microsoft.XMLHTTP" ) ); payload = ( regex.put_post.test( type ) || regex.patch.test( type ) ) && args !== undefined ? args : null; cached = type === "get" ? cache.get( uri ) : false; typed = type.capitalize(); contentType = null; doc = ( typeof Document !== "undefined" ); ab = ( typeof ArrayBuffer !== "undefined" ); blob = ( typeof Blob !==