1 /* jQuery Calendar v2.7
\r
2 Written by Marc Grabanski (m@marcgrabanski.com) and enhanced by Keith Wood (kbwood@iprimus.com.au).
\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
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
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
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
69 $.extend(PopUpCal.prototype, {
\r
70 /* Class name added to elements to indicate already configured with a calendar. */
\r
71 markerClassName: 'hasCalendar',
\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
80 /* Retrieve a particular calendar instance based on its ID. */
\r
81 _getInst: function(id) {
\r
82 return this._inst[id] || id;
\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
88 setDefaults: function(settings) {
\r
89 extendRemove(this._defaults, settings || {});
\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
121 else if (e.keyCode == 36 && e.ctrlKey) { // display the calendar on ctrl+home
\r
122 popUpCal.showFor(this);
\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
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
140 var appendText = inst._get('appendText');
\r
142 input.after('<span class="calendar_append">' + appendText + '</span>');
\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
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
160 input.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress);
\r
161 input[0]._calId = inst._id;
\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
170 input.addClass(this.markerClassName).append(inst._calendarDiv);
\r
171 input[0]._calId = inst._id;
\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
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
187 dialogCalendar: function(dateText, onSelect, settings, pos) {
\r
188 var inst = this._dialogInst; // internal instance
\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
196 extendRemove(inst._settings, settings || {});
\r
197 this._dialogInput.val(dateText);
\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
210 this._pos = pos || // should use actual width/height below
\r
211 [(windowWidth / 2) - 100, (windowHeight / 2) - 100];
\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
220 $.blockUI(this._calendarDiv);
\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
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
234 popUpCal._disabledInputs = $.map(popUpCal._disabledInputs,
\r
235 function(value) { return (value == $this ? null : value); }); // delete entry
\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
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
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
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
260 reconfigureFor: function(control, settings) {
\r
261 control = (typeof control == 'string' ? $(control)[0] : control);
\r
262 var inst = this._getInst(control._calId);
\r
264 extendRemove(inst._settings, settings || {});
\r
265 this._updateCalendar(inst);
\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
273 setDateFor: function(control, date) {
\r
274 var inst = this._getInst(control._calId);
\r
276 inst._setDate(date);
\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
288 /* Pop-up the calendar for a given input field.
\r
289 @param target element - the input field attached to the calendar
\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
296 if (popUpCal._lastInput == input) { // already here
\r
299 for (var i = 0; i < popUpCal._disabledInputs.length; i++) { // check not disabled
\r
300 if (popUpCal._disabledInputs[i] == input) {
\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
313 if (!popUpCal._pos) { // position below input
\r
314 popUpCal._pos = popUpCal._findPos(input);
\r
315 popUpCal._pos[1] += input.offsetHeight;
\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
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
334 popUpCal._popUpShowing = true;
\r
335 popUpCal._afterShow(inst);
\r
337 if (inst._input[0].type != 'hidden') {
\r
338 inst._input[0].focus();
\r
340 this._curInst = inst;
\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
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
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
365 browserWidth = document.documentElement.clientWidth;
\r
367 if ( document.documentElement && (document.documentElement.scrollLeft)) {
\r
368 browserX = document.documentElement.scrollLeft;
\r
370 browserX = document.body.scrollLeft;
\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
377 // Get browser height and Y value (IE6+, FF, Safari, Opera)
\r
378 if( typeof( window.innerHeight ) == 'number' ) {
\r
379 browserHeight = window.innerHeight;
\r
381 browserHeight = document.documentElement.clientHeight;
\r
383 if ( document.documentElement && (document.documentElement.scrollTop)) {
\r
384 browserTopY = document.documentElement.scrollTop;
\r
386 browserTopY = document.body.scrollTop;
\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
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
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
408 popUpCal._tidyDialog(inst);
\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
418 $('body').append(this._calendarDiv);
\r
421 popUpCal._inDialog = false;
\r
423 popUpCal._curInst = null;
\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
432 /* Close calendar if clicked elsewhere. */
\r
433 _checkExternalClick: function(event) {
\r
434 if (!popUpCal._curInst) {
\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
443 popUpCal.hideCalendar(popUpCal._curInst, '');
\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
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
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
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
479 inst._selectingMonthYear = !inst._selectingMonthYear;
\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
493 this._updateCalendar(inst);
\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
503 /* Erase the input field and hide the calendar. */
\r
504 _clearDate: function(id) {
\r
505 this._selectDate(id, '');
\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
513 inst._input.val(dateStr);
\r
515 var onSelect = inst._get('onSelect');
\r
517 onSelect(dateStr, inst); // trigger custom callback
\r
520 inst._input.trigger('change'); // fire the change event
\r
522 if (inst._inline) {
\r
523 this._updateCalendar(inst);
\r
526 this.hideCalendar(inst, inst._get('speed'));
\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
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
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
551 curleft = origcurleft;
\r
553 curtop += obj.offsetTop;
\r
556 return [curleft,curtop];
\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
574 this._setDate(this._getDefaultDate());
\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
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
595 var date = this._getDefaultDate();
\r
596 this._currentDay = date.getDate();
\r
597 this._currentMonth = date.getMonth();
\r
598 this._currentYear = date.getFullYear();
\r
600 this._selectedDay = this._currentDay;
\r
601 this._selectedMonth = this._currentMonth;
\r
602 this._selectedYear = this._currentYear;
\r
603 this._adjustDate();
\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
613 var defaultDate = this._get('defaultDate');
\r
614 return (defaultDate == null ? new Date() :
\r
615 (typeof defaultDate == 'number' ? offsetDate(defaultDate) : defaultDate));
\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
626 /* Retrieve the date directly. */
\r
627 _getDate: function() {
\r
628 return new Date(this._currentYear, this._currentMonth, this._currentDay);
\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
659 var monthNames = this._get('monthNames');
\r
660 if (!this._get('changeMonth')) {
\r
661 html += monthNames[this._selectedMonth] + ' ';
\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
677 html += '</select>';
\r
680 if (!this._get('changeYear')) {
\r
681 html += this._selectedYear;
\r
684 // determine range of years to display
\r
685 var years = this._get('yearRange').split(':');
\r
688 if (years.length != 2) {
\r
689 year = this._selectedYear - 10;
\r
690 endYear = this._selectedYear + 10;
\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
697 year = parseInt(years[0], 10);
\r
698 endYear = parseInt(years[1], 10);
\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
705 for (; year <= endYear; year++) {
\r
706 html += '<option value="' + year + '"' +
\r
707 (year == this._selectedYear ? ' selected="selected"' : '') +
\r
708 '>' + year + '</option>';
\r
710 html += '</select>';
\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
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() : ' ') : // 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
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
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
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
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
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
791 date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
\r
793 return this._isInRange(date);
\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
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
817 return dateString.substring(dateFormat.charAt(3) ? 1 : 0);
\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
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
842 inlineSettings = inlineSettings || {};
\r
844 inlineSettings[attrName] = eval(attrValue);
\r
847 inlineSettings[attrName] = attrValue;
\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
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
868 /* Initialise the calendar. */
\r
869 $(document).ready(function() {
\r
870 popUpCal = new PopUpCal(); // singleton instance
\r