UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

681 lines (579 loc) 17.4 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Andreas Ecker (ecker) ====================================================================== This class contains code based on the following work: * jQuery http://jquery.com Version 1.3.1 Copyright: 2009 John Resig License: MIT: http://www.opensource.org/licenses/mit-license.php * Underscore.js http://underscorejs.org Copyright: 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors License: MIT: http://www.opensource.org/licenses/mit-license.php ************************************************************************ */ /** * Static helper functions for arrays with a lot of often used convenience * methods like <code>remove</code> or <code>contains</code>. * * The native JavaScript Array is not modified by this class. However, * there are modifications to the native Array in {@link qx.lang.normalize.Array} for * browsers that do not support certain JavaScript features natively . * * @ignore(qx.data) * @ignore(qx.data.IListData) * @ignore(qx.Class.*) * @require(qx.lang.normalize.Date) */ qx.Bootstrap.define("qx.lang.Array", { statics : { /** * Converts an array like object to any other array like * object. * * Attention: The returned array may be same * instance as the incoming one if the constructor is identical! * * @param object {var} any array-like object * @param constructor {Function} constructor of the new instance * @param offset {Integer?0} position to start from * @return {Array} the converted array */ cast : function(object, constructor, offset) { if (object.constructor === constructor) { return object; } if (qx.data && qx.data.IListData) { if (qx.Class && qx.Class.hasInterface(object, qx.data.IListData)) { var object = object.toArray(); } } // Create from given constructor var ret = new constructor; // Some collections in mshtml are not able to be sliced. // These lines are a special workaround for this client. if ((qx.core.Environment.get("engine.name") == "mshtml")) { if (object.item) { for (var i=offset||0, l=object.length; i<l; i++) { ret.push(object[i]); } return ret; } } // Copy over items if (Object.prototype.toString.call(object) === "[object Array]" && offset == null) { ret.push.apply(ret, object); } else { ret.push.apply(ret, Array.prototype.slice.call(object, offset||0)); } return ret; }, /** * Convert an arguments object into an array. * * @param args {arguments} arguments object * @param offset {Integer?0} position to start from * @return {Array} a newly created array (copy) with the content of the arguments object. */ fromArguments : function(args, offset) { return Array.prototype.slice.call(args, offset||0); }, /** * Convert a (node) collection into an array * * @param coll {var} node collection * @return {Array} a newly created array (copy) with the content of the node collection. */ fromCollection : function(coll) { // The native Array.slice cannot be used with some Array-like objects // including NodeLists in older IEs if ((qx.core.Environment.get("engine.name") == "mshtml")) { if (coll.item) { var arr = []; for (var i=0, l=coll.length; i<l; i++) { arr[i] = coll[i]; } return arr; } } return Array.prototype.slice.call(coll, 0); }, /** * Expand shorthand definition to a four element list. * This is an utility function for padding/margin and all other shorthand handling. * * @param input {Array} arr with one to four elements * @return {Array} an arr with four elements */ fromShortHand : function(input) { var len = input.length; var result = qx.lang.Array.clone(input); // Copy Values (according to the length) switch(len) { case 1: result[1] = result[2] = result[3] = result[0]; break; case 2: result[2] = result[0]; // no break here case 3: result[3] = result[1]; } // Return list with 4 items return result; }, /** * Return a copy of the given array * * @param arr {Array} the array to copy * @return {Array} copy of the array */ clone : function(arr) { return arr.concat(); }, /** * Insert an element at a given position into the array * * @param arr {Array} the array * @param obj {var} the element to insert * @param i {Integer} position where to insert the element into the array * @return {Array} the array */ insertAt : function(arr, obj, i) { arr.splice(i, 0, obj); return arr; }, /** * Insert an element into the array before a given second element. * * @param arr {Array} the array * @param obj {var} object to be inserted * @param obj2 {var} insert obj1 before this object * @return {Array} the array */ insertBefore : function(arr, obj, obj2) { var i = arr.indexOf(obj2); if (i == -1) { arr.push(obj); } else { arr.splice(i, 0, obj); } return arr; }, /** * Insert an element into the array after a given second element. * * @param arr {Array} the array * @param obj {var} object to be inserted * @param obj2 {var} insert obj1 after this object * @return {Array} the array */ insertAfter : function(arr, obj, obj2) { var i = arr.indexOf(obj2); if (i == -1 || i == (arr.length - 1)) { arr.push(obj); } else { arr.splice(i + 1, 0, obj); } return arr; }, /** * Remove an element from the array at the given index * * @param arr {Array} the array * @param i {Integer} index of the element to be removed * @return {var} The removed element. */ removeAt : function(arr, i) { return arr.splice(i, 1)[0]; }, /** * Remove all elements from the array * * @param arr {Array} the array * @return {Array} empty array */ removeAll : function(arr) { arr.length = 0; return this; }, /** * Append the elements of an array to the array * * @param arr1 {Array} the array * @param arr2 {Array} the elements of this array will be appended to other one * @return {Array} The modified array. * @throws {Error} if one of the arguments is not an array */ append : function(arr1, arr2) { if (arr1 instanceof qx.data.Array) { return arr1.append(arr2); } if (arr2 instanceof qx.data.Array) { arr2 = arr2.toArray(); } // this check is important because opera throws an uncatchable error if apply is called without // an arr as second argument. if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertArray(arr1, "The first parameter must be an array."); qx.core.Assert && qx.core.Assert.assertArray(arr2, "The second parameter must be an array."); } Array.prototype.push.apply(arr1, arr2); return arr1; }, /** * Modifies the first array as it removes all elements * which are listed in the second array as well. * * @param arr1 {Array} the array * @param arr2 {Array} the elements of this array will be excluded from the other one * @return {Array} The modified array. * @throws {Error} if one of the arguments is not an array */ exclude : function(arr1, arr2) { if (arr1 instanceof qx.data.Array) { return arr1.exclude(arr2); } // this check is important because opera throws an uncatchable error if apply is called without // an arr as second argument. if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertArray(arr1, "The first parameter must be an array."); qx.core.Assert && qx.core.Assert.assertArray(arr2, "The second parameter must be an array."); } arr2.forEach(function(item) { var index = arr1.indexOf(item); if (index != -1) { arr1.splice(index, 1); } }); return arr1; }, /** * Remove an element from the array. * * @param arr {Array} the array * @param obj {var} element to be removed from the array * @return {var} the removed element */ remove : function(arr, obj) { if (arr instanceof qx.data.Array) { return arr.remove(obj); } var i = arr.indexOf(obj); if (i != -1) { arr.splice(i, 1); return obj; } }, /** * Whether the array contains the given element * * @deprecated {6.0} Please use Array instance include method instead * * @param arr {Array} the array * @param obj {var} object to look for * @return {Boolean} whether the arr contains the element */ contains : function(arr, obj) { return arr.includes(obj); }, /** * Check whether the two arrays have the same content. Checks only the * equality of the arrays' content. * * @param arr1 {Array} first array * @param arr2 {Array} second array * @return {Boolean} Whether the two arrays are equal */ equals : function(arr1, arr2) { if (arr1 instanceof qx.data.Array) { return arr1.equals(arr2); } arr2 = qx.lang.Array.toNativeArray(arr2); var length = arr1.length; if (length !== arr2.length) { return false; } for (var i=0; i<length; i++) { if (arr1[i] !== arr2[i]) { return false; } } return true; }, /** * Returns the sum of all values in the given array. Supports * numeric values only. * * @param arr {Number[]} Array to process * @return {Number} The sum of all values. */ sum : function(arr) { var result = 0; for (var i=0, l=arr.length; i<l; i++) { if (arr[i] != undefined) { result += arr[i]; } } return result; }, /** * Returns the highest value in the given array. Supports * numeric values only. * * @param arr {Number[]} Array to process * @return {Number | null} The highest of all values or undefined if array is empty. */ max : function(arr) { if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertArray(arr, "Parameter must be an array."); } var i, len=arr.length, result = arr[0]; for (i = 1; i < len; i++) { if (arr[i] > result) { result = arr[i]; } } return result === undefined ? null : result; }, /** * Returns the lowest value in the given array. Supports * numeric values only. * * @param arr {Number[]} Array to process * @return {Number | null} The lowest of all values or undefined if array is empty. */ min : function(arr) { if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertArray(arr, "Parameter must be an array."); } var i, len=arr.length, result = arr[0]; for (i = 1; i < len; i++) { if (arr[i] < result) { result = arr[i]; } } return result === undefined ? null : result; }, /** * Recreates an array which is free of all duplicate elements from the original. * * This method does not modify the original array! * * Keep in mind that this methods deletes undefined indexes. * * @param arr {Array} Incoming array * @return {Array} Returns a copy with no duplicates */ unique: function(arr) { var ret=[], doneStrings={}, doneNumbers={}, doneObjects={}; var value, count=0; var key = "qx" + Date.now(); var hasNull=false, hasFalse=false, hasTrue=false; // Rebuild array and omit duplicates for (var i=0, len=arr.length; i<len; i++) { value = arr[i]; // Differ between null, primitives and reference types if (value === null) { if (!hasNull) { hasNull = true; ret.push(value); } } else if (value === undefined) { // pass } else if (value === false) { if (!hasFalse) { hasFalse = true; ret.push(value); } } else if (value === true) { if (!hasTrue) { hasTrue = true; ret.push(value); } } else if (typeof value === "string") { if (!doneStrings[value]) { doneStrings[value] = 1; ret.push(value); } } else if (typeof value === "number") { if (!doneNumbers[value]) { doneNumbers[value] = 1; ret.push(value); } } else { var hash = value[key]; if (hash == null) { hash = value[key] = count++; } if (!doneObjects[hash]) { doneObjects[hash] = value; ret.push(value); } } } // Clear object hashs for (var hash in doneObjects) { try { delete doneObjects[hash][key]; } catch(ex) { try { doneObjects[hash][key] = null; } catch(ex1) { throw new Error("Cannot clean-up map entry doneObjects[" + hash + "][" + key + "]"); } } } return ret; }, /** * Returns a new array with integers from start to stop incremented or decremented by step. * * @param start {Integer} start of the new array, defaults to 0 * @param stop {Integer} stop of the new array * @param step {Integer} increment / decrement - depends whether you use positive or negative values * @return {Array} Returns a new array with integers */ range : function(start, stop, step) { if (arguments.length <= 1) { stop = start || 0; start = 0; } step = arguments[2] || 1; var length = Math.max(Math.ceil((stop - start) / step), 0); var idx = 0; var range = Array(length); while (idx < length) { range[idx++] = start; start += step; } return range; }, /** * Replaces the contents of the array `dest` * * @param dest {Array|qx.data.Array} the array to edit (if null then a new array is created) * @param src {Array|qx.data.Array} the array to copy from, or null * @return {Array} the edited array (or the new array, if dest is null) */ replace: function(dest, src) { if (dest instanceof qx.data.Array) { return dest.replace(src); } if (src === null) { if (dest === null) { return null; } else { return []; } } src = qx.lang.Array.toNativeArray(src); if (dest === null) { dest = src.slice(0); } else { var args = [ 0, dest.length ]; src.forEach(function(item) { args.push(item); }); dest.splice.apply(dest, args); } return dest; }, /** * Returns a native array from src where possible; qx.data.Array is converted to its native array, * in which case unless `clone` parameter is set to true the rules of qx.data.Array.toArray should * be followed, ie that the array should not be manipulated directly. * * @param src {qx.data.Array|Array} the object to return as an array * @param clone{Boolean?} whether to make the returned array a clone, ie editable by the calling code * @return {Array} */ toNativeArray: function(src, clone) { if (src === undefined || src === null) { return src; } if (src instanceof qx.data.Array) { if (clone) { return src.toArray().slice(0); } return src.toArray(); } if (qx.lang.Type.isArray(src)) { if (clone) { return src.slice(0); } return src; } return [ src ]; } } });