1 //TODO: move arrow styles and button click code into configurable items, with defaults matching the existing code
4 * Timepicker Component for Twitter Bootstrap
6 * Copyright 2013 Joris de Wit
8 * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors
10 * For the full copyright and license information, please view the LICENSE
11 * file that was distributed with this source code.
13 (function($, window, document, undefined) {
16 // TIMEPICKER PUBLIC CLASS DEFINITION
17 var Timepicker = function(element, options) {
19 this.$element = $(element);
20 this.defaultTime = options.defaultTime;
21 this.disableFocus = options.disableFocus;
22 this.isOpen = options.isOpen;
23 this.minuteStep = options.minuteStep;
24 this.modalBackdrop = options.modalBackdrop;
25 this.secondStep = options.secondStep;
26 this.showInputs = options.showInputs;
27 this.showMeridian = options.showMeridian;
28 this.showSeconds = options.showSeconds;
29 this.template = options.template;
30 this.appendWidgetTo = options.appendWidgetTo;
31 this.upArrowStyle = options.upArrowStyle;
32 this.downArrowStyle = options.downArrowStyle;
33 this.containerClass = options.containerClass;
38 Timepicker.prototype = {
40 constructor: Timepicker,
45 if (this.$element.parent().hasClass('input-append') || this.$element.parent().hasClass('input-prepend')) {
46 if (this.$element.parent('.input-append, .input-prepend').find('.add-on').length) {
47 this.$element.parent('.input-append, .input-prepend').find('.add-on').on({
48 'click.timepicker': $.proxy(this.showWidget, this)
51 this.$element.closest(this.containerClass).find('.add-on').on({
52 'click.timepicker': $.proxy(this.showWidget, this)
57 'focus.timepicker': $.proxy(this.highlightUnit, this),
58 'click.timepicker': $.proxy(this.highlightUnit, this),
59 'keydown.timepicker': $.proxy(this.elementKeydown, this),
60 'blur.timepicker': $.proxy(this.blurElement, this)
65 'focus.timepicker': $.proxy(this.showWidget, this),
66 'click.timepicker': $.proxy(this.showWidget, this),
67 'blur.timepicker': $.proxy(this.blurElement, this)
71 'focus.timepicker': $.proxy(this.highlightUnit, this),
72 'click.timepicker': $.proxy(this.highlightUnit, this),
73 'keydown.timepicker': $.proxy(this.elementKeydown, this),
74 'blur.timepicker': $.proxy(this.blurElement, this)
79 if (this.template !== false) {
80 this.$widget = $(this.getTemplate()).prependTo(this.$element.parents(this.appendWidgetTo)).on('click', $.proxy(this.widgetClick, this));
85 if (this.showInputs && this.$widget !== false) {
86 this.$widget.find('input').each(function() {
88 'click.timepicker': function() { $(this).select(); },
89 'keydown.timepicker': $.proxy(self.widgetKeydown, self)
94 this.setDefaultTime(this.defaultTime);
97 blurElement: function() {
98 this.highlightedUnit = undefined;
99 this.updateFromElementVal();
102 decrementHour: function() {
103 if (this.showMeridian) {
104 if (this.hour === 1) {
106 } else if (this.hour === 12) {
109 return this.toggleMeridian();
110 } else if (this.hour === 0) {
113 return this.toggleMeridian();
118 if (this.hour === 0) {
127 decrementMinute: function(step) {
131 newVal = this.minute - step;
133 newVal = this.minute - this.minuteStep;
137 this.decrementHour();
138 this.minute = newVal + 60;
140 this.minute = newVal;
145 decrementSecond: function() {
146 var newVal = this.second - this.secondStep;
149 this.decrementMinute(true);
150 this.second = newVal + 60;
152 this.second = newVal;
157 elementKeydown: function(e) {
160 this.updateFromElementVal();
162 switch (this.highlightedUnit) {
165 this.highlightNextUnit();
168 if (this.showMeridian || this.showSeconds) {
170 this.highlightNextUnit();
174 if (this.showMeridian) {
176 this.highlightNextUnit();
182 this.updateFromElementVal();
184 case 37: // left arrow
186 this.highlightPrevUnit();
187 this.updateFromElementVal();
191 switch (this.highlightedUnit) {
193 this.incrementHour();
194 this.highlightHour();
197 this.incrementMinute();
198 this.highlightMinute();
201 this.incrementSecond();
202 this.highlightSecond();
205 this.toggleMeridian();
206 this.highlightMeridian();
210 case 39: // right arrow
212 this.updateFromElementVal();
213 this.highlightNextUnit();
215 case 40: // down arrow
217 switch (this.highlightedUnit) {
219 this.decrementHour();
220 this.highlightHour();
223 this.decrementMinute();
224 this.highlightMinute();
227 this.decrementSecond();
228 this.highlightSecond();
231 this.toggleMeridian();
232 this.highlightMeridian();
239 formatTime: function(hour, minute, second, meridian) {
240 hour = hour < 10 ? '0' + hour : hour;
241 minute = minute < 10 ? '0' + minute : minute;
242 second = second < 10 ? '0' + second : second;
244 return hour + ':' + minute + (this.showSeconds ? ':' + second : '') + (this.showMeridian ? ' ' + meridian : '');
247 getCursorPosition: function() {
248 var input = this.$element.get(0);
250 if ('selectionStart' in input) {// Standard-compliant browsers
252 return input.selectionStart;
253 } else if (document.selection) {// IE fix
255 var sel = document.selection.createRange(),
256 selLen = document.selection.createRange().text.length;
258 sel.moveStart('character', - input.value.length);
260 return sel.text.length - selLen;
264 getTemplate: function() {
272 if (this.showInputs) {
273 hourTemplate = '<input type="text" name="hour" class="bootstrap-timepicker-hour form-control" maxlength="2"/>';
274 minuteTemplate = '<input type="text" name="minute" class="bootstrap-timepicker-minute form-control" maxlength="2"/>';
275 secondTemplate = '<input type="text" name="second" class="bootstrap-timepicker-second form-control" maxlength="2"/>';
276 meridianTemplate = '<input type="text" name="meridian" class="bootstrap-timepicker-meridian form-control" maxlength="2"/>';
278 hourTemplate = '<span class="bootstrap-timepicker-hour"></span>';
279 minuteTemplate = '<span class="bootstrap-timepicker-minute"></span>';
280 secondTemplate = '<span class="bootstrap-timepicker-second"></span>';
281 meridianTemplate = '<span class="bootstrap-timepicker-meridian"></span>';
284 templateContent = '<table>'+
286 '<td><a href="#" data-action="incrementHour"><i class="' + this.upArrowStyle + '"></i></a></td>'+
287 '<td class="separator"> </td>'+
288 '<td><a href="#" data-action="incrementMinute"><i class="' + this.upArrowStyle + '"></i></a></td>'+
290 '<td class="separator"> </td>'+
291 '<td><a href="#" data-action="incrementSecond"><i class="' + this.upArrowStyle + '"></i></a></td>'
294 '<td class="separator"> </td>'+
295 '<td class="meridian-column"><a href="#" data-action="toggleMeridian"><i class="' + this.upArrowStyle + '"></i></a></td>'
299 '<td>'+ hourTemplate +'</td> '+
300 '<td class="separator">:</td>'+
301 '<td>'+ minuteTemplate +'</td> '+
303 '<td class="separator">:</td>'+
304 '<td>'+ secondTemplate +'</td>'
307 '<td class="separator"> </td>'+
308 '<td>'+ meridianTemplate +'</td>'
312 '<td><a href="#" data-action="decrementHour"><i class="' + this.downArrowStyle + '"></i></a></td>'+
313 '<td class="separator"></td>'+
314 '<td><a href="#" data-action="decrementMinute"><i class="' + this.downArrowStyle + '"></i></a></td>'+
316 '<td class="separator"> </td>'+
317 '<td><a href="#" data-action="decrementSecond"><i class="' + this.downArrowStyle + '"></i></a></td>'
320 '<td class="separator"> </td>'+
321 '<td><a href="#" data-action="toggleMeridian"><i class="' + this.downArrowStyle + '"></i></a></td>'
326 switch(this.template) {
328 template = '<div class="bootstrap-timepicker-widget modal hide fade in" data-backdrop="'+ (this.modalBackdrop ? 'true' : 'false') +'">'+
329 '<div class="modal-header">'+
330 '<a href="#" class="close" data-dismiss="modal">×</a>'+
331 '<h3>Pick a Time</h3>'+
333 '<div class="modal-content">'+
336 '<div class="modal-footer">'+
337 '<a href="#" class="btn btn-primary" data-dismiss="modal">OK</a>'+
342 template = '<div class="bootstrap-timepicker-widget dropdown-menu">'+ templateContent +'</div>';
349 getTime: function() {
350 return this.formatTime(this.hour, this.minute, this.second, this.meridian);
353 hideWidget: function() {
354 if (this.isOpen === false) {
358 if (this.showInputs) {
359 this.updateFromWidgetInputs();
362 this.$element.trigger({
363 'type': 'hide.timepicker',
365 'value': this.getTime(),
367 'minutes': this.minute,
368 'seconds': this.second,
369 'meridian': this.meridian
373 if (this.template === 'modal' && this.$widget.modal) {
374 this.$widget.modal('hide');
376 this.$widget.removeClass('open');
379 $(document).off('mousedown.timepicker');
384 highlightUnit: function() {
385 this.position = this.getCursorPosition();
386 if (this.position >= 0 && this.position <= 2) {
387 this.highlightHour();
388 } else if (this.position >= 3 && this.position <= 5) {
389 this.highlightMinute();
390 } else if (this.position >= 6 && this.position <= 8) {
391 if (this.showSeconds) {
392 this.highlightSecond();
394 this.highlightMeridian();
396 } else if (this.position >= 9 && this.position <= 11) {
397 this.highlightMeridian();
401 highlightNextUnit: function() {
402 switch (this.highlightedUnit) {
404 this.highlightMinute();
407 if (this.showSeconds) {
408 this.highlightSecond();
409 } else if (this.showMeridian){
410 this.highlightMeridian();
412 this.highlightHour();
416 if (this.showMeridian) {
417 this.highlightMeridian();
419 this.highlightHour();
423 this.highlightHour();
428 highlightPrevUnit: function() {
429 switch (this.highlightedUnit) {
431 this.highlightMeridian();
434 this.highlightHour();
437 this.highlightMinute();
440 if (this.showSeconds) {
441 this.highlightSecond();
443 this.highlightMinute();
449 highlightHour: function() {
450 var $element = this.$element.get(0);
452 this.highlightedUnit = 'hour';
454 if ($element.setSelectionRange) {
455 setTimeout(function() {
456 $element.setSelectionRange(0,2);
461 highlightMinute: function() {
462 var $element = this.$element.get(0);
464 this.highlightedUnit = 'minute';
466 if ($element.setSelectionRange) {
467 setTimeout(function() {
468 $element.setSelectionRange(3,5);
473 highlightSecond: function() {
474 var $element = this.$element.get(0);
476 this.highlightedUnit = 'second';
478 if ($element.setSelectionRange) {
479 setTimeout(function() {
480 $element.setSelectionRange(6,8);
485 highlightMeridian: function() {
486 var $element = this.$element.get(0);
488 this.highlightedUnit = 'meridian';
490 if ($element.setSelectionRange) {
491 if (this.showSeconds) {
492 setTimeout(function() {
493 $element.setSelectionRange(9,11);
496 setTimeout(function() {
497 $element.setSelectionRange(6,8);
503 incrementHour: function() {
504 if (this.showMeridian) {
505 if (this.hour === 11) {
507 return this.toggleMeridian();
508 } else if (this.hour === 12) {
512 if (this.hour === 23) {
521 incrementMinute: function(step) {
525 newVal = this.minute + step;
527 newVal = this.minute + this.minuteStep - (this.minute % this.minuteStep);
531 this.incrementHour();
532 this.minute = newVal - 60;
534 this.minute = newVal;
539 incrementSecond: function() {
540 var newVal = this.second + this.secondStep - (this.second % this.secondStep);
543 this.incrementMinute(true);
544 this.second = newVal - 60;
546 this.second = newVal;
552 $('document').off('.timepicker');
554 this.$widget.remove();
556 delete this.$element.data().timepicker;
559 setDefaultTime: function(defaultTime){
560 if (!this.$element.val()) {
561 if (defaultTime === 'current') {
562 var dTime = new Date(),
563 hours = dTime.getHours(),
564 minutes = Math.floor(dTime.getMinutes() / this.minuteStep) * this.minuteStep,
565 seconds = Math.floor(dTime.getSeconds() / this.secondStep) * this.secondStep,
568 if (this.showMeridian) {
571 } else if (hours >= 12) {
582 this.minute = minutes;
583 this.second = seconds;
584 this.meridian = meridian;
588 } else if (defaultTime === false) {
592 this.meridian = 'AM';
594 this.setTime(defaultTime);
597 this.updateFromElementVal();
601 setTime: function(time) {
605 if (this.showMeridian) {
606 arr = time.split(' ');
607 timeArray = arr[0].split(':');
608 this.meridian = arr[1];
610 timeArray = time.split(':');
613 this.hour = parseInt(timeArray[0], 10);
614 this.minute = parseInt(timeArray[1], 10);
615 this.second = parseInt(timeArray[2], 10);
617 if (isNaN(this.hour)) {
620 if (isNaN(this.minute)) {
624 if (this.showMeridian) {
625 if (this.hour > 12) {
627 } else if (this.hour < 1) {
631 if (this.meridian === 'am' || this.meridian === 'a') {
632 this.meridian = 'AM';
633 } else if (this.meridian === 'pm' || this.meridian === 'p') {
634 this.meridian = 'PM';
637 if (this.meridian !== 'AM' && this.meridian !== 'PM') {
638 this.meridian = 'AM';
641 if (this.hour >= 24) {
643 } else if (this.hour < 0) {
648 if (this.minute < 0) {
650 } else if (this.minute >= 60) {
654 if (this.showSeconds) {
655 if (isNaN(this.second)) {
657 } else if (this.second < 0) {
659 } else if (this.second >= 60) {
667 showWidget: function() {
672 if (this.$element.is(':disabled')) {
677 $(document).on('mousedown.timepicker', function (e) {
678 // Clicked outside the timepicker, hide it
679 if ($(e.target).closest('.bootstrap-timepicker-widget').length === 0) {
684 this.$element.trigger({
685 'type': 'show.timepicker',
687 'value': this.getTime(),
689 'minutes': this.minute,
690 'seconds': this.second,
691 'meridian': this.meridian
695 if (this.disableFocus) {
696 this.$element.blur();
699 this.updateFromElementVal();
701 if (this.template === 'modal' && this.$widget.modal) {
702 this.$widget.modal('show').on('hidden', $.proxy(this.hideWidget, this));
704 if (this.isOpen === false) {
705 this.$widget.addClass('open');
712 toggleMeridian: function() {
713 this.meridian = this.meridian === 'AM' ? 'PM' : 'AM';
718 this.$element.trigger({
719 'type': 'changeTime.timepicker',
721 'value': this.getTime(),
723 'minutes': this.minute,
724 'seconds': this.second,
725 'meridian': this.meridian
729 this.updateElement();
733 updateElement: function() {
734 this.$element.val(this.getTime()).change();
737 updateFromElementVal: function() {
738 var val = this.$element.val();
745 updateWidget: function() {
746 if (this.$widget === false) {
750 var hour = this.hour < 10 ? '0' + this.hour : this.hour,
751 minute = this.minute < 10 ? '0' + this.minute : this.minute,
752 second = this.second < 10 ? '0' + this.second : this.second;
754 if (this.showInputs) {
755 this.$widget.find('input.bootstrap-timepicker-hour').val(hour);
756 this.$widget.find('input.bootstrap-timepicker-minute').val(minute);
758 if (this.showSeconds) {
759 this.$widget.find('input.bootstrap-timepicker-second').val(second);
761 if (this.showMeridian) {
762 this.$widget.find('input.bootstrap-timepicker-meridian').val(this.meridian);
765 this.$widget.find('span.bootstrap-timepicker-hour').text(hour);
766 this.$widget.find('span.bootstrap-timepicker-minute').text(minute);
768 if (this.showSeconds) {
769 this.$widget.find('span.bootstrap-timepicker-second').text(second);
771 if (this.showMeridian) {
772 this.$widget.find('span.bootstrap-timepicker-meridian').text(this.meridian);
777 updateFromWidgetInputs: function() {
778 if (this.$widget === false) {
781 var time = $('input.bootstrap-timepicker-hour', this.$widget).val() + ':' +
782 $('input.bootstrap-timepicker-minute', this.$widget).val() +
783 (this.showSeconds ? ':' + $('input.bootstrap-timepicker-second', this.$widget).val() : '') +
784 (this.showMeridian ? ' ' + $('input.bootstrap-timepicker-meridian', this.$widget).val() : '');
789 widgetClick: function(e) {
793 var action = $(e.target).closest('a').data('action');
799 widgetKeydown: function(e) {
800 var $input = $(e.target).closest('input'),
801 name = $input.attr('name');
805 if (this.showMeridian) {
806 if (name === 'meridian') {
807 return this.hideWidget();
810 if (this.showSeconds) {
811 if (name === 'second') {
812 return this.hideWidget();
815 if (name === 'minute') {
816 return this.hideWidget();
821 this.updateFromWidgetInputs();
830 this.incrementHour();
833 this.incrementMinute();
836 this.incrementSecond();
839 this.toggleMeridian();
843 case 40: // down arrow
847 this.decrementHour();
850 this.decrementMinute();
853 this.decrementSecond();
856 this.toggleMeridian();
865 //TIMEPICKER PLUGIN DEFINITION
866 $.fn.timepicker = function(option) {
867 var args = Array.apply(null, arguments);
869 return this.each(function() {
871 data = $this.data('timepicker'),
872 options = typeof option === 'object' && option;
875 $this.data('timepicker', (data = new Timepicker(this, $.extend({}, $.fn.timepicker.defaults, options, $(this).data()))));
878 if (typeof option === 'string') {
879 data[option].apply(data, args);
884 $.fn.timepicker.defaults = {
885 defaultTime: 'current',
889 modalBackdrop: false,
894 template: 'dropdown',
895 appendWidgetTo: '.bootstrap-timepicker',
896 upArrowStyle: 'glyphicon glyphicon-chevron-up',
897 downArrowStyle: 'glyphicon glyphicon-chevron-down',
898 containerClass: 'bootstrap-timepicker'
901 $.fn.timepicker.Constructor = Timepicker;
903 })(jQuery, window, document);