|
1 /* |
|
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) |
|
5 * |
|
6 * Dual licensed under the MIT and GPL licenses. |
|
7 * Copyright (c) 2009 Anders Fajerson |
|
8 * @name timePicker |
|
9 * @version 0.2 |
|
10 * @author Anders Fajerson (http://perifer.se) |
|
11 * @example $("#mytime").timePicker(); |
|
12 * @example $("#mytime").timePicker({step:30, startTime:"15:00", endTime:"18:00"}); |
|
13 */ |
|
14 |
|
15 (function($){ |
|
16 $.fn.timePicker = function(options) { |
|
17 // Build main options before element iteration |
|
18 var settings = $.extend({}, $.fn.timePicker.defaults, options); |
|
19 |
|
20 return this.each(function() { |
|
21 $.timePicker(this, settings); |
|
22 }); |
|
23 }; |
|
24 |
|
25 $.timePicker = function (elm, settings) { |
|
26 var e = $(elm)[0]; |
|
27 return e.timePicker || (e.timePicker = new jQuery._timePicker(e, settings)); |
|
28 }; |
|
29 |
|
30 $._timePicker = function(elm, settings) { |
|
31 |
|
32 var tpOver = false; |
|
33 var keyDown = false; |
|
34 var startTime = timeToDate(settings.startTime, settings); |
|
35 var endTime = timeToDate(settings.endTime, settings); |
|
36 |
|
37 $(elm).attr('autocomplete', 'OFF'); // Disable browser autocomplete |
|
38 |
|
39 var times = []; |
|
40 var time = new Date(startTime); // Create a new date object. |
|
41 while(time <= endTime) { |
|
42 times[times.length] = formatTime(time, settings); |
|
43 time = new Date(time.setMinutes(time.getMinutes() + settings.step)); |
|
44 } |
|
45 |
|
46 var $tpDiv = $('<div class="time-picker'+ (settings.show24Hours ? '' : ' time-picker-12hours') +'"></div>'); |
|
47 var $tpList = $('<ul></ul>'); |
|
48 |
|
49 // Build the list. |
|
50 for(var i = 0; i < times.length; i++) { |
|
51 if (times[i] == settings.selectedTime) { |
|
52 $tpList.append('<li class="selected">' + times[i] + "</li>"); |
|
53 } else { |
|
54 $tpList.append("<li>" + times[i] + "</li>"); |
|
55 } |
|
56 } |
|
57 $tpDiv.append($tpList); |
|
58 // Append the timPicker to the body and position it. |
|
59 var elmOffset = $(elm).offset(); |
|
60 $tpDiv.appendTo('body').css({'top':elmOffset.top+20, 'left':elmOffset.left}).hide(); |
|
61 |
|
62 // Store the mouse state, used by the blur event. Use mouseover instead of |
|
63 // mousedown since Opera fires blur before mousedown. |
|
64 $tpDiv.mouseover(function() { |
|
65 tpOver = true; |
|
66 }).mouseout(function() { |
|
67 tpOver = false; |
|
68 }); |
|
69 |
|
70 $("li", $tpList).mouseover(function() { |
|
71 if (!keyDown) { |
|
72 $("li.selected", $tpDiv).removeClass("selected"); |
|
73 $(this).addClass("selected"); |
|
74 } |
|
75 }).mousedown(function() { |
|
76 tpOver = true; |
|
77 }).click(function() { |
|
78 setTimeVal(elm, this, $tpDiv, settings); |
|
79 tpOver = false; |
|
80 }); |
|
81 |
|
82 var showPicker = function() { |
|
83 if ($tpDiv.is(":visible")) { |
|
84 return false; |
|
85 } |
|
86 $("li", $tpDiv).removeClass("selected"); |
|
87 |
|
88 // Show picker. This has to be done before scrollTop is set since that |
|
89 // can't be done on hidden elements. |
|
90 $tpDiv.show(); |
|
91 |
|
92 // Try to find a time in the list that matches the entered time. |
|
93 var time = elm.value ? timeStringToDate(elm.value, settings) : startTime; |
|
94 var startMin = startTime.getHours() * 60 + startTime.getMinutes(); |
|
95 var min = (time.getHours() * 60 + time.getMinutes()) - startMin; |
|
96 var steps = Math.round(min / settings.step); |
|
97 var roundTime = normaliseTime(new Date(0, 0, 0, 0, (steps * settings.step + startMin), 0)); |
|
98 roundTime = (startTime < roundTime && roundTime <= endTime) ? roundTime : startTime; |
|
99 var $matchedTime = $("li:contains(" + formatTime(roundTime, settings) + ")", $tpDiv); |
|
100 |
|
101 if ($matchedTime.length) { |
|
102 $matchedTime.addClass("selected"); |
|
103 // Scroll to matched time. |
|
104 $tpDiv[0].scrollTop = $matchedTime[0].offsetTop; |
|
105 } |
|
106 return true; |
|
107 }; |
|
108 // Attach to click as well as focus so timePicker can be shown again when |
|
109 // clicking on the input when it already has focus. |
|
110 $(elm).focus(showPicker).click(showPicker); |
|
111 // Hide timepicker on blur |
|
112 $(elm).blur(function() { |
|
113 if (!tpOver) { |
|
114 $tpDiv.hide(); |
|
115 } |
|
116 }); |
|
117 // Keypress doesn't repeat on Safari for non-text keys. |
|
118 // Keydown doesn't repeat on Firefox and Opera on Mac. |
|
119 // Using kepress for Opera and Firefox and keydown for the rest seems to |
|
120 // work with up/down/enter/esc. |
|
121 var event = ($.browser.opera || $.browser.mozilla) ? 'keypress' : 'keydown'; |
|
122 $(elm)[event](function(e) { |
|
123 var $selected; |
|
124 keyDown = true; |
|
125 var top = $tpDiv[0].scrollTop; |
|
126 switch (e.keyCode) { |
|
127 case 38: // Up arrow. |
|
128 // Just show picker if it's hidden. |
|
129 if (showPicker()) { |
|
130 return false; |
|
131 }; |
|
132 $selected = $("li.selected", $tpList); |
|
133 var prev = $selected.prev().addClass("selected")[0]; |
|
134 if (prev) { |
|
135 $selected.removeClass("selected"); |
|
136 // Scroll item into view. |
|
137 if (prev.offsetTop < top) { |
|
138 $tpDiv[0].scrollTop = top - prev.offsetHeight; |
|
139 } |
|
140 } |
|
141 else { |
|
142 // Loop to next item. |
|
143 $selected.removeClass("selected"); |
|
144 prev = $("li:last", $tpList).addClass("selected")[0]; |
|
145 $tpDiv[0].scrollTop = prev.offsetTop - prev.offsetHeight; |
|
146 } |
|
147 return false; |
|
148 break; |
|
149 case 40: // Down arrow, similar in behaviour to up arrow. |
|
150 if (showPicker()) { |
|
151 return false; |
|
152 }; |
|
153 $selected = $("li.selected", $tpList); |
|
154 var next = $selected.next().addClass("selected")[0]; |
|
155 if (next) { |
|
156 $selected.removeClass("selected"); |
|
157 if (next.offsetTop + next.offsetHeight > top + $tpDiv[0].offsetHeight) { |
|
158 $tpDiv[0].scrollTop = top + next.offsetHeight; |
|
159 } |
|
160 } |
|
161 else { |
|
162 $selected.removeClass("selected"); |
|
163 next = $("li:first", $tpList).addClass("selected")[0]; |
|
164 $tpDiv[0].scrollTop = 0; |
|
165 } |
|
166 return false; |
|
167 break; |
|
168 case 13: // Enter |
|
169 if ($tpDiv.is(":visible")) { |
|
170 var sel = $("li.selected", $tpList)[0]; |
|
171 setTimeVal(elm, sel, $tpDiv, settings); |
|
172 } |
|
173 return false; |
|
174 break; |
|
175 case 27: // Esc |
|
176 $tpDiv.hide(); |
|
177 return false; |
|
178 break; |
|
179 } |
|
180 return true; |
|
181 }); |
|
182 $(elm).keyup(function(e) { |
|
183 keyDown = false; |
|
184 }); |
|
185 // Helper function to get an inputs current time as Date object. |
|
186 // Returns a Date object. |
|
187 this.getTime = function() { |
|
188 return timeStringToDate(elm.value, settings); |
|
189 }; |
|
190 // Helper function to set a time input. |
|
191 // Takes a Date object. |
|
192 this.setTime = function(time) { |
|
193 elm.value = formatTime(normaliseTime(time), settings); |
|
194 // Trigger element's change events. |
|
195 $(elm).change(); |
|
196 }; |
|
197 |
|
198 }; // End fn; |
|
199 |
|
200 // Plugin defaults. |
|
201 $.fn.timePicker.defaults = { |
|
202 step:30, |
|
203 startTime: new Date(0, 0, 0, 0, 0, 0), |
|
204 endTime: new Date(0, 0, 0, 23, 30, 0), |
|
205 selectedTime: new Date(0, 0, 0, 0, 0, 0), |
|
206 separator: ':', |
|
207 show24Hours: true |
|
208 }; |
|
209 |
|
210 // Private functions. |
|
211 |
|
212 function setTimeVal(elm, sel, $tpDiv, settings) { |
|
213 // Update input field |
|
214 elm.value = $(sel).text(); |
|
215 // Trigger element's change events. |
|
216 $(elm).change(); |
|
217 // Keep focus for all but IE (which doesn't like it) |
|
218 if (!$.browser.msie) { |
|
219 elm.focus(); |
|
220 } |
|
221 // Hide picker |
|
222 $tpDiv.hide(); |
|
223 } |
|
224 |
|
225 function formatTime(time, settings) { |
|
226 var h = time.getHours(); |
|
227 var hours = settings.show24Hours ? h : (((h + 11) % 12) + 1); |
|
228 var minutes = time.getMinutes(); |
|
229 return formatNumber(hours) + settings.separator + formatNumber(minutes) + (settings.show24Hours ? '' : ((h < 12) ? ' AM' : ' PM')); |
|
230 } |
|
231 |
|
232 function formatNumber(value) { |
|
233 return (value < 10 ? '0' : '') + value; |
|
234 } |
|
235 |
|
236 function timeToDate(input, settings) { |
|
237 return (typeof input == 'object') ? normaliseTime(input) : timeStringToDate(input, settings); |
|
238 } |
|
239 |
|
240 function timeStringToDate(input, settings) { |
|
241 if (input) { |
|
242 var array = input.split(settings.separator); |
|
243 var hours = parseFloat(array[0]); |
|
244 var minutes = parseFloat(array[1]); |
|
245 |
|
246 // Convert AM/PM hour to 24-hour format. |
|
247 if (!settings.show24Hours) { |
|
248 if (hours === 12 && input.substr('AM') !== -1) { |
|
249 hours = 0; |
|
250 } |
|
251 else if (hours !== 12 && input.indexOf('PM') !== -1) { |
|
252 hours += 12; |
|
253 } |
|
254 } |
|
255 var time = new Date(0, 0, 0, hours, minutes, 0); |
|
256 return normaliseTime(time); |
|
257 } |
|
258 return null; |
|
259 } |
|
260 |
|
261 /* Normalise time object to a common date. */ |
|
262 function normaliseTime(time) { |
|
263 time.setFullYear(2001); |
|
264 time.setMonth(0); |
|
265 time.setDate(0); |
|
266 return time; |
|
267 } |
|
268 |
|
269 })(jQuery); |