2 * A time picker for jQuery
4 * Dual licensed under the MIT and GPL licenses.
5 * Copyright (c) 2009 Anders Fajerson
7 * @author Anders Fajerson (http://perifer.se)
8 * @example $("#mytime").timePicker();
9 * @example $("#mytime").timePicker({step:30, startTime:"15:00", endTime:"18:00"});
11 * Based on timePicker by Sam Collet (http://www.texotela.co.uk/code/jquery/timepicker/)
14 * step: # of minutes to step the time by
15 * startTime: beginning of the range of acceptable times
16 * endTime: end of the range of acceptable times
17 * separator: separator string to use between hours and minutes (e.g. ':')
18 * show24Hours: use a 24-hour scheme
22 $.fn.timePicker = function(options) {
23 // Build main options before element iteration
24 var settings = $.extend({}, $.fn.timePicker.defaults, options);
26 return this.each(function() {
27 $.timePicker(this, settings);
31 $.timePicker = function (elm, settings) {
33 return e.timePicker || (e.timePicker = new jQuery._timePicker(e, settings));
36 $.timePicker.version = '0.3';
38 $._timePicker = function(elm, settings) {
42 var startTime = timeToDate(settings.startTime, settings);
43 var endTime = timeToDate(settings.endTime, settings);
44 var selectedClass = "selected";
45 var selectedSelector = "li." + selectedClass;
47 $(elm).attr('autocomplete', 'OFF'); // Disable browser autocomplete
50 var time = new Date(startTime); // Create a new date object.
51 while(time <= endTime) {
52 times[times.length] = formatTime(time, settings);
53 time = new Date(time.setMinutes(time.getMinutes() + settings.step));
56 var $tpDiv = $('<div class="time-picker'+ (settings.show24Hours ? '' : ' time-picker-12hours') +'"></div>');
57 var $tpList = $('<ul></ul>');
60 for(var i = 0; i < times.length; i++) {
61 $tpList.append("<li>" + times[i] + "</li>");
63 $tpDiv.append($tpList);
64 // Append the timPicker to the body and position it.
65 $tpDiv.appendTo('body').hide();
67 // Store the mouse state, used by the blur event. Use mouseover instead of
68 // mousedown since Opera fires blur before mousedown.
69 $tpDiv.mouseover(function() {
71 }).mouseout(function() {
75 $("li", $tpList).mouseover(function() {
77 $(selectedSelector, $tpDiv).removeClass(selectedClass);
78 $(this).addClass(selectedClass);
80 }).mousedown(function() {
83 setTimeVal(elm, this, $tpDiv, settings);
87 var showPicker = function() {
88 if ($tpDiv.is(":visible")) {
91 $("li", $tpDiv).removeClass(selectedClass);
94 var elmOffset = $(elm).offset();
95 $tpDiv.css({'top':elmOffset.top + elm.offsetHeight, 'left':elmOffset.left});
97 // Show picker. This has to be done before scrollTop is set since that
98 // can't be done on hidden elements.
101 // Try to find a time in the list that matches the entered time.
102 var time = elm.value ? timeStringToDate(elm.value, settings) : startTime;
103 var startMin = startTime.getHours() * 60 + startTime.getMinutes();
104 var min = (time.getHours() * 60 + time.getMinutes()) - startMin;
105 var steps = Math.round(min / settings.step);
106 var roundTime = normaliseTime(new Date(0, 0, 0, 0, (steps * settings.step + startMin), 0));
107 roundTime = (startTime < roundTime && roundTime <= endTime) ? roundTime : startTime;
108 var $matchedTime = $("li:contains(" + formatTime(roundTime, settings) + ")", $tpDiv);
110 if ($matchedTime.length) {
111 $matchedTime.addClass(selectedClass);
112 // Scroll to matched time.
113 $tpDiv[0].scrollTop = $matchedTime[0].offsetTop;
117 // Attach to click as well as focus so timePicker can be shown again when
118 // clicking on the input when it already has focus.
119 $(elm).focus(showPicker).click(showPicker);
120 // Hide timepicker on blur
121 $(elm).blur(function() {
126 // Keypress doesn't repeat on Safari for non-text keys.
127 // Keydown doesn't repeat on Firefox and Opera on Mac.
128 // Using kepress for Opera and Firefox and keydown for the rest seems to
129 // work with up/down/enter/esc.
130 var event = ($.browser.opera || $.browser.mozilla) ? 'keypress' : 'keydown';
131 $(elm)[event](function(e) {
134 var top = $tpDiv[0].scrollTop;
136 case 38: // Up arrow.
137 // Just show picker if it's hidden.
141 $selected = $(selectedSelector, $tpList);
142 var prev = $selected.prev().addClass(selectedClass)[0];
144 $selected.removeClass(selectedClass);
145 // Scroll item into view.
146 if (prev.offsetTop < top) {
147 $tpDiv[0].scrollTop = top - prev.offsetHeight;
151 // Loop to next item.
152 $selected.removeClass(selectedClass);
153 prev = $("li:last", $tpList).addClass(selectedClass)[0];
154 $tpDiv[0].scrollTop = prev.offsetTop - prev.offsetHeight;
158 case 40: // Down arrow, similar in behaviour to up arrow.
162 $selected = $(selectedSelector, $tpList);
163 var next = $selected.next().addClass(selectedClass)[0];
165 $selected.removeClass(selectedClass);
166 if (next.offsetTop + next.offsetHeight > top + $tpDiv[0].offsetHeight) {
167 $tpDiv[0].scrollTop = top + next.offsetHeight;
171 $selected.removeClass(selectedClass);
172 next = $("li:first", $tpList).addClass(selectedClass)[0];
173 $tpDiv[0].scrollTop = 0;
178 if ($tpDiv.is(":visible")) {
179 var sel = $(selectedSelector, $tpList)[0];
180 setTimeVal(elm, sel, $tpDiv, settings);
191 $(elm).keyup(function(e) {
194 // Helper function to get an inputs current time as Date object.
195 // Returns a Date object.
196 this.getTime = function() {
197 return timeStringToDate(elm.value, settings);
199 // Helper function to set a time input.
200 // Takes a Date object or string.
201 this.setTime = function(time) {
202 elm.value = formatTime(timeToDate(time, settings), settings);
203 // Trigger element's change events.
210 $.fn.timePicker.defaults = {
212 startTime: new Date(0, 0, 0, 0, 0, 0),
213 endTime: new Date(0, 0, 0, 23, 30, 0),
218 // Private functions.
220 function setTimeVal(elm, sel, $tpDiv, settings) {
221 // Update input field
222 elm.value = $(sel).text();
223 // Trigger element's change events.
225 // Keep focus for all but IE (which doesn't like it)
226 if (!$.browser.msie) {
233 function formatTime(time, settings) {
234 var h = time.getHours();
235 var hours = settings.show24Hours ? h : (((h + 11) % 12) + 1);
236 var minutes = time.getMinutes();
237 return formatNumber(hours) + settings.separator + formatNumber(minutes) + (settings.show24Hours ? '' : ((h < 12) ? ' AM' : ' PM'));
240 function formatNumber(value) {
241 return (value < 10 ? '0' : '') + value;
244 function timeToDate(input, settings) {
245 return (typeof input == 'object') ? normaliseTime(input) : timeStringToDate(input, settings);
248 function timeStringToDate(input, settings) {
250 var array = input.split(settings.separator);
251 var hours = parseFloat(array[0]);
252 var minutes = parseFloat(array[1]);
254 // Convert AM/PM hour to 24-hour format.
255 if (!settings.show24Hours) {
256 if (hours === 12 && input.indexOf('AM') !== -1) {
259 else if (hours !== 12 && input.indexOf('PM') !== -1) {
263 var time = new Date(0, 0, 0, hours, minutes, 0);
264 return normaliseTime(time);
269 /* Normalise time object to a common date. */
270 function normaliseTime(time) {
271 time.setFullYear(2001);