Committer: dpetrov
LJSUP-9847: Implement javascript shims for several es5 functionsU trunk/htdocs/js/basic.js
Modified: trunk/htdocs/js/basic.js =================================================================== --- trunk/htdocs/js/basic.js 2011-09-21 08:38:18 UTC (rev 20100) +++ trunk/htdocs/js/basic.js 2011-09-21 08:38:48 UTC (rev 20101) @@ -123,16 +123,46 @@ return d }; +/* function extensions */ +/** + * Returns an array of all own enumerable properties found upon a given object, + * in the same order as that provided by a for-in loop. + * + * @param {Object} The object whose enumerable own properties are to be returned. + * + * @return {Array} Array with properties names. + */ +Object.extend(Object, { + keys: function(o) { + if (o !== Object(o)) { + throw new TypeError('Object.keys called on non-object'); + } + var ret=[],p; + for(p in o) if(Object.prototype.hasOwnProperty.call(o,p)) ret.push(p); + return ret; + } +}); -/* function extensions */ Object.extend(Function.prototype, { - bind: function(object) - { - var method = this; - return function() { - return method.apply(object, arguments); - } + //Mozilla MDN implementation + //Url: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/Bind + bind: function(oThis) { + if (typeof this !== "function") // closest thing possible to the ECMAScript 5 internal IsCallable function + throw new TypeError("Function.prototype.bind - what is trying to be fBound is not callable"); + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP ? this : oThis || window, aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + + return fBound; + }, bindEventListener: function(object) { @@ -267,10 +297,29 @@ } }); +Object.extend(Array, { + /** + * Returns true if an object is an array, false if it is not. + * + * @param {Object} Argument to test. + * + * @return {Boolean} Test result. + */ + isArray: function(arg) { + return Object.prototype.toString.call(arg) == '[object Array]'; + } +}); - /* extend array object */ Object.extend(Array.prototype, { + /** + * Check if index fits in current array size and fix it otherwise. + * + * @param {Number} fromIndex Index to check. + * @param {Number} defaultIndex This value will be taken if fromIndex is not defined. + * + * @return {Number} Fixed index value. + */ fitIndex: function(fromIndex, defaultIndex) { if (fromIndex !== undefined || fromIndex == null) { @@ -286,7 +335,12 @@ return fromIndex; }, - add: function() + /** + * The function takes its arguments and add the ones that are not already inside to the end. + * + * @return {Number} New length of the array. + */ + add: function(/* a1, a2, ... */) { for (var j, a = arguments, i = 0; i < a.length; i++ ) { j = this.indexOf(a[i]); @@ -297,7 +351,12 @@ return this.length; }, - remove: function() + /* + * The function takes its arguments and removes them from the array, if they are inside + * + * @return {Number} New length of the array. + */ + remove: function(/* a1, a2, ... */) { for (var j, a = arguments, i = 0; i < a.length; i++ ) { j = this.indexOf(a[i]); @@ -310,8 +369,21 @@ /* javascript 1.5 array methods */ /* http://developer-test.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array#Methods */ - filter: function(fun, thisp) + /** + * Creates a new array with all elements that pass the test implemented by the provided function. + * + * @param {Function} fun Function to test each element of the array. + * @param {Function} thisp Object to use as this when executing callback. + * + * @param {Array} Filtered array. + */ + filter: function(fun/*, thisp*/) { + var thisp = arguments[1] || null; + if (typeof fun !== "function") { + throw new TypeError("First argument is not callable"); + } + for (var i = 0, len = this.length >>> 0, res = []; i < len; i++) { if (i in this) { var val = this[i]; // in case fun mutates this @@ -323,8 +395,21 @@ return res; }, - forEach: function(fun, thisp) + /** + * Executes a provided function once per array element. + * + * @param {Function} fun Function to test each element of the array. + * @param {Function} thisp Object to use as this when executing callback. + * + * @return {Void} + */ + forEach: function(fun/*, thisp*/) { + if (typeof fun !== "function") { + throw new TypeError("First argument is not callable"); + } + + var thisp = arguments[1] || null; for (var i = 0, len = this.length >>> 0; i < len; i++) { if (i in this) { fun.call(thisp, this[i], i, this); @@ -332,11 +417,30 @@ } }, - indexOf: function(elt, from) + /** + * Returns the first index at which a given element can be found in the array, + * or -1 if it is not present. + * + * @param {Object} elt Element to locate in the array. + * @param {Number} from The index at which to begin the search. Defaults to 0, i.e. + * the whole array will be searched. If the index is greater than or equal + * to the length of the array, -1 is returned, i.e. the array will not be + * searched. If negative, it is taken as the offset from the end of the array. + * Note that even when the index is negative, the array is still searched + * from front to back. If the calculated index is less than 0, the whole + * array will be searched. + * + * @return {Number} Array index. + */ + indexOf: function(elt/*, from*/) { + if (this === null || this === void 0) { + throw new TypeError(); + } + var len = this.length >>> 0; - from = Number(from) || 0; + var from = Number(arguments[1]) || 0; from = from < 0 ? Math.ceil(from) : Math.floor(from); @@ -344,36 +448,266 @@ from += len; } for (; from < len; from++) { - if (from in this && this[from] === elt) + if (((from in this) || (len > from + 1 && this[from] === void 0)) && this[from] === elt) { return from; + } } return -1; }, - lastIndexOf: function(elt, from) + /** + * Returns the last index at which a given element can be found in the array, + * or -1 if it is not present. The array is searched backwards, starting at fromIndex. + * + * @param {Object} elt Element to locate in the array. + * @param {Number=0} from The index at which to start searching backwards. Defaults to + * the array's length, i.e. the whole array will be searched. If the index is + * greater than or equal to the length of the array, the whole array will be + * searched. If negative, it is taken as the offset from the end of the array. + * Note that even when the index is negative, the array is still searched from + * back to front. If the calculated index is less than 0, -1 is returned, i.e. + * the array will not be searched. + * + * @return {Number} Array index. + */ + lastIndexOf: function(elt/*, from*/) { var len = this.length >>> 0; + if (len === 1) { + return -1; + } - var from = Number(from); - if (isNaN(from)) { - from = len - 1; + var from = Number(arguments[1]); + + if (arguments.length === 1) { + from = len; } else { - from = (from < 0) - ? Math.ceil(from) - : Math.floor(from); - if (from < 0) { - from += len; - } else if (from >= len) { - from = len - 1; + if (isNaN(from)) { + if (arguments[1] === void 0) { + from = 0; + } else { + from = -1; + } + } else { + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) { + from += len; + } else if (from >= len) { + from = len - 1; + } } } for (; from > -1; from--) { - if (from in this && this[from] === elt) { + if (((from in this) || (len > from + 1 && this[from] === void 0)) && this[from] === elt) { return from; } } return -1; + }, + + /** + * Tests whether all elements in the array pass the test implemented by the provided function. + * + * Implementation from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every + * + * @param {Function} fun Function to test for each element. + * @param {Object=} thisp Object to use as this when executing fun. + * + * @return {Boolean} Test result. + */ + every: function(fun/*, thisp */) { + if (this === void 0 || this === null) { + throw new TypeError(); + } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== "function") { + throw new TypeError(); + } + + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (i in t && !fun.call(thisp, t[i], i, t)) { + return false; + } + } + + return true; + }, + + /** + * Tests whether some element in the array passes the test implemented + * by the provided function. + * + * Implementation from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some + * + * @param {Function} fun Function to test for each element. + * @param {Object=} thisp Object to use as this when executing fun. + * + * @return {Boolean} Test result. + */ + some: function(fun/*, thisp */) { + if (this === void 0 || this === null) { + throw new TypeError(); + } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== "function") { + throw new TypeError(); + } + + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (i in t && fun.call(thisp, t[i], i, t)) { + return true; + } + } + + return false; + }, + + /** + * Creates a new array with the results of calling a provided function on every element in this array. + * + * Implementation from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map + * + * @param {Function} callback Function that produces an element of the new Array from an element of the current one. + * @param {Object=} thisp Object to use as this when executing fun. + * + * @return {Boolean} New array. + */ + map: function(callback/*, thisp*/ ) { + var A, k; + var thisp = arguments[1] || null; + + if (this == null) { + throw new TypeError(" this is null or not defined"); + } + + var O = Object(this); + var len = O.length >>> 0; + + if ({}.toString.call(callback) != "[object Function]") { + throw new TypeError(callback + " is not a function"); + } + + A = new Array(len); + k = 0; + while(k < len) { + var kValue, mappedValue; + + if (k in O) { + kValue = O[ k ]; + mappedValue = callback.call(thisp, kValue, k, O); + A[ k ] = mappedValue; + } + k++; + } + + return A; + }, + + /** + * Apply a function against an accumulator and each value of the array (from left-to-right) + * as to reduce it to a single value. + * + * Implementation from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Reduce + * + * @param {Function} accumulator Function to execute on each value in the array. + * @param {Object=} initial Object to use as the first argument to the first call of the callback. + * + * @return {Object} Result of function application. + */ + reduce: function(accumulator/*, initial */) { + var i, l = Number(this.length), curr; + + if (typeof accumulator !== "function") { // ES5 : "If IsCallable(callbackfn) is false, throw a TypeError exception." + throw new TypeError("First argument is not callable"); + } + + if (l === 0) { + if (arguments.length > 1) { + return arguments[1]; + } else { + throw new TypeError("No initial value for empty array"); + } + } + + if (l === null && (arguments.length <= 1)) {// == on purpose to test 0 and false. + throw new TypeError("Array length is 0 and no second argument"); + } + + if (arguments.length <= 1) { + curr = this[0]; // Increase i to start searching the secondly defined element in the array + i = 1; // start accumulating at the second element + } else { + curr = arguments[1]; + } + + for (i = i || 0 ; i < l ; ++i) { + if(i in this) { + curr = accumulator.call(undefined, curr, this[i], i, this); + } + } + + return curr; + }, + + /** + * Apply a function simultaneously against two values of the array (from right-to-left) + * as to reduce it to a single value. + * + * @param {Function} callbackfn Function to execute on each value in the array. + * @param {Object=} initial Object to use as the first argument to the first call of the callback. + * + * @return {Object} Result of function application. + */ + reduceRight: function(callbackfn/*, initial */) { + if (this === void 0 || this === null) { + throw new TypeError(); + } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof callbackfn !== "function") { + throw new TypeError(); + } + + // no value to return if no initial value, empty array + if (len === 0 && arguments.length === 1) + throw new TypeError(); + + var k = len - 1; + var accumulator; + if (arguments.length >= 2) { + accumulator = arguments[1]; + } else { + do { + if (k in this) { + accumulator = this[k--]; + break; + } + + // if array contains no values, no initial value to return + if (--k < 0) { + throw new TypeError(); + } + } while (true); + } + + while (k >= 0) { + if (k in t) { + accumulator = callbackfn.call(undefined, accumulator, t[k], k, t); + } + k--; + } + + return accumulator; } });