1 | /* |
---|
2 | * Date picker plugin for jQuery |
---|
3 | * http://kelvinluck.com/assets/jquery/datePicker |
---|
4 | * |
---|
5 | * Copyright (c) 2006 Kelvin Luck (kelvnluck.com) |
---|
6 | * Licensed under the MIT License: |
---|
7 | * http://www.opensource.org/licenses/mit-license.php |
---|
8 | * |
---|
9 | * $LastChangedDate: 2006-10-08 19:08:47 +0100 (Sun, 08 Oct 2006) $ |
---|
10 | * $Rev: 23 $ |
---|
11 | */ |
---|
12 | |
---|
13 | $.datePicker = function() |
---|
14 | { |
---|
15 | // so that firebug console.log statements don't break IE |
---|
16 | if (window.console == undefined) { window.console = {log:function(){}}; } |
---|
17 | |
---|
18 | var months = ['January', 'Febuary', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; |
---|
19 | var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; |
---|
20 | var navLinks = {p:'Prev', n:'Next', c:'Close'}; |
---|
21 | var dateFormat = 'dd/mm/yyyy'; |
---|
22 | var _firstDate; |
---|
23 | var _lastDate; |
---|
24 | |
---|
25 | var _selectedDate; |
---|
26 | var _openCal; |
---|
27 | |
---|
28 | var _zeroPad = function(num) { |
---|
29 | var s = '0'+num; |
---|
30 | return s.substring(s.length-2) |
---|
31 | //return ('0'+num).substring(-2); // doesn't work on IE :( |
---|
32 | }; |
---|
33 | var _strToDate = function(dIn) |
---|
34 | { |
---|
35 | switch (dateFormat.toLowerCase()) { |
---|
36 | case 'yyyy-mm-dd': |
---|
37 | dParts = dIn.split('-'); |
---|
38 | return new Date(dParts[0], Number(dParts[1])-1, dParts[2]); |
---|
39 | case 'dd/mm/yyyy': |
---|
40 | dParts = dIn.split('/'); |
---|
41 | return new Date(dParts[2], Number(dParts[1])-1, Number(dParts[0])); |
---|
42 | case 'mm/dd/yyyy': |
---|
43 | default: |
---|
44 | var parts = parts ? parts : [2, 1, 0]; |
---|
45 | dParts = dIn.split('/'); |
---|
46 | return new Date(dParts[2], Number(dParts[0])-1, Number(dParts[1])); |
---|
47 | } |
---|
48 | }; |
---|
49 | var _dateToStr = function(d) |
---|
50 | { |
---|
51 | var dY = d.getFullYear(); |
---|
52 | var dM = _zeroPad(d.getMonth()+1); |
---|
53 | var dD = _zeroPad(d.getDate()); |
---|
54 | switch (dateFormat.toLowerCase()) { |
---|
55 | case 'yyyy/mm/dd': |
---|
56 | return dY + '/' + dM + '/' + dD; |
---|
57 | case 'yyyy-mm-dd': |
---|
58 | return dY + '-' + dM + '-' + dD; |
---|
59 | case 'dd/mm/yyyy': |
---|
60 | return dD + '/' + dM + '/' + dY; |
---|
61 | case 'mm/dd/yyyy': |
---|
62 | default: |
---|
63 | return dM + '/' + dD + '/' + dY; |
---|
64 | } |
---|
65 | }; |
---|
66 | |
---|
67 | var _getCalendarDiv = function(dIn) |
---|
68 | { |
---|
69 | var today = new Date(); |
---|
70 | if (dIn == undefined) { |
---|
71 | // start from this month. |
---|
72 | d = new Date(today.getFullYear(), today.getMonth(), 1); |
---|
73 | } else { |
---|
74 | // start from the passed in date |
---|
75 | d = dIn; |
---|
76 | d.setDate(1); |
---|
77 | } |
---|
78 | // check that date is within allowed limits: |
---|
79 | if ((d.getMonth() < _firstDate.getMonth() && d.getFullYear() == _firstDate.getFullYear()) || d.getFullYear() < _firstDate.getFullYear()) { |
---|
80 | d = new Date(_firstDate.getFullYear(), _firstDate.getMonth(), 1);; |
---|
81 | } else if ((d.getMonth() > _lastDate.getMonth() && d.getFullYear() == _lastDate.getFullYear()) || d.getFullYear() > _lastDate.getFullYear()) { |
---|
82 | d = new Date(_lastDate.getFullYear(), _lastDate.getMonth(), 1);; |
---|
83 | } |
---|
84 | |
---|
85 | var calDiv = $.DIV({className:'popup-calendar'}, ''); |
---|
86 | var jCalDiv = $(calDiv); |
---|
87 | var firstMonth = true; |
---|
88 | var firstDate = _firstDate.getDate(); |
---|
89 | |
---|
90 | // create prev and next links |
---|
91 | var prevLinkDiv = ''; |
---|
92 | if (!(d.getMonth() == _firstDate.getMonth() && d.getFullYear() == _firstDate.getFullYear())) { |
---|
93 | // not in first display month so show a previous link |
---|
94 | firstMonth = false; |
---|
95 | var lastMonth = new Date(d.getFullYear(), d.getMonth()-1, 1); |
---|
96 | var prevLink = $.A({href:'javascript:;'}, navLinks.p); |
---|
97 | $(prevLink).click(function() |
---|
98 | { |
---|
99 | $.datePicker.changeMonth(lastMonth, this); |
---|
100 | return false; |
---|
101 | }); |
---|
102 | prevLinkDiv = $.DIV({className:'link-prev'}, '<', prevLink); |
---|
103 | } |
---|
104 | |
---|
105 | var finalMonth = true; |
---|
106 | var lastDate = _lastDate.getDate(); |
---|
107 | nextLinkDiv = ''; |
---|
108 | if (!(d.getMonth() == _lastDate.getMonth() && d.getFullYear() == _lastDate.getFullYear())) { |
---|
109 | // in the last month - no next link |
---|
110 | finalMonth = false; |
---|
111 | var nextMonth = new Date(d.getFullYear(), d.getMonth()+1, 1); |
---|
112 | var nextLink = $.A({href:'javascript:;'}, navLinks.n); |
---|
113 | $(nextLink).click(function() |
---|
114 | { |
---|
115 | $.datePicker.changeMonth(nextMonth, this); |
---|
116 | return false; |
---|
117 | }); |
---|
118 | nextLinkDiv = $.DIV({className:'link-next'}, nextLink, '>'); |
---|
119 | } |
---|
120 | |
---|
121 | var closeLink = $.A({href:'javascript:;'}, navLinks.c); |
---|
122 | $(closeLink).click(function() |
---|
123 | { |
---|
124 | $.datePicker.closeCalendar(); |
---|
125 | }); |
---|
126 | |
---|
127 | jCalDiv.append( |
---|
128 | $.DIV({className:'link-close'}, closeLink), |
---|
129 | $.H3({}, months[d.getMonth()], ' ', d.getFullYear()) |
---|
130 | ); |
---|
131 | |
---|
132 | var headRow = $.TR({}); |
---|
133 | for (var i=0; i<7; i++) { |
---|
134 | var day = days[i]; |
---|
135 | headRow.appendChild( |
---|
136 | $.TH({scope:'col', abbr:day, title:day}, day.substr(0, 1)) |
---|
137 | ); |
---|
138 | } |
---|
139 | |
---|
140 | var tBody = $.TBODY(); |
---|
141 | |
---|
142 | var lastDay = (new Date(d.getFullYear(), d.getMonth()+1, 0)).getDate(); |
---|
143 | var curDay = -d.getDay(); |
---|
144 | |
---|
145 | var todayDate = (new Date()).getDate(); |
---|
146 | var thisMonth = d.getMonth() == today.getMonth() && d.getFullYear() == today.getFullYear(); |
---|
147 | |
---|
148 | var w = 0; |
---|
149 | while (w++<6) { |
---|
150 | var thisRow = $.TR({}); |
---|
151 | for (var i=0; i<7; i++) { |
---|
152 | var atts = {}; |
---|
153 | |
---|
154 | if (curDay < 0 || curDay >= lastDay) { |
---|
155 | dayStr = ' '; |
---|
156 | } else if (firstMonth && curDay < firstDate-1) { |
---|
157 | dayStr = curDay+1; |
---|
158 | atts.className = 'inactive'; |
---|
159 | } else if (finalMonth && curDay > lastDate-1) { |
---|
160 | dayStr = curDay+1; |
---|
161 | atts.className = 'inactive'; |
---|
162 | } else { |
---|
163 | d.setDate(curDay+1); |
---|
164 | var dStr = _dateToStr(d); |
---|
165 | dayStr = $.A({href:'#', rel:dStr}, curDay+1); |
---|
166 | $(dayStr).click(function(e) |
---|
167 | { |
---|
168 | $.datePicker.selectDate($.attr(this, 'rel'), this); |
---|
169 | return false; |
---|
170 | }); |
---|
171 | if (_selectedDate && _selectedDate==dStr) { |
---|
172 | $(dayStr).addClass('selected'); |
---|
173 | } |
---|
174 | } |
---|
175 | |
---|
176 | if (thisMonth && curDay+1 == todayDate) { |
---|
177 | atts.className = 'today'; |
---|
178 | } |
---|
179 | thisRow.appendChild($.TD(atts, dayStr)); |
---|
180 | curDay++; |
---|
181 | } |
---|
182 | tBody.appendChild(thisRow); |
---|
183 | } |
---|
184 | |
---|
185 | jCalDiv.append( |
---|
186 | $.TABLE({cellspacing:2}, $.THEAD({}, headRow), tBody), |
---|
187 | prevLinkDiv, |
---|
188 | nextLinkDiv |
---|
189 | ); |
---|
190 | |
---|
191 | if ($.browser.msie) { |
---|
192 | |
---|
193 | // we put a styled iframe behind the calendar so HTML SELECT elements don't show through |
---|
194 | jCalDiv.append(document.createElement('iframe')); |
---|
195 | |
---|
196 | } |
---|
197 | jCalDiv.css({'display':'block'}); |
---|
198 | return calDiv; |
---|
199 | }; |
---|
200 | var _draw = function(c) |
---|
201 | { |
---|
202 | // explicitly empty the calendar before removing it to reduce the (MASSIVE!) memory leak in IE |
---|
203 | // still not perfect but a lot better! |
---|
204 | // Strangely if you chain the methods it reacts differently - when chained opening the calendar on |
---|
205 | // IE uses a bunch of memory and pressing next/prev doubles this memory. When you close the calendar |
---|
206 | // the memory is freed. If they aren't chained then pressing next or previous doesn't double the used |
---|
207 | // memory so only one chunk of memory is used when you open the calendar (which is also freed when you |
---|
208 | // close the calendar). |
---|
209 | $('div.popup-calendar a', _openCal).unbind(); |
---|
210 | $('div.popup-calendar', _openCal).empty(); |
---|
211 | $('div.popup-calendar', _openCal).remove(); |
---|
212 | _openCal.append(c); |
---|
213 | }; |
---|
214 | var _closeDatePicker = function() |
---|
215 | { |
---|
216 | $('div.popup-calendar a', _openCal).unbind(); |
---|
217 | $('div.popup-calendar', _openCal).empty(); |
---|
218 | $('div.popup-calendar', _openCal).css({'display':'none'}); |
---|
219 | |
---|
220 | /* |
---|
221 | if ($.browser.msie) { |
---|
222 | _openCal.unbind('keypress', _handleKeys); |
---|
223 | } else { |
---|
224 | $(window).unbind('keypress', _handleKeys); |
---|
225 | } |
---|
226 | */ |
---|
227 | $(document).unbind('mousedown', _checkMouse); |
---|
228 | delete _openCal; |
---|
229 | _openCal = null; |
---|
230 | }; |
---|
231 | var _handleKeys = function(e) |
---|
232 | { |
---|
233 | var key = e.keyCode ? e.keyCode : (e.which ? e.which: 0); |
---|
234 | //console.log('KEY!! ' + key); |
---|
235 | if (key == 27) { |
---|
236 | _closeDatePicker(); |
---|
237 | } |
---|
238 | return false; |
---|
239 | }; |
---|
240 | var _checkMouse = function(e) |
---|
241 | { |
---|
242 | var target = $.browser.msie ? window.event.srcElement : e.target; |
---|
243 | var cp = $(target).findClosestParent('div.popup-calendar'); |
---|
244 | if (cp.get(0).className != 'date-picker-holder') { |
---|
245 | _closeDatePicker(); |
---|
246 | } |
---|
247 | }; |
---|
248 | |
---|
249 | return { |
---|
250 | show: function() |
---|
251 | { |
---|
252 | if (_openCal) { |
---|
253 | _closeDatePicker(); |
---|
254 | } |
---|
255 | this.blur(); |
---|
256 | var input = $('input', $(this).findClosestParent('input'))[0]; |
---|
257 | _firstDate = input._startDate; |
---|
258 | _lastDate = input._endDate; |
---|
259 | _openCal = $(this).findClosestParent('div.popup-calendar'); |
---|
260 | var d = $(input).val(); |
---|
261 | if (d != '') { |
---|
262 | if (_dateToStr(_strToDate(d)) == d) { |
---|
263 | _selectedDate = d; |
---|
264 | _draw(_getCalendarDiv(_strToDate(d))); |
---|
265 | } else { |
---|
266 | // invalid date in the input field - just default to this month |
---|
267 | _selectedDate = false; |
---|
268 | _draw(_getCalendarDiv()); |
---|
269 | } |
---|
270 | } else { |
---|
271 | _selectedDate = false; |
---|
272 | _draw(_getCalendarDiv()); |
---|
273 | } |
---|
274 | /* |
---|
275 | if ($.browser == "msie") { |
---|
276 | _openCal.bind('keypress', _handleKeys); |
---|
277 | } else { |
---|
278 | $(window).bind('keypress', _handleKeys); |
---|
279 | } |
---|
280 | */ |
---|
281 | $(document).bind('mousedown', _checkMouse); |
---|
282 | }, |
---|
283 | changeMonth: function(d, e) |
---|
284 | { |
---|
285 | |
---|
286 | _draw(_getCalendarDiv(d)); |
---|
287 | }, |
---|
288 | selectDate: function(d, ele) |
---|
289 | { |
---|
290 | selectedDate = d; |
---|
291 | $('input', $(ele).findClosestParent('input')).val(d); |
---|
292 | _closeDatePicker(ele); |
---|
293 | }, |
---|
294 | closeCalendar: function() |
---|
295 | { |
---|
296 | _closeDatePicker(this); |
---|
297 | }, |
---|
298 | setInited: function(i) |
---|
299 | { |
---|
300 | i._inited = true; |
---|
301 | }, |
---|
302 | isInited: function(i) |
---|
303 | { |
---|
304 | return i._inited != undefined; |
---|
305 | }, |
---|
306 | setDateFormat: function(format) |
---|
307 | { |
---|
308 | // set's the format that selected dates are returned in. |
---|
309 | // options are 'dd/mm/yyyy' (european), 'mm/dd/yyyy' (americian) and 'yyyy-mm-dd' (unicode) |
---|
310 | dateFormat = format; |
---|
311 | }, |
---|
312 | /** |
---|
313 | * Function: setLanguageStrings |
---|
314 | * |
---|
315 | * Allows you to localise the calendar by passing in relevant text for the english strings in the plugin. |
---|
316 | * |
---|
317 | * Arguments: |
---|
318 | * days - Array, e.g. ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] |
---|
319 | * months - Array, e.g. ['January', 'Febuary', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; |
---|
320 | * navLinks - Object, e.g. {p:'Prev', n:'Next', c:'Close'} |
---|
321 | **/ |
---|
322 | setLanguageStrings: function(aDays, aMonths, aNavLinks) |
---|
323 | { |
---|
324 | days = aDays; |
---|
325 | months = aMonths; |
---|
326 | navLinks = aNavLinks; |
---|
327 | }, |
---|
328 | /** |
---|
329 | * Function: setDateWindow |
---|
330 | * |
---|
331 | * Used internally to set the start and end dates for a given date select |
---|
332 | * |
---|
333 | * Arguments: |
---|
334 | * i - The id of the INPUT element this date window is for |
---|
335 | * w - The date window - an object containing startDate and endDate properties |
---|
336 | * each in the current format as set in a call to setDateFormat (or in the |
---|
337 | * default format dd/mm/yyyy if setDateFormat hasn't been called). |
---|
338 | * e.g. {startDate:'01/03/2006', endDate:'11/04/2006} |
---|
339 | **/ |
---|
340 | setDateWindow: function(i, w) |
---|
341 | { |
---|
342 | if (w == undefined) w = {}; |
---|
343 | if (w.startDate == undefined) { |
---|
344 | i._startDate = new Date(); |
---|
345 | } else { |
---|
346 | i._startDate = _strToDate(w.startDate); |
---|
347 | } |
---|
348 | if (w.endDate == undefined) { |
---|
349 | i._endDate = new Date(); |
---|
350 | i._endDate.setFullYear(i._endDate.getFullYear()+5); |
---|
351 | } else { |
---|
352 | i._endDate = _strToDate(w.endDate); |
---|
353 | }; |
---|
354 | } |
---|
355 | }; |
---|
356 | }(); |
---|
357 | $.fn.findClosestParent = function(s) |
---|
358 | { |
---|
359 | var ele = this; |
---|
360 | while (true) { |
---|
361 | if ($(s, ele).length > 0) { |
---|
362 | return (ele); |
---|
363 | } |
---|
364 | ele = ele.parent(); |
---|
365 | if(ele[0].length == 0) { |
---|
366 | return false; |
---|
367 | } |
---|
368 | } |
---|
369 | }; |
---|
370 | $.fn.datePicker = function(a) |
---|
371 | { |
---|
372 | this.each(function() { |
---|
373 | if(this.nodeName.toLowerCase() != 'input') return; |
---|
374 | $.datePicker.setDateWindow(this, a); |
---|
375 | if (!$.datePicker.isInited(this)) { |
---|
376 | var calBut = $.A({href:'javascript:;', className:'date-picker', title:'Choose date'}, $.SPAN({}, 'Choose date')); |
---|
377 | $(calBut).click($.datePicker.show); |
---|
378 | $(this).wrap( |
---|
379 | '<div class="date-picker-holder"></div>' |
---|
380 | ).before( |
---|
381 | $.DIV({className:'popup-calendar'}) |
---|
382 | ).after( |
---|
383 | calBut |
---|
384 | ); |
---|
385 | $.datePicker.setInited(this); |
---|
386 | } |
---|
387 | }); |
---|
388 | |
---|
389 | }; |
---|
390 | /* |
---|
391 | <!-- Generated calendar HTML looks like this - style with CSS --> |
---|
392 | <div class="popup-calendar"> |
---|
393 | <div class="link-close"><a href="#">Close</a></div> |
---|
394 | <h3>July 2006</h3> |
---|
395 | <table cellspacing="2"> |
---|
396 | <thead> |
---|
397 | <tr> |
---|
398 | <th scope="col" abbr="Monday" title="Monday">M</th> |
---|
399 | <th scope="col" abbr="Tuesday" title="Tuesday">T</th> |
---|
400 | <th scope="col" abbr="Wednesday" title="Wednesday">W</th> |
---|
401 | <th scope="col" abbr="Thursday" title="Thursday">T</th> |
---|
402 | <th scope="col" abbr="Friday" title="Friday">F</th> |
---|
403 | <th scope="col" abbr="Saturday" title="Saturday">S</th> |
---|
404 | <th scope="col" abbr="Sunday" title="Sunday">S</th> |
---|
405 | </tr> |
---|
406 | </thead> |
---|
407 | <tbody> |
---|
408 | <tr> |
---|
409 | <td> </td> |
---|
410 | <td> </td> |
---|
411 | <td> </td> |
---|
412 | <td class="inactive">1</td> |
---|
413 | <td class="inactive">2</td> |
---|
414 | <td class="inactive">3</td> |
---|
415 | <td class="inactive">4</td> |
---|
416 | </tr> |
---|
417 | <tr> |
---|
418 | <td class="inactive">5</td> |
---|
419 | <td class="inactive">6</td> |
---|
420 | <td class="inactive">7</td> |
---|
421 | <td class="today"><a href="#">8</a></td> |
---|
422 | <td><a href="#">9</a></td> |
---|
423 | <td><a href="#">10</a></td> |
---|
424 | <td><a href="#">11</a></td> |
---|
425 | </tr> |
---|
426 | <tr> |
---|
427 | <td><a href="#">12</a></td> |
---|
428 | <td><a href="#">13</a></td> |
---|
429 | <td><a href="#">14</a></td> |
---|
430 | <td><a href="#">15</a></td> |
---|
431 | <td><a href="#">16</a></td> |
---|
432 | <td><a href="#">17</a></td> |
---|
433 | <td><a href="#" class="selected">18</a></td> |
---|
434 | </tr> |
---|
435 | <tr> |
---|
436 | <td><a href="#">19</a></td> |
---|
437 | <td><a href="#">20</a></td> |
---|
438 | <td><a href="#">21</a></td> |
---|
439 | <td><a href="#">22</a></td> |
---|
440 | <td><a href="#">23</a></td> |
---|
441 | <td><a href="#">24</a></td> |
---|
442 | <td><a href="#">25</a></td> |
---|
443 | </tr> |
---|
444 | <tr> |
---|
445 | <td><a href="#">26</a></td> |
---|
446 | <td><a href="#">27</a></td> |
---|
447 | <td><a href="#">28</a></td> |
---|
448 | <td><a href="#">29</a></td> |
---|
449 | <td><a href="#">30</a></td> |
---|
450 | <td> </td> |
---|
451 | <td> </td> |
---|
452 | </tr> |
---|
453 | </tbody> |
---|
454 | </table> |
---|
455 | <div class="link-prev"><a href="#">Prev</a></div> |
---|
456 | <div class="link-next"><a href="#">Next</a></div> |
---|
457 | </div> |
---|
458 | */ |
---|