2 * A time picker for jQuery
3 * Based on original timePicker by Sam Collet (http://www.texotela.co.uk) -
4 * copyright (c) 2006 Sam Collett (http://www.texotela.co.uk)
6 * Dual licensed under the MIT and GPL licenses.
7 * Copyright (c) 2009 Anders Fajerson
10 * @author Anders Fajerson (http://perifer.se)
11 * @example $("#mytime").timePicker();
12 * @example $("#mytime").timePicker({step:30, startTime:"15:00", endTime:"18:00"});
15 $.fn.timePicker = function(options) {
16 // Build main options before element iteration
17 var settings = $.extend({}, $.fn.timePicker.defaults, options);
19 return this.each(function() {
20 $.timePicker(this, settings);
24 $.timePicker = function (elm, settings) {
26 return e.timePicker || (e.timePicker = new jQuery._timePicker(e, settings));
29 $._timePicker = function(elm, settings) {
33 var startTime = timeToDate(settings.startTime, settings);
34 var endTime = timeToDate(settings.endTime, settings);
36 $(elm).attr('autocomplete', 'OFF'); // Disable browser autocomplete
39 var time = new Date(startTime); // Create a new date object.
40 while(time <= endTime) {
41 times[times.length] = formatTime(time, settings);
42 time = new Date(time.setMinutes(time.getMinutes() + settings.step));
45 var $tpDiv = $('<div class="time-picker'+ (settings.show24Hours ? '' : ' time-picker-12hours') +'"></div>');
46 var $tpList = $('<ul></ul>');
49 for(var i = 0; i < times.length; i++) {
50 $tpList.append("<li>" + times[i] + "</li>");
52 $tpDiv.append($tpList);
53 // Append the timPicker to the body and position it.
54 var elmOffset = $(elm).offset();
55 var aboveFancybox = 3000;
56 $tpDiv.appendTo('body').css({'top':elmOffset.top, 'left':elmOffset.left, 'zIndex': aboveFancybox}).hide();
58 // Store the mouse state, used by the blur event. Use mouseover instead of
59 // mousedown since Opera fires blur before mousedown.
60 $tpDiv.mouseover(function() {
62 }).mouseout(function() {
66 $("li", $tpList).mouseover(function() {
68 $("li.selected", $tpDiv).removeClass("selected");
69 $(this).addClass("selected");
71 }).mousedown(function() {
74 setTimeVal(elm, this, $tpDiv, settings);
78 var showPicker = function() {
79 if ($tpDiv.is(":visible")) {
82 $("li", $tpDiv).removeClass("selected");
84 // Show picker. This has to be done before scrollTop is set since that
85 // can't be done on hidden elements.
88 // Try to find a time in the list that matches the entered time.
89 var time = elm.value ? timeStringToDate(elm.value, settings) : startTime;
90 var startMin = startTime.getHours() * 60 + startTime.getMinutes();
91 var min = (time.getHours() * 60 + time.getMinutes()) - startMin;
92 var steps = Math.round(min / settings.step);
93 var roundTime = normaliseTime(new Date(0, 0, 0, 0, (steps * settings.step + startMin), 0));
94 roundTime = (startTime < roundTime && roundTime <= endTime) ? roundTime : startTime;
95 var $matchedTime = $("li:contains(" + formatTime(roundTime, settings) + ")", $tpDiv);
97 if ($matchedTime.length) {
98 $matchedTime.addClass("selected");
99 // Scroll to matched time.
100 $tpDiv[0].scrollTop = $matchedTime[0].offsetTop;
104 // Attach to click as well as focus so timePicker can be shown again when
105 // clicking on the input when it already has focus.
106 $(elm).focus(showPicker).click(showPicker);
107 // Hide timepicker on blur
108 $(elm).blur(function() {
113 // Keypress doesn't repeat on Safari for non-text keys.
114 // Keydown doesn't repeat on Firefox and Opera on Mac.
115 // Using kepress for Opera and Firefox and keydown for the rest seems to
116 // work with up/down/enter/esc.
117 var event = ($.browser.opera || $.browser.mozilla) ? 'keypress' : 'keydown';
118 $(elm)[event](function(e) {
121 var top = $tpDiv[0].scrollTop;
123 case 38: // Up arrow.
124 // Just show picker if it's hidden.
128 $selected = $("li.selected", $tpList);
129 var prev = $selected.prev().addClass("selected")[0];
131 $selected.removeClass("selected");
132 // Scroll item into view.
133 if (prev.offsetTop < top) {
134 $tpDiv[0].scrollTop = top - prev.offsetHeight;
138 // Loop to next item.
139 $selected.removeClass("selected");
140 prev = $("li:last", $tpList).addClass("selected")[0];
141 $tpDiv[0].scrollTop = prev.offsetTop - prev.offsetHeight;
145 case 40: // Down arrow, similar in behaviour to up arrow.
149 $selected = $("li.selected", $tpList);
150 var next = $selected.next().addClass("selected")[0];
152 $selected.removeClass("selected");
153 if (next.offsetTop + next.offsetHeight > top + $tpDiv[0].offsetHeight) {
154 $tpDiv[0].scrollTop = top + next.offsetHeight;
158 $selected.removeClass("selected");
159 next = $("li:first", $tpList).addClass("selected")[0];
160 $tpDiv[0].scrollTop = 0;
165 if ($tpDiv.is(":visible")) {
166 var sel = $("li.selected", $tpList)[0];
167 setTimeVal(elm, sel, $tpDiv, settings);
178 $(elm).keyup(function(e) {
181 // Helper function to get an inputs current time as Date object.
182 // Returns a Date object.
183 this.getTime = function() {
184 return timeStringToDate(elm.value, settings);
186 // Helper function to set a time input.
187 // Takes a Date object.
188 this.setTime = function(time) {
189 elm.value = formatTime(normaliseTime(time), settings);
190 // Trigger element's change events.
197 $.fn.timePicker.defaults = {
199 startTime: new Date(0, 0, 0, 0, 0, 0),
200 endTime: new Date(0, 0, 0, 23, 30, 0),
205 // Private functions.
207 function setTimeVal(elm, sel, $tpDiv, settings) {
208 // Update input field
209 elm.value = $(sel).text();
210 // Trigger element's change events.
212 // Keep focus for all but IE (which doesn't like it)
213 if (!$.browser.msie) {
220 function formatTime(time, settings) {
221 var h = time.getHours();
222 var hours = settings.show24Hours ? h : (((h + 11) % 12) + 1);
223 var minutes = time.getMinutes();
224 return formatNumber(hours) + settings.separator + formatNumber(minutes) + (settings.show24Hours ? '' : ((h < 12) ? ' AM' : ' PM'));
227 function formatNumber(value) {
228 return (value < 10 ? '0' : '') + value;
231 function timeToDate(input, settings) {
232 return (typeof input == 'object') ? normaliseTime(input) : timeStringToDate(input, settings);
235 function timeStringToDate(input, settings) {
237 var array = input.split(settings.separator);
238 var hours = parseFloat(array[0]);
239 var minutes = parseFloat(array[1]);
241 // Convert AM/PM hour to 24-hour format.
242 if (!settings.show24Hours) {
243 if (hours === 12 && input.substr('AM') !== -1) {
246 else if (hours !== 12 && input.indexOf('PM') !== -1) {
250 var time = new Date(0, 0, 0, hours, minutes, 0);
251 return normaliseTime(time);
256 /* Normalise time object to a common date. */
257 function normaliseTime(time) {
258 time.setFullYear(2001);