UNPKG

itsa-jsext

Version:

Extensions to native javascript-objects, all within the itsa_ namespace

342 lines (315 loc) 11.2 kB
/** * * Pollyfils for often used functionality for Arrays * * <i>Copyright (c) 2014 ITSA - https://github.com/itsa</i> * New BSD License - http://choosealicense.com/licenses/bsd-3-clause/ * * @module js-ext * @submodule lib/array.js * @class Array * */ "use strict"; require("./date"); var TYPES = { "undefined" : true, "number" : true, "boolean" : true, "string" : true, "[object Function]" : true, "[object RegExp]" : true, "[object Array]" : true, "[object Date]" : true, "[object Error]" : true, "[object Promise]" : true }, FUNCTION = "function", isObject, objSameValue, cloneObj, deepCloneObj, valuesAreTheSame; isObject = function (item) { return !!(!TYPES[typeof item] && !TYPES[({}.toString).call(item)] && item); }; objSameValue = function(obj1, obj2) { var keys = Object.getOwnPropertyNames(obj1), keysObj2 = Object.getOwnPropertyNames(obj2), l = keys.length, i = -1, same, key; same = (l===keysObj2.length); // loop through the members: while (same && (++i < l)) { key = keys[i]; same = obj2.hasOwnProperty(key) ? valuesAreTheSame(obj1[key], obj2[key]) : false; } return same; }; deepCloneObj = function (obj, descriptors, parseDateString) { var m = Object.create(Object.getPrototypeOf(obj)), keys = Object.getOwnPropertyNames(obj), l = keys.length, i = -1, key, value, propDescriptor; // loop through the members: while (++i < l) { key = keys[i]; value = obj[key]; if (descriptors) { propDescriptor = Object.getOwnPropertyDescriptor(obj, key); if (propDescriptor.writable) { Object.defineProperty(m, key, propDescriptor); } if ((isObject(value) || Array.isArray(value) || Date.itsa_isDate(value, parseDateString)) && ((typeof propDescriptor.get)!==FUNCTION) && ((typeof propDescriptor.set)!==FUNCTION) ) { m[key] = cloneObj(value, descriptors, parseDateString); } else { m[key] = value; } } else { m[key] = (isObject(value) || Array.isArray(value) || Date.itsa_isDate(value, parseDateString)) ? cloneObj(value, descriptors, parseDateString) : value; } } return m; }; cloneObj = function(obj, descriptors, parseDateString, target) { var copy, i, len, value; // Handle Array if (Array.isArray(obj)) { copy = target || []; len = obj.length; for (i=0; i<len; i++) { value = obj[i]; copy[i] = (isObject(value) || Array.isArray(value) || Date.itsa_isDate(value, parseDateString)) ? cloneObj(value, descriptors, parseDateString) : value; } return copy; } // Handle Date if (Date.itsa_isDate(obj, parseDateString)) { if (parseDateString && (typeof obj==="string")) { copy = new Date(obj); } else { copy = new Date(); copy.setTime(obj.getTime()); } return copy; } // Handle Object if (isObject(obj)) { return deepCloneObj(obj, descriptors, parseDateString); } return obj; }; valuesAreTheSame = function(value1, value2) { var same; // complex values need to be inspected differently: if (isObject(value1)) { same = isObject(value2) ? objSameValue(value1, value2) : false; } else if (Array.isArray(value1)) { same = Array.isArray(value2) ? value1.itsa_sameValue(value2) : false; } else if (Date.itsa_isDate(value1)) { same = Date.itsa_isDate(value2) ? (value1.getTime()===value2.getTime()) : false; } else { same = (value1===value2); } return same; }; (function(ArrayPrototype) { /** * Checks whether an item is inside the Array. * Alias for (array.indexOf(item) > -1) * * @method itsa_contains * @param item {Any} the item to seek * @return {Boolean} whether the item is part of the Array */ ArrayPrototype.itsa_contains = function(item) { return (this.indexOf(item) > -1); }; /** * Removes an item from the array * * @method itsa_remove * @param item {any|Array} the item (or an hash of items) to be removed * @param [arrayItem=false] {Boolean} whether `item` is an arrayItem that should be treated as a single item to be removed * You need to set `arrayItem=true` in those cases. Otherwise, all single items from `item` are removed separately. * @chainable */ ArrayPrototype.itsa_remove = function(item, arrayItem) { var instance = this, removeItem = function(oneItem) { var index = instance.indexOf(oneItem); (index > -1) && instance.splice(index, 1); }; if (!arrayItem && Array.isArray(item)) { item.forEach(removeItem); } else { removeItem(item); } return instance; }; /** * Replaces an item in the array. If the previous item is not part of the array, the new item is appended. * * @method itsa_replace * @param prevItem {any} the item to be replaced * @param newItem {any} the item to be added * @chainable */ ArrayPrototype.itsa_replace = function(prevItem, newItem) { var instance = this, index = instance.indexOf(prevItem); (index!==-1) ? instance.splice(index, 1, newItem) : instance.push(newItem); return instance; }; /** * Inserts an item in the array at the specified position. If index is larger than array.length, the new item(s) will be appended. * If the item already exists, it will be moved to its new position, unless `duplicate` is set true * * @method itsa_insertAt * @param item {any|Array} the item to be replaced, may be an Array of items * @param index {Number} the position where to add the item(s). When larger than Array.length, the item(s) will be appended. * @param [duplicate=false] {boolean} if an item should be duplicated when already in the array * @chainable */ ArrayPrototype.itsa_insertAt = function(item, index, duplicate) { var instance = this, prevIndex; if (!duplicate) { prevIndex = instance.indexOf(item); if (prevIndex===index) { return instance; } (prevIndex > -1) && instance.splice(prevIndex, 1); } instance.splice(index, 0, item); return instance; }; /** * Shuffles the items in the Array randomly * * @method itsa_shuffle * @chainable */ ArrayPrototype.itsa_shuffle = function() { var instance = this, counter = instance.length, temp, index; // While there are elements in the instance while (counter>0) { // Pick a random index index = Math.floor(Math.random() * counter); // Decrease counter by 1 counter--; // And swap the last element with it temp = instance[counter]; instance[counter] = instance[index]; instance[index] = temp; } return instance; }; /** * Returns a deep copy of the Array. * Only handles members of primary types, Dates, Arrays and Objects. * * @method itsa_deepClone * @param [descriptors=false] {Boolean} whether to use the descriptors when cloning * @param [parseDateString=false] {Boolean} whether to automaticly parse stringified ISO Dates into Date objects * @return {Array} deep-copy of the original */ ArrayPrototype.itsa_deepClone = function (descriptors, parseDateString) { // make it possible to pass 'parseDateString' as 3rd argument, just like with Object: if ((typeof parseDateString!=="boolean") && (typeof arguments[2]==="boolean")) { parseDateString = arguments[2]; } return cloneObj(this, descriptors, parseDateString); }; /** * Compares this object with the reference-object whether they have the same value. * Not by reference, but their content as simple types. * * Compares both JSON.stringify objects * * @method itsa_sameValue * @param refObj {Object} the object to compare with * @return {Boolean} whether both objects have the same value */ ArrayPrototype.itsa_sameValue = function(refArray) { var instance = this, len = instance.length, i = -1, same; same = (len===refArray.length); // loop through the members: while (same && (++i < len)) { same = valuesAreTheSame(instance[i], refArray[i]); } return same; }; /** * Sets the items of `array` to the instance. This will refill the array, while remaining the instance. * This way, external references to the array-instance remain valid. * * @method itsa_defineData * @param array {Array} the Array that holds the new items. * @param [clone=false] {Boolean} whether the items should be cloned * @chainable */ ArrayPrototype.itsa_defineData = function(array, clone) { var thisArray = this, len, i; thisArray.itsa_makeEmpty(); if (clone) { cloneObj(array, true, false, thisArray); } else { len = array.length; for (i=0; i<len; i++) { thisArray[i] = array[i]; } } return thisArray; }, /** * Concats `array` into this array (appended by default). * * @method itsa_concat * @param array {Array} the Array to be merged * @param [prepend=false] {Boolean} whether the items prepended * @param [clone=false] {Boolean} whether the items should be cloned * @param [descriptors=false] {Boolean} whether to use the descriptors when cloning * @param [parseDateString=false] {Boolean} whether to automaticly parse stringified ISO Dates into Date objects * @chainable */ ArrayPrototype.itsa_concat = function(array, prepend, clone, descriptors, parseDateString) { var instance = this, mergeArray = clone ? array.itsa_deepClone(descriptors, parseDateString) : array; if (prepend) { mergeArray.reduceRight(function(coll, item) { coll.unshift(item); return coll; }, instance); } else { mergeArray.reduce(function(coll, item) { coll[coll.length] = item; return coll; }, instance); } return instance; }; /** * Empties the Array by setting its length to zero. * * @method itsa_makeEmpty * @chainable */ ArrayPrototype.itsa_makeEmpty = function() { this.length = 0; return this; }; }(Array.prototype));