1/*! 2 * Timepicker Component for Twitter Bootstrap 3 * 4 * Copyright 2013 Joris de Wit and bootstrap-timepicker contributors 5 * 6 * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11(function($, window, document) { 12 'use strict'; 13 14 // TIMEPICKER PUBLIC CLASS DEFINITION 15 var Timepicker = function(element, options) { 16 this.widget = ''; 17 this.$element = $(element); 18 this.defaultTime = options.defaultTime; 19 this.disableFocus = options.disableFocus; 20 this.disableMousewheel = options.disableMousewheel; 21 this.isOpen = options.isOpen; 22 this.minuteStep = options.minuteStep; 23 this.modalBackdrop = options.modalBackdrop; 24 this.orientation = options.orientation; 25 this.secondStep = options.secondStep; 26 this.snapToStep = options.snapToStep; 27 this.showInputs = options.showInputs; 28 this.showMeridian = options.showMeridian; 29 this.showSeconds = options.showSeconds; 30 this.template = options.template; 31 this.appendWidgetTo = options.appendWidgetTo; 32 this.showWidgetOnAddonClick = options.showWidgetOnAddonClick; 33 this.icons = options.icons; 34 this.maxHours = options.maxHours; 35 this.explicitMode = options.explicitMode; // If true 123 = 1:23, 12345 = 1:23:45, else invalid. 36 37 this.handleDocumentClick = function (e) { 38 var self = e.data.scope; 39 // This condition was inspired by bootstrap-datepicker. 40 // The element the timepicker is invoked on is the input but it has a sibling for addon/button. 41 if (!(self.$element.parent().find(e.target).length || 42 self.$widget.is(e.target) || 43 self.$widget.find(e.target).length)) { 44 self.hideWidget(); 45 } 46 }; 47 48 this._init(); 49 }; 50 51 Timepicker.prototype = { 52 53 constructor: Timepicker, 54 _init: function() { 55 var self = this; 56 57 if (this.showWidgetOnAddonClick && (this.$element.parent().hasClass('input-group') && this.$element.parent().hasClass('bootstrap-timepicker'))) { 58 this.$element.parent('.input-group.bootstrap-timepicker').find('.input-group-addon').on({ 59 'click.timepicker': $.proxy(this.showWidget, this) 60 }); 61 this.$element.on({ 62 'focus.timepicker': $.proxy(this.highlightUnit, this), 63 'click.timepicker': $.proxy(this.highlightUnit, this), 64 'keydown.timepicker': $.proxy(this.elementKeydown, this), 65 'blur.timepicker': $.proxy(this.blurElement, this), 66 'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this) 67 }); 68 } else { 69 if (this.template) { 70 this.$element.on({ 71 'focus.timepicker': $.proxy(this.showWidget, this), 72 'click.timepicker': $.proxy(this.showWidget, this), 73 'blur.timepicker': $.proxy(this.blurElement, this), 74 'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this) 75 }); 76 } else { 77 this.$element.on({ 78 'focus.timepicker': $.proxy(this.highlightUnit, this), 79 'click.timepicker': $.proxy(this.highlightUnit, this), 80 'keydown.timepicker': $.proxy(this.elementKeydown, this), 81 'blur.timepicker': $.proxy(this.blurElement, this), 82 'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this) 83 }); 84 } 85 } 86 87 if (this.template !== false) { 88 this.$widget = $(this.getTemplate()).on('click', $.proxy(this.widgetClick, this)); 89 } else { 90 this.$widget = false; 91 } 92 93 if (this.showInputs && this.$widget !== false) { 94 this.$widget.find('input').each(function() { 95 $(this).on({ 96 'click.timepicker': function() { $(this).select(); }, 97 'keydown.timepicker': $.proxy(self.widgetKeydown, self), 98 'keyup.timepicker': $.proxy(self.widgetKeyup, self) 99 }); 100 }); 101 } 102 103 this.setDefaultTime(this.defaultTime); 104 }, 105 106 blurElement: function() { 107 this.highlightedUnit = null; 108 this.updateFromElementVal(); 109 }, 110 111 clear: function() { 112 this.hour = ''; 113 this.minute = ''; 114 this.second = ''; 115 this.meridian = ''; 116 117 this.$element.val(''); 118 }, 119 120 decrementHour: function() { 121 if (this.showMeridian) { 122 if (this.hour === 1) { 123 this.hour = 12; 124 } else if (this.hour === 12) { 125 this.hour--; 126 127 return this.toggleMeridian(); 128 } else if (this.hour === 0) { 129 this.hour = 11; 130 131 return this.toggleMeridian(); 132 } else { 133 this.hour--; 134 } 135 } else { 136 if (this.hour <= 0) { 137 this.hour = this.maxHours - 1; 138 } else { 139 this.hour--; 140 } 141 } 142 }, 143 144 decrementMinute: function(step) { 145 var newVal; 146 147 if (step) { 148 newVal = this.minute - step; 149 } else { 150 newVal = this.minute - this.minuteStep; 151 } 152 153 if (newVal < 0) { 154 this.decrementHour(); 155 this.minute = newVal + 60; 156 } else { 157 this.minute = newVal; 158 } 159 }, 160 161 decrementSecond: function() { 162 var newVal = this.second - this.secondStep; 163 164 if (newVal < 0) { 165 this.decrementMinute(true); 166 this.second = newVal + 60; 167 } else { 168 this.second = newVal; 169 } 170 }, 171
172 elementKeydown: function(e) { 173 switch (e.which) { 174 case 9: //tab 175 if (e.shiftKey) { 176 if (this.highlightedUnit === 'hour') { 177 this.hideWidget(); 178 break; 179 } 180 this.highlightPrevUnit(); 181 } else if ((this.showMeridian && this.highlightedUnit === 'meridian') || (this.showSeconds && this.highlightedUnit === 'second') || (!this.showMeridian && !this.showSeconds && this.highlightedUnit ==='minute')) { 182 this.hideWidget(); 183 break; 184 } else { 185 this.highlightNextUnit(); 186 } 187 e.preventDefault(); 188 this.updateFromElementVal(); 189 break; 190 case 27: // escape 191 this.updateFromElementVal(); 192 break; 193 case 37: // left arrow 194 e.preventDefault(); 195 this.highlightPrevUnit(); 196 this.updateFromElementVal(); 197 break; 198 case 38: // up arrow 199 e.preventDefault(); 200 switch (this.highlightedUnit) { 201 case 'hour': 202 this.incrementHour(); 203 this.highlightHour(); 204 break; 205 case 'minute': 206 this.incrementMinute(); 207 this.highlightMinute(); 208 break; 209 case 'second': 210 this.incrementSecond(); 211 this.highlightSecond(); 212 break; 213 case 'meridian': 214 this.toggleMeridian(); 215 this.highlightMeridian(); 216 break; 217 } 218 this.update(); 219 break; 220 case 39: // right arrow 221 e.preventDefault(); 222 this.highlightNextUnit(); 223 this.updateFromElementVal(); 224 break; 225 case 40: // down arrow 226 e.preventDefault(); 227 switch (this.highlightedUnit) { 228 case 'hour': 229 this.decrementHour(); 230 this.highlightHour(); 231 break; 232 case 'minute': 233 this.decrementMinute(); 234 this.highlightMinute(); 235 break; 236 case 'second': 237 this.decrementSecond(); 238 this.highlightSecond(); 239 break; 240 case 'meridian': 241 this.toggleMeridian(); 242 this.highlightMeridian(); 243 break; 244 } 245 246 this.update(); 247 break; 248 } 249 }, 250 251 getCursorPosition: function() { 252 var input = this.$element.get(0); 253 254 if ('selectionStart' in input) {// Standard-compliant browsers 255 256 return input.selectionStart; 257 } else if (document.selection) {// IE fix 258 input.focus(); 259 var sel = document.selection.createRange(), 260 selLen = document.selection.createRange().text.length; 261 262 sel.moveStart('character', - input.value.length); 263 264 return sel.text.length - selLen; 265 } 266 }, 267 268 getTemplate: function() { 269 var template, 270 hourTemplate, 271 minuteTemplate, 272 secondTemplate, 273 meridianTemplate, 274 templateContent; 275 276 if (this.showInputs) { 277 hourTemplate = '<input type="text" class="bootstrap-timepicker-hour" maxlength="2"/>'; 278 minuteTemplate = '<input type="text" class="bootstrap-timepicker-minute" maxlength="2"/>'; 279 secondTemplate = '<input type="text" class="bootstrap-timepicker-second" maxlength="2"/>'; 280 meridianTemplate = '<input type="text" class="bootstrap-timepicker-meridian" maxlength="2"/>'; 281 } else { 282 hourTemplate = '<span class="bootstrap-timepicker-hour"></span>'; 283 minuteTemplate = '<span class="bootstrap-timepicker-minute"></span>'; 284 secondTemplate = '<span class="bootstrap-timepicker-second"></span>'; 285 meridianTemplate = '<span class="bootstrap-timepicker-meridian"></span>'; 286 } 287 288 templateContent = '<table>'+ 289 '<tr>'+ 290 '<td><a href="#" data-action="incrementHour"><span class="'+ this.icons.up +'"></span></a></td>'+ 291 '<td class="separator"> </td>'+ 292 '<td><a href="#" data-action="incrementMinute"><span class="'+ this.icons.up +'"></span></a></td>'+ 293 (this.showSeconds ? 294 '<td class="separator"> </td>'+ 295 '<td><a href="#" data-action="incrementSecond"><span class="'+ this.icons.up +'"></span></a></td>' 296 : '') + 297 (this.showMeridian ? 298 '<td class="separator"> </td>'+ 299 '<td class="meridian-column"><a href="#" data-action="toggleMeridian"><span class="'+ this.icons.up +'"></span></a></td>' 300 : '') + 301 '</tr>'+ 302 '<tr>'+ 303 '<td>'+ hourTemplate +'</td> '+ 304 '<td class="separator">:</td>'+ 305 '<td>'+ minuteTemplate +'</td> '+ 306 (this.showSeconds ? 307 '<td class="separator">:</td>'+ 308 '<td>'+ secondTemplate +'</td>' 309 : '') + 310 (this.showMeridian ? 311 '<td class="separator"> </td>'+ 312 '<td>'+ meridianTemplate +'</td>' 313 : '') + 314 '</tr>'+ 315 '<tr>'+ 316 '<td><a href="#" data-action="decrementHour"><span class="'+ this.icons.down +'"></span></a></td>'+ 317 '<td class="separator"></td>'+ 318 '<td><a href="#" data-action="decrementMinute"><span class="'+ this.icons.down +'"></span></a></td>'+ 319 (this.showSeconds ? 320 '<td class="separator"> </td>'+ 321 '<td><a href="#" data-action="decrementSecond"><span class="'+ this.icons.down +'"></span></a></td>' 322 : '') + 323 (this.showMeridian ? 324 '<td class="separator"> </td>'+ 325 '<td><a href="#" data-action="toggleMeridian"><span class="'+ this.icons.down +'"></span></a></td>' 326 : '') + 327 '</tr>'+ 328 '</table>'; 329 330 switch(this.template) { 331 case 'modal': 332 template = '<div class="bootstrap-timepicker-widget modal hide fade in" data-backdrop="'+ (this.modalBackdrop ? 'true' : 'false') +'">'+ 333 '<div class="modal-header">'+ 334 '<a href="#" class="close" data-dismiss="modal">×</a>'+ 335 '<h3>
335Pick a Time</h3>'+ 336 '</div>'+ 337 '<div class="modal-content">'+ 338 templateContent + 339 '</div>'+ 340 '<div class="modal-footer">'+ 341 '<a href="#" class="btn btn-primary" data-dismiss="modal">OK</a>'+ 342 '</div>'+ 343 '</div>'; 344 break; 345 case 'dropdown': 346 template = '<div class="bootstrap-timepicker-widget dropdown-menu">'+ templateContent +'</div>'; 347 break; 348 } 349 350 return template; 351 }, 352 353 getTime: function() { 354 if (this.hour === '') { 355 return ''; 356 } 357 358 return this.hour + ':' + (this.minute.toString().length === 1 ? '0' + this.minute : this.minute) + (this.showSeconds ? ':' + (this.second.toString().length === 1 ? '0' + this.second : this.second) : '') + (this.showMeridian ? ' ' + this.meridian : ''); 359 }, 360 361 hideWidget: function() { 362 if (this.isOpen === false) { 363 return; 364 } 365 366 this.$element.trigger({ 367 'type': 'hide.timepicker', 368 'time': { 369 'value': this.getTime(), 370 'hours': this.hour, 371 'minutes': this.minute, 372 'seconds': this.second, 373 'meridian': this.meridian 374 } 375 }); 376 377 if (this.template === 'modal' && this.$widget.modal) { 378 this.$widget.modal('hide'); 379 } else { 380 this.$widget.removeClass('open'); 381 } 382 383 $(document).off('mousedown.timepicker, touchend.timepicker', this.handleDocumentClick); 384 385 this.isOpen = false; 386 // show/hide approach taken by datepicker 387 this.$widget.detach(); 388 }, 389 390 highlightUnit: function() { 391 this.position = this.getCursorPosition(); 392 if (this.position >= 0 && this.position <= 2) { 393 this.highlightHour(); 394 } else if (this.position >= 3 && this.position <= 5) { 395 this.highlightMinute(); 396 } else if (this.position >= 6 && this.position <= 8) { 397 if (this.showSeconds) { 398 this.highlightSecond(); 399 } else { 400 this.highlightMeridian(); 401 } 402 } else if (this.position >= 9 && this.position <= 11) { 403 this.highlightMeridian(); 404 } 405 }, 406 407 highlightNextUnit: function() { 408 switch (this.highlightedUnit) { 409 case 'hour': 410 this.highlightMinute(); 411 break; 412 case 'minute': 413 if (this.showSeconds) { 414 this.highlightSecond(); 415 } else if (this.showMeridian){ 416 this.highlightMeridian(); 417 } else { 418 this.highlightHour(); 419 } 420 break; 421 case 'second': 422 if (this.showMeridian) { 423 this.highlightMeridian(); 424 } else { 425 this.highlightHour(); 426 } 427 break; 428 case 'meridian': 429 this.highlightHour(); 430 break; 431 } 432 }, 433 434 highlightPrevUnit: function() { 435 switch (this.highlightedUnit) { 436 case 'hour': 437 if(this.showMeridian){ 438 this.highlightMeridian(); 439 } else if (this.showSeconds) { 440 this.highlightSecond(); 441 } else { 442 this.highlightMinute(); 443 } 444 break; 445 case 'minute': 446 this.highlightHour(); 447 break; 448 case 'second': 449 this.highlightMinute(); 450 break; 451 case 'meridian': 452 if (this.showSeconds) { 453 this.highlightSecond(); 454 } else { 455 this.highlightMinute(); 456 } 457 break; 458 } 459 }, 460 461 highlightHour: function() { 462 var $element = this.$element.get(0), 463 self = this; 464 465 this.highlightedUnit = 'hour'; 466 467 if ($element.setSelectionRange) { 468 setTimeout(function() { 469 if (self.hour < 10) { 470 $element.setSelectionRange(0,1); 471 } else { 472 $element.setSelectionRange(0,2); 473 } 474 }, 0); 475 } 476 }, 477 478 highlightMinute: function() { 479 var $element = this.$element.get(0), 480 self = this; 481 482 this.highlightedUnit = 'minute'; 483 484 if ($element.setSelectionRange) { 485 setTimeout(function() { 486 if (self.hour < 10) { 487 $element.setSelectionRange(2,4); 488 } else { 489 $element.setSelectionRange(3,5); 490 } 491 }, 0); 492 } 493 }, 494 495 highlightSecond: function() { 496 var $element = this.$element.get(0), 497 self = this; 498 499 this.highlightedUnit = 'second'; 500 501 if ($element.setSelectionRange) { 502 setTimeout(function() { 503 if (self.hour < 10) { 504 $element.setSelectionRange(5,7); 505 } else { 506 $element.setSelectionRange(6,8); 507 } 508 }, 0); 509 } 510 }, 511 512 highlightMeridian: function() { 513 var $element = this.$element.get(0), 514 self = this; 515 516 this.highlightedUnit = 'meridian'; 517 518 if ($element.setSelectionRange) { 519 if (this.showSeconds) { 520 setTimeout(function() { 521 if (self.hour < 10) { 522 $element.setSelectionRange(8,10); 523 } else { 524 $element.setSelectionRange(9,11); 525 } 526 }, 0); 527 } else { 528 setTimeout(function() { 529 if (self.hour < 10) { 530 $element.setSelectionRange(5,7); 531 } else { 532 $element.setSelectionRange(6,8); 533 } 534 }, 0); 535 } 536 } 537 }, 538 539 incrementHour: function() { 540 if (this.showMeridian) { 541 if (this.hour === 11) { 542 this.hour++; 543 return this.toggleMeridian(); 544 } else if (this.hour === 12) { 545 this.hour = 0; 546 } 547 } 548 if (this.hour === this.maxHours - 1) { 549 this.hour = 0; 550 551 return; 552 } 553 this.hour++; 554 }, 555 556 incrementMinute: function(step) { 557 var newVal; 558 559 if (step) { 560 newVal = this.minute + step; 561 } else { 562 newVal = this.minute + this.minuteStep - (this.minute % this.minuteStep); 563 } 564 565 if (newVal > 59) { 566 this.incrementHour(); 567 this.minute = newVal - 60; 568 } else { 569 this.minute = newVal; 570 } 571 }, 572 573 incrementSecond: function() { 574 var newVal = this.second + this.secondStep - (this.second % this.secondStep); 575 576 if (newVal > 59) { 577 this.incrementMinute(true); 578 this.second = newVal - 60; 579 } else { 580 this.second = newVal; 581 } 582 }, 583 584 mousewheel: function(e) { 585 if (this.disableMousewheel) { 586 return; 587 } 588 589 e.preventDefault(); 590 e.stopPropagation(); 591 592 var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail, 593 scrollTo = null; 594 595 if (e.type === 'mousewheel') { 596 scrollTo = (e.originalEvent.wheelDelta * -1); 597 } 598 else if (e.type === 'DOMMouseScroll') { 599 scrollTo = 40 * e.originalEvent.detail; 600 } 601 602 if (scrollTo) { 603 e.preventDefault(); 604 $(this).scrollTop(scrollTo + $(this).scrollTop()); 605 } 606 607 switch (this.highlightedUnit) { 608 case 'minute': 609 if (delta > 0) { 610 this.incrementMinute(); 611 } else { 612 this.decrementMinute(); 613 } 614 this.highlightMinute(); 615 break; 616 case 'second': 617 if (delta > 0) { 618 this.incrementSecond(); 619 } else { 620 this.decrementSecond(); 621 } 622 this.highlightSecond(); 623 break; 624 case 'meridian': 625 this.toggleMeridian(); 626 this.highlightMeridian(); 627 break; 628 default: 629 if (delta > 0) { 630 this.incrementHour(); 631 } else { 632 this.decrementHour(); 633 } 634 this.highlightHour(); 635 break; 636 } 637 638 return false;
639 }, 640 641 /** 642 * Given a segment value like 43, will round and snap the segment 643 * to the nearest "step", like 45 if step is 15. Segment will 644 * "overflow" to 0 if it's larger than 59 or would otherwise 645 * round up to 60. 646 */ 647 changeToNearestStep: function (segment, step) { 648 if (segment % step === 0) { 649 return segment; 650 } 651 if (Math.round((segment % step) / step)) { 652 return (segment + (step - segment % step)) % 60; 653 } else { 654 return segment - segment % step; 655 } 656 }, 657 658 // This method was adapted from bootstrap-datepicker. 659 place : function() { 660 if (this.isInline) { 661 return; 662 } 663 var widgetWidth = this.$widget.outerWidth(), widgetHeight = this.$widget.outerHeight(), visualPadding = 10, windowWidth = 664 $(window).width(), windowHeight = $(window).height(), scrollTop = $(window).scrollTop(); 665 666 var zIndex = parseInt(this.$element.parents().filter(function() { return $(this).css('z-index') !== 'auto'; }).first().css('z-index'), 10) + 10; 667 var offset = this.component ? this.component.parent().offset() : this.$element.offset(); 668 var height = this.component ? this.component.outerHeight(true) : this.$element.outerHeight(false); 669 var width = this.component ? this.component.outerWidth(true) : this.$element.outerWidth(false); 670 var left = offset.left, top = offset.top; 671 672 this.$widget.removeClass('timepicker-orient-top timepicker-orient-bottom timepicker-orient-right timepicker-orient-left'); 673 674 if (this.orientation.x !== 'auto') { 675 this.$widget.addClass('timepicker-orient-' + this.orientation.x); 676 if (this.orientation.x === 'right') { 677 left -= widgetWidth - width; 678 } 679 } else{ 680 // auto x orientation is best-placement: if it crosses a window edge, fudge it sideways 681 // Default to left 682 this.$widget.addClass('timepicker-orient-left'); 683 if (offset.left < 0) { 684 left -= offset.left - visualPadding; 685 } else if (offset.left + widgetWidth > windowWidth) { 686 left = windowWidth - widgetWidth - visualPadding; 687 } 688 } 689 // auto y orientation is best-situation: top or bottom, no fudging, decision based on which shows more of the widget 690 var yorient = this.orientation.y, topOverflow, bottomOverflow; 691 if (yorient === 'auto') { 692 topOverflow = -scrollTop + offset.top - widgetHeight; 693 bottomOverflow = scrollTop + windowHeight - (offset.top + height + widgetHeight); 694 if (Math.max(topOverflow, bottomOverflow) === bottomOverflow) { 695 yorient = 'top'; 696 } else { 697 yorient = 'bottom'; 698 } 699 } 700 this.$widget.addClass('timepicker-orient-' + yorient); 701 if (yorient === 'top'){ 702 top += height; 703 } else{ 704 top -= widgetHeight + parseInt(this.$widget.css('padding-top'), 10); 705 } 706 707 this.$widget.css({ 708 top : top, 709 left : left, 710 zIndex : zIndex 711 }); 712 }, 713 714 remove: function() { 715 $('document').off('.timepicker'); 716 if (this.$widget) { 717 this.$widget.remove(); 718 } 719 delete this.$element.data().timepicker; 720 }, 721 722 setDefaultTime: function(defaultTime) { 723 if (!this.$element.val()) { 724 if (defaultTime === 'current') { 725 var dTime = new Date(), 726 hours = dTime.getHours(),
727 minutes = dTime.getMinutes(), 728 seconds = dTime.getSeconds(), 729 meridian = 'AM'; 730 731 if (seconds !== 0) { 732 seconds = Math.ceil(dTime.getSeconds() / this.secondStep) * this.secondStep; 733 if (seconds === 60) { 734 minutes += 1; 735 seconds = 0; 736 } 737 } 738 739 if (minutes !== 0) { 740 minutes = Math.ceil(dTime.getMinutes() / this.minuteStep) * this.minuteStep; 741 if (minutes === 60) { 742 hours += 1; 743 minutes = 0; 744 } 745 } 746 747 if (this.showMeridian) { 748 if (hours === 0) { 749 hours = 12; 750 } else if (hours >= 12) { 751 if (hours > 12) { 752 hours = hours - 12; 753 } 754 meridian = 'PM'; 755 } else { 756 meridian = 'AM'; 757 } 758 } 759 760 this.hour = hours; 761 this.minute = minutes; 762 this.second = seconds; 763 this.meridian = meridian; 764 765 this.update(); 766 767 } else if (defaultTime === false) { 768 this.hour = 0; 769 this.minute = 0; 770 this.second = 0; 771 this.meridian = 'AM'; 772 } else { 773 this.setTime(defaultTime); 774 } 775 } else { 776 this.updateFromElementVal(); 777 } 778 }, 779 780 setTime: function(time, ignoreWidget) { 781 if (!time) { 782 this.clear(); 783 return; 784 } 785 786 var timeMode, 787 timeArray, 788 hour, 789 minute, 790 second, 791 meridian; 792 793 if (typeof time === 'object' && time.getMonth){ 794 // this is a date object 795 hour = time.getHours(); 796 minute = time.getMinutes(); 797 second = time.getSeconds(); 798 799 if (this.showMeridian){ 800 meridian = 'AM'; 801 if (hour > 12){ 802 meridian = 'PM'; 803 hour = hour % 12; 804 } 805 806 if (hour === 12){ 807 meridian = 'PM'; 808 } 809 } 810 } else { 811 timeMode = ((/a/i).test(time) ? 1 : 0) + ((/p/i).test(time) ? 2 : 0); // 0 = none, 1 = AM, 2 = PM, 3 = BOTH. 812 if (timeMode > 2) { // If both are present, fail. 813 this.clear(); 814 return; 815 } 816 817 timeArray = time.replace(/[^0-9\:]/g, '').split(':'); 818 819 hour = timeArray[0] ? timeArray[0].toString() : timeArray.toString(); 820 821 if(this.explicitMode && hour.length > 2 && (hour.length % 2) !== 0 ) { 822 this.clear(); 823 return; 824 } 825 826 minute = timeArray[1] ? timeArray[1].toString() : ''; 827 second = timeArray[2] ? timeArray[2].toString() : ''; 828 829 // adaptive time parsing 830 if (hour.length > 4) { 831 second = hour.slice(-2); 832 hour = hour.slice(0, -2); 833 } 834 835 if (hour.length > 2) { 836 minute = hour.slice(-2); 837 hour = hour.slice(0, -2); 838 } 839 840 if (minute.length > 2) { 841 second = minute.slice(-2); 842 minute = minute.slice(0, -2); 843 } 844 845 hour = parseInt(hour, 10); 846 minute = parseInt(minute, 10); 847 second = parseInt(second, 10); 848 849 if (isNaN(hour)) { 850 hour = 0; 851 } 852 if (isNaN(minute)) { 853 minute = 0; 854 } 855 if (isNaN(second)) { 856 second = 0; 857 } 858 859 // Adjust the time based upon unit boundary. 860 // NOTE: Negatives will never occur due to time.replace() above. 861 if (second > 59) { 862 second = 59; 863 } 864 865 if (minute > 59) { 866 minute = 59; 867 } 868 869 if (hour >= this.maxHours) { 870 // No day/date handling. 871 hour = this.maxHours - 1; 872 } 873 874 if (this.showMeridian) { 875 if (hour > 12) { 876 // Force PM. 877 timeMode = 2; 878 hour -= 12; 879 } 880 if (!timeMode) { 881 timeMode = 1; 882 } 883 if (hour === 0) { 884 hour = 12; // AM or PM, reset to 12. 0 AM = 12 AM. 0 PM = 12 PM, etc. 885 } 886 meridian = timeMode === 1 ? 'AM' : 'PM'; 887 } else if (hour < 12 && timeMode === 2) { 888 hour += 12; 889 } else { 890 if (hour >= this.maxHours) { 891 hour = this.maxHours - 1; 892 } else if ((hour < 0) || (hour === 12 && timeMode === 1)){ 893 hour = 0; 894 } 895 } 896 } 897 898 this.hour = hour; 899 if (this.snapToStep) { 900 this.minute = this.changeToNearestStep(minute, this.minuteStep); 901 this.second = this.changeToNearestStep(second, this.secondStep); 902 } else { 903 this.minute = minute; 904 this.second = second; 905 } 906 this.meridian = meridian; 907 908 this.update(ignoreWidget); 909 }, 910 911 showWidget: function() { 912 if (this.isOpen) { 913 return; 914 } 915 916 if (this.$element.is(':disabled')) { 917 return; 918 } 919 920 // show/hide approach taken by datepicker 921 this.$widget.appendTo(this.appendWidgetTo); 922 $(document).on('mousedown.timepicker, touchend.timepicker', {scope: this}, this.handleDocumentClick); 923 924 this.$element.trigger({ 925 'type': 'show.timepicker', 926 'time': { 927 'value': this.getTime(), 928 'hours': this.hour, 929 'minutes': this.minute, 930 'seconds': this.second, 931 'meridian': this.meridian 932 } 933 }); 934 935 this.place(); 936 if (this.disableFocus) { 937 this.$element.blur(); 938 } 939 940 // widget shouldn't be empty on open 941 if (this.hour === '') { 942 if (this.defaultTime) { 943 this.setDefaultTime(this.defaultTime); 944 } else { 945 this.setTime('0:0:0'); 946 } 947 } 948 949 if (this.template === 'modal' && this.$widget.modal) { 950 this.$widget.modal('show').on('hidden', $.proxy(this.hideWidget, this)); 951 } else { 952 if (this.isOpen === false) { 953 this.$widget.addClass('open'); 954 } 955 } 956 957 this.isOpen = true; 958 }, 959 960 toggleMeridian: function() { 961 this.meridian = this.meridian === 'AM' ? 'PM' : 'AM'; 962 }, 963 964 update: function(ignoreWidget) { 965 this.updateElement(); 966 if (!ignoreWidget) { 967 this.updateWidget(); 968 } 969 970 this.$element.trigger({ 971 'type': 'changeTime.timepicker', 972 'time': { 973 'value': this.getTime(), 974 'hours': this.hour, 975 'minutes': this.minute, 976 'seconds': this.second, 977 'meridian': this.meridian 978 } 979 }); 980 }, 981 982 updateElement: function() { 983 this.$element.val(this.getTime()).change(); 984 }, 985 986 updateFromElementVal: function() { 987 this.setTime(this.$element.val()); 988 }, 989 990 updateWidget: function() { 991 if (this.$widget === false) { 992 return; 993 } 994 995 var hour = this.hour, 996 minute = this.minute.toString().length === 1 ? '0' + this.minute : this.minute, 997 second = this.second.toString().length === 1 ? '0' + this.second : this.second; 998 999 if (this.showInputs) { 1000 this.$widget.find('input.bootstrap-timepicker-hour').val(hour); 1001 this.$widget.find('input.bootstrap-timepicker-minute').val(minute); 1002 1003 if (this.showSeconds) { 1004 this.$widget.find('input.bootstrap-timepicker-second').val(second); 1005 } 1006 if (this.showMeridian) { 1007 this.$widget.find('input.bootstrap-timepicker-meridian').val(this.meridian); 1008 } 1009 } else { 1010 this.$widget.find('span.bootstrap-timepicker-hour').text(hour); 1011 this.$widget.find('span.bootstrap-timepicker-minute').text(minute); 1012 1013 if (this.showSeconds) { 1014 this.$widget.find('span.bootstrap-timepicker-second').text(second); 1015 } 1016 if (this.showMeridian) { 1017 this.$widget.find('span.bootstrap-timepicker-meridian').text(this.meridian); 1018 } 1019 } 1020 }, 1021
1022 updateFromWidgetInputs: function() { 1023 if (this.$widget === false) { 1024 return; 1025 } 1026 1027 var t = this.$widget.find('input.bootstrap-timepicker-hour').val() + ':' + 1028 this.$widget.find('input.bootstrap-timepicker-minute').val() + 1029 (this.showSeconds ? ':' + this.$widget.find('input.bootstrap-timepicker-second').val() : '') + 1030 (this.showMeridian ? this.$widget.find('input.bootstrap-timepicker-meridian').val() : '') 1031 ; 1032 1033 this.setTime(t, true); 1034 }, 1035 1036 widgetClick: function(e) { 1037 e.stopPropagation(); 1038 e.preventDefault(); 1039 1040 var $input = $(e.target), 1041 action = $input.closest('a').data('action'); 1042 1043 if (action) { 1044 this[action](); 1045 } 1046 this.update(); 1047 1048 if ($input.is('input')) { 1049 $input.get(0).setSelectionRange(0,2); 1050 } 1051 }, 1052 1053 widgetKeydown: function(e) { 1054 var $input = $(e.target), 1055 name = $input.attr('class').replace('bootstrap-timepicker-', ''); 1056 1057 switch (e.which) { 1058 case 9: //tab 1059 if (e.shiftKey) { 1060 if (name === 'hour') { 1061 return this.hideWidget(); 1062 } 1063 } else if ((this.showMeridian && name === 'meridian') || (this.showSeconds && name === 'second') || (!this.showMeridian && !this.showSeconds && name === 'minute')) { 1064 return this.hideWidget(); 1065 } 1066 break; 1067 case 27: // escape 1068 this.hideWidget(); 1069 break; 1070 case 38: // up arrow 1071 e.preventDefault(); 1072 switch (name) { 1073 case 'hour': 1074 this.incrementHour(); 1075 break; 1076 case 'minute': 1077 this.incrementMinute(); 1078 break; 1079 case 'second': 1080 this.incrementSecond(); 1081 break; 1082 case 'meridian': 1083 this.toggleMeridian(); 1084 break; 1085 } 1086 this.setTime(this.getTime()); 1087 $input.get(0).setSelectionRange(0,2); 1088 break; 1089 case 40: // down arrow 1090 e.preventDefault(); 1091 switch (name) { 1092 case 'hour': 1093 this.decrementHour(); 1094 break; 1095 case 'minute': 1096 this.decrementMinute(); 1097 break; 1098 case 'second': 1099 this.decrementSecond(); 1100 break; 1101 case 'meridian': 1102 this.toggleMeridian(); 1103 break; 1104 } 1105 this.setTime(this.getTime()); 1106 $input.get(0).setSelectionRange(0,2); 1107 break; 1108 } 1109 }, 1110 1111 widgetKeyup: function(e) { 1112 if ((e.which === 65) || (e.which === 77) || (e.which === 80) || (e.which === 46) || (e.which === 8) || (e.which >= 48 && e.which <= 57) || (e.which >= 96 && e.which <= 105)) { 1113 this.updateFromWidgetInputs(); 1114 } 1115 } 1116 }; 1117 1118 //TIMEPICKER PLUGIN DEFINITION 1119 $.fn.timepicker = function(option) { 1120 var args = Array.apply(null, arguments); 1121 args.shift(); 1122 return this.each(function() { 1123 var $this = $(this), 1124 data = $this.data('timepicker'), 1125 options = typeof option === 'object' && option; 1126 1127 if (!data) { 1128 $this.data('timepicker', (data = new Timepicker(this, $.extend({}, $.fn.timepicker.defaults, options, $(this).data())))); 1129 } 1130 1131 if (typeof option === 'string') { 1132 data[option].apply(data, args); 1133 } 1134 }); 1135 }; 1136 1137 $.fn.timepicker.defaults = { 1138 defaultTime: 'current', 1139 disableFocus: false, 1140 disableMousewheel: false, 1141 isOpen: false, 1142 minuteStep: 15, 1143 modalBackdrop: false, 1144 orientation: { x: 'auto', y: 'auto'}, 1145 secondStep: 15, 1146 snapToStep: false, 1147 showSeconds: false, 1148 showInputs: true, 1149 showMeridian: true, 1150 template: 'dropdown', 1151 appendWidgetTo: 'body', 1152 showWidgetOnAddonClick: true, 1153 icons: { 1154 up: 'glyphicon glyphicon-chevron-up', 1155 down: 'glyphicon glyphicon-chevron-down' 1156 }, 1157 maxHours: 24, 1158 explicitMode: false 1159 }; 1160 1161 $.fn.timepicker.Constructor = Timepicker; 1162 1163 $(document).on( 1164 'focus.timepicker.data-api click.timepicker.data-api', 1165 '[data-provide="timepicker"]', 1166 function(e){ 1167 var $this = $(this); 1168 if ($this.data('timepicker')) { 1169 return; 1170 } 1171 e.preventDefault(); 1172 // component click requires us to explicitly show it 1173 $this.timepicker(); 1174 } 1175 ); 1176 1177})(jQuery, window, document);
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.