1/*! 2 * Date picker for pickadate.js v3.6.2 3 * http://amsul.github.io/pickadate.js/date.htm 4 */ 5 6(function ( factory ) { 7 8 // AMD. 9 if ( typeof define == 'function' && define.amd ) 10 define( ['./picker', 'jquery'], factory ) 11 12 // Node.js/browserify. 13 else if ( typeof exports == 'object' ) 14 module.exports = factory( require('./picker.js'), require('jquery') ) 15 16 // Browser globals. 17 else factory( Picker, jQuery ) 18 19}(function( Picker, $ ) { 20 21 22/** 23 * Globals and constants 24 */ 25var DAYS_IN_WEEK = 7, 26 WEEKS_IN_CALENDAR = 6, 27 _ = Picker._ 28 29 30 31/** 32 * The date picker constructor 33 */ 34function DatePicker( picker, settings ) { 35 36 var calendar = this, 37 element = picker.$node[ 0 ], 38 elementValue = element.value, 39 elementDataValue = picker.$node.data( 'value' ), 40 valueString = elementDataValue || elementValue, 41 formatString = elementDataValue ? settings.formatSubmit : settings.format, 42 isRTL = function() { 43 44 return element.currentStyle ? 45 46 // For IE. 47 element.currentStyle.direction == 'rtl' : 48 49 // For normal browsers. 50 getComputedStyle( picker.$root[0] ).direction == 'rtl' 51 } 52 53 calendar.settings = settings 54 calendar.$node = picker.$node 55 56 // The queue of methods that will be used to build item objects. 57 calendar.queue = { 58 min: 'measure create', 59 max: 'measure create', 60 now: 'now create', 61 select: 'parse create validate', 62 highlight: 'parse navigate create validate', 63 view: 'parse create validate viewset', 64 disable: 'deactivate', 65 enable: 'activate' 66 } 67 68 // The component's item object. 69 calendar.item = {} 70 71 calendar.item.clear = null 72 calendar.item.disable = ( settings.disable || [] ).slice( 0 ) 73 calendar.item.enable = -(function( collectionDisabled ) { 74 return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1 75 })( calendar.item.disable ) 76 77 calendar. 78 set( 'min', settings.min ). 79 set( 'max', settings.max ). 80 set( 'now' ) 81 82 // When thereâs a value, set the `select`, which in turn 83 // also sets the `highlight` and `view`. 84 if ( valueString ) { 85 calendar.set( 'select', valueString, { 86 format: formatString, 87 defaultValue: true 88 }) 89 } 90 91 // If thereâs no value, default to highlighting âtodayâ. 92 else { 93 calendar. 94 set( 'select', null ). 95 set( 'highlight', calendar.item.now ) 96 } 97 98 99 // The keycode to movement mapping. 100 calendar.key = { 101 40: 7, // Down 102 38: -7, // Up 103 39: function() { return isRTL() ? -1 : 1 }, // Right 104 37: function() { return isRTL() ? 1 : -1 }, // Left 105 go: function( timeChange ) { 106 var highlightedObject = calendar.item.highlight, 107 targetDate = new Date( highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange ) 108 calendar.set( 109 'highlight', 110 targetDate, 111 { interval: timeChange } 112 ) 113 this.render() 114 } 115 } 116 117 118 // Bind some picker events. 119 picker. 120 on( 'render', function() { 121 picker.$root.find( '.' + settings.klass.selectMonth ).on( 'change', function() { 122 var value = this.value 123 if ( value ) { 124 picker.set( 'highlight', [ picker.get( 'view' ).year, value, picker.get( 'highlight' ).date ] ) 125 picker.$root.find( '.' + settings.klass.selectMonth ).trigger( 'focus' ) 126 } 127 }) 128 picker.$root.find( '.' + settings.klass.selectYear ).on( 'change', function() { 129 var value = this.value 130 if ( value ) { 131 picker.set( 'highlight', [ value, picker.get( 'view' ).month, picker.get( 'highlight' ).date ] ) 132 picker.$root.find( '.' + settings.klass.selectYear ).trigger( 'focus' ) 133 } 134 }) 135 }, 1 ). 136 on( 'open', function() { 137 var includeToday = '' 138 if ( calendar.disabled( calendar.get('now') ) ) { 139 includeToday = ':not(.' + settings.klass.buttonToday + ')' 140 } 141 picker.$root.find( 'button' + includeToday + ', select' ).attr( 'disabled', false ) 142 }, 1 ). 143 on( 'close', function() { 144 picker.$root.find( 'button, select' ).attr( 'disabled', true ) 145 }, 1 ) 146 147} //DatePicker 148 149 150/** 151 * Set a datepicker item object. 152 */ 153DatePicker.prototype.set = function( type, value, options ) { 154 155 var calendar = this, 156 calendarItem = calendar.item 157 158 // If the value is `null` just set it immediately. 159 if ( value === null ) { 160 if ( type == 'clear' ) type = 'select' 161 calendarItem[ type ] = value 162 return calendar 163 } 164 165 // Otherwise go through the queue of methods, and invoke the functions. 166 // Update this as the time unit, and set the final value as this item. 167 // * In the case of `enable`, keep the queue but set `disable` instead. 168 // And in the case of `flip`, keep the queue but set `enable` instead. 169 calendarItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = calendar.queue[ type ].split( ' ' ).map( function( method ) { 170 value = calendar[ method ]( type, value, options ) 171 return value 172 }).pop() 173 174 // Check if we need to cascade through more updates. 175 if ( type == 'select' ) { 176 calendar.set( 'highlight', calendarItem.select, options ) 177 } 178 else if ( type == 'highlight' ) { 179 calendar.set( 'view', calendarItem.highlight, options ) 180 } 181 else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) { 182 if ( calendarItem.select && calendar.disabled( calendarItem.select ) ) { 183 calendar.set( 'select', calendarItem.select, options ) 184 } 185 if ( calendarItem.highlight && calendar.disabled( calendarItem.highlight ) ) { 186 calendar.set( 'highlight', calendarItem.highlight, options ) 187 } 188 } 189 190 return calendar 191} //DatePicker.prototype.set 192 193 194/** 195 * Get a datepicker item object. 196 */ 197DatePicker.prototype.get = function( type ) { 198 return this.item[ type ] 199} //DatePicker.prototype.get 200 201 202/** 203 * Create a picker date object. 204 */ 205DatePicker.prototype.create = function( type, value, options ) { 206 207 var isInfiniteValue, 208 calendar = this 209 210 // If thereâs no value, use the type as the value. 211 value = value === undefined ? type : value 212 213 214 // If itâs infinity, update the value. 215 if ( value == -Infinity || value == Infinity ) { 216 isInfiniteValue = value 217 } 218 219 // If itâs an object, use the native date object. 220 else if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) { 221 value = value.obj 222 } 223 224 // If itâs an array, convert it into a date and make sure 225 // that itâs a valid date â otherwise default to today. 226 else if ( $.isArray( value ) ) { 227 value = new Date( value[ 0 ], value[ 1 ], value[ 2 ] ) 228 value = _.isDate( value ) ? value : calendar.create().obj 229 } 230 231 // If itâs a number or date object, make a normalized date. 232 else if ( _.isInteger( value ) || _.isDate( value ) ) { 233 value = calendar.normalize( new Date( value ), options ) 234 } 235 236 // If itâs a literal true or any other case, set it to now. 237 else /*if ( value === true )*/ { 238 value = calendar.now( type, value, options ) 239 } 240 241 // Return the compiled object. 242 return { 243 year: isInfiniteValue || value.getFullYear(), 244 month: isInfiniteValue || value.getMonth(), 245 date: isInfiniteValue || value.getDate(), 246 day: isInfiniteValue || value.getDay(), 247 obj: isInfiniteValue || value, 248 pick: isInfiniteValue || value.getTime() 249 } 250} //DatePicker.prototype.create 251 252 253/** 254 * Create a range limit object using an array, date object, 255 * literal âtrueâ, or integer relative to another time. 256 */ 257DatePicker.prototype.createRange = function( from, to ) { 258 259 var calendar = this, 260 createDate = function( date ) { 261 if ( date === true || $.isArray( date ) || _.isDate( date ) ) { 262 return calendar.create( date ) 263 } 264 return date 265 } 266 267 // Create objects if possible. 268 if ( !_.isInteger( from ) ) { 269 from = createDate( from ) 270 } 271 if ( !_.isInteger( to ) ) { 272 to = createDate( to ) 273 } 274 275 // Create relative dates. 276 if ( _.isInteger( from ) && $.isPlainObject( to ) ) { 277 from = [ to.year, to.month, to.date + from ]; 278 } 279 else if ( _.isInteger( to ) && $.isPlainObject( from ) ) { 280 to = [ from.year, from.month, from.date + to ]; 281 } 282 283 return { 284 from: createDate( from ), 285 to: createDate( to ) 286 } 287} //DatePicker.prototype.createRange 288 289 290/** 291 * Check if a date unit falls within a date range object. 292 */ 293DatePicker.prototype.withinRange = function( range, dateUnit ) { 294 range = this.createRange(range.from, range.to) 295 return dateUnit.pick >= range.from.pick && dateUnit.pick <= range.to.pick 296} 297 298 299/** 300 * Check if two date range objects overlap. 301 */ 302DatePicker.prototype.overlapRanges = function( one, two ) { 303 304 var calendar = this 305 306 // Convert the ranges into comparable dates. 307 one = calendar.createRange( one.from, one.to ) 308 two = calendar.createRange( two.from, two.to ) 309 310 return calendar.withinRange( one, two.from ) || calendar.withinRange( one, two.to ) || 311 calendar.withinRange( two, one.from ) || calendar.withinRange( two, one.to ) 312} 313 314 315/** 316 * Get the date today. 317 */ 318DatePicker.prototype.now = function( type, value, options ) { 319 value = new Date() 320 if ( options && options.rel ) { 321 value.setDate( value.getDate() + options.rel ) 322 } 323 return this.normalize( value, options ) 324} 325 326 327/** 328 * Navigate to next/prev month. 329 */ 330DatePicker.prototype.navigate = function( type, value, options ) { 331 332 var targetDateObject, 333 targetYear, 334 targetMonth, 335 targetDate, 336 isTargetArray = $.isArray( value ), 337 isTargetObject = $.isPlainObject( value ), 338 viewsetObject = this.item.view/*, 339 safety = 100*/ 340 341 342 if ( isTargetArray || isTargetObject ) { 343 344 if ( isTargetObject ) { 345 targetYear = value.year 346 targetMonth = value.month 347 targetDate = value.date 348 } 349 else { 350 targetYear = +value[0] 351 targetMonth = +value[1] 352 targetDate = +value[2] 353 } 354 355 // If weâre navigating months but the view is in a different 356 // month, navigate to the viewâs year and month. 357 if ( options && options.nav && viewsetObject && viewsetObject.month !== targetMonth ) { 358 targetYear = viewsetObject.year 359 targetMonth = viewsetObject.month 360 } 361 362 // Figure out the expected target year and month. 363 targetDateObject = new Date( targetYear, targetMonth + ( options && options.nav ? options.nav : 0 ), 1 ) 364 targetYear = targetDateObject.getFullYear() 365 targetMonth = targetDateObject.getMonth() 366 367 // If the month weâre going to doesnât have enough days, 368 // keep decreasing the date until we reach the monthâs last date. 369 while ( /*safety &&*/ new Date( targetYear, targetMonth, targetDate ).getMonth() !== targetMonth ) { 370 targetDate -= 1 371 /*safety -= 1 372 if ( !safety ) { 373 throw 'Fell into an infinite loop while navigating to ' + new Date( targetYear, targetMonth, targetDate ) + '.' 374 }*/ 375 } 376 377 value = [ targetYear, targetMonth, targetDate ] 378 } 379 380 return value 381} //DatePicker.prototype.navigate 382 383 384/** 385 * Normalize a date by setting the hours to midnight. 386 */ 387DatePicker.prototype.normalize = function( value/*, options*/ ) { 388 value.setHours( 0, 0, 0, 0 ) 389 return value 390} 391 392 393/** 394 * Measure the range of dates. 395 */ 396DatePicker.prototype.measure = function( type, value/*, options*/ ) { 397 398 var calendar = this 399 400 // If it's an integer, get a date relative to today. 401 if ( _.isInteger( value ) ) { 402 value = calendar.now( type, value, { rel: value } ) 403 } 404 405 // If itâs anything false-y, remove the limits. 406 else if ( !value ) { 407 value = type == 'min' ? -Infinity : Infinity 408 } 409 410 // If itâs a string, parse it. 411 else if ( typeof value == 'string' ) { 412 value = calendar.parse( type, value ) 413 } 414 415 return value 416} ///DatePicker.prototype.measure 417 418 419/** 420 * Create a viewset object based on navigation. 421 */ 422DatePicker.prototype.viewset = function( type, dateObject/*, options*/ ) { 423 return this.create([ dateObject.year, dateObject.month, 1 ]) 424} 425 426 427/** 428 * Validate a date as enabled and shift if needed. 429 */ 430DatePicker.prototype.validate = function( type, dateObject, options ) { 431 432 var calendar = this, 433 434 // Keep a reference to the original date. 435 originalDateObject = dateObject, 436 437 // Make sure we have an interval. 438 interval = options && options.interval ? options.interval : 1, 439 440 // Check if the calendar enabled dates are inverted. 441 isFlippedBase = calendar.item.enable === -1, 442 443 // Check if we have any enabled dates after/before now. 444 hasEnabledBeforeTarget, hasEnabledAfterTarget, 445 446 // The min & max limits. 447 minLimitObject = calendar.item.min, 448 maxLimitObject = calendar.item.max, 449 450 // Check if weâve reached the limit during shifting. 451 reachedMin, reachedMax, 452 453 // Check if the calendar is inverted and at least one weekday is enabled. 454 hasEnabledWeekdays = isFlippedBase && calendar.item.disable.filter( function( value ) { 455 456 // If thereâs a date, check where it is relative to the target. 457 if ( $.isArray( value ) ) { 458 var dateTime = calendar.create( value ).pick 459 if ( dateTime < dateObject.pick ) hasEnabledBeforeTarget = true 460 else if ( dateTime > dateObject.pick ) hasEnabledAfterTarget = true 461 } 462 463 // Return only integers for enabled weekdays. 464 return _.isInteger( value ) 465 }).length/*, 466 467 safety = 100*/ 468 469 470 471 // Cases to validate for: 472 // [1] Not inverted and date disabled. 473 // [2] Inverted and some dates enabled. 474 // [3] Not inverted and out of range. 475 // 476 // Cases to **not** validate for: 477 // ⢠Navigating months. 478 // ⢠Not inverted and date enabled. 479 // ⢠Inverted and all dates disabled. 480 // ⢠..and anything else.
481 if ( !options || (!options.nav && !options.defaultValue) ) if ( 482 /* 1 */ ( !isFlippedBase && calendar.disabled( dateObject ) ) || 483 /* 2 */ ( isFlippedBase && calendar.disabled( dateObject ) && ( hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget ) ) || 484 /* 3 */ ( !isFlippedBase && (dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick) ) 485 ) { 486 487 488 // When inverted, flip the direction if there arenât any enabled weekdays 489 // and there are no enabled dates in the direction of the interval. 490 if ( isFlippedBase && !hasEnabledWeekdays && ( ( !hasEnabledAfterTarget && interval > 0 ) || ( !hasEnabledBeforeTarget && interval < 0 ) ) ) { 491 interval *= -1 492 } 493 494 495 // Keep looping until we reach an enabled date. 496 while ( /*safety &&*/ calendar.disabled( dateObject ) ) { 497 498 /*safety -= 1 499 if ( !safety ) { 500 throw 'Fell into an infinite loop while validating ' + dateObject.obj + '.' 501 }*/ 502 503 504 // If weâve looped into the next/prev month with a large interval, return to the original date and flatten the interval. 505 if ( Math.abs( interval ) > 1 && ( dateObject.month < originalDateObject.month || dateObject.month > originalDateObject.month ) ) { 506 dateObject = originalDateObject 507 interval = interval > 0 ? 1 : -1 508 } 509 510 511 // If weâve reached the min/max limit, reverse the direction, flatten the interval and set it to the limit. 512 if ( dateObject.pick <= minLimitObject.pick ) { 513 reachedMin = true 514 interval = 1 515 dateObject = calendar.create([ 516 minLimitObject.year, 517 minLimitObject.month, 518 minLimitObject.date + (dateObject.pick === minLimitObject.pick ? 0 : -1) 519 ]) 520 } 521 else if ( dateObject.pick >= maxLimitObject.pick ) { 522 reachedMax = true 523 interval = -1 524 dateObject = calendar.create([ 525 maxLimitObject.year, 526 maxLimitObject.month, 527 maxLimitObject.date + (dateObject.pick === maxLimitObject.pick ? 0 : 1) 528 ]) 529 } 530 531 532 // If weâve reached both limits, just break out of the loop. 533 if ( reachedMin && reachedMax ) { 534 break 535 } 536 537 538 // Finally, create the shifted date using the interval and keep looping. 539 dateObject = calendar.create([ dateObject.year, dateObject.month, dateObject.date + interval ]) 540 } 541 542 } //endif 543 544 545 // Return the date object settled on. 546 return dateObject 547} //DatePicker.prototype.validate 548 549 550/** 551 * Check if a date is disabled. 552 */ 553DatePicker.prototype.disabled = function( dateToVerify ) { 554 555 var 556 calendar = this, 557 558 // Filter through the disabled dates to check if this is one. 559 isDisabledMatch = calendar.item.disable.filter( function( dateToDisable ) { 560 561 // If the date is a number, match the weekday with 0index and `firstDay` check. 562 if ( _.isInteger( dateToDisable ) ) { 563 return dateToVerify.day === ( calendar.settings.firstDay ? dateToDisable : dateToDisable - 1 ) % 7 564 } 565 566 // If itâs an array or a native JS date, create and match the exact date. 567 if ( $.isArray( dateToDisable ) || _.isDate( dateToDisable ) ) { 568 return dateToVerify.pick === calendar.create( dateToDisable ).pick 569 } 570 571 // If itâs an object, match a date within the âfromâ and âtoâ range. 572 if ( $.isPlainObject( dateToDisable ) ) { 573 return calendar.withinRange( dateToDisable, dateToVerify ) 574 } 575 }) 576 577 // If this date matches a disabled date, confirm itâs not inverted. 578 isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( dateToDisable ) { 579 return $.isArray( dateToDisable ) && dateToDisable[3] == 'inverted' || 580 $.isPlainObject( dateToDisable ) && dateToDisable.inverted 581 }).length 582 583 // Check the calendar âenabledâ flag and respectively flip the 584 // disabled state. Then also check if itâs beyond the min/max limits. 585 return calendar.item.enable === -1 ? !isDisabledMatch : isDisabledMatch || 586 dateToVerify.pick < calendar.item.min.pick || 587 dateToVerify.pick > calendar.item.max.pick 588 589} //DatePicker.prototype.disabled 590 591 592/** 593 * Parse a string into a usable type. 594 */ 595DatePicker.prototype.parse = function( type, value, options ) { 596 597 var calendar = this, 598 parsingObject = {} 599 600 // If itâs already parsed, weâre good. 601 if ( !value || typeof value != 'string' ) { 602 return value 603 } 604 605 // We need a `.format` to parse the value with. 606 if ( !( options && options.format ) ) { 607 options = options || {} 608 options.format = calendar.settings.format 609 } 610 611 // Convert the format into an array and then map through it. 612 calendar.formats.toArray( options.format ).map( function( label ) { 613 614 var 615 // Grab the formatting label. 616 formattingLabel = calendar.formats[ label ], 617 618 // The format length is from the formatting label function or the 619 // label length without the escaping exclamation (!) mark. 620 formatLength = formattingLabel ? _.trigger( formattingLabel, calendar, [ value, parsingObject ] ) : label.replace( /^!/, '' ).length 621 622 // If there's a format label, split the value up to the format length. 623 // Then add it to the parsing object with appropriate label. 624 if ( formattingLabel ) { 625 parsingObject[ label ] = value.substr( 0, formatLength ) 626 } 627 628 // Update the value as the substring from format length to end. 629 value = value.substr( formatLength ) 630 }) 631 632 // Compensate for month 0index. 633 return [ 634 parsingObject.yyyy || parsingObject.yy, 635 +( parsingObject.mm || parsingObject.m ) - 1, 636 parsingObject.dd || parsingObject.d 637 ] 638} //DatePicker.prototype.parse 639 640 641/** 642 * Various formats to display the object in. 643 */ 644DatePicker.prototype.formats = (function() { 645 646 // Return the length of the first word in a collection. 647 function getWordLengthFromCollection( string, collection, dateObject ) { 648 649 // Grab the first word from the string. 650 // Regex pattern from http://stackoverflow.com/q/150033 651 var word = string.match( /[^\x00-\x7F]+|\w+/ )[ 0 ] 652 653 // If there's no month index, add it to the date object 654 if ( !dateObject.mm && !dateObject.m ) { 655 dateObject.m = collection.indexOf( word ) + 1 656 } 657 658 // Return the length of the word. 659 return word.length 660 } 661 662 // Get the length of the first word in a string. 663 function getFirstWordLength( string ) { 664 return string.match( /\w+/ )[ 0 ].length 665 } 666 667 return { 668 669 d: function( string, dateObject ) { 670 671 // If there's string, then get the digits length. 672 // Otherwise return the selected date. 673 return string ? _.digits( string ) : dateObject.date 674 }, 675 dd: function( string, dateObject ) { 676 677 // If there's a string, then the length is always 2. 678 // Otherwise return the selected date with a leading zero. 679 return string ? 2 : _.lead( dateObject.date ) 680 }, 681 ddd: function( string, dateObject ) { 682 683 // If there's a string, then get the length of the first word. 684 // Otherwise return the short selected weekday. 685 return string ? getFirstWordLength( string ) : this.settings.weekdaysShort[ dateObject.day ] 686 }, 687 dddd: function( string, dateObject ) { 688 689 // If there's a string, then get the length of the first word. 690 // Otherwise return the full selected weekday. 691 return string ? getFirstWordLength( string ) : this.settings.weekdaysFull[ dateObject.day ] 692 }, 693 m: function( string, dateObject ) { 694 695 // If there's a string, then get the length of the digits 696 // Otherwise return the selected month with 0index compensation. 697 return string ? _.digits( string ) : dateObject.month + 1 698 }, 699 mm: function( string, dateObject ) { 700 701 // If there's a string, then the length is always 2. 702 // Otherwise return the selected month with 0index and leading zero. 703 return string ? 2 : _.lead( dateObject.month + 1 ) 704 }, 705 mmm: function( string, dateObject ) { 706 707 var collection = this.settings.monthsShort 708 709 // If there's a string, get length of the relevant month from the short 710 // months collection. Otherwise return the selected month from that c
710ollection. 711 return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] 712 }, 713 mmmm: function( string, dateObject ) { 714 715 var collection = this.settings.monthsFull 716 717 // If there's a string, get length of the relevant month from the full 718 // months collection. Otherwise return the selected month from that collection. 719 return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] 720 }, 721 yy: function( string, dateObject ) { 722 723 // If there's a string, then the length is always 2. 724 // Otherwise return the selected year by slicing out the first 2 digits. 725 return string ? 2 : ( '' + dateObject.year ).slice( 2 ) 726 }, 727 yyyy: function( string, dateObject ) { 728 729 // If there's a string, then the length is always 4. 730 // Otherwise return the selected year. 731 return string ? 4 : dateObject.year 732 }, 733 734 // Create an array by splitting the formatting string passed. 735 toArray: function( formatString ) { return formatString.split( /(d{1,4}|m{1,4}|y{4}|yy|!.)/g ) }, 736 737 // Format an object into a string using the formatting options. 738 toString: function ( formatString, itemObject ) { 739 var calendar = this 740 return calendar.formats.toArray( formatString ).map( function( label ) { 741 return _.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' ) 742 }).join( '' ) 743 } 744 } 745})() //DatePicker.prototype.formats 746 747 748 749 750/** 751 * Check if two date units are the exact. 752 */ 753DatePicker.prototype.isDateExact = function( one, two ) { 754 755 var calendar = this 756 757 // When weâre working with weekdays, do a direct comparison. 758 if ( 759 ( _.isInteger( one ) && _.isInteger( two ) ) || 760 ( typeof one == 'boolean' && typeof two == 'boolean' ) 761 ) { 762 return one === two 763 } 764 765 // When weâre working with date representations, compare the âpickâ value. 766 if ( 767 ( _.isDate( one ) || $.isArray( one ) ) && 768 ( _.isDate( two ) || $.isArray( two ) ) 769 ) { 770 return calendar.create( one ).pick === calendar.create( two ).pick 771 } 772 773 // When weâre working with range objects, compare the âfromâ and âtoâ. 774 if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) { 775 return calendar.isDateExact( one.from, two.from ) && calendar.isDateExact( one.to, two.to ) 776 } 777 778 return false 779} 780 781 782/** 783 * Check if two date units overlap. 784 */ 785DatePicker.prototype.isDateOverlap = function( one, two ) { 786 787 var calendar = this, 788 firstDay = calendar.settings.firstDay ? 1 : 0 789 790 // When weâre working with a weekday index, compare the days. 791 if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) { 792 one = one % 7 + firstDay 793 return one === calendar.create( two ).day + 1 794 } 795 if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) { 796 two = two % 7 + firstDay 797 return two === calendar.create( one ).day + 1 798 } 799 800 // When weâre working with range objects, check if the ranges overlap. 801 if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) { 802 return calendar.overlapRanges( one, two ) 803 } 804 805 return false 806} 807 808 809/** 810 * Flip the âenabledâ state. 811 */ 812DatePicker.prototype.flipEnable = function(val) { 813 var itemObject = this.item 814 itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1) 815} 816 817 818/** 819 * Mark a collection of dates as âdisabledâ. 820 */ 821DatePicker.prototype.deactivate = function( type, datesToDisable ) { 822 823 var calendar = this, 824 disabledItems = calendar.item.disable.slice(0) 825 826 827 // If weâre flipping, thatâs all we need to do. 828 if ( datesToDisable == 'flip' ) { 829 calendar.flipEnable() 830 } 831 832 else if ( datesToDisable === false ) { 833 calendar.flipEnable(1) 834 disabledItems = [] 835 } 836 837 else if ( datesToDisable === true ) { 838 calendar.flipEnable(-1) 839 disabledItems = [] 840 } 841 842 // Otherwise go through the dates to disable. 843 else { 844 845 datesToDisable.map(function( unitToDisable ) { 846 847 var matchFound 848 849 // When we have disabled items, check for matches. 850 // If something is matched, immediately break out. 851 for ( var index = 0; index < disabledItems.length; index += 1 ) { 852 if ( calendar.isDateExact( unitToDisable, disabledItems[index] ) ) { 853 matchFound = true 854 break 855 } 856 } 857 858 // If nothing was found, add the validated unit to the collection. 859 if ( !matchFound ) { 860 if ( 861 _.isInteger( unitToDisable ) || 862 _.isDate( unitToDisable ) || 863 $.isArray( unitToDisable ) || 864 ( $.isPlainObject( unitToDisable ) && unitToDisable.from && unitToDisable.to ) 865 ) { 866 disabledItems.push( unitToDisable ) 867 } 868 } 869 }) 870 } 871 872 // Return the updated collection. 873 return disabledItems 874} //DatePicker.prototype.deactivate 875 876 877/** 878 * Mark a collection of dates as âenabledâ. 879 */ 880DatePicker.prototype.activate = function( type, datesToEnable ) { 881 882 var calendar = this, 883 disabledItems = calendar.item.disable, 884 disabledItemsCount = disabledItems.length 885 886 // If weâre flipping, thatâs all we need to do. 887 if ( datesToEnable == 'flip' ) { 888 calendar.flipEnable() 889 } 890 891 else if ( datesToEnable === true ) { 892 calendar.flipEnable(1) 893 disabledItems = [] 894 } 895 896 else if ( datesToEnable === false ) { 897 calendar.flipEnable(-1) 898 disabledItems = [] 899 } 900 901 // Otherwise go through the disabled dates. 902 else { 903 904 datesToEnable.map(function( unitToEnable ) { 905 906 var matchFound, 907 disabledUnit, 908 index, 909 isExactRange 910 911 // Go through the disabled items and try to find a match. 912 for ( index = 0; index < disabledItemsCount; index += 1 ) { 913 914 disabledUnit = disabledItems[index] 915 916 // When an exact match is found, remove it from the collection. 917 if ( calendar.isDateExact( disabledUnit, unitToEnable ) ) { 918 matchFound = disabledItems[index] = null 919 isExactRange = true 920 break 921 } 922 923 // When an overlapped match is found, add the âinvertedâ state to it. 924 else if ( calendar.isDateOverlap( disabledUnit, unitToEnable ) ) { 925 if ( $.isPlainObject( unitToEnable ) ) { 926 unitToEnable.inverted = true 927 matchFound = unitToEnable 928 } 929 else if ( $.isArray( unitToEnable ) ) { 930 matchFound = unitToEnable 931 if ( !matchFound[3] ) matchFound.push( '
931inverted' ) 932 } 933 else if ( _.isDate( unitToEnable ) ) { 934 matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ] 935 } 936 break 937 } 938 } 939 940 // If a match was found, remove a previous duplicate entry. 941 if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) { 942 if ( calendar.isDateExact( disabledItems[index], unitToEnable ) ) { 943 disabledItems[index] = null 944 break 945 } 946 } 947 948 // In the event that weâre dealing with an exact range of dates, 949 // make sure there are no âinvertedâ dates because of it. 950 if ( isExactRange ) for ( index = 0; index < disabledItemsCount; index += 1 ) { 951 if ( calendar.isDateOverlap( disabledItems[index], unitToEnable ) ) { 952 disabledItems[index] = null 953 break 954 } 955 } 956 957 // If something is still matched, add it into the collection. 958 if ( matchFound ) { 959 disabledItems.push( matchFound ) 960 } 961 }) 962 } 963 964 // Return the updated collection. 965 return disabledItems.filter(function( val ) { return val != null }) 966} //DatePicker.prototype.activate 967 968 969/** 970 * Create a string for the nodes in the picker. 971 */ 972DatePicker.prototype.nodes = function( isOpen ) { 973 974 var 975 calendar = this, 976 settings = calendar.settings, 977 calendarItem = calendar.item, 978 nowObject = calendarItem.now, 979 selectedObject = calendarItem.select, 980 highlightedObject = calendarItem.highlight, 981 viewsetObject = calendarItem.view, 982 disabledCollection = calendarItem.disable, 983 minLimitObject = calendarItem.min, 984 maxLimitObject = calendarItem.max, 985 986 987 // Create the calendar table head using a copy of weekday labels collection. 988 // * We do a copy so we don't mutate the original array. 989 tableHead = (function( collection, fullCollection ) { 990 991 // If the first day should be Monday, move Sunday to the end. 992 if ( settings.firstDay ) { 993 collection.push( collection.shift() ) 994 fullCollection.push( fullCollection.shift() ) 995 } 996 997 // Create and return the table head group. 998 return _.node( 999 'thead', 1000 _.node( 1001 'tr', 1002 _.group({ 1003 min: 0, 1004 max: DAYS_IN_WEEK - 1, 1005 i: 1, 1006 node: 'th', 1007 item: function( counter ) { 1008 return [ 1009 collection[ counter ], 1010 settings.klass.weekdays, 1011 'scope=col title="' + fullCollection[ counter ] + '"' 1012 ] 1013 } 1014 }) 1015 ) 1016 ) //endreturn 1017 })( ( settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysShort ).slice( 0 ), settings.weekdaysFull.slice( 0 ) ), //tableHead 1018 1019 1020 // Create the nav for next/prev month. 1021 createMonthNav = function( next ) { 1022 1023 // Otherwise, return the created month tag. 1024 return _.node( 1025 'div', 1026 ' ', 1027 settings.klass[ 'nav' + ( next ? 'Next' : 'Prev' ) ] + ( 1028 1029 // If the focused month is outside the range, disabled the button. 1030 ( next && viewsetObject.year >= maxLimitObject.year && viewsetObject.month >= maxLimitObject.month ) || 1031 ( !next && viewsetObject.year <= minLimitObject.year && viewsetObject.month <= minLimitObject.month ) ? 1032 ' ' + settings.klass.navDisabled : '' 1033 ), 1034 'data-nav=' + ( next || -1 ) + ' ' + 1035 _.ariaAttr({ 1036 role: 'button', 1037 controls: calendar.$node[0].id + '_table' 1038 }) + ' ' + 1039 'title="' + (next ? settings.labelMonthNext : settings.labelMonthPrev ) + '"' 1040 ) //endreturn 1041 }, //createMonthNav 1042 1043 1044 // Create the month label. 1045 createMonthLabel = function() { 1046 1047 var monthsCollection = settings.showMonthsShort ? settings.monthsShort : settings.monthsFull 1048 1049 // If there are months to select, add a dropdown menu. 1050 if ( settings.selectMonths ) { 1051
1052 return _.node( 'select', 1053 _.group({ 1054 min: 0, 1055 max: 11, 1056 i: 1, 1057 node: 'option', 1058 item: function( loopedMonth ) { 1059 1060 return [ 1061 1062 // The looped month and no classes. 1063 monthsCollection[ loopedMonth ], 0, 1064 1065 // Set the value and selected index. 1066 'value=' + loopedMonth + 1067 ( viewsetObject.month == loopedMonth ? ' selected' : '' ) + 1068 ( 1069 ( 1070 ( viewsetObject.year == minLimitObject.year && loopedMonth < minLimitObject.month ) || 1071 ( viewsetObject.year == maxLimitObject.year && loopedMonth > maxLimitObject.month ) 1072 ) ? 1073 ' disabled' : '' 1074 ) 1075 ] 1076 } 1077 }), 1078 settings.klass.selectMonth, 1079 ( isOpen ? '' : 'disabled' ) + ' ' + 1080 _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + 1081 'title="' + settings.labelMonthSelect + '"' 1082 ) 1083 } 1084 1085 // If there's a need for a month selector 1086 return _.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month ) 1087 }, //createMonthLabel 1088 1089 1090 // Create the year label. 1091 createYearLabel = function() { 1092 1093 var focusedYear = viewsetObject.year, 1094 1095 // If years selector is set to a literal "true", set it to 5. Otherwise 1096 // divide in half to get half before and half after focused year. 1097 numberYears = settings.selectYears === true ? 5 : ~~( settings.selectYears / 2 ) 1098 1099 // If there are years to select, add a dropdown menu. 1100 if ( numberYears ) { 1101 1102 var 1103 minYear = minLimitObject.year, 1104 maxYear = maxLimitObject.year, 1105 lowestYear = focusedYear - numberYears, 1106 highestYear = focusedYear + numberYears 1107 1108 // If the min year is greater than the lowest year, increase the highest year 1109 // by the difference and set the lowest year to the min year. 1110 if ( minYear > lowestYear ) { 1111 highestYear += minYear - lowestYear 1112 lowestYear = minYear 1113 } 1114 1115 // If the max year is less than the highest year, decrease the lowest year 1116 // by the lower of the two: available and needed years. Then set the 1117 // highest year to the max year. 1118 if ( maxYear < highestYear ) { 1119 1120 var availableYears = lowestYear - minYear, 1121 neededYears = highestYear - maxYear 1122 1123 lowestYear -= availableYears > neededYears ? neededYears : availableYears 1124 highestYear = maxYear 1125 } 1126 1127 return _.node( 'select', 1128 _.group({ 1129 min: lowestYear, 1130 max: highestYear, 1131 i: 1, 1132 node: 'option', 1133 item: function( loopedYear ) { 1134 return [ 1135 1136 // The looped year and no classes. 1137 loopedYear, 0, 1138 1139 // Set the value and selected index. 1140 'value=' + loopedYear + ( focusedYear == loopedYear ? ' selected' : '' ) 1141 ] 1142 } 1143 }), 1144 settings.klass.selectYear, 1145 ( isOpen ? '' : 'disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + 1146 'title="' + settings.labelYearSelect + '"' 1147 ) 1148 } 1149 1150 // Otherwise just return the year focused
1151 return _.node( 'div', focusedYear, settings.klass.year ) 1152 } //createYearLabel 1153 1154 1155 // Create and return the entire calendar. 1156 return _.node( 1157 'div', 1158 ( settings.selectYears ? createYearLabel() + createMonthLabel() : createMonthLabel() + createYearLabel() ) + 1159 createMonthNav() + createMonthNav( 1 ), 1160 settings.klass.header 1161 ) + _.node( 1162 'table', 1163 tableHead + 1164 _.node( 1165 'tbody', 1166 _.group({ 1167 min: 0, 1168 max: WEEKS_IN_CALENDAR - 1, 1169 i: 1, 1170 node: 'tr', 1171 item: function( rowCounter ) { 1172 1173 // If Monday is the first day and the month starts on Sunday, shift the date back a week. 1174 var shiftDateBy = settings.firstDay && calendar.create([ viewsetObject.year, viewsetObject.month, 1 ]).day === 0 ? -7 : 0 1175 1176 return [ 1177 _.group({ 1178 min: DAYS_IN_WEEK * rowCounter - viewsetObject.day + shiftDateBy + 1, // Add 1 for weekday 0index 1179 max: function() { 1180 return this.min + DAYS_IN_WEEK - 1 1181 }, 1182 i: 1, 1183 node: 'td', 1184 item: function( targetDate ) { 1185 1186 // Convert the time date from a relative date to a target date. 1187 targetDate = calendar.create([ viewsetObject.year, viewsetObject.month, targetDate + ( settings.firstDay ? 1 : 0 ) ]) 1188 1189 var isSelected = selectedObject && selectedObject.pick == targetDate.pick, 1190 isHighlighted = highlightedObject && highlightedObject.pick == targetDate.pick, 1191 isDisabled = disabledCollection && calendar.disabled( targetDate ) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick, 1192 formattedDate = _.trigger( calendar.formats.toString, calendar, [ settings.format, targetDate ] ) 1193 1194 return [ 1195 _.node( 1196 'div', 1197 targetDate.date, 1198 (function( klasses ) { 1199 1200 // Add the `infocus` or `outfocus` classes based on month in view. 1201 klasses.push( viewsetObject.month == targetDate.month ? settings.klass.infocus : settings.klass.outfocus ) 1202 1203 // Add the `today` class if needed. 1204 if ( nowObject.pick == targetDate.pick ) { 1205 klasses.push( settings.klass.now ) 1206 } 1207 1208 // Add the `selected` class if something's selected and the time matches. 1209 if ( isSelected ) { 1210 klasses.push( settings.klass.selected ) 1211 } 1212 1213 // Add the `highlighted` class if something's highlighted and the time matches. 1214 if ( isHighlighted ) { 1215 klasses.push( settings.klass.highlighted ) 1216 } 1217 1218 // Add the `disabled` class if something's disabled and the object matches. 1219 if ( isDisabled ) { 1220 klasses.push( settings.klass.disabled ) 1221 } 1222 1223 return klasses.join( ' ' ) 1224 })([ settings.klass.day ]), 1225 'data-pick=' + targetDate.pick + ' ' + _.ariaAttr({ 1226 role: 'gridcell', 1227 label: formattedDate, 1228 selected: isSelected && calendar.$node.val() === formattedDate ? true : null, 1229 activedescendant: isHighlighted ? true : null, 1230 disabled: isDisabled ? true : null 1231 }) 1232 ), 1233 '', 1234 _.ariaAttr({ role: 'presentation' }) 1235 ] //endreturn 1236 } 1237 }) 1238 ] //endreturn 1239 } 1240 }) 1241 ), 1242 settings.klass.table, 1243 'id="' + calendar.$node[0].id + '_table' + '" ' + _.ariaAttr({ 1244 role: 'grid', 1245 controls: calendar.$node[0].id, 1246 readonly: true 1247 }) 1248 ) + 1249 1250 // * For Firefox forms to submit, make sure to set the buttonsâ `type` attributes as âbuttonâ. 1251 _.node( 1252 'div', 1253 _.node( 'button', settings.today, settings.klass.buttonToday,
1254 'type=button data-pick=' + nowObject.pick + 1255 ( isOpen && !calendar.disabled(nowObject) ? '' : ' disabled' ) + ' ' + 1256 _.ariaAttr({ controls: calendar.$node[0].id }) ) + 1257 _.node( 'button', settings.clear, settings.klass.buttonClear, 1258 'type=button data-clear=1' + 1259 ( isOpen ? '' : ' disabled' ) + ' ' + 1260 _.ariaAttr({ controls: calendar.$node[0].id }) ) + 1261 _.node('button', settings.close, settings.klass.buttonClose, 1262 'type=button data-close=true ' + 1263 ( isOpen ? '' : ' disabled' ) + ' ' + 1264 _.ariaAttr({ controls: calendar.$node[0].id }) ), 1265 settings.klass.footer 1266 ) //endreturn 1267} //DatePicker.prototype.nodes 1268 1269 1270 1271 1272/** 1273 * The date picker defaults. 1274 */ 1275DatePicker.defaults = (function( prefix ) { 1276 1277 return { 1278 1279 // The title label to use for the month nav buttons 1280 labelMonthNext: 'Next month', 1281 labelMonthPrev: 'Previous month', 1282 1283 // The title label to use for the dropdown selectors 1284 labelMonthSelect: 'Select a month', 1285 labelYearSelect: 'Select a year', 1286 1287 // Months and weekdays 1288 monthsFull: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], 1289 monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], 1290 weekdaysFull: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], 1291 weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], 1292 1293 // Today and clear 1294 today: 'Today', 1295 clear: 'Clear', 1296 close: 'Close', 1297 1298 // Picker close behavior 1299 closeOnSelect: true, 1300 closeOnClear: true, 1301 1302 // Update input value on select/clear 1303 updateInput: true, 1304 1305 // The format to show on the `input` element 1306 format: 'd mmmm, yyyy', 1307 1308 // Classes 1309 klass: { 1310 1311 table: prefix + 'table', 1312 1313 header: prefix + 'header', 1314 1315 navPrev: prefix + 'nav--prev', 1316 navNext: prefix + 'nav--next', 1317 navDisabled: prefix + 'nav--disabled', 1318 1319 month: prefix + 'month', 1320 year: prefix + 'year', 1321 1322 selectMonth: prefix + 'select--month', 1323 selectYear: prefix + 'select--year', 1324 1325 weekdays: prefix + 'weekday', 1326 1327 day: prefix + 'day', 1328 disabled: prefix + 'day--disabled', 1329 selected: prefix + 'day--selected', 1330 highlighted: prefix + 'day--highlighted', 1331 now: prefix + 'day--today', 1332 infocus: prefix + 'day--infocus', 1333 outfocus: prefix + 'day--outfocus', 1334 1335 footer: prefix + 'footer', 1336 1337 buttonClear: prefix + 'button--clear', 1338 buttonToday: prefix + 'button--today', 1339 buttonClose: prefix + 'button--close' 1340 } 1341 } 1342})( Picker.klasses().picker + '__' ) 1343 1344 1345 1346 1347 1348/** 1349 * Extend the picker to add the date picker. 1350 */ 1351Picker.extend( 'pickadate', DatePicker ) 1352 1353 1354})); 1355 1356 1357
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.