OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Sugar Library v1.4.1 |
| 3 * |
| 4 * Freely distributable and licensed under the MIT-style license. |
| 5 * Copyright (c) 2013 Andrew Plummer |
| 6 * http://sugarjs.com/ |
| 7 * |
| 8 * ---------------------------- */ |
| 9 (function(){ |
| 10 /*** |
| 11 * @package Core |
| 12 * @description Internal utility and common methods. |
| 13 ***/ |
| 14 |
| 15 |
| 16 // A few optimizations for Google Closure Compiler will save us a couple kb in
the release script. |
| 17 var object = Object, array = Array, regexp = RegExp, date = Date, string = Str
ing, number = Number, math = Math, Undefined; |
| 18 |
| 19 // The global context |
| 20 var globalContext = typeof global !== 'undefined' ? global : this; |
| 21 |
| 22 // Internal toString |
| 23 var internalToString = object.prototype.toString; |
| 24 |
| 25 // Internal hasOwnProperty |
| 26 var internalHasOwnProperty = object.prototype.hasOwnProperty; |
| 27 |
| 28 // defineProperty exists in IE8 but will error when trying to define a propert
y on |
| 29 // native objects. IE8 does not have defineProperies, however, so this check s
aves a try/catch block. |
| 30 var definePropertySupport = object.defineProperty && object.defineProperties; |
| 31 |
| 32 // Are regexes type function? |
| 33 var regexIsFunction = typeof regexp() === 'function'; |
| 34 |
| 35 // Do strings have no keys? |
| 36 var noKeysInStringObjects = !('0' in new string('a')); |
| 37 |
| 38 // Type check methods need a way to be accessed dynamically. |
| 39 var typeChecks = {}; |
| 40 |
| 41 // Classes that can be matched by value |
| 42 var matchedByValueReg = /^\[object Date|Array|String|Number|RegExp|Boolean|Arg
uments\]$/; |
| 43 |
| 44 // Class initializers and class helpers |
| 45 var ClassNames = 'Boolean,Number,String,Array,Date,RegExp,Function'.split(',')
; |
| 46 |
| 47 var isBoolean = buildPrimitiveClassCheck('boolean', ClassNames[0]); |
| 48 var isNumber = buildPrimitiveClassCheck('number', ClassNames[1]); |
| 49 var isString = buildPrimitiveClassCheck('string', ClassNames[2]); |
| 50 |
| 51 var isArray = buildClassCheck(ClassNames[3]); |
| 52 var isDate = buildClassCheck(ClassNames[4]); |
| 53 var isRegExp = buildClassCheck(ClassNames[5]); |
| 54 |
| 55 |
| 56 // Wanted to enhance performance here by using simply "typeof" |
| 57 // but Firefox has two major issues that make this impossible, |
| 58 // one fixed, the other not. Despite being typeof "function" |
| 59 // the objects below still report in as [object Function], so |
| 60 // we need to perform a full class check here. |
| 61 // |
| 62 // 1. Regexes can be typeof "function" in FF < 3 |
| 63 // https://bugzilla.mozilla.org/show_bug.cgi?id=61911 (fixed) |
| 64 // |
| 65 // 2. HTMLEmbedElement and HTMLObjectElement are be typeof "function" |
| 66 // https://bugzilla.mozilla.org/show_bug.cgi?id=268945 (won't fix) |
| 67 // |
| 68 var isFunction = buildClassCheck(ClassNames[6]); |
| 69 |
| 70 function isClass(obj, klass, cached) { |
| 71 var k = cached || className(obj); |
| 72 return k === '[object '+klass+']'; |
| 73 } |
| 74 |
| 75 function buildClassCheck(klass) { |
| 76 var fn = (klass === 'Array' && array.isArray) || function(obj, cached) { |
| 77 return isClass(obj, klass, cached); |
| 78 }; |
| 79 typeChecks[klass] = fn; |
| 80 return fn; |
| 81 } |
| 82 |
| 83 function buildPrimitiveClassCheck(type, klass) { |
| 84 var fn = function(obj) { |
| 85 if(isObjectType(obj)) { |
| 86 return isClass(obj, klass); |
| 87 } |
| 88 return typeof obj === type; |
| 89 } |
| 90 typeChecks[klass] = fn; |
| 91 return fn; |
| 92 } |
| 93 |
| 94 function className(obj) { |
| 95 return internalToString.call(obj); |
| 96 } |
| 97 |
| 98 function initializeClasses() { |
| 99 initializeClass(object); |
| 100 iterateOverObject(ClassNames, function(i,name) { |
| 101 initializeClass(globalContext[name]); |
| 102 }); |
| 103 } |
| 104 |
| 105 function initializeClass(klass) { |
| 106 if(klass['SugarMethods']) return; |
| 107 defineProperty(klass, 'SugarMethods', {}); |
| 108 extend(klass, false, true, { |
| 109 'extend': function(methods, override, instance) { |
| 110 extend(klass, instance !== false, override, methods); |
| 111 }, |
| 112 'sugarRestore': function() { |
| 113 return batchMethodExecute(this, klass, arguments, function(target, name,
m) { |
| 114 defineProperty(target, name, m.method); |
| 115 }); |
| 116 }, |
| 117 'sugarRevert': function() { |
| 118 return batchMethodExecute(this, klass, arguments, function(target, name,
m) { |
| 119 if(m['existed']) { |
| 120 defineProperty(target, name, m['original']); |
| 121 } else { |
| 122 delete target[name]; |
| 123 } |
| 124 }); |
| 125 } |
| 126 }); |
| 127 } |
| 128 |
| 129 // Class extending methods |
| 130 |
| 131 function extend(klass, instance, override, methods) { |
| 132 var extendee = instance ? klass.prototype : klass; |
| 133 initializeClass(klass); |
| 134 iterateOverObject(methods, function(name, extendedFn) { |
| 135 var nativeFn = extendee[name], |
| 136 existed = hasOwnProperty(extendee, name); |
| 137 if(isFunction(override) && nativeFn) { |
| 138 extendedFn = wrapNative(nativeFn, extendedFn, override); |
| 139 } |
| 140 if(override !== false || !nativeFn) { |
| 141 defineProperty(extendee, name, extendedFn); |
| 142 } |
| 143 // If the method is internal to Sugar, then |
| 144 // store a reference so it can be restored later. |
| 145 klass['SugarMethods'][name] = { |
| 146 'method': extendedFn, |
| 147 'existed': existed, |
| 148 'original': nativeFn, |
| 149 'instance': instance |
| 150 }; |
| 151 }); |
| 152 } |
| 153 |
| 154 function extendSimilar(klass, instance, override, set, fn) { |
| 155 var methods = {}; |
| 156 set = isString(set) ? set.split(',') : set; |
| 157 set.forEach(function(name, i) { |
| 158 fn(methods, name, i); |
| 159 }); |
| 160 extend(klass, instance, override, methods); |
| 161 } |
| 162 |
| 163 function batchMethodExecute(target, klass, args, fn) { |
| 164 var all = args.length === 0, methods = multiArgs(args), changed = false; |
| 165 iterateOverObject(klass['SugarMethods'], function(name, m) { |
| 166 if(all || methods.indexOf(name) !== -1) { |
| 167 changed = true; |
| 168 fn(m['instance'] ? target.prototype : target, name, m); |
| 169 } |
| 170 }); |
| 171 return changed; |
| 172 } |
| 173 |
| 174 function wrapNative(nativeFn, extendedFn, condition) { |
| 175 return function(a) { |
| 176 return condition.apply(this, arguments) ? |
| 177 extendedFn.apply(this, arguments) : |
| 178 nativeFn.apply(this, arguments); |
| 179 } |
| 180 } |
| 181 |
| 182 function defineProperty(target, name, method) { |
| 183 if(definePropertySupport) { |
| 184 object.defineProperty(target, name, { |
| 185 'value': method, |
| 186 'configurable': true, |
| 187 'enumerable': false, |
| 188 'writable': true |
| 189 }); |
| 190 } else { |
| 191 target[name] = method; |
| 192 } |
| 193 } |
| 194 |
| 195 |
| 196 // Argument helpers |
| 197 |
| 198 function multiArgs(args, fn, from) { |
| 199 var result = [], i = from || 0, len; |
| 200 for(len = args.length; i < len; i++) { |
| 201 result.push(args[i]); |
| 202 if(fn) fn.call(args, args[i], i); |
| 203 } |
| 204 return result; |
| 205 } |
| 206 |
| 207 function flattenedArgs(args, fn, from) { |
| 208 var arg = args[from || 0]; |
| 209 if(isArray(arg)) { |
| 210 args = arg; |
| 211 from = 0; |
| 212 } |
| 213 return multiArgs(args, fn, from); |
| 214 } |
| 215 |
| 216 function checkCallback(fn) { |
| 217 if(!fn || !fn.call) { |
| 218 throw new TypeError('Callback is not callable'); |
| 219 } |
| 220 } |
| 221 |
| 222 |
| 223 // General helpers |
| 224 |
| 225 function isDefined(o) { |
| 226 return o !== Undefined; |
| 227 } |
| 228 |
| 229 function isUndefined(o) { |
| 230 return o === Undefined; |
| 231 } |
| 232 |
| 233 |
| 234 // Object helpers |
| 235 |
| 236 function hasProperty(obj, prop) { |
| 237 return !isPrimitiveType(obj) && prop in obj; |
| 238 } |
| 239 |
| 240 function hasOwnProperty(obj, prop) { |
| 241 return !!obj && internalHasOwnProperty.call(obj, prop); |
| 242 } |
| 243 |
| 244 function isObjectType(obj) { |
| 245 // 1. Check for null |
| 246 // 2. Check for regexes in environments where they are "functions". |
| 247 return !!obj && (typeof obj === 'object' || (regexIsFunction && isRegExp(obj
))); |
| 248 } |
| 249 |
| 250 function isPrimitiveType(obj) { |
| 251 var type = typeof obj; |
| 252 return obj == null || type === 'string' || type === 'number' || type === 'bo
olean'; |
| 253 } |
| 254 |
| 255 function isPlainObject(obj, klass) { |
| 256 klass = klass || className(obj); |
| 257 try { |
| 258 // Not own constructor property must be Object |
| 259 // This code was borrowed from jQuery.isPlainObject |
| 260 if (obj && obj.constructor && |
| 261 !hasOwnProperty(obj, 'constructor') && |
| 262 !hasOwnProperty(obj.constructor.prototype, 'isPrototypeOf')) { |
| 263 return false; |
| 264 } |
| 265 } catch (e) { |
| 266 // IE8,9 Will throw exceptions on certain host objects. |
| 267 return false; |
| 268 } |
| 269 // === on the constructor is not safe across iframes |
| 270 // 'hasOwnProperty' ensures that the object also inherits |
| 271 // from Object, which is false for DOMElements in IE. |
| 272 return !!obj && klass === '[object Object]' && 'hasOwnProperty' in obj; |
| 273 } |
| 274 |
| 275 function iterateOverObject(obj, fn) { |
| 276 var key; |
| 277 for(key in obj) { |
| 278 if(!hasOwnProperty(obj, key)) continue; |
| 279 if(fn.call(obj, key, obj[key], obj) === false) break; |
| 280 } |
| 281 } |
| 282 |
| 283 function simpleRepeat(n, fn) { |
| 284 for(var i = 0; i < n; i++) { |
| 285 fn(i); |
| 286 } |
| 287 } |
| 288 |
| 289 function simpleMerge(target, source) { |
| 290 iterateOverObject(source, function(key) { |
| 291 target[key] = source[key]; |
| 292 }); |
| 293 return target; |
| 294 } |
| 295 |
| 296 // Make primtives types like strings into objects. |
| 297 function coercePrimitiveToObject(obj) { |
| 298 if(isPrimitiveType(obj)) { |
| 299 obj = object(obj); |
| 300 } |
| 301 if(noKeysInStringObjects && isString(obj)) { |
| 302 forceStringCoercion(obj); |
| 303 } |
| 304 return obj; |
| 305 } |
| 306 |
| 307 // Force strings to have their indexes set in |
| 308 // environments that don't do this automatically. |
| 309 function forceStringCoercion(obj) { |
| 310 var i = 0, chr; |
| 311 while(chr = obj.charAt(i)) { |
| 312 obj[i++] = chr; |
| 313 } |
| 314 } |
| 315 |
| 316 // Hash definition |
| 317 |
| 318 function Hash(obj) { |
| 319 simpleMerge(this, coercePrimitiveToObject(obj)); |
| 320 }; |
| 321 |
| 322 Hash.prototype.constructor = object; |
| 323 |
| 324 // Math helpers |
| 325 |
| 326 var abs = math.abs; |
| 327 var pow = math.pow; |
| 328 var ceil = math.ceil; |
| 329 var floor = math.floor; |
| 330 var round = math.round; |
| 331 var min = math.min; |
| 332 var max = math.max; |
| 333 |
| 334 function withPrecision(val, precision, fn) { |
| 335 var multiplier = pow(10, abs(precision || 0)); |
| 336 fn = fn || round; |
| 337 if(precision < 0) multiplier = 1 / multiplier; |
| 338 return fn(val * multiplier) / multiplier; |
| 339 } |
| 340 |
| 341 // Full width number helpers |
| 342 |
| 343 var HalfWidthZeroCode = 0x30; |
| 344 var HalfWidthNineCode = 0x39; |
| 345 var FullWidthZeroCode = 0xff10; |
| 346 var FullWidthNineCode = 0xff19; |
| 347 |
| 348 var HalfWidthPeriod = '.'; |
| 349 var FullWidthPeriod = '.'; |
| 350 var HalfWidthComma = ','; |
| 351 |
| 352 // Used here and later in the Date package. |
| 353 var FullWidthDigits = ''; |
| 354 |
| 355 var NumberNormalizeMap = {}; |
| 356 var NumberNormalizeReg; |
| 357 |
| 358 function codeIsNumeral(code) { |
| 359 return (code >= HalfWidthZeroCode && code <= HalfWidthNineCode) || |
| 360 (code >= FullWidthZeroCode && code <= FullWidthNineCode); |
| 361 } |
| 362 |
| 363 function buildNumberHelpers() { |
| 364 var digit, i; |
| 365 for(i = 0; i <= 9; i++) { |
| 366 digit = chr(i + FullWidthZeroCode); |
| 367 FullWidthDigits += digit; |
| 368 NumberNormalizeMap[digit] = chr(i + HalfWidthZeroCode); |
| 369 } |
| 370 NumberNormalizeMap[HalfWidthComma] = ''; |
| 371 NumberNormalizeMap[FullWidthPeriod] = HalfWidthPeriod; |
| 372 // Mapping this to itself to easily be able to easily |
| 373 // capture it in stringToNumber to detect decimals later. |
| 374 NumberNormalizeMap[HalfWidthPeriod] = HalfWidthPeriod; |
| 375 NumberNormalizeReg = regexp('[' + FullWidthDigits + FullWidthPeriod + HalfWi
dthComma + HalfWidthPeriod + ']', 'g'); |
| 376 } |
| 377 |
| 378 // String helpers |
| 379 |
| 380 function chr(num) { |
| 381 return string.fromCharCode(num); |
| 382 } |
| 383 |
| 384 // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in th
e Space, Separator category. |
| 385 function getTrimmableCharacters() { |
| 386 return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u
2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u30
00\uFEFF'; |
| 387 } |
| 388 |
| 389 function repeatString(str, num) { |
| 390 var result = '', str = str.toString(); |
| 391 while (num > 0) { |
| 392 if (num & 1) { |
| 393 result += str; |
| 394 } |
| 395 if (num >>= 1) { |
| 396 str += str; |
| 397 } |
| 398 } |
| 399 return result; |
| 400 } |
| 401 |
| 402 // Returns taking into account full-width characters, commas, and decimals. |
| 403 function stringToNumber(str, base) { |
| 404 var sanitized, isDecimal; |
| 405 sanitized = str.replace(NumberNormalizeReg, function(chr) { |
| 406 var replacement = NumberNormalizeMap[chr]; |
| 407 if(replacement === HalfWidthPeriod) { |
| 408 isDecimal = true; |
| 409 } |
| 410 return replacement; |
| 411 }); |
| 412 return isDecimal ? parseFloat(sanitized) : parseInt(sanitized, base || 10); |
| 413 } |
| 414 |
| 415 |
| 416 // Used by Number and Date |
| 417 |
| 418 function padNumber(num, place, sign, base) { |
| 419 var str = abs(num).toString(base || 10); |
| 420 str = repeatString('0', place - str.replace(/\.\d+/, '').length) + str; |
| 421 if(sign || num < 0) { |
| 422 str = (num < 0 ? '-' : '+') + str; |
| 423 } |
| 424 return str; |
| 425 } |
| 426 |
| 427 function getOrdinalizedSuffix(num) { |
| 428 if(num >= 11 && num <= 13) { |
| 429 return 'th'; |
| 430 } else { |
| 431 switch(num % 10) { |
| 432 case 1: return 'st'; |
| 433 case 2: return 'nd'; |
| 434 case 3: return 'rd'; |
| 435 default: return 'th'; |
| 436 } |
| 437 } |
| 438 } |
| 439 |
| 440 |
| 441 // RegExp helpers |
| 442 |
| 443 function getRegExpFlags(reg, add) { |
| 444 var flags = ''; |
| 445 add = add || ''; |
| 446 function checkFlag(prop, flag) { |
| 447 if(prop || add.indexOf(flag) > -1) { |
| 448 flags += flag; |
| 449 } |
| 450 } |
| 451 checkFlag(reg.multiline, 'm'); |
| 452 checkFlag(reg.ignoreCase, 'i'); |
| 453 checkFlag(reg.global, 'g'); |
| 454 checkFlag(reg.sticky, 'y'); |
| 455 return flags; |
| 456 } |
| 457 |
| 458 function escapeRegExp(str) { |
| 459 if(!isString(str)) str = string(str); |
| 460 return str.replace(/([\\/\'*+?|()\[\]{}.^$])/g,'\\$1'); |
| 461 } |
| 462 |
| 463 |
| 464 // Date helpers |
| 465 |
| 466 function callDateGet(d, method) { |
| 467 return d['get' + (d._utc ? 'UTC' : '') + method](); |
| 468 } |
| 469 |
| 470 function callDateSet(d, method, value) { |
| 471 return d['set' + (d._utc && method != 'ISOWeek' ? 'UTC' : '') + method](valu
e); |
| 472 } |
| 473 |
| 474 // Used by Array#unique and Object.equal |
| 475 |
| 476 function stringify(thing, stack) { |
| 477 var type = typeof thing, |
| 478 thingIsObject, |
| 479 thingIsArray, |
| 480 klass, value, |
| 481 arr, key, i, len; |
| 482 |
| 483 // Return quickly if string to save cycles |
| 484 if(type === 'string') return thing; |
| 485 |
| 486 klass = internalToString.call(thing) |
| 487 thingIsObject = isPlainObject(thing, klass); |
| 488 thingIsArray = isArray(thing, klass); |
| 489 |
| 490 if(thing != null && thingIsObject || thingIsArray) { |
| 491 // This method for checking for cyclic structures was egregiously stolen f
rom |
| 492 // the ingenious method by @kitcambridge from the Underscore script: |
| 493 // https://github.com/documentcloud/underscore/issues/240 |
| 494 if(!stack) stack = []; |
| 495 // Allowing a step into the structure before triggering this |
| 496 // script to save cycles on standard JSON structures and also to |
| 497 // try as hard as possible to catch basic properties that may have |
| 498 // been modified. |
| 499 if(stack.length > 1) { |
| 500 i = stack.length; |
| 501 while (i--) { |
| 502 if (stack[i] === thing) { |
| 503 return 'CYC'; |
| 504 } |
| 505 } |
| 506 } |
| 507 stack.push(thing); |
| 508 value = thing.valueOf() + string(thing.constructor); |
| 509 arr = thingIsArray ? thing : object.keys(thing).sort(); |
| 510 for(i = 0, len = arr.length; i < len; i++) { |
| 511 key = thingIsArray ? i : arr[i]; |
| 512 value += key + stringify(thing[key], stack); |
| 513 } |
| 514 stack.pop(); |
| 515 } else if(1 / thing === -Infinity) { |
| 516 value = '-0'; |
| 517 } else { |
| 518 value = string(thing && thing.valueOf ? thing.valueOf() : thing); |
| 519 } |
| 520 return type + klass + value; |
| 521 } |
| 522 |
| 523 function isEqual(a, b) { |
| 524 if(a === b) { |
| 525 // Return quickly up front when matching by reference, |
| 526 // but be careful about 0 !== -0. |
| 527 return a !== 0 || 1 / a === 1 / b; |
| 528 } else if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) { |
| 529 return stringify(a) === stringify(b); |
| 530 } |
| 531 return false; |
| 532 } |
| 533 |
| 534 function objectIsMatchedByValue(obj) { |
| 535 // Only known objects are matched by value. This is notably excluding functi
ons, DOM Elements, and instances of |
| 536 // user-created classes. The latter can arguably be matched by value, but di
stinguishing between these and |
| 537 // host objects -- which should never be compared by value -- is very tricky
so not dealing with it here. |
| 538 var klass = className(obj); |
| 539 return matchedByValueReg.test(klass) || isPlainObject(obj, klass); |
| 540 } |
| 541 |
| 542 |
| 543 // Used by Array#at and String#at |
| 544 |
| 545 function getEntriesForIndexes(obj, args, isString) { |
| 546 var result, |
| 547 length = obj.length, |
| 548 argsLen = args.length, |
| 549 overshoot = args[argsLen - 1] !== false, |
| 550 multiple = argsLen > (overshoot ? 1 : 2); |
| 551 if(!multiple) { |
| 552 return entryAtIndex(obj, length, args[0], overshoot, isString); |
| 553 } |
| 554 result = []; |
| 555 multiArgs(args, function(index) { |
| 556 if(isBoolean(index)) return false; |
| 557 result.push(entryAtIndex(obj, length, index, overshoot, isString)); |
| 558 }); |
| 559 return result; |
| 560 } |
| 561 |
| 562 function entryAtIndex(obj, length, index, overshoot, isString) { |
| 563 if(overshoot) { |
| 564 index = index % length; |
| 565 if(index < 0) index = length + index; |
| 566 } |
| 567 return isString ? obj.charAt(index) : obj[index]; |
| 568 } |
| 569 |
| 570 |
| 571 // Object class methods implemented as instance methods |
| 572 |
| 573 function buildObjectInstanceMethods(set, target) { |
| 574 extendSimilar(target, true, false, set, function(methods, name) { |
| 575 methods[name + (name === 'equal' ? 's' : '')] = function() { |
| 576 return object[name].apply(null, [this].concat(multiArgs(arguments))); |
| 577 } |
| 578 }); |
| 579 } |
| 580 |
| 581 initializeClasses(); |
| 582 buildNumberHelpers(); |
| 583 |
| 584 |
| 585 /*** |
| 586 * @package ES5 |
| 587 * @description Shim methods that provide ES5 compatible functionality. This p
ackage can be excluded if you do not require legacy browser support (IE8 and bel
ow). |
| 588 * |
| 589 ***/ |
| 590 |
| 591 |
| 592 /*** |
| 593 * Object module |
| 594 * |
| 595 ***/ |
| 596 |
| 597 extend(object, false, false, { |
| 598 |
| 599 'keys': function(obj) { |
| 600 var keys = []; |
| 601 if(!isObjectType(obj) && !isRegExp(obj) && !isFunction(obj)) { |
| 602 throw new TypeError('Object required'); |
| 603 } |
| 604 iterateOverObject(obj, function(key, value) { |
| 605 keys.push(key); |
| 606 }); |
| 607 return keys; |
| 608 } |
| 609 |
| 610 }); |
| 611 |
| 612 |
| 613 /*** |
| 614 * Array module |
| 615 * |
| 616 ***/ |
| 617 |
| 618 // ECMA5 methods |
| 619 |
| 620 function arrayIndexOf(arr, search, fromIndex, increment) { |
| 621 var length = arr.length, |
| 622 fromRight = increment == -1, |
| 623 start = fromRight ? length - 1 : 0, |
| 624 index = toIntegerWithDefault(fromIndex, start); |
| 625 if(index < 0) { |
| 626 index = length + index; |
| 627 } |
| 628 if((!fromRight && index < 0) || (fromRight && index >= length)) { |
| 629 index = start; |
| 630 } |
| 631 while((fromRight && index >= 0) || (!fromRight && index < length)) { |
| 632 if(arr[index] === search) { |
| 633 return index; |
| 634 } |
| 635 index += increment; |
| 636 } |
| 637 return -1; |
| 638 } |
| 639 |
| 640 function arrayReduce(arr, fn, initialValue, fromRight) { |
| 641 var length = arr.length, count = 0, defined = isDefined(initialValue), resul
t, index; |
| 642 checkCallback(fn); |
| 643 if(length == 0 && !defined) { |
| 644 throw new TypeError('Reduce called on empty array with no initial value'); |
| 645 } else if(defined) { |
| 646 result = initialValue; |
| 647 } else { |
| 648 result = arr[fromRight ? length - 1 : count]; |
| 649 count++; |
| 650 } |
| 651 while(count < length) { |
| 652 index = fromRight ? length - count - 1 : count; |
| 653 if(index in arr) { |
| 654 result = fn(result, arr[index], index, arr); |
| 655 } |
| 656 count++; |
| 657 } |
| 658 return result; |
| 659 } |
| 660 |
| 661 function toIntegerWithDefault(i, d) { |
| 662 if(isNaN(i)) { |
| 663 return d; |
| 664 } else { |
| 665 return parseInt(i >> 0); |
| 666 } |
| 667 } |
| 668 |
| 669 function checkFirstArgumentExists(args) { |
| 670 if(args.length === 0) { |
| 671 throw new TypeError('First argument must be defined'); |
| 672 } |
| 673 } |
| 674 |
| 675 |
| 676 |
| 677 |
| 678 extend(array, false, false, { |
| 679 |
| 680 /*** |
| 681 * |
| 682 * @method Array.isArray(<obj>) |
| 683 * @returns Boolean |
| 684 * @short Returns true if <obj> is an Array. |
| 685 * @extra This method is provided for browsers that don't support it interna
lly. |
| 686 * @example |
| 687 * |
| 688 * Array.isArray(3) -> false |
| 689 * Array.isArray(true) -> false |
| 690 * Array.isArray('wasabi') -> false |
| 691 * Array.isArray([1,2,3]) -> true |
| 692 * |
| 693 ***/ |
| 694 'isArray': function(obj) { |
| 695 return isArray(obj); |
| 696 } |
| 697 |
| 698 }); |
| 699 |
| 700 |
| 701 extend(array, true, false, { |
| 702 |
| 703 /*** |
| 704 * @method every(<f>, [scope]) |
| 705 * @returns Boolean |
| 706 * @short Returns true if all elements in the array match <f>. |
| 707 * @extra [scope] is the %this% object. %all% is provided an alias. In addit
ion to providing this method for browsers that don't support it natively, this m
ethod also implements @array_matching. |
| 708 * @example |
| 709 * |
| 710 + ['a','a','a'].every(function(n) { |
| 711 * return n == 'a'; |
| 712 * }); |
| 713 * ['a','a','a'].every('a') -> true |
| 714 * [{a:2},{a:2}].every({a:2}) -> true |
| 715 ***/ |
| 716 'every': function(fn, scope) { |
| 717 var length = this.length, index = 0; |
| 718 checkFirstArgumentExists(arguments); |
| 719 while(index < length) { |
| 720 if(index in this && !fn.call(scope, this[index], index, this)) { |
| 721 return false; |
| 722 } |
| 723 index++; |
| 724 } |
| 725 return true; |
| 726 }, |
| 727 |
| 728 /*** |
| 729 * @method some(<f>, [scope]) |
| 730 * @returns Boolean |
| 731 * @short Returns true if any element in the array matches <f>. |
| 732 * @extra [scope] is the %this% object. %any% is provided as an alias. In ad
dition to providing this method for browsers that don't support it natively, thi
s method also implements @array_matching. |
| 733 * @example |
| 734 * |
| 735 + ['a','b','c'].some(function(n) { |
| 736 * return n == 'a'; |
| 737 * }); |
| 738 + ['a','b','c'].some(function(n) { |
| 739 * return n == 'd'; |
| 740 * }); |
| 741 * ['a','b','c'].some('a') -> true |
| 742 * [{a:2},{b:5}].some({a:2}) -> true |
| 743 ***/ |
| 744 'some': function(fn, scope) { |
| 745 var length = this.length, index = 0; |
| 746 checkFirstArgumentExists(arguments); |
| 747 while(index < length) { |
| 748 if(index in this && fn.call(scope, this[index], index, this)) { |
| 749 return true; |
| 750 } |
| 751 index++; |
| 752 } |
| 753 return false; |
| 754 }, |
| 755 |
| 756 /*** |
| 757 * @method map(<map>, [scope]) |
| 758 * @returns Array |
| 759 * @short Maps the array to another array containing the values that are the
result of calling <map> on each element. |
| 760 * @extra [scope] is the %this% object. When <map> is a function, it receive
s three arguments: the current element, the current index, and a reference to th
e 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 short
cut for a function that gets that property (or invokes a function) on each eleme
nt. |
| 761 * @example |
| 762 * |
| 763 * [1,2,3].map(function(n) { |
| 764 * return n * 3; |
| 765 * }); -> [3,6,9] |
| 766 * ['one','two','three'].map(function(n) { |
| 767 * return n.length; |
| 768 * }); -> [3,3,5] |
| 769 * ['one','two','three'].map('length') -> [3,3,5] |
| 770 * |
| 771 ***/ |
| 772 'map': function(fn, scope) { |
| 773 var scope = arguments[1], length = this.length, index = 0, result = new Ar
ray(length); |
| 774 checkFirstArgumentExists(arguments); |
| 775 while(index < length) { |
| 776 if(index in this) { |
| 777 result[index] = fn.call(scope, this[index], index, this); |
| 778 } |
| 779 index++; |
| 780 } |
| 781 return result; |
| 782 }, |
| 783 |
| 784 /*** |
| 785 * @method filter(<f>, [scope]) |
| 786 * @returns Array |
| 787 * @short Returns any elements in the array that match <f>. |
| 788 * @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. |
| 789 * @example |
| 790 * |
| 791 + [1,2,3].filter(function(n) { |
| 792 * return n > 1; |
| 793 * }); |
| 794 * [1,2,2,4].filter(2) -> 2 |
| 795 * |
| 796 ***/ |
| 797 'filter': function(fn) { |
| 798 var scope = arguments[1]; |
| 799 var length = this.length, index = 0, result = []; |
| 800 checkFirstArgumentExists(arguments); |
| 801 while(index < length) { |
| 802 if(index in this && fn.call(scope, this[index], index, this)) { |
| 803 result.push(this[index]); |
| 804 } |
| 805 index++; |
| 806 } |
| 807 return result; |
| 808 }, |
| 809 |
| 810 /*** |
| 811 * @method indexOf(<search>, [fromIndex]) |
| 812 * @returns Number |
| 813 * @short Searches the array and returns the first index where <search> occu
rs, or -1 if the element is not found. |
| 814 * @extra [fromIndex] is the index from which to begin the search. This meth
od 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% met
hod instead. |
| 815 * @example |
| 816 * |
| 817 * [1,2,3].indexOf(3) -> 1 |
| 818 * [1,2,3].indexOf(7) -> -1 |
| 819 * |
| 820 ***/ |
| 821 'indexOf': function(search) { |
| 822 var fromIndex = arguments[1]; |
| 823 if(isString(this)) return this.indexOf(search, fromIndex); |
| 824 return arrayIndexOf(this, search, fromIndex, 1); |
| 825 }, |
| 826 |
| 827 /*** |
| 828 * @method lastIndexOf(<search>, [fromIndex]) |
| 829 * @returns Number |
| 830 * @short Searches the array and returns the last index where <search> occur
s, or -1 if the element is not found. |
| 831 * @extra [fromIndex] is the index from which to begin the search. This meth
od performs a simple strict equality comparison on <search>. |
| 832 * @example |
| 833 * |
| 834 * [1,2,1].lastIndexOf(1) -> 2 |
| 835 * [1,2,1].lastIndexOf(7) -> -1 |
| 836 * |
| 837 ***/ |
| 838 'lastIndexOf': function(search) { |
| 839 var fromIndex = arguments[1]; |
| 840 if(isString(this)) return this.lastIndexOf(search, fromIndex); |
| 841 return arrayIndexOf(this, search, fromIndex, -1); |
| 842 }, |
| 843 |
| 844 /*** |
| 845 * @method forEach([fn], [scope]) |
| 846 * @returns Nothing |
| 847 * @short Iterates over the array, calling [fn] on each loop. |
| 848 * @extra This method is only provided for those browsers that do not suppor
t it natively. [scope] becomes the %this% object. |
| 849 * @example |
| 850 * |
| 851 * ['a','b','c'].forEach(function(a) { |
| 852 * // Called 3 times: 'a','b','c' |
| 853 * }); |
| 854 * |
| 855 ***/ |
| 856 'forEach': function(fn) { |
| 857 var length = this.length, index = 0, scope = arguments[1]; |
| 858 checkCallback(fn); |
| 859 while(index < length) { |
| 860 if(index in this) { |
| 861 fn.call(scope, this[index], index, this); |
| 862 } |
| 863 index++; |
| 864 } |
| 865 }, |
| 866 |
| 867 /*** |
| 868 * @method reduce(<fn>, [init]) |
| 869 * @returns Mixed |
| 870 * @short Reduces the array to a single result. |
| 871 * @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 el
ement in the array. From that point, the result of the callback will then be use
d as the first argument of the next iteration. This is often refered to as "accu
mulation", 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 th
is case, on the first iteration only, the first argument will be the first eleme
nt of the array, and the second argument will be the second. After that callback
s work as normal, using the result of the previous callback as the first argumen
t of the next. This method is only provided for those browsers that do not suppo
rt it natively. |
| 872 * |
| 873 * @example |
| 874 * |
| 875 + [1,2,3,4].reduce(function(a, b) { |
| 876 * return a - b; |
| 877 * }); |
| 878 + [1,2,3,4].reduce(function(a, b) { |
| 879 * return a - b; |
| 880 * }, 100); |
| 881 * |
| 882 ***/ |
| 883 'reduce': function(fn) { |
| 884 return arrayReduce(this, fn, arguments[1]); |
| 885 }, |
| 886 |
| 887 /*** |
| 888 * @method reduceRight([fn], [init]) |
| 889 * @returns Mixed |
| 890 * @short Identical to %Array#reduce%, but operates on the elements in rever
se order. |
| 891 * @extra This method is only provided for those browsers that do not suppor
t it natively. |
| 892 * |
| 893 * |
| 894 * |
| 895 * |
| 896 * @example |
| 897 * |
| 898 + [1,2,3,4].reduceRight(function(a, b) { |
| 899 * return a - b; |
| 900 * }); |
| 901 * |
| 902 ***/ |
| 903 'reduceRight': function(fn) { |
| 904 return arrayReduce(this, fn, arguments[1], true); |
| 905 } |
| 906 |
| 907 |
| 908 }); |
| 909 |
| 910 |
| 911 |
| 912 |
| 913 /*** |
| 914 * String module |
| 915 * |
| 916 ***/ |
| 917 |
| 918 |
| 919 function buildTrim() { |
| 920 var support = getTrimmableCharacters().match(/^\s+$/); |
| 921 try { string.prototype.trim.call([1]); } catch(e) { support = false; } |
| 922 extend(string, true, !support, { |
| 923 |
| 924 /*** |
| 925 * @method trim[Side]() |
| 926 * @returns String |
| 927 * @short Removes leading and/or trailing whitespace from the string. |
| 928 * @extra Whitespace is defined as line breaks, tabs, and any character in
the "Space, Separator" Unicode category, conforming to the the ES5 spec. The st
andard %trim% method is only added when not fully supported natively. |
| 929 * |
| 930 * @set |
| 931 * trim |
| 932 * trimLeft |
| 933 * trimRight |
| 934 * |
| 935 * @example |
| 936 * |
| 937 * ' wasabi '.trim() -> 'wasabi' |
| 938 * ' wasabi '.trimLeft() -> 'wasabi ' |
| 939 * ' wasabi '.trimRight() -> ' wasabi' |
| 940 * |
| 941 ***/ |
| 942 'trim': function() { |
| 943 return this.toString().trimLeft().trimRight(); |
| 944 }, |
| 945 |
| 946 'trimLeft': function() { |
| 947 return this.replace(regexp('^['+getTrimmableCharacters()+']+'), ''); |
| 948 }, |
| 949 |
| 950 'trimRight': function() { |
| 951 return this.replace(regexp('['+getTrimmableCharacters()+']+$'), ''); |
| 952 } |
| 953 }); |
| 954 } |
| 955 |
| 956 |
| 957 |
| 958 /*** |
| 959 * Function module |
| 960 * |
| 961 ***/ |
| 962 |
| 963 |
| 964 extend(Function, true, false, { |
| 965 |
| 966 /*** |
| 967 * @method bind(<scope>, [arg1], ...) |
| 968 * @returns Function |
| 969 * @short Binds <scope> as the %this% object for the function when it is cal
led. Also allows currying an unlimited number of parameters. |
| 970 * @extra "currying" means setting parameters ([arg1], [arg2], etc.) ahead o
f time so that they are passed when the function is called later. If you pass ad
ditional parameters when the function is actually called, they will be added wil
l be added to the end of the curried parameters. This method is provided for bro
wsers that don't support it internally. |
| 971 * @example |
| 972 * |
| 973 + (function() { |
| 974 * return this; |
| 975 * }).bind('woof')(); -> returns 'woof'; function is bound with 'woof' as
the this object. |
| 976 * (function(a) { |
| 977 * return a; |
| 978 * }).bind(1, 2)(); -> returns 2; function is bound with 1 as the this o
bject and 2 curried as the first parameter |
| 979 * (function(a, b) { |
| 980 * return a + b; |
| 981 * }).bind(1, 2)(3); -> returns 5; function is bound with 1 as the this o
bject, 2 curied as the first parameter and 3 passed as the second when calling t
he function |
| 982 * |
| 983 ***/ |
| 984 'bind': function(scope) { |
| 985 var fn = this, args = multiArgs(arguments, null, 1), bound; |
| 986 if(!isFunction(this)) { |
| 987 throw new TypeError('Function.prototype.bind called on a non-function'); |
| 988 } |
| 989 bound = function() { |
| 990 return fn.apply(fn.prototype && this instanceof fn ? this : scope, args.
concat(multiArgs(arguments))); |
| 991 } |
| 992 bound.prototype = this.prototype; |
| 993 return bound; |
| 994 } |
| 995 |
| 996 }); |
| 997 |
| 998 /*** |
| 999 * Date module |
| 1000 * |
| 1001 ***/ |
| 1002 |
| 1003 /*** |
| 1004 * @method toISOString() |
| 1005 * @returns String |
| 1006 * @short Formats the string to ISO8601 format. |
| 1007 * @extra This will always format as UTC time. Provided for browsers that do n
ot support this method. |
| 1008 * @example |
| 1009 * |
| 1010 * Date.create().toISOString() -> ex. 2011-07-05 12:24:55.528Z |
| 1011 * |
| 1012 *** |
| 1013 * @method toJSON() |
| 1014 * @returns String |
| 1015 * @short Returns a JSON representation of the date. |
| 1016 * @extra This is effectively an alias for %toISOString%. Will always return t
he date in UTC time. Provided for browsers that do not support this method. |
| 1017 * @example |
| 1018 * |
| 1019 * Date.create().toJSON() -> ex. 2011-07-05 12:24:55.528Z |
| 1020 * |
| 1021 ***/ |
| 1022 |
| 1023 extend(date, false, false, { |
| 1024 |
| 1025 /*** |
| 1026 * @method Date.now() |
| 1027 * @returns String |
| 1028 * @short Returns the number of milliseconds since January 1st, 1970 00:00:0
0 (UTC time). |
| 1029 * @extra Provided for browsers that do not support this method. |
| 1030 * @example |
| 1031 * |
| 1032 * Date.now() -> ex. 1311938296231 |
| 1033 * |
| 1034 ***/ |
| 1035 'now': function() { |
| 1036 return new date().getTime(); |
| 1037 } |
| 1038 |
| 1039 }); |
| 1040 |
| 1041 function buildISOString() { |
| 1042 var d = new date(date.UTC(1999, 11, 31)), target = '1999-12-31T00:00:00.000Z
'; |
| 1043 var support = d.toISOString && d.toISOString() === target; |
| 1044 extendSimilar(date, true, !support, 'toISOString,toJSON', function(methods,
name) { |
| 1045 methods[name] = function() { |
| 1046 return padNumber(this.getUTCFullYear(), 4) + '-' + |
| 1047 padNumber(this.getUTCMonth() + 1, 2) + '-' + |
| 1048 padNumber(this.getUTCDate(), 2) + 'T' + |
| 1049 padNumber(this.getUTCHours(), 2) + ':' + |
| 1050 padNumber(this.getUTCMinutes(), 2) + ':' + |
| 1051 padNumber(this.getUTCSeconds(), 2) + '.' + |
| 1052 padNumber(this.getUTCMilliseconds(), 3) + 'Z'; |
| 1053 } |
| 1054 }); |
| 1055 } |
| 1056 |
| 1057 // Initialize |
| 1058 buildTrim(); |
| 1059 buildISOString(); |
| 1060 |
| 1061 |
| 1062 /*** |
| 1063 * @package Array |
| 1064 * @dependency core |
| 1065 * @description Array manipulation and traversal, "fuzzy matching" against ele
ments, alphanumeric sorting and collation, enumerable methods on Object. |
| 1066 * |
| 1067 ***/ |
| 1068 |
| 1069 |
| 1070 function regexMatcher(reg) { |
| 1071 reg = regexp(reg); |
| 1072 return function (el) { |
| 1073 return reg.test(el); |
| 1074 } |
| 1075 } |
| 1076 |
| 1077 function dateMatcher(d) { |
| 1078 var ms = d.getTime(); |
| 1079 return function (el) { |
| 1080 return !!(el && el.getTime) && el.getTime() === ms; |
| 1081 } |
| 1082 } |
| 1083 |
| 1084 function functionMatcher(fn) { |
| 1085 return function (el, i, arr) { |
| 1086 // Return true up front if match by reference |
| 1087 return el === fn || fn.call(this, el, i, arr); |
| 1088 } |
| 1089 } |
| 1090 |
| 1091 function invertedArgsFunctionMatcher(fn) { |
| 1092 return function (value, key, obj) { |
| 1093 // Return true up front if match by reference |
| 1094 return value === fn || fn.call(obj, key, value, obj); |
| 1095 } |
| 1096 } |
| 1097 |
| 1098 function fuzzyMatcher(obj, isObject) { |
| 1099 var matchers = {}; |
| 1100 return function (el, i, arr) { |
| 1101 var key; |
| 1102 if(!isObjectType(el)) { |
| 1103 return false; |
| 1104 } |
| 1105 for(key in obj) { |
| 1106 matchers[key] = matchers[key] || getMatcher(obj[key], isObject); |
| 1107 if(matchers[key].call(arr, el[key], i, arr) === false) { |
| 1108 return false; |
| 1109 } |
| 1110 } |
| 1111 return true; |
| 1112 } |
| 1113 } |
| 1114 |
| 1115 function defaultMatcher(f) { |
| 1116 return function (el) { |
| 1117 return el === f || isEqual(el, f); |
| 1118 } |
| 1119 } |
| 1120 |
| 1121 function getMatcher(f, isObject) { |
| 1122 if(isPrimitiveType(f)) { |
| 1123 // Do nothing and fall through to the |
| 1124 // default matcher below. |
| 1125 } else if(isRegExp(f)) { |
| 1126 // Match against a regexp |
| 1127 return regexMatcher(f); |
| 1128 } else if(isDate(f)) { |
| 1129 // Match against a date. isEqual below should also |
| 1130 // catch this but matching directly up front for speed. |
| 1131 return dateMatcher(f); |
| 1132 } else if(isFunction(f)) { |
| 1133 // Match against a filtering function |
| 1134 if(isObject) { |
| 1135 return invertedArgsFunctionMatcher(f); |
| 1136 } else { |
| 1137 return functionMatcher(f); |
| 1138 } |
| 1139 } else if(isPlainObject(f)) { |
| 1140 // Match against a fuzzy hash or array. |
| 1141 return fuzzyMatcher(f, isObject); |
| 1142 } |
| 1143 // Default is standard isEqual |
| 1144 return defaultMatcher(f); |
| 1145 } |
| 1146 |
| 1147 function transformArgument(el, map, context, mapArgs) { |
| 1148 if(!map) { |
| 1149 return el; |
| 1150 } else if(map.apply) { |
| 1151 return map.apply(context, mapArgs || []); |
| 1152 } else if(isFunction(el[map])) { |
| 1153 return el[map].call(el); |
| 1154 } else { |
| 1155 return el[map]; |
| 1156 } |
| 1157 } |
| 1158 |
| 1159 // Basic array internal methods |
| 1160 |
| 1161 function arrayEach(arr, fn, startIndex, loop) { |
| 1162 var index, i, length = +arr.length; |
| 1163 if(startIndex < 0) startIndex = arr.length + startIndex; |
| 1164 i = isNaN(startIndex) ? 0 : startIndex; |
| 1165 if(loop === true) { |
| 1166 length += i; |
| 1167 } |
| 1168 while(i < length) { |
| 1169 index = i % arr.length; |
| 1170 if(!(index in arr)) { |
| 1171 return iterateOverSparseArray(arr, fn, i, loop); |
| 1172 } else if(fn.call(arr, arr[index], index, arr) === false) { |
| 1173 break; |
| 1174 } |
| 1175 i++; |
| 1176 } |
| 1177 } |
| 1178 |
| 1179 function iterateOverSparseArray(arr, fn, fromIndex, loop) { |
| 1180 var indexes = [], i; |
| 1181 for(i in arr) { |
| 1182 if(isArrayIndex(arr, i) && i >= fromIndex) { |
| 1183 indexes.push(parseInt(i)); |
| 1184 } |
| 1185 } |
| 1186 indexes.sort().each(function(index) { |
| 1187 return fn.call(arr, arr[index], index, arr); |
| 1188 }); |
| 1189 return arr; |
| 1190 } |
| 1191 |
| 1192 function isArrayIndex(arr, i) { |
| 1193 return i in arr && toUInt32(i) == i && i != 0xffffffff; |
| 1194 } |
| 1195 |
| 1196 function toUInt32(i) { |
| 1197 return i >>> 0; |
| 1198 } |
| 1199 |
| 1200 function arrayFind(arr, f, startIndex, loop, returnIndex, context) { |
| 1201 var result, index, matcher; |
| 1202 if(arr.length > 0) { |
| 1203 matcher = getMatcher(f); |
| 1204 arrayEach(arr, function(el, i) { |
| 1205 if(matcher.call(context, el, i, arr)) { |
| 1206 result = el; |
| 1207 index = i; |
| 1208 return false; |
| 1209 } |
| 1210 }, startIndex, loop); |
| 1211 } |
| 1212 return returnIndex ? index : result; |
| 1213 } |
| 1214 |
| 1215 function arrayUnique(arr, map) { |
| 1216 var result = [], o = {}, transformed; |
| 1217 arrayEach(arr, function(el, i) { |
| 1218 transformed = map ? transformArgument(el, map, arr, [el, i, arr]) : el; |
| 1219 if(!checkForElementInHashAndSet(o, transformed)) { |
| 1220 result.push(el); |
| 1221 } |
| 1222 }) |
| 1223 return result; |
| 1224 } |
| 1225 |
| 1226 function arrayIntersect(arr1, arr2, subtract) { |
| 1227 var result = [], o = {}; |
| 1228 arr2.each(function(el) { |
| 1229 checkForElementInHashAndSet(o, el); |
| 1230 }); |
| 1231 arr1.each(function(el) { |
| 1232 var stringified = stringify(el), |
| 1233 isReference = !objectIsMatchedByValue(el); |
| 1234 // Add the result to the array if: |
| 1235 // 1. We're subtracting intersections or it doesn't already exist in the r
esult and |
| 1236 // 2. It exists in the compared array and we're adding, or it doesn't exis
t and we're removing. |
| 1237 if(elementExistsInHash(o, stringified, el, isReference) !== subtract) { |
| 1238 discardElementFromHash(o, stringified, el, isReference); |
| 1239 result.push(el); |
| 1240 } |
| 1241 }); |
| 1242 return result; |
| 1243 } |
| 1244 |
| 1245 function arrayFlatten(arr, level, current) { |
| 1246 level = level || Infinity; |
| 1247 current = current || 0; |
| 1248 var result = []; |
| 1249 arrayEach(arr, function(el) { |
| 1250 if(isArray(el) && current < level) { |
| 1251 result = result.concat(arrayFlatten(el, level, current + 1)); |
| 1252 } else { |
| 1253 result.push(el); |
| 1254 } |
| 1255 }); |
| 1256 return result; |
| 1257 } |
| 1258 |
| 1259 function isArrayLike(obj) { |
| 1260 return hasProperty(obj, 'length') && !isString(obj) && !isPlainObject(obj); |
| 1261 } |
| 1262 |
| 1263 function isArgumentsObject(obj) { |
| 1264 // .callee exists on Arguments objects in < IE8 |
| 1265 return hasProperty(obj, 'length') && (className(obj) === '[object Arguments]
' || !!obj.callee); |
| 1266 } |
| 1267 |
| 1268 function flatArguments(args) { |
| 1269 var result = []; |
| 1270 multiArgs(args, function(arg) { |
| 1271 result = result.concat(arg); |
| 1272 }); |
| 1273 return result; |
| 1274 } |
| 1275 |
| 1276 function elementExistsInHash(hash, key, element, isReference) { |
| 1277 var exists = key in hash; |
| 1278 if(isReference) { |
| 1279 if(!hash[key]) { |
| 1280 hash[key] = []; |
| 1281 } |
| 1282 exists = hash[key].indexOf(element) !== -1; |
| 1283 } |
| 1284 return exists; |
| 1285 } |
| 1286 |
| 1287 function checkForElementInHashAndSet(hash, element) { |
| 1288 var stringified = stringify(element), |
| 1289 isReference = !objectIsMatchedByValue(element), |
| 1290 exists = elementExistsInHash(hash, stringified, element, isReferenc
e); |
| 1291 if(isReference) { |
| 1292 hash[stringified].push(element); |
| 1293 } else { |
| 1294 hash[stringified] = element; |
| 1295 } |
| 1296 return exists; |
| 1297 } |
| 1298 |
| 1299 function discardElementFromHash(hash, key, element, isReference) { |
| 1300 var arr, i = 0; |
| 1301 if(isReference) { |
| 1302 arr = hash[key]; |
| 1303 while(i < arr.length) { |
| 1304 if(arr[i] === element) { |
| 1305 arr.splice(i, 1); |
| 1306 } else { |
| 1307 i += 1; |
| 1308 } |
| 1309 } |
| 1310 } else { |
| 1311 delete hash[key]; |
| 1312 } |
| 1313 } |
| 1314 |
| 1315 // Support methods |
| 1316 |
| 1317 function getMinOrMax(obj, map, which, all) { |
| 1318 var el, |
| 1319 key, |
| 1320 edge, |
| 1321 test, |
| 1322 result = [], |
| 1323 max = which === 'max', |
| 1324 min = which === 'min', |
| 1325 isArray = array.isArray(obj); |
| 1326 for(key in obj) { |
| 1327 if(!obj.hasOwnProperty(key)) continue; |
| 1328 el = obj[key]; |
| 1329 test = transformArgument(el, map, obj, isArray ? [el, parseInt(key), obj]
: []); |
| 1330 if(isUndefined(test)) { |
| 1331 throw new TypeError('Cannot compare with undefined'); |
| 1332 } |
| 1333 if(test === edge) { |
| 1334 result.push(el); |
| 1335 } else if(isUndefined(edge) || (max && test > edge) || (min && test < edge
)) { |
| 1336 result = [el]; |
| 1337 edge = test; |
| 1338 } |
| 1339 } |
| 1340 if(!isArray) result = arrayFlatten(result, 1); |
| 1341 return all ? result : result[0]; |
| 1342 } |
| 1343 |
| 1344 |
| 1345 // Alphanumeric collation helpers |
| 1346 |
| 1347 function collateStrings(a, b) { |
| 1348 var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0; |
| 1349 |
| 1350 var sortIgnore = array[AlphanumericSortIgnore]; |
| 1351 var sortIgnoreCase = array[AlphanumericSortIgnoreCase]; |
| 1352 var sortEquivalents = array[AlphanumericSortEquivalents]; |
| 1353 var sortOrder = array[AlphanumericSortOrder]; |
| 1354 var naturalSort = array[AlphanumericSortNatural]; |
| 1355 |
| 1356 a = getCollationReadyString(a, sortIgnore, sortIgnoreCase); |
| 1357 b = getCollationReadyString(b, sortIgnore, sortIgnoreCase); |
| 1358 |
| 1359 do { |
| 1360 |
| 1361 aChar = getCollationCharacter(a, index, sortEquivalents); |
| 1362 bChar = getCollationCharacter(b, index, sortEquivalents); |
| 1363 aValue = getSortOrderIndex(aChar, sortOrder); |
| 1364 bValue = getSortOrderIndex(bChar, sortOrder); |
| 1365 |
| 1366 if(aValue === -1 || bValue === -1) { |
| 1367 aValue = a.charCodeAt(index) || null; |
| 1368 bValue = b.charCodeAt(index) || null; |
| 1369 if(naturalSort && codeIsNumeral(aValue) && codeIsNumeral(bValue)) { |
| 1370 aValue = stringToNumber(a.slice(index)); |
| 1371 bValue = stringToNumber(b.slice(index)); |
| 1372 } |
| 1373 } else { |
| 1374 aEquiv = aChar !== a.charAt(index); |
| 1375 bEquiv = bChar !== b.charAt(index); |
| 1376 if(aEquiv !== bEquiv && tiebreaker === 0) { |
| 1377 tiebreaker = aEquiv - bEquiv; |
| 1378 } |
| 1379 } |
| 1380 index += 1; |
| 1381 } while(aValue != null && bValue != null && aValue === bValue); |
| 1382 if(aValue === bValue) return tiebreaker; |
| 1383 return aValue - bValue; |
| 1384 } |
| 1385 |
| 1386 function getCollationReadyString(str, sortIgnore, sortIgnoreCase) { |
| 1387 if(!isString(str)) str = string(str); |
| 1388 if(sortIgnoreCase) { |
| 1389 str = str.toLowerCase(); |
| 1390 } |
| 1391 if(sortIgnore) { |
| 1392 str = str.replace(sortIgnore, ''); |
| 1393 } |
| 1394 return str; |
| 1395 } |
| 1396 |
| 1397 function getCollationCharacter(str, index, sortEquivalents) { |
| 1398 var chr = str.charAt(index); |
| 1399 return sortEquivalents[chr] || chr; |
| 1400 } |
| 1401 |
| 1402 function getSortOrderIndex(chr, sortOrder) { |
| 1403 if(!chr) { |
| 1404 return null; |
| 1405 } else { |
| 1406 return sortOrder.indexOf(chr); |
| 1407 } |
| 1408 } |
| 1409 |
| 1410 var AlphanumericSort = 'AlphanumericSort'; |
| 1411 var AlphanumericSortOrder = 'AlphanumericSortOrder'; |
| 1412 var AlphanumericSortIgnore = 'AlphanumericSortIgnore'; |
| 1413 var AlphanumericSortIgnoreCase = 'AlphanumericSortIgnoreCase'; |
| 1414 var AlphanumericSortEquivalents = 'AlphanumericSortEquivalents'; |
| 1415 var AlphanumericSortNatural = 'AlphanumericSortNatural'; |
| 1416 |
| 1417 |
| 1418 |
| 1419 function buildEnhancements() { |
| 1420 var nativeMap = array.prototype.map; |
| 1421 var callbackCheck = function() { |
| 1422 var args = arguments; |
| 1423 return args.length > 0 && !isFunction(args[0]); |
| 1424 }; |
| 1425 extendSimilar(array, true, callbackCheck, 'every,all,some,filter,any,none,fi
nd,findIndex', function(methods, name) { |
| 1426 var nativeFn = array.prototype[name] |
| 1427 methods[name] = function(f) { |
| 1428 var matcher = getMatcher(f); |
| 1429 return nativeFn.call(this, function(el, index) { |
| 1430 return matcher(el, index, this); |
| 1431 }); |
| 1432 } |
| 1433 }); |
| 1434 extend(array, true, callbackCheck, { |
| 1435 'map': function(f) { |
| 1436 return nativeMap.call(this, function(el, index) { |
| 1437 return transformArgument(el, f, this, [el, index, this]); |
| 1438 }); |
| 1439 } |
| 1440 }); |
| 1441 } |
| 1442 |
| 1443 function buildAlphanumericSort() { |
| 1444 var order = 'AÁÀÂÃĄBCĆČÇDĎÐEÉÈĚÊËĘFGĞHıIÍÌİÎÏJKLŁMNŃŇÑOÓÒÔPQRŘSŚŠŞTŤUÚÙŮÛÜVW
XYÝZŹŻŽÞÆŒØÕÅÄÖ'; |
| 1445 var equiv = 'AÁÀÂÃÄ,CÇ,EÉÈÊË,IÍÌİÎÏ,OÓÒÔÕÖ,Sß,UÚÙÛÜ'; |
| 1446 array[AlphanumericSortOrder] = order.split('').map(function(str) { |
| 1447 return str + str.toLowerCase(); |
| 1448 }).join(''); |
| 1449 var equivalents = {}; |
| 1450 arrayEach(equiv.split(','), function(set) { |
| 1451 var equivalent = set.charAt(0); |
| 1452 arrayEach(set.slice(1).split(''), function(chr) { |
| 1453 equivalents[chr] = equivalent; |
| 1454 equivalents[chr.toLowerCase()] = equivalent.toLowerCase(); |
| 1455 }); |
| 1456 }); |
| 1457 array[AlphanumericSortNatural] = true; |
| 1458 array[AlphanumericSortIgnoreCase] = true; |
| 1459 array[AlphanumericSortEquivalents] = equivalents; |
| 1460 } |
| 1461 |
| 1462 extend(array, false, true, { |
| 1463 |
| 1464 /*** |
| 1465 * |
| 1466 * @method Array.create(<obj1>, <obj2>, ...) |
| 1467 * @returns Array |
| 1468 * @short Alternate array constructor. |
| 1469 * @extra This method will create a single array by calling %concat% on all
arguments passed. In addition to ensuring that an unknown variable is in a singl
e, flat array (the standard constructor will create nested arrays, this one will
not), it is also a useful shorthand to convert a function's arguments object in
to a standard array. |
| 1470 * @example |
| 1471 * |
| 1472 * Array.create('one', true, 3) -> ['one', true, 3] |
| 1473 * Array.create(['one', true, 3]) -> ['one', true, 3] |
| 1474 + Array.create(function(n) { |
| 1475 * return arguments; |
| 1476 * }('howdy', 'doody')); |
| 1477 * |
| 1478 ***/ |
| 1479 'create': function() { |
| 1480 var result = []; |
| 1481 multiArgs(arguments, function(a) { |
| 1482 if(isArgumentsObject(a) || isArrayLike(a)) { |
| 1483 a = array.prototype.slice.call(a, 0); |
| 1484 } |
| 1485 result = result.concat(a); |
| 1486 }); |
| 1487 return result; |
| 1488 } |
| 1489 |
| 1490 }); |
| 1491 |
| 1492 extend(array, true, false, { |
| 1493 |
| 1494 /*** |
| 1495 * @method find(<f>, [context] = undefined) |
| 1496 * @returns Mixed |
| 1497 * @short Returns the first element that matches <f>. |
| 1498 * @extra [context] is the %this% object if passed. When <f> is a function,
will use native implementation if it exists. <f> will also match a string, numbe
r, array, object, or alternately test against a function or regex. This method i
mplements @array_matching. |
| 1499 * @example |
| 1500 * |
| 1501 + [{a:1,b:2},{a:1,b:3},{a:1,b:4}].find(function(n) { |
| 1502 * return n['a'] == 1; |
| 1503 * }); -> {a:1,b:3} |
| 1504 * ['cuba','japan','canada'].find(/^c/) -> 'cuba' |
| 1505 * |
| 1506 ***/ |
| 1507 'find': function(f, context) { |
| 1508 checkCallback(f); |
| 1509 return arrayFind(this, f, 0, false, false, context); |
| 1510 }, |
| 1511 |
| 1512 /*** |
| 1513 * @method findIndex(<f>, [context] = undefined) |
| 1514 * @returns Number |
| 1515 * @short Returns the index of the first element that matches <f> or -1 if n
ot found. |
| 1516 * @extra [context] is the %this% object if passed. When <f> is a function,
will use native implementation if it exists. <f> will also match a string, numbe
r, array, object, or alternately test against a function or regex. This method i
mplements @array_matching. |
| 1517 * |
| 1518 * @example |
| 1519 * |
| 1520 + [1,2,3,4].findIndex(function(n) { |
| 1521 * return n % 2 == 0; |
| 1522 * }); -> 1 |
| 1523 + [1,2,3,4].findIndex(3); -> 2 |
| 1524 + ['one','two','three'].findIndex(/t/); -> 1 |
| 1525 * |
| 1526 ***/ |
| 1527 'findIndex': function(f, context) { |
| 1528 var index; |
| 1529 checkCallback(f); |
| 1530 index = arrayFind(this, f, 0, false, true, context); |
| 1531 return isUndefined(index) ? -1 : index; |
| 1532 } |
| 1533 |
| 1534 }); |
| 1535 |
| 1536 extend(array, true, true, { |
| 1537 |
| 1538 /*** |
| 1539 * @method findFrom(<f>, [index] = 0, [loop] = false) |
| 1540 * @returns Array |
| 1541 * @short Returns any element that matches <f>, beginning from [index]. |
| 1542 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. Will continue from index = 0 if [loop] is true. T
his method implements @array_matching. |
| 1543 * @example |
| 1544 * |
| 1545 * ['cuba','japan','canada'].findFrom(/^c/, 2) -> 'canada' |
| 1546 * |
| 1547 ***/ |
| 1548 'findFrom': function(f, index, loop) { |
| 1549 return arrayFind(this, f, index, loop); |
| 1550 }, |
| 1551 |
| 1552 /*** |
| 1553 * @method findIndexFrom(<f>, [index] = 0, [loop] = false) |
| 1554 * @returns Array |
| 1555 * @short Returns the index of any element that matches <f>, beginning from
[index]. |
| 1556 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. Will continue from index = 0 if [loop] is true. T
his method implements @array_matching. |
| 1557 * @example |
| 1558 * |
| 1559 * ['cuba','japan','canada'].findIndexFrom(/^c/, 2) -> 2 |
| 1560 * |
| 1561 ***/ |
| 1562 'findIndexFrom': function(f, index, loop) { |
| 1563 var index = arrayFind(this, f, index, loop, true); |
| 1564 return isUndefined(index) ? -1 : index; |
| 1565 }, |
| 1566 |
| 1567 /*** |
| 1568 * @method findAll(<f>, [index] = 0, [loop] = false) |
| 1569 * @returns Array |
| 1570 * @short Returns all elements that match <f>. |
| 1571 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. Starts at [index], and will continue once from in
dex = 0 if [loop] is true. This method implements @array_matching. |
| 1572 * @example |
| 1573 * |
| 1574 + [{a:1,b:2},{a:1,b:3},{a:2,b:4}].findAll(function(n) { |
| 1575 * return n['a'] == 1; |
| 1576 * }); -> [{a:1,b:3},{a:1,b:4}] |
| 1577 * ['cuba','japan','canada'].findAll(/^c/) -> 'cuba','canada' |
| 1578 * ['cuba','japan','canada'].findAll(/^c/, 2) -> 'canada' |
| 1579 * |
| 1580 ***/ |
| 1581 'findAll': function(f, index, loop) { |
| 1582 var result = [], matcher; |
| 1583 if(this.length > 0) { |
| 1584 matcher = getMatcher(f); |
| 1585 arrayEach(this, function(el, i, arr) { |
| 1586 if(matcher(el, i, arr)) { |
| 1587 result.push(el); |
| 1588 } |
| 1589 }, index, loop); |
| 1590 } |
| 1591 return result; |
| 1592 }, |
| 1593 |
| 1594 /*** |
| 1595 * @method count(<f>) |
| 1596 * @returns Number |
| 1597 * @short Counts all elements in the array that match <f>. |
| 1598 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. This method implements @array_matching. |
| 1599 * @example |
| 1600 * |
| 1601 * [1,2,3,1].count(1) -> 2 |
| 1602 * ['a','b','c'].count(/b/) -> 1 |
| 1603 + [{a:1},{b:2}].count(function(n) { |
| 1604 * return n['a'] > 1; |
| 1605 * }); -> 0 |
| 1606 * |
| 1607 ***/ |
| 1608 'count': function(f) { |
| 1609 if(isUndefined(f)) return this.length; |
| 1610 return this.findAll(f).length; |
| 1611 }, |
| 1612 |
| 1613 /*** |
| 1614 * @method removeAt(<start>, [end]) |
| 1615 * @returns Array |
| 1616 * @short Removes element at <start>. If [end] is specified, removes the ran
ge between <start> and [end]. This method will change the array! If you don't in
tend the array to be changed use %clone% first. |
| 1617 * @example |
| 1618 * |
| 1619 * ['a','b','c'].removeAt(0) -> ['b','c'] |
| 1620 * [1,2,3,4].removeAt(1, 3) -> [1] |
| 1621 * |
| 1622 ***/ |
| 1623 'removeAt': function(start, end) { |
| 1624 if(isUndefined(start)) return this; |
| 1625 if(isUndefined(end)) end = start; |
| 1626 this.splice(start, end - start + 1); |
| 1627 return this; |
| 1628 }, |
| 1629 |
| 1630 /*** |
| 1631 * @method include(<el>, [index]) |
| 1632 * @returns Array |
| 1633 * @short Adds <el> to the array. |
| 1634 * @extra This is a non-destructive alias for %add%. It will not change the
original array. |
| 1635 * @example |
| 1636 * |
| 1637 * [1,2,3,4].include(5) -> [1,2,3,4,5] |
| 1638 * [1,2,3,4].include(8, 1) -> [1,8,2,3,4] |
| 1639 * [1,2,3,4].include([5,6,7]) -> [1,2,3,4,5,6,7] |
| 1640 * |
| 1641 ***/ |
| 1642 'include': function(el, index) { |
| 1643 return this.clone().add(el, index); |
| 1644 }, |
| 1645 |
| 1646 /*** |
| 1647 * @method exclude([f1], [f2], ...) |
| 1648 * @returns Array |
| 1649 * @short Removes any element in the array that matches [f1], [f2], etc. |
| 1650 * @extra This is a non-destructive alias for %remove%. It will not change t
he original array. This method implements @array_matching. |
| 1651 * @example |
| 1652 * |
| 1653 * [1,2,3].exclude(3) -> [1,2] |
| 1654 * ['a','b','c'].exclude(/b/) -> ['a','c'] |
| 1655 + [{a:1},{b:2}].exclude(function(n) { |
| 1656 * return n['a'] == 1; |
| 1657 * }); -> [{b:2}] |
| 1658 * |
| 1659 ***/ |
| 1660 'exclude': function() { |
| 1661 return array.prototype.remove.apply(this.clone(), arguments); |
| 1662 }, |
| 1663 |
| 1664 /*** |
| 1665 * @method clone() |
| 1666 * @returns Array |
| 1667 * @short Makes a shallow clone of the array. |
| 1668 * @example |
| 1669 * |
| 1670 * [1,2,3].clone() -> [1,2,3] |
| 1671 * |
| 1672 ***/ |
| 1673 'clone': function() { |
| 1674 return simpleMerge([], this); |
| 1675 }, |
| 1676 |
| 1677 /*** |
| 1678 * @method unique([map] = null) |
| 1679 * @returns Array |
| 1680 * @short Removes all duplicate elements in the array. |
| 1681 * @extra [map] may be a function mapping the value to be uniqued on or a st
ring acting as a shortcut. This is most commonly used when you have a key that e
nsures the object's uniqueness, and don't need to check all fields. This method
will also correctly operate on arrays of objects. |
| 1682 * @example |
| 1683 * |
| 1684 * [1,2,2,3].unique() -> [1,2,3] |
| 1685 * [{foo:'bar'},{foo:'bar'}].unique() -> [{foo:'bar'}] |
| 1686 + [{foo:'bar'},{foo:'bar'}].unique(function(obj){ |
| 1687 * return obj.foo; |
| 1688 * }); -> [{foo:'bar'}] |
| 1689 * [{foo:'bar'},{foo:'bar'}].unique('foo') -> [{foo:'bar'}] |
| 1690 * |
| 1691 ***/ |
| 1692 'unique': function(map) { |
| 1693 return arrayUnique(this, map); |
| 1694 }, |
| 1695 |
| 1696 /*** |
| 1697 * @method flatten([limit] = Infinity) |
| 1698 * @returns Array |
| 1699 * @short Returns a flattened, one-dimensional copy of the array. |
| 1700 * @extra You can optionally specify a [limit], which will only flatten that
depth. |
| 1701 * @example |
| 1702 * |
| 1703 * [[1], 2, [3]].flatten() -> [1,2,3] |
| 1704 * [['a'],[],'b','c'].flatten() -> ['a','b','c'] |
| 1705 * |
| 1706 ***/ |
| 1707 'flatten': function(limit) { |
| 1708 return arrayFlatten(this, limit); |
| 1709 }, |
| 1710 |
| 1711 /*** |
| 1712 * @method union([a1], [a2], ...) |
| 1713 * @returns Array |
| 1714 * @short Returns an array containing all elements in all arrays with duplic
ates removed. |
| 1715 * @extra This method will also correctly operate on arrays of objects. |
| 1716 * @example |
| 1717 * |
| 1718 * [1,3,5].union([5,7,9]) -> [1,3,5,7,9] |
| 1719 * ['a','b'].union(['b','c']) -> ['a','b','c'] |
| 1720 * |
| 1721 ***/ |
| 1722 'union': function() { |
| 1723 return arrayUnique(this.concat(flatArguments(arguments))); |
| 1724 }, |
| 1725 |
| 1726 /*** |
| 1727 * @method intersect([a1], [a2], ...) |
| 1728 * @returns Array |
| 1729 * @short Returns an array containing the elements all arrays have in common
. |
| 1730 * @extra This method will also correctly operate on arrays of objects. |
| 1731 * @example |
| 1732 * |
| 1733 * [1,3,5].intersect([5,7,9]) -> [5] |
| 1734 * ['a','b'].intersect('b','c') -> ['b'] |
| 1735 * |
| 1736 ***/ |
| 1737 'intersect': function() { |
| 1738 return arrayIntersect(this, flatArguments(arguments), false); |
| 1739 }, |
| 1740 |
| 1741 /*** |
| 1742 * @method subtract([a1], [a2], ...) |
| 1743 * @returns Array |
| 1744 * @short Subtracts from the array all elements in [a1], [a2], etc. |
| 1745 * @extra This method will also correctly operate on arrays of objects. |
| 1746 * @example |
| 1747 * |
| 1748 * [1,3,5].subtract([5,7,9]) -> [1,3] |
| 1749 * [1,3,5].subtract([3],[5]) -> [1] |
| 1750 * ['a','b'].subtract('b','c') -> ['a'] |
| 1751 * |
| 1752 ***/ |
| 1753 'subtract': function(a) { |
| 1754 return arrayIntersect(this, flatArguments(arguments), true); |
| 1755 }, |
| 1756 |
| 1757 /*** |
| 1758 * @method at(<index>, [loop] = true) |
| 1759 * @returns Mixed |
| 1760 * @short Gets the element(s) at a given index. |
| 1761 * @extra When [loop] is true, overshooting the end of the array (or the beg
inning) will begin counting from the other end. As an alternate syntax, passing
multiple indexes will get the elements at those indexes. |
| 1762 * @example |
| 1763 * |
| 1764 * [1,2,3].at(0) -> 1 |
| 1765 * [1,2,3].at(2) -> 3 |
| 1766 * [1,2,3].at(4) -> 2 |
| 1767 * [1,2,3].at(4, false) -> null |
| 1768 * [1,2,3].at(-1) -> 3 |
| 1769 * [1,2,3].at(0,1) -> [1,2] |
| 1770 * |
| 1771 ***/ |
| 1772 'at': function() { |
| 1773 return getEntriesForIndexes(this, arguments); |
| 1774 }, |
| 1775 |
| 1776 /*** |
| 1777 * @method first([num] = 1) |
| 1778 * @returns Mixed |
| 1779 * @short Returns the first element(s) in the array. |
| 1780 * @extra When <num> is passed, returns the first <num> elements in the arra
y. |
| 1781 * @example |
| 1782 * |
| 1783 * [1,2,3].first() -> 1 |
| 1784 * [1,2,3].first(2) -> [1,2] |
| 1785 * |
| 1786 ***/ |
| 1787 'first': function(num) { |
| 1788 if(isUndefined(num)) return this[0]; |
| 1789 if(num < 0) num = 0; |
| 1790 return this.slice(0, num); |
| 1791 }, |
| 1792 |
| 1793 /*** |
| 1794 * @method last([num] = 1) |
| 1795 * @returns Mixed |
| 1796 * @short Returns the last element(s) in the array. |
| 1797 * @extra When <num> is passed, returns the last <num> elements in the array
. |
| 1798 * @example |
| 1799 * |
| 1800 * [1,2,3].last() -> 3 |
| 1801 * [1,2,3].last(2) -> [2,3] |
| 1802 * |
| 1803 ***/ |
| 1804 'last': function(num) { |
| 1805 if(isUndefined(num)) return this[this.length - 1]; |
| 1806 var start = this.length - num < 0 ? 0 : this.length - num; |
| 1807 return this.slice(start); |
| 1808 }, |
| 1809 |
| 1810 /*** |
| 1811 * @method from(<index>) |
| 1812 * @returns Array |
| 1813 * @short Returns a slice of the array from <index>. |
| 1814 * @example |
| 1815 * |
| 1816 * [1,2,3].from(1) -> [2,3] |
| 1817 * [1,2,3].from(2) -> [3] |
| 1818 * |
| 1819 ***/ |
| 1820 'from': function(num) { |
| 1821 return this.slice(num); |
| 1822 }, |
| 1823 |
| 1824 /*** |
| 1825 * @method to(<index>) |
| 1826 * @returns Array |
| 1827 * @short Returns a slice of the array up to <index>. |
| 1828 * @example |
| 1829 * |
| 1830 * [1,2,3].to(1) -> [1] |
| 1831 * [1,2,3].to(2) -> [1,2] |
| 1832 * |
| 1833 ***/ |
| 1834 'to': function(num) { |
| 1835 if(isUndefined(num)) num = this.length; |
| 1836 return this.slice(0, num); |
| 1837 }, |
| 1838 |
| 1839 /*** |
| 1840 * @method min([map], [all] = false) |
| 1841 * @returns Mixed |
| 1842 * @short Returns the element in the array with the lowest value. |
| 1843 * @extra [map] may be a function mapping the value to be checked or a strin
g acting as a shortcut. If [all] is true, will return all min values in an array
. |
| 1844 * @example |
| 1845 * |
| 1846 * [1,2,3].min() -> 1 |
| 1847 * ['fee','fo','fum'].min('length') -> 'fo' |
| 1848 * ['fee','fo','fum'].min('length', true) -> ['fo'] |
| 1849 + ['fee','fo','fum'].min(function(n) { |
| 1850 * return n.length; |
| 1851 * }); -> ['fo'] |
| 1852 + [{a:3,a:2}].min(function(n) { |
| 1853 * return n['a']; |
| 1854 * }); -> [{a:2}] |
| 1855 * |
| 1856 ***/ |
| 1857 'min': function(map, all) { |
| 1858 return getMinOrMax(this, map, 'min', all); |
| 1859 }, |
| 1860 |
| 1861 /*** |
| 1862 * @method max([map], [all] = false) |
| 1863 * @returns Mixed |
| 1864 * @short Returns the element in the array with the greatest value. |
| 1865 * @extra [map] may be a function mapping the value to be checked or a strin
g acting as a shortcut. If [all] is true, will return all max values in an array
. |
| 1866 * @example |
| 1867 * |
| 1868 * [1,2,3].max() -> 3 |
| 1869 * ['fee','fo','fum'].max('length') -> 'fee' |
| 1870 * ['fee','fo','fum'].max('length', true) -> ['fee'] |
| 1871 + [{a:3,a:2}].max(function(n) { |
| 1872 * return n['a']; |
| 1873 * }); -> {a:3} |
| 1874 * |
| 1875 ***/ |
| 1876 'max': function(map, all) { |
| 1877 return getMinOrMax(this, map, 'max', all); |
| 1878 }, |
| 1879 |
| 1880 /*** |
| 1881 * @method least([map]) |
| 1882 * @returns Array |
| 1883 * @short Returns the elements in the array with the least commonly occuring
value. |
| 1884 * @extra [map] may be a function mapping the value to be checked or a strin
g acting as a shortcut. |
| 1885 * @example |
| 1886 * |
| 1887 * [3,2,2].least() -> [3] |
| 1888 * ['fe','fo','fum'].least('length') -> ['fum'] |
| 1889 + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].least(fun
ction(n) { |
| 1890 * return n.age; |
| 1891 * }); -> [{age:35,name:'ken'}] |
| 1892 * |
| 1893 ***/ |
| 1894 'least': function(map, all) { |
| 1895 return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'min', all); |
| 1896 }, |
| 1897 |
| 1898 /*** |
| 1899 * @method most([map]) |
| 1900 * @returns Array |
| 1901 * @short Returns the elements in the array with the most commonly occuring
value. |
| 1902 * @extra [map] may be a function mapping the value to be checked or a strin
g acting as a shortcut. |
| 1903 * @example |
| 1904 * |
| 1905 * [3,2,2].most() -> [2] |
| 1906 * ['fe','fo','fum'].most('length') -> ['fe','fo'] |
| 1907 + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].most(func
tion(n) { |
| 1908 * return n.age; |
| 1909 * }); -> [{age:12,name:'bob'},{age:12,name:'
ted'}] |
| 1910 * |
| 1911 ***/ |
| 1912 'most': function(map, all) { |
| 1913 return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'max', all); |
| 1914 }, |
| 1915 |
| 1916 /*** |
| 1917 * @method sum([map]) |
| 1918 * @returns Number |
| 1919 * @short Sums all values in the array. |
| 1920 * @extra [map] may be a function mapping the value to be summed or a string
acting as a shortcut. |
| 1921 * @example |
| 1922 * |
| 1923 * [1,2,2].sum() -> 5 |
| 1924 + [{age:35},{age:12},{age:12}].sum(function(n) { |
| 1925 * return n.age; |
| 1926 * }); -> 59 |
| 1927 * [{age:35},{age:12},{age:12}].sum('age') -> 59 |
| 1928 * |
| 1929 ***/ |
| 1930 'sum': function(map) { |
| 1931 var arr = map ? this.map(map) : this; |
| 1932 return arr.length > 0 ? arr.reduce(function(a,b) { return a + b; }) : 0; |
| 1933 }, |
| 1934 |
| 1935 /*** |
| 1936 * @method average([map]) |
| 1937 * @returns Number |
| 1938 * @short Gets the mean average for all values in the array. |
| 1939 * @extra [map] may be a function mapping the value to be averaged or a stri
ng acting as a shortcut. |
| 1940 * @example |
| 1941 * |
| 1942 * [1,2,3].average() -> 2 |
| 1943 + [{age:35},{age:11},{age:11}].average(function(n) { |
| 1944 * return n.age; |
| 1945 * }); -> 19 |
| 1946 * [{age:35},{age:11},{age:11}].average('age') -> 19 |
| 1947 * |
| 1948 ***/ |
| 1949 'average': function(map) { |
| 1950 var arr = map ? this.map(map) : this; |
| 1951 return arr.length > 0 ? arr.sum() / arr.length : 0; |
| 1952 }, |
| 1953 |
| 1954 /*** |
| 1955 * @method inGroups(<num>, [padding]) |
| 1956 * @returns Array |
| 1957 * @short Groups the array into <num> arrays. |
| 1958 * @extra [padding] specifies a value with which to pad the last array so th
at they are all equal length. |
| 1959 * @example |
| 1960 * |
| 1961 * [1,2,3,4,5,6,7].inGroups(3) -> [ [1,2,3], [4,5,6], [7] ] |
| 1962 * [1,2,3,4,5,6,7].inGroups(3, 'none') -> [ [1,2,3], [4,5,6], [7,'none','n
one'] ] |
| 1963 * |
| 1964 ***/ |
| 1965 'inGroups': function(num, padding) { |
| 1966 var pad = arguments.length > 1; |
| 1967 var arr = this; |
| 1968 var result = []; |
| 1969 var divisor = ceil(this.length / num); |
| 1970 simpleRepeat(num, function(i) { |
| 1971 var index = i * divisor; |
| 1972 var group = arr.slice(index, index + divisor); |
| 1973 if(pad && group.length < divisor) { |
| 1974 simpleRepeat(divisor - group.length, function() { |
| 1975 group = group.add(padding); |
| 1976 }); |
| 1977 } |
| 1978 result.push(group); |
| 1979 }); |
| 1980 return result; |
| 1981 }, |
| 1982 |
| 1983 /*** |
| 1984 * @method inGroupsOf(<num>, [padding] = null) |
| 1985 * @returns Array |
| 1986 * @short Groups the array into arrays of <num> elements each. |
| 1987 * @extra [padding] specifies a value with which to pad the last array so th
at they are all equal length. |
| 1988 * @example |
| 1989 * |
| 1990 * [1,2,3,4,5,6,7].inGroupsOf(4) -> [ [1,2,3,4], [5,6,7] ] |
| 1991 * [1,2,3,4,5,6,7].inGroupsOf(4, 'none') -> [ [1,2,3,4], [5,6,7,'none'] ] |
| 1992 * |
| 1993 ***/ |
| 1994 'inGroupsOf': function(num, padding) { |
| 1995 var result = [], len = this.length, arr = this, group; |
| 1996 if(len === 0 || num === 0) return arr; |
| 1997 if(isUndefined(num)) num = 1; |
| 1998 if(isUndefined(padding)) padding = null; |
| 1999 simpleRepeat(ceil(len / num), function(i) { |
| 2000 group = arr.slice(num * i, num * i + num); |
| 2001 while(group.length < num) { |
| 2002 group.push(padding); |
| 2003 } |
| 2004 result.push(group); |
| 2005 }); |
| 2006 return result; |
| 2007 }, |
| 2008 |
| 2009 /*** |
| 2010 * @method isEmpty() |
| 2011 * @returns Boolean |
| 2012 * @short Returns true if the array is empty. |
| 2013 * @extra This is true if the array has a length of zero, or contains only %
undefined%, %null%, or %NaN%. |
| 2014 * @example |
| 2015 * |
| 2016 * [].isEmpty() -> true |
| 2017 * [null,undefined].isEmpty() -> true |
| 2018 * |
| 2019 ***/ |
| 2020 'isEmpty': function() { |
| 2021 return this.compact().length == 0; |
| 2022 }, |
| 2023 |
| 2024 /*** |
| 2025 * @method sortBy(<map>, [desc] = false) |
| 2026 * @returns Array |
| 2027 * @short Sorts the array by <map>. |
| 2028 * @extra <map> may be a function, a string acting as a shortcut, or blank (
direct comparison of array values). [desc] will sort the array in descending ord
er. When the field being sorted on is a string, the resulting order will be dete
rmined by an internal collation algorithm that is optimized for major Western la
nguages, but can be customized. For more information see @array_sorting. |
| 2029 * @example |
| 2030 * |
| 2031 * ['world','a','new'].sortBy('length') -> ['a','new','world'] |
| 2032 * ['world','a','new'].sortBy('length', true) -> ['world','new','a'] |
| 2033 + [{age:72},{age:13},{age:18}].sortBy(function(n) { |
| 2034 * return n.age; |
| 2035 * }); -> [{age:13},{age:18},{age:7
2}] |
| 2036 * |
| 2037 ***/ |
| 2038 'sortBy': function(map, desc) { |
| 2039 var arr = this.clone(); |
| 2040 arr.sort(function(a, b) { |
| 2041 var aProperty, bProperty, comp; |
| 2042 aProperty = transformArgument(a, map, arr, [a]); |
| 2043 bProperty = transformArgument(b, map, arr, [b]); |
| 2044 if(isString(aProperty) && isString(bProperty)) { |
| 2045 comp = collateStrings(aProperty, bProperty); |
| 2046 } else if(aProperty < bProperty) { |
| 2047 comp = -1; |
| 2048 } else if(aProperty > bProperty) { |
| 2049 comp = 1; |
| 2050 } else { |
| 2051 comp = 0; |
| 2052 } |
| 2053 return comp * (desc ? -1 : 1); |
| 2054 }); |
| 2055 return arr; |
| 2056 }, |
| 2057 |
| 2058 /*** |
| 2059 * @method randomize() |
| 2060 * @returns Array |
| 2061 * @short Returns a copy of the array with the elements randomized. |
| 2062 * @extra Uses Fisher-Yates algorithm. |
| 2063 * @example |
| 2064 * |
| 2065 * [1,2,3,4].randomize() -> [?,?,?,?] |
| 2066 * |
| 2067 ***/ |
| 2068 'randomize': function() { |
| 2069 var arr = this.concat(), i = arr.length, j, x; |
| 2070 while(i) { |
| 2071 j = (math.random() * i) | 0; |
| 2072 x = arr[--i]; |
| 2073 arr[i] = arr[j]; |
| 2074 arr[j] = x; |
| 2075 } |
| 2076 return arr; |
| 2077 }, |
| 2078 |
| 2079 /*** |
| 2080 * @method zip([arr1], [arr2], ...) |
| 2081 * @returns Array |
| 2082 * @short Merges multiple arrays together. |
| 2083 * @extra This method "zips up" smaller arrays into one large whose elements
are "all elements at index 0", "all elements at index 1", etc. Useful when you
have associated data that is split over separated arrays. If the arrays passed h
ave more elements than the original array, they will be discarded. If they have
fewer elements, the missing elements will filled with %null%. |
| 2084 * @example |
| 2085 * |
| 2086 * [1,2,3].zip([4,5,6]) -> [[1,2], [
3,4], [5,6]] |
| 2087 * ['Martin','John'].zip(['Luther','F.'], ['King','Kennedy']) -> [['Martin
','Luther','King'], ['John','F.','Kennedy']] |
| 2088 * |
| 2089 ***/ |
| 2090 'zip': function() { |
| 2091 var args = multiArgs(arguments); |
| 2092 return this.map(function(el, i) { |
| 2093 return [el].concat(args.map(function(k) { |
| 2094 return (i in k) ? k[i] : null; |
| 2095 })); |
| 2096 }); |
| 2097 }, |
| 2098 |
| 2099 /*** |
| 2100 * @method sample([num]) |
| 2101 * @returns Mixed |
| 2102 * @short Returns a random element from the array. |
| 2103 * @extra If [num] is passed, will return [num] samples from the array. |
| 2104 * @example |
| 2105 * |
| 2106 * [1,2,3,4,5].sample() -> // Random element |
| 2107 * [1,2,3,4,5].sample(3) -> // Array of 3 random elements |
| 2108 * |
| 2109 ***/ |
| 2110 'sample': function(num) { |
| 2111 var arr = this.randomize(); |
| 2112 return arguments.length > 0 ? arr.slice(0, num) : arr[0]; |
| 2113 }, |
| 2114 |
| 2115 /*** |
| 2116 * @method each(<fn>, [index] = 0, [loop] = false) |
| 2117 * @returns Array |
| 2118 * @short Runs <fn> against each element in the array. Enhanced version of %
Array#forEach%. |
| 2119 * @extra Parameters passed to <fn> are identical to %forEach%, ie. the firs
t parameter is the current element, second parameter is the current index, and t
hird parameter is the array itself. If <fn> returns %false% at any time it will
break out of the loop. Once %each% finishes, it will return the array. If [index
] is passed, <fn> will begin at that index and work its way to the end. If [loop
] is true, it will then start over from the beginning of the array and continue
until it reaches [index] - 1. |
| 2120 * @example |
| 2121 * |
| 2122 * [1,2,3,4].each(function(n) { |
| 2123 * // Called 4 times: 1, 2, 3, 4 |
| 2124 * }); |
| 2125 * [1,2,3,4].each(function(n) { |
| 2126 * // Called 4 times: 3, 4, 1, 2 |
| 2127 * }, 2, true); |
| 2128 * |
| 2129 ***/ |
| 2130 'each': function(fn, index, loop) { |
| 2131 arrayEach(this, fn, index, loop); |
| 2132 return this; |
| 2133 }, |
| 2134 |
| 2135 /*** |
| 2136 * @method add(<el>, [index]) |
| 2137 * @returns Array |
| 2138 * @short Adds <el> to the array. |
| 2139 * @extra If [index] is specified, it will add at [index], otherwise adds to
the end of the array. %add% behaves like %concat% in that if <el> is an array i
t will be joined, not inserted. This method will change the array! Use %include%
for a non-destructive alias. Also, %insert% is provided as an alias that reads
better when using an index. |
| 2140 * @example |
| 2141 * |
| 2142 * [1,2,3,4].add(5) -> [1,2,3,4,5] |
| 2143 * [1,2,3,4].add([5,6,7]) -> [1,2,3,4,5,6,7] |
| 2144 * [1,2,3,4].insert(8, 1) -> [1,8,2,3,4] |
| 2145 * |
| 2146 ***/ |
| 2147 'add': function(el, index) { |
| 2148 if(!isNumber(number(index)) || isNaN(index)) index = this.length; |
| 2149 array.prototype.splice.apply(this, [index, 0].concat(el)); |
| 2150 return this; |
| 2151 }, |
| 2152 |
| 2153 /*** |
| 2154 * @method remove([f1], [f2], ...) |
| 2155 * @returns Array |
| 2156 * @short Removes any element in the array that matches [f1], [f2], etc. |
| 2157 * @extra Will match a string, number, array, object, or alternately test ag
ainst a function or regex. This method will change the array! Use %exclude% for
a non-destructive alias. This method implements @array_matching. |
| 2158 * @example |
| 2159 * |
| 2160 * [1,2,3].remove(3) -> [1,2] |
| 2161 * ['a','b','c'].remove(/b/) -> ['a','c'] |
| 2162 + [{a:1},{b:2}].remove(function(n) { |
| 2163 * return n['a'] == 1; |
| 2164 * }); -> [{b:2}] |
| 2165 * |
| 2166 ***/ |
| 2167 'remove': function() { |
| 2168 var arr = this; |
| 2169 multiArgs(arguments, function(f) { |
| 2170 var i = 0, matcher = getMatcher(f); |
| 2171 while(i < arr.length) { |
| 2172 if(matcher(arr[i], i, arr)) { |
| 2173 arr.splice(i, 1); |
| 2174 } else { |
| 2175 i++; |
| 2176 } |
| 2177 } |
| 2178 }); |
| 2179 return arr; |
| 2180 }, |
| 2181 |
| 2182 /*** |
| 2183 * @method compact([all] = false) |
| 2184 * @returns Array |
| 2185 * @short Removes all instances of %undefined%, %null%, and %NaN% from the a
rray. |
| 2186 * @extra If [all] is %true%, all "falsy" elements will be removed. This inc
ludes empty strings, 0, and false. |
| 2187 * @example |
| 2188 * |
| 2189 * [1,null,2,undefined,3].compact() -> [1,2,3] |
| 2190 * [1,'',2,false,3].compact() -> [1,'',2,false,3] |
| 2191 * [1,'',2,false,3].compact(true) -> [1,2,3] |
| 2192 * |
| 2193 ***/ |
| 2194 'compact': function(all) { |
| 2195 var result = []; |
| 2196 arrayEach(this, function(el, i) { |
| 2197 if(isArray(el)) { |
| 2198 result.push(el.compact()); |
| 2199 } else if(all && el) { |
| 2200 result.push(el); |
| 2201 } else if(!all && el != null && el.valueOf() === el.valueOf()) { |
| 2202 result.push(el); |
| 2203 } |
| 2204 }); |
| 2205 return result; |
| 2206 }, |
| 2207 |
| 2208 /*** |
| 2209 * @method groupBy(<map>, [fn]) |
| 2210 * @returns Object |
| 2211 * @short Groups the array by <map>. |
| 2212 * @extra Will return an object with keys equal to the grouped values. <map>
may be a mapping function, or a string acting as a shortcut. Optionally calls [
fn] for each group. |
| 2213 * @example |
| 2214 * |
| 2215 * ['fee','fi','fum'].groupBy('length') -> { 2: ['fi'], 3: ['fee','fum'] } |
| 2216 + [{age:35,name:'ken'},{age:15,name:'bob'}].groupBy(function(n) { |
| 2217 * return n.age; |
| 2218 * }); -> { 35: [{age:35,name:'ken'}], 15
: [{age:15,name:'bob'}] } |
| 2219 * |
| 2220 ***/ |
| 2221 'groupBy': function(map, fn) { |
| 2222 var arr = this, result = {}, key; |
| 2223 arrayEach(arr, function(el, index) { |
| 2224 key = transformArgument(el, map, arr, [el, index, arr]); |
| 2225 if(!result[key]) result[key] = []; |
| 2226 result[key].push(el); |
| 2227 }); |
| 2228 if(fn) { |
| 2229 iterateOverObject(result, fn); |
| 2230 } |
| 2231 return result; |
| 2232 }, |
| 2233 |
| 2234 /*** |
| 2235 * @method none(<f>) |
| 2236 * @returns Boolean |
| 2237 * @short Returns true if none of the elements in the array match <f>. |
| 2238 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. This method implements @array_matching. |
| 2239 * @example |
| 2240 * |
| 2241 * [1,2,3].none(5) -> true |
| 2242 * ['a','b','c'].none(/b/) -> false |
| 2243 + [{a:1},{b:2}].none(function(n) { |
| 2244 * return n['a'] > 1; |
| 2245 * }); -> true |
| 2246 * |
| 2247 ***/ |
| 2248 'none': function() { |
| 2249 return !this.any.apply(this, arguments); |
| 2250 } |
| 2251 |
| 2252 |
| 2253 }); |
| 2254 |
| 2255 |
| 2256 // Aliases |
| 2257 |
| 2258 extend(array, true, true, { |
| 2259 |
| 2260 /*** |
| 2261 * @method all() |
| 2262 * @alias every |
| 2263 * |
| 2264 ***/ |
| 2265 'all': array.prototype.every, |
| 2266 |
| 2267 /*** @method any() |
| 2268 * @alias some |
| 2269 * |
| 2270 ***/ |
| 2271 'any': array.prototype.some, |
| 2272 |
| 2273 /*** |
| 2274 * @method insert() |
| 2275 * @alias add |
| 2276 * |
| 2277 ***/ |
| 2278 'insert': array.prototype.add |
| 2279 |
| 2280 }); |
| 2281 |
| 2282 |
| 2283 /*** |
| 2284 * Object module |
| 2285 * Enumerable methods on objects |
| 2286 * |
| 2287 ***/ |
| 2288 |
| 2289 function keysWithObjectCoercion(obj) { |
| 2290 return object.keys(coercePrimitiveToObject(obj)); |
| 2291 } |
| 2292 |
| 2293 /*** |
| 2294 * @method [enumerable](<obj>) |
| 2295 * @returns Boolean |
| 2296 * @short Enumerable methods in the Array package are also available to the Ob
ject class. They will perform their normal operations for every property in <obj
>. |
| 2297 * @extra In cases where a callback is used, instead of %element, index%, the
callback will instead be passed %key, value%. Enumerable methods are also availa
ble to extended objects as instance methods. |
| 2298 * |
| 2299 * @set |
| 2300 * each |
| 2301 * map |
| 2302 * any |
| 2303 * all |
| 2304 * none |
| 2305 * count |
| 2306 * find |
| 2307 * findAll |
| 2308 * reduce |
| 2309 * isEmpty |
| 2310 * sum |
| 2311 * average |
| 2312 * min |
| 2313 * max |
| 2314 * least |
| 2315 * most |
| 2316 * |
| 2317 * @example |
| 2318 * |
| 2319 * Object.any({foo:'bar'}, 'bar') -> true |
| 2320 * Object.extended({foo:'bar'}).any('bar') -> true |
| 2321 * Object.isEmpty({}) -> true |
| 2322 + Object.map({ fred: { age: 52 } }, 'age'); -> { fred: 52 } |
| 2323 * |
| 2324 ***/ |
| 2325 |
| 2326 function buildEnumerableMethods(names, mapping) { |
| 2327 extendSimilar(object, false, true, names, function(methods, name) { |
| 2328 methods[name] = function(obj, arg1, arg2) { |
| 2329 var result, coerced = keysWithObjectCoercion(obj), matcher; |
| 2330 if(!mapping) { |
| 2331 matcher = getMatcher(arg1, true); |
| 2332 } |
| 2333 result = array.prototype[name].call(coerced, function(key) { |
| 2334 var value = obj[key]; |
| 2335 if(mapping) { |
| 2336 return transformArgument(value, arg1, obj, [key, value, obj]); |
| 2337 } else { |
| 2338 return matcher(value, key, obj); |
| 2339 } |
| 2340 }, arg2); |
| 2341 if(isArray(result)) { |
| 2342 // The method has returned an array of keys so use this array |
| 2343 // to build up the resulting object in the form we want it in. |
| 2344 result = result.reduce(function(o, key, i) { |
| 2345 o[key] = obj[key]; |
| 2346 return o; |
| 2347 }, {}); |
| 2348 } |
| 2349 return result; |
| 2350 }; |
| 2351 }); |
| 2352 buildObjectInstanceMethods(names, Hash); |
| 2353 } |
| 2354 |
| 2355 function exportSortAlgorithm() { |
| 2356 array[AlphanumericSort] = collateStrings; |
| 2357 } |
| 2358 |
| 2359 extend(object, false, true, { |
| 2360 |
| 2361 'map': function(obj, map) { |
| 2362 var result = {}, key, value; |
| 2363 for(key in obj) { |
| 2364 if(!hasOwnProperty(obj, key)) continue; |
| 2365 value = obj[key]; |
| 2366 result[key] = transformArgument(value, map, obj, [key, value, obj]); |
| 2367 } |
| 2368 return result; |
| 2369 }, |
| 2370 |
| 2371 'reduce': function(obj) { |
| 2372 var values = keysWithObjectCoercion(obj).map(function(key) { |
| 2373 return obj[key]; |
| 2374 }); |
| 2375 return values.reduce.apply(values, multiArgs(arguments, null, 1)); |
| 2376 }, |
| 2377 |
| 2378 'each': function(obj, fn) { |
| 2379 checkCallback(fn); |
| 2380 iterateOverObject(obj, fn); |
| 2381 return obj; |
| 2382 }, |
| 2383 |
| 2384 /*** |
| 2385 * @method size(<obj>) |
| 2386 * @returns Number |
| 2387 * @short Returns the number of properties in <obj>. |
| 2388 * @extra %size% is available as an instance method on extended objects. |
| 2389 * @example |
| 2390 * |
| 2391 * Object.size({ foo: 'bar' }) -> 1 |
| 2392 * |
| 2393 ***/ |
| 2394 'size': function (obj) { |
| 2395 return keysWithObjectCoercion(obj).length; |
| 2396 } |
| 2397 |
| 2398 }); |
| 2399 |
| 2400 var EnumerableFindingMethods = 'any,all,none,count,find,findAll,isEmpty'.split
(','); |
| 2401 var EnumerableMappingMethods = 'sum,average,min,max,least,most'.split(','); |
| 2402 var EnumerableOtherMethods = 'map,reduce,size'.split(','); |
| 2403 var EnumerableMethods = EnumerableFindingMethods.concat(EnumerableMappi
ngMethods).concat(EnumerableOtherMethods); |
| 2404 |
| 2405 buildEnhancements(); |
| 2406 buildAlphanumericSort(); |
| 2407 buildEnumerableMethods(EnumerableFindingMethods); |
| 2408 buildEnumerableMethods(EnumerableMappingMethods, true); |
| 2409 buildObjectInstanceMethods(EnumerableOtherMethods, Hash); |
| 2410 exportSortAlgorithm(); |
| 2411 |
| 2412 |
| 2413 /*** |
| 2414 * @package Date |
| 2415 * @dependency core |
| 2416 * @description Date parsing and formatting, relative formats like "1 minute a
go", Number methods like "daysAgo", localization support with default English lo
cale definition. |
| 2417 * |
| 2418 ***/ |
| 2419 |
| 2420 var English; |
| 2421 var CurrentLocalization; |
| 2422 |
| 2423 var TimeFormat = ['ampm','hour','minute','second','ampm','utc','offset_sign','
offset_hours','offset_minutes','ampm'] |
| 2424 var DecimalReg = '(?:[,.]\\d+)?'; |
| 2425 var HoursReg = '\\d{1,2}' + DecimalReg; |
| 2426 var SixtyReg = '[0-5]\\d' + DecimalReg; |
| 2427 var RequiredTime = '({t})?\\s*('+HoursReg+')(?:{h}('+SixtyReg+')?{m}(?::?('+Si
xtyReg+'){s})?\\s*(?:({t})|(Z)|(?:([+-])(\\d{2,2})(?::?(\\d{2,2}))?)?)?|\\s*({t}
))'; |
| 2428 |
| 2429 var KanjiDigits = '〇一二三四五六七八九十百千万'; |
| 2430 var AsianDigitMap = {}; |
| 2431 var AsianDigitReg; |
| 2432 |
| 2433 var DateArgumentUnits; |
| 2434 var DateUnitsReversed; |
| 2435 var CoreDateFormats = []; |
| 2436 var CompiledOutputFormats = {}; |
| 2437 |
| 2438 var DateFormatTokens = { |
| 2439 |
| 2440 'yyyy': function(d) { |
| 2441 return callDateGet(d, 'FullYear'); |
| 2442 }, |
| 2443 |
| 2444 'yy': function(d) { |
| 2445 return callDateGet(d, 'FullYear') % 100; |
| 2446 }, |
| 2447 |
| 2448 'ord': function(d) { |
| 2449 var date = callDateGet(d, 'Date'); |
| 2450 return date + getOrdinalizedSuffix(date); |
| 2451 }, |
| 2452 |
| 2453 'tz': function(d) { |
| 2454 return d.getUTCOffset(); |
| 2455 }, |
| 2456 |
| 2457 'isotz': function(d) { |
| 2458 return d.getUTCOffset(true); |
| 2459 }, |
| 2460 |
| 2461 'Z': function(d) { |
| 2462 return d.getUTCOffset(); |
| 2463 }, |
| 2464 |
| 2465 'ZZ': function(d) { |
| 2466 return d.getUTCOffset().replace(/(\d{2})$/, ':$1'); |
| 2467 } |
| 2468 |
| 2469 }; |
| 2470 |
| 2471 var DateUnits = [ |
| 2472 { |
| 2473 name: 'year', |
| 2474 method: 'FullYear', |
| 2475 ambiguous: true, |
| 2476 multiplier: function(d) { |
| 2477 var adjust = d ? (d.isLeapYear() ? 1 : 0) : 0.25; |
| 2478 return (365 + adjust) * 24 * 60 * 60 * 1000; |
| 2479 } |
| 2480 }, |
| 2481 { |
| 2482 name: 'month', |
| 2483 error: 0.919, // Feb 1-28 over 1 month |
| 2484 method: 'Month', |
| 2485 ambiguous: true, |
| 2486 multiplier: function(d, ms) { |
| 2487 var days = 30.4375, inMonth; |
| 2488 if(d) { |
| 2489 inMonth = d.daysInMonth(); |
| 2490 if(ms <= inMonth.days()) { |
| 2491 days = inMonth; |
| 2492 } |
| 2493 } |
| 2494 return days * 24 * 60 * 60 * 1000; |
| 2495 } |
| 2496 }, |
| 2497 { |
| 2498 name: 'week', |
| 2499 method: 'ISOWeek', |
| 2500 multiplier: function() { |
| 2501 return 7 * 24 * 60 * 60 * 1000; |
| 2502 } |
| 2503 }, |
| 2504 { |
| 2505 name: 'day', |
| 2506 error: 0.958, // DST traversal over 1 day |
| 2507 method: 'Date', |
| 2508 ambiguous: true, |
| 2509 multiplier: function() { |
| 2510 return 24 * 60 * 60 * 1000; |
| 2511 } |
| 2512 }, |
| 2513 { |
| 2514 name: 'hour', |
| 2515 method: 'Hours', |
| 2516 multiplier: function() { |
| 2517 return 60 * 60 * 1000; |
| 2518 } |
| 2519 }, |
| 2520 { |
| 2521 name: 'minute', |
| 2522 method: 'Minutes', |
| 2523 multiplier: function() { |
| 2524 return 60 * 1000; |
| 2525 } |
| 2526 }, |
| 2527 { |
| 2528 name: 'second', |
| 2529 method: 'Seconds', |
| 2530 multiplier: function() { |
| 2531 return 1000; |
| 2532 } |
| 2533 }, |
| 2534 { |
| 2535 name: 'millisecond', |
| 2536 method: 'Milliseconds', |
| 2537 multiplier: function() { |
| 2538 return 1; |
| 2539 } |
| 2540 } |
| 2541 ]; |
| 2542 |
| 2543 |
| 2544 |
| 2545 |
| 2546 // Date Localization |
| 2547 |
| 2548 var Localizations = {}; |
| 2549 |
| 2550 // Localization object |
| 2551 |
| 2552 function Localization(l) { |
| 2553 simpleMerge(this, l); |
| 2554 this.compiledFormats = CoreDateFormats.concat(); |
| 2555 } |
| 2556 |
| 2557 Localization.prototype = { |
| 2558 |
| 2559 getMonth: function(n) { |
| 2560 if(isNumber(n)) { |
| 2561 return n - 1; |
| 2562 } else { |
| 2563 return this['months'].indexOf(n) % 12; |
| 2564 } |
| 2565 }, |
| 2566 |
| 2567 getWeekday: function(n) { |
| 2568 return this['weekdays'].indexOf(n) % 7; |
| 2569 }, |
| 2570 |
| 2571 getNumber: function(n) { |
| 2572 var i; |
| 2573 if(isNumber(n)) { |
| 2574 return n; |
| 2575 } else if(n && (i = this['numbers'].indexOf(n)) !== -1) { |
| 2576 return (i + 1) % 10; |
| 2577 } else { |
| 2578 return 1; |
| 2579 } |
| 2580 }, |
| 2581 |
| 2582 getNumericDate: function(n) { |
| 2583 var self = this; |
| 2584 return n.replace(regexp(this['num'], 'g'), function(d) { |
| 2585 var num = self.getNumber(d); |
| 2586 return num || ''; |
| 2587 }); |
| 2588 }, |
| 2589 |
| 2590 getUnitIndex: function(n) { |
| 2591 return this['units'].indexOf(n) % 8; |
| 2592 }, |
| 2593 |
| 2594 getRelativeFormat: function(adu) { |
| 2595 return this.convertAdjustedToFormat(adu, adu[2] > 0 ? 'future' : 'past'); |
| 2596 }, |
| 2597 |
| 2598 getDuration: function(ms) { |
| 2599 return this.convertAdjustedToFormat(getAdjustedUnit(ms), 'duration'); |
| 2600 }, |
| 2601 |
| 2602 hasVariant: function(code) { |
| 2603 code = code || this.code; |
| 2604 return code === 'en' || code === 'en-US' ? true : this['variant']; |
| 2605 }, |
| 2606 |
| 2607 matchAM: function(str) { |
| 2608 return str === this['ampm'][0]; |
| 2609 }, |
| 2610 |
| 2611 matchPM: function(str) { |
| 2612 return str && str === this['ampm'][1]; |
| 2613 }, |
| 2614 |
| 2615 convertAdjustedToFormat: function(adu, mode) { |
| 2616 var sign, unit, mult, |
| 2617 num = adu[0], |
| 2618 u = adu[1], |
| 2619 ms = adu[2], |
| 2620 format = this[mode] || this['relative']; |
| 2621 if(isFunction(format)) { |
| 2622 return format.call(this, num, u, ms, mode); |
| 2623 } |
| 2624 mult = this['plural'] && num > 1 ? 1 : 0; |
| 2625 unit = this['units'][mult * 8 + u] || this['units'][u]; |
| 2626 if(this['capitalizeUnit']) unit = simpleCapitalize(unit); |
| 2627 sign = this['modifiers'].filter(function(m) { return m.name == 'sign' && m
.value == (ms > 0 ? 1 : -1); })[0]; |
| 2628 return format.replace(/\{(.*?)\}/g, function(full, match) { |
| 2629 switch(match) { |
| 2630 case 'num': return num; |
| 2631 case 'unit': return unit; |
| 2632 case 'sign': return sign.src; |
| 2633 } |
| 2634 }); |
| 2635 }, |
| 2636 |
| 2637 getFormats: function() { |
| 2638 return this.cachedFormat ? [this.cachedFormat].concat(this.compiledFormats
) : this.compiledFormats; |
| 2639 }, |
| 2640 |
| 2641 addFormat: function(src, allowsTime, match, variant, iso) { |
| 2642 var to = match || [], loc = this, time, timeMarkers, lastIsNumeral; |
| 2643 |
| 2644 src = src.replace(/\s+/g, '[,. ]*'); |
| 2645 src = src.replace(/\{([^,]+?)\}/g, function(all, k) { |
| 2646 var value, arr, result, |
| 2647 opt = k.match(/\?$/), |
| 2648 nc = k.match(/^(\d+)\??$/), |
| 2649 slice = k.match(/(\d)(?:-(\d))?/), |
| 2650 key = k.replace(/[^a-z]+$/, ''); |
| 2651 if(nc) { |
| 2652 value = loc['tokens'][nc[1]]; |
| 2653 } else if(loc[key]) { |
| 2654 value = loc[key]; |
| 2655 } else if(loc[key + 's']) { |
| 2656 value = loc[key + 's']; |
| 2657 if(slice) { |
| 2658 // Can't use filter here as Prototype hijacks the method and doesn't |
| 2659 // pass an index, so use a simple loop instead! |
| 2660 arr = []; |
| 2661 value.forEach(function(m, i) { |
| 2662 var mod = i % (loc['units'] ? 8 : value.length); |
| 2663 if(mod >= slice[1] && mod <= (slice[2] || slice[1])) { |
| 2664 arr.push(m); |
| 2665 } |
| 2666 }); |
| 2667 value = arr; |
| 2668 } |
| 2669 value = arrayToAlternates(value); |
| 2670 } |
| 2671 if(nc) { |
| 2672 result = '(?:' + value + ')'; |
| 2673 } else { |
| 2674 if(!match) { |
| 2675 to.push(key); |
| 2676 } |
| 2677 result = '(' + value + ')'; |
| 2678 } |
| 2679 if(opt) { |
| 2680 result += '?'; |
| 2681 } |
| 2682 return result; |
| 2683 }); |
| 2684 if(allowsTime) { |
| 2685 time = prepareTime(RequiredTime, loc, iso); |
| 2686 timeMarkers = ['t','[\\s\\u3000]'].concat(loc['timeMarker']); |
| 2687 lastIsNumeral = src.match(/\\d\{\d,\d\}\)+\??$/); |
| 2688 addDateInputFormat(loc, '(?:' + time + ')[,\\s\\u3000]+?' + src, TimeFor
mat.concat(to), variant); |
| 2689 addDateInputFormat(loc, src + '(?:[,\\s]*(?:' + timeMarkers.join('|') +
(lastIsNumeral ? '+' : '*') +')' + time + ')?', to.concat(TimeFormat), variant); |
| 2690 } else { |
| 2691 addDateInputFormat(loc, src, to, variant); |
| 2692 } |
| 2693 } |
| 2694 |
| 2695 }; |
| 2696 |
| 2697 |
| 2698 // Localization helpers |
| 2699 |
| 2700 function getLocalization(localeCode, fallback) { |
| 2701 var loc; |
| 2702 if(!isString(localeCode)) localeCode = ''; |
| 2703 loc = Localizations[localeCode] || Localizations[localeCode.slice(0,2)]; |
| 2704 if(fallback === false && !loc) { |
| 2705 throw new TypeError('Invalid locale.'); |
| 2706 } |
| 2707 return loc || CurrentLocalization; |
| 2708 } |
| 2709 |
| 2710 function setLocalization(localeCode, set) { |
| 2711 var loc, canAbbreviate; |
| 2712 |
| 2713 function initializeField(name) { |
| 2714 var val = loc[name]; |
| 2715 if(isString(val)) { |
| 2716 loc[name] = val.split(','); |
| 2717 } else if(!val) { |
| 2718 loc[name] = []; |
| 2719 } |
| 2720 } |
| 2721 |
| 2722 function eachAlternate(str, fn) { |
| 2723 str = str.split('+').map(function(split) { |
| 2724 return split.replace(/(.+):(.+)$/, function(full, base, suffixes) { |
| 2725 return suffixes.split('|').map(function(suffix) { |
| 2726 return base + suffix; |
| 2727 }).join('|'); |
| 2728 }); |
| 2729 }).join('|'); |
| 2730 return str.split('|').forEach(fn); |
| 2731 } |
| 2732 |
| 2733 function setArray(name, abbreviate, multiple) { |
| 2734 var arr = []; |
| 2735 loc[name].forEach(function(full, i) { |
| 2736 if(abbreviate) { |
| 2737 full += '+' + full.slice(0,3); |
| 2738 } |
| 2739 eachAlternate(full, function(day, j) { |
| 2740 arr[j * multiple + i] = day.toLowerCase(); |
| 2741 }); |
| 2742 }); |
| 2743 loc[name] = arr; |
| 2744 } |
| 2745 |
| 2746 function getDigit(start, stop, allowNumbers) { |
| 2747 var str = '\\d{' + start + ',' + stop + '}'; |
| 2748 if(allowNumbers) str += '|(?:' + arrayToAlternates(loc['numbers']) + ')+'; |
| 2749 return str; |
| 2750 } |
| 2751 |
| 2752 function getNum() { |
| 2753 var arr = ['-?\\d+'].concat(loc['articles']); |
| 2754 if(loc['numbers']) arr = arr.concat(loc['numbers']); |
| 2755 return arrayToAlternates(arr); |
| 2756 } |
| 2757 |
| 2758 function setDefault(name, value) { |
| 2759 loc[name] = loc[name] || value; |
| 2760 } |
| 2761 |
| 2762 function setModifiers() { |
| 2763 var arr = []; |
| 2764 loc.modifiersByName = {}; |
| 2765 loc['modifiers'].push({ 'name': 'day', 'src': 'yesterday', 'value': -1 }); |
| 2766 loc['modifiers'].push({ 'name': 'day', 'src': 'today', 'value': 0 }); |
| 2767 loc['modifiers'].push({ 'name': 'day', 'src': 'tomorrow', 'value': 1 }); |
| 2768 loc['modifiers'].forEach(function(modifier) { |
| 2769 var name = modifier.name; |
| 2770 eachAlternate(modifier.src, function(t) { |
| 2771 var locEntry = loc[name]; |
| 2772 loc.modifiersByName[t] = modifier; |
| 2773 arr.push({ name: name, src: t, value: modifier.value }); |
| 2774 loc[name] = locEntry ? locEntry + '|' + t : t; |
| 2775 }); |
| 2776 }); |
| 2777 loc['day'] += '|' + arrayToAlternates(loc['weekdays']); |
| 2778 loc['modifiers'] = arr; |
| 2779 } |
| 2780 |
| 2781 // Initialize the locale |
| 2782 loc = new Localization(set); |
| 2783 initializeField('modifiers'); |
| 2784 'months,weekdays,units,numbers,articles,tokens,timeMarker,ampm,timeSuffixes,
dateParse,timeParse'.split(',').forEach(initializeField); |
| 2785 |
| 2786 canAbbreviate = !loc['monthSuffix']; |
| 2787 |
| 2788 setArray('months', canAbbreviate, 12); |
| 2789 setArray('weekdays', canAbbreviate, 7); |
| 2790 setArray('units', false, 8); |
| 2791 setArray('numbers', false, 10); |
| 2792 |
| 2793 setDefault('code', localeCode); |
| 2794 setDefault('date', getDigit(1,2, loc['digitDate'])); |
| 2795 setDefault('year', "'\\d{2}|" + getDigit(4,4)); |
| 2796 setDefault('num', getNum()); |
| 2797 |
| 2798 setModifiers(); |
| 2799 |
| 2800 if(loc['monthSuffix']) { |
| 2801 loc['month'] = getDigit(1,2); |
| 2802 loc['months'] = '1,2,3,4,5,6,7,8,9,10,11,12'.split(',').map(function(n) {
return n + loc['monthSuffix']; }); |
| 2803 } |
| 2804 loc['full_month'] = getDigit(1,2) + '|' + arrayToAlternates(loc['months']); |
| 2805 |
| 2806 // The order of these formats is very important. Order is reversed so format
s that come |
| 2807 // later will take precedence over formats that come before. This generally
means that |
| 2808 // more specific formats should come later, however, the {year} format shoul
d come before |
| 2809 // {day}, as 2011 needs to be parsed as a year (2011) and not date (20) + ho
urs (11) |
| 2810 |
| 2811 // If the locale has time suffixes then add a time only format for that loca
le |
| 2812 // that is separate from the core English-based one. |
| 2813 if(loc['timeSuffixes'].length > 0) { |
| 2814 loc.addFormat(prepareTime(RequiredTime, loc), false, TimeFormat) |
| 2815 } |
| 2816 |
| 2817 loc.addFormat('{day}', true); |
| 2818 loc.addFormat('{month}' + (loc['monthSuffix'] || '')); |
| 2819 loc.addFormat('{year}' + (loc['yearSuffix'] || '')); |
| 2820 |
| 2821 loc['timeParse'].forEach(function(src) { |
| 2822 loc.addFormat(src, true); |
| 2823 }); |
| 2824 |
| 2825 loc['dateParse'].forEach(function(src) { |
| 2826 loc.addFormat(src); |
| 2827 }); |
| 2828 |
| 2829 return Localizations[localeCode] = loc; |
| 2830 } |
| 2831 |
| 2832 |
| 2833 // General helpers |
| 2834 |
| 2835 function addDateInputFormat(locale, format, match, variant) { |
| 2836 locale.compiledFormats.unshift({ |
| 2837 variant: variant, |
| 2838 locale: locale, |
| 2839 reg: regexp('^' + format + '$', 'i'), |
| 2840 to: match |
| 2841 }); |
| 2842 } |
| 2843 |
| 2844 function simpleCapitalize(str) { |
| 2845 return str.slice(0,1).toUpperCase() + str.slice(1); |
| 2846 } |
| 2847 |
| 2848 function arrayToAlternates(arr) { |
| 2849 return arr.filter(function(el) { |
| 2850 return !!el; |
| 2851 }).join('|'); |
| 2852 } |
| 2853 |
| 2854 function getNewDate() { |
| 2855 var fn = date.SugarNewDate; |
| 2856 return fn ? fn() : new date; |
| 2857 } |
| 2858 |
| 2859 // Date argument helpers |
| 2860 |
| 2861 function collectDateArguments(args, allowDuration) { |
| 2862 var obj; |
| 2863 if(isObjectType(args[0])) { |
| 2864 return args; |
| 2865 } else if (isNumber(args[0]) && !isNumber(args[1])) { |
| 2866 return [args[0]]; |
| 2867 } else if (isString(args[0]) && allowDuration) { |
| 2868 return [getDateParamsFromString(args[0]), args[1]]; |
| 2869 } |
| 2870 obj = {}; |
| 2871 DateArgumentUnits.forEach(function(u,i) { |
| 2872 obj[u.name] = args[i]; |
| 2873 }); |
| 2874 return [obj]; |
| 2875 } |
| 2876 |
| 2877 function getDateParamsFromString(str, num) { |
| 2878 var match, params = {}; |
| 2879 match = str.match(/^(\d+)?\s?(\w+?)s?$/i); |
| 2880 if(match) { |
| 2881 if(isUndefined(num)) { |
| 2882 num = parseInt(match[1]) || 1; |
| 2883 } |
| 2884 params[match[2].toLowerCase()] = num; |
| 2885 } |
| 2886 return params; |
| 2887 } |
| 2888 |
| 2889 // Date iteration helpers |
| 2890 |
| 2891 function iterateOverDateUnits(fn, from, to) { |
| 2892 var i, unit; |
| 2893 if(isUndefined(to)) to = DateUnitsReversed.length; |
| 2894 for(i = from || 0; i < to; i++) { |
| 2895 unit = DateUnitsReversed[i]; |
| 2896 if(fn(unit.name, unit, i) === false) { |
| 2897 break; |
| 2898 } |
| 2899 } |
| 2900 } |
| 2901 |
| 2902 // Date parsing helpers |
| 2903 |
| 2904 function getFormatMatch(match, arr) { |
| 2905 var obj = {}, value, num; |
| 2906 arr.forEach(function(key, i) { |
| 2907 value = match[i + 1]; |
| 2908 if(isUndefined(value) || value === '') return; |
| 2909 if(key === 'year') { |
| 2910 obj.yearAsString = value.replace(/'/, ''); |
| 2911 } |
| 2912 num = parseFloat(value.replace(/'/, '').replace(/,/, '.')); |
| 2913 obj[key] = !isNaN(num) ? num : value.toLowerCase(); |
| 2914 }); |
| 2915 return obj; |
| 2916 } |
| 2917 |
| 2918 function cleanDateInput(str) { |
| 2919 str = str.trim().replace(/^just (?=now)|\.+$/i, ''); |
| 2920 return convertAsianDigits(str); |
| 2921 } |
| 2922 |
| 2923 function convertAsianDigits(str) { |
| 2924 return str.replace(AsianDigitReg, function(full, disallowed, match) { |
| 2925 var sum = 0, place = 1, lastWasHolder, lastHolder; |
| 2926 if(disallowed) return full; |
| 2927 match.split('').reverse().forEach(function(letter) { |
| 2928 var value = AsianDigitMap[letter], holder = value > 9; |
| 2929 if(holder) { |
| 2930 if(lastWasHolder) sum += place; |
| 2931 place *= value / (lastHolder || 1); |
| 2932 lastHolder = value; |
| 2933 } else { |
| 2934 if(lastWasHolder === false) { |
| 2935 place *= 10; |
| 2936 } |
| 2937 sum += place * value; |
| 2938 } |
| 2939 lastWasHolder = holder; |
| 2940 }); |
| 2941 if(lastWasHolder) sum += place; |
| 2942 return sum; |
| 2943 }); |
| 2944 } |
| 2945 |
| 2946 function getExtendedDate(f, localeCode, prefer, forceUTC) { |
| 2947 var d, relative, baseLocalization, afterCallbacks, loc, set, unit, unitIndex
, weekday, num, tmp; |
| 2948 |
| 2949 d = getNewDate(); |
| 2950 afterCallbacks = []; |
| 2951 |
| 2952 function afterDateSet(fn) { |
| 2953 afterCallbacks.push(fn); |
| 2954 } |
| 2955 |
| 2956 function fireCallbacks() { |
| 2957 afterCallbacks.forEach(function(fn) { |
| 2958 fn.call(); |
| 2959 }); |
| 2960 } |
| 2961 |
| 2962 function setWeekdayOfMonth() { |
| 2963 var w = d.getWeekday(); |
| 2964 d.setWeekday((7 * (set['num'] - 1)) + (w > weekday ? weekday + 7 : weekday
)); |
| 2965 } |
| 2966 |
| 2967 function setUnitEdge() { |
| 2968 var modifier = loc.modifiersByName[set['edge']]; |
| 2969 iterateOverDateUnits(function(name) { |
| 2970 if(isDefined(set[name])) { |
| 2971 unit = name; |
| 2972 return false; |
| 2973 } |
| 2974 }, 4); |
| 2975 if(unit === 'year') set.specificity = 'month'; |
| 2976 else if(unit === 'month' || unit === 'week') set.specificity = 'day'; |
| 2977 d[(modifier.value < 0 ? 'endOf' : 'beginningOf') + simpleCapitalize(unit)]
(); |
| 2978 // This value of -2 is arbitrary but it's a nice clean way to hook into th
is system. |
| 2979 if(modifier.value === -2) d.reset(); |
| 2980 } |
| 2981 |
| 2982 function separateAbsoluteUnits() { |
| 2983 var params; |
| 2984 iterateOverDateUnits(function(name, u, i) { |
| 2985 if(name === 'day') name = 'date'; |
| 2986 if(isDefined(set[name])) { |
| 2987 // If there is a time unit set that is more specific than |
| 2988 // the matched unit we have a string like "5:30am in 2 minutes", |
| 2989 // which is meaningless, so invalidate the date... |
| 2990 if(i >= unitIndex) { |
| 2991 invalidateDate(d); |
| 2992 return false; |
| 2993 } |
| 2994 // ...otherwise set the params to set the absolute date |
| 2995 // as a callback after the relative date has been set. |
| 2996 params = params || {}; |
| 2997 params[name] = set[name]; |
| 2998 delete set[name]; |
| 2999 } |
| 3000 }); |
| 3001 if(params) { |
| 3002 afterDateSet(function() { |
| 3003 d.set(params, true); |
| 3004 }); |
| 3005 } |
| 3006 } |
| 3007 |
| 3008 d.utc(forceUTC); |
| 3009 |
| 3010 if(isDate(f)) { |
| 3011 // If the source here is already a date object, then the operation |
| 3012 // is the same as cloning the date, which preserves the UTC flag. |
| 3013 d.utc(f.isUTC()).setTime(f.getTime()); |
| 3014 } else if(isNumber(f)) { |
| 3015 d.setTime(f); |
| 3016 } else if(isObjectType(f)) { |
| 3017 d.set(f, true); |
| 3018 set = f; |
| 3019 } else if(isString(f)) { |
| 3020 |
| 3021 // The act of getting the localization will pre-initialize |
| 3022 // if it is missing and add the required formats. |
| 3023 baseLocalization = getLocalization(localeCode); |
| 3024 |
| 3025 // Clean the input and convert Kanji based numerals if they exist. |
| 3026 f = cleanDateInput(f); |
| 3027 |
| 3028 if(baseLocalization) { |
| 3029 iterateOverObject(baseLocalization.getFormats(), function(i, dif) { |
| 3030 var match = f.match(dif.reg); |
| 3031 if(match) { |
| 3032 |
| 3033 loc = dif.locale; |
| 3034 set = getFormatMatch(match, dif.to, loc); |
| 3035 loc.cachedFormat = dif; |
| 3036 |
| 3037 |
| 3038 if(set['utc']) { |
| 3039 d.utc(); |
| 3040 } |
| 3041 |
| 3042 if(set.timestamp) { |
| 3043 set = set.timestamp; |
| 3044 return false; |
| 3045 } |
| 3046 |
| 3047 // If there's a variant (crazy Endian American format), swap the mon
th and day. |
| 3048 if(dif.variant && !isString(set['month']) && (isString(set['date'])
|| baseLocalization.hasVariant(localeCode))) { |
| 3049 tmp = set['month']; |
| 3050 set['month'] = set['date']; |
| 3051 set['date'] = tmp; |
| 3052 } |
| 3053 |
| 3054 // If the year is 2 digits then get the implied century. |
| 3055 if(set['year'] && set.yearAsString.length === 2) { |
| 3056 set['year'] = getYearFromAbbreviation(set['year']); |
| 3057 } |
| 3058 |
| 3059 // Set the month which may be localized. |
| 3060 if(set['month']) { |
| 3061 set['month'] = loc.getMonth(set['month']); |
| 3062 if(set['shift'] && !set['unit']) set['unit'] = loc['units'][7]; |
| 3063 } |
| 3064 |
| 3065 // If there is both a weekday and a date, the date takes precedence. |
| 3066 if(set['weekday'] && set['date']) { |
| 3067 delete set['weekday']; |
| 3068 // Otherwise set a localized weekday. |
| 3069 } else if(set['weekday']) { |
| 3070 set['weekday'] = loc.getWeekday(set['weekday']); |
| 3071 if(set['shift'] && !set['unit']) set['unit'] = loc['units'][5]; |
| 3072 } |
| 3073 |
| 3074 // Relative day localizations such as "today" and "tomorrow". |
| 3075 if(set['day'] && (tmp = loc.modifiersByName[set['day']])) { |
| 3076 set['day'] = tmp.value; |
| 3077 d.reset(); |
| 3078 relative = true; |
| 3079 // If the day is a weekday, then set that instead. |
| 3080 } else if(set['day'] && (weekday = loc.getWeekday(set['day'])) > -1)
{ |
| 3081 delete set['day']; |
| 3082 if(set['num'] && set['month']) { |
| 3083 // If we have "the 2nd tuesday of June", set the day to the begi
nning of the month, then |
| 3084 // set the weekday after all other properties have been set. The
weekday needs to be set |
| 3085 // after the actual set because it requires overriding the "pref
er" argument which |
| 3086 // could unintentionally send the year into the future, past, et
c. |
| 3087 afterDateSet(setWeekdayOfMonth); |
| 3088 set['day'] = 1; |
| 3089 } else { |
| 3090 set['weekday'] = weekday; |
| 3091 } |
| 3092 } |
| 3093 |
| 3094 if(set['date'] && !isNumber(set['date'])) { |
| 3095 set['date'] = loc.getNumericDate(set['date']); |
| 3096 } |
| 3097 |
| 3098 // If the time is 1pm-11pm advance the time by 12 hours. |
| 3099 if(loc.matchPM(set['ampm']) && set['hour'] < 12) { |
| 3100 set['hour'] += 12; |
| 3101 } else if(loc.matchAM(set['ampm']) && set['hour'] === 12) { |
| 3102 set['hour'] = 0; |
| 3103 } |
| 3104 |
| 3105 // Adjust for timezone offset |
| 3106 if('offset_hours' in set || 'offset_minutes' in set) { |
| 3107 d.utc(); |
| 3108 set['offset_minutes'] = set['offset_minutes'] || 0; |
| 3109 set['offset_minutes'] += set['offset_hours'] * 60; |
| 3110 if(set['offset_sign'] === '-') { |
| 3111 set['offset_minutes'] *= -1; |
| 3112 } |
| 3113 set['minute'] -= set['offset_minutes']; |
| 3114 } |
| 3115 |
| 3116 // Date has a unit like "days", "months", etc. are all relative to t
he current date. |
| 3117 if(set['unit']) { |
| 3118 relative = true; |
| 3119 num = loc.getNumber(set['num']); |
| 3120 unitIndex = loc.getUnitIndex(set['unit']); |
| 3121 unit = English['units'][unitIndex]; |
| 3122 |
| 3123 // Formats like "the 15th of last month" or "6:30pm of next week" |
| 3124 // contain absolute units in addition to relative ones, so separat
e |
| 3125 // them here, remove them from the params, and set up a callback t
o |
| 3126 // set them after the relative ones have been set. |
| 3127 separateAbsoluteUnits(); |
| 3128 |
| 3129 // Shift and unit, ie "next month", "last week", etc. |
| 3130 if(set['shift']) { |
| 3131 num *= (tmp = loc.modifiersByName[set['shift']]) ? tmp.value : 0
; |
| 3132 } |
| 3133 |
| 3134 // Unit and sign, ie "months ago", "weeks from now", etc. |
| 3135 if(set['sign'] && (tmp = loc.modifiersByName[set['sign']])) { |
| 3136 num *= tmp.value; |
| 3137 } |
| 3138 |
| 3139 // Units can be with non-relative dates, set here. ie "the day aft
er monday" |
| 3140 if(isDefined(set['weekday'])) { |
| 3141 d.set({'weekday': set['weekday'] }, true); |
| 3142 delete set['weekday']; |
| 3143 } |
| 3144 |
| 3145 // Finally shift the unit. |
| 3146 set[unit] = (set[unit] || 0) + num; |
| 3147 } |
| 3148 |
| 3149 // If there is an "edge" it needs to be set after the |
| 3150 // other fields are set. ie "the end of February" |
| 3151 if(set['edge']) { |
| 3152 afterDateSet(setUnitEdge); |
| 3153 } |
| 3154 |
| 3155 if(set['year_sign'] === '-') { |
| 3156 set['year'] *= -1; |
| 3157 } |
| 3158 |
| 3159 iterateOverDateUnits(function(name, unit, i) { |
| 3160 var value = set[name], fraction = value % 1; |
| 3161 if(fraction) { |
| 3162 set[DateUnitsReversed[i - 1].name] = round(fraction * (name ===
'second' ? 1000 : 60)); |
| 3163 set[name] = floor(value); |
| 3164 } |
| 3165 }, 1, 4); |
| 3166 return false; |
| 3167 } |
| 3168 }); |
| 3169 } |
| 3170 if(!set) { |
| 3171 // The Date constructor does something tricky like checking the number |
| 3172 // of arguments so simply passing in undefined won't work. |
| 3173 if(f !== 'now') { |
| 3174 d = new date(f); |
| 3175 } |
| 3176 if(forceUTC) { |
| 3177 // Falling back to system date here which cannot be parsed as UTC, |
| 3178 // so if we're forcing UTC then simply add the offset. |
| 3179 d.addMinutes(-d.getTimezoneOffset()); |
| 3180 } |
| 3181 } else if(relative) { |
| 3182 d.advance(set); |
| 3183 } else { |
| 3184 if(d._utc) { |
| 3185 // UTC times can traverse into other days or even months, |
| 3186 // so preemtively reset the time here to prevent this. |
| 3187 d.reset(); |
| 3188 } |
| 3189 updateDate(d, set, true, false, prefer); |
| 3190 } |
| 3191 fireCallbacks(); |
| 3192 // A date created by parsing a string presumes that the format *itself* is
UTC, but |
| 3193 // not that the date, once created, should be manipulated as such. In othe
r words, |
| 3194 // if you are creating a date object from a server time "2012-11-15T12:00:
00Z", |
| 3195 // in the majority of cases you are using it to create a date that will, a
fter creation, |
| 3196 // be manipulated as local, so reset the utc flag here. |
| 3197 d.utc(false); |
| 3198 } |
| 3199 return { |
| 3200 date: d, |
| 3201 set: set |
| 3202 } |
| 3203 } |
| 3204 |
| 3205 // If the year is two digits, add the most appropriate century prefix. |
| 3206 function getYearFromAbbreviation(year) { |
| 3207 return round(callDateGet(getNewDate(), 'FullYear') / 100) * 100 - round(year
/ 100) * 100 + year; |
| 3208 } |
| 3209 |
| 3210 function getShortHour(d) { |
| 3211 var hours = callDateGet(d, 'Hours'); |
| 3212 return hours === 0 ? 12 : hours - (floor(hours / 13) * 12); |
| 3213 } |
| 3214 |
| 3215 // weeksSince won't work here as the result needs to be floored, not rounded. |
| 3216 function getWeekNumber(date) { |
| 3217 date = date.clone(); |
| 3218 var dow = callDateGet(date, 'Day') || 7; |
| 3219 date.addDays(4 - dow).reset(); |
| 3220 return 1 + floor(date.daysSince(date.clone().beginningOfYear()) / 7); |
| 3221 } |
| 3222 |
| 3223 function getAdjustedUnit(ms) { |
| 3224 var next, ams = abs(ms), value = ams, unitIndex = 0; |
| 3225 iterateOverDateUnits(function(name, unit, i) { |
| 3226 next = floor(withPrecision(ams / unit.multiplier(), 1)); |
| 3227 if(next >= 1) { |
| 3228 value = next; |
| 3229 unitIndex = i; |
| 3230 } |
| 3231 }, 1); |
| 3232 return [value, unitIndex, ms]; |
| 3233 } |
| 3234 |
| 3235 function getRelativeWithMonthFallback(date) { |
| 3236 var adu = getAdjustedUnit(date.millisecondsFromNow()); |
| 3237 if(allowMonthFallback(date, adu)) { |
| 3238 // If the adjusted unit is in months, then better to use |
| 3239 // the "monthsfromNow" which applies a special error margin |
| 3240 // for edge cases such as Jan-09 - Mar-09 being less than |
| 3241 // 2 months apart (when using a strict numeric definition). |
| 3242 // The third "ms" element in the array will handle the sign |
| 3243 // (past or future), so simply take the absolute value here. |
| 3244 adu[0] = abs(date.monthsFromNow()); |
| 3245 adu[1] = 6; |
| 3246 } |
| 3247 return adu; |
| 3248 } |
| 3249 |
| 3250 function allowMonthFallback(date, adu) { |
| 3251 // Allow falling back to monthsFromNow if the unit is in months... |
| 3252 return adu[1] === 6 || |
| 3253 // ...or if it's === 4 weeks and there are more days than in the given month |
| 3254 (adu[1] === 5 && adu[0] === 4 && date.daysFromNow() >= getNewDate().daysInMo
nth()); |
| 3255 } |
| 3256 |
| 3257 |
| 3258 // Date format token helpers |
| 3259 |
| 3260 function createMeridianTokens(slice, caps) { |
| 3261 var fn = function(d, localeCode) { |
| 3262 var hours = callDateGet(d, 'Hours'); |
| 3263 return getLocalization(localeCode)['ampm'][floor(hours / 12)] || ''; |
| 3264 } |
| 3265 createFormatToken('t', fn, 1); |
| 3266 createFormatToken('tt', fn); |
| 3267 createFormatToken('T', fn, 1, 1); |
| 3268 createFormatToken('TT', fn, null, 2); |
| 3269 } |
| 3270 |
| 3271 function createWeekdayTokens(slice, caps) { |
| 3272 var fn = function(d, localeCode) { |
| 3273 var dow = callDateGet(d, 'Day'); |
| 3274 return getLocalization(localeCode)['weekdays'][dow]; |
| 3275 } |
| 3276 createFormatToken('dow', fn, 3); |
| 3277 createFormatToken('Dow', fn, 3, 1); |
| 3278 createFormatToken('weekday', fn); |
| 3279 createFormatToken('Weekday', fn, null, 1); |
| 3280 } |
| 3281 |
| 3282 function createMonthTokens(slice, caps) { |
| 3283 createMonthToken('mon', 0, 3); |
| 3284 createMonthToken('month', 0); |
| 3285 |
| 3286 // For inflected month forms, namely Russian. |
| 3287 createMonthToken('month2', 1); |
| 3288 createMonthToken('month3', 2); |
| 3289 } |
| 3290 |
| 3291 function createMonthToken(token, multiplier, slice) { |
| 3292 var fn = function(d, localeCode) { |
| 3293 var month = callDateGet(d, 'Month'); |
| 3294 return getLocalization(localeCode)['months'][month + (multiplier * 12)]; |
| 3295 }; |
| 3296 createFormatToken(token, fn, slice); |
| 3297 createFormatToken(simpleCapitalize(token), fn, slice, 1); |
| 3298 } |
| 3299 |
| 3300 function createFormatToken(t, fn, slice, caps) { |
| 3301 DateFormatTokens[t] = function(d, localeCode) { |
| 3302 var str = fn(d, localeCode); |
| 3303 if(slice) str = str.slice(0, slice); |
| 3304 if(caps) str = str.slice(0, caps).toUpperCase() + str.slice(caps); |
| 3305 return str; |
| 3306 } |
| 3307 } |
| 3308 |
| 3309 function createPaddedToken(t, fn, ms) { |
| 3310 DateFormatTokens[t] = fn; |
| 3311 DateFormatTokens[t + t] = function (d, localeCode) { |
| 3312 return padNumber(fn(d, localeCode), 2); |
| 3313 }; |
| 3314 if(ms) { |
| 3315 DateFormatTokens[t + t + t] = function (d, localeCode) { |
| 3316 return padNumber(fn(d, localeCode), 3); |
| 3317 }; |
| 3318 DateFormatTokens[t + t + t + t] = function (d, localeCode) { |
| 3319 return padNumber(fn(d, localeCode), 4); |
| 3320 }; |
| 3321 } |
| 3322 } |
| 3323 |
| 3324 |
| 3325 // Date formatting helpers |
| 3326 |
| 3327 function buildCompiledOutputFormat(format) { |
| 3328 var match = format.match(/(\{\w+\})|[^{}]+/g); |
| 3329 CompiledOutputFormats[format] = match.map(function(p) { |
| 3330 p.replace(/\{(\w+)\}/, function(full, token) { |
| 3331 p = DateFormatTokens[token] || token; |
| 3332 return token; |
| 3333 }); |
| 3334 return p; |
| 3335 }); |
| 3336 } |
| 3337 |
| 3338 function executeCompiledOutputFormat(date, format, localeCode) { |
| 3339 var compiledFormat, length, i, t, result = ''; |
| 3340 compiledFormat = CompiledOutputFormats[format]; |
| 3341 for(i = 0, length = compiledFormat.length; i < length; i++) { |
| 3342 t = compiledFormat[i]; |
| 3343 result += isFunction(t) ? t(date, localeCode) : t; |
| 3344 } |
| 3345 return result; |
| 3346 } |
| 3347 |
| 3348 function formatDate(date, format, relative, localeCode) { |
| 3349 var adu; |
| 3350 if(!date.isValid()) { |
| 3351 return 'Invalid Date'; |
| 3352 } else if(Date[format]) { |
| 3353 format = Date[format]; |
| 3354 } else if(isFunction(format)) { |
| 3355 adu = getRelativeWithMonthFallback(date); |
| 3356 format = format.apply(date, adu.concat(getLocalization(localeCode))); |
| 3357 } |
| 3358 if(!format && relative) { |
| 3359 adu = adu || getRelativeWithMonthFallback(date); |
| 3360 // Adjust up if time is in ms, as this doesn't |
| 3361 // look very good for a standard relative date. |
| 3362 if(adu[1] === 0) { |
| 3363 adu[1] = 1; |
| 3364 adu[0] = 1; |
| 3365 } |
| 3366 return getLocalization(localeCode).getRelativeFormat(adu); |
| 3367 } |
| 3368 format = format || 'long'; |
| 3369 if(format === 'short' || format === 'long' || format === 'full') { |
| 3370 format = getLocalization(localeCode)[format]; |
| 3371 } |
| 3372 |
| 3373 if(!CompiledOutputFormats[format]) { |
| 3374 buildCompiledOutputFormat(format); |
| 3375 } |
| 3376 |
| 3377 return executeCompiledOutputFormat(date, format, localeCode); |
| 3378 } |
| 3379 |
| 3380 // Date comparison helpers |
| 3381 |
| 3382 function compareDate(d, find, localeCode, buffer, forceUTC) { |
| 3383 var p, t, min, max, override, capitalized, accuracy = 0, loBuffer = 0, hiBuf
fer = 0; |
| 3384 p = getExtendedDate(find, localeCode, null, forceUTC); |
| 3385 if(buffer > 0) { |
| 3386 loBuffer = hiBuffer = buffer; |
| 3387 override = true; |
| 3388 } |
| 3389 if(!p.date.isValid()) return false; |
| 3390 if(p.set && p.set.specificity) { |
| 3391 DateUnits.forEach(function(u, i) { |
| 3392 if(u.name === p.set.specificity) { |
| 3393 accuracy = u.multiplier(p.date, d - p.date) - 1; |
| 3394 } |
| 3395 }); |
| 3396 capitalized = simpleCapitalize(p.set.specificity); |
| 3397 if(p.set['edge'] || p.set['shift']) { |
| 3398 p.date['beginningOf' + capitalized](); |
| 3399 } |
| 3400 if(p.set.specificity === 'month') { |
| 3401 max = p.date.clone()['endOf' + capitalized]().getTime(); |
| 3402 } |
| 3403 if(!override && p.set['sign'] && p.set.specificity != 'millisecond') { |
| 3404 // If the time is relative, there can occasionally be an disparity betwe
en the relative date |
| 3405 // and "now", which it is being compared to, so set an extra buffer to a
ccount for this. |
| 3406 loBuffer = 50; |
| 3407 hiBuffer = -50; |
| 3408 } |
| 3409 } |
| 3410 t = d.getTime(); |
| 3411 min = p.date.getTime(); |
| 3412 max = max || (min + accuracy); |
| 3413 max = compensateForTimezoneTraversal(d, min, max); |
| 3414 return t >= (min - loBuffer) && t <= (max + hiBuffer); |
| 3415 } |
| 3416 |
| 3417 function compensateForTimezoneTraversal(d, min, max) { |
| 3418 var dMin, dMax, minOffset, maxOffset; |
| 3419 dMin = new date(min); |
| 3420 dMax = new date(max).utc(d.isUTC()); |
| 3421 if(callDateGet(dMax, 'Hours') !== 23) { |
| 3422 minOffset = dMin.getTimezoneOffset(); |
| 3423 maxOffset = dMax.getTimezoneOffset(); |
| 3424 if(minOffset !== maxOffset) { |
| 3425 max += (maxOffset - minOffset).minutes(); |
| 3426 } |
| 3427 } |
| 3428 return max; |
| 3429 } |
| 3430 |
| 3431 function updateDate(d, params, reset, advance, prefer) { |
| 3432 var weekday, specificityIndex; |
| 3433 |
| 3434 function getParam(key) { |
| 3435 return isDefined(params[key]) ? params[key] : params[key + 's']; |
| 3436 } |
| 3437 |
| 3438 function paramExists(key) { |
| 3439 return isDefined(getParam(key)); |
| 3440 } |
| 3441 |
| 3442 function uniqueParamExists(key, isDay) { |
| 3443 return paramExists(key) || (isDay && paramExists('weekday')); |
| 3444 } |
| 3445 |
| 3446 function canDisambiguate() { |
| 3447 switch(prefer) { |
| 3448 case -1: return d > getNewDate(); |
| 3449 case 1: return d < getNewDate(); |
| 3450 } |
| 3451 } |
| 3452 |
| 3453 if(isNumber(params) && advance) { |
| 3454 // If param is a number and we're advancing, the number is presumed to be
milliseconds. |
| 3455 params = { 'milliseconds': params }; |
| 3456 } else if(isNumber(params)) { |
| 3457 // Otherwise just set the timestamp and return. |
| 3458 d.setTime(params); |
| 3459 return d; |
| 3460 } |
| 3461 |
| 3462 // "date" can also be passed for the day |
| 3463 if(isDefined(params['date'])) { |
| 3464 params['day'] = params['date']; |
| 3465 } |
| 3466 |
| 3467 // Reset any unit lower than the least specific unit set. Do not do this for
weeks |
| 3468 // or for years. This needs to be performed before the acutal setting of the
date |
| 3469 // because the order needs to be reversed in order to get the lowest specifi
city, |
| 3470 // also because higher order units can be overwritten by lower order units,
such |
| 3471 // as setting hour: 3, minute: 345, etc. |
| 3472 iterateOverDateUnits(function(name, unit, i) { |
| 3473 var isDay = name === 'day'; |
| 3474 if(uniqueParamExists(name, isDay)) { |
| 3475 params.specificity = name; |
| 3476 specificityIndex = +i; |
| 3477 return false; |
| 3478 } else if(reset && name !== 'week' && (!isDay || !paramExists('week'))) { |
| 3479 // Days are relative to months, not weeks, so don't reset if a week exis
ts. |
| 3480 callDateSet(d, unit.method, (isDay ? 1 : 0)); |
| 3481 } |
| 3482 }); |
| 3483 |
| 3484 // Now actually set or advance the date in order, higher units first. |
| 3485 DateUnits.forEach(function(u, i) { |
| 3486 var name = u.name, method = u.method, higherUnit = DateUnits[i - 1], value
; |
| 3487 value = getParam(name) |
| 3488 if(isUndefined(value)) return; |
| 3489 if(advance) { |
| 3490 if(name === 'week') { |
| 3491 value = (params['day'] || 0) + (value * 7); |
| 3492 method = 'Date'; |
| 3493 } |
| 3494 value = (value * advance) + callDateGet(d, method); |
| 3495 } else if(name === 'month' && paramExists('day')) { |
| 3496 // When setting the month, there is a chance that we will traverse into
a new month. |
| 3497 // This happens in DST shifts, for example June 1st DST jumping to Janua
ry 1st |
| 3498 // (non-DST) will have a shift of -1:00 which will traverse into the pre
vious year. |
| 3499 // Prevent this by proactively setting the day when we know it will be s
et again anyway. |
| 3500 // It can also happen when there are not enough days in the target month
. This second |
| 3501 // situation is identical to checkMonthTraversal below, however when we
are advancing |
| 3502 // we want to reset the date to "the last date in the target month". In
the case of |
| 3503 // DST shifts, however, we want to avoid the "edges" of months as that i
s where this |
| 3504 // unintended traversal can happen. This is the reason for the different
handling of |
| 3505 // two similar but slightly different situations. |
| 3506 // |
| 3507 // TL;DR This method avoids the edges of a month IF not advancing and th
e date is going |
| 3508 // to be set anyway, while checkMonthTraversal resets the date to the la
st day if advancing. |
| 3509 // |
| 3510 callDateSet(d, 'Date', 15); |
| 3511 } |
| 3512 callDateSet(d, method, value); |
| 3513 if(advance && name === 'month') { |
| 3514 checkMonthTraversal(d, value); |
| 3515 } |
| 3516 }); |
| 3517 |
| 3518 |
| 3519 // If a weekday is included in the params, set it ahead of time and set the
params |
| 3520 // to reflect the updated date so that resetting works properly. |
| 3521 if(!advance && !paramExists('day') && paramExists('weekday')) { |
| 3522 var weekday = getParam('weekday'), isAhead, futurePreferred; |
| 3523 d.setWeekday(weekday); |
| 3524 } |
| 3525 |
| 3526 // If past or future is preferred, then the process of "disambiguation" will
ensure that an |
| 3527 // ambiguous time/date ("4pm", "thursday", "June", etc.) will be in the past
or future. |
| 3528 if(canDisambiguate()) { |
| 3529 iterateOverDateUnits(function(name, unit) { |
| 3530 var ambiguous = unit.ambiguous || (name === 'week' && paramExists('weekd
ay')); |
| 3531 if(ambiguous && !uniqueParamExists(name, name === 'day')) { |
| 3532 d[unit.addMethod](prefer); |
| 3533 return false; |
| 3534 } |
| 3535 }, specificityIndex + 1); |
| 3536 } |
| 3537 return d; |
| 3538 } |
| 3539 |
| 3540 // The ISO format allows times strung together without a demarcating ":", so m
ake sure |
| 3541 // that these markers are now optional. |
| 3542 function prepareTime(format, loc, iso) { |
| 3543 var timeSuffixMapping = {'h':0,'m':1,'s':2}, add; |
| 3544 loc = loc || English; |
| 3545 return format.replace(/{([a-z])}/g, function(full, token) { |
| 3546 var separators = [], |
| 3547 isHours = token === 'h', |
| 3548 tokenIsRequired = isHours && !iso; |
| 3549 if(token === 't') { |
| 3550 return loc['ampm'].join('|'); |
| 3551 } else { |
| 3552 if(isHours) { |
| 3553 separators.push(':'); |
| 3554 } |
| 3555 if(add = loc['timeSuffixes'][timeSuffixMapping[token]]) { |
| 3556 separators.push(add + '\\s*'); |
| 3557 } |
| 3558 return separators.length === 0 ? '' : '(?:' + separators.join('|') + ')'
+ (tokenIsRequired ? '' : '?'); |
| 3559 } |
| 3560 }); |
| 3561 } |
| 3562 |
| 3563 |
| 3564 // If the month is being set, then we don't want to accidentally |
| 3565 // traverse into a new month just because the target month doesn't have enough |
| 3566 // days. In other words, "5 months ago" from July 30th is still February, even |
| 3567 // though there is no February 30th, so it will of necessity be February 28th |
| 3568 // (or 29th in the case of a leap year). |
| 3569 |
| 3570 function checkMonthTraversal(date, targetMonth) { |
| 3571 if(targetMonth < 0) { |
| 3572 targetMonth = targetMonth % 12 + 12; |
| 3573 } |
| 3574 if(targetMonth % 12 != callDateGet(date, 'Month')) { |
| 3575 callDateSet(date, 'Date', 0); |
| 3576 } |
| 3577 } |
| 3578 |
| 3579 function createDate(args, prefer, forceUTC) { |
| 3580 var f, localeCode; |
| 3581 if(isNumber(args[1])) { |
| 3582 // If the second argument is a number, then we have an enumerated construc
tor type as in "new Date(2003, 2, 12);" |
| 3583 f = collectDateArguments(args)[0]; |
| 3584 } else { |
| 3585 f = args[0]; |
| 3586 localeCode = args[1]; |
| 3587 } |
| 3588 return getExtendedDate(f, localeCode, prefer, forceUTC).date; |
| 3589 } |
| 3590 |
| 3591 function invalidateDate(d) { |
| 3592 d.setTime(NaN); |
| 3593 } |
| 3594 |
| 3595 function buildDateUnits() { |
| 3596 DateUnitsReversed = DateUnits.concat().reverse(); |
| 3597 DateArgumentUnits = DateUnits.concat(); |
| 3598 DateArgumentUnits.splice(2,1); |
| 3599 } |
| 3600 |
| 3601 |
| 3602 /*** |
| 3603 * @method [units]Since([d], [locale] = currentLocale) |
| 3604 * @returns Number |
| 3605 * @short Returns the time since [d] in the appropriate unit. |
| 3606 * @extra [d] will accept a date object, timestamp, or text format. If not spe
cified, [d] is assumed to be now. [locale] can be passed to specify the locale t
hat the date is in. %[unit]Ago% is provided as an alias to make this more readab
le when [d] is assumed to be the current date. For more see @date_format. |
| 3607 * |
| 3608 * @set |
| 3609 * millisecondsSince |
| 3610 * secondsSince |
| 3611 * minutesSince |
| 3612 * hoursSince |
| 3613 * daysSince |
| 3614 * weeksSince |
| 3615 * monthsSince |
| 3616 * yearsSince |
| 3617 * |
| 3618 * @example |
| 3619 * |
| 3620 * Date.create().millisecondsSince('1 hour ago') -> 3,600,000 |
| 3621 * Date.create().daysSince('1 week ago') -> 7 |
| 3622 * Date.create().yearsSince('15 years ago') -> 15 |
| 3623 * Date.create('15 years ago').yearsAgo() -> 15 |
| 3624 * |
| 3625 *** |
| 3626 * @method [units]Ago() |
| 3627 * @returns Number |
| 3628 * @short Returns the time ago in the appropriate unit. |
| 3629 * |
| 3630 * @set |
| 3631 * millisecondsAgo |
| 3632 * secondsAgo |
| 3633 * minutesAgo |
| 3634 * hoursAgo |
| 3635 * daysAgo |
| 3636 * weeksAgo |
| 3637 * monthsAgo |
| 3638 * yearsAgo |
| 3639 * |
| 3640 * @example |
| 3641 * |
| 3642 * Date.create('last year').millisecondsAgo() -> 3,600,000 |
| 3643 * Date.create('last year').daysAgo() -> 7 |
| 3644 * Date.create('last year').yearsAgo() -> 15 |
| 3645 * |
| 3646 *** |
| 3647 * @method [units]Until([d], [locale] = currentLocale) |
| 3648 * @returns Number |
| 3649 * @short Returns the time until [d] in the appropriate unit. |
| 3650 * @extra [d] will accept a date object, timestamp, or text format. If not spe
cified, [d] is assumed to be now. [locale] can be passed to specify the locale t
hat the date is in. %[unit]FromNow% is provided as an alias to make this more re
adable when [d] is assumed to be the current date. For more see @date_format. |
| 3651 * |
| 3652 * @set |
| 3653 * millisecondsUntil |
| 3654 * secondsUntil |
| 3655 * minutesUntil |
| 3656 * hoursUntil |
| 3657 * daysUntil |
| 3658 * weeksUntil |
| 3659 * monthsUntil |
| 3660 * yearsUntil |
| 3661 * |
| 3662 * @example |
| 3663 * |
| 3664 * Date.create().millisecondsUntil('1 hour from now') -> 3,600,000 |
| 3665 * Date.create().daysUntil('1 week from now') -> 7 |
| 3666 * Date.create().yearsUntil('15 years from now') -> 15 |
| 3667 * Date.create('15 years from now').yearsFromNow() -> 15 |
| 3668 * |
| 3669 *** |
| 3670 * @method [units]FromNow() |
| 3671 * @returns Number |
| 3672 * @short Returns the time from now in the appropriate unit. |
| 3673 * |
| 3674 * @set |
| 3675 * millisecondsFromNow |
| 3676 * secondsFromNow |
| 3677 * minutesFromNow |
| 3678 * hoursFromNow |
| 3679 * daysFromNow |
| 3680 * weeksFromNow |
| 3681 * monthsFromNow |
| 3682 * yearsFromNow |
| 3683 * |
| 3684 * @example |
| 3685 * |
| 3686 * Date.create('next year').millisecondsFromNow() -> 3,600,000 |
| 3687 * Date.create('next year').daysFromNow() -> 7 |
| 3688 * Date.create('next year').yearsFromNow() -> 15 |
| 3689 * |
| 3690 *** |
| 3691 * @method add[Units](<num>, [reset] = false) |
| 3692 * @returns Date |
| 3693 * @short Adds <num> of the unit to the date. If [reset] is true, all lower un
its will be reset. |
| 3694 * @extra Note that "months" is ambiguous as a unit of time. If the target dat
e falls on a day that does not exist (ie. August 31 -> February 31), the date wi
ll be shifted to the last day of the month. Don't use %addMonths% if you need pr
ecision. |
| 3695 * |
| 3696 * @set |
| 3697 * addMilliseconds |
| 3698 * addSeconds |
| 3699 * addMinutes |
| 3700 * addHours |
| 3701 * addDays |
| 3702 * addWeeks |
| 3703 * addMonths |
| 3704 * addYears |
| 3705 * |
| 3706 * @example |
| 3707 * |
| 3708 * Date.create().addMilliseconds(5) -> current time + 5 milliseconds |
| 3709 * Date.create().addDays(5) -> current time + 5 days |
| 3710 * Date.create().addYears(5) -> current time + 5 years |
| 3711 * |
| 3712 *** |
| 3713 * @method isLast[Unit]() |
| 3714 * @returns Boolean |
| 3715 * @short Returns true if the date is last week/month/year. |
| 3716 * |
| 3717 * @set |
| 3718 * isLastWeek |
| 3719 * isLastMonth |
| 3720 * isLastYear |
| 3721 * |
| 3722 * @example |
| 3723 * |
| 3724 * Date.create('yesterday').isLastWeek() -> true or false? |
| 3725 * Date.create('yesterday').isLastMonth() -> probably not... |
| 3726 * Date.create('yesterday').isLastYear() -> even less likely... |
| 3727 * |
| 3728 *** |
| 3729 * @method isThis[Unit]() |
| 3730 * @returns Boolean |
| 3731 * @short Returns true if the date is this week/month/year. |
| 3732 * |
| 3733 * @set |
| 3734 * isThisWeek |
| 3735 * isThisMonth |
| 3736 * isThisYear |
| 3737 * |
| 3738 * @example |
| 3739 * |
| 3740 * Date.create('tomorrow').isThisWeek() -> true or false? |
| 3741 * Date.create('tomorrow').isThisMonth() -> probably... |
| 3742 * Date.create('tomorrow').isThisYear() -> signs point to yes... |
| 3743 * |
| 3744 *** |
| 3745 * @method isNext[Unit]() |
| 3746 * @returns Boolean |
| 3747 * @short Returns true if the date is next week/month/year. |
| 3748 * |
| 3749 * @set |
| 3750 * isNextWeek |
| 3751 * isNextMonth |
| 3752 * isNextYear |
| 3753 * |
| 3754 * @example |
| 3755 * |
| 3756 * Date.create('tomorrow').isNextWeek() -> true or false? |
| 3757 * Date.create('tomorrow').isNextMonth() -> probably not... |
| 3758 * Date.create('tomorrow').isNextYear() -> even less likely... |
| 3759 * |
| 3760 *** |
| 3761 * @method beginningOf[Unit]() |
| 3762 * @returns Date |
| 3763 * @short Sets the date to the beginning of the appropriate unit. |
| 3764 * |
| 3765 * @set |
| 3766 * beginningOfDay |
| 3767 * beginningOfWeek |
| 3768 * beginningOfMonth |
| 3769 * beginningOfYear |
| 3770 * |
| 3771 * @example |
| 3772 * |
| 3773 * Date.create().beginningOfDay() -> the beginning of today (resets the ti
me) |
| 3774 * Date.create().beginningOfWeek() -> the beginning of the week |
| 3775 * Date.create().beginningOfMonth() -> the beginning of the month |
| 3776 * Date.create().beginningOfYear() -> the beginning of the year |
| 3777 * |
| 3778 *** |
| 3779 * @method endOf[Unit]() |
| 3780 * @returns Date |
| 3781 * @short Sets the date to the end of the appropriate unit. |
| 3782 * |
| 3783 * @set |
| 3784 * endOfDay |
| 3785 * endOfWeek |
| 3786 * endOfMonth |
| 3787 * endOfYear |
| 3788 * |
| 3789 * @example |
| 3790 * |
| 3791 * Date.create().endOfDay() -> the end of today (sets the time to 23:59:59
.999) |
| 3792 * Date.create().endOfWeek() -> the end of the week |
| 3793 * Date.create().endOfMonth() -> the end of the month |
| 3794 * Date.create().endOfYear() -> the end of the year |
| 3795 * |
| 3796 ***/ |
| 3797 |
| 3798 function buildDateMethods() { |
| 3799 extendSimilar(date, true, true, DateUnits, function(methods, u, i) { |
| 3800 var name = u.name, caps = simpleCapitalize(name), multiplier = u.multiplie
r(), since, until; |
| 3801 u.addMethod = 'add' + caps + 's'; |
| 3802 // "since/until now" only count "past" an integer, i.e. "2 days ago" is |
| 3803 // anything between 2 - 2.999 days. The default margin of error is 0.999, |
| 3804 // but "months" have an inherently larger margin, as the number of days |
| 3805 // in a given month may be significantly less than the number of days in |
| 3806 // the average month, so for example "30 days" before March 15 may in fact |
| 3807 // be 1 month ago. Years also have a margin of error due to leap years, |
| 3808 // but this is roughly 0.999 anyway (365 / 365.25). Other units do not |
| 3809 // technically need the error margin applied to them but this accounts |
| 3810 // for discrepancies like (15).hoursAgo() which technically creates the |
| 3811 // current date first, then creates a date 15 hours before and compares |
| 3812 // them, the discrepancy between the creation of the 2 dates means that |
| 3813 // they may actually be 15.0001 hours apart. Milliseconds don't have |
| 3814 // fractions, so they won't be subject to this error margin. |
| 3815 function applyErrorMargin(ms) { |
| 3816 var num = ms / multiplier, |
| 3817 fraction = num % 1, |
| 3818 error = u.error || 0.999; |
| 3819 if(fraction && abs(fraction % 1) > error) { |
| 3820 num = round(num); |
| 3821 } |
| 3822 return num < 0 ? ceil(num) : floor(num); |
| 3823 } |
| 3824 since = function(f, localeCode) { |
| 3825 return applyErrorMargin(this.getTime() - date.create(f, localeCode).getT
ime()); |
| 3826 }; |
| 3827 until = function(f, localeCode) { |
| 3828 return applyErrorMargin(date.create(f, localeCode).getTime() - this.getT
ime()); |
| 3829 }; |
| 3830 methods[name+'sAgo'] = until; |
| 3831 methods[name+'sUntil'] = until; |
| 3832 methods[name+'sSince'] = since; |
| 3833 methods[name+'sFromNow'] = since; |
| 3834 methods[u.addMethod] = function(num, reset) { |
| 3835 var set = {}; |
| 3836 set[name] = num; |
| 3837 return this.advance(set, reset); |
| 3838 }; |
| 3839 buildNumberToDateAlias(u, multiplier); |
| 3840 if(i < 3) { |
| 3841 ['Last','This','Next'].forEach(function(shift) { |
| 3842 methods['is' + shift + caps] = function() { |
| 3843 return compareDate(this, shift + ' ' + name, 'en'); |
| 3844 }; |
| 3845 }); |
| 3846 } |
| 3847 if(i < 4) { |
| 3848 methods['beginningOf' + caps] = function() { |
| 3849 var set = {}; |
| 3850 switch(name) { |
| 3851 case 'year': set['year'] = callDateGet(this, 'FullYear'); break; |
| 3852 case 'month': set['month'] = callDateGet(this, 'Month'); break; |
| 3853 case 'day': set['day'] = callDateGet(this, 'Date'); break; |
| 3854 case 'week': set['weekday'] = 0; break; |
| 3855 } |
| 3856 return this.set(set, true); |
| 3857 }; |
| 3858 methods['endOf' + caps] = function() { |
| 3859 var set = { 'hours': 23, 'minutes': 59, 'seconds': 59, 'milliseconds':
999 }; |
| 3860 switch(name) { |
| 3861 case 'year': set['month'] = 11; set['day'] = 31; break; |
| 3862 case 'month': set['day'] = this.daysInMonth(); break; |
| 3863 case 'week': set['weekday'] = 6; break; |
| 3864 } |
| 3865 return this.set(set, true); |
| 3866 }; |
| 3867 } |
| 3868 }); |
| 3869 } |
| 3870 |
| 3871 function buildCoreInputFormats() { |
| 3872 English.addFormat('([+-])?(\\d{4,4})[-.]?{full_month}[-.]?(\\d{1,2})?', true
, ['year_sign','year','month','date'], false, true); |
| 3873 English.addFormat('(\\d{1,2})[-.\\/]{full_month}(?:[-.\\/](\\d{2,4}))?', tru
e, ['date','month','year'], true); |
| 3874 English.addFormat('{full_month}[-.](\\d{4,4})', false, ['month','year']); |
| 3875 English.addFormat('\\/Date\\((\\d+(?:[+-]\\d{4,4})?)\\)\\/', false, ['timest
amp']) |
| 3876 English.addFormat(prepareTime(RequiredTime, English), false, TimeFormat) |
| 3877 |
| 3878 // When a new locale is initialized it will have the CoreDateFormats initial
ized by default. |
| 3879 // From there, adding new formats will push them in front of the previous on
es, so the core |
| 3880 // formats will be the last to be reached. However, the core formats themsel
ves have English |
| 3881 // months in them, which means that English needs to first be initialized an
d creates a race |
| 3882 // condition. I'm getting around this here by adding these generalized forma
ts in the order |
| 3883 // specific -> general, which will mean they will be added to the English lo
calization in |
| 3884 // general -> specific order, then chopping them off the front and reversing
to get the correct |
| 3885 // order. Note that there are 7 formats as 2 have times which adds a front a
nd a back format. |
| 3886 CoreDateFormats = English.compiledFormats.slice(0,7).reverse(); |
| 3887 English.compiledFormats = English.compiledFormats.slice(7).concat(CoreDateFo
rmats); |
| 3888 } |
| 3889 |
| 3890 function buildFormatTokens() { |
| 3891 |
| 3892 createPaddedToken('f', function(d) { |
| 3893 return callDateGet(d, 'Milliseconds'); |
| 3894 }, true); |
| 3895 |
| 3896 createPaddedToken('s', function(d) { |
| 3897 return callDateGet(d, 'Seconds'); |
| 3898 }); |
| 3899 |
| 3900 createPaddedToken('m', function(d) { |
| 3901 return callDateGet(d, 'Minutes'); |
| 3902 }); |
| 3903 |
| 3904 createPaddedToken('h', function(d) { |
| 3905 return callDateGet(d, 'Hours') % 12 || 12; |
| 3906 }); |
| 3907 |
| 3908 createPaddedToken('H', function(d) { |
| 3909 return callDateGet(d, 'Hours'); |
| 3910 }); |
| 3911 |
| 3912 createPaddedToken('d', function(d) { |
| 3913 return callDateGet(d, 'Date'); |
| 3914 }); |
| 3915 |
| 3916 createPaddedToken('M', function(d) { |
| 3917 return callDateGet(d, 'Month') + 1; |
| 3918 }); |
| 3919 |
| 3920 createMeridianTokens(); |
| 3921 createWeekdayTokens(); |
| 3922 createMonthTokens(); |
| 3923 |
| 3924 // Aliases |
| 3925 DateFormatTokens['ms'] = DateFormatTokens['f']; |
| 3926 DateFormatTokens['milliseconds'] = DateFormatTokens['f']; |
| 3927 DateFormatTokens['seconds'] = DateFormatTokens['s']; |
| 3928 DateFormatTokens['minutes'] = DateFormatTokens['m']; |
| 3929 DateFormatTokens['hours'] = DateFormatTokens['h']; |
| 3930 DateFormatTokens['24hr'] = DateFormatTokens['H']; |
| 3931 DateFormatTokens['12hr'] = DateFormatTokens['h']; |
| 3932 DateFormatTokens['date'] = DateFormatTokens['d']; |
| 3933 DateFormatTokens['day'] = DateFormatTokens['d']; |
| 3934 DateFormatTokens['year'] = DateFormatTokens['yyyy']; |
| 3935 |
| 3936 } |
| 3937 |
| 3938 function buildFormatShortcuts() { |
| 3939 extendSimilar(date, true, true, 'short,long,full', function(methods, name) { |
| 3940 methods[name] = function(localeCode) { |
| 3941 return formatDate(this, name, false, localeCode); |
| 3942 } |
| 3943 }); |
| 3944 } |
| 3945 |
| 3946 function buildAsianDigits() { |
| 3947 KanjiDigits.split('').forEach(function(digit, value) { |
| 3948 var holder; |
| 3949 if(value > 9) { |
| 3950 value = pow(10, value - 9); |
| 3951 } |
| 3952 AsianDigitMap[digit] = value; |
| 3953 }); |
| 3954 simpleMerge(AsianDigitMap, NumberNormalizeMap); |
| 3955 // Kanji numerals may also be included in phrases which are text-based rathe
r |
| 3956 // than actual numbers such as Chinese weekdays (上周三), and "the day before |
| 3957 // yesterday" (一昨日) in Japanese, so don't match these. |
| 3958 AsianDigitReg = regexp('([期週周])?([' + KanjiDigits + FullWidthDigits + ']+)(?
!昨)', 'g'); |
| 3959 } |
| 3960 |
| 3961 /*** |
| 3962 * @method is[Day]() |
| 3963 * @returns Boolean |
| 3964 * @short Returns true if the date falls on that day. |
| 3965 * @extra Also available: %isYesterday%, %isToday%, %isTomorrow%, %isWeekday%,
and %isWeekend%. |
| 3966 * |
| 3967 * @set |
| 3968 * isToday |
| 3969 * isYesterday |
| 3970 * isTomorrow |
| 3971 * isWeekday |
| 3972 * isWeekend |
| 3973 * isSunday |
| 3974 * isMonday |
| 3975 * isTuesday |
| 3976 * isWednesday |
| 3977 * isThursday |
| 3978 * isFriday |
| 3979 * isSaturday |
| 3980 * |
| 3981 * @example |
| 3982 * |
| 3983 * Date.create('tomorrow').isToday() -> false |
| 3984 * Date.create('thursday').isTomorrow() -> ? |
| 3985 * Date.create('yesterday').isWednesday() -> ? |
| 3986 * Date.create('today').isWeekend() -> ? |
| 3987 * |
| 3988 *** |
| 3989 * @method isFuture() |
| 3990 * @returns Boolean |
| 3991 * @short Returns true if the date is in the future. |
| 3992 * @example |
| 3993 * |
| 3994 * Date.create('next week').isFuture() -> true |
| 3995 * Date.create('last week').isFuture() -> false |
| 3996 * |
| 3997 *** |
| 3998 * @method isPast() |
| 3999 * @returns Boolean |
| 4000 * @short Returns true if the date is in the past. |
| 4001 * @example |
| 4002 * |
| 4003 * Date.create('last week').isPast() -> true |
| 4004 * Date.create('next week').isPast() -> false |
| 4005 * |
| 4006 ***/ |
| 4007 function buildRelativeAliases() { |
| 4008 var special = 'today,yesterday,tomorrow,weekday,weekend,future,past'.split(
','); |
| 4009 var weekdays = English['weekdays'].slice(0,7); |
| 4010 var months = English['months'].slice(0,12); |
| 4011 extendSimilar(date, true, true, special.concat(weekdays).concat(months), fun
ction(methods, name) { |
| 4012 methods['is'+ simpleCapitalize(name)] = function(utc) { |
| 4013 return this.is(name, 0, utc); |
| 4014 }; |
| 4015 }); |
| 4016 } |
| 4017 |
| 4018 function buildUTCAliases() { |
| 4019 // Don't want to use extend here as it will override |
| 4020 // the actual "utc" method on the prototype. |
| 4021 if(date['utc']) return; |
| 4022 date['utc'] = { |
| 4023 |
| 4024 'create': function() { |
| 4025 return createDate(arguments, 0, true); |
| 4026 }, |
| 4027 |
| 4028 'past': function() { |
| 4029 return createDate(arguments, -1, true); |
| 4030 }, |
| 4031 |
| 4032 'future': function() { |
| 4033 return createDate(arguments, 1, true); |
| 4034 } |
| 4035 }; |
| 4036 } |
| 4037 |
| 4038 function setDateProperties() { |
| 4039 extend(date, false , true, { |
| 4040 'RFC1123': '{Dow}, {dd} {Mon} {yyyy} {HH}:{mm}:{ss} {tz}', |
| 4041 'RFC1036': '{Weekday}, {dd}-{Mon}-{yy} {HH}:{mm}:{ss} {tz}', |
| 4042 'ISO8601_DATE': '{yyyy}-{MM}-{dd}', |
| 4043 'ISO8601_DATETIME': '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{fff}{isotz}' |
| 4044 }); |
| 4045 } |
| 4046 |
| 4047 |
| 4048 extend(date, false, true, { |
| 4049 |
| 4050 /*** |
| 4051 * @method Date.create(<d>, [locale] = currentLocale) |
| 4052 * @returns Date |
| 4053 * @short Alternate Date constructor which understands many different text f
ormats, a timestamp, or another date. |
| 4054 * @extra If no argument is given, date is assumed to be now. %Date.create%
additionally can accept enumerated parameters as with the standard date construc
tor. [locale] can be passed to specify the locale that the date is in. When unsp
ecified, the current locale (default is English) is assumed. UTC-based dates can
be created through the %utc% object. For more see @date_format. |
| 4055 * @set |
| 4056 * Date.utc.create |
| 4057 * |
| 4058 * @example |
| 4059 * |
| 4060 * Date.create('July') -> July of this year |
| 4061 * Date.create('1776') -> 1776 |
| 4062 * Date.create('today') -> today |
| 4063 * Date.create('wednesday') -> This wednesday |
| 4064 * Date.create('next friday') -> Next friday |
| 4065 * Date.create('July 4, 1776') -> July 4, 1776 |
| 4066 * Date.create(-446806800000) -> November 5, 1955 |
| 4067 * Date.create(1776, 6, 4) -> July 4, 1776 |
| 4068 * Date.create('1776年07月04日', 'ja') -> July 4, 1776 |
| 4069 * Date.utc.create('July 4, 1776', 'en') -> July 4, 1776 |
| 4070 * |
| 4071 ***/ |
| 4072 'create': function() { |
| 4073 return createDate(arguments); |
| 4074 }, |
| 4075 |
| 4076 /*** |
| 4077 * @method Date.past(<d>, [locale] = currentLocale) |
| 4078 * @returns Date |
| 4079 * @short Alternate form of %Date.create% with any ambiguity assumed to be t
he past. |
| 4080 * @extra For example %"Sunday"% can be either "the Sunday coming up" or "th
e Sunday last" depending on context. Note that dates explicitly in the future ("
next Sunday") will remain in the future. This method simply provides a hint when
ambiguity exists. UTC-based dates can be created through the %utc% object. For
more, see @date_format. |
| 4081 * @set |
| 4082 * Date.utc.past |
| 4083 * |
| 4084 * @example |
| 4085 * |
| 4086 * Date.past('July') -> July of this year or last depending on th
e current month |
| 4087 * Date.past('Wednesday') -> This wednesday or last depending on the c
urrent weekday |
| 4088 * |
| 4089 ***/ |
| 4090 'past': function() { |
| 4091 return createDate(arguments, -1); |
| 4092 }, |
| 4093 |
| 4094 /*** |
| 4095 * @method Date.future(<d>, [locale] = currentLocale) |
| 4096 * @returns Date |
| 4097 * @short Alternate form of %Date.create% with any ambiguity assumed to be t
he future. |
| 4098 * @extra For example %"Sunday"% can be either "the Sunday coming up" or "th
e Sunday last" depending on context. Note that dates explicitly in the past ("la
st Sunday") will remain in the past. This method simply provides a hint when amb
iguity exists. UTC-based dates can be created through the %utc% object. For more
, see @date_format. |
| 4099 * @set |
| 4100 * Date.utc.future |
| 4101 * |
| 4102 * @example |
| 4103 * |
| 4104 * Date.future('July') -> July of this year or next depending on
the current month |
| 4105 * Date.future('Wednesday') -> This wednesday or next depending on the
current weekday |
| 4106 * |
| 4107 ***/ |
| 4108 'future': function() { |
| 4109 return createDate(arguments, 1); |
| 4110 }, |
| 4111 |
| 4112 /*** |
| 4113 * @method Date.addLocale(<code>, <set>) |
| 4114 * @returns Locale |
| 4115 * @short Adds a locale <set> to the locales understood by Sugar. |
| 4116 * @extra For more see @date_format. |
| 4117 * |
| 4118 ***/ |
| 4119 'addLocale': function(localeCode, set) { |
| 4120 return setLocalization(localeCode, set); |
| 4121 }, |
| 4122 |
| 4123 /*** |
| 4124 * @method Date.setLocale(<code>) |
| 4125 * @returns Locale |
| 4126 * @short Sets the current locale to be used with dates. |
| 4127 * @extra Sugar has support for 13 locales that are available through the "D
ate Locales" package. In addition you can define a new locale with %Date.addLoca
le%. For more see @date_format. |
| 4128 * |
| 4129 ***/ |
| 4130 'setLocale': function(localeCode, set) { |
| 4131 var loc = getLocalization(localeCode, false); |
| 4132 CurrentLocalization = loc; |
| 4133 // The code is allowed to be more specific than the codes which are requir
ed: |
| 4134 // i.e. zh-CN or en-US. Currently this only affects US date variants such
as 8/10/2000. |
| 4135 if(localeCode && localeCode != loc['code']) { |
| 4136 loc['code'] = localeCode; |
| 4137 } |
| 4138 return loc; |
| 4139 }, |
| 4140 |
| 4141 /*** |
| 4142 * @method Date.getLocale([code] = current) |
| 4143 * @returns Locale |
| 4144 * @short Gets the locale for the given code, or the current locale. |
| 4145 * @extra The resulting locale object can be manipulated to provide more con
trol over date localizations. For more about locales, see @date_format. |
| 4146 * |
| 4147 ***/ |
| 4148 'getLocale': function(localeCode) { |
| 4149 return !localeCode ? CurrentLocalization : getLocalization(localeCode, fal
se); |
| 4150 }, |
| 4151 |
| 4152 /** |
| 4153 * @method Date.addFormat(<format>, <match>, [code] = null) |
| 4154 * @returns Nothing |
| 4155 * @short Manually adds a new date input format. |
| 4156 * @extra This method allows fine grained control for alternate formats. <fo
rmat> is a string that can have regex tokens inside. <match> is an array of the
tokens that each regex capturing group will map to, for example %year%, %date%,
etc. For more, see @date_format. |
| 4157 * |
| 4158 **/ |
| 4159 'addFormat': function(format, match, localeCode) { |
| 4160 addDateInputFormat(getLocalization(localeCode), format, match); |
| 4161 } |
| 4162 |
| 4163 }); |
| 4164 |
| 4165 extend(date, true, true, { |
| 4166 |
| 4167 /*** |
| 4168 * @method set(<set>, [reset] = false) |
| 4169 * @returns Date |
| 4170 * @short Sets the date object. |
| 4171 * @extra This method can accept multiple formats including a single number
as a timestamp, an object, or enumerated parameters (as with the Date constructo
r). If [reset] is %true%, any units more specific than those passed will be rese
t. |
| 4172 * |
| 4173 * @example |
| 4174 * |
| 4175 * new Date().set({ year: 2011, month: 11, day: 31 }) -> December 31, 2011 |
| 4176 * new Date().set(2011, 11, 31) -> December 31, 2011 |
| 4177 * new Date().set(86400000) -> 1 day after Jan 1
, 1970 |
| 4178 * new Date().set({ year: 2004, month: 6 }, true) -> June 1, 2004, 00:
00:00.000 |
| 4179 * |
| 4180 ***/ |
| 4181 'set': function() { |
| 4182 var args = collectDateArguments(arguments); |
| 4183 return updateDate(this, args[0], args[1]) |
| 4184 }, |
| 4185 |
| 4186 /*** |
| 4187 * @method setWeekday() |
| 4188 * @returns Nothing |
| 4189 * @short Sets the weekday of the date. |
| 4190 * @extra In order to maintain a parallel with %getWeekday% (which itself is
an alias for Javascript native %getDay%), Sunday is considered day %0%. This co
ntrasts with ISO-8601 standard (used in %getISOWeek% and %setISOWeek%) which pla
ces Sunday at the end of the week (day 7). This effectively means that passing %
0% to this method while in the middle of a week will rewind the date, where pass
ing %7% will advance it. |
| 4191 * |
| 4192 * @example |
| 4193 * |
| 4194 * d = new Date(); d.setWeekday(1); d; -> Monday of this week |
| 4195 * d = new Date(); d.setWeekday(6); d; -> Saturday of this week |
| 4196 * |
| 4197 ***/ |
| 4198 'setWeekday': function(dow) { |
| 4199 if(isUndefined(dow)) return; |
| 4200 return callDateSet(this, 'Date', callDateGet(this, 'Date') + dow - callDat
eGet(this, 'Day')); |
| 4201 }, |
| 4202 |
| 4203 /*** |
| 4204 * @method setISOWeek() |
| 4205 * @returns Nothing |
| 4206 * @short Sets the week (of the year) as defined by the ISO-8601 standard. |
| 4207 * @extra Note that this standard places Sunday at the end of the week (day
7). |
| 4208 * |
| 4209 * @example |
| 4210 * |
| 4211 * d = new Date(); d.setISOWeek(15); d; -> 15th week of the year |
| 4212 * |
| 4213 ***/ |
| 4214 'setISOWeek': function(week) { |
| 4215 var weekday = callDateGet(this, 'Day') || 7; |
| 4216 if(isUndefined(week)) return; |
| 4217 this.set({ 'month': 0, 'date': 4 }); |
| 4218 this.set({ 'weekday': 1 }); |
| 4219 if(week > 1) { |
| 4220 this.addWeeks(week - 1); |
| 4221 } |
| 4222 if(weekday !== 1) { |
| 4223 this.advance({ 'days': weekday - 1 }); |
| 4224 } |
| 4225 return this.getTime(); |
| 4226 }, |
| 4227 |
| 4228 /*** |
| 4229 * @method getISOWeek() |
| 4230 * @returns Number |
| 4231 * @short Gets the date's week (of the year) as defined by the ISO-8601 stan
dard. |
| 4232 * @extra Note that this standard places Sunday at the end of the week (day
7). If %utc% is set on the date, the week will be according to UTC time. |
| 4233 * |
| 4234 * @example |
| 4235 * |
| 4236 * new Date().getISOWeek() -> today's week of the year |
| 4237 * |
| 4238 ***/ |
| 4239 'getISOWeek': function() { |
| 4240 return getWeekNumber(this); |
| 4241 }, |
| 4242 |
| 4243 /*** |
| 4244 * @method beginningOfISOWeek() |
| 4245 * @returns Date |
| 4246 * @short Set the date to the beginning of week as defined by this ISO-8601
standard. |
| 4247 * @extra Note that this standard places Monday at the start of the week. |
| 4248 * @example |
| 4249 * |
| 4250 * Date.create().beginningOfISOWeek() -> Monday |
| 4251 * |
| 4252 ***/ |
| 4253 'beginningOfISOWeek': function() { |
| 4254 var day = this.getDay(); |
| 4255 if(day === 0) { |
| 4256 day = -6; |
| 4257 } else if(day !== 1) { |
| 4258 day = 1; |
| 4259 } |
| 4260 this.setWeekday(day); |
| 4261 return this.reset(); |
| 4262 }, |
| 4263 |
| 4264 /*** |
| 4265 * @method endOfISOWeek() |
| 4266 * @returns Date |
| 4267 * @short Set the date to the end of week as defined by this ISO-8601 standa
rd. |
| 4268 * @extra Note that this standard places Sunday at the end of the week. |
| 4269 * @example |
| 4270 * |
| 4271 * Date.create().endOfISOWeek() -> Sunday |
| 4272 * |
| 4273 ***/ |
| 4274 'endOfISOWeek': function() { |
| 4275 if(this.getDay() !== 0) { |
| 4276 this.setWeekday(7); |
| 4277 } |
| 4278 return this.endOfDay() |
| 4279 }, |
| 4280 |
| 4281 /*** |
| 4282 * @method getUTCOffset([iso]) |
| 4283 * @returns String |
| 4284 * @short Returns a string representation of the offset from UTC time. If [i
so] is true the offset will be in ISO8601 format. |
| 4285 * @example |
| 4286 * |
| 4287 * new Date().getUTCOffset() -> "+0900" |
| 4288 * new Date().getUTCOffset(true) -> "+09:00" |
| 4289 * |
| 4290 ***/ |
| 4291 'getUTCOffset': function(iso) { |
| 4292 var offset = this._utc ? 0 : this.getTimezoneOffset(); |
| 4293 var colon = iso === true ? ':' : ''; |
| 4294 if(!offset && iso) return 'Z'; |
| 4295 return padNumber(floor(-offset / 60), 2, true) + colon + padNumber(abs(off
set % 60), 2); |
| 4296 }, |
| 4297 |
| 4298 /*** |
| 4299 * @method utc([on] = true) |
| 4300 * @returns Date |
| 4301 * @short Sets the internal utc flag for the date. When on, UTC-based method
s will be called internally. |
| 4302 * @extra For more see @date_format. |
| 4303 * @example |
| 4304 * |
| 4305 * new Date().utc(true) |
| 4306 * new Date().utc(false) |
| 4307 * |
| 4308 ***/ |
| 4309 'utc': function(set) { |
| 4310 defineProperty(this, '_utc', set === true || arguments.length === 0); |
| 4311 return this; |
| 4312 }, |
| 4313 |
| 4314 /*** |
| 4315 * @method isUTC() |
| 4316 * @returns Boolean |
| 4317 * @short Returns true if the date has no timezone offset. |
| 4318 * @extra This will also return true for utc-based dates (dates that have th
e %utc% method set true). Note that even if the utc flag is set, %getTimezoneOff
set% will always report the same thing as Javascript always reports that based o
n the environment's locale. |
| 4319 * @example |
| 4320 * |
| 4321 * new Date().isUTC() -> true or false? |
| 4322 * new Date().utc(true).isUTC() -> true |
| 4323 * |
| 4324 ***/ |
| 4325 'isUTC': function() { |
| 4326 return !!this._utc || this.getTimezoneOffset() === 0; |
| 4327 }, |
| 4328 |
| 4329 /*** |
| 4330 * @method advance(<set>, [reset] = false) |
| 4331 * @returns Date |
| 4332 * @short Sets the date forward. |
| 4333 * @extra This method can accept multiple formats including an object, a str
ing in the format %3 days%, a single number as milliseconds, or enumerated param
eters (as with the Date constructor). If [reset] is %true%, any units more speci
fic than those passed will be reset. For more see @date_format. |
| 4334 * @example |
| 4335 * |
| 4336 * new Date().advance({ year: 2 }) -> 2 years in the future |
| 4337 * new Date().advance('2 days') -> 2 days in the future |
| 4338 * new Date().advance(0, 2, 3) -> 2 months 3 days in the future |
| 4339 * new Date().advance(86400000) -> 1 day in the future |
| 4340 * |
| 4341 ***/ |
| 4342 'advance': function() { |
| 4343 var args = collectDateArguments(arguments, true); |
| 4344 return updateDate(this, args[0], args[1], 1); |
| 4345 }, |
| 4346 |
| 4347 /*** |
| 4348 * @method rewind(<set>, [reset] = false) |
| 4349 * @returns Date |
| 4350 * @short Sets the date back. |
| 4351 * @extra This method can accept multiple formats including a single number
as a timestamp, an object, or enumerated parameters (as with the Date constructo
r). If [reset] is %true%, any units more specific than those passed will be rese
t. For more see @date_format. |
| 4352 * @example |
| 4353 * |
| 4354 * new Date().rewind({ year: 2 }) -> 2 years in the past |
| 4355 * new Date().rewind(0, 2, 3) -> 2 months 3 days in the past |
| 4356 * new Date().rewind(86400000) -> 1 day in the past |
| 4357 * |
| 4358 ***/ |
| 4359 'rewind': function() { |
| 4360 var args = collectDateArguments(arguments, true); |
| 4361 return updateDate(this, args[0], args[1], -1); |
| 4362 }, |
| 4363 |
| 4364 /*** |
| 4365 * @method isValid() |
| 4366 * @returns Boolean |
| 4367 * @short Returns true if the date is valid. |
| 4368 * @example |
| 4369 * |
| 4370 * new Date().isValid() -> true |
| 4371 * new Date('flexor').isValid() -> false |
| 4372 * |
| 4373 ***/ |
| 4374 'isValid': function() { |
| 4375 return !isNaN(this.getTime()); |
| 4376 }, |
| 4377 |
| 4378 /*** |
| 4379 * @method isAfter(<d>, [margin] = 0) |
| 4380 * @returns Boolean |
| 4381 * @short Returns true if the date is after the <d>. |
| 4382 * @extra [margin] is to allow extra margin of error (in ms). <d> will accep
t a date object, timestamp, or text format. If not specified, <d> is assumed to
be now. See @date_format for more. |
| 4383 * @example |
| 4384 * |
| 4385 * new Date().isAfter('tomorrow') -> false |
| 4386 * new Date().isAfter('yesterday') -> true |
| 4387 * |
| 4388 ***/ |
| 4389 'isAfter': function(d, margin, utc) { |
| 4390 return this.getTime() > date.create(d).getTime() - (margin || 0); |
| 4391 }, |
| 4392 |
| 4393 /*** |
| 4394 * @method isBefore(<d>, [margin] = 0) |
| 4395 * @returns Boolean |
| 4396 * @short Returns true if the date is before <d>. |
| 4397 * @extra [margin] is to allow extra margin of error (in ms). <d> will accep
t a date object, timestamp, or text format. If not specified, <d> is assumed to
be now. See @date_format for more. |
| 4398 * @example |
| 4399 * |
| 4400 * new Date().isBefore('tomorrow') -> true |
| 4401 * new Date().isBefore('yesterday') -> false |
| 4402 * |
| 4403 ***/ |
| 4404 'isBefore': function(d, margin) { |
| 4405 return this.getTime() < date.create(d).getTime() + (margin || 0); |
| 4406 }, |
| 4407 |
| 4408 /*** |
| 4409 * @method isBetween(<d1>, <d2>, [margin] = 0) |
| 4410 * @returns Boolean |
| 4411 * @short Returns true if the date falls between <d1> and <d2>. |
| 4412 * @extra [margin] is to allow extra margin of error (in ms). <d1> and <d2>
will accept a date object, timestamp, or text format. If not specified, they are
assumed to be now. See @date_format for more. |
| 4413 * @example |
| 4414 * |
| 4415 * new Date().isBetween('yesterday', 'tomorrow') -> true |
| 4416 * new Date().isBetween('last year', '2 years ago') -> false |
| 4417 * |
| 4418 ***/ |
| 4419 'isBetween': function(d1, d2, margin) { |
| 4420 var t = this.getTime(); |
| 4421 var t1 = date.create(d1).getTime(); |
| 4422 var t2 = date.create(d2).getTime(); |
| 4423 var lo = min(t1, t2); |
| 4424 var hi = max(t1, t2); |
| 4425 margin = margin || 0; |
| 4426 return (lo - margin < t) && (hi + margin > t); |
| 4427 }, |
| 4428 |
| 4429 /*** |
| 4430 * @method isLeapYear() |
| 4431 * @returns Boolean |
| 4432 * @short Returns true if the date is a leap year. |
| 4433 * @example |
| 4434 * |
| 4435 * Date.create('2000').isLeapYear() -> true |
| 4436 * |
| 4437 ***/ |
| 4438 'isLeapYear': function() { |
| 4439 var year = callDateGet(this, 'FullYear'); |
| 4440 return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0); |
| 4441 }, |
| 4442 |
| 4443 /*** |
| 4444 * @method daysInMonth() |
| 4445 * @returns Number |
| 4446 * @short Returns the number of days in the date's month. |
| 4447 * @example |
| 4448 * |
| 4449 * Date.create('May').daysInMonth() -> 31 |
| 4450 * Date.create('February, 2000').daysInMonth() -> 29 |
| 4451 * |
| 4452 ***/ |
| 4453 'daysInMonth': function() { |
| 4454 return 32 - callDateGet(new date(callDateGet(this, 'FullYear'), callDateGe
t(this, 'Month'), 32), 'Date'); |
| 4455 }, |
| 4456 |
| 4457 /*** |
| 4458 * @method format(<format>, [locale] = currentLocale) |
| 4459 * @returns String |
| 4460 * @short Formats and outputs the date. |
| 4461 * @extra <format> can be a number of pre-determined formats or a string of
tokens. Locale-specific formats are %short%, %long%, and %full% which have their
own aliases and can be called with %date.short()%, etc. If <format> is not spec
ified the %long% format is assumed. [locale] specifies a locale code to use (if
not specified the current locale is used). See @date_format for more details. |
| 4462 * |
| 4463 * @set |
| 4464 * short |
| 4465 * long |
| 4466 * full |
| 4467 * |
| 4468 * @example |
| 4469 * |
| 4470 * Date.create().format() -> ex. July 4,
2003 |
| 4471 * Date.create().format('{Weekday} {d} {Month}, {yyyy}') -> ex. Monday
July 4, 2003 |
| 4472 * Date.create().format('{hh}:{mm}') -> ex. 15:57 |
| 4473 * Date.create().format('{12hr}:{mm}{tt}') -> ex. 3:57pm |
| 4474 * Date.create().format(Date.ISO8601_DATETIME) -> ex. 2011-07
-05 12:24:55.528Z |
| 4475 * Date.create('last week').format('short', 'ja') -> ex. 先週 |
| 4476 * Date.create('yesterday').format(function(value,unit,ms,loc) { |
| 4477 * // value = 1, unit = 3, ms = -86400000, loc = [current locale object] |
| 4478 * }); -> ex. 1 day a
go |
| 4479 * |
| 4480 ***/ |
| 4481 'format': function(f, localeCode) { |
| 4482 return formatDate(this, f, false, localeCode); |
| 4483 }, |
| 4484 |
| 4485 /*** |
| 4486 * @method relative([fn], [locale] = currentLocale) |
| 4487 * @returns String |
| 4488 * @short Returns a relative date string offset to the current time. |
| 4489 * @extra [fn] can be passed to provide for more granular control over the r
esulting string. [fn] is passed 4 arguments: the adjusted value, unit, offset in
milliseconds, and a localization object. As an alternate syntax, [locale] can a
lso be passed as the first (and only) parameter. For more, see @date_format. |
| 4490 * @example |
| 4491 * |
| 4492 * Date.create('90 seconds ago').relative() -> 1 minute ago |
| 4493 * Date.create('January').relative() -> ex. 5 months ago |
| 4494 * Date.create('January').relative('ja') -> 3ヶ月前 |
| 4495 * Date.create('120 minutes ago').relative(function(val,unit,ms,loc) { |
| 4496 * // value = 2, unit = 3, ms = -7200, loc = [current locale object] |
| 4497 * }); -> ex. 5 months ago |
| 4498 * |
| 4499 ***/ |
| 4500 'relative': function(fn, localeCode) { |
| 4501 if(isString(fn)) { |
| 4502 localeCode = fn; |
| 4503 fn = null; |
| 4504 } |
| 4505 return formatDate(this, fn, true, localeCode); |
| 4506 }, |
| 4507 |
| 4508 /*** |
| 4509 * @method is(<d>, [margin] = 0) |
| 4510 * @returns Boolean |
| 4511 * @short Returns true if the date is <d>. |
| 4512 * @extra <d> will accept a date object, timestamp, or text format. %is% add
itionally understands more generalized expressions like month/weekday names, 'to
day', etc, and compares to the precision implied in <d>. [margin] allows an extr
a margin of error in milliseconds. For more, see @date_format. |
| 4513 * @example |
| 4514 * |
| 4515 * Date.create().is('July') -> true or false? |
| 4516 * Date.create().is('1776') -> false |
| 4517 * Date.create().is('today') -> true |
| 4518 * Date.create().is('weekday') -> true or false? |
| 4519 * Date.create().is('July 4, 1776') -> false |
| 4520 * Date.create().is(-6106093200000) -> false |
| 4521 * Date.create().is(new Date(1776, 6, 4)) -> false |
| 4522 * |
| 4523 ***/ |
| 4524 'is': function(d, margin, utc) { |
| 4525 var tmp, comp; |
| 4526 if(!this.isValid()) return; |
| 4527 if(isString(d)) { |
| 4528 d = d.trim().toLowerCase(); |
| 4529 comp = this.clone().utc(utc); |
| 4530 switch(true) { |
| 4531 case d === 'future': return this.getTime() > getNewDate().getTime(); |
| 4532 case d === 'past': return this.getTime() < getNewDate().getTime(); |
| 4533 case d === 'weekday': return callDateGet(comp, 'Day') > 0 && callDateG
et(comp, 'Day') < 6; |
| 4534 case d === 'weekend': return callDateGet(comp, 'Day') === 0 || callDat
eGet(comp, 'Day') === 6; |
| 4535 case (tmp = English['weekdays'].indexOf(d) % 7) > -1: return callDateG
et(comp, 'Day') === tmp; |
| 4536 case (tmp = English['months'].indexOf(d) % 12) > -1: return callDateG
et(comp, 'Month') === tmp; |
| 4537 } |
| 4538 } |
| 4539 return compareDate(this, d, null, margin, utc); |
| 4540 }, |
| 4541 |
| 4542 /*** |
| 4543 * @method reset([unit] = 'hours') |
| 4544 * @returns Date |
| 4545 * @short Resets the unit passed and all smaller units. Default is "hours",
effectively resetting the time. |
| 4546 * @example |
| 4547 * |
| 4548 * Date.create().reset('day') -> Beginning of today |
| 4549 * Date.create().reset('month') -> 1st of the month |
| 4550 * |
| 4551 ***/ |
| 4552 'reset': function(unit) { |
| 4553 var params = {}, recognized; |
| 4554 unit = unit || 'hours'; |
| 4555 if(unit === 'date') unit = 'days'; |
| 4556 recognized = DateUnits.some(function(u) { |
| 4557 return unit === u.name || unit === u.name + 's'; |
| 4558 }); |
| 4559 params[unit] = unit.match(/^days?/) ? 1 : 0; |
| 4560 return recognized ? this.set(params, true) : this; |
| 4561 }, |
| 4562 |
| 4563 /*** |
| 4564 * @method clone() |
| 4565 * @returns Date |
| 4566 * @short Clones the date. |
| 4567 * @example |
| 4568 * |
| 4569 * Date.create().clone() -> Copy of now |
| 4570 * |
| 4571 ***/ |
| 4572 'clone': function() { |
| 4573 var d = new date(this.getTime()); |
| 4574 d.utc(!!this._utc); |
| 4575 return d; |
| 4576 } |
| 4577 |
| 4578 }); |
| 4579 |
| 4580 |
| 4581 // Instance aliases |
| 4582 extend(date, true, true, { |
| 4583 |
| 4584 /*** |
| 4585 * @method iso() |
| 4586 * @alias toISOString |
| 4587 * |
| 4588 ***/ |
| 4589 'iso': function() { |
| 4590 return this.toISOString(); |
| 4591 }, |
| 4592 |
| 4593 /*** |
| 4594 * @method getWeekday() |
| 4595 * @returns Number |
| 4596 * @short Alias for %getDay%. |
| 4597 * @set |
| 4598 * getUTCWeekday |
| 4599 * |
| 4600 * @example |
| 4601 * |
| 4602 + Date.create().getWeekday(); -> (ex.) 3 |
| 4603 + Date.create().getUTCWeekday(); -> (ex.) 3 |
| 4604 * |
| 4605 ***/ |
| 4606 'getWeekday': date.prototype.getDay, |
| 4607 'getUTCWeekday': date.prototype.getUTCDay |
| 4608 |
| 4609 }); |
| 4610 |
| 4611 |
| 4612 |
| 4613 /*** |
| 4614 * Number module |
| 4615 * |
| 4616 ***/ |
| 4617 |
| 4618 /*** |
| 4619 * @method [unit]() |
| 4620 * @returns Number |
| 4621 * @short Takes the number as a corresponding unit of time and converts to mil
liseconds. |
| 4622 * @extra Method names can be singular or plural. Note that as "a month" is a
mbiguous as a unit of time, %months% will be equivalent to 30.4375 days, the ave
rage number in a month. Be careful using %months% if you need exact precision. |
| 4623 * |
| 4624 * @set |
| 4625 * millisecond |
| 4626 * milliseconds |
| 4627 * second |
| 4628 * seconds |
| 4629 * minute |
| 4630 * minutes |
| 4631 * hour |
| 4632 * hours |
| 4633 * day |
| 4634 * days |
| 4635 * week |
| 4636 * weeks |
| 4637 * month |
| 4638 * months |
| 4639 * year |
| 4640 * years |
| 4641 * |
| 4642 * @example |
| 4643 * |
| 4644 * (5).milliseconds() -> 5 |
| 4645 * (10).hours() -> 36000000 |
| 4646 * (1).day() -> 86400000 |
| 4647 * |
| 4648 *** |
| 4649 * @method [unit]Before([d], [locale] = currentLocale) |
| 4650 * @returns Date |
| 4651 * @short Returns a date that is <n> units before [d], where <n> is the number
. |
| 4652 * @extra [d] will accept a date object, timestamp, or text format. Note that
"months" is ambiguous as a unit of time. If the target date falls on a day that
does not exist (ie. August 31 -> February 31), the date will be shifted to the l
ast day of the month. Be careful using %monthsBefore% if you need exact precisio
n. See @date_format for more. |
| 4653 * |
| 4654 * @set |
| 4655 * millisecondBefore |
| 4656 * millisecondsBefore |
| 4657 * secondBefore |
| 4658 * secondsBefore |
| 4659 * minuteBefore |
| 4660 * minutesBefore |
| 4661 * hourBefore |
| 4662 * hoursBefore |
| 4663 * dayBefore |
| 4664 * daysBefore |
| 4665 * weekBefore |
| 4666 * weeksBefore |
| 4667 * monthBefore |
| 4668 * monthsBefore |
| 4669 * yearBefore |
| 4670 * yearsBefore |
| 4671 * |
| 4672 * @example |
| 4673 * |
| 4674 * (5).daysBefore('tuesday') -> 5 days before tuesday of this week |
| 4675 * (1).yearBefore('January 23, 1997') -> January 23, 1996 |
| 4676 * |
| 4677 *** |
| 4678 * @method [unit]Ago() |
| 4679 * @returns Date |
| 4680 * @short Returns a date that is <n> units ago. |
| 4681 * @extra Note that "months" is ambiguous as a unit of time. If the target dat
e falls on a day that does not exist (ie. August 31 -> February 31), the date wi
ll be shifted to the last day of the month. Be careful using %monthsAgo% if you
need exact precision. |
| 4682 * |
| 4683 * @set |
| 4684 * millisecondAgo |
| 4685 * millisecondsAgo |
| 4686 * secondAgo |
| 4687 * secondsAgo |
| 4688 * minuteAgo |
| 4689 * minutesAgo |
| 4690 * hourAgo |
| 4691 * hoursAgo |
| 4692 * dayAgo |
| 4693 * daysAgo |
| 4694 * weekAgo |
| 4695 * weeksAgo |
| 4696 * monthAgo |
| 4697 * monthsAgo |
| 4698 * yearAgo |
| 4699 * yearsAgo |
| 4700 * |
| 4701 * @example |
| 4702 * |
| 4703 * (5).weeksAgo() -> 5 weeks ago |
| 4704 * (1).yearAgo() -> January 23, 1996 |
| 4705 * |
| 4706 *** |
| 4707 * @method [unit]After([d], [locale] = currentLocale) |
| 4708 * @returns Date |
| 4709 * @short Returns a date <n> units after [d], where <n> is the number. |
| 4710 * @extra [d] will accept a date object, timestamp, or text format. Note that
"months" is ambiguous as a unit of time. If the target date falls on a day that
does not exist (ie. August 31 -> February 31), the date will be shifted to the l
ast day of the month. Be careful using %monthsAfter% if you need exact precision
. See @date_format for more. |
| 4711 * |
| 4712 * @set |
| 4713 * millisecondAfter |
| 4714 * millisecondsAfter |
| 4715 * secondAfter |
| 4716 * secondsAfter |
| 4717 * minuteAfter |
| 4718 * minutesAfter |
| 4719 * hourAfter |
| 4720 * hoursAfter |
| 4721 * dayAfter |
| 4722 * daysAfter |
| 4723 * weekAfter |
| 4724 * weeksAfter |
| 4725 * monthAfter |
| 4726 * monthsAfter |
| 4727 * yearAfter |
| 4728 * yearsAfter |
| 4729 * |
| 4730 * @example |
| 4731 * |
| 4732 * (5).daysAfter('tuesday') -> 5 days after tuesday of this week |
| 4733 * (1).yearAfter('January 23, 1997') -> January 23, 1998 |
| 4734 * |
| 4735 *** |
| 4736 * @method [unit]FromNow() |
| 4737 * @returns Date |
| 4738 * @short Returns a date <n> units from now. |
| 4739 * @extra Note that "months" is ambiguous as a unit of time. If the target dat
e falls on a day that does not exist (ie. August 31 -> February 31), the date wi
ll be shifted to the last day of the month. Be careful using %monthsFromNow% if
you need exact precision. |
| 4740 * |
| 4741 * @set |
| 4742 * millisecondFromNow |
| 4743 * millisecondsFromNow |
| 4744 * secondFromNow |
| 4745 * secondsFromNow |
| 4746 * minuteFromNow |
| 4747 * minutesFromNow |
| 4748 * hourFromNow |
| 4749 * hoursFromNow |
| 4750 * dayFromNow |
| 4751 * daysFromNow |
| 4752 * weekFromNow |
| 4753 * weeksFromNow |
| 4754 * monthFromNow |
| 4755 * monthsFromNow |
| 4756 * yearFromNow |
| 4757 * yearsFromNow |
| 4758 * |
| 4759 * @example |
| 4760 * |
| 4761 * (5).weeksFromNow() -> 5 weeks ago |
| 4762 * (1).yearFromNow() -> January 23, 1998 |
| 4763 * |
| 4764 ***/ |
| 4765 function buildNumberToDateAlias(u, multiplier) { |
| 4766 var name = u.name, methods = {}; |
| 4767 function base() { return round(this * multiplier); } |
| 4768 function after() { return createDate(arguments)[u.addMethod](this); } |
| 4769 function before() { return createDate(arguments)[u.addMethod](-this); } |
| 4770 methods[name] = base; |
| 4771 methods[name + 's'] = base; |
| 4772 methods[name + 'Before'] = before; |
| 4773 methods[name + 'sBefore'] = before; |
| 4774 methods[name + 'Ago'] = before; |
| 4775 methods[name + 'sAgo'] = before; |
| 4776 methods[name + 'After'] = after; |
| 4777 methods[name + 'sAfter'] = after; |
| 4778 methods[name + 'FromNow'] = after; |
| 4779 methods[name + 'sFromNow'] = after; |
| 4780 number.extend(methods); |
| 4781 } |
| 4782 |
| 4783 extend(number, true, true, { |
| 4784 |
| 4785 /*** |
| 4786 * @method duration([locale] = currentLocale) |
| 4787 * @returns String |
| 4788 * @short Takes the number as milliseconds and returns a unit-adjusted local
ized string. |
| 4789 * @extra This method is the same as %Date#relative% without the localized e
quivalent of "from now" or "ago". [locale] can be passed as the first (and only)
parameter. Note that this method is only available when the dates package is in
cluded. |
| 4790 * @example |
| 4791 * |
| 4792 * (500).duration() -> '500 milliseconds' |
| 4793 * (1200).duration() -> '1 second' |
| 4794 * (75).minutes().duration() -> '1 hour' |
| 4795 * (75).minutes().duration('es') -> '1 hora' |
| 4796 * |
| 4797 ***/ |
| 4798 'duration': function(localeCode) { |
| 4799 return getLocalization(localeCode).getDuration(this); |
| 4800 } |
| 4801 |
| 4802 }); |
| 4803 |
| 4804 |
| 4805 English = CurrentLocalization = date.addLocale('en', { |
| 4806 'plural': true, |
| 4807 'timeMarker': 'at', |
| 4808 'ampm': 'am,pm', |
| 4809 'months': 'January,February,March,April,May,June,July,August,September,O
ctober,November,December', |
| 4810 'weekdays': 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday', |
| 4811 'units': 'millisecond:|s,second:|s,minute:|s,hour:|s,day:|s,week:|s,mon
th:|s,year:|s', |
| 4812 'numbers': 'one,two,three,four,five,six,seven,eight,nine,ten', |
| 4813 'articles': 'a,an,the', |
| 4814 'tokens': 'the,st|nd|rd|th,of', |
| 4815 'short': '{Month} {d}, {yyyy}', |
| 4816 'long': '{Month} {d}, {yyyy} {h}:{mm}{tt}', |
| 4817 'full': '{Weekday} {Month} {d}, {yyyy} {h}:{mm}:{ss}{tt}', |
| 4818 'past': '{num} {unit} {sign}', |
| 4819 'future': '{num} {unit} {sign}', |
| 4820 'duration': '{num} {unit}', |
| 4821 'modifiers': [ |
| 4822 { 'name': 'sign', 'src': 'ago|before', 'value': -1 }, |
| 4823 { 'name': 'sign', 'src': 'from now|after|from|in|later', 'value': 1 }, |
| 4824 { 'name': 'edge', 'src': 'last day', 'value': -2 }, |
| 4825 { 'name': 'edge', 'src': 'end', 'value': -1 }, |
| 4826 { 'name': 'edge', 'src': 'first day|beginning', 'value': 1 }, |
| 4827 { 'name': 'shift', 'src': 'last', 'value': -1 }, |
| 4828 { 'name': 'shift', 'src': 'the|this', 'value': 0 }, |
| 4829 { 'name': 'shift', 'src': 'next', 'value': 1 } |
| 4830 ], |
| 4831 'dateParse': [ |
| 4832 '{month} {year}', |
| 4833 '{shift} {unit=5-7}', |
| 4834 '{0?} {date}{1}', |
| 4835 '{0?} {edge} of {shift?} {unit=4-7?}{month?}{year?}' |
| 4836 ], |
| 4837 'timeParse': [ |
| 4838 '{num} {unit} {sign}', |
| 4839 '{sign} {num} {unit}', |
| 4840 '{0} {num}{1} {day} of {month} {year?}', |
| 4841 '{weekday?} {month} {date}{1?} {year?}', |
| 4842 '{date} {month} {year}', |
| 4843 '{date} {month}', |
| 4844 '{shift} {weekday}', |
| 4845 '{shift} week {weekday}', |
| 4846 '{weekday} {2?} {shift} week', |
| 4847 '{num} {unit=4-5} {sign} {day}', |
| 4848 '{0?} {date}{1} of {month}', |
| 4849 '{0?}{month?} {date?}{1?} of {shift} {unit=6-7}' |
| 4850 ] |
| 4851 }); |
| 4852 |
| 4853 buildDateUnits(); |
| 4854 buildDateMethods(); |
| 4855 buildCoreInputFormats(); |
| 4856 buildFormatTokens(); |
| 4857 buildFormatShortcuts(); |
| 4858 buildAsianDigits(); |
| 4859 buildRelativeAliases(); |
| 4860 buildUTCAliases(); |
| 4861 setDateProperties(); |
| 4862 |
| 4863 |
| 4864 /*** |
| 4865 * @package Function |
| 4866 * @dependency core |
| 4867 * @description Lazy, throttled, and memoized functions, delayed functions and
handling of timers, argument currying. |
| 4868 * |
| 4869 ***/ |
| 4870 |
| 4871 function setDelay(fn, ms, after, scope, args) { |
| 4872 // Delay of infinity is never called of course... |
| 4873 if(ms === Infinity) return; |
| 4874 if(!fn.timers) fn.timers = []; |
| 4875 if(!isNumber(ms)) ms = 1; |
| 4876 // This is a workaround for <= IE8, which apparently has the |
| 4877 // ability to call timeouts in the queue on the same tick (ms?) |
| 4878 // even if functionally they have already been cleared. |
| 4879 fn._canceled = false; |
| 4880 fn.timers.push(setTimeout(function(){ |
| 4881 if(!fn._canceled) { |
| 4882 after.apply(scope, args || []); |
| 4883 } |
| 4884 }, ms)); |
| 4885 } |
| 4886 |
| 4887 extend(Function, true, true, { |
| 4888 |
| 4889 /*** |
| 4890 * @method lazy([ms] = 1, [immediate] = false, [limit] = Infinity) |
| 4891 * @returns Function |
| 4892 * @short Creates a lazy function that, when called repeatedly, will queue e
xecution and wait [ms] milliseconds to execute. |
| 4893 * @extra If [immediate] is %true%, first execution will happen immediately,
then lock. If [limit] is a fininte number, calls past [limit] will be ignored w
hile execution is locked. Compare this to %throttle%, which will execute only on
ce per [ms] milliseconds. Note that [ms] can also be a fraction. Calling %cancel
% on a lazy function will clear the entire queue. For more see @functions. |
| 4894 * @example |
| 4895 * |
| 4896 * (function() { |
| 4897 * // Executes immediately. |
| 4898 * }).lazy()(); |
| 4899 * (3).times(function() { |
| 4900 * // Executes 3 times, with each execution 20ms later than the last. |
| 4901 * }.lazy(20)); |
| 4902 * (100).times(function() { |
| 4903 * // Executes 50 times, with each execution 20ms later than the last. |
| 4904 * }.lazy(20, false, 50)); |
| 4905 * |
| 4906 ***/ |
| 4907 'lazy': function(ms, immediate, limit) { |
| 4908 var fn = this, queue = [], locked = false, execute, rounded, perExecution,
result; |
| 4909 ms = ms || 1; |
| 4910 limit = limit || Infinity; |
| 4911 rounded = ceil(ms); |
| 4912 perExecution = round(rounded / ms) || 1; |
| 4913 execute = function() { |
| 4914 var queueLength = queue.length, maxPerRound; |
| 4915 if(queueLength == 0) return; |
| 4916 // Allow fractions of a millisecond by calling |
| 4917 // multiple times per actual timeout execution |
| 4918 maxPerRound = max(queueLength - perExecution, 0); |
| 4919 while(queueLength > maxPerRound) { |
| 4920 // Getting uber-meta here... |
| 4921 result = Function.prototype.apply.apply(fn, queue.shift()); |
| 4922 queueLength--; |
| 4923 } |
| 4924 setDelay(lazy, rounded, function() { |
| 4925 locked = false; |
| 4926 execute(); |
| 4927 }); |
| 4928 } |
| 4929 function lazy() { |
| 4930 // If the execution has locked and it's immediate, then |
| 4931 // allow 1 less in the queue as 1 call has already taken place. |
| 4932 if(queue.length < limit - (locked && immediate ? 1 : 0)) { |
| 4933 queue.push([this, arguments]); |
| 4934 } |
| 4935 if(!locked) { |
| 4936 locked = true; |
| 4937 if(immediate) { |
| 4938 execute(); |
| 4939 } else { |
| 4940 setDelay(lazy, rounded, execute); |
| 4941 } |
| 4942 } |
| 4943 // Return the memoized result |
| 4944 return result; |
| 4945 } |
| 4946 return lazy; |
| 4947 }, |
| 4948 |
| 4949 /*** |
| 4950 * @method throttle([ms] = 1) |
| 4951 * @returns Function |
| 4952 * @short Creates a "throttled" version of the function that will only be ex
ecuted once per <ms> milliseconds. |
| 4953 * @extra This is functionally equivalent to calling %lazy% with a [limit] o
f %1% and [immediate] as %true%. %throttle% is appropriate when you want to make
sure a function is only executed at most once for a given duration. For more se
e @functions. |
| 4954 * @example |
| 4955 * |
| 4956 * (3).times(function() { |
| 4957 * // called only once. will wait 50ms until it responds again |
| 4958 * }.throttle(50)); |
| 4959 * |
| 4960 ***/ |
| 4961 'throttle': function(ms) { |
| 4962 return this.lazy(ms, true, 1); |
| 4963 }, |
| 4964 |
| 4965 /*** |
| 4966 * @method debounce([ms] = 1) |
| 4967 * @returns Function |
| 4968 * @short Creates a "debounced" function that postpones its execution until
after <ms> milliseconds have passed. |
| 4969 * @extra This method is useful to execute a function after things have "set
tled down". A good example of this is when a user tabs quickly through form fiel
ds, execution of a heavy operation should happen after a few milliseconds when t
hey have "settled" on a field. For more see @functions. |
| 4970 * @example |
| 4971 * |
| 4972 * var fn = (function(arg1) { |
| 4973 * // called once 50ms later |
| 4974 * }).debounce(50); fn() fn() fn(); |
| 4975 * |
| 4976 ***/ |
| 4977 'debounce': function(ms) { |
| 4978 var fn = this; |
| 4979 function debounced() { |
| 4980 debounced.cancel(); |
| 4981 setDelay(debounced, ms, fn, this, arguments); |
| 4982 }; |
| 4983 return debounced; |
| 4984 }, |
| 4985 |
| 4986 /*** |
| 4987 * @method delay([ms] = 1, [arg1], ...) |
| 4988 * @returns Function |
| 4989 * @short Executes the function after <ms> milliseconds. |
| 4990 * @extra Returns a reference to itself. %delay% is also a way to execute no
n-blocking operations that will wait until the CPU is free. Delayed functions ca
n be canceled using the %cancel% method. Can also curry arguments passed in afte
r <ms>. |
| 4991 * @example |
| 4992 * |
| 4993 * (function(arg1) { |
| 4994 * // called 1s later |
| 4995 * }).delay(1000, 'arg1'); |
| 4996 * |
| 4997 ***/ |
| 4998 'delay': function(ms) { |
| 4999 var fn = this; |
| 5000 var args = multiArgs(arguments, null, 1); |
| 5001 setDelay(fn, ms, fn, fn, args); |
| 5002 return fn; |
| 5003 }, |
| 5004 |
| 5005 /*** |
| 5006 * @method every([ms] = 1, [arg1], ...) |
| 5007 * @returns Function |
| 5008 * @short Executes the function every <ms> milliseconds. |
| 5009 * @extra Returns a reference to itself. Repeating functions with %every% ca
n be canceled using the %cancel% method. Can also curry arguments passed in afte
r <ms>. |
| 5010 * @example |
| 5011 * |
| 5012 * (function(arg1) { |
| 5013 * // called every 1s |
| 5014 * }).every(1000, 'arg1'); |
| 5015 * |
| 5016 ***/ |
| 5017 'every': function(ms) { |
| 5018 var fn = this, args = arguments; |
| 5019 args = args.length > 1 ? multiArgs(args, null, 1) : []; |
| 5020 function execute () { |
| 5021 fn.apply(fn, args); |
| 5022 setDelay(fn, ms, execute); |
| 5023 } |
| 5024 setDelay(fn, ms, execute); |
| 5025 return fn; |
| 5026 }, |
| 5027 |
| 5028 /*** |
| 5029 * @method cancel() |
| 5030 * @returns Function |
| 5031 * @short Cancels a delayed function scheduled to be run. |
| 5032 * @extra %delay%, %lazy%, %throttle%, and %debounce% can all set delays. |
| 5033 * @example |
| 5034 * |
| 5035 * (function() { |
| 5036 * alert('hay'); // Never called |
| 5037 * }).delay(500).cancel(); |
| 5038 * |
| 5039 ***/ |
| 5040 'cancel': function() { |
| 5041 var timers = this.timers, timer; |
| 5042 if(isArray(timers)) { |
| 5043 while(timer = timers.shift()) { |
| 5044 clearTimeout(timer); |
| 5045 } |
| 5046 } |
| 5047 this._canceled = true; |
| 5048 return this; |
| 5049 }, |
| 5050 |
| 5051 /*** |
| 5052 * @method after([num] = 1) |
| 5053 * @returns Function |
| 5054 * @short Creates a function that will execute after [num] calls. |
| 5055 * @extra %after% is useful for running a final callback after a series of a
synchronous operations, when the order in which the operations will complete is
unknown. |
| 5056 * @example |
| 5057 * |
| 5058 * var fn = (function() { |
| 5059 * // Will be executed once only |
| 5060 * }).after(3); fn(); fn(); fn(); |
| 5061 * |
| 5062 ***/ |
| 5063 'after': function(num) { |
| 5064 var fn = this, counter = 0, storedArguments = []; |
| 5065 if(!isNumber(num)) { |
| 5066 num = 1; |
| 5067 } else if(num === 0) { |
| 5068 fn.call(); |
| 5069 return fn; |
| 5070 } |
| 5071 return function() { |
| 5072 var ret; |
| 5073 storedArguments.push(multiArgs(arguments)); |
| 5074 counter++; |
| 5075 if(counter == num) { |
| 5076 ret = fn.call(this, storedArguments); |
| 5077 counter = 0; |
| 5078 storedArguments = []; |
| 5079 return ret; |
| 5080 } |
| 5081 } |
| 5082 }, |
| 5083 |
| 5084 /*** |
| 5085 * @method once() |
| 5086 * @returns Function |
| 5087 * @short Creates a function that will execute only once and store the resul
t. |
| 5088 * @extra %once% is useful for creating functions that will cache the result
of an expensive operation and use it on subsequent calls. Also it can be useful
for creating initialization functions that only need to be run once. |
| 5089 * @example |
| 5090 * |
| 5091 * var fn = (function() { |
| 5092 * // Will be executed once only |
| 5093 * }).once(); fn(); fn(); fn(); |
| 5094 * |
| 5095 ***/ |
| 5096 'once': function() { |
| 5097 return this.throttle(Infinity, true); |
| 5098 }, |
| 5099 |
| 5100 /*** |
| 5101 * @method fill(<arg1>, <arg2>, ...) |
| 5102 * @returns Function |
| 5103 * @short Returns a new version of the function which when called will have
some of its arguments pre-emptively filled in, also known as "currying". |
| 5104 * @extra Arguments passed to a "filled" function are generally appended to
the curried arguments. However, if %undefined% is passed as any of the arguments
to %fill%, it will be replaced, when the "filled" function is executed. This al
lows currying of arguments even when they occur toward the end of an argument li
st (the example demonstrates this much more clearly). |
| 5105 * @example |
| 5106 * |
| 5107 * var delayOneSecond = setTimeout.fill(undefined, 1000); |
| 5108 * delayOneSecond(function() { |
| 5109 * // Will be executed 1s later |
| 5110 * }); |
| 5111 * |
| 5112 ***/ |
| 5113 'fill': function() { |
| 5114 var fn = this, curried = multiArgs(arguments); |
| 5115 return function() { |
| 5116 var args = multiArgs(arguments); |
| 5117 curried.forEach(function(arg, index) { |
| 5118 if(arg != null || index >= args.length) args.splice(index, 0, arg); |
| 5119 }); |
| 5120 return fn.apply(this, args); |
| 5121 } |
| 5122 } |
| 5123 |
| 5124 |
| 5125 }); |
| 5126 |
| 5127 |
| 5128 /*** |
| 5129 * @package Number |
| 5130 * @dependency core |
| 5131 * @description Number formatting, rounding (with precision), and ranges. Alia
ses to Math methods. |
| 5132 * |
| 5133 ***/ |
| 5134 |
| 5135 |
| 5136 function abbreviateNumber(num, roundTo, str, mid, limit, bytes) { |
| 5137 var fixed = num.toFixed(20), |
| 5138 decimalPlace = fixed.search(/\./), |
| 5139 numeralPlace = fixed.search(/[1-9]/), |
| 5140 significant = decimalPlace - numeralPlace, |
| 5141 unit, i, divisor; |
| 5142 if(significant > 0) { |
| 5143 significant -= 1; |
| 5144 } |
| 5145 i = max(min(floor(significant / 3), limit === false ? str.length : limit), -
mid); |
| 5146 unit = str.charAt(i + mid - 1); |
| 5147 if(significant < -9) { |
| 5148 i = -3; |
| 5149 roundTo = abs(significant) - 9; |
| 5150 unit = str.slice(0,1); |
| 5151 } |
| 5152 divisor = bytes ? pow(2, 10 * i) : pow(10, i * 3); |
| 5153 return withPrecision(num / divisor, roundTo || 0).format() + unit.trim(); |
| 5154 } |
| 5155 |
| 5156 |
| 5157 extend(number, false, true, { |
| 5158 |
| 5159 /*** |
| 5160 * @method Number.random([n1], [n2]) |
| 5161 * @returns Number |
| 5162 * @short Returns a random integer between [n1] and [n2]. |
| 5163 * @extra If only 1 number is passed, the other will be 0. If none are passe
d, the number will be either 0 or 1. |
| 5164 * @example |
| 5165 * |
| 5166 * Number.random(50, 100) -> ex. 85 |
| 5167 * Number.random(50) -> ex. 27 |
| 5168 * Number.random() -> ex. 0 |
| 5169 * |
| 5170 ***/ |
| 5171 'random': function(n1, n2) { |
| 5172 var minNum, maxNum; |
| 5173 if(arguments.length == 1) n2 = n1, n1 = 0; |
| 5174 minNum = min(n1 || 0, isUndefined(n2) ? 1 : n2); |
| 5175 maxNum = max(n1 || 0, isUndefined(n2) ? 1 : n2) + 1; |
| 5176 return floor((math.random() * (maxNum - minNum)) + minNum); |
| 5177 } |
| 5178 |
| 5179 }); |
| 5180 |
| 5181 extend(number, true, true, { |
| 5182 |
| 5183 /*** |
| 5184 * @method log(<base> = Math.E) |
| 5185 * @returns Number |
| 5186 * @short Returns the logarithm of the number with base <base>, or natural l
ogarithm of the number if <base> is undefined. |
| 5187 * @example |
| 5188 * |
| 5189 * (64).log(2) -> 6 |
| 5190 * (9).log(3) -> 2 |
| 5191 * (5).log() -> 1.6094379124341003 |
| 5192 * |
| 5193 ***/ |
| 5194 |
| 5195 'log': function(base) { |
| 5196 return math.log(this) / (base ? math.log(base) : 1); |
| 5197 }, |
| 5198 |
| 5199 /*** |
| 5200 * @method abbr([precision] = 0) |
| 5201 * @returns String |
| 5202 * @short Returns an abbreviated form of the number. |
| 5203 * @extra [precision] will round to the given precision. |
| 5204 * @example |
| 5205 * |
| 5206 * (1000).abbr() -> "1k" |
| 5207 * (1000000).abbr() -> "1m" |
| 5208 * (1280).abbr(1) -> "1.3k" |
| 5209 * |
| 5210 ***/ |
| 5211 'abbr': function(precision) { |
| 5212 return abbreviateNumber(this, precision, 'kmbt', 0, 4); |
| 5213 }, |
| 5214 |
| 5215 /*** |
| 5216 * @method metric([precision] = 0, [limit] = 1) |
| 5217 * @returns String |
| 5218 * @short Returns the number as a string in metric notation. |
| 5219 * @extra [precision] will round to the given precision. Both very large num
bers and very small numbers are supported. [limit] is the upper limit for the un
its. The default is %1%, which is "kilo". If [limit] is %false%, the upper limit
will be "exa". The lower limit is "nano", and cannot be changed. |
| 5220 * @example |
| 5221 * |
| 5222 * (1000).metric() -> "1k" |
| 5223 * (1000000).metric() -> "1,000k" |
| 5224 * (1000000).metric(0, false) -> "1M" |
| 5225 * (1249).metric(2) + 'g' -> "1.25kg" |
| 5226 * (0.025).metric() + 'm' -> "25mm" |
| 5227 * |
| 5228 ***/ |
| 5229 'metric': function(precision, limit) { |
| 5230 return abbreviateNumber(this, precision, 'nμm kMGTPE', 4, isUndefined(limi
t) ? 1 : limit); |
| 5231 }, |
| 5232 |
| 5233 /*** |
| 5234 * @method bytes([precision] = 0, [limit] = 4) |
| 5235 * @returns String |
| 5236 * @short Returns an abbreviated form of the number, considered to be "Bytes
". |
| 5237 * @extra [precision] will round to the given precision. [limit] is the uppe
r limit for the units. The default is %4%, which is "terabytes" (TB). If [limit]
is %false%, the upper limit will be "exa". |
| 5238 * @example |
| 5239 * |
| 5240 * (1000).bytes() -> "1kB" |
| 5241 * (1000).bytes(2) -> "0.98kB" |
| 5242 * ((10).pow(20)).bytes() -> "90,949,470TB" |
| 5243 * ((10).pow(20)).bytes(0, false) -> "87EB" |
| 5244 * |
| 5245 ***/ |
| 5246 'bytes': function(precision, limit) { |
| 5247 return abbreviateNumber(this, precision, 'kMGTPE', 0, isUndefined(limit) ?
4 : limit, true) + 'B'; |
| 5248 }, |
| 5249 |
| 5250 /*** |
| 5251 * @method isInteger() |
| 5252 * @returns Boolean |
| 5253 * @short Returns true if the number has no trailing decimal. |
| 5254 * @example |
| 5255 * |
| 5256 * (420).isInteger() -> true |
| 5257 * (4.5).isInteger() -> false |
| 5258 * |
| 5259 ***/ |
| 5260 'isInteger': function() { |
| 5261 return this % 1 == 0; |
| 5262 }, |
| 5263 |
| 5264 /*** |
| 5265 * @method isOdd() |
| 5266 * @returns Boolean |
| 5267 * @short Returns true if the number is odd. |
| 5268 * @example |
| 5269 * |
| 5270 * (3).isOdd() -> true |
| 5271 * (18).isOdd() -> false |
| 5272 * |
| 5273 ***/ |
| 5274 'isOdd': function() { |
| 5275 return !isNaN(this) && !this.isMultipleOf(2); |
| 5276 }, |
| 5277 |
| 5278 /*** |
| 5279 * @method isEven() |
| 5280 * @returns Boolean |
| 5281 * @short Returns true if the number is even. |
| 5282 * @example |
| 5283 * |
| 5284 * (6).isEven() -> true |
| 5285 * (17).isEven() -> false |
| 5286 * |
| 5287 ***/ |
| 5288 'isEven': function() { |
| 5289 return this.isMultipleOf(2); |
| 5290 }, |
| 5291 |
| 5292 /*** |
| 5293 * @method isMultipleOf(<num>) |
| 5294 * @returns Boolean |
| 5295 * @short Returns true if the number is a multiple of <num>. |
| 5296 * @example |
| 5297 * |
| 5298 * (6).isMultipleOf(2) -> true |
| 5299 * (17).isMultipleOf(2) -> false |
| 5300 * (32).isMultipleOf(4) -> true |
| 5301 * (34).isMultipleOf(4) -> false |
| 5302 * |
| 5303 ***/ |
| 5304 'isMultipleOf': function(num) { |
| 5305 return this % num === 0; |
| 5306 }, |
| 5307 |
| 5308 |
| 5309 /*** |
| 5310 * @method format([place] = 0, [thousands] = ',', [decimal] = '.') |
| 5311 * @returns String |
| 5312 * @short Formats the number to a readable string. |
| 5313 * @extra If [place] is %undefined%, will automatically determine the place.
[thousands] is the character used for the thousands separator. [decimal] is the
character used for the decimal point. |
| 5314 * @example |
| 5315 * |
| 5316 * (56782).format() -> '56,782' |
| 5317 * (56782).format(2) -> '56,782.00' |
| 5318 * (4388.43).format(2, ' ') -> '4 388.43' |
| 5319 * (4388.43).format(2, '.', ',') -> '4.388,43' |
| 5320 * |
| 5321 ***/ |
| 5322 'format': function(place, thousands, decimal) { |
| 5323 var i, str, split, integer, fraction, result = ''; |
| 5324 if(isUndefined(thousands)) { |
| 5325 thousands = ','; |
| 5326 } |
| 5327 if(isUndefined(decimal)) { |
| 5328 decimal = '.'; |
| 5329 } |
| 5330 str = (isNumber(place) ? withPrecision(this, place || 0).toFixed(max(
place, 0)) : this.toString()).replace(/^-/, ''); |
| 5331 split = str.split('.'); |
| 5332 integer = split[0]; |
| 5333 fraction = split[1]; |
| 5334 for(i = integer.length; i > 0; i -= 3) { |
| 5335 if(i < integer.length) { |
| 5336 result = thousands + result; |
| 5337 } |
| 5338 result = integer.slice(max(0, i - 3), i) + result; |
| 5339 } |
| 5340 if(fraction) { |
| 5341 result += decimal + repeatString('0', (place || 0) - fraction.length) +
fraction; |
| 5342 } |
| 5343 return (this < 0 ? '-' : '') + result; |
| 5344 }, |
| 5345 |
| 5346 /*** |
| 5347 * @method hex([pad] = 1) |
| 5348 * @returns String |
| 5349 * @short Converts the number to hexidecimal. |
| 5350 * @extra [pad] will pad the resulting string to that many places. |
| 5351 * @example |
| 5352 * |
| 5353 * (255).hex() -> 'ff'; |
| 5354 * (255).hex(4) -> '00ff'; |
| 5355 * (23654).hex() -> '5c66'; |
| 5356 * |
| 5357 ***/ |
| 5358 'hex': function(pad) { |
| 5359 return this.pad(pad || 1, false, 16); |
| 5360 }, |
| 5361 |
| 5362 /*** |
| 5363 * @method times(<fn>) |
| 5364 * @returns Number |
| 5365 * @short Calls <fn> a number of times equivalent to the number. |
| 5366 * @example |
| 5367 * |
| 5368 * (8).times(function(i) { |
| 5369 * // This function is called 8 times. |
| 5370 * }); |
| 5371 * |
| 5372 ***/ |
| 5373 'times': function(fn) { |
| 5374 if(fn) { |
| 5375 for(var i = 0; i < this; i++) { |
| 5376 fn.call(this, i); |
| 5377 } |
| 5378 } |
| 5379 return this.toNumber(); |
| 5380 }, |
| 5381 |
| 5382 /*** |
| 5383 * @method chr() |
| 5384 * @returns String |
| 5385 * @short Returns a string at the code point of the number. |
| 5386 * @example |
| 5387 * |
| 5388 * (65).chr() -> "A" |
| 5389 * (75).chr() -> "K" |
| 5390 * |
| 5391 ***/ |
| 5392 'chr': function() { |
| 5393 return string.fromCharCode(this); |
| 5394 }, |
| 5395 |
| 5396 /*** |
| 5397 * @method pad(<place> = 0, [sign] = false, [base] = 10) |
| 5398 * @returns String |
| 5399 * @short Pads a number with "0" to <place>. |
| 5400 * @extra [sign] allows you to force the sign as well (+05, etc). [base] can
change the base for numeral conversion. |
| 5401 * @example |
| 5402 * |
| 5403 * (5).pad(2) -> '05' |
| 5404 * (-5).pad(4) -> '-0005' |
| 5405 * (82).pad(3, true) -> '+082' |
| 5406 * |
| 5407 ***/ |
| 5408 'pad': function(place, sign, base) { |
| 5409 return padNumber(this, place, sign, base); |
| 5410 }, |
| 5411 |
| 5412 /*** |
| 5413 * @method ordinalize() |
| 5414 * @returns String |
| 5415 * @short Returns an ordinalized (English) string, i.e. "1st", "2nd", etc. |
| 5416 * @example |
| 5417 * |
| 5418 * (1).ordinalize() -> '1st'; |
| 5419 * (2).ordinalize() -> '2nd'; |
| 5420 * (8).ordinalize() -> '8th'; |
| 5421 * |
| 5422 ***/ |
| 5423 'ordinalize': function() { |
| 5424 var suffix, num = abs(this), last = parseInt(num.toString().slice(-2)); |
| 5425 return this + getOrdinalizedSuffix(last); |
| 5426 }, |
| 5427 |
| 5428 /*** |
| 5429 * @method toNumber() |
| 5430 * @returns Number |
| 5431 * @short Returns a number. This is mostly for compatibility reasons. |
| 5432 * @example |
| 5433 * |
| 5434 * (420).toNumber() -> 420 |
| 5435 * |
| 5436 ***/ |
| 5437 'toNumber': function() { |
| 5438 return parseFloat(this, 10); |
| 5439 } |
| 5440 |
| 5441 }); |
| 5442 |
| 5443 /*** |
| 5444 * @method round(<precision> = 0) |
| 5445 * @returns Number |
| 5446 * @short Shortcut for %Math.round% that also allows a <precision>. |
| 5447 * |
| 5448 * @example |
| 5449 * |
| 5450 * (3.241).round() -> 3 |
| 5451 * (-3.841).round() -> -4 |
| 5452 * (3.241).round(2) -> 3.24 |
| 5453 * (3748).round(-2) -> 3800 |
| 5454 * |
| 5455 *** |
| 5456 * @method ceil(<precision> = 0) |
| 5457 * @returns Number |
| 5458 * @short Shortcut for %Math.ceil% that also allows a <precision>. |
| 5459 * |
| 5460 * @example |
| 5461 * |
| 5462 * (3.241).ceil() -> 4 |
| 5463 * (-3.241).ceil() -> -3 |
| 5464 * (3.241).ceil(2) -> 3.25 |
| 5465 * (3748).ceil(-2) -> 3800 |
| 5466 * |
| 5467 *** |
| 5468 * @method floor(<precision> = 0) |
| 5469 * @returns Number |
| 5470 * @short Shortcut for %Math.floor% that also allows a <precision>. |
| 5471 * |
| 5472 * @example |
| 5473 * |
| 5474 * (3.241).floor() -> 3 |
| 5475 * (-3.841).floor() -> -4 |
| 5476 * (3.241).floor(2) -> 3.24 |
| 5477 * (3748).floor(-2) -> 3700 |
| 5478 * |
| 5479 *** |
| 5480 * @method [math]() |
| 5481 * @returns Number |
| 5482 * @short Math related functions are mapped as shortcuts to numbers and are id
entical. Note that %Number#log% provides some special defaults. |
| 5483 * |
| 5484 * @set |
| 5485 * abs |
| 5486 * sin |
| 5487 * asin |
| 5488 * cos |
| 5489 * acos |
| 5490 * tan |
| 5491 * atan |
| 5492 * sqrt |
| 5493 * exp |
| 5494 * pow |
| 5495 * |
| 5496 * @example |
| 5497 * |
| 5498 * (3).pow(3) -> 27 |
| 5499 * (-3).abs() -> 3 |
| 5500 * (1024).sqrt() -> 32 |
| 5501 * |
| 5502 ***/ |
| 5503 |
| 5504 function buildNumber() { |
| 5505 function createRoundingFunction(fn) { |
| 5506 return function (precision) { |
| 5507 return precision ? withPrecision(this, precision, fn) : fn(this); |
| 5508 } |
| 5509 } |
| 5510 extend(number, true, true, { |
| 5511 'ceil': createRoundingFunction(ceil), |
| 5512 'round': createRoundingFunction(round), |
| 5513 'floor': createRoundingFunction(floor) |
| 5514 }); |
| 5515 extendSimilar(number, true, true, 'abs,pow,sin,asin,cos,acos,tan,atan,exp,po
w,sqrt', function(methods, name) { |
| 5516 methods[name] = function(a, b) { |
| 5517 return math[name](this, a, b); |
| 5518 } |
| 5519 }); |
| 5520 } |
| 5521 |
| 5522 buildNumber(); |
| 5523 |
| 5524 |
| 5525 /*** |
| 5526 * @package Object |
| 5527 * @dependency core |
| 5528 * @description Object manipulation, type checking (isNumber, isString, ...),
extended objects with hash-like methods available as instance methods. |
| 5529 * |
| 5530 * Much thanks to kangax for his informative aricle about how problems with in
stanceof and constructor |
| 5531 * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-
robust-isarray/ |
| 5532 * |
| 5533 ***/ |
| 5534 |
| 5535 var ObjectTypeMethods = 'isObject,isNaN'.split(','); |
| 5536 var ObjectHashMethods = 'keys,values,select,reject,each,merge,clone,equal,watc
h,tap,has,toQueryString'.split(','); |
| 5537 |
| 5538 function setParamsObject(obj, param, value, castBoolean) { |
| 5539 var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key; |
| 5540 if(match = param.match(reg)) { |
| 5541 key = match[1]; |
| 5542 allKeys = match[2].replace(/^\[|\]$/g, '').split(']['); |
| 5543 allKeys.forEach(function(k) { |
| 5544 paramIsArray = !k || k.match(/^\d+$/); |
| 5545 if(!key && isArray(obj)) key = obj.length; |
| 5546 if(!hasOwnProperty(obj, key)) { |
| 5547 obj[key] = paramIsArray ? [] : {}; |
| 5548 } |
| 5549 obj = obj[key]; |
| 5550 key = k; |
| 5551 }); |
| 5552 if(!key && paramIsArray) key = obj.length.toString(); |
| 5553 setParamsObject(obj, key, value, castBoolean); |
| 5554 } else if(castBoolean && value === 'true') { |
| 5555 obj[param] = true; |
| 5556 } else if(castBoolean && value === 'false') { |
| 5557 obj[param] = false; |
| 5558 } else { |
| 5559 obj[param] = value; |
| 5560 } |
| 5561 } |
| 5562 |
| 5563 function objectToQueryString(base, obj) { |
| 5564 var tmp; |
| 5565 // If a custom toString exists bail here and use that instead |
| 5566 if(isArray(obj) || (isObjectType(obj) && obj.toString === internalToString))
{ |
| 5567 tmp = []; |
| 5568 iterateOverObject(obj, function(key, value) { |
| 5569 if(base) { |
| 5570 key = base + '[' + key + ']'; |
| 5571 } |
| 5572 tmp.push(objectToQueryString(key, value)); |
| 5573 }); |
| 5574 return tmp.join('&'); |
| 5575 } else { |
| 5576 if(!base) return ''; |
| 5577 return sanitizeURIComponent(base) + '=' + (isDate(obj) ? obj.getTime() : s
anitizeURIComponent(obj)); |
| 5578 } |
| 5579 } |
| 5580 |
| 5581 function sanitizeURIComponent(obj) { |
| 5582 // undefined, null, and NaN are represented as a blank string, |
| 5583 // while false and 0 are stringified. "+" is allowed in query string |
| 5584 return !obj && obj !== false && obj !== 0 ? '' : encodeURIComponent(obj).rep
lace(/%20/g, '+'); |
| 5585 } |
| 5586 |
| 5587 function matchInObject(match, key, value) { |
| 5588 if(isRegExp(match)) { |
| 5589 return match.test(key); |
| 5590 } else if(isObjectType(match)) { |
| 5591 return match[key] === value; |
| 5592 } else { |
| 5593 return key === string(match); |
| 5594 } |
| 5595 } |
| 5596 |
| 5597 function selectFromObject(obj, args, select) { |
| 5598 var match, result = obj instanceof Hash ? new Hash : {}; |
| 5599 iterateOverObject(obj, function(key, value) { |
| 5600 match = false; |
| 5601 flattenedArgs(args, function(arg) { |
| 5602 if(matchInObject(arg, key, value)) { |
| 5603 match = true; |
| 5604 } |
| 5605 }, 1); |
| 5606 if(match === select) { |
| 5607 result[key] = value; |
| 5608 } |
| 5609 }); |
| 5610 return result; |
| 5611 } |
| 5612 |
| 5613 |
| 5614 /*** |
| 5615 * @method Object.is[Type](<obj>) |
| 5616 * @returns Boolean |
| 5617 * @short Returns true if <obj> is an object of that type. |
| 5618 * @extra %isObject% will return false on anything that is not an object liter
al, including instances of inherited classes. Note also that %isNaN% will ONLY r
eturn true if the object IS %NaN%. It does not mean the same as browser native %
isNaN%, which returns true for anything that is "not a number". |
| 5619 * |
| 5620 * @set |
| 5621 * isArray |
| 5622 * isObject |
| 5623 * isBoolean |
| 5624 * isDate |
| 5625 * isFunction |
| 5626 * isNaN |
| 5627 * isNumber |
| 5628 * isString |
| 5629 * isRegExp |
| 5630 * |
| 5631 * @example |
| 5632 * |
| 5633 * Object.isArray([1,2,3]) -> true |
| 5634 * Object.isDate(3) -> false |
| 5635 * Object.isRegExp(/wasabi/) -> true |
| 5636 * Object.isObject({ broken:'wear' }) -> true |
| 5637 * |
| 5638 ***/ |
| 5639 function buildTypeMethods() { |
| 5640 extendSimilar(object, false, true, ClassNames, function(methods, name) { |
| 5641 var method = 'is' + name; |
| 5642 ObjectTypeMethods.push(method); |
| 5643 methods[method] = typeChecks[name]; |
| 5644 }); |
| 5645 } |
| 5646 |
| 5647 function buildObjectExtend() { |
| 5648 extend(object, false, function(){ return arguments.length === 0; }, { |
| 5649 'extend': function() { |
| 5650 var methods = ObjectTypeMethods.concat(ObjectHashMethods) |
| 5651 if(typeof EnumerableMethods !== 'undefined') { |
| 5652 methods = methods.concat(EnumerableMethods); |
| 5653 } |
| 5654 buildObjectInstanceMethods(methods, object); |
| 5655 } |
| 5656 }); |
| 5657 } |
| 5658 |
| 5659 extend(object, false, true, { |
| 5660 /*** |
| 5661 * @method watch(<obj>, <prop>, <fn>) |
| 5662 * @returns Nothing |
| 5663 * @short Watches a property of <obj> and runs <fn> when it changes. |
| 5664 * @extra <fn> is passed three arguments: the property <prop>, the old val
ue, and the new value. The return value of [fn] will be set as the new value. Th
is method is useful for things such as validating or cleaning the value when it
is set. Warning: this method WILL NOT work in browsers that don't support %Objec
t.defineProperty% (IE 8 and below). This is the only method in Sugar that is not
fully compatible with all browsers. %watch% is available as an instance method
on extended objects. |
| 5665 * @example |
| 5666 * |
| 5667 * Object.watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) { |
| 5668 * // Will be run when the property 'foo' is set on the object. |
| 5669 * }); |
| 5670 * Object.extended().watch({ foo: 'bar' }, 'foo', function(prop, oldVal,
newVal) { |
| 5671 * // Will be run when the property 'foo' is set on the object. |
| 5672 * }); |
| 5673 * |
| 5674 ***/ |
| 5675 'watch': function(obj, prop, fn) { |
| 5676 if(!definePropertySupport) return; |
| 5677 var value = obj[prop]; |
| 5678 object.defineProperty(obj, prop, { |
| 5679 'enumerable' : true, |
| 5680 'configurable': true, |
| 5681 'get': function() { |
| 5682 return value; |
| 5683 }, |
| 5684 'set': function(to) { |
| 5685 value = fn.call(obj, prop, value, to); |
| 5686 } |
| 5687 }); |
| 5688 } |
| 5689 }); |
| 5690 |
| 5691 extend(object, false, function() { return arguments.length > 1; }, { |
| 5692 |
| 5693 /*** |
| 5694 * @method keys(<obj>, [fn]) |
| 5695 * @returns Array |
| 5696 * @short Returns an array containing the keys in <obj>. Optionally calls [f
n] for each key. |
| 5697 * @extra This method is provided for browsers that don't support it nativel
y, and additionally is enhanced to accept the callback [fn]. Returned keys are i
n no particular order. %keys% is available as an instance method on extended obj
ects. |
| 5698 * @example |
| 5699 * |
| 5700 * Object.keys({ broken: 'wear' }) -> ['broken'] |
| 5701 * Object.keys({ broken: 'wear' }, function(key, value) { |
| 5702 * // Called once for each key. |
| 5703 * }); |
| 5704 * Object.extended({ broken: 'wear' }).keys() -> ['broken'] |
| 5705 * |
| 5706 ***/ |
| 5707 'keys': function(obj, fn) { |
| 5708 var keys = object.keys(obj); |
| 5709 keys.forEach(function(key) { |
| 5710 fn.call(obj, key, obj[key]); |
| 5711 }); |
| 5712 return keys; |
| 5713 } |
| 5714 |
| 5715 }); |
| 5716 |
| 5717 extend(object, false, true, { |
| 5718 |
| 5719 'isObject': function(obj) { |
| 5720 return isPlainObject(obj); |
| 5721 }, |
| 5722 |
| 5723 'isNaN': function(obj) { |
| 5724 // This is only true of NaN |
| 5725 return isNumber(obj) && obj.valueOf() !== obj.valueOf(); |
| 5726 }, |
| 5727 |
| 5728 /*** |
| 5729 * @method equal(<a>, <b>) |
| 5730 * @returns Boolean |
| 5731 * @short Returns true if <a> and <b> are equal. |
| 5732 * @extra %equal% in Sugar is "egal", meaning the values are equal if they a
re "not observably distinguishable". Note that on extended objects the name is %
equals% for readability. |
| 5733 * @example |
| 5734 * |
| 5735 * Object.equal({a:2}, {a:2}) -> true |
| 5736 * Object.equal({a:2}, {a:3}) -> false |
| 5737 * Object.extended({a:2}).equals({a:3}) -> false |
| 5738 * |
| 5739 ***/ |
| 5740 'equal': function(a, b) { |
| 5741 return isEqual(a, b); |
| 5742 }, |
| 5743 |
| 5744 /*** |
| 5745 * @method Object.extended(<obj> = {}) |
| 5746 * @returns Extended object |
| 5747 * @short Creates a new object, equivalent to %new Object()% or %{}%, but wi
th extended methods. |
| 5748 * @extra See extended objects for more. |
| 5749 * @example |
| 5750 * |
| 5751 * Object.extended() |
| 5752 * Object.extended({ happy:true, pappy:false }).keys() -> ['happy','pappy'
] |
| 5753 * Object.extended({ happy:true, pappy:false }).values() -> [true, false] |
| 5754 * |
| 5755 ***/ |
| 5756 'extended': function(obj) { |
| 5757 return new Hash(obj); |
| 5758 }, |
| 5759 |
| 5760 /*** |
| 5761 * @method merge(<target>, <source>, [deep] = false, [resolve] = true) |
| 5762 * @returns Merged object |
| 5763 * @short Merges all the properties of <source> into <target>. |
| 5764 * @extra Merges are shallow unless [deep] is %true%. Properties of <source>
will win in the case of conflicts, unless [resolve] is %false%. [resolve] can a
lso be a function that resolves the conflict. In this case it will be passed 3 a
rguments, %key%, %targetVal%, and %sourceVal%, with the context set to <source>.
This will allow you to solve conflict any way you want, ie. adding two numbers
together, etc. %merge% is available as an instance method on extended objects. |
| 5765 * @example |
| 5766 * |
| 5767 * Object.merge({a:1},{b:2}) -> { a:1, b:2 } |
| 5768 * Object.merge({a:1},{a:2}, false, false) -> { a:1 } |
| 5769 + Object.merge({a:1},{a:2}, false, function(key, a, b) { |
| 5770 * return a + b; |
| 5771 * }); -> { a:3 } |
| 5772 * Object.extended({a:1}).merge({b:2}) -> { a:1, b:2 } |
| 5773 * |
| 5774 ***/ |
| 5775 'merge': function(target, source, deep, resolve) { |
| 5776 var key, sourceIsObject, targetIsObject, sourceVal, targetVal, conflict, r
esult; |
| 5777 // Strings cannot be reliably merged thanks to |
| 5778 // their properties not being enumerable in < IE8. |
| 5779 if(target && typeof source !== 'string') { |
| 5780 for(key in source) { |
| 5781 if(!hasOwnProperty(source, key) || !target) continue; |
| 5782 sourceVal = source[key]; |
| 5783 targetVal = target[key]; |
| 5784 conflict = isDefined(targetVal); |
| 5785 sourceIsObject = isObjectType(sourceVal); |
| 5786 targetIsObject = isObjectType(targetVal); |
| 5787 result = conflict && resolve === false ? targetVal : sourceVal
; |
| 5788 |
| 5789 if(conflict) { |
| 5790 if(isFunction(resolve)) { |
| 5791 // Use the result of the callback as the result. |
| 5792 result = resolve.call(source, key, targetVal, sourceVal) |
| 5793 } |
| 5794 } |
| 5795 |
| 5796 // Going deep |
| 5797 if(deep && (sourceIsObject || targetIsObject)) { |
| 5798 if(isDate(sourceVal)) { |
| 5799 result = new date(sourceVal.getTime()); |
| 5800 } else if(isRegExp(sourceVal)) { |
| 5801 result = new regexp(sourceVal.source, getRegExpFlags(sourceVal)); |
| 5802 } else { |
| 5803 if(!targetIsObject) target[key] = array.isArray(sourceVal) ? [] :
{}; |
| 5804 object.merge(target[key], sourceVal, deep, resolve); |
| 5805 continue; |
| 5806 } |
| 5807 } |
| 5808 target[key] = result; |
| 5809 } |
| 5810 } |
| 5811 return target; |
| 5812 }, |
| 5813 |
| 5814 /*** |
| 5815 * @method values(<obj>, [fn]) |
| 5816 * @returns Array |
| 5817 * @short Returns an array containing the values in <obj>. Optionally calls
[fn] for each value. |
| 5818 * @extra Returned values are in no particular order. %values% is available
as an instance method on extended objects. |
| 5819 * @example |
| 5820 * |
| 5821 * Object.values({ broken: 'wear' }) -> ['wear'] |
| 5822 * Object.values({ broken: 'wear' }, function(value) { |
| 5823 * // Called once for each value. |
| 5824 * }); |
| 5825 * Object.extended({ broken: 'wear' }).values() -> ['wear'] |
| 5826 * |
| 5827 ***/ |
| 5828 'values': function(obj, fn) { |
| 5829 var values = []; |
| 5830 iterateOverObject(obj, function(k,v) { |
| 5831 values.push(v); |
| 5832 if(fn) fn.call(obj,v); |
| 5833 }); |
| 5834 return values; |
| 5835 }, |
| 5836 |
| 5837 /*** |
| 5838 * @method clone(<obj> = {}, [deep] = false) |
| 5839 * @returns Cloned object |
| 5840 * @short Creates a clone (copy) of <obj>. |
| 5841 * @extra Default is a shallow clone, unless [deep] is true. %clone% is avai
lable as an instance method on extended objects. |
| 5842 * @example |
| 5843 * |
| 5844 * Object.clone({foo:'bar'}) -> { foo: 'bar' } |
| 5845 * Object.clone() -> {} |
| 5846 * Object.extended({foo:'bar'}).clone() -> { foo: 'bar' } |
| 5847 * |
| 5848 ***/ |
| 5849 'clone': function(obj, deep) { |
| 5850 var target, klass; |
| 5851 if(!isObjectType(obj)) { |
| 5852 return obj; |
| 5853 } |
| 5854 klass = className(obj); |
| 5855 if(isDate(obj, klass) && obj.clone) { |
| 5856 // Preserve internal UTC flag when applicable. |
| 5857 return obj.clone(); |
| 5858 } else if(isDate(obj, klass) || isRegExp(obj, klass)) { |
| 5859 return new obj.constructor(obj); |
| 5860 } else if(obj instanceof Hash) { |
| 5861 target = new Hash; |
| 5862 } else if(isArray(obj, klass)) { |
| 5863 target = []; |
| 5864 } else if(isPlainObject(obj, klass)) { |
| 5865 target = {}; |
| 5866 } else { |
| 5867 throw new TypeError('Clone must be a basic data type.'); |
| 5868 } |
| 5869 return object.merge(target, obj, deep); |
| 5870 }, |
| 5871 |
| 5872 /*** |
| 5873 * @method Object.fromQueryString(<str>, [booleans] = false) |
| 5874 * @returns Object |
| 5875 * @short Converts the query string of a URL into an object. |
| 5876 * @extra If [booleans] is true, then %"true"% and %"false"% will be cast in
to booleans. All other values, including numbers will remain their string values
. |
| 5877 * @example |
| 5878 * |
| 5879 * Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken:
'wear' } |
| 5880 * Object.fromQueryString('foo[]=1&foo[]=2') -> { foo: ['1','2'] } |
| 5881 * Object.fromQueryString('foo=true', true) -> { foo: true } |
| 5882 * |
| 5883 ***/ |
| 5884 'fromQueryString': function(str, castBoolean) { |
| 5885 var result = object.extended(), split; |
| 5886 str = str && str.toString ? str.toString() : ''; |
| 5887 str.replace(/^.*?\?/, '').split('&').forEach(function(p) { |
| 5888 var split = p.split('='); |
| 5889 if(split.length !== 2) return; |
| 5890 setParamsObject(result, split[0], decodeURIComponent(split[1]), castBool
ean); |
| 5891 }); |
| 5892 return result; |
| 5893 }, |
| 5894 |
| 5895 /*** |
| 5896 * @method Object.toQueryString(<obj>, [namespace] = null) |
| 5897 * @returns Object |
| 5898 * @short Converts the object into a query string. |
| 5899 * @extra Accepts deep nested objects and arrays. If [namespace] is passed,
it will be prefixed to all param names. |
| 5900 * @example |
| 5901 * |
| 5902 * Object.toQueryString({foo:'bar'}) -> 'foo=bar' |
| 5903 * Object.toQueryString({foo:['a','b','c']}) -> 'foo[0]=a&foo[1]=b&foo[2]
=c' |
| 5904 * Object.toQueryString({name:'Bob'}, 'user') -> 'user[name]=Bob' |
| 5905 * |
| 5906 ***/ |
| 5907 'toQueryString': function(obj, namespace) { |
| 5908 return objectToQueryString(namespace, obj); |
| 5909 }, |
| 5910 |
| 5911 /*** |
| 5912 * @method tap(<obj>, <fn>) |
| 5913 * @returns Object |
| 5914 * @short Runs <fn> and returns <obj>. |
| 5915 * @extra A string can also be used as a shortcut to a method. This method
is used to run an intermediary function in the middle of method chaining. As a s
tandalone method on the Object class it doesn't have too much use. The power of
%tap% comes when using extended objects or modifying the Object prototype with O
bject.extend(). |
| 5916 * @example |
| 5917 * |
| 5918 * Object.extend(); |
| 5919 * [2,4,6].map(Math.exp).tap(function(arr) { |
| 5920 * arr.pop() |
| 5921 * }); |
| 5922 * [2,4,6].map(Math.exp).tap('pop').map(Math.round); -> [7,55] |
| 5923 * |
| 5924 ***/ |
| 5925 'tap': function(obj, arg) { |
| 5926 var fn = arg; |
| 5927 if(!isFunction(arg)) { |
| 5928 fn = function() { |
| 5929 if(arg) obj[arg](); |
| 5930 } |
| 5931 } |
| 5932 fn.call(obj, obj); |
| 5933 return obj; |
| 5934 }, |
| 5935 |
| 5936 /*** |
| 5937 * @method has(<obj>, <key>) |
| 5938 * @returns Boolean |
| 5939 * @short Checks if <obj> has <key> using hasOwnProperty from Object.prototy
pe. |
| 5940 * @extra This method is considered safer than %Object#hasOwnProperty% when
using objects as hashes. See http://www.devthought.com/2012/01/18/an-object-is-n
ot-a-hash/ for more. |
| 5941 * @example |
| 5942 * |
| 5943 * Object.has({ foo: 'bar' }, 'foo') -> true |
| 5944 * Object.has({ foo: 'bar' }, 'baz') -> false |
| 5945 * Object.has({ hasOwnProperty: true }, 'foo') -> false |
| 5946 * |
| 5947 ***/ |
| 5948 'has': function (obj, key) { |
| 5949 return hasOwnProperty(obj, key); |
| 5950 }, |
| 5951 |
| 5952 /*** |
| 5953 * @method select(<obj>, <find>, ...) |
| 5954 * @returns Object |
| 5955 * @short Builds a new object containing the values specified in <find>. |
| 5956 * @extra When <find> is a string, that single key will be selected. It can
also be a regex, selecting any key that matches, or an object which will match i
f the key also exists in that object, effectively doing an "intersect" operation
on that object. Multiple selections may also be passed as an array or directly
as enumerated arguments. %select% is available as an instance method on extended
objects. |
| 5957 * @example |
| 5958 * |
| 5959 * Object.select({a:1,b:2}, 'a') -> {a:1} |
| 5960 * Object.select({a:1,b:2}, /[a-z]/) -> {a:1,ba:2} |
| 5961 * Object.select({a:1,b:2}, {a:1}) -> {a:1} |
| 5962 * Object.select({a:1,b:2}, 'a', 'b') -> {a:1,b:2} |
| 5963 * Object.select({a:1,b:2}, ['a', 'b']) -> {a:1,b:2} |
| 5964 * |
| 5965 ***/ |
| 5966 'select': function (obj) { |
| 5967 return selectFromObject(obj, arguments, true); |
| 5968 }, |
| 5969 |
| 5970 /*** |
| 5971 * @method reject(<obj>, <find>, ...) |
| 5972 * @returns Object |
| 5973 * @short Builds a new object containing all values except those specified i
n <find>. |
| 5974 * @extra When <find> is a string, that single key will be rejected. It can
also be a regex, rejecting any key that matches, or an object which will match i
f the key also exists in that object, effectively "subtracting" that object. Mul
tiple selections may also be passed as an array or directly as enumerated argume
nts. %reject% is available as an instance method on extended objects. |
| 5975 * @example |
| 5976 * |
| 5977 * Object.reject({a:1,b:2}, 'a') -> {b:2} |
| 5978 * Object.reject({a:1,b:2}, /[a-z]/) -> {} |
| 5979 * Object.reject({a:1,b:2}, {a:1}) -> {b:2} |
| 5980 * Object.reject({a:1,b:2}, 'a', 'b') -> {} |
| 5981 * Object.reject({a:1,b:2}, ['a', 'b']) -> {} |
| 5982 * |
| 5983 ***/ |
| 5984 'reject': function (obj) { |
| 5985 return selectFromObject(obj, arguments, false); |
| 5986 } |
| 5987 |
| 5988 }); |
| 5989 |
| 5990 |
| 5991 buildTypeMethods(); |
| 5992 buildObjectExtend(); |
| 5993 buildObjectInstanceMethods(ObjectHashMethods, Hash); |
| 5994 |
| 5995 /*** |
| 5996 * @package Range |
| 5997 * @dependency core |
| 5998 * @description Ranges allow creating spans of numbers, strings, or dates. The
y can enumerate over specific points within that range, and be manipulated and c
ompared. |
| 5999 * |
| 6000 ***/ |
| 6001 |
| 6002 function Range(start, end) { |
| 6003 this.start = cloneRangeMember(start); |
| 6004 this.end = cloneRangeMember(end); |
| 6005 }; |
| 6006 |
| 6007 function getRangeMemberNumericValue(m) { |
| 6008 return isString(m) ? m.charCodeAt(0) : m; |
| 6009 } |
| 6010 |
| 6011 function getRangeMemberPrimitiveValue(m) { |
| 6012 if(m == null) return m; |
| 6013 return isDate(m) ? m.getTime() : m.valueOf(); |
| 6014 } |
| 6015 |
| 6016 function cloneRangeMember(m) { |
| 6017 if(isDate(m)) { |
| 6018 return new date(m.getTime()); |
| 6019 } else { |
| 6020 return getRangeMemberPrimitiveValue(m); |
| 6021 } |
| 6022 } |
| 6023 |
| 6024 function isValidRangeMember(m) { |
| 6025 var val = getRangeMemberPrimitiveValue(m); |
| 6026 return !!val || val === 0; |
| 6027 } |
| 6028 |
| 6029 function getDuration(amt) { |
| 6030 var match, val, unit; |
| 6031 if(isNumber(amt)) { |
| 6032 return amt; |
| 6033 } |
| 6034 match = amt.toLowerCase().match(/^(\d+)?\s?(\w+?)s?$/i); |
| 6035 val = parseInt(match[1]) || 1; |
| 6036 unit = match[2].slice(0,1).toUpperCase() + match[2].slice(1); |
| 6037 if(unit.match(/hour|minute|second/i)) { |
| 6038 unit += 's'; |
| 6039 } else if(unit === 'Year') { |
| 6040 unit = 'FullYear'; |
| 6041 } else if(unit === 'Day') { |
| 6042 unit = 'Date'; |
| 6043 } |
| 6044 return [val, unit]; |
| 6045 } |
| 6046 |
| 6047 function incrementDate(current, amount) { |
| 6048 var num, unit, val, d; |
| 6049 if(isNumber(amount)) { |
| 6050 return new date(current.getTime() + amount); |
| 6051 } |
| 6052 num = amount[0]; |
| 6053 unit = amount[1]; |
| 6054 val = callDateGet(current, unit); |
| 6055 d = new date(current.getTime()); |
| 6056 callDateSet(d, unit, val + num); |
| 6057 return d; |
| 6058 } |
| 6059 |
| 6060 function incrementString(current, amount) { |
| 6061 return string.fromCharCode(current.charCodeAt(0) + amount); |
| 6062 } |
| 6063 |
| 6064 function incrementNumber(current, amount) { |
| 6065 return current + amount; |
| 6066 } |
| 6067 |
| 6068 /*** |
| 6069 * @method toString() |
| 6070 * @returns String |
| 6071 * @short Returns a string representation of the range. |
| 6072 * @example |
| 6073 * |
| 6074 * Number.range(1, 5).toString() -> 1..5 |
| 6075 * Date.range(new Date(2003, 0), new Date(2005, 0)).toString() -> January 1,
2003..January 1, 2005 |
| 6076 * |
| 6077 ***/ |
| 6078 |
| 6079 // Note: 'toString' doesn't appear in a for..in loop in IE even though |
| 6080 // hasOwnProperty reports true, so extend() can't be used here. |
| 6081 // Also tried simply setting the prototype = {} up front for all |
| 6082 // methods but GCC very oddly started dropping properties in the |
| 6083 // object randomly (maybe because of the global scope?) hence |
| 6084 // the need for the split logic here. |
| 6085 Range.prototype.toString = function() { |
| 6086 return this.isValid() ? this.start + ".." + this.end : 'Invalid Range'; |
| 6087 }; |
| 6088 |
| 6089 extend(Range, true, true, { |
| 6090 |
| 6091 /*** |
| 6092 * @method isValid() |
| 6093 * @returns Boolean |
| 6094 * @short Returns true if the range is valid, false otherwise. |
| 6095 * @example |
| 6096 * |
| 6097 * Date.range(new Date(2003, 0), new Date(2005, 0)).isValid() -> true |
| 6098 * Number.range(NaN, NaN).isValid() -> false |
| 6099 * |
| 6100 ***/ |
| 6101 'isValid': function() { |
| 6102 return isValidRangeMember(this.start) && isValidRangeMember(this.end) && t
ypeof this.start === typeof this.end; |
| 6103 }, |
| 6104 |
| 6105 /*** |
| 6106 * @method span() |
| 6107 * @returns Number |
| 6108 * @short Returns the span of the range. If the range is a date range, the v
alue is in milliseconds. |
| 6109 * @extra The span includes both the start and the end. |
| 6110 * @example |
| 6111 * |
| 6112 * Number.range(5, 10).span() -> 6 |
| 6113 * Date.range(new Date(2003, 0), new Date(2005, 0)).span() -> 94694400000 |
| 6114 * |
| 6115 ***/ |
| 6116 'span': function() { |
| 6117 return this.isValid() ? abs( |
| 6118 getRangeMemberNumericValue(this.end) - getRangeMemberNumericValue(this.s
tart) |
| 6119 ) + 1 : NaN; |
| 6120 }, |
| 6121 |
| 6122 /*** |
| 6123 * @method contains(<obj>) |
| 6124 * @returns Boolean |
| 6125 * @short Returns true if <obj> is contained inside the range. <obj> may be
a value or another range. |
| 6126 * @example |
| 6127 * |
| 6128 * Number.range(5, 10).contains(7)
-> true |
| 6129 * Date.range(new Date(2003, 0), new Date(2005, 0)).contains(new Date(2004
, 0)) -> true |
| 6130 * |
| 6131 ***/ |
| 6132 'contains': function(obj) { |
| 6133 var self = this, arr; |
| 6134 if(obj == null) return false; |
| 6135 if(obj.start && obj.end) { |
| 6136 return obj.start >= this.start && obj.start <= this.end && |
| 6137 obj.end >= this.start && obj.end <= this.end; |
| 6138 } else { |
| 6139 return obj >= this.start && obj <= this.end; |
| 6140 } |
| 6141 }, |
| 6142 |
| 6143 /*** |
| 6144 * @method every(<amount>, [fn]) |
| 6145 * @returns Array |
| 6146 * @short Iterates through the range for every <amount>, calling [fn] if it
is passed. Returns an array of each increment visited. |
| 6147 * @extra In the case of date ranges, <amount> can also be a string, in whic
h case it will increment a number of units. Note that %(2).months()% first reso
lves to a number, which will be interpreted as milliseconds and is an approximat
ion, so stepping through the actual months by passing %"2 months"% is usually pr
eferable. |
| 6148 * @example |
| 6149 * |
| 6150 * Number.range(2, 8).every(2) -> [2
,4,6,8] |
| 6151 * Date.range(new Date(2003, 1), new Date(2003,3)).every("2 months") -> [.
..] |
| 6152 * |
| 6153 ***/ |
| 6154 'every': function(amount, fn) { |
| 6155 var increment, |
| 6156 start = this.start, |
| 6157 end = this.end, |
| 6158 inverse = end < start, |
| 6159 current = start, |
| 6160 index = 0, |
| 6161 result = []; |
| 6162 |
| 6163 if(isFunction(amount)) { |
| 6164 fn = amount; |
| 6165 amount = null; |
| 6166 } |
| 6167 amount = amount || 1; |
| 6168 if(isNumber(start)) { |
| 6169 increment = incrementNumber; |
| 6170 } else if(isString(start)) { |
| 6171 increment = incrementString; |
| 6172 } else if(isDate(start)) { |
| 6173 amount = getDuration(amount); |
| 6174 increment = incrementDate; |
| 6175 } |
| 6176 // Avoiding infinite loops |
| 6177 if(inverse && amount > 0) { |
| 6178 amount *= -1; |
| 6179 } |
| 6180 while(inverse ? current >= end : current <= end) { |
| 6181 result.push(current); |
| 6182 if(fn) { |
| 6183 fn(current, index); |
| 6184 } |
| 6185 current = increment(current, amount); |
| 6186 index++; |
| 6187 } |
| 6188 return result; |
| 6189 }, |
| 6190 |
| 6191 /*** |
| 6192 * @method union(<range>) |
| 6193 * @returns Range |
| 6194 * @short Returns a new range with the earliest starting point as its start,
and the latest ending point as its end. If the two ranges do not intersect this
will effectively remove the "gap" between them. |
| 6195 * @example |
| 6196 * |
| 6197 * Number.range(1, 3).union(Number.range(2, 5)) -> 1..5 |
| 6198 * Date.range(new Date(2003, 1), new Date(2005, 1)).union(Date.range(new D
ate(2004, 1), new Date(2006, 1))) -> Jan 1, 2003..Jan 1, 2006 |
| 6199 * |
| 6200 ***/ |
| 6201 'union': function(range) { |
| 6202 return new Range( |
| 6203 this.start < range.start ? this.start : range.start, |
| 6204 this.end > range.end ? this.end : range.end |
| 6205 ); |
| 6206 }, |
| 6207 |
| 6208 /*** |
| 6209 * @method intersect(<range>) |
| 6210 * @returns Range |
| 6211 * @short Returns a new range with the latest starting point as its start, a
nd the earliest ending point as its end. If the two ranges do not intersect this
will effectively produce an invalid range. |
| 6212 * @example |
| 6213 * |
| 6214 * Number.range(1, 5).intersect(Number.range(4, 8)) -> 4..5 |
| 6215 * Date.range(new Date(2003, 1), new Date(2005, 1)).intersect(Date.range(n
ew Date(2004, 1), new Date(2006, 1))) -> Jan 1, 2004..Jan 1, 2005 |
| 6216 * |
| 6217 ***/ |
| 6218 'intersect': function(range) { |
| 6219 if(range.start > this.end || range.end < this.start) { |
| 6220 return new Range(NaN, NaN); |
| 6221 } |
| 6222 return new Range( |
| 6223 this.start > range.start ? this.start : range.start, |
| 6224 this.end < range.end ? this.end : range.end |
| 6225 ); |
| 6226 }, |
| 6227 |
| 6228 /*** |
| 6229 * @method clone() |
| 6230 * @returns Range |
| 6231 * @short Clones the range. |
| 6232 * @extra Members of the range will also be cloned. |
| 6233 * @example |
| 6234 * |
| 6235 * Number.range(1, 5).clone() -> Returns a copy of the range. |
| 6236 * |
| 6237 ***/ |
| 6238 'clone': function(range) { |
| 6239 return new Range(this.start, this.end); |
| 6240 }, |
| 6241 |
| 6242 /*** |
| 6243 * @method clamp(<obj>) |
| 6244 * @returns Mixed |
| 6245 * @short Clamps <obj> to be within the range if it falls outside. |
| 6246 * @example |
| 6247 * |
| 6248 * Number.range(1, 5).clamp(8) -> 5 |
| 6249 * Date.range(new Date(2010, 0), new Date(2012, 0)).clamp(new Date(2013, 0
)) -> 2012-01 |
| 6250 * |
| 6251 ***/ |
| 6252 'clamp': function(obj) { |
| 6253 var clamped, |
| 6254 start = this.start, |
| 6255 end = this.end, |
| 6256 min = end < start ? end : start, |
| 6257 max = start > end ? start : end; |
| 6258 if(obj < min) { |
| 6259 clamped = min; |
| 6260 } else if(obj > max) { |
| 6261 clamped = max; |
| 6262 } else { |
| 6263 clamped = obj; |
| 6264 } |
| 6265 return cloneRangeMember(clamped); |
| 6266 } |
| 6267 |
| 6268 }); |
| 6269 |
| 6270 |
| 6271 /*** |
| 6272 * Number module |
| 6273 *** |
| 6274 * @method Number.range([start], [end]) |
| 6275 * @returns Range |
| 6276 * @short Creates a new range between [start] and [end]. See @ranges for more. |
| 6277 * @example |
| 6278 * |
| 6279 * Number.range(5, 10) |
| 6280 * |
| 6281 *** |
| 6282 * String module |
| 6283 *** |
| 6284 * @method String.range([start], [end]) |
| 6285 * @returns Range |
| 6286 * @short Creates a new range between [start] and [end]. See @ranges for more. |
| 6287 * @example |
| 6288 * |
| 6289 * String.range('a', 'z') |
| 6290 * |
| 6291 *** |
| 6292 * Date module |
| 6293 *** |
| 6294 * @method Date.range([start], [end]) |
| 6295 * @returns Range |
| 6296 * @short Creates a new range between [start] and [end]. |
| 6297 * @extra If either [start] or [end] are null, they will default to the curren
t date. See @ranges for more. |
| 6298 * @example |
| 6299 * |
| 6300 * Date.range('today', 'tomorrow') |
| 6301 * |
| 6302 ***/ |
| 6303 [number, string, date].forEach(function(klass) { |
| 6304 extend(klass, false, true, { |
| 6305 |
| 6306 'range': function(start, end) { |
| 6307 if(klass.create) { |
| 6308 start = klass.create(start); |
| 6309 end = klass.create(end); |
| 6310 } |
| 6311 return new Range(start, end); |
| 6312 } |
| 6313 |
| 6314 }); |
| 6315 |
| 6316 }); |
| 6317 |
| 6318 /*** |
| 6319 * Number module |
| 6320 * |
| 6321 ***/ |
| 6322 |
| 6323 extend(number, true, true, { |
| 6324 |
| 6325 /*** |
| 6326 * @method upto(<num>, [fn], [step] = 1) |
| 6327 * @returns Array |
| 6328 * @short Returns an array containing numbers from the number up to <num>. |
| 6329 * @extra Optionally calls [fn] callback for each number in that array. [ste
p] allows multiples greater than 1. |
| 6330 * @example |
| 6331 * |
| 6332 * (2).upto(6) -> [2, 3, 4, 5, 6] |
| 6333 * (2).upto(6, function(n) { |
| 6334 * // This function is called 5 times receiving n as the value. |
| 6335 * }); |
| 6336 * (2).upto(8, null, 2) -> [2, 4, 6, 8] |
| 6337 * |
| 6338 ***/ |
| 6339 'upto': function(num, fn, step) { |
| 6340 return number.range(this, num).every(step, fn); |
| 6341 }, |
| 6342 |
| 6343 /*** |
| 6344 * @method clamp([start] = Infinity, [end] = Infinity) |
| 6345 * @returns Number |
| 6346 * @short Constrains the number so that it is between [start] and [end]. |
| 6347 * @extra This will build a range object that has an equivalent %clamp% meth
od. |
| 6348 * @example |
| 6349 * |
| 6350 * (3).clamp(50, 100) -> 50 |
| 6351 * (85).clamp(50, 100) -> 85 |
| 6352 * |
| 6353 ***/ |
| 6354 'clamp': function(start, end) { |
| 6355 return new Range(start, end).clamp(this); |
| 6356 }, |
| 6357 |
| 6358 /*** |
| 6359 * @method cap([max] = Infinity) |
| 6360 * @returns Number |
| 6361 * @short Constrains the number so that it is no greater than [max]. |
| 6362 * @extra This will build a range object that has an equivalent %cap% method
. |
| 6363 * @example |
| 6364 * |
| 6365 * (100).cap(80) -> 80 |
| 6366 * |
| 6367 ***/ |
| 6368 'cap': function(max) { |
| 6369 return this.clamp(Undefined, max); |
| 6370 } |
| 6371 |
| 6372 }); |
| 6373 |
| 6374 extend(number, true, true, { |
| 6375 |
| 6376 /*** |
| 6377 * @method downto(<num>, [fn], [step] = 1) |
| 6378 * @returns Array |
| 6379 * @short Returns an array containing numbers from the number down to <num>. |
| 6380 * @extra Optionally calls [fn] callback for each number in that array. [ste
p] allows multiples greater than 1. |
| 6381 * @example |
| 6382 * |
| 6383 * (8).downto(3) -> [8, 7, 6, 5, 4, 3] |
| 6384 * (8).downto(3, function(n) { |
| 6385 * // This function is called 6 times receiving n as the value. |
| 6386 * }); |
| 6387 * (8).downto(2, null, 2) -> [8, 6, 4, 2] |
| 6388 * |
| 6389 ***/ |
| 6390 'downto': number.prototype.upto |
| 6391 |
| 6392 }); |
| 6393 |
| 6394 |
| 6395 /*** |
| 6396 * Array module |
| 6397 * |
| 6398 ***/ |
| 6399 |
| 6400 extend(array, false, function(a) { return a instanceof Range; }, { |
| 6401 |
| 6402 'create': function(range) { |
| 6403 return range.every(); |
| 6404 } |
| 6405 |
| 6406 }); |
| 6407 |
| 6408 |
| 6409 /*** |
| 6410 * @package RegExp |
| 6411 * @dependency core |
| 6412 * @description Escaping regexes and manipulating their flags. |
| 6413 * |
| 6414 * Note here that methods on the RegExp class like .exec and .test will fail i
n the current version of SpiderMonkey being |
| 6415 * used by CouchDB when using shorthand regex notation like /foo/. This is the
reason for the intermixed use of shorthand |
| 6416 * and compiled regexes here. If you're using JS in CouchDB, it is safer to AL
WAYS compile your regexes from a string. |
| 6417 * |
| 6418 ***/ |
| 6419 |
| 6420 extend(regexp, false, true, { |
| 6421 |
| 6422 /*** |
| 6423 * @method RegExp.escape(<str> = '') |
| 6424 * @returns String |
| 6425 * @short Escapes all RegExp tokens in a string. |
| 6426 * @example |
| 6427 * |
| 6428 * RegExp.escape('really?') -> 'really\?' |
| 6429 * RegExp.escape('yes.') -> 'yes\.' |
| 6430 * RegExp.escape('(not really)') -> '\(not really\)' |
| 6431 * |
| 6432 ***/ |
| 6433 'escape': function(str) { |
| 6434 return escapeRegExp(str); |
| 6435 } |
| 6436 |
| 6437 }); |
| 6438 |
| 6439 extend(regexp, true, true, { |
| 6440 |
| 6441 /*** |
| 6442 * @method getFlags() |
| 6443 * @returns String |
| 6444 * @short Returns the flags of the regex as a string. |
| 6445 * @example |
| 6446 * |
| 6447 * /texty/gim.getFlags('testy') -> 'gim' |
| 6448 * |
| 6449 ***/ |
| 6450 'getFlags': function() { |
| 6451 return getRegExpFlags(this); |
| 6452 }, |
| 6453 |
| 6454 /*** |
| 6455 * @method setFlags(<flags>) |
| 6456 * @returns RegExp |
| 6457 * @short Sets the flags on a regex and retuns a copy. |
| 6458 * @example |
| 6459 * |
| 6460 * /texty/.setFlags('gim') -> now has global, ignoreCase, and multiline set |
| 6461 * |
| 6462 ***/ |
| 6463 'setFlags': function(flags) { |
| 6464 return regexp(this.source, flags); |
| 6465 }, |
| 6466 |
| 6467 /*** |
| 6468 * @method addFlag(<flag>) |
| 6469 * @returns RegExp |
| 6470 * @short Adds <flag> to the regex. |
| 6471 * @example |
| 6472 * |
| 6473 * /texty/.addFlag('g') -> now has global flag set |
| 6474 * |
| 6475 ***/ |
| 6476 'addFlag': function(flag) { |
| 6477 return this.setFlags(getRegExpFlags(this, flag)); |
| 6478 }, |
| 6479 |
| 6480 /*** |
| 6481 * @method removeFlag(<flag>) |
| 6482 * @returns RegExp |
| 6483 * @short Removes <flag> from the regex. |
| 6484 * @example |
| 6485 * |
| 6486 * /texty/g.removeFlag('g') -> now has global flag removed |
| 6487 * |
| 6488 ***/ |
| 6489 'removeFlag': function(flag) { |
| 6490 return this.setFlags(getRegExpFlags(this).replace(flag, '')); |
| 6491 } |
| 6492 |
| 6493 }); |
| 6494 |
| 6495 |
| 6496 |
| 6497 /*** |
| 6498 * @package String |
| 6499 * @dependency core |
| 6500 * @description String manupulation, escaping, encoding, truncation, and:conve
rsion. |
| 6501 * |
| 6502 ***/ |
| 6503 |
| 6504 function getAcronym(word) { |
| 6505 var inflector = string.Inflector; |
| 6506 var word = inflector && inflector.acronyms[word]; |
| 6507 if(isString(word)) { |
| 6508 return word; |
| 6509 } |
| 6510 } |
| 6511 |
| 6512 function checkRepeatRange(num) { |
| 6513 num = +num; |
| 6514 if(num < 0 || num === Infinity) { |
| 6515 throw new RangeError('Invalid number'); |
| 6516 } |
| 6517 return num; |
| 6518 } |
| 6519 |
| 6520 function padString(num, padding) { |
| 6521 return repeatString(isDefined(padding) ? padding : ' ', num); |
| 6522 } |
| 6523 |
| 6524 function truncateString(str, length, from, ellipsis, split) { |
| 6525 var str1, str2, len1, len2; |
| 6526 if(str.length <= length) { |
| 6527 return str.toString(); |
| 6528 } |
| 6529 ellipsis = isUndefined(ellipsis) ? '...' : ellipsis; |
| 6530 switch(from) { |
| 6531 case 'left': |
| 6532 str2 = split ? truncateOnWord(str, length, true) : str.slice(str.length
- length); |
| 6533 return ellipsis + str2; |
| 6534 case 'middle': |
| 6535 len1 = ceil(length / 2); |
| 6536 len2 = floor(length / 2); |
| 6537 str1 = split ? truncateOnWord(str, len1) : str.slice(0, len1); |
| 6538 str2 = split ? truncateOnWord(str, len2, true) : str.slice(str.length -
len2); |
| 6539 return str1 + ellipsis + str2; |
| 6540 default: |
| 6541 str1 = split ? truncateOnWord(str, length) : str.slice(0, length); |
| 6542 return str1 + ellipsis; |
| 6543 } |
| 6544 } |
| 6545 |
| 6546 function truncateOnWord(str, limit, fromLeft) { |
| 6547 if(fromLeft) { |
| 6548 return truncateOnWord(str.reverse(), limit).reverse(); |
| 6549 } |
| 6550 var reg = regexp('(?=[' + getTrimmableCharacters() + '])'); |
| 6551 var words = str.split(reg); |
| 6552 var count = 0; |
| 6553 return words.filter(function(word) { |
| 6554 count += word.length; |
| 6555 return count <= limit; |
| 6556 }).join(''); |
| 6557 } |
| 6558 |
| 6559 function numberOrIndex(str, n, from) { |
| 6560 if(isString(n)) { |
| 6561 n = str.indexOf(n); |
| 6562 if(n === -1) { |
| 6563 n = from ? str.length : 0; |
| 6564 } |
| 6565 } |
| 6566 return n; |
| 6567 } |
| 6568 |
| 6569 var btoa, atob; |
| 6570 |
| 6571 function buildBase64(key) { |
| 6572 if(globalContext.btoa) { |
| 6573 btoa = globalContext.btoa; |
| 6574 atob = globalContext.atob; |
| 6575 return; |
| 6576 } |
| 6577 var base64reg = /[^A-Za-z0-9\+\/\=]/g; |
| 6578 btoa = function(str) { |
| 6579 var output = ''; |
| 6580 var chr1, chr2, chr3; |
| 6581 var enc1, enc2, enc3, enc4; |
| 6582 var i = 0; |
| 6583 do { |
| 6584 chr1 = str.charCodeAt(i++); |
| 6585 chr2 = str.charCodeAt(i++); |
| 6586 chr3 = str.charCodeAt(i++); |
| 6587 enc1 = chr1 >> 2; |
| 6588 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); |
| 6589 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); |
| 6590 enc4 = chr3 & 63; |
| 6591 if (isNaN(chr2)) { |
| 6592 enc3 = enc4 = 64; |
| 6593 } else if (isNaN(chr3)) { |
| 6594 enc4 = 64; |
| 6595 } |
| 6596 output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3)
+ key.charAt(enc4); |
| 6597 chr1 = chr2 = chr3 = ''; |
| 6598 enc1 = enc2 = enc3 = enc4 = ''; |
| 6599 } while (i < str.length); |
| 6600 return output; |
| 6601 } |
| 6602 atob = function(input) { |
| 6603 var output = ''; |
| 6604 var chr1, chr2, chr3; |
| 6605 var enc1, enc2, enc3, enc4; |
| 6606 var i = 0; |
| 6607 if(input.match(base64reg)) { |
| 6608 throw new Error('String contains invalid base64 characters'); |
| 6609 } |
| 6610 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); |
| 6611 do { |
| 6612 enc1 = key.indexOf(input.charAt(i++)); |
| 6613 enc2 = key.indexOf(input.charAt(i++)); |
| 6614 enc3 = key.indexOf(input.charAt(i++)); |
| 6615 enc4 = key.indexOf(input.charAt(i++)); |
| 6616 chr1 = (enc1 << 2) | (enc2 >> 4); |
| 6617 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); |
| 6618 chr3 = ((enc3 & 3) << 6) | enc4; |
| 6619 output = output + chr(chr1); |
| 6620 if (enc3 != 64) { |
| 6621 output = output + chr(chr2); |
| 6622 } |
| 6623 if (enc4 != 64) { |
| 6624 output = output + chr(chr3); |
| 6625 } |
| 6626 chr1 = chr2 = chr3 = ''; |
| 6627 enc1 = enc2 = enc3 = enc4 = ''; |
| 6628 } while (i < input.length); |
| 6629 return output; |
| 6630 } |
| 6631 } |
| 6632 |
| 6633 extend(string, true, false, { |
| 6634 /*** |
| 6635 * @method repeat([num] = 0) |
| 6636 * @returns String |
| 6637 * @short Returns the string repeated [num] times. |
| 6638 * @example |
| 6639 * |
| 6640 * 'jumpy'.repeat(2) -> 'jumpyjumpy' |
| 6641 * 'a'.repeat(5) -> 'aaaaa' |
| 6642 * 'a'.repeat(0) -> '' |
| 6643 * |
| 6644 ***/ |
| 6645 'repeat': function(num) { |
| 6646 num = checkRepeatRange(num); |
| 6647 return repeatString(this, num); |
| 6648 } |
| 6649 |
| 6650 }); |
| 6651 |
| 6652 extend(string, true, function(reg) { return isRegExp(reg) || arguments.length
> 2; }, { |
| 6653 |
| 6654 /*** |
| 6655 * @method startsWith(<find>, [pos] = 0, [case] = true) |
| 6656 * @returns Boolean |
| 6657 * @short Returns true if the string starts with <find>. |
| 6658 * @extra <find> may be either a string or regex. Search begins at [pos], wh
ich defaults to the entire string. Case sensitive if [case] is true. |
| 6659 * @example |
| 6660 * |
| 6661 * 'hello'.startsWith('hell') -> true |
| 6662 * 'hello'.startsWith(/[a-h]/) -> true |
| 6663 * 'hello'.startsWith('HELL') -> false |
| 6664 * 'hello'.startsWith('ell', 1) -> true |
| 6665 * 'hello'.startsWith('HELL', 0, false) -> true |
| 6666 * |
| 6667 ***/ |
| 6668 'startsWith': function(reg) { |
| 6669 var args = arguments, pos = args[1], c = args[2], str = this, source; |
| 6670 if(pos) str = str.slice(pos); |
| 6671 if(isUndefined(c)) c = true; |
| 6672 source = isRegExp(reg) ? reg.source.replace('^', '') : escapeRegExp(reg); |
| 6673 return regexp('^' + source, c ? '' : 'i').test(str); |
| 6674 }, |
| 6675 |
| 6676 /*** |
| 6677 * @method endsWith(<find>, [pos] = length, [case] = true) |
| 6678 * @returns Boolean |
| 6679 * @short Returns true if the string ends with <find>. |
| 6680 * @extra <find> may be either a string or regex. Search ends at [pos], whic
h defaults to the entire string. Case sensitive if [case] is true. |
| 6681 * @example |
| 6682 * |
| 6683 * 'jumpy'.endsWith('py') -> true |
| 6684 * 'jumpy'.endsWith(/[q-z]/) -> true |
| 6685 * 'jumpy'.endsWith('MPY') -> false |
| 6686 * 'jumpy'.endsWith('mp', 4) -> false |
| 6687 * 'jumpy'.endsWith('MPY', 5, false) -> true |
| 6688 * |
| 6689 ***/ |
| 6690 'endsWith': function(reg) { |
| 6691 var args = arguments, pos = args[1], c = args[2], str = this, source; |
| 6692 if(isDefined(pos)) str = str.slice(0, pos); |
| 6693 if(isUndefined(c)) c = true; |
| 6694 source = isRegExp(reg) ? reg.source.replace('$', '') : escapeRegExp(reg); |
| 6695 return regexp(source + '$', c ? '' : 'i').test(str); |
| 6696 } |
| 6697 |
| 6698 }); |
| 6699 |
| 6700 extend(string, true, true, { |
| 6701 |
| 6702 /*** |
| 6703 * @method escapeRegExp() |
| 6704 * @returns String |
| 6705 * @short Escapes all RegExp tokens in the string. |
| 6706 * @example |
| 6707 * |
| 6708 * 'really?'.escapeRegExp() -> 'really\?' |
| 6709 * 'yes.'.escapeRegExp() -> 'yes\.' |
| 6710 * '(not really)'.escapeRegExp() -> '\(not really\)' |
| 6711 * |
| 6712 ***/ |
| 6713 'escapeRegExp': function() { |
| 6714 return escapeRegExp(this); |
| 6715 }, |
| 6716 |
| 6717 /*** |
| 6718 * @method escapeURL([param] = false) |
| 6719 * @returns String |
| 6720 * @short Escapes characters in a string to make a valid URL. |
| 6721 * @extra If [param] is true, it will also escape valid URL characters for
use as a URL parameter. |
| 6722 * @example |
| 6723 * |
| 6724 * 'http://foo.com/"bar"'.escapeURL() -> 'http://foo.com/%22bar%22' |
| 6725 * 'http://foo.com/"bar"'.escapeURL(true) -> 'http%3A%2F%2Ffoo.com%2F%22b
ar%22' |
| 6726 * |
| 6727 ***/ |
| 6728 'escapeURL': function(param) { |
| 6729 return param ? encodeURIComponent(this) : encodeURI(this); |
| 6730 }, |
| 6731 |
| 6732 /*** |
| 6733 * @method unescapeURL([partial] = false) |
| 6734 * @returns String |
| 6735 * @short Restores escaped characters in a URL escaped string. |
| 6736 * @extra If [partial] is true, it will only unescape non-valid URL charact
ers. [partial] is included here for completeness, but should very rarely be need
ed. |
| 6737 * @example |
| 6738 * |
| 6739 * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL() -> 'http://foo.co
m/the bar' |
| 6740 * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL(true) -> 'http%3A%2F%2F
foo.com%2Fthe bar' |
| 6741 * |
| 6742 ***/ |
| 6743 'unescapeURL': function(param) { |
| 6744 return param ? decodeURI(this) : decodeURIComponent(this); |
| 6745 }, |
| 6746 |
| 6747 /*** |
| 6748 * @method escapeHTML() |
| 6749 * @returns String |
| 6750 * @short Converts HTML characters to their entity equivalents. |
| 6751 * @example |
| 6752 * |
| 6753 * '<p>some text</p>'.escapeHTML() -> '<p>some text</p>' |
| 6754 * 'one & two'.escapeHTML() -> 'one & two' |
| 6755 * |
| 6756 ***/ |
| 6757 'escapeHTML': function() { |
| 6758 return this.replace(/&/g, '&' ) |
| 6759 .replace(/</g, '<' ) |
| 6760 .replace(/>/g, '>' ) |
| 6761 .replace(/"/g, '"') |
| 6762 .replace(/'/g, ''') |
| 6763 .replace(/\//g, '/'); |
| 6764 }, |
| 6765 |
| 6766 /*** |
| 6767 * @method unescapeHTML([partial] = false) |
| 6768 * @returns String |
| 6769 * @short Restores escaped HTML characters. |
| 6770 * @example |
| 6771 * |
| 6772 * '<p>some text</p>'.unescapeHTML() -> '<p>some text</p>' |
| 6773 * 'one & two'.unescapeHTML() -> 'one & two' |
| 6774 * |
| 6775 ***/ |
| 6776 'unescapeHTML': function() { |
| 6777 return this.replace(/</g, '<') |
| 6778 .replace(/>/g, '>') |
| 6779 .replace(/"/g, '"') |
| 6780 .replace(/'/g, "'") |
| 6781 .replace(///g, '/') |
| 6782 .replace(/&/g, '&'); |
| 6783 }, |
| 6784 |
| 6785 /*** |
| 6786 * @method encodeBase64() |
| 6787 * @returns String |
| 6788 * @short Encodes the string into base64 encoding. |
| 6789 * @extra This method wraps the browser native %btoa% when available, and u
ses a custom implementation when not available. It can also handle Unicode strin
g encodings. |
| 6790 * @example |
| 6791 * |
| 6792 * 'gonna get encoded!'.encodeBase64() -> 'Z29ubmEgZ2V0IGVuY29kZWQh' |
| 6793 * 'http://twitter.com/'.encodeBase64() -> 'aHR0cDovL3R3aXR0ZXIuY29tLw==' |
| 6794 * |
| 6795 ***/ |
| 6796 'encodeBase64': function() { |
| 6797 return btoa(unescape(encodeURIComponent(this))); |
| 6798 }, |
| 6799 |
| 6800 /*** |
| 6801 * @method decodeBase64() |
| 6802 * @returns String |
| 6803 * @short Decodes the string from base64 encoding. |
| 6804 * @extra This method wraps the browser native %atob% when available, and u
ses a custom implementation when not available. It can also handle Unicode strin
g encodings. |
| 6805 * @example |
| 6806 * |
| 6807 * 'aHR0cDovL3R3aXR0ZXIuY29tLw=='.decodeBase64() -> 'http://twitter.com/' |
| 6808 * 'anVzdCBnb3QgZGVjb2RlZA=='.decodeBase64() -> 'just got decoded!' |
| 6809 * |
| 6810 ***/ |
| 6811 'decodeBase64': function() { |
| 6812 return decodeURIComponent(escape(atob(this))); |
| 6813 }, |
| 6814 |
| 6815 /*** |
| 6816 * @method each([search] = single character, [fn]) |
| 6817 * @returns Array |
| 6818 * @short Runs callback [fn] against each occurence of [search]. |
| 6819 * @extra Returns an array of matches. [search] may be either a string or re
gex, and defaults to every character in the string. |
| 6820 * @example |
| 6821 * |
| 6822 * 'jumpy'.each() -> ['j','u','m','p','y'] |
| 6823 * 'jumpy'.each(/[r-z]/) -> ['u','y'] |
| 6824 * 'jumpy'.each(/[r-z]/, function(m) { |
| 6825 * // Called twice: "u", "y" |
| 6826 * }); |
| 6827 * |
| 6828 ***/ |
| 6829 'each': function(search, fn) { |
| 6830 var match, i, len; |
| 6831 if(isFunction(search)) { |
| 6832 fn = search; |
| 6833 search = /[\s\S]/g; |
| 6834 } else if(!search) { |
| 6835 search = /[\s\S]/g |
| 6836 } else if(isString(search)) { |
| 6837 search = regexp(escapeRegExp(search), 'gi'); |
| 6838 } else if(isRegExp(search)) { |
| 6839 search = regexp(search.source, getRegExpFlags(search, 'g')); |
| 6840 } |
| 6841 match = this.match(search) || []; |
| 6842 if(fn) { |
| 6843 for(i = 0, len = match.length; i < len; i++) { |
| 6844 match[i] = fn.call(this, match[i], i, match) || match[i]; |
| 6845 } |
| 6846 } |
| 6847 return match; |
| 6848 }, |
| 6849 |
| 6850 /*** |
| 6851 * @method shift(<n>) |
| 6852 * @returns Array |
| 6853 * @short Shifts each character in the string <n> places in the character ma
p. |
| 6854 * @example |
| 6855 * |
| 6856 * 'a'.shift(1) -> 'b' |
| 6857 * 'ク'.shift(1) -> 'グ' |
| 6858 * |
| 6859 ***/ |
| 6860 'shift': function(n) { |
| 6861 var result = ''; |
| 6862 n = n || 0; |
| 6863 this.codes(function(c) { |
| 6864 result += chr(c + n); |
| 6865 }); |
| 6866 return result; |
| 6867 }, |
| 6868 |
| 6869 /*** |
| 6870 * @method codes([fn]) |
| 6871 * @returns Array |
| 6872 * @short Runs callback [fn] against each character code in the string. Retu
rns an array of character codes. |
| 6873 * @example |
| 6874 * |
| 6875 * 'jumpy'.codes() -> [106,117,109,112,121] |
| 6876 * 'jumpy'.codes(function(c) { |
| 6877 * // Called 5 times: 106, 117, 109, 112, 121 |
| 6878 * }); |
| 6879 * |
| 6880 ***/ |
| 6881 'codes': function(fn) { |
| 6882 var codes = [], i, len; |
| 6883 for(i = 0, len = this.length; i < len; i++) { |
| 6884 var code = this.charCodeAt(i); |
| 6885 codes.push(code); |
| 6886 if(fn) fn.call(this, code, i); |
| 6887 } |
| 6888 return codes; |
| 6889 }, |
| 6890 |
| 6891 /*** |
| 6892 * @method chars([fn]) |
| 6893 * @returns Array |
| 6894 * @short Runs callback [fn] against each character in the string. Returns a
n array of characters. |
| 6895 * @example |
| 6896 * |
| 6897 * 'jumpy'.chars() -> ['j','u','m','p','y'] |
| 6898 * 'jumpy'.chars(function(c) { |
| 6899 * // Called 5 times: "j","u","m","p","y" |
| 6900 * }); |
| 6901 * |
| 6902 ***/ |
| 6903 'chars': function(fn) { |
| 6904 return this.each(fn); |
| 6905 }, |
| 6906 |
| 6907 /*** |
| 6908 * @method words([fn]) |
| 6909 * @returns Array |
| 6910 * @short Runs callback [fn] against each word in the string. Returns an arr
ay of words. |
| 6911 * @extra A "word" here is defined as any sequence of non-whitespace charact
ers. |
| 6912 * @example |
| 6913 * |
| 6914 * 'broken wear'.words() -> ['broken','wear'] |
| 6915 * 'broken wear'.words(function(w) { |
| 6916 * // Called twice: "broken", "wear" |
| 6917 * }); |
| 6918 * |
| 6919 ***/ |
| 6920 'words': function(fn) { |
| 6921 return this.trim().each(/\S+/g, fn); |
| 6922 }, |
| 6923 |
| 6924 /*** |
| 6925 * @method lines([fn]) |
| 6926 * @returns Array |
| 6927 * @short Runs callback [fn] against each line in the string. Returns an arr
ay of lines. |
| 6928 * @example |
| 6929 * |
| 6930 * 'broken wear\nand\njumpy jump'.lines() -> ['broken wear','and','jumpy j
ump'] |
| 6931 * 'broken wear\nand\njumpy jump'.lines(function(l) { |
| 6932 * // Called three times: "broken wear", "and", "jumpy jump" |
| 6933 * }); |
| 6934 * |
| 6935 ***/ |
| 6936 'lines': function(fn) { |
| 6937 return this.trim().each(/^.*$/gm, fn); |
| 6938 }, |
| 6939 |
| 6940 /*** |
| 6941 * @method paragraphs([fn]) |
| 6942 * @returns Array |
| 6943 * @short Runs callback [fn] against each paragraph in the string. Returns a
n array of paragraphs. |
| 6944 * @extra A paragraph here is defined as a block of text bounded by two or m
ore line breaks. |
| 6945 * @example |
| 6946 * |
| 6947 * 'Once upon a time.\n\nIn the land of oz...'.paragraphs() -> ['Once upon
a time.','In the land of oz...'] |
| 6948 * 'Once upon a time.\n\nIn the land of oz...'.paragraphs(function(p) { |
| 6949 * // Called twice: "Once upon a time.", "In teh land of oz..." |
| 6950 * }); |
| 6951 * |
| 6952 ***/ |
| 6953 'paragraphs': function(fn) { |
| 6954 var paragraphs = this.trim().split(/[\r\n]{2,}/); |
| 6955 paragraphs = paragraphs.map(function(p) { |
| 6956 if(fn) var s = fn.call(p); |
| 6957 return s ? s : p; |
| 6958 }); |
| 6959 return paragraphs; |
| 6960 }, |
| 6961 |
| 6962 /*** |
| 6963 * @method isBlank() |
| 6964 * @returns Boolean |
| 6965 * @short Returns true if the string has a length of 0 or contains only whit
espace. |
| 6966 * @example |
| 6967 * |
| 6968 * ''.isBlank() -> true |
| 6969 * ' '.isBlank() -> true |
| 6970 * 'noway'.isBlank() -> false |
| 6971 * |
| 6972 ***/ |
| 6973 'isBlank': function() { |
| 6974 return this.trim().length === 0; |
| 6975 }, |
| 6976 |
| 6977 /*** |
| 6978 * @method has(<find>) |
| 6979 * @returns Boolean |
| 6980 * @short Returns true if the string matches <find>. |
| 6981 * @extra <find> may be a string or regex. |
| 6982 * @example |
| 6983 * |
| 6984 * 'jumpy'.has('py') -> true |
| 6985 * 'broken'.has(/[a-n]/) -> true |
| 6986 * 'broken'.has(/[s-z]/) -> false |
| 6987 * |
| 6988 ***/ |
| 6989 'has': function(find) { |
| 6990 return this.search(isRegExp(find) ? find : escapeRegExp(find)) !== -1; |
| 6991 }, |
| 6992 |
| 6993 |
| 6994 /*** |
| 6995 * @method add(<str>, [index] = length) |
| 6996 * @returns String |
| 6997 * @short Adds <str> at [index]. Negative values are also allowed. |
| 6998 * @extra %insert% is provided as an alias, and is generally more readable w
hen using an index. |
| 6999 * @example |
| 7000 * |
| 7001 * 'schfifty'.add(' five') -> schfifty five |
| 7002 * 'dopamine'.insert('e', 3) -> dopeamine |
| 7003 * 'spelling eror'.insert('r', -3) -> spelling error |
| 7004 * |
| 7005 ***/ |
| 7006 'add': function(str, index) { |
| 7007 index = isUndefined(index) ? this.length : index; |
| 7008 return this.slice(0, index) + str + this.slice(index); |
| 7009 }, |
| 7010 |
| 7011 /*** |
| 7012 * @method remove(<f>) |
| 7013 * @returns String |
| 7014 * @short Removes any part of the string that matches <f>. |
| 7015 * @extra <f> can be a string or a regex. |
| 7016 * @example |
| 7017 * |
| 7018 * 'schfifty five'.remove('f') -> 'schity ive' |
| 7019 * 'schfifty five'.remove(/[a-f]/g) -> 'shity iv' |
| 7020 * |
| 7021 ***/ |
| 7022 'remove': function(f) { |
| 7023 return this.replace(f, ''); |
| 7024 }, |
| 7025 |
| 7026 /*** |
| 7027 * @method reverse() |
| 7028 * @returns String |
| 7029 * @short Reverses the string. |
| 7030 * @example |
| 7031 * |
| 7032 * 'jumpy'.reverse() -> 'ypmuj' |
| 7033 * 'lucky charms'.reverse() -> 'smrahc ykcul' |
| 7034 * |
| 7035 ***/ |
| 7036 'reverse': function() { |
| 7037 return this.split('').reverse().join(''); |
| 7038 }, |
| 7039 |
| 7040 /*** |
| 7041 * @method compact() |
| 7042 * @returns String |
| 7043 * @short Compacts all white space in the string to a single space and trims
the ends. |
| 7044 * @example |
| 7045 * |
| 7046 * 'too \n much \n space'.compact() -> 'too much space' |
| 7047 * 'enough \n '.compact() -> 'enought' |
| 7048 * |
| 7049 ***/ |
| 7050 'compact': function() { |
| 7051 return this.trim().replace(/([\r\n\s ])+/g, function(match, whitespace){ |
| 7052 return whitespace === ' ' ? whitespace : ' '; |
| 7053 }); |
| 7054 }, |
| 7055 |
| 7056 /*** |
| 7057 * @method at(<index>, [loop] = true) |
| 7058 * @returns String or Array |
| 7059 * @short Gets the character(s) at a given index. |
| 7060 * @extra When [loop] is true, overshooting the end of the string (or the be
ginning) will begin counting from the other end. As an alternate syntax, passing
multiple indexes will get the characters at those indexes. |
| 7061 * @example |
| 7062 * |
| 7063 * 'jumpy'.at(0) -> 'j' |
| 7064 * 'jumpy'.at(2) -> 'm' |
| 7065 * 'jumpy'.at(5) -> 'j' |
| 7066 * 'jumpy'.at(5, false) -> '' |
| 7067 * 'jumpy'.at(-1) -> 'y' |
| 7068 * 'lucky charms'.at(2,4,6,8) -> ['u','k','y',c'] |
| 7069 * |
| 7070 ***/ |
| 7071 'at': function() { |
| 7072 return getEntriesForIndexes(this, arguments, true); |
| 7073 }, |
| 7074 |
| 7075 /*** |
| 7076 * @method from([index] = 0) |
| 7077 * @returns String |
| 7078 * @short Returns a section of the string starting from [index]. |
| 7079 * @example |
| 7080 * |
| 7081 * 'lucky charms'.from() -> 'lucky charms' |
| 7082 * 'lucky charms'.from(7) -> 'harms' |
| 7083 * |
| 7084 ***/ |
| 7085 'from': function(from) { |
| 7086 return this.slice(numberOrIndex(this, from, true)); |
| 7087 }, |
| 7088 |
| 7089 /*** |
| 7090 * @method to([index] = end) |
| 7091 * @returns String |
| 7092 * @short Returns a section of the string ending at [index]. |
| 7093 * @example |
| 7094 * |
| 7095 * 'lucky charms'.to() -> 'lucky charms' |
| 7096 * 'lucky charms'.to(7) -> 'lucky ch' |
| 7097 * |
| 7098 ***/ |
| 7099 'to': function(to) { |
| 7100 if(isUndefined(to)) to = this.length; |
| 7101 return this.slice(0, numberOrIndex(this, to)); |
| 7102 }, |
| 7103 |
| 7104 /*** |
| 7105 * @method dasherize() |
| 7106 * @returns String |
| 7107 * @short Converts underscores and camel casing to hypens. |
| 7108 * @example |
| 7109 * |
| 7110 * 'a_farewell_to_arms'.dasherize() -> 'a-farewell-to-arms' |
| 7111 * 'capsLock'.dasherize() -> 'caps-lock' |
| 7112 * |
| 7113 ***/ |
| 7114 'dasherize': function() { |
| 7115 return this.underscore().replace(/_/g, '-'); |
| 7116 }, |
| 7117 |
| 7118 /*** |
| 7119 * @method underscore() |
| 7120 * @returns String |
| 7121 * @short Converts hyphens and camel casing to underscores. |
| 7122 * @example |
| 7123 * |
| 7124 * 'a-farewell-to-arms'.underscore() -> 'a_farewell_to_arms' |
| 7125 * 'capsLock'.underscore() -> 'caps_lock' |
| 7126 * |
| 7127 ***/ |
| 7128 'underscore': function() { |
| 7129 return this |
| 7130 .replace(/[-\s]+/g, '_') |
| 7131 .replace(string.Inflector && string.Inflector.acronymRegExp, function(ac
ronym, index) { |
| 7132 return (index > 0 ? '_' : '') + acronym.toLowerCase(); |
| 7133 }) |
| 7134 .replace(/([A-Z\d]+)([A-Z][a-z])/g,'$1_$2') |
| 7135 .replace(/([a-z\d])([A-Z])/g,'$1_$2') |
| 7136 .toLowerCase(); |
| 7137 }, |
| 7138 |
| 7139 /*** |
| 7140 * @method camelize([first] = true) |
| 7141 * @returns String |
| 7142 * @short Converts underscores and hyphens to camel case. If [first] is true
the first letter will also be capitalized. |
| 7143 * @extra If the Inflections package is included acryonyms can also be defin
ed that will be used when camelizing. |
| 7144 * @example |
| 7145 * |
| 7146 * 'caps_lock'.camelize() -> 'CapsLock' |
| 7147 * 'moz-border-radius'.camelize() -> 'MozBorderRadius' |
| 7148 * 'moz-border-radius'.camelize(false) -> 'mozBorderRadius' |
| 7149 * |
| 7150 ***/ |
| 7151 'camelize': function(first) { |
| 7152 return this.underscore().replace(/(^|_)([^_]+)/g, function(match, pre, wor
d, index) { |
| 7153 var acronym = getAcronym(word), capitalize = first !== false || index >
0; |
| 7154 if(acronym) return capitalize ? acronym : acronym.toLowerCase(); |
| 7155 return capitalize ? word.capitalize() : word; |
| 7156 }); |
| 7157 }, |
| 7158 |
| 7159 /*** |
| 7160 * @method spacify() |
| 7161 * @returns String |
| 7162 * @short Converts camel case, underscores, and hyphens to a properly spaced
string. |
| 7163 * @example |
| 7164 * |
| 7165 * 'camelCase'.spacify() -> 'camel case' |
| 7166 * 'an-ugly-string'.spacify() -> 'an ugly string' |
| 7167 * 'oh-no_youDid-not'.spacify().capitalize(true) -> 'something else' |
| 7168 * |
| 7169 ***/ |
| 7170 'spacify': function() { |
| 7171 return this.underscore().replace(/_/g, ' '); |
| 7172 }, |
| 7173 |
| 7174 /*** |
| 7175 * @method stripTags([tag1], [tag2], ...) |
| 7176 * @returns String |
| 7177 * @short Strips all HTML tags from the string. |
| 7178 * @extra Tags to strip may be enumerated in the parameters, otherwise will
strip all. |
| 7179 * @example |
| 7180 * |
| 7181 * '<p>just <b>some</b> text</p>'.stripTags() -> 'just some text' |
| 7182 * '<p>just <b>some</b> text</p>'.stripTags('p') -> 'just <b>some</b> text
' |
| 7183 * |
| 7184 ***/ |
| 7185 'stripTags': function() { |
| 7186 var str = this, args = arguments.length > 0 ? arguments : ['']; |
| 7187 flattenedArgs(args, function(tag) { |
| 7188 str = str.replace(regexp('<\/?' + escapeRegExp(tag) + '[^<>]*>', 'gi'),
''); |
| 7189 }); |
| 7190 return str; |
| 7191 }, |
| 7192 |
| 7193 /*** |
| 7194 * @method removeTags([tag1], [tag2], ...) |
| 7195 * @returns String |
| 7196 * @short Removes all HTML tags and their contents from the string. |
| 7197 * @extra Tags to remove may be enumerated in the parameters, otherwise will
remove all. |
| 7198 * @example |
| 7199 * |
| 7200 * '<p>just <b>some</b> text</p>'.removeTags() -> '' |
| 7201 * '<p>just <b>some</b> text</p>'.removeTags('b') -> '<p>just text</p>' |
| 7202 * |
| 7203 ***/ |
| 7204 'removeTags': function() { |
| 7205 var str = this, args = arguments.length > 0 ? arguments : ['\\S+']; |
| 7206 flattenedArgs(args, function(t) { |
| 7207 var reg = regexp('<(' + t + ')[^<>]*(?:\\/>|>.*?<\\/\\1>)', 'gi'); |
| 7208 str = str.replace(reg, ''); |
| 7209 }); |
| 7210 return str; |
| 7211 }, |
| 7212 |
| 7213 /*** |
| 7214 * @method truncate(<length>, [from] = 'right', [ellipsis] = '...') |
| 7215 * @returns String |
| 7216 * @short Truncates a string. |
| 7217 * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is
shorter than <length>, [ellipsis] will not be added. |
| 7218 * @example |
| 7219 * |
| 7220 * 'sittin on the dock of the bay'.truncate(18) -> 'just sittin
on the do...' |
| 7221 * 'sittin on the dock of the bay'.truncate(18, 'left') -> '...the dock
of the bay' |
| 7222 * 'sittin on the dock of the bay'.truncate(18, 'middle') -> 'just sitt...
of the bay' |
| 7223 * |
| 7224 ***/ |
| 7225 'truncate': function(length, from, ellipsis) { |
| 7226 return truncateString(this, length, from, ellipsis); |
| 7227 }, |
| 7228 |
| 7229 /*** |
| 7230 * @method truncateOnWord(<length>, [from] = 'right', [ellipsis] = '...') |
| 7231 * @returns String |
| 7232 * @short Truncates a string without splitting up words. |
| 7233 * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is
shorter than <length>, [ellipsis] will not be added. |
| 7234 * @example |
| 7235 * |
| 7236 * 'here we go'.truncateOnWord(5) -> 'here...' |
| 7237 * 'here we go'.truncateOnWord(5, 'left') -> '...we go' |
| 7238 * |
| 7239 ***/ |
| 7240 'truncateOnWord': function(length, from, ellipsis) { |
| 7241 return truncateString(this, length, from, ellipsis, true); |
| 7242 }, |
| 7243 |
| 7244 /*** |
| 7245 * @method pad[Side](<num> = null, [padding] = ' ') |
| 7246 * @returns String |
| 7247 * @short Pads the string out with [padding] to be exactly <num> characters. |
| 7248 * |
| 7249 * @set |
| 7250 * pad |
| 7251 * padLeft |
| 7252 * padRight |
| 7253 * |
| 7254 * @example |
| 7255 * |
| 7256 * 'wasabi'.pad(8) -> ' wasabi ' |
| 7257 * 'wasabi'.padLeft(8) -> ' wasabi' |
| 7258 * 'wasabi'.padRight(8) -> 'wasabi ' |
| 7259 * 'wasabi'.padRight(8, '-') -> 'wasabi--' |
| 7260 * |
| 7261 ***/ |
| 7262 'pad': function(num, padding) { |
| 7263 var half, front, back; |
| 7264 num = checkRepeatRange(num); |
| 7265 half = max(0, num - this.length) / 2; |
| 7266 front = floor(half); |
| 7267 back = ceil(half); |
| 7268 return padString(front, padding) + this + padString(back, padding); |
| 7269 }, |
| 7270 |
| 7271 'padLeft': function(num, padding) { |
| 7272 num = checkRepeatRange(num); |
| 7273 return padString(max(0, num - this.length), padding) + this; |
| 7274 }, |
| 7275 |
| 7276 'padRight': function(num, padding) { |
| 7277 num = checkRepeatRange(num); |
| 7278 return this + padString(max(0, num - this.length), padding); |
| 7279 }, |
| 7280 |
| 7281 /*** |
| 7282 * @method first([n] = 1) |
| 7283 * @returns String |
| 7284 * @short Returns the first [n] characters of the string. |
| 7285 * @example |
| 7286 * |
| 7287 * 'lucky charms'.first() -> 'l' |
| 7288 * 'lucky charms'.first(3) -> 'luc' |
| 7289 * |
| 7290 ***/ |
| 7291 'first': function(num) { |
| 7292 if(isUndefined(num)) num = 1; |
| 7293 return this.substr(0, num); |
| 7294 }, |
| 7295 |
| 7296 /*** |
| 7297 * @method last([n] = 1) |
| 7298 * @returns String |
| 7299 * @short Returns the last [n] characters of the string. |
| 7300 * @example |
| 7301 * |
| 7302 * 'lucky charms'.last() -> 's' |
| 7303 * 'lucky charms'.last(3) -> 'rms' |
| 7304 * |
| 7305 ***/ |
| 7306 'last': function(num) { |
| 7307 if(isUndefined(num)) num = 1; |
| 7308 var start = this.length - num < 0 ? 0 : this.length - num; |
| 7309 return this.substr(start); |
| 7310 }, |
| 7311 |
| 7312 /*** |
| 7313 * @method toNumber([base] = 10) |
| 7314 * @returns Number |
| 7315 * @short Converts the string into a number. |
| 7316 * @extra Any value with a "." fill be converted to a floating point value,
otherwise an integer. |
| 7317 * @example |
| 7318 * |
| 7319 * '153'.toNumber() -> 153 |
| 7320 * '12,000'.toNumber() -> 12000 |
| 7321 * '10px'.toNumber() -> 10 |
| 7322 * 'ff'.toNumber(16) -> 255 |
| 7323 * |
| 7324 ***/ |
| 7325 'toNumber': function(base) { |
| 7326 return stringToNumber(this, base); |
| 7327 }, |
| 7328 |
| 7329 /*** |
| 7330 * @method capitalize([all] = false) |
| 7331 * @returns String |
| 7332 * @short Capitalizes the first character in the string and downcases all ot
her letters. |
| 7333 * @extra If [all] is true, all words in the string will be capitalized. |
| 7334 * @example |
| 7335 * |
| 7336 * 'hello'.capitalize() -> 'Hello' |
| 7337 * 'hello kitty'.capitalize() -> 'Hello kitty' |
| 7338 * 'hello kitty'.capitalize(true) -> 'Hello Kitty' |
| 7339 * |
| 7340 * |
| 7341 ***/ |
| 7342 'capitalize': function(all) { |
| 7343 var lastResponded; |
| 7344 return this.toLowerCase().replace(all ? /[^']/g : /^\S/, function(lower) { |
| 7345 var upper = lower.toUpperCase(), result; |
| 7346 result = lastResponded ? lower : upper; |
| 7347 lastResponded = upper !== lower; |
| 7348 return result; |
| 7349 }); |
| 7350 }, |
| 7351 |
| 7352 /*** |
| 7353 * @method assign(<obj1>, <obj2>, ...) |
| 7354 * @returns String |
| 7355 * @short Assigns variables to tokens in a string, demarcated with `{}`. |
| 7356 * @extra If an object is passed, it's properties can be assigned using the
object's keys (i.e. {name}). If a non-object (string, number, etc.) is passed it
can be accessed by the argument number beginning with {1} (as with regex tokens
). Multiple objects can be passed and will be merged together (original objects
are unaffected). |
| 7357 * @example |
| 7358 * |
| 7359 * 'Welcome, Mr. {name}.'.assign({ name: 'Franklin' }) -> 'Welcome, Mr.
Franklin.' |
| 7360 * 'You are {1} years old today.'.assign(14) -> 'You are 14 ye
ars old today.' |
| 7361 * '{n} and {r}'.assign({ n: 'Cheech' }, { r: 'Chong' }) -> 'Cheech and Ch
ong' |
| 7362 * |
| 7363 ***/ |
| 7364 'assign': function() { |
| 7365 var assign = {}; |
| 7366 flattenedArgs(arguments, function(a, i) { |
| 7367 if(isObjectType(a)) { |
| 7368 simpleMerge(assign, a); |
| 7369 } else { |
| 7370 assign[i + 1] = a; |
| 7371 } |
| 7372 }); |
| 7373 return this.replace(/\{([^{]+?)\}/g, function(m, key) { |
| 7374 return hasOwnProperty(assign, key) ? assign[key] : m; |
| 7375 }); |
| 7376 } |
| 7377 |
| 7378 }); |
| 7379 |
| 7380 |
| 7381 // Aliases |
| 7382 |
| 7383 extend(string, true, true, { |
| 7384 |
| 7385 /*** |
| 7386 * @method insert() |
| 7387 * @alias add |
| 7388 * |
| 7389 ***/ |
| 7390 'insert': string.prototype.add |
| 7391 }); |
| 7392 |
| 7393 buildBase64('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
'); |
| 7394 |
| 7395 |
| 7396 /*** |
| 7397 * |
| 7398 * @package Inflections |
| 7399 * @dependency string |
| 7400 * @description Pluralization similar to ActiveSupport including uncountable w
ords and acronyms. Humanized and URL-friendly strings. |
| 7401 * |
| 7402 ***/ |
| 7403 |
| 7404 /*** |
| 7405 * String module |
| 7406 * |
| 7407 ***/ |
| 7408 |
| 7409 |
| 7410 var plurals = [], |
| 7411 singulars = [], |
| 7412 uncountables = [], |
| 7413 humans = [], |
| 7414 acronyms = {}, |
| 7415 Downcased, |
| 7416 Inflector; |
| 7417 |
| 7418 function removeFromArray(arr, find) { |
| 7419 var index = arr.indexOf(find); |
| 7420 if(index > -1) { |
| 7421 arr.splice(index, 1); |
| 7422 } |
| 7423 } |
| 7424 |
| 7425 function removeFromUncountablesAndAddTo(arr, rule, replacement) { |
| 7426 if(isString(rule)) { |
| 7427 removeFromArray(uncountables, rule); |
| 7428 } |
| 7429 removeFromArray(uncountables, replacement); |
| 7430 arr.unshift({ rule: rule, replacement: replacement }) |
| 7431 } |
| 7432 |
| 7433 function paramMatchesType(param, type) { |
| 7434 return param == type || param == 'all' || !param; |
| 7435 } |
| 7436 |
| 7437 function isUncountable(word) { |
| 7438 return uncountables.some(function(uncountable) { |
| 7439 return new regexp('\\b' + uncountable + '$', 'i').test(word); |
| 7440 }); |
| 7441 } |
| 7442 |
| 7443 function inflect(word, pluralize) { |
| 7444 word = isString(word) ? word.toString() : ''; |
| 7445 if(word.isBlank() || isUncountable(word)) { |
| 7446 return word; |
| 7447 } else { |
| 7448 return runReplacements(word, pluralize ? plurals : singulars); |
| 7449 } |
| 7450 } |
| 7451 |
| 7452 function runReplacements(word, table) { |
| 7453 iterateOverObject(table, function(i, inflection) { |
| 7454 if(word.match(inflection.rule)) { |
| 7455 word = word.replace(inflection.rule, inflection.replacement); |
| 7456 return false; |
| 7457 } |
| 7458 }); |
| 7459 return word; |
| 7460 } |
| 7461 |
| 7462 function capitalize(word) { |
| 7463 return word.replace(/^\W*[a-z]/, function(w){ |
| 7464 return w.toUpperCase(); |
| 7465 }); |
| 7466 } |
| 7467 |
| 7468 Inflector = { |
| 7469 |
| 7470 /* |
| 7471 * Specifies a new acronym. An acronym must be specified as it will appear i
n a camelized string. An underscore |
| 7472 * string that contains the acronym will retain the acronym when passed to %
camelize%, %humanize%, or %titleize%. |
| 7473 * A camelized string that contains the acronym will maintain the acronym wh
en titleized or humanized, and will |
| 7474 * convert the acronym into a non-delimited single lowercase word when passe
d to String#underscore. |
| 7475 * |
| 7476 * Examples: |
| 7477 * String.Inflector.acronym('HTML') |
| 7478 * 'html'.titleize() -> 'HTML' |
| 7479 * 'html'.camelize() -> 'HTML' |
| 7480 * 'MyHTML'.underscore() -> 'my_html' |
| 7481 * |
| 7482 * The acronym, however, must occur as a delimited unit and not be part of a
nother word for conversions to recognize it: |
| 7483 * |
| 7484 * String.Inflector.acronym('HTTP') |
| 7485 * 'my_http_delimited'.camelize() -> 'MyHTTPDelimited' |
| 7486 * 'https'.camelize() -> 'Https', not 'HTTPs' |
| 7487 * 'HTTPS'.underscore() -> 'http_s', not 'https' |
| 7488 * |
| 7489 * String.Inflector.acronym('HTTPS') |
| 7490 * 'https'.camelize() -> 'HTTPS' |
| 7491 * 'HTTPS'.underscore() -> 'https' |
| 7492 * |
| 7493 * Note: Acronyms that are passed to %pluralize% will no longer be recognize
d, since the acronym will not occur as |
| 7494 * a delimited unit in the pluralized result. To work around this, you must
specify the pluralized form as an |
| 7495 * acronym as well: |
| 7496 * |
| 7497 * String.Inflector.acronym('API') |
| 7498 * 'api'.pluralize().camelize() -> 'Apis' |
| 7499 * |
| 7500 * String.Inflector.acronym('APIs') |
| 7501 * 'api'.pluralize().camelize() -> 'APIs' |
| 7502 * |
| 7503 * %acronym% may be used to specify any word that contains an acronym or oth
erwise needs to maintain a non-standard |
| 7504 * capitalization. The only restriction is that the word must begin with a c
apital letter. |
| 7505 * |
| 7506 * Examples: |
| 7507 * String.Inflector.acronym('RESTful') |
| 7508 * 'RESTful'.underscore() -> 'restful' |
| 7509 * 'RESTfulController'.underscore() -> 'restful_controller' |
| 7510 * 'RESTfulController'.titleize() -> 'RESTful Controller' |
| 7511 * 'restful'.camelize() -> 'RESTful' |
| 7512 * 'restful_controller'.camelize() -> 'RESTfulController' |
| 7513 * |
| 7514 * String.Inflector.acronym('McDonald') |
| 7515 * 'McDonald'.underscore() -> 'mcdonald' |
| 7516 * 'mcdonald'.camelize() -> 'McDonald' |
| 7517 */ |
| 7518 'acronym': function(word) { |
| 7519 acronyms[word.toLowerCase()] = word; |
| 7520 var all = object.keys(acronyms).map(function(key) { |
| 7521 return acronyms[key]; |
| 7522 }); |
| 7523 Inflector.acronymRegExp = regexp(all.join('|'), 'g'); |
| 7524 }, |
| 7525 |
| 7526 /* |
| 7527 * Specifies a new pluralization rule and its replacement. The rule can eith
er be a string or a regular expression. |
| 7528 * The replacement should always be a string that may include references to
the matched data from the rule. |
| 7529 */ |
| 7530 'plural': function(rule, replacement) { |
| 7531 removeFromUncountablesAndAddTo(plurals, rule, replacement); |
| 7532 }, |
| 7533 |
| 7534 /* |
| 7535 * Specifies a new singularization rule and its replacement. The rule can ei
ther be a string or a regular expression. |
| 7536 * The replacement should always be a string that may include references to
the matched data from the rule. |
| 7537 */ |
| 7538 'singular': function(rule, replacement) { |
| 7539 removeFromUncountablesAndAddTo(singulars, rule, replacement); |
| 7540 }, |
| 7541 |
| 7542 /* |
| 7543 * Specifies a new irregular that applies to both pluralization and singular
ization at the same time. This can only be used |
| 7544 * for strings, not regular expressions. You simply pass the irregular in si
ngular and plural form. |
| 7545 * |
| 7546 * Examples: |
| 7547 * String.Inflector.irregular('octopus', 'octopi') |
| 7548 * String.Inflector.irregular('person', 'people') |
| 7549 */ |
| 7550 'irregular': function(singular, plural) { |
| 7551 var singularFirst = singular.first(), |
| 7552 singularRest = singular.from(1), |
| 7553 pluralFirst = plural.first(), |
| 7554 pluralRest = plural.from(1), |
| 7555 pluralFirstUpper = pluralFirst.toUpperCase(), |
| 7556 pluralFirstLower = pluralFirst.toLowerCase(), |
| 7557 singularFirstUpper = singularFirst.toUpperCase(), |
| 7558 singularFirstLower = singularFirst.toLowerCase(); |
| 7559 removeFromArray(uncountables, singular); |
| 7560 removeFromArray(uncountables, plural); |
| 7561 if(singularFirstUpper == pluralFirstUpper) { |
| 7562 Inflector.plural(new regexp('({1}){2}$'.assign(singularFirst, singularRe
st), 'i'), '$1' + pluralRest); |
| 7563 Inflector.plural(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest),
'i'), '$1' + pluralRest); |
| 7564 Inflector.singular(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest
), 'i'), '$1' + singularRest); |
| 7565 } else { |
| 7566 Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstUpper, singula
rRest)), pluralFirstUpper + pluralRest); |
| 7567 Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstLower, singula
rRest)), pluralFirstLower + pluralRest); |
| 7568 Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralRes
t)), pluralFirstUpper + pluralRest); |
| 7569 Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralRes
t)), pluralFirstLower + pluralRest); |
| 7570 Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralR
est)), singularFirstUpper + singularRest); |
| 7571 Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralR
est)), singularFirstLower + singularRest); |
| 7572 } |
| 7573 }, |
| 7574 |
| 7575 /* |
| 7576 * Add uncountable words that shouldn't be attempted inflected. |
| 7577 * |
| 7578 * Examples: |
| 7579 * String.Inflector.uncountable('money') |
| 7580 * String.Inflector.uncountable('money', 'information') |
| 7581 * String.Inflector.uncountable(['money', 'information', 'rice']) |
| 7582 */ |
| 7583 'uncountable': function(first) { |
| 7584 var add = array.isArray(first) ? first : multiArgs(arguments); |
| 7585 uncountables = uncountables.concat(add); |
| 7586 }, |
| 7587 |
| 7588 /* |
| 7589 * Specifies a humanized form of a string by a regular expression rule or by
a string mapping. |
| 7590 * When using a regular expression based replacement, the normal humanize fo
rmatting is called after the replacement. |
| 7591 * When a string is used, the human form should be specified as desired (exa
mple: 'The name', not 'the_name') |
| 7592 * |
| 7593 * Examples: |
| 7594 * String.Inflector.human(/_cnt$/i, '_count') |
| 7595 * String.Inflector.human('legacy_col_person_name', 'Name') |
| 7596 */ |
| 7597 'human': function(rule, replacement) { |
| 7598 humans.unshift({ rule: rule, replacement: replacement }) |
| 7599 }, |
| 7600 |
| 7601 |
| 7602 /* |
| 7603 * Clears the loaded inflections within a given scope (default is 'all'). |
| 7604 * Options are: 'all', 'plurals', 'singulars', 'uncountables', 'humans'. |
| 7605 * |
| 7606 * Examples: |
| 7607 * String.Inflector.clear('all') |
| 7608 * String.Inflector.clear('plurals') |
| 7609 */ |
| 7610 'clear': function(type) { |
| 7611 if(paramMatchesType(type, 'singulars')) singulars = []; |
| 7612 if(paramMatchesType(type, 'plurals')) plurals = []; |
| 7613 if(paramMatchesType(type, 'uncountables')) uncountables = []; |
| 7614 if(paramMatchesType(type, 'humans')) humans = []; |
| 7615 if(paramMatchesType(type, 'acronyms')) acronyms = {}; |
| 7616 } |
| 7617 |
| 7618 }; |
| 7619 |
| 7620 Downcased = [ |
| 7621 'and', 'or', 'nor', 'a', 'an', 'the', 'so', 'but', 'to', 'of', 'at', |
| 7622 'by', 'from', 'into', 'on', 'onto', 'off', 'out', 'in', 'over', |
| 7623 'with', 'for' |
| 7624 ]; |
| 7625 |
| 7626 Inflector.plural(/$/, 's'); |
| 7627 Inflector.plural(/s$/gi, 's'); |
| 7628 Inflector.plural(/(ax|test)is$/gi, '$1es'); |
| 7629 Inflector.plural(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1i'); |
| 7630 Inflector.plural(/(census|alias|status)$/gi, '$1es'); |
| 7631 Inflector.plural(/(bu)s$/gi, '$1ses'); |
| 7632 Inflector.plural(/(buffal|tomat)o$/gi, '$1oes'); |
| 7633 Inflector.plural(/([ti])um$/gi, '$1a'); |
| 7634 Inflector.plural(/([ti])a$/gi, '$1a'); |
| 7635 Inflector.plural(/sis$/gi, 'ses'); |
| 7636 Inflector.plural(/f+e?$/gi, 'ves'); |
| 7637 Inflector.plural(/(cuff|roof)$/gi, '$1s'); |
| 7638 Inflector.plural(/([ht]ive)$/gi, '$1s'); |
| 7639 Inflector.plural(/([^aeiouy]o)$/gi, '$1es'); |
| 7640 Inflector.plural(/([^aeiouy]|qu)y$/gi, '$1ies'); |
| 7641 Inflector.plural(/(x|ch|ss|sh)$/gi, '$1es'); |
| 7642 Inflector.plural(/(matr|vert|ind)(?:ix|ex)$/gi, '$1ices'); |
| 7643 Inflector.plural(/([ml])ouse$/gi, '$1ice'); |
| 7644 Inflector.plural(/([ml])ice$/gi, '$1ice'); |
| 7645 Inflector.plural(/^(ox)$/gi, '$1en'); |
| 7646 Inflector.plural(/^(oxen)$/gi, '$1'); |
| 7647 Inflector.plural(/(quiz)$/gi, '$1zes'); |
| 7648 Inflector.plural(/(phot|cant|hom|zer|pian|portic|pr|quart|kimon)o$/gi, '$1os')
; |
| 7649 Inflector.plural(/(craft)$/gi, '$1'); |
| 7650 Inflector.plural(/([ft])[eo]{2}(th?)$/gi, '$1ee$2'); |
| 7651 |
| 7652 Inflector.singular(/s$/gi, ''); |
| 7653 Inflector.singular(/([pst][aiu]s)$/gi, '$1'); |
| 7654 Inflector.singular(/([aeiouy])ss$/gi, '$1ss'); |
| 7655 Inflector.singular(/(n)ews$/gi, '$1ews'); |
| 7656 Inflector.singular(/([ti])a$/gi, '$1um'); |
| 7657 Inflector.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)s
es$/gi, '$1$2sis'); |
| 7658 Inflector.singular(/(^analy)ses$/gi, '$1sis'); |
| 7659 Inflector.singular(/(i)(f|ves)$/i, '$1fe'); |
| 7660 Inflector.singular(/([aeolr]f?)(f|ves)$/i, '$1f'); |
| 7661 Inflector.singular(/([ht]ive)s$/gi, '$1'); |
| 7662 Inflector.singular(/([^aeiouy]|qu)ies$/gi, '$1y'); |
| 7663 Inflector.singular(/(s)eries$/gi, '$1eries'); |
| 7664 Inflector.singular(/(m)ovies$/gi, '$1ovie'); |
| 7665 Inflector.singular(/(x|ch|ss|sh)es$/gi, '$1'); |
| 7666 Inflector.singular(/([ml])(ous|ic)e$/gi, '$1ouse'); |
| 7667 Inflector.singular(/(bus)(es)?$/gi, '$1'); |
| 7668 Inflector.singular(/(o)es$/gi, '$1'); |
| 7669 Inflector.singular(/(shoe)s?$/gi, '$1'); |
| 7670 Inflector.singular(/(cris|ax|test)[ie]s$/gi, '$1is'); |
| 7671 Inflector.singular(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1us'); |
| 7672 Inflector.singular(/(census|alias|status)(es)?$/gi, '$1'); |
| 7673 Inflector.singular(/^(ox)(en)?/gi, '$1'); |
| 7674 Inflector.singular(/(vert|ind)(ex|ices)$/gi, '$1ex'); |
| 7675 Inflector.singular(/(matr)(ix|ices)$/gi, '$1ix'); |
| 7676 Inflector.singular(/(quiz)(zes)?$/gi, '$1'); |
| 7677 Inflector.singular(/(database)s?$/gi, '$1'); |
| 7678 Inflector.singular(/ee(th?)$/gi, 'oo$1'); |
| 7679 |
| 7680 Inflector.irregular('person', 'people'); |
| 7681 Inflector.irregular('man', 'men'); |
| 7682 Inflector.irregular('child', 'children'); |
| 7683 Inflector.irregular('sex', 'sexes'); |
| 7684 Inflector.irregular('move', 'moves'); |
| 7685 Inflector.irregular('save', 'saves'); |
| 7686 Inflector.irregular('cow', 'kine'); |
| 7687 Inflector.irregular('goose', 'geese'); |
| 7688 Inflector.irregular('zombie', 'zombies'); |
| 7689 |
| 7690 Inflector.uncountable('equipment,information,rice,money,species,series,fish,sh
eep,jeans'.split(',')); |
| 7691 |
| 7692 |
| 7693 extend(string, true, true, { |
| 7694 |
| 7695 /*** |
| 7696 * @method pluralize() |
| 7697 * @returns String |
| 7698 * @short Returns the plural form of the word in the string. |
| 7699 * @example |
| 7700 * |
| 7701 * 'post'.pluralize() -> 'posts' |
| 7702 * 'octopus'.pluralize() -> 'octopi' |
| 7703 * 'sheep'.pluralize() -> 'sheep' |
| 7704 * 'words'.pluralize() -> 'words' |
| 7705 * 'CamelOctopus'.pluralize() -> 'CamelOctopi' |
| 7706 * |
| 7707 ***/ |
| 7708 'pluralize': function() { |
| 7709 return inflect(this, true); |
| 7710 }, |
| 7711 |
| 7712 /*** |
| 7713 * @method singularize() |
| 7714 * @returns String |
| 7715 * @short The reverse of String#pluralize. Returns the singular form of a wo
rd in a string. |
| 7716 * @example |
| 7717 * |
| 7718 * 'posts'.singularize() -> 'post' |
| 7719 * 'octopi'.singularize() -> 'octopus' |
| 7720 * 'sheep'.singularize() -> 'sheep' |
| 7721 * 'word'.singularize() -> 'word' |
| 7722 * 'CamelOctopi'.singularize() -> 'CamelOctopus' |
| 7723 * |
| 7724 ***/ |
| 7725 'singularize': function() { |
| 7726 return inflect(this, false); |
| 7727 }, |
| 7728 |
| 7729 /*** |
| 7730 * @method humanize() |
| 7731 * @returns String |
| 7732 * @short Creates a human readable string. |
| 7733 * @extra Capitalizes the first word and turns underscores into spaces and s
trips a trailing '_id', if any. Like String#titleize, this is meant for creating
pretty output. |
| 7734 * @example |
| 7735 * |
| 7736 * 'employee_salary'.humanize() -> 'Employee salary' |
| 7737 * 'author_id'.humanize() -> 'Author' |
| 7738 * |
| 7739 ***/ |
| 7740 'humanize': function() { |
| 7741 var str = runReplacements(this, humans), acronym; |
| 7742 str = str.replace(/_id$/g, ''); |
| 7743 str = str.replace(/(_)?([a-z\d]*)/gi, function(match, _, word){ |
| 7744 acronym = hasOwnProperty(acronyms, word) ? acronyms[word] : null; |
| 7745 return (_ ? ' ' : '') + (acronym || word.toLowerCase()); |
| 7746 }); |
| 7747 return capitalize(str); |
| 7748 }, |
| 7749 |
| 7750 /*** |
| 7751 * @method titleize() |
| 7752 * @returns String |
| 7753 * @short Creates a title version of the string. |
| 7754 * @extra Capitalizes all the words and replaces some characters in the stri
ng to create a nicer looking title. String#titleize is meant for creating pretty
output. |
| 7755 * @example |
| 7756 * |
| 7757 * 'man from the boondocks'.titleize() -> 'Man from the Boondocks' |
| 7758 * 'x-men: the last stand'.titleize() -> 'X Men: The Last Stand' |
| 7759 * 'TheManWithoutAPast'.titleize() -> 'The Man Without a Past' |
| 7760 * 'raiders_of_the_lost_ark'.titleize() -> 'Raiders of the Lost Ark' |
| 7761 * |
| 7762 ***/ |
| 7763 'titleize': function() { |
| 7764 var fullStopPunctuation = /[.:;!]$/, hasPunctuation, lastHadPunctuation, i
sFirstOrLast; |
| 7765 return this.spacify().humanize().words(function(word, index, words) { |
| 7766 hasPunctuation = fullStopPunctuation.test(word); |
| 7767 isFirstOrLast = index == 0 || index == words.length - 1 || hasPunctuatio
n || lastHadPunctuation; |
| 7768 lastHadPunctuation = hasPunctuation; |
| 7769 if(isFirstOrLast || Downcased.indexOf(word) === -1) { |
| 7770 return capitalize(word); |
| 7771 } else { |
| 7772 return word; |
| 7773 } |
| 7774 }).join(' '); |
| 7775 }, |
| 7776 |
| 7777 /*** |
| 7778 * @method parameterize() |
| 7779 * @returns String |
| 7780 * @short Replaces special characters in a string so that it may be used as
part of a pretty URL. |
| 7781 * @example |
| 7782 * |
| 7783 * 'hell, no!'.parameterize() -> 'hell-no' |
| 7784 * |
| 7785 ***/ |
| 7786 'parameterize': function(separator) { |
| 7787 var str = this; |
| 7788 if(separator === undefined) separator = '-'; |
| 7789 if(str.normalize) { |
| 7790 str = str.normalize(); |
| 7791 } |
| 7792 str = str.replace(/[^a-z0-9\-_]+/gi, separator) |
| 7793 if(separator) { |
| 7794 str = str.replace(new regexp('^{sep}+|{sep}+$|({sep}){sep}+'.assign({ 's
ep': escapeRegExp(separator) }), 'g'), '$1'); |
| 7795 } |
| 7796 return encodeURI(str.toLowerCase()); |
| 7797 } |
| 7798 |
| 7799 }); |
| 7800 |
| 7801 string.Inflector = Inflector; |
| 7802 string.Inflector.acronyms = acronyms; |
| 7803 |
| 7804 })(); |
OLD | NEW |