5c126550be3435c1faacbd6bf6c70bc3aca0601f
[atutor.git] / mods / photo_album / fluid / component-templates / js / jquery.ui-1.0 / ui.calendar.js
1 /* jQuery Calendar v2.7\r
2    Written by Marc Grabanski (m@marcgrabanski.com) and enhanced by Keith Wood (kbwood@iprimus.com.au).\r
3 \r
4    Copyright (c) 2007 Marc Grabanski (http://marcgrabanski.com/code/jquery-calendar)\r
5    Dual licensed under the GPL (http://www.gnu.org/licenses/gpl-3.0.txt) and \r
6    CC (http://creativecommons.org/licenses/by/3.0/) licenses. "Share or Remix it but please Attribute the authors."\r
7    Date: 09-03-2007  */\r
8 \r
9 /* PopUp Calendar manager.\r
10    Use the singleton instance of this class, popUpCal, to interact with the calendar.\r
11    Settings for (groups of) calendars are maintained in an instance object\r
12    (PopUpCalInstance), allowing multiple different settings on the same page. */\r
13 function PopUpCal() {\r
14         this._nextId = 0; // Next ID for a calendar instance\r
15         this._inst = []; // List of instances indexed by ID\r
16         this._curInst = null; // The current instance in use\r
17         this._disabledInputs = []; // List of calendar inputs that have been disabled\r
18         this._popUpShowing = false; // True if the popup calendar is showing , false if not\r
19         this._inDialog = false; // True if showing within a "dialog", false if not\r
20         this.regional = []; // Available regional settings, indexed by language code\r
21         this.regional[''] = { // Default regional settings\r
22                 clearText: 'Clear', // Display text for clear link\r
23                 closeText: 'Close', // Display text for close link\r
24                 prevText: '<Prev', // Display text for previous month link\r
25                 nextText: 'Next>', // Display text for next month link\r
26                 currentText: 'Today', // Display text for current month link\r
27                 dayNames: ['Su','Mo','Tu','We','Th','Fr','Sa'], // Names of days starting at Sunday\r
28                 monthNames: ['January','February','March','April','May','June',\r
29                         'July','August','September','October','November','December'], // Names of months\r
30                 dateFormat: 'DMY/' // First three are day, month, year in the required order,\r
31                         // fourth (optional) is the separator, e.g. US would be 'MDY/', ISO would be 'YMD-'\r
32         };\r
33         this._defaults = { // Global defaults for all the calendar instances\r
34                 autoPopUp: 'focus', // 'focus' for popup on focus,\r
35                         // 'button' for trigger button, or 'both' for either\r
36                 defaultDate: null, // Used when field is blank: actual date,\r
37                         // +/-number for offset from today, null for today\r
38                 appendText: '', // Display text following the input box, e.g. showing the format\r
39                 buttonText: '...', // Text for trigger button\r
40                 buttonImage: '', // URL for trigger button image\r
41                 buttonImageOnly: false, // True if the image appears alone, false if it appears on a button\r
42                 closeAtTop: true, // True to have the clear/close at the top,\r
43                         // false to have them at the bottom\r
44                 hideIfNoPrevNext: false, // True to hide next/previous month links\r
45                         // if not applicable, false to just disable them\r
46                 changeMonth: true, // True if month can be selected directly, false if only prev/next\r
47                 changeYear: true, // True if year can be selected directly, false if only prev/next\r
48                 yearRange: '-10:+10', // Range of years to display in drop-down,\r
49                         // either relative to current year (-nn:+nn) or absolute (nnnn:nnnn)\r
50                 firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...\r
51                 changeFirstDay: true, // True to click on day name to change, false to remain as set\r
52                 showOtherMonths: false, // True to show dates in other months, false to leave blank\r
53                 minDate: null, // The earliest selectable date, or null for no limit\r
54                 maxDate: null, // The latest selectable date, or null for no limit\r
55                 speed: 'medium', // Speed of display/closure\r
56                 customDate: null, // Function that takes a date and returns an array with\r
57                         // [0] = true if selectable, false if not,\r
58                         // [1] = custom CSS class name(s) or '', e.g. popUpCal.noWeekends\r
59                 fieldSettings: null, // Function that takes an input field and\r
60                         // returns a set of custom settings for the calendar\r
61                 onSelect: null // Define a callback function when a date is selected\r
62         };\r
63         $.extend(this._defaults, this.regional['']);\r
64         this._calendarDiv = $('<div id="calendar_div"></div>');\r
65         $(document.body).append(this._calendarDiv);\r
66         $(document.body).mousedown(this._checkExternalClick);\r
67 }\r
68 \r
69 $.extend(PopUpCal.prototype, {\r
70         /* Class name added to elements to indicate already configured with a calendar. */\r
71         markerClassName: 'hasCalendar',\r
72         \r
73         /* Register a new calendar instance - with custom settings. */\r
74         _register: function(inst) {\r
75                 var id = this._nextId++;\r
76                 this._inst[id] = inst;\r
77                 return id;\r
78         },\r
79 \r
80         /* Retrieve a particular calendar instance based on its ID. */\r
81         _getInst: function(id) {\r
82                 return this._inst[id] || id;\r
83         },\r
84 \r
85         /* Override the default settings for all instances of the calendar. \r
86            @param  settings  object - the new settings to use as defaults (anonymous object)\r
87            @return void */\r
88         setDefaults: function(settings) {\r
89                 extendRemove(this._defaults, settings || {});\r
90         },\r
91 \r
92         /* Handle keystrokes. */\r
93         _doKeyDown: function(e) {\r
94                 var inst = popUpCal._getInst(this._calId);\r
95                 if (popUpCal._popUpShowing) {\r
96                         switch (e.keyCode) {\r
97                                 case 9:  popUpCal.hideCalendar(inst, '');\r
98                                                 break; // hide on tab out\r
99                                 case 13: popUpCal._selectDate(inst);\r
100                                                 break; // select the value on enter\r
101                                 case 27: popUpCal.hideCalendar(inst, inst._get('speed'));\r
102                                                 break; // hide on escape\r
103                                 case 33: popUpCal._adjustDate(inst, -1, (e.ctrlKey ? 'Y' : 'M'));\r
104                                                 break; // previous month/year on page up/+ ctrl\r
105                                 case 34: popUpCal._adjustDate(inst, +1, (e.ctrlKey ? 'Y' : 'M'));\r
106                                                 break; // next month/year on page down/+ ctrl\r
107                                 case 35: if (e.ctrlKey) popUpCal._clearDate(inst);\r
108                                                 break; // clear on ctrl+end\r
109                                 case 36: if (e.ctrlKey) popUpCal._gotoToday(inst);\r
110                                                 break; // current on ctrl+home\r
111                                 case 37: if (e.ctrlKey) popUpCal._adjustDate(inst, -1, 'D');\r
112                                                 break; // -1 day on ctrl+left\r
113                                 case 38: if (e.ctrlKey) popUpCal._adjustDate(inst, -7, 'D');\r
114                                                 break; // -1 week on ctrl+up\r
115                                 case 39: if (e.ctrlKey) popUpCal._adjustDate(inst, +1, 'D');\r
116                                                 break; // +1 day on ctrl+right\r
117                                 case 40: if (e.ctrlKey) popUpCal._adjustDate(inst, +7, 'D');\r
118                                                 break; // +1 week on ctrl+down\r
119                         }\r
120                 }\r
121                 else if (e.keyCode == 36 && e.ctrlKey) { // display the calendar on ctrl+home\r
122                         popUpCal.showFor(this);\r
123                 }\r
124         },\r
125 \r
126         /* Filter entered characters. */\r
127         _doKeyPress: function(e) {\r
128                 var inst = popUpCal._getInst(this._calId);\r
129                 var chr = String.fromCharCode(e.charCode == undefined ? e.keyCode : e.charCode);\r
130                 return (chr < ' ' || chr == inst._get('dateFormat').charAt(3) ||\r
131                         (chr >= '0' && chr <= '9')); // only allow numbers and separator\r
132         },\r
133 \r
134         /* Attach the calendar to an input field. */\r
135         _connectCalendar: function(target, inst) {\r
136                 var input = $(target);\r
137                 if (this._hasClass(input, this.markerClassName)) {\r
138                         return;\r
139                 }\r
140                 var appendText = inst._get('appendText');\r
141                 if (appendText) {\r
142                         input.after('<span class="calendar_append">' + appendText + '</span>');\r
143                 }\r
144                 var autoPopUp = inst._get('autoPopUp');\r
145                 if (autoPopUp == 'focus' || autoPopUp == 'both') { // pop-up calendar when in the marked field\r
146                         input.focus(this.showFor);\r
147                 }\r
148                 if (autoPopUp == 'button' || autoPopUp == 'both') { // pop-up calendar when button clicked\r
149                         var buttonText = inst._get('buttonText');\r
150                         var buttonImage = inst._get('buttonImage');\r
151                         var buttonImageOnly = inst._get('buttonImageOnly');\r
152                         var trigger = $(buttonImageOnly ? '<img class="calendar_trigger" src="' +\r
153                                 buttonImage + '" alt="' + buttonText + '" title="' + buttonText + '"/>' :\r
154                                 '<button type="button" class="calendar_trigger">' + (buttonImage != '' ?\r
155                                 '<img src="' + buttonImage + '" alt="' + buttonText + '" title="' + buttonText + '"/>' :\r
156                                 buttonText) + '</button>');\r
157                         input.wrap('<span class="calendar_wrap"></span>').after(trigger);\r
158                         trigger.click(this.showFor);\r
159                 }\r
160                 input.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress);\r
161                 input[0]._calId = inst._id;\r
162         },\r
163 \r
164         /* Attach an inline calendar to a div. */\r
165         _inlineCalendar: function(target, inst) {\r
166                 var input = $(target);\r
167                 if (this._hasClass(input, this.markerClassName)) {\r
168                         return;\r
169                 }\r
170                 input.addClass(this.markerClassName).append(inst._calendarDiv);\r
171                 input[0]._calId = inst._id;\r
172         },\r
173 \r
174         /* Does this element have a particular class? */\r
175         _hasClass: function(element, className) {\r
176                 var classes = element.attr('class');\r
177                 return (classes && classes.indexOf(className) > -1);\r
178         },\r
179 \r
180         /* Pop-up the calendar in a "dialog" box.\r
181            @param  dateText  string - the initial date to display (in the current format)\r
182            @param  onSelect  function - the function(dateText) to call when a date is selected\r
183            @param  settings  object - update the dialog calendar instance's settings (anonymous object)\r
184            @param  pos       int[2] - coordinates for the dialog's position within the screen\r
185                         leave empty for default (screen centre)\r
186            @return void */\r
187         dialogCalendar: function(dateText, onSelect, settings, pos) {\r
188                 var inst = this._dialogInst; // internal instance\r
189                 if (!inst) {\r
190                         inst = this._dialogInst = new PopUpCalInstance({}, false);\r
191                         this._dialogInput = $('<input type="text" size="1" style="position: absolute; top: -100px;"/>');\r
192                         this._dialogInput.keydown(this._doKeyDown);\r
193                         $('body').append(this._dialogInput);\r
194                         this._dialogInput[0]._calId = inst._id;\r
195                 }\r
196                 extendRemove(inst._settings, settings || {});\r
197                 this._dialogInput.val(dateText);\r
198                 \r
199                 /*      Cross Browser Positioning */\r
200                 if (self.innerHeight) { // all except Explorer\r
201                         windowWidth = self.innerWidth;\r
202                         windowHeight = self.innerHeight;\r
203                 } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode\r
204                         windowWidth = document.documentElement.clientWidth;\r
205                         windowHeight = document.documentElement.clientHeight;\r
206                 } else if (document.body) { // other Explorers\r
207                         windowWidth = document.body.clientWidth;\r
208                         windowHeight = document.body.clientHeight;\r
209                 } \r
210                 this._pos = pos || // should use actual width/height below\r
211                         [(windowWidth / 2) - 100, (windowHeight / 2) - 100];\r
212 \r
213                 // move input on screen for focus, but hidden behind dialog\r
214                 this._dialogInput.css('left', this._pos[0] + 'px').css('top', this._pos[1] + 'px');\r
215                 inst._settings.onSelect = onSelect;\r
216                 this._inDialog = true;\r
217                 this._calendarDiv.addClass('calendar_dialog');\r
218                 this.showFor(this._dialogInput[0]);\r
219                 if ($.blockUI) {\r
220                         $.blockUI(this._calendarDiv);\r
221                 }\r
222         },\r
223 \r
224         /* Enable the input field(s) for entry.\r
225            @param  inputs  element/object - single input field or jQuery collection of input fields\r
226            @return void */\r
227         enableFor: function(inputs) {\r
228                 inputs = (inputs.jquery ? inputs : $(inputs));\r
229                 inputs.each(function() {\r
230                         this.disabled = false;\r
231                         $('../button.calendar_trigger', this).each(function() { this.disabled = false; });\r
232                         $('../img.calendar_trigger', this).css({opacity:'1.0',cursor:''});\r
233                         var $this = this;\r
234                         popUpCal._disabledInputs = $.map(popUpCal._disabledInputs,\r
235                                 function(value) { return (value == $this ? null : value); }); // delete entry\r
236                 });\r
237         },\r
238 \r
239         /* Disable the input field(s) from entry.\r
240            @param  inputs  element/object - single input field or jQuery collection of input fields\r
241            @return void */\r
242         disableFor: function(inputs) {\r
243                 inputs = (inputs.jquery ? inputs : $(inputs));\r
244                 inputs.each(function() {\r
245                         this.disabled = true;\r
246                         $('../button.calendar_trigger', this).each(function() { this.disabled = true; });\r
247                         $('../img.calendar_trigger', this).css({opacity:'0.5',cursor:'default'});\r
248                         var $this = this;\r
249                         popUpCal._disabledInputs = $.map(popUpCal._disabledInputs,\r
250                                 function(value) { return (value == $this ? null : value); }); // delete entry\r
251                         popUpCal._disabledInputs[popUpCal._disabledInputs.length] = this;\r
252                 });\r
253         },\r
254 \r
255         /* Update the settings for a calendar attached to an input field or division.\r
256            @param  control   element - the input field or div/span attached to the calendar or\r
257                              string - the ID or other jQuery selector of the input field\r
258            @param  settings  object - the new settings to update\r
259            @return void */\r
260         reconfigureFor: function(control, settings) {\r
261                 control = (typeof control == 'string' ? $(control)[0] : control);\r
262                 var inst = this._getInst(control._calId);\r
263                 if (inst) {\r
264                         extendRemove(inst._settings, settings || {});\r
265                         this._updateCalendar(inst);\r
266                 }\r
267         },\r
268 \r
269         /* Set the date for a calendar attached to an input field or division.\r
270            @param  control  element - the input field or div/span attached to the calendar\r
271            @param  date     Date - the new date\r
272            @return void */\r
273         setDateFor: function(control, date) {\r
274                 var inst = this._getInst(control._calId);\r
275                 if (inst) {\r
276                         inst._setDate(date);\r
277                 }\r
278         },\r
279 \r
280         /* Retrieve the date for a calendar attached to an input field or division.\r
281            @param  control  element - the input field or div/span attached to the calendar\r
282            @return Date - the current date */\r
283         getDateFor: function(control) {\r
284                 var inst = this._getInst(control._calId);\r
285                 return (inst ? inst._getDate() : null);\r
286         },\r
287 \r
288         /* Pop-up the calendar for a given input field.\r
289            @param  target  element - the input field attached to the calendar\r
290            @return void */\r
291         showFor: function(target) {\r
292                 var input = (target.nodeName && target.nodeName.toLowerCase() == 'input' ? target : this);\r
293                 if (input.nodeName.toLowerCase() != 'input') { // find from button/image trigger\r
294                         input = $('input', input.parentNode)[0];\r
295                 }\r
296                 if (popUpCal._lastInput == input) { // already here\r
297                         return;\r
298                 }\r
299                 for (var i = 0; i < popUpCal._disabledInputs.length; i++) {  // check not disabled\r
300                         if (popUpCal._disabledInputs[i] == input) {\r
301                                 return;\r
302                         }\r
303                 }\r
304                 var inst = popUpCal._getInst(input._calId);\r
305                 var fieldSettings = inst._get('fieldSettings');\r
306                 extendRemove(inst._settings, (fieldSettings ? fieldSettings(input) : {}));\r
307                 popUpCal.hideCalendar(inst, '');\r
308                 popUpCal._lastInput = input;\r
309                 inst._setDateFromField(input);\r
310                 if (popUpCal._inDialog) { // hide cursor\r
311                         input.value = '';\r
312                 }\r
313                 if (!popUpCal._pos) { // position below input\r
314                         popUpCal._pos = popUpCal._findPos(input);\r
315                         popUpCal._pos[1] += input.offsetHeight;\r
316                 }\r
317                 inst._calendarDiv.css('position', (popUpCal._inDialog && $.blockUI ? 'static' : 'absolute')).\r
318                         css('left', popUpCal._pos[0] + 'px').css('top', popUpCal._pos[1] + 'px');\r
319                 popUpCal._pos = null;\r
320                 popUpCal._showCalendar(inst);\r
321         },\r
322 \r
323         /* Construct and display the calendar. */\r
324         _showCalendar: function(id) {\r
325                 var inst = this._getInst(id);\r
326                 popUpCal._updateCalendar(inst);\r
327                 if (!inst._inline) {\r
328                         var speed = inst._get('speed');\r
329                         inst._calendarDiv.show(speed, function() {\r
330                                 popUpCal._popUpShowing = true;\r
331                                 popUpCal._afterShow(inst);\r
332                         });\r
333                         if (speed == '') {\r
334                                 popUpCal._popUpShowing = true;\r
335                                 popUpCal._afterShow(inst);\r
336                         }\r
337                         if (inst._input[0].type != 'hidden') {\r
338                                 inst._input[0].focus();\r
339                         }\r
340                         this._curInst = inst;\r
341                 }\r
342         },\r
343 \r
344         /* Generate the calendar content. */\r
345         _updateCalendar: function(inst) {\r
346                 inst._calendarDiv.empty().append(inst._generateCalendar());\r
347                 if (inst._input && inst._input[0].type != 'hidden') {\r
348                         inst._input[0].focus();\r
349                 }\r
350         },\r
351 \r
352         /* Tidy up after displaying the calendar. */\r
353         _afterShow: function(inst) {\r
354                 if ($.browser.msie) { // fix IE < 7 select problems\r
355                         $('#calendar_cover').css({width: inst._calendarDiv[0].offsetWidth + 4,\r
356                                 height: inst._calendarDiv[0].offsetHeight + 4});\r
357                 }\r
358                 // re-position on screen if necessary\r
359                 var calDiv = inst._calendarDiv[0];\r
360                 var pos = popUpCal._findPos(inst._input[0]);\r
361                 // Get browser width and X value (IE6+, FF, Safari, Opera)\r
362                 if( typeof( window.innerWidth ) == 'number' ) {\r
363                         browserWidth = window.innerWidth;\r
364                 } else {\r
365                         browserWidth = document.documentElement.clientWidth;\r
366                 }\r
367                 if ( document.documentElement && (document.documentElement.scrollLeft)) {\r
368                         browserX = document.documentElement.scrollLeft; \r
369                 } else {\r
370                         browserX = document.body.scrollLeft;\r
371                 }\r
372                 // Reposition calendar if outside the browser window.\r
373                 if ((calDiv.offsetLeft + calDiv.offsetWidth) >\r
374                                 (browserWidth + browserX) ) {\r
375                         inst._calendarDiv.css('left', (pos[0] + inst._input[0].offsetWidth - calDiv.offsetWidth) + 'px');\r
376                 }\r
377                 // Get browser height and Y value (IE6+, FF, Safari, Opera)\r
378                 if( typeof( window.innerHeight ) == 'number' ) {\r
379                         browserHeight = window.innerHeight;\r
380                 } else {\r
381                         browserHeight = document.documentElement.clientHeight;\r
382                 }\r
383                 if ( document.documentElement && (document.documentElement.scrollTop)) {\r
384                         browserTopY = document.documentElement.scrollTop;\r
385                 } else {\r
386                         browserTopY = document.body.scrollTop;\r
387                 }\r
388                 // Reposition calendar if outside the browser window.\r
389                 if ((calDiv.offsetTop + calDiv.offsetHeight) >\r
390                                 (browserTopY + browserHeight) ) {\r
391                         inst._calendarDiv.css('top', (pos[1] - calDiv.offsetHeight) + 'px');\r
392                 }\r
393         },\r
394 \r
395         /* Hide the calendar from view.\r
396            @param  id     string/object - the ID of the current calendar instance,\r
397                         or the instance itself\r
398            @param  speed  string - the speed at which to close the calendar\r
399            @return void */\r
400         hideCalendar: function(id, speed) {\r
401                 var inst = this._getInst(id);\r
402                 if (popUpCal._popUpShowing) {\r
403                         speed = (speed != null ? speed : inst._get('speed'));\r
404                         inst._calendarDiv.hide(speed, function() {\r
405                                 popUpCal._tidyDialog(inst);\r
406                         });\r
407                         if (speed == '') {\r
408                                 popUpCal._tidyDialog(inst);\r
409                         }\r
410                         popUpCal._popUpShowing = false;\r
411                         popUpCal._lastInput = null;\r
412                         inst._settings.prompt = null;\r
413                         if (popUpCal._inDialog) {\r
414                                 popUpCal._dialogInput.css('position', 'absolute').\r
415                                         css('left', '0px').css('top', '-100px');\r
416                                 if ($.blockUI) {\r
417                                         $.unblockUI();\r
418                                         $('body').append(this._calendarDiv);\r
419                                 }\r
420                         }\r
421                         popUpCal._inDialog = false;\r
422                 }\r
423                 popUpCal._curInst = null;\r
424         },\r
425 \r
426         /* Tidy up after a dialog display. */\r
427         _tidyDialog: function(inst) {\r
428                 inst._calendarDiv.removeClass('calendar_dialog');\r
429                 $('.calendar_prompt', inst._calendarDiv).remove();\r
430         },\r
431 \r
432         /* Close calendar if clicked elsewhere. */\r
433         _checkExternalClick: function(event) {\r
434                 if (!popUpCal._curInst) {\r
435                         return;\r
436                 }\r
437                 var target = $(event.target);\r
438                 if( (target.parents("#calendar_div").length == 0)\r
439                         && (target.attr('class') != 'calendar_trigger')\r
440                         && popUpCal._popUpShowing \r
441                         && !(popUpCal._inDialog && $.blockUI) )\r
442                 {\r
443                         popUpCal.hideCalendar(popUpCal._curInst, '');\r
444                 }\r
445         },\r
446 \r
447         /* Adjust one of the date sub-fields. */\r
448         _adjustDate: function(id, offset, period) {\r
449                 var inst = this._getInst(id);\r
450                 inst._adjustDate(offset, period);\r
451                 this._updateCalendar(inst);\r
452         },\r
453 \r
454         /* Action for current link. */\r
455         _gotoToday: function(id) {\r
456                 var date = new Date();\r
457                 var inst = this._getInst(id);\r
458                 inst._selectedDay = date.getDate();\r
459                 inst._selectedMonth = date.getMonth();\r
460                 inst._selectedYear = date.getFullYear();\r
461                 this._adjustDate(inst);\r
462         },\r
463 \r
464         /* Action for selecting a new month/year. */\r
465         _selectMonthYear: function(id, select, period) {\r
466                 var inst = this._getInst(id);\r
467                 inst._selectingMonthYear = false;\r
468                 inst[period == 'M' ? '_selectedMonth' : '_selectedYear'] =\r
469                         select.options[select.selectedIndex].value - 0;\r
470                 this._adjustDate(inst);\r
471         },\r
472 \r
473         /* Restore input focus after not changing month/year. */\r
474         _clickMonthYear: function(id) {\r
475                 var inst = this._getInst(id);\r
476                 if (inst._input && inst._selectingMonthYear && !$.browser.msie) {\r
477                         inst._input[0].focus();\r
478                 }\r
479                 inst._selectingMonthYear = !inst._selectingMonthYear;\r
480         },\r
481 \r
482         /* Action for changing the first week day. */\r
483         _changeFirstDay: function(id, a) {\r
484                 var inst = this._getInst(id);\r
485                 var dayNames = inst._get('dayNames');\r
486                 var value = a.firstChild.nodeValue;\r
487                 for (var i = 0; i < 7; i++) {\r
488                         if (dayNames[i] == value) {\r
489                                 inst._settings.firstDay = i;\r
490                                 break;\r
491                         }\r
492                 }\r
493                 this._updateCalendar(inst);\r
494         },\r
495 \r
496         /* Action for selecting a day. */\r
497         _selectDay: function(id, td) {\r
498                 var inst = this._getInst(id);\r
499                 inst._selectedDay = $("a", td).html();\r
500                 this._selectDate(id);\r
501         },\r
502 \r
503         /* Erase the input field and hide the calendar. */\r
504         _clearDate: function(id) {\r
505                 this._selectDate(id, '');\r
506         },\r
507 \r
508         /* Update the input field with the selected date. */\r
509         _selectDate: function(id, dateStr) {\r
510                 var inst = this._getInst(id);\r
511                 dateStr = (dateStr != null ? dateStr : inst._formatDate());\r
512                 if (inst._input) {\r
513                         inst._input.val(dateStr);\r
514                 }\r
515                 var onSelect = inst._get('onSelect');\r
516                 if (onSelect) {\r
517                         onSelect(dateStr, inst);  // trigger custom callback\r
518                 }\r
519                 else {\r
520                         inst._input.trigger('change'); // fire the change event\r
521                 }\r
522                 if (inst._inline) {\r
523                         this._updateCalendar(inst);\r
524                 }\r
525                 else {\r
526                         this.hideCalendar(inst, inst._get('speed'));\r
527                 }\r
528         },\r
529 \r
530         /* Set as customDate function to prevent selection of weekends.\r
531            @param  date  Date - the date to customise\r
532            @return [boolean, string] - is this date selectable?, what is its CSS class? */\r
533         noWeekends: function(date) {\r
534                 var day = date.getDay();\r
535                 return [(day > 0 && day < 6), ''];\r
536         },\r
537 \r
538         /* Find an object's position on the screen. */\r
539         _findPos: function(obj) {\r
540                 while (obj && (obj.type == 'hidden' || obj.nodeType != 1)) {\r
541                         obj = obj.nextSibling;\r
542                 }\r
543                 var curleft = curtop = 0;\r
544                 if (obj && obj.offsetParent) {\r
545                         curleft = obj.offsetLeft;\r
546                         curtop = obj.offsetTop;\r
547                         while (obj = obj.offsetParent) {\r
548                                 var origcurleft = curleft;\r
549                                 curleft += obj.offsetLeft;\r
550                                 if (curleft < 0) {\r
551                                         curleft = origcurleft;\r
552                                 }\r
553                                 curtop += obj.offsetTop;\r
554                         }\r
555                 }\r
556                 return [curleft,curtop];\r
557         }\r
558 });\r
559 \r
560 /* Individualised settings for calendars applied to one or more related inputs.\r
561    Instances are managed and manipulated through the PopUpCal manager. */\r
562 function PopUpCalInstance(settings, inline) {\r
563         this._id = popUpCal._register(this);\r
564         this._selectedDay = 0;\r
565         this._selectedMonth = 0; // 0-11\r
566         this._selectedYear = 0; // 4-digit year\r
567         this._input = null; // The attached input field\r
568         this._inline = inline; // True if showing inline, false if used in a popup\r
569         this._calendarDiv = (!inline ? popUpCal._calendarDiv :\r
570                 $('<div id="calendar_div_' + this._id + '" class="calendar_inline"></div>'));\r
571         // customise the calendar object - uses manager defaults if not overridden\r
572         this._settings = extendRemove({}, settings || {}); // clone\r
573         if (inline) {\r
574                 this._setDate(this._getDefaultDate());\r
575         }\r
576 }\r
577 \r
578 $.extend(PopUpCalInstance.prototype, {\r
579         /* Get a setting value, defaulting if necessary. */\r
580         _get: function(name) {\r
581                 return (this._settings[name] != null ? this._settings[name] : popUpCal._defaults[name]);\r
582         },\r
583 \r
584         /* Parse existing date and initialise calendar. */\r
585         _setDateFromField: function(input) {\r
586                 this._input = $(input);\r
587                 var dateFormat = this._get('dateFormat');\r
588                 var currentDate = this._input.val().split(dateFormat.charAt(3));\r
589                 if (currentDate.length == 3) {\r
590                         this._currentDay = parseInt(currentDate[dateFormat.indexOf('D')], 10);\r
591                         this._currentMonth = parseInt(currentDate[dateFormat.indexOf('M')], 10) - 1;\r
592                         this._currentYear = parseInt(currentDate[dateFormat.indexOf('Y')], 10);\r
593                 }\r
594                 else {\r
595                         var date = this._getDefaultDate();\r
596                         this._currentDay = date.getDate();\r
597                         this._currentMonth = date.getMonth();\r
598                         this._currentYear = date.getFullYear();\r
599                 }\r
600                 this._selectedDay = this._currentDay;\r
601                 this._selectedMonth = this._currentMonth;\r
602                 this._selectedYear = this._currentYear;\r
603                 this._adjustDate();\r
604         },\r
605         \r
606         /* Retrieve the default date shown on opening. */\r
607         _getDefaultDate: function() {\r
608                 var offsetDate = function(offset) {\r
609                         var date = new Date();\r
610                         date.setDate(date.getDate() + offset);\r
611                         return date;\r
612                 };\r
613                 var defaultDate = this._get('defaultDate');\r
614                 return (defaultDate == null ? new Date() :\r
615                         (typeof defaultDate == 'number' ? offsetDate(defaultDate) : defaultDate));\r
616         },\r
617 \r
618         /* Set the date directly. */\r
619         _setDate: function(date) {\r
620                 this._selectedDay = this._currentDay = date.getDate();\r
621                 this._selectedMonth = this._currentMonth = date.getMonth();\r
622                 this._selectedYear = this._currentYear = date.getFullYear();\r
623                 this._adjustDate();\r
624         },\r
625 \r
626         /* Retrieve the date directly. */\r
627         _getDate: function() {\r
628                 return new Date(this._currentYear, this._currentMonth, this._currentDay);\r
629         },\r
630 \r
631         /* Generate the HTML for the current state of the calendar. */\r
632         _generateCalendar: function() {\r
633                 var today = new Date();\r
634                 today = new Date(today.getFullYear(), today.getMonth(), today.getDate()); // clear time\r
635                 // build the calendar HTML\r
636                 var controls = '<div class="calendar_control">' +\r
637                         '<a class="calendar_clear" onclick="popUpCal._clearDate(' + this._id + ');">' +\r
638                         this._get('clearText') + '</a>' +\r
639                         '<a class="calendar_close" onclick="popUpCal.hideCalendar(' + this._id + ');">' +\r
640                         this._get('closeText') + '</a></div>';\r
641                 var prompt = this._get('prompt');\r
642                 var closeAtTop = this._get('closeAtTop');\r
643                 var hideIfNoPrevNext = this._get('hideIfNoPrevNext');\r
644                 // controls and links\r
645                 var html = (prompt ? '<div class="calendar_prompt">' + prompt + '</div>' : '') +\r
646                         (closeAtTop && !this._inline ? controls : '') + '<div class="calendar_links">' +\r
647                         (this._canAdjustMonth(-1) ? '<a class="calendar_prev" ' +\r
648                         'onclick="popUpCal._adjustDate(' + this._id + ', -1, \'M\');">' + this._get('prevText') + '</a>' :\r
649                         (hideIfNoPrevNext ? '' : '<label class="calendar_prev">' + this._get('prevText') + '</label>')) +\r
650                         (this._isInRange(today) ? '<a class="calendar_current" ' +\r
651                         'onclick="popUpCal._gotoToday(' + this._id + ');">' + this._get('currentText') + '</a>' : '') +\r
652                         (this._canAdjustMonth(+1) ? '<a class="calendar_next" ' +\r
653                         'onclick="popUpCal._adjustDate(' + this._id + ', +1, \'M\');">' + this._get('nextText') + '</a>' :\r
654                         (hideIfNoPrevNext ? '' : '<label class="calendar_next">' + this._get('nextText') + '</label>')) +\r
655                         '</div><div class="calendar_header">';\r
656                 var minDate = this._get('minDate');\r
657                 var maxDate = this._get('maxDate');\r
658                 // month selection\r
659                 var monthNames = this._get('monthNames');\r
660                 if (!this._get('changeMonth')) {\r
661                         html += monthNames[this._selectedMonth] + '&nbsp;';\r
662                 }\r
663                 else {\r
664                         var inMinYear = (minDate && minDate.getFullYear() == this._selectedYear);\r
665                         var inMaxYear = (maxDate && maxDate.getFullYear() == this._selectedYear);\r
666                         html += '<select class="calendar_newMonth" ' +\r
667                                 'onchange="popUpCal._selectMonthYear(' + this._id + ', this, \'M\');" ' +\r
668                                 'onclick="popUpCal._clickMonthYear(' + this._id + ');">';\r
669                         for (var month = 0; month < 12; month++) {\r
670                                 if ((!inMinYear || month >= minDate.getMonth()) &&\r
671                                                 (!inMaxYear || month <= maxDate.getMonth())) {\r
672                                         html += '<option value="' + month + '"' +\r
673                                                 (month == this._selectedMonth ? ' selected="selected"' : '') +\r
674                                                 '>' + monthNames[month] + '</option>';\r
675                                 }\r
676                         }\r
677                         html += '</select>';\r
678                 }\r
679                 // year selection\r
680                 if (!this._get('changeYear')) {\r
681                         html += this._selectedYear;\r
682                 }\r
683                 else {\r
684                         // determine range of years to display\r
685                         var years = this._get('yearRange').split(':');\r
686                         var year = 0;\r
687                         var endYear = 0;\r
688                         if (years.length != 2) {\r
689                                 year = this._selectedYear - 10;\r
690                                 endYear = this._selectedYear + 10;\r
691                         }\r
692                         else if (years[0].charAt(0) == '+' || years[0].charAt(0) == '-') {\r
693                                 year = this._selectedYear + parseInt(years[0], 10);\r
694                                 endYear = this._selectedYear + parseInt(years[1], 10);\r
695                         }\r
696                         else {\r
697                                 year = parseInt(years[0], 10);\r
698                                 endYear = parseInt(years[1], 10);\r
699                         }\r
700                         year = (minDate ? Math.max(year, minDate.getFullYear()) : year);\r
701                         endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);\r
702                         html += '<select class="calendar_newYear" onchange="popUpCal._selectMonthYear(' +\r
703                                 this._id + ', this, \'Y\');" ' + 'onclick="popUpCal._clickMonthYear(' +\r
704                                 this._id + ');">';\r
705                         for (; year <= endYear; year++) {\r
706                                 html += '<option value="' + year + '"' +\r
707                                         (year == this._selectedYear ? ' selected="selected"' : '') +\r
708                                         '>' + year + '</option>';\r
709                         }\r
710                         html += '</select>';\r
711                 }\r
712                 html += '</div><table class="calendar" cellpadding="0" cellspacing="0"><thead>' +\r
713                         '<tr class="calendar_titleRow">';\r
714                 var firstDay = this._get('firstDay');\r
715                 var changeFirstDay = this._get('changeFirstDay');\r
716                 var dayNames = this._get('dayNames');\r
717                 for (var dow = 0; dow < 7; dow++) { // days of the week\r
718                         html += '<td>' + (!changeFirstDay ? '' : '<a onclick="popUpCal._changeFirstDay(' +\r
719                                 this._id + ', this);">') + dayNames[(dow + firstDay) % 7] +\r
720                                 (changeFirstDay ? '</a>' : '') + '</td>';\r
721                 }\r
722                 html += '</tr></thead><tbody>';\r
723                 var daysInMonth = this._getDaysInMonth(this._selectedYear, this._selectedMonth);\r
724                 this._selectedDay = Math.min(this._selectedDay, daysInMonth);\r
725                 var leadDays = (this._getFirstDayOfMonth(this._selectedYear, this._selectedMonth) - firstDay + 7) % 7;\r
726                 var currentDate = new Date(this._currentYear, this._currentMonth, this._currentDay);\r
727                 var selectedDate = new Date(this._selectedYear, this._selectedMonth, this._selectedDay);\r
728                 var printDate = new Date(this._selectedYear, this._selectedMonth, 1 - leadDays);\r
729                 var numRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate\r
730                 var customDate = this._get('customDate');\r
731                 var showOtherMonths = this._get('showOtherMonths');\r
732                 for (var row = 0; row < numRows; row++) { // create calendar rows\r
733                         html += '<tr class="calendar_daysRow">';\r
734                         for (var dow = 0; dow < 7; dow++) { // create calendar days\r
735                                 var customSettings = (customDate ? customDate(printDate) : [true, '']);\r
736                                 var otherMonth = (printDate.getMonth() != this._selectedMonth);\r
737                                 var unselectable = otherMonth || !customSettings[0] ||\r
738                                         (minDate && printDate < minDate) || (maxDate && printDate > maxDate);\r
739                                 html += '<td class="calendar_daysCell' +\r
740                                         ((dow + firstDay + 6) % 7 >= 5 ? ' calendar_weekEndCell' : '') + // highlight weekends\r
741                                         (otherMonth ? ' calendar_otherMonth' : '') + // highlight days from other months\r
742                                         (printDate.getTime() == selectedDate.getTime() ? ' calendar_daysCellOver' : '') + // highlight selected day\r
743                                         (unselectable ? ' calendar_unselectable' : '') +  // highlight unselectable days\r
744                                         (otherMonth && !showOtherMonths ? '' : ' ' + customSettings[1] + // highlight custom dates\r
745                                         (printDate.getTime() == currentDate.getTime() ? ' calendar_currentDay' : // highlight current day\r
746                                         (printDate.getTime() == today.getTime() ? ' calendar_today' : ''))) + '"' + // highlight today (if different)\r
747                                         (unselectable ? '' : ' onmouseover="$(this).addClass(\'calendar_daysCellOver\');"' +\r
748                                         ' onmouseout="$(this).removeClass(\'calendar_daysCellOver\');"' +\r
749                                         ' onclick="popUpCal._selectDay(' + this._id + ', this);"') + '>' + // actions\r
750                                         (otherMonth ? (showOtherMonths ? printDate.getDate() : '&nbsp;') : // display for other months\r
751                                         (unselectable ? printDate.getDate() : '<a>' + printDate.getDate() + '</a>')) + '</td>'; // display for this month\r
752                                 printDate.setDate(printDate.getDate() + 1);\r
753                         }\r
754                         html += '</tr>';\r
755                 }\r
756                 html += '</tbody></table>' + (!closeAtTop && !this._inline ? controls : '') +\r
757                         '<div style="clear: both;"></div>' + (!$.browser.msie ? '' :\r
758                         '<!--[if lte IE 6.5]><iframe src="javascript:false;" class="calendar_cover"></iframe><![endif]-->');\r
759                 return html;\r
760         },\r
761 \r
762         /* Adjust one of the date sub-fields. */\r
763         _adjustDate: function(offset, period) {\r
764                 var date = new Date(this._selectedYear + (period == 'Y' ? offset : 0),\r
765                         this._selectedMonth + (period == 'M' ? offset : 0),\r
766                         this._selectedDay + (period == 'D' ? offset : 0));\r
767                 // ensure it is within the bounds set\r
768                 var minDate = this._get('minDate');\r
769                 var maxDate = this._get('maxDate');\r
770                 date = (minDate && date < minDate ? minDate : date);\r
771                 date = (maxDate && date > maxDate ? maxDate : date);\r
772                 this._selectedDay = date.getDate();\r
773                 this._selectedMonth = date.getMonth();\r
774                 this._selectedYear = date.getFullYear();\r
775         },\r
776 \r
777         /* Find the number of days in a given month. */\r
778         _getDaysInMonth: function(year, month) {\r
779                 return 32 - new Date(year, month, 32).getDate();\r
780         },\r
781 \r
782         /* Find the day of the week of the first of a month. */\r
783         _getFirstDayOfMonth: function(year, month) {\r
784                 return new Date(year, month, 1).getDay();\r
785         },\r
786 \r
787         /* Determines if we should allow a "next/prev" month display change. */\r
788         _canAdjustMonth: function(offset) {\r
789                 var date = new Date(this._selectedYear, this._selectedMonth + offset, 1);\r
790                 if (offset < 0) {\r
791                         date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));\r
792                 }\r
793                 return this._isInRange(date);\r
794         },\r
795 \r
796         /* Is the given date in the accepted range? */\r
797         _isInRange: function(date) {\r
798                 var minDate = this._get('minDate');\r
799                 var maxDate = this._get('maxDate');\r
800                 return ((!minDate || date >= minDate) && (!maxDate || date <= maxDate));\r
801         },\r
802 \r
803         /* Format the given date for display. */\r
804         _formatDate: function() {\r
805                 var day = this._currentDay = this._selectedDay;\r
806                 var month = this._currentMonth = this._selectedMonth;\r
807                 var year = this._currentYear = this._selectedYear;\r
808                 month++; // adjust javascript month\r
809                 var dateFormat = this._get('dateFormat');\r
810                 var dateString = '';\r
811                 for (var i = 0; i < 3; i++) {\r
812                         dateString += dateFormat.charAt(3) +\r
813                                 (dateFormat.charAt(i) == 'D' ? (day < 10 ? '0' : '') + day :\r
814                                 (dateFormat.charAt(i) == 'M' ? (month < 10 ? '0' : '') + month :\r
815                                 (dateFormat.charAt(i) == 'Y' ? year : '?')));\r
816                 }\r
817                 return dateString.substring(dateFormat.charAt(3) ? 1 : 0);\r
818         }\r
819 });\r
820 \r
821 /* jQuery extend now ignores nulls! */\r
822 function extendRemove(target, props) {\r
823         $.extend(target, props);\r
824         for (var name in props) {\r
825                 if (props[name] == null) {\r
826                         target[name] = null;\r
827                 }\r
828         }\r
829         return target;\r
830 }\r
831 \r
832 /* Attach the calendar to a jQuery selection.\r
833    @param  settings  object - the new settings to use for this calendar instance (anonymous)\r
834    @return jQuery object - for chaining further calls */\r
835 $.fn.calendar = function(settings) {\r
836         return this.each(function() {\r
837                 // check for settings on the control itself - in namespace 'cal:'\r
838                 var inlineSettings = null;\r
839                 for (attrName in popUpCal._defaults) {\r
840                         var attrValue = this.getAttribute('cal:' + attrName);\r
841                         if (attrValue) {\r
842                                 inlineSettings = inlineSettings || {};\r
843                                 try {\r
844                                         inlineSettings[attrName] = eval(attrValue);\r
845                                 }\r
846                                 catch (err) {\r
847                                         inlineSettings[attrName] = attrValue;\r
848                                 }\r
849                         }\r
850                 }\r
851                 var nodeName = this.nodeName.toLowerCase();\r
852                 if (nodeName == 'input') {\r
853                         var instSettings = (inlineSettings ? $.extend($.extend({}, settings || {}),\r
854                                 inlineSettings || {}) : settings); // clone and customise\r
855                         var inst = (inst && !inlineSettings ? inst :\r
856                                 new PopUpCalInstance(instSettings, false));\r
857                         popUpCal._connectCalendar(this, inst);\r
858                 } \r
859                 else if (nodeName == 'div' || nodeName == 'span') {\r
860                         var instSettings = $.extend($.extend({}, settings || {}),\r
861                                 inlineSettings || {}); // clone and customise\r
862                         var inst = new PopUpCalInstance(instSettings, true);\r
863                         popUpCal._inlineCalendar(this, inst);\r
864                 }\r
865         });\r
866 };\r
867 \r
868 /* Initialise the calendar. */\r
869 $(document).ready(function() {\r
870    popUpCal = new PopUpCal(); // singleton instance\r
871 });\r