1 (function () { 2 var testArray = function (o) { 3 return Object.prototype.toString.apply(o) === '[object Array]'; 4 }; 5 6 /** 7 * @namespace Functions for dealing with Objects. 8 */ 9 Obj = { 10 /** 11 * Adds all of the passed properties if they are not already 12 * defined. 13 * 14 * @example 15 * var o = { a: 1 }; 16 * Object.merge(o, { 17 * a: 2, 18 * b: 2 19 * }); 20 * o; // { a: 1, b: 2 } 21 * 22 * @name merge 23 * @methodOf Obj 24 * @param {Object} o The object to modify (in-place). 25 * @param {Object} props The properties to write. 26 * @param {Boolean} clobber true to overwrite defined properties 27 * @return {Object} The modified object. 28 */ 29 merge: function (o, props, clobber) { 30 for (var p in props) { 31 if (props.hasOwnProperty(p) && (!o[p] || clobber)) { 32 o[p] = props[p]; 33 } 34 } 35 return o; 36 } 37 }; 38 // Functions that are used in assembling nativelib. 39 40 Obj.merge(Obj, { 41 /** 42 * Maps each property to its value. 43 * 44 * @example 45 * var o = { a: 1 }; 46 * Object.alias(o, { 47 * a: 'b' 48 * }; // { a: 1, b: 1 } 49 * Object.alias(o, { 50 * b: ['c', 'd'] 51 * }; // { a: 1, b: 1, c: 1, d: 1 } 52 * 53 * @name alias 54 * @methodOf Obj 55 * @param {Object} o The object to modify (in-place). 56 * @param {Object} map The properties to map. 57 * @return {Object} The modified object. 58 */ 59 alias: function (o, map) { 60 for (var p in map) { 61 if (map.hasOwnProperty(p) && !o[map[p]]) { 62 if (testArray(map[p])) { 63 for (var i = 0, len = map[p].length; i < len; i++) { 64 o[map[p][i]] = o[p]; 65 } 66 } else { 67 o[map[p]] = o[p]; 68 } 69 } 70 } 71 return o; 72 }, 73 74 /** 75 * Copies each property of the provided object into a new object, 76 * creating a shallow copy. 77 * 78 * @name copy 79 * @methodOf Obj 80 * @param {Object} o The object to copy. 81 * @return {Object} The copied object. 82 */ 83 copy: function (o) { 84 return Obj.merge({}, o); 85 }, 86 87 /** 88 * Creates a new object from an array of property-value pairs. 89 * 90 * @example 91 * Object.create([['a', 1], ['b', 2]]); // { a: 1, b: 2 } 92 * 93 * @name create 94 * @methodOf Obj 95 * @param {Array} pairs The pair array. 96 * @return {Object} The created object. 97 */ 98 create: function (pairs) { 99 var o = {}; 100 for (var i = 0; i < pairs.length; i++) { 101 o[pairs[i][0]] = pairs[i][1]; 102 } 103 return o; 104 }, 105 106 /** 107 * Compares the value of each property in each object. 108 * 109 * @name equals 110 * @methodOf Obj 111 * @param {Object} o One object to compare. 112 * @param {Object} o2 The object to compare it with. 113 * @return {Boolean} true if the properties are equal, false if not. 114 */ 115 equals: function (o, o2) { 116 var p; 117 for (p in o2) { 118 if (o2.hasOwnProperty(p) && !Obj.valuesEqual(o[p], o2[p])) { 119 return false; 120 } 121 } 122 for (p in o) { 123 if (o.hasOwnProperty(p) && !Obj.valuesEqual(o[p], o2[p])) { 124 return false; 125 } 126 } 127 return true; 128 }, 129 130 /** 131 * Creates a new object containing only the properties that return 132 * true when passed to the provided function. 133 * 134 * @name filter 135 * @methodOf Obj 136 * @param {Object} o The object to filter. 137 * @param {Function} fn The filtering function. 138 * @param {Object} ctx The function's execution context. 139 * @return {Object} The filtered object. 140 */ 141 filter: function (o, fn, ctx) { 142 var filtered = {}; 143 for (var p in o) { 144 if (o.hasOwnProperty(p) && fn.call(ctx || o, o[p], p, o)) { 145 filtered[p] = o[p]; 146 } 147 } 148 return filtered; 149 }, 150 151 /** 152 * The same as filter, but only returning the first match. 153 * 154 * @name filterOne 155 * @methodOf Obj 156 * @param {Object} o The object to filter. 157 * @param {Function} fn The filtering function. 158 * @param {Object} ctx The function's execution context. 159 * @return The first filtered item, if found. 160 */ 161 filterOne: function (o, fn, ctx) { 162 var filtered = {}; 163 for (var p in o) { 164 if (o.hasOwnProperty(p) && fn.call(ctx || o, o[p], p, o)) { 165 filtered[p] = o[p]; 166 return filtered; 167 } 168 } 169 }, 170 171 /** 172 * Returns an object matching the specified path, if it exists. 173 * 174 * @name fromPath 175 * @methodOf Obj 176 * @param {String} path The period '.' separated object path. 177 * @param {Object} root The root path (default is window). 178 * @return {Object} The object, if found. 179 */ 180 fromPath: function (path, root) { 181 return path.split('.').reduce(function (a, b) { 182 return a ? a[b] : undefined; 183 }, root || window); 184 }, 185 186 /** 187 * Calls the property, if present, on each property in the passed 188 * object, returning an object with the results at each property. If 189 * the named property is not present on a given item, the result 190 * property will be undefined. 191 * 192 * @name invoke 193 * @methodOf Obj 194 * @param {Object} o The object 195 * @param {String} name The property name to call. 196 * @param {Array} args An argument list that will be provided with 197 * each call. 198 * @param {Object} ctx The function's execution context. 199 * @return {Object} The return value of each call. 200 */ 201 invoke: function (o, name, args, ctx) { 202 var invoked = {}; 203 for (var p in o) { 204 if (o.hasOwnProperty(p)) { 205 invoked[p] = (name in o[p] ? 206 o[p][name].apply(ctx || o[p], args || []) : undefined); 207 } 208 } 209 return invoked; 210 }, 211 212 /** 213 * Creates an array containing the property names of an object. 214 * 215 * @name keys 216 * @methodOf Obj 217 * @param {Object} o The object 218 * @return {Array} The object's keys 219 */ 220 keys: function (o) { 221 var keys = []; 222 for (var p in o) { 223 if (o.hasOwnProperty(p)) { 224 keys.push(p); 225 } 226 } 227 return keys; 228 }, 229 230 /** 231 * Runs the passed function for each property in the object. 232 * 233 * @name forEach 234 * @methodOf Obj 235 * @param {Object} o The object to iterate. 236 * @param {Function} fn The function to call. 237 * @param {Object} ctx The function's execution context. 238 */ 239 forEach: function (o, fn, ctx) { 240 for (var p in o) { 241 if (o.hasOwnProperty(p)) { 242 fn.call(ctx || o, o[p], p, o); 243 } 244 } 245 }, 246 247 /** 248 * Creates an array containing the values of a function applied to 249 * each property in the object. 250 * 251 * @example 252 * Object.map({ Rice: 'Jerry', Clark: 'Gary' }, function (v, p, o) { 253 * return p + ', ' + v; 254 * }; // { Rice: 'Rice, Jerry', Clark: 'Clark, Gary' } 255 * 256 * @name map 257 * @methodOf Obj 258 * @param {Object} o The object to iterate. 259 * @param {Function} fn The map function. 260 * @param {Object} ctx The function's execution context, called with (value, 261 * property, object). 262 * @return {Object} The mapped object. 263 */ 264 map: function (o, fn, ctx) { 265 var mapped = {}; 266 for (var p in o) { 267 if (o.hasOwnProperty(p)) { 268 mapped[p] = fn.call(ctx || o, o[p], o, o); 269 } 270 } 271 return mapped; 272 }, 273 274 /** 275 * Creates a nested object to the depth specified, not overwriting 276 * existing properties. 277 * 278 * @name namespace 279 * @methodOf Obj 280 * @param {String} path The period '.' separated object path. 281 * @return {Object} The resulting object. 282 */ 283 namespace: function (path) { 284 return path.split('.').reduce(function (o, part) { 285 return o[part] || (o[part] = {}); 286 }, window); 287 }, 288 289 /** 290 * Checks if the passed item is an object. 291 * 292 * @name test 293 * @methodOf Obj 294 * @param {Object} o The item to check. 295 * @return {Boolean} true if it is an object, false if not. 296 */ 297 test: function (o) { 298 return Object.prototype.toString.call(o) === '[object Object]'; 299 }, 300 301 /** 302 * Returns each property's value. 303 * 304 * @name values 305 * @methodOf Obj 306 * @param {Object} o The object to use. 307 * @return {Array} An array of values. 308 */ 309 values: function (o) { 310 var vals = []; 311 for (var p in o) { 312 if (o.hasOwnProperty(p)) { 313 vals.push(o[p]); 314 } 315 } 316 return vals; 317 }, 318 319 /** 320 * Checks if the two passed objects are equal, performing deep 321 * checks for Objects and Arrays. 322 * 323 * @name valuesEqual 324 * @methodOf Obj 325 * @param {Object} a The item to check. 326 * @param {Object} b The item to check it against. 327 * @return {Boolean} true if the values are equal, false if not. 328 */ 329 valuesEqual: function (a, b) { 330 if (testArray(a)) { 331 return testArray(b) && a.equals(b); 332 } else if (Obj.test(a)) { 333 return Obj.test(b) && Obj.equals(a, b); 334 } else { 335 return a === b; 336 } 337 } 338 }); 339 340 Obj.alias(Obj, { 341 /** 342 * Alias of filter. 343 * 344 * @name select 345 * @methodOf Obj 346 */ 347 filter: 'select', 348 349 /** 350 * Alias of filterOne. 351 * 352 * @name detect 353 * @methodOf Obj 354 */ 355 filterOne: 'detect', 356 357 /** 358 * Alias of forEach. 359 * 360 * @name each 361 * @methodOf Obj 362 */ 363 forEach: 'each', 364 365 /** 366 * Alias of namespace 367 * 368 * @name ns 369 * @methodOf Obj 370 */ 371 namespace: 'ns' 372 }); 373 374 Obj.merge(Array.prototype, { 375 /** 376 * Checks if all items in this array pass the provided test. 377 * 378 * @name all 379 * @methodOf Array# 380 * @param {Function} fn The filtering function. 381 * @param {Object} ctx The function's execution context. 382 * @return {Boolean} If each item passes. 383 */ 384 all: function (fn, ctx) { 385 for (var i = 0, len = this.length; i < len; i++) { 386 if (!fn.call(ctx || this, this[i], i, this)) { 387 return false; 388 } 389 } 390 return true; 391 }, 392 393 /** 394 * Creates a copy of this array. 395 * 396 * @name copy 397 * @methodOf Array# 398 * @return {Array} The copy. 399 */ 400 copy: function () { 401 return this.slice(0); 402 }, 403 404 /** 405 * Checks that the passed array equals this one at each index. 406 * 407 * @name equals 408 * @methodOf Array# 409 * @param {Array} a The array to check 410 * @return {Boolean} true if equal, false if not 411 */ 412 equals: function (a) { 413 if (a.length !== this.length) { 414 return false; 415 } 416 for (var i = 0, len = a.length; i < len; i++) { 417 if (!Obj.valuesEqual(this[i], a[i])) { 418 return false; 419 } 420 } 421 return true; 422 }, 423 424 /** 425 * Creates an array containing only the elements in this one for 426 * which the filtering function returns true. 427 * 428 * @name filter 429 * @methodOf Array# 430 * @param {Function} fn The filtering function. 431 * @param {Object} ctx The function's execution context. 432 * @return {Array} The filtered array. 433 */ 434 filter: function (fn, ctx) { 435 var filtered = []; 436 for (var i = 0, len = this.length; i < len; i++) { 437 if (fn.call(ctx || this, this[i], i, this)) { 438 filtered.push(this[i]); 439 } 440 } 441 return filtered; 442 }, 443 444 /** 445 * Creates a flattened array, merging down all sub-arrays. 446 * 447 * @name flatten 448 * @methodOf Array# 449 * @return {Array} The flattened array. 450 */ 451 flatten: function () { 452 var flattened = []; 453 for (var i = 0, len = this.length; i < len; i++) { 454 if (testArray(this[i])) { 455 flattened = flattened.concat(this[i].flatten()); 456 } else { 457 flattened.push(this[i]); 458 } 459 } 460 return flattened; 461 }, 462 463 /** 464 * The same as filter, but only returning the first match. 465 * 466 * @name filterOne 467 * @methodOf Array# 468 * @param {Function} fn The filtering function. 469 * @param {Object} ctx The function's execution context. 470 * @return {Object} The first filtered item, if found. 471 */ 472 filterOne: function (fn, ctx) { 473 for (var i = 0, len = this.length; i < len; i++) { 474 if (fn.call(ctx || this, this[i], i, this)) { 475 return this[i]; 476 } 477 } 478 }, 479 480 /** 481 * Gets the array's first item. 482 * 483 * @name first 484 * @methodOf Array# 485 * @return {Object} The first item. 486 */ 487 first: function () { 488 return this[0]; 489 }, 490 491 /** 492 * Gets a propertie's value for each object in the array. 493 * 494 * @name eachProperty 495 * @methodOf Array# 496 * @param {String} p The property value to retrieve. 497 * @param {Function} fn true if the property is a function and should be 498 * called. 499 * @param {Object} ctx The function's execution context. 500 * @return {Array} Each property's value. 501 */ 502 eachProperty: function (p, fn, ctx) { 503 var vals = []; 504 for (var i = 0, len = this.length; i < len; i++) { 505 vals.push(fn ? this[i][p].apply(ctx || this[i][p]) : this[i][p]); 506 } 507 return vals; 508 }, 509 510 /** 511 * Runs the provided function for each item in this array. 512 * 513 * @name forEach 514 * @methodOf Array# 515 * @param {Function} fn The function to run. 516 * @param {Object} ctx The function's execution context. 517 */ 518 forEach: function (fn, ctx) { 519 for (var i = 0, len = this.length; i < len; i++) { 520 fn.call(ctx || this, this[i], i, this); 521 } 522 }, 523 524 /** 525 * Checks if the array includes an item identical to the one passed. 526 * 527 * @name includes 528 * @methodOf Array# 529 * @param {Object} item The item to check. 530 * @return {Boolean} true if the item was found, false if not. 531 */ 532 includes: function (item) { 533 for (var i = 0, len = this.length; i < len; i++) { 534 if (this[i] === item) { 535 return true; 536 } 537 } 538 return false; 539 }, 540 541 /** 542 * Gets all but the last item of the array. 543 * 544 * @name init 545 * @methodOf Array# 546 * @return {Array} The array's init. 547 */ 548 init: function () { 549 return this.slice(0, this.length - 1); 550 }, 551 552 /** 553 * Calls the property, if present, on each item in this array, 554 * returning a list of results. If the named property is not present 555 * on a given item, undefined will pushed on the result list. 556 * 557 * @name invoke 558 * @methodOf Array# 559 * @param {String} name The property name to call. 560 * @param {Array} args An argument list that will be provided with 561 * each call. 562 * @param {Object} ctx The function's execution context. 563 * @return {Array} The return value of each call. 564 */ 565 invoke: function (name, args, ctx) { 566 var results = []; 567 for (var i = 0, len = this.length; i < len; i++) { 568 results.push(name in this[i] ? 569 this[i][name].apply(ctx || this[i], args || []) : undefined); 570 } 571 return results; 572 }, 573 574 /** 575 * Gets the last item of the array. 576 * 577 * @name last 578 * @methodOf Array# 579 * @return {Object} The array's last item. 580 */ 581 last: function () { 582 return this[this.length - 1]; 583 }, 584 585 /** 586 * Creates an array containing the values of a function applied to 587 * each item in the array. 588 * 589 * @example 590 * [1, 2, 3].map(function (n, i, a) { 591 * return n * i; 592 * }; // [0, 2, 6] 593 * 594 * @name map 595 * @methodOf Array# 596 * @param {Function} fn The map function 597 * @param {Object} ctx The function's execution context, called with (value, 598 * index, array) 599 * @return {Array} The mapped array 600 */ 601 map: function (fn, ctx) { 602 var mapped = []; 603 for (var i = 0, len = this.length; i < len; i++) { 604 mapped.push(fn.call(ctx || this, this[i], i, this)); 605 } 606 return mapped; 607 }, 608 609 /** 610 * Splits this array in two based on the return value of the 611 * partitioning function. The first array contains all positives and 612 * the second all negatives. 613 * 614 * @name partition 615 * @methodOf Array# 616 * @param {Function} fn The partition function. 617 * @param {Object} ctx The function's execution context. 618 * @return {Array} The array of paritions. 619 */ 620 partition: function (fn, ctx) { 621 var parts = [[], []]; 622 for (var i = 0, len = this.length; i < len; i++) { 623 parts[fn.call(ctx || this, this[i], i, this) ? 0 : 1].push(this[i]); 624 } 625 return parts; 626 }, 627 628 /** 629 * Processes the items of an array from left to right, applying the 630 * reducing function to each item. 631 * 632 * @name reduce 633 * @methodOf Array# 634 * @param {Function} fn The function that is called with each iteration. This 635 * function is provided with the following arguments: 636 * - The result of the previous iteration 637 * - The current item 638 * - The current item's index 639 * - The array 640 * @param {Object} init A value to be used as the previous value in the first 641 * iteration. 642 * @param {Object} ctx The context in which to apply the function. 643 * @return {Object} The reduced value. 644 */ 645 reduce: function (fn, init, ctx) { 646 var i = 0, len = this.length, prev = init || this[i++]; 647 while (i < len) { 648 prev = fn.apply(ctx || this, [prev, this[i], i++, this]); 649 } 650 return prev; 651 }, 652 653 /** 654 * Removes all items matching the passed object. 655 * 656 * @name remove 657 * @methodOf Array# 658 * @param {Object} o The object to remove. 659 * @return {Boolean} true if the object was removed, false if not. 660 */ 661 remove: function (o) { 662 var removed = false; 663 for (var i = 0, len = this.length; i < len; i++) { 664 if (this[i] === o) { 665 removed = true; 666 this.splice(i--, 1); 667 } 668 } 669 return removed; 670 }, 671 672 /** 673 * Checks if any item in this array passes the provided test. 674 * 675 * @name some 676 * @methodOf Array# 677 * @param {Function} fn The filtering function. 678 * @param {Object} ctx The function's execution context. 679 * @return {Boolean} If any item passes. 680 */ 681 some: function (fn, ctx) { 682 for (var i = 0, len = this.length; i < len; i++) { 683 if (fn.call(ctx || this, this[i], i, this)) { 684 return true; 685 } 686 } 687 return false; 688 }, 689 690 /** 691 * Returns an array containing all items not identical to those in the 692 * passed array. 693 * 694 * @name subtract 695 * @methodOf Array# 696 * @param {Array} a The array of of objects to filter. 697 * @param {Boolean} v true to check value rather than identity. 698 * @return {Array} The array with none of the passed objects. 699 */ 700 subtract: function (a, v) { 701 var subtracted = []; 702 703 if (v) { 704 for (var i = 0, len = this.length; i < len; i++) { 705 for (var j = 0, len2 = a.length; j < len2; j++) { 706 if (this[i] != a[j]) { 707 subtracted.push(this[i]); 708 } 709 } 710 } 711 } else { 712 for (var i = 0, len = this.length; i < len; i++) { 713 for (var j = 0, len2 = a.length; j < len2; j++) { 714 if (this[i] !== a[j]) { 715 subtracted.push(this[i]); 716 } 717 } 718 } 719 } 720 721 return subtracted; 722 }, 723 724 /** 725 * Returns an array containing only items with unique identity. 726 * 727 * @name unique 728 * @methodOf Array# 729 * @return {Array} The array with no duplicate objects. 730 */ 731 unique: function () { 732 var uniq = []; 733 for (var i = 0, len = this.length; i < len; i++) { 734 if (!uniq.includes(this[i])) { 735 uniq.push(this[i]); 736 } 737 } 738 return uniq; 739 }, 740 741 /** 742 * Creates an array containing arrays of each item at each index. 743 * 744 * @example 745 * [1, 2, 3].zip([4, 5, 6]); // [[1, 4], [2, 5], [3, 6]] 746 * 747 * @name zip 748 * @methodOf Array# 749 * @param {Array} arr The array to zip. 750 * @return {Array} The zipped array. 751 */ 752 zip: function (arr) { 753 var zipped = []; 754 var len = this.length > arr.length ? this.length : arr.length; 755 for (var i = 0; i < len; i++) { 756 zipped.push([this[i], arr[i]]); 757 } 758 return zipped; 759 } 760 }); 761 762 Obj.merge(Array, { 763 /** 764 * Creates an array from an array-like object. 765 * 766 * @name create 767 * @methodOf Array 768 * @param {Object} o The source object 769 * @return {Array} The new array 770 */ 771 create: function (o) { 772 var arr; 773 if (testArray(o)) { 774 arr = o; 775 } else if (o.toArray) { 776 arr = o.toArray(); 777 } else { 778 arr = []; 779 for (var i = 0, len = o.length || 0; i < len; i++) { 780 arr.push(o[i]); 781 } 782 } 783 return arr; 784 }, 785 786 /** 787 * Checks if the object is an array. 788 * 789 * @name test 790 * @methodOf Array 791 * @param {Object} o The object to check. 792 * @return {Boolean} true if it is an array, false if not. 793 */ 794 test: testArray 795 }); 796 797 Obj.alias(Array.prototype, { 798 /** 799 * Alias of eachProperty. 800 * 801 * @name pluck 802 * @methodOf Array# 803 */ 804 eachProperty: 'pluck', 805 806 /** 807 * Alias of filter. 808 * 809 * @name select 810 * @methodOf Array# 811 */ 812 filter: 'select', 813 814 /** 815 * Alias of filterOne. 816 * 817 * @name detect 818 * @methodOf Array# 819 */ 820 filterOne: 'detect', 821 822 /** 823 * Alias of forEach. 824 * 825 * @name each 826 * @methodOf Array# 827 */ 828 forEach: 'each' 829 }); 830 831 Obj.merge(Function.prototype, { 832 /** 833 * Binds an execution context and an optional argument list to this 834 * function. 835 * 836 * @name bind 837 * @methodOf Function# 838 * @param {Object} ctx The bound execution context. 839 * @param {Array} args The bound arguments array. 840 * @return {Function} The bound function. 841 */ 842 bind: function (ctx, args) { 843 var fn = this; 844 return function () { 845 return fn.apply(ctx, args || []); 846 }; 847 }, 848 849 /** 850 * Bind a number of arguments to this function, not specifying 851 * execution context. 852 * 853 * @name curry 854 * @methodOf Function# 855 * @param {Arguments} list An argument list to bind. 856 * @return {Function} The bound function. 857 */ 858 curry: function () { 859 var fn = this, args = Array.create(arguments); 860 return function () { 861 return fn.apply(this, args.concat(Array.create(arguments))); 862 }; 863 }, 864 865 /** 866 * Calls the function after the specified timeout. 867 * 868 * @name delay 869 * @methodOf Function# 870 * @param {Number} ms The number of milliseconds to delay. 871 * @param {Object} ctx The function's execution context. 872 * @param {Array} args An arguments array to pass to the function. 873 * @return {Number} The timeout's ID. 874 */ 875 delay: function (ms, ctx, args) { 876 var fn = this; 877 return setTimeout(function () { 878 fn.apply(ctx || this, args || []); 879 }, ms); 880 }, 881 882 /** 883 * Calls this function n times, providing the current iteration 884 * (1-based) and n and the each time. 885 * 886 * @name repeat 887 * @methodOf Function# 888 * @param {Number} n The number of iterations. 889 * @param {Object} ctx (optional) The exection context. 890 */ 891 repeat: function (n, ctx) { 892 var fn = this; 893 return function (x, n) { 894 return n.range(1).reduce(function (p) { 895 return fn.call(ctx || this, p); 896 }, x); 897 } 898 } 899 }); 900 901 Obj.merge(Function, { 902 /** 903 * Returns an empty (typically placeholder) function. 904 * 905 * @name empty 906 * @methodOf Function 907 * @return {Function} The empty function. 908 */ 909 empty: function () { 910 return function () {}; 911 }, 912 913 /** 914 * Checks if the provided object is a Function. 915 * 916 * @name test 917 * @methodOf Function 918 * @param {Object} o The object to check. 919 * @return {Boolean} true if it is a Function, false if not. 920 */ 921 test: function (o) { 922 return Object.prototype.toString.apply(o) === '[object Function]'; 923 } 924 }); 925 926 Obj.alias(Function.prototype, { 927 /** 928 * Alias of curry. 929 * 930 * @name partial 931 * @methodOf Function# 932 */ 933 curry: 'partial' 934 }); 935 936 Obj.merge(Math, { 937 /** 938 * Calculates the average (mean) of a list of numbers. 939 * 940 * @name average 941 * @methodOf Math 942 * @param {Array/Arguments} list An array or argument list of numbers. 943 * @return {Number} Their average. 944 */ 945 average: function (list) { 946 var nums; 947 if (testArray(list)) { 948 nums = list; 949 } else { 950 nums = Array.create(arguments); 951 } 952 return Math.sum(nums) / nums.length; 953 }, 954 955 /** 956 * Creates a range between two numbers. 957 * 958 * @name range 959 * @methodOf Math 960 * @param {Number} a The start of the range. 961 * @param {Number} b The end of the range. 962 * @return {Array} The range. 963 */ 964 range: function (a, b) { 965 // Switch a and b so that the second is greater 966 if (a > b) { var _b = b; b = a; a = _b; } 967 var range = []; 968 while (a <= b && range.push(a++)) {} 969 return range; 970 }, 971 972 /** 973 * Calculates the sum of a list of numbers. 974 * 975 * @name sum 976 * @methodOf Math 977 * @param {Array/Arguments} list An array or argument list of numbers. 978 * @return {Number} Their sum. 979 */ 980 sum: function (list) { 981 var nums; 982 if (testArray(list)) { 983 nums = list; 984 } else { 985 nums = Array.create(arguments); 986 } 987 return nums.reduce(function (a, b) { 988 return a + b; 989 }); 990 } 991 }); 992 993 Obj.alias(Math, { 994 /** 995 * Alias of average. 996 * 997 * @name mean 998 * @methodOf Math 999 */ 1000 average: 'mean' 1001 }); 1002 1003 Obj.merge(Number.prototype, { 1004 /** 1005 * Calls the passed function 'n' times, where 'n' is this Number. 1006 * 1007 * @name times 1008 * @methodOf Number# 1009 * @param {Function} fn The function to call. 1010 * @param {Object} ctx The execution context for the function. 1011 */ 1012 times: function (fn, ctx) { 1013 for (var i = 1; i <= this; i++) { 1014 fn.call(ctx || this, i); 1015 } 1016 } 1017 }); 1018 1019 // Make all Math methods callable from a Number 1020 Obj.merge(Number.prototype, Obj.create([ 1021 'abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 1022 'log', 'max', 'min', 'pow', 'random', 'round', 'sin', 'sqrt', 'tan' 1023 ].concat(Obj.keys(Math)).map(function (fn) { 1024 return [fn, function () { 1025 return Math[fn].apply(this, [this].concat(Array.prototype.slice.apply(arguments))); 1026 }]; 1027 }))); 1028 1029 Obj.merge(Number, { 1030 /** 1031 * Checks if the passed object is a Number. 1032 * 1033 * @name test 1034 * @methodOf Number 1035 * @param {Object} o The object to check. 1036 * @return {Boolean} true if it is a number, false if not. 1037 */ 1038 test: function (o) { 1039 return typeof o === 'number'; 1040 } 1041 }); 1042 1043 Obj.alias(Number.prototype, { 1044 /** 1045 * Alias of range. 1046 * 1047 * @name to 1048 * @methodOf Number# 1049 */ 1050 range: 'to' 1051 }); 1052 1053 Obj.merge(String.prototype, { 1054 /** 1055 * Capitalizes the string's first letter. 1056 * 1057 * @name capitalize 1058 * @methodOf String# 1059 * @return {String} The capitalized string. 1060 */ 1061 capitalize: function () { 1062 return this.substr(0, 1).toUpperCase() + this.slice(1); 1063 }, 1064 1065 /** 1066 * Checks if this string ends with the passed string. 1067 * 1068 * @name endsWith 1069 * @methodOf String# 1070 * @param {String} str The string to check. 1071 * @return {Boolean} true if it matches, false if not. 1072 */ 1073 endsWith: function (str) { 1074 return this.slice(this.length - str.length) === str; 1075 }, 1076 1077 /** 1078 * Checks if this string has non-whitespace characters. 1079 * 1080 * @name empty 1081 * @methodOf String# 1082 * @return {Boolean} true if empty, false, if not. 1083 */ 1084 empty: function () { 1085 return this.strip().length < 1; 1086 }, 1087 1088 /** 1089 * Creates a formatted string. 1090 * 1091 * @example 1092 * '{0}-{1}-{2}'.format('a', 'b', 'c'); // 'a-b-c' 1093 * '{1}-{1}-{3}'.format('a', 'b', 'c', 'd'); // 'b-b-d' 1094 * 1095 * @name format 1096 * @methodOf String# 1097 * @param {Arguments} list An argument list of ordered replacements. 1098 * @return {String} The formatted string. 1099 */ 1100 format: function () { 1101 var replacements = arguments; 1102 return this.replace(/\{(\d+)\}/g, function (match, group) { 1103 return replacements[Number(group)]; 1104 }); 1105 }, 1106 1107 /** 1108 * Concatenates this string n times. 1109 * 1110 * @name mult 1111 * @methodOf String# 1112 * @param {Number} n The number of times to concatenate the string. 1113 * @return {String} The multipled string. 1114 */ 1115 mult: function (n) { 1116 var str = ''; 1117 for (var i = 0; i < n; i++) { 1118 str += this; 1119 } 1120 return str; 1121 }, 1122 1123 /** 1124 * sprintf implementation: works for basic replacement but 1125 * precision, width, and flags are not implemented fully. 1126 * 1127 * @name sprintf 1128 * @methodOf String# 1129 * @param {Arguments} An argument list of ordered replacements. 1130 * @return {String} The formatted string. 1131 */ 1132 sprintf: function () { 1133 var args = Array.prototype.slice.call(arguments); 1134 1135 return this.replace(String.sprintfRegex, function (match, 1136 mainGroup, flag, width, precisionMatch, precision, 1137 specifier, chars) { 1138 // Special cases that use no argument 1139 if (specifier === '%') { 1140 return '%'; 1141 } 1142 if (specifier === 'n') { 1143 return chars; 1144 } 1145 if (width === '*') { 1146 width = args.shift(); 1147 } 1148 if (precisionMatch && precision === '') { 1149 precision = 0; 1150 } else if (precision === '*') { 1151 precision = args.shift(); 1152 } 1153 1154 var replace = args.shift(); 1155 1156 switch (specifier) { 1157 case 'c': 1158 case 's': 1159 replace = String(replace); 1160 break; 1161 1162 case 'd': 1163 case 'i': 1164 case 'u': 1165 replace = Number(replace).floor(); 1166 break; 1167 1168 case 'e': 1169 replace = Number(replace).toExponential(); 1170 break; 1171 1172 case 'E': 1173 replace = Number(replace).toExponential().toUpperCase(); 1174 break; 1175 1176 case 'f': 1177 replace = Number(replace); 1178 break; 1179 1180 case 'g': 1181 if (Number(replace).toExponential().length <= 1182 String(Number(replace)).length) { 1183 replace = Number(replace).toExponential(); 1184 } else { 1185 replace = Number(replace); 1186 } 1187 break; 1188 1189 case 'G': 1190 if (Number(replace).toExponential().length <= 1191 String(Number(replace)).length) { 1192 replace = Number(replace).toExponential().toUpperCase(); 1193 } else { 1194 replace = Number(replace); 1195 } 1196 break; 1197 1198 case 'o': 1199 replace = Number(replace).toString(8); 1200 break; 1201 1202 case 'x': 1203 replace = Number(replace).toString(16); 1204 break; 1205 1206 case 'X': 1207 replace = Number(replace).toString(16).toUpperCase(); 1208 break; 1209 1210 default: 1211 break; 1212 } 1213 1214 return replace; 1215 } 1216 ); 1217 }, 1218 1219 /** 1220 * Checks if this string starts with the passed string. 1221 * 1222 * @name startsWith 1223 * @methodOf String# 1224 * @param {String} str The string to check. 1225 * @return {Boolean} true if it matches, false if not. 1226 */ 1227 startsWith: function (str) { 1228 return this.slice(0, str.length) === str; 1229 }, 1230 1231 /** 1232 * Strips all whitespace characters from each side of the string. 1233 * 1234 * @name strip 1235 * @methodOf String# 1236 * @return {String} The stripped string. 1237 */ 1238 strip: function () { 1239 return this.replace(/^\s+/g, '').replace(/\s+$/, ''); 1240 }, 1241 1242 /** 1243 * Splits the string into words. 1244 * 1245 * @name words 1246 * @methodOf String# 1247 * @return {Array} An array of words. 1248 */ 1249 words: function () { 1250 return this.split(/\s+/); 1251 } 1252 }); 1253 1254 Obj.merge(String, { 1255 sprintfRegex: new RegExp( 1256 '%' + // % Prefix 1257 '(' + 1258 '([-+ #0])?' + // Flags 1259 '(\\d+|\\*)?' + // Width 1260 '(\\.(\\d+|\\*))?' + // Precision 1261 '([cdieEfgGosuxXpn%])' + // Specifier 1262 ')', 1263 'g'), 1264 1265 /** 1266 * Checks if the provided object is a string. 1267 * 1268 * @name test 1269 * @methodOf String 1270 * @param {Object} o The object to check. 1271 * @return {Boolean} true if it is a string, false if not. 1272 */ 1273 test: function (o) { 1274 return typeof o === 'string'; 1275 } 1276 }); 1277 1278 Obj.alias(String.prototype, { 1279 /** 1280 * Alias of mult. 1281 * 1282 * @name x 1283 * @methodOf String# 1284 */ 1285 mult: 'x' 1286 }); 1287 1288 })(); 1289