can3p (can3p) wrote in changelog,
can3p
can3p
changelog

[livejournal] r20101: LJSUP-9847: Implement javascript shims f...

Committer: dpetrov
LJSUP-9847: Implement javascript shims for several es5 functions
U   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;
 	}
 });
 

Tags: can3p, js, livejournal
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments