master
Raw Download raw file
  1
  2  'use strict';
  3
  4  /***
  5   * @package ES5
  6   * @description Shim methods that provide ES5 compatible functionality. This package can be excluded if you do not require legacy browser support (IE8 and below).
  7   *
  8   ***/
  9
 10
 11  /***
 12   * Object module
 13   *
 14   ***/
 15
 16  extend(object, false, false, {
 17
 18    'keys': function(obj) {
 19      var keys = [];
 20      if(!isObjectType(obj) && !isRegExp(obj) && !isFunction(obj)) {
 21        throw new TypeError('Object required');
 22      }
 23      iterateOverObject(obj, function(key, value) {
 24        keys.push(key);
 25      });
 26      return keys;
 27    }
 28
 29  });
 30
 31
 32  /***
 33   * Array module
 34   *
 35   ***/
 36
 37  // ECMA5 methods
 38
 39  function arrayIndexOf(arr, search, fromIndex, increment) {
 40    var length = arr.length,
 41        fromRight = increment == -1,
 42        start = fromRight ? length - 1 : 0,
 43        index = toIntegerWithDefault(fromIndex, start);
 44    if(index < 0) {
 45      index = length + index;
 46    }
 47    if((!fromRight && index < 0) || (fromRight && index >= length)) {
 48      index = start;
 49    }
 50    while((fromRight && index >= 0) || (!fromRight && index < length)) {
 51      if(arr[index] === search) {
 52        return index;
 53      }
 54      index += increment;
 55    }
 56    return -1;
 57  }
 58
 59  function arrayReduce(arr, fn, initialValue, fromRight) {
 60    var length = arr.length, count = 0, defined = isDefined(initialValue), result, index;
 61    checkCallback(fn);
 62    if(length == 0 && !defined) {
 63      throw new TypeError('Reduce called on empty array with no initial value');
 64    } else if(defined) {
 65      result = initialValue;
 66    } else {
 67      result = arr[fromRight ? length - 1 : count];
 68      count++;
 69    }
 70    while(count < length) {
 71      index = fromRight ? length - count - 1 : count;
 72      if(index in arr) {
 73        result = fn(result, arr[index], index, arr);
 74      }
 75      count++;
 76    }
 77    return result;
 78  }
 79
 80  function toIntegerWithDefault(i, d) {
 81    if(isNaN(i)) {
 82      return d;
 83    } else {
 84      return parseInt(i >> 0);
 85    }
 86  }
 87
 88  function checkFirstArgumentExists(args) {
 89    if(args.length === 0) {
 90      throw new TypeError('First argument must be defined');
 91    }
 92  }
 93
 94
 95
 96
 97  extend(array, false, false, {
 98
 99    /***
100     *
101     * @method Array.isArray(<obj>)
102     * @returns Boolean
103     * @short Returns true if <obj> is an Array.
104     * @extra This method is provided for browsers that don't support it internally.
105     * @example
106     *
107     *   Array.isArray(3)        -> false
108     *   Array.isArray(true)     -> false
109     *   Array.isArray('wasabi') -> false
110     *   Array.isArray([1,2,3])  -> true
111     *
112     ***/
113    'isArray': function(obj) {
114      return isArray(obj);
115    }
116
117  });
118
119
120  extend(array, true, false, {
121
122    /***
123     * @method every(<f>, [scope])
124     * @returns Boolean
125     * @short Returns true if all elements in the array match <f>.
126     * @extra [scope] is the %this% object. %all% is provided an alias. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
127     * @example
128     *
129     +   ['a','a','a'].every(function(n) {
130     *     return n == 'a';
131     *   });
132     *   ['a','a','a'].every('a')   -> true
133     *   [{a:2},{a:2}].every({a:2}) -> true
134     ***/
135    'every': function(fn, scope) {
136      var length = this.length, index = 0;
137      checkFirstArgumentExists(arguments);
138      while(index < length) {
139        if(index in this && !fn.call(scope, this[index], index, this)) {
140          return false;
141        }
142        index++;
143      }
144      return true;
145    },
146
147    /***
148     * @method some(<f>, [scope])
149     * @returns Boolean
150     * @short Returns true if any element in the array matches <f>.
151     * @extra [scope] is the %this% object. %any% is provided as an alias. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
152     * @example
153     *
154     +   ['a','b','c'].some(function(n) {
155     *     return n == 'a';
156     *   });
157     +   ['a','b','c'].some(function(n) {
158     *     return n == 'd';
159     *   });
160     *   ['a','b','c'].some('a')   -> true
161     *   [{a:2},{b:5}].some({a:2}) -> true
162     ***/
163    'some': function(fn, scope) {
164      var length = this.length, index = 0;
165      checkFirstArgumentExists(arguments);
166      while(index < length) {
167        if(index in this && fn.call(scope, this[index], index, this)) {
168          return true;
169        }
170        index++;
171      }
172      return false;
173    },
174
175    /***
176     * @method map(<map>, [scope])
177     * @returns Array
178     * @short Maps the array to another array containing the values that are the result of calling <map> on each element.
179     * @extra [scope] is the %this% object. When <map> is a function, it receives three arguments: the current element, the current index, and a reference to the array. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts a string, which is a shortcut for a function that gets that property (or invokes a function) on each element.
180     * @example
181     *
182     *   [1,2,3].map(function(n) {
183     *     return n * 3;
184     *   });                                  -> [3,6,9]
185     *   ['one','two','three'].map(function(n) {
186     *     return n.length;
187     *   });                                  -> [3,3,5]
188     *   ['one','two','three'].map('length')  -> [3,3,5]
189     *
190     ***/
191    'map': function(fn, scope) {
192      var scope = arguments[1], length = this.length, index = 0, result = new Array(length);
193      checkFirstArgumentExists(arguments);
194      while(index < length) {
195        if(index in this) {
196          result[index] = fn.call(scope, this[index], index, this);
197        }
198        index++;
199      }
200      return result;
201    },
202
203    /***
204     * @method filter(<f>, [scope])
205     * @returns Array
206     * @short Returns any elements in the array that match <f>.
207     * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
208     * @example
209     *
210     +   [1,2,3].filter(function(n) {
211     *     return n > 1;
212     *   });
213     *   [1,2,2,4].filter(2) -> 2
214     *
215     ***/
216    'filter': function(fn) {
217      var scope = arguments[1];
218      var length = this.length, index = 0, result = [];
219      checkFirstArgumentExists(arguments);
220      while(index < length) {
221        if(index in this && fn.call(scope, this[index], index, this)) {
222          result.push(this[index]);
223        }
224        index++;
225      }
226      return result;
227    },
228
229    /***
230     * @method indexOf(<search>, [fromIndex])
231     * @returns Number
232     * @short Searches the array and returns the first index where <search> occurs, or -1 if the element is not found.
233     * @extra [fromIndex] is the index from which to begin the search. This method performs a simple strict equality comparison on <search>. It does not support enhanced functionality such as searching the contents against a regex, callback, or deep comparison of objects. For such functionality, use the %findIndex% method instead.
234     * @example
235     *
236     *   [1,2,3].indexOf(3)           -> 1
237     *   [1,2,3].indexOf(7)           -> -1
238     *
239     ***/
240    'indexOf': function(search) {
241      var fromIndex = arguments[1];
242      if(isString(this)) return this.indexOf(search, fromIndex);
243      return arrayIndexOf(this, search, fromIndex, 1);
244    },
245
246    /***
247     * @method lastIndexOf(<search>, [fromIndex])
248     * @returns Number
249     * @short Searches the array and returns the last index where <search> occurs, or -1 if the element is not found.
250     * @extra [fromIndex] is the index from which to begin the search. This method performs a simple strict equality comparison on <search>.
251     * @example
252     *
253     *   [1,2,1].lastIndexOf(1)                 -> 2
254     *   [1,2,1].lastIndexOf(7)                 -> -1
255     *
256     ***/
257    'lastIndexOf': function(search) {
258      var fromIndex = arguments[1];
259      if(isString(this)) return this.lastIndexOf(search, fromIndex);
260      return arrayIndexOf(this, search, fromIndex, -1);
261    },
262
263    /***
264     * @method forEach([fn], [scope])
265     * @returns Nothing
266     * @short Iterates over the array, calling [fn] on each loop.
267     * @extra This method is only provided for those browsers that do not support it natively. [scope] becomes the %this% object.
268     * @example
269     *
270     *   ['a','b','c'].forEach(function(a) {
271     *     // Called 3 times: 'a','b','c'
272     *   });
273     *
274     ***/
275    'forEach': function(fn) {
276      var length = this.length, index = 0, scope = arguments[1];
277      checkCallback(fn);
278      while(index < length) {
279        if(index in this) {
280          fn.call(scope, this[index], index, this);
281        }
282        index++;
283      }
284    },
285
286    /***
287     * @method reduce(<fn>, [init])
288     * @returns Mixed
289     * @short Reduces the array to a single result.
290     * @extra If [init] is passed as a starting value, that value will be passed as the first argument to the callback. The second argument will be the first element in the array. From that point, the result of the callback will then be used as the first argument of the next iteration. This is often refered to as "accumulation", and [init] is often called an "accumulator". If [init] is not passed, then <fn> will be called n - 1 times, where n is the length of the array. In this case, on the first iteration only, the first argument will be the first element of the array, and the second argument will be the second. After that callbacks work as normal, using the result of the previous callback as the first argument of the next. This method is only provided for those browsers that do not support it natively.
291     *
292     * @example
293     *
294     +   [1,2,3,4].reduce(function(a, b) {
295     *     return a - b;
296     *   });
297     +   [1,2,3,4].reduce(function(a, b) {
298     *     return a - b;
299     *   }, 100);
300     *
301     ***/
302    'reduce': function(fn) {
303      return arrayReduce(this, fn, arguments[1]);
304    },
305
306    /***
307     * @method reduceRight([fn], [init])
308     * @returns Mixed
309     * @short Identical to %Array#reduce%, but operates on the elements in reverse order.
310     * @extra This method is only provided for those browsers that do not support it natively.
311     *
312     *
313     *
314     *
315     * @example
316     *
317     +   [1,2,3,4].reduceRight(function(a, b) {
318     *     return a - b;
319     *   });
320     *
321     ***/
322    'reduceRight': function(fn) {
323      return arrayReduce(this, fn, arguments[1], true);
324    }
325
326
327  });
328
329
330
331
332  /***
333   * String module
334   *
335   ***/
336
337
338  function buildTrim() {
339    var support = getTrimmableCharacters().match(/^\s+$/);
340    try { string.prototype.trim.call([1]); } catch(e) { support = false; }
341    extend(string, true, !support, {
342
343      /***
344       * @method trim[Side]()
345       * @returns String
346       * @short Removes leading and/or trailing whitespace from the string.
347       * @extra Whitespace is defined as line breaks, tabs, and any character in the "Space, Separator" Unicode category, conforming to the the ES5 spec. The standard %trim% method is only added when not fully supported natively.
348       *
349       * @set
350       *   trim
351       *   trimLeft
352       *   trimRight
353       *
354       * @example
355       *
356       *   '   wasabi   '.trim()      -> 'wasabi'
357       *   '   wasabi   '.trimLeft()  -> 'wasabi   '
358       *   '   wasabi   '.trimRight() -> '   wasabi'
359       *
360       ***/
361      'trim': function() {
362        return this.toString().trimLeft().trimRight();
363      },
364
365      'trimLeft': function() {
366        return this.replace(regexp('^['+getTrimmableCharacters()+']+'), '');
367      },
368
369      'trimRight': function() {
370        return this.replace(regexp('['+getTrimmableCharacters()+']+$'), '');
371      }
372    });
373  }
374
375
376
377  /***
378   * Function module
379   *
380   ***/
381
382
383  extend(Function, true, false, {
384
385     /***
386     * @method bind(<scope>, [arg1], ...)
387     * @returns Function
388     * @short Binds <scope> as the %this% object for the function when it is called. Also allows currying an unlimited number of parameters.
389     * @extra "currying" means setting parameters ([arg1], [arg2], etc.) ahead of time so that they are passed when the function is called later. If you pass additional parameters when the function is actually called, they will be added will be added to the end of the curried parameters. This method is provided for browsers that don't support it internally.
390     * @example
391     *
392     +   (function() {
393     *     return this;
394     *   }).bind('woof')(); -> returns 'woof'; function is bound with 'woof' as the this object.
395     *   (function(a) {
396     *     return a;
397     *   }).bind(1, 2)();   -> returns 2; function is bound with 1 as the this object and 2 curried as the first parameter
398     *   (function(a, b) {
399     *     return a + b;
400     *   }).bind(1, 2)(3);  -> returns 5; function is bound with 1 as the this object, 2 curied as the first parameter and 3 passed as the second when calling the function
401     *
402     ***/
403    'bind': function(scope) {
404      var fn = this, args = multiArgs(arguments, null, 1), bound;
405      if(!isFunction(this)) {
406        throw new TypeError('Function.prototype.bind called on a non-function');
407      }
408      bound = function() {
409        return fn.apply(fn.prototype && this instanceof fn ? this : scope, args.concat(multiArgs(arguments)));
410      }
411      bound.prototype = this.prototype;
412      return bound;
413    }
414
415  });
416
417  /***
418   * Date module
419   *
420   ***/
421
422   /***
423   * @method toISOString()
424   * @returns String
425   * @short Formats the string to ISO8601 format.
426   * @extra This will always format as UTC time. Provided for browsers that do not support this method.
427   * @example
428   *
429   *   Date.create().toISOString() -> ex. 2011-07-05 12:24:55.528Z
430   *
431   ***
432   * @method toJSON()
433   * @returns String
434   * @short Returns a JSON representation of the date.
435   * @extra This is effectively an alias for %toISOString%. Will always return the date in UTC time. Provided for browsers that do not support this method.
436   * @example
437   *
438   *   Date.create().toJSON() -> ex. 2011-07-05 12:24:55.528Z
439   *
440   ***/
441
442  extend(date, false, false, {
443
444     /***
445     * @method Date.now()
446     * @returns String
447     * @short Returns the number of milliseconds since January 1st, 1970 00:00:00 (UTC time).
448     * @extra Provided for browsers that do not support this method.
449     * @example
450     *
451     *   Date.now() -> ex. 1311938296231
452     *
453     ***/
454    'now': function() {
455      return new date().getTime();
456    }
457
458  });
459
460   function buildISOString() {
461    var d = new date(date.UTC(1999, 11, 31)), target = '1999-12-31T00:00:00.000Z';
462    var support = d.toISOString && d.toISOString() === target;
463    extendSimilar(date, true, !support, 'toISOString,toJSON', function(methods, name) {
464      methods[name] = function() {
465        return padNumber(this.getUTCFullYear(), 4) + '-' +
466               padNumber(this.getUTCMonth() + 1, 2) + '-' +
467               padNumber(this.getUTCDate(), 2) + 'T' +
468               padNumber(this.getUTCHours(), 2) + ':' +
469               padNumber(this.getUTCMinutes(), 2) + ':' +
470               padNumber(this.getUTCSeconds(), 2) + '.' +
471               padNumber(this.getUTCMilliseconds(), 3) + 'Z';
472      }
473    });
474   }
475
476  // Initialize
477  buildTrim();
478  buildISOString();
479