1 /* jQuery Calendar v2.7
2 Written by Marc Grabanski (m@marcgrabanski.com) and enhanced by Keith Wood (kbwood@iprimus.com.au).
4 Copyright (c) 2007 Marc Grabanski (http://marcgrabanski.com/code/jquery-calendar)
5 Dual licensed under the GPL (http://www.gnu.org/licenses/gpl-3.0.txt) and
6 CC (http://creativecommons.org/licenses/by/3.0/) licenses. "Share or Remix it but please Attribute the authors."
9 /* PopUp Calendar manager.
10 Use the singleton instance of this class, popUpCal, to interact with the calendar.
11 Settings for (groups of) calendars are maintained in an instance object
12 (PopUpCalInstance), allowing multiple different settings on the same page. */
14 this._nextId = 0; // Next ID for a calendar instance
15 this._inst = []; // List of instances indexed by ID
16 this._curInst = null; // The current instance in use
17 this._disabledInputs = []; // List of calendar inputs that have been disabled
18 this._popUpShowing = false; // True if the popup calendar is showing , false if not
19 this._inDialog = false; // True if showing within a "dialog", false if not
20 this.regional = []; // Available regional settings, indexed by language code
21 this.regional[''] = { // Default regional settings
22 clearText: 'Clear', // Display text for clear link
23 closeText: 'Close', // Display text for close link
24 prevText: '<Prev', // Display text for previous month link
25 nextText: 'Next>', // Display text for next month link
26 currentText: 'Today', // Display text for current month link
27 dayNames: ['Su','Mo','Tu','We','Th','Fr','Sa'], // Names of days starting at Sunday
28 monthNames: ['January','February','March','April','May','June',
29 'July','August','September','October','November','December'], // Names of months
30 dateFormat: 'DMY/' // First three are day, month, year in the required order,
31 // fourth (optional) is the separator, e.g. US would be 'MDY/', ISO would be 'YMD-'
33 this._defaults = { // Global defaults for all the calendar instances
34 autoPopUp: 'focus', // 'focus' for popup on focus,
35 // 'button' for trigger button, or 'both' for either
36 appendText: '', // Display text following the input box, e.g. showing the format
37 buttonText: '...', // Text for trigger button
38 buttonImage: '', // URL for trigger button image
39 buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
40 closeAtTop: true, // True to have the clear/close at the top,
41 // false to have them at the bottom
42 hideIfNoPrevNext: false, // True to hide next/previous month links
43 // if not applicable, false to just disable them
44 changeMonth: true, // True if month can be selected directly, false if only prev/next
45 changeYear: true, // True if year can be selected directly, false if only prev/next
46 yearRange: '-10:+10', // Range of years to display in drop-down,
47 // either relative to current year (-nn:+nn) or absolute (nnnn:nnnn)
48 firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
49 changeFirstDay: true, // True to click on day name to change, false to remain as set
50 showOtherMonths: false, // True to show dates in other months, false to leave blank
51 minDate: null, // The earliest selectable date, or null for no limit
52 maxDate: null, // The latest selectable date, or null for no limit
53 speed: 'medium', // Speed of display/closure
54 customDate: null, // Function that takes a date and returns an array with
55 // [0] = true if selectable, false if not,
56 // [1] = custom CSS class name(s) or '', e.g. popUpCal.noWeekends
57 fieldSettings: null, // Function that takes an input field and
58 // returns a set of custom settings for the calendar
59 onSelect: null // Define a callback function when a date is selected
61 $.extend(this._defaults, this.regional['']);
62 this._calendarDiv = $('<div id="calendar_div"></div>');
63 $(document.body).append(this._calendarDiv);
64 $(document.body).mousedown(this._checkExternalClick);
67 $.extend(PopUpCal.prototype, {
68 /* Register a new calendar instance - with custom settings. */
69 _register: function(inst) {
70 var id = this._nextId++;
71 this._inst[id] = inst;
75 /* Retrieve a particular calendar instance based on its ID. */
76 _getInst: function(id) {
77 return this._inst[id] || id;
80 /* Override the default settings for all instances of the calendar.
81 @param settings object - the new settings to use as defaults (anonymous object)
83 setDefaults: function(settings) {
84 $.extend(this._defaults, settings || {});
87 /* Handle keystrokes. */
88 _doKeyDown: function(e) {
89 var inst = popUpCal._getInst(this._calId);
90 if (popUpCal._popUpShowing) {
92 case 9: popUpCal.hideCalendar(inst, '');
93 break; // hide on tab out
94 case 13: popUpCal._selectDate(inst);
95 break; // select the value on enter
96 case 27: popUpCal.hideCalendar(inst, inst._get('speed'));
97 break; // hide on escape
98 case 33: popUpCal._adjustDate(inst, -1, (e.ctrlKey ? 'Y' : 'M'));
99 break; // previous month/year on page up/+ ctrl
100 case 34: popUpCal._adjustDate(inst, +1, (e.ctrlKey ? 'Y' : 'M'));
101 break; // next month/year on page down/+ ctrl
102 case 35: if (e.ctrlKey) popUpCal._clearDate(inst);
103 break; // clear on ctrl+end
104 case 36: if (e.ctrlKey) popUpCal._gotoToday(inst);
105 break; // current on ctrl+home
106 case 37: if (e.ctrlKey) popUpCal._adjustDate(inst, -1, 'D');
107 break; // -1 day on ctrl+left
108 case 38: if (e.ctrlKey) popUpCal._adjustDate(inst, -7, 'D');
109 break; // -1 week on ctrl+up
110 case 39: if (e.ctrlKey) popUpCal._adjustDate(inst, +1, 'D');
111 break; // +1 day on ctrl+right
112 case 40: if (e.ctrlKey) popUpCal._adjustDate(inst, +7, 'D');
113 break; // +1 week on ctrl+down
116 else if (e.keyCode == 36 && e.ctrlKey) { // display the calendar on ctrl+home
117 popUpCal.showFor(this);
121 /* Filter entered characters. */
122 _doKeyPress: function(e) {
123 var inst = popUpCal._getInst(this._calId);
124 var chr = String.fromCharCode(e.charCode == undefined ? e.keyCode : e.charCode);
125 return (chr < ' ' || chr == inst._get('dateFormat').charAt(3) ||
126 (chr >= '0' && chr <= '9')); // only allow numbers and separator
129 /* Attach the calendar to an input field. */
130 _connectCalendar: function(target, inst) {
131 var $input = $(target);
132 var appendText = inst._get('appendText');
134 $input.after('<span class="calendar_append">' + appendText + '</span>');
136 var autoPopUp = inst._get('autoPopUp');
137 if (autoPopUp == 'focus' || autoPopUp == 'both') { // pop-up calendar when in the marked field
138 $input.focus(this.showFor);
140 if (autoPopUp == 'button' || autoPopUp == 'both') { // pop-up calendar when button clicked
141 var buttonText = inst._get('buttonText');
142 var buttonImage = inst._get('buttonImage');
143 var buttonImageOnly = inst._get('buttonImageOnly');
144 var trigger = $(buttonImageOnly ? '<img class="calendar_trigger" src="' +
145 buttonImage + '" alt="' + buttonText + '" title="' + buttonText + '"/>' :
146 '<button type="button" class="calendar_trigger">' + (buttonImage != '' ?
147 '<img src="' + buttonImage + '" alt="' + buttonText + '" title="' + buttonText + '"/>' :
148 buttonText) + '</button>');
149 $input.wrap('<span class="calendar_wrap"></span>').after(trigger);
150 trigger.click(this.showFor);
152 $input.keydown(this._doKeyDown).keypress(this._doKeyPress);
153 $input[0]._calId = inst._id;
156 /* Attach an inline calendar to a div. */
157 _inlineCalendar: function(target, inst) {
158 $(target).append(inst._calendarDiv);
159 target._calId = inst._id;
160 var date = new Date();
161 inst._selectedDay = date.getDate();
162 inst._selectedMonth = date.getMonth();
163 inst._selectedYear = date.getFullYear();
164 popUpCal._adjustDate(inst);
167 /* Pop-up the calendar in a "dialog" box.
168 @param dateText string - the initial date to display (in the current format)
169 @param onSelect function - the function(dateText) to call when a date is selected
170 @param settings object - update the dialog calendar instance's settings (anonymous object)
171 @param pos int[2] - coordinates for the dialog's position within the screen
172 leave empty for default (screen centre)
174 dialogCalendar: function(dateText, onSelect, settings, pos) {
175 var inst = this._dialogInst; // internal instance
177 inst = this._dialogInst = new PopUpCalInstance({}, false);
178 this._dialogInput = $('<input type="text" size="1" style="position: absolute; top: -100px;"/>');
179 this._dialogInput.keydown(this._doKeyDown);
180 $('body').append(this._dialogInput);
181 this._dialogInput[0]._calId = inst._id;
183 $.extend(inst._settings, settings || {});
184 this._dialogInput.val(dateText);
186 /* Cross Browser Positioning */
187 if (self.innerHeight) { // all except Explorer
188 windowWidth = self.innerWidth;
189 windowHeight = self.innerHeight;
190 } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
191 windowWidth = document.documentElement.clientWidth;
192 windowHeight = document.documentElement.clientHeight;
193 } else if (document.body) { // other Explorers
194 windowWidth = document.body.clientWidth;
195 windowHeight = document.body.clientHeight;
197 this._pos = pos || // should use actual width/height below
198 [(windowWidth / 2) - 100, (windowHeight / 2) - 100];
200 // move input on screen for focus, but hidden behind dialog
201 this._dialogInput.css('left', this._pos[0] + 'px').css('top', this._pos[1] + 'px');
202 inst._settings.onSelect = onSelect;
203 this._inDialog = true;
204 this._calendarDiv.addClass('calendar_dialog');
205 this.showFor(this._dialogInput[0]);
207 $.blockUI(this._calendarDiv);
211 /* Enable the input field(s) for entry.
212 @param inputs element/object - single input field or jQuery collection of input fields
214 enableFor: function(inputs) {
215 inputs = (inputs.jquery ? inputs : $(inputs));
216 inputs.each(function() {
217 this.disabled = false;
218 $('../button.calendar_trigger', this).each(function() { this.disabled = false; });
219 $('../img.calendar_trigger',
220 this).css({opacity:'1.0',cursor:''});
222 popUpCal._disabledInputs = $.map(popUpCal._disabledInputs,
223 function(value) { return (value == $this ? null : value); }); // delete entry
227 /* Disable the input field(s) from entry.
228 @param inputs element/object - single input field or jQuery collection of input fields
230 disableFor: function(inputs) {
231 inputs = (inputs.jquery ? inputs : $(inputs));
232 inputs.each(function() {
233 this.disabled = true;
234 $('../button.calendar_trigger', this).each(function() { this.disabled = true; });
235 $('../img.calendar_trigger', this).css({opacity:'0.5',cursor:'default'});
237 popUpCal._disabledInputs = $.map(popUpCal._disabledInputs,
238 function(value) { return (value == $this ? null : value); }); // delete entry
239 popUpCal._disabledInputs[popUpCal._disabledInputs.length] = this;
243 /* Update the settings for a calendar attached to an input field or division.
244 @param control element - the input field or div/span attached to the calendar
245 @param settings object - the new settings to update
247 reconfigureFor: function(control, settings) {
248 var inst = this._getInst(control._calId);
250 $.extend(inst._settings, settings || {});
251 this._updateCalendar(inst);
255 /* Set the date for a calendar attached to an input field or division.
256 @param control element - the input field or div/span attached to the calendar
257 @param date Date - the new date
259 setDateFor: function(control, date) {
260 var inst = this._getInst(control._calId);
266 /* Retrieve the date for a calendar attached to an input field or division.
267 @param control element - the input field or div/span attached to the calendar
268 @return Date - the current date */
269 getDateFor: function(control) {
270 var inst = this._getInst(control._calId);
271 return (inst ? inst._getDate() : null);
274 /* Pop-up the calendar for a given input field.
275 @param target element - the input field attached to the calendar
277 showFor: function(target) {
278 var input = (target.nodeName && target.nodeName.toLowerCase() == 'input' ? target : this);
279 if (input.nodeName.toLowerCase() != 'input') { // find from button/image trigger
280 input = $('input', input.parentNode)[0];
282 if (popUpCal._lastInput == input) { // already here
285 for (var i = 0; i < popUpCal._disabledInputs.length; i++) { // check not disabled
286 if (popUpCal._disabledInputs[i] == input) {
290 var inst = popUpCal._getInst(input._calId);
291 popUpCal.hideCalendar(inst, '');
292 popUpCal._lastInput = input;
293 inst._setDateFromField(input);
294 if (popUpCal._inDialog) { // hide cursor
297 if (!popUpCal._pos) { // position below input
298 popUpCal._pos = popUpCal._findPos(input);
299 popUpCal._pos[1] += input.offsetHeight;
301 inst._calendarDiv.css('position', (popUpCal._inDialog && $.blockUI ? 'static' : 'absolute')).
302 css('left', popUpCal._pos[0] + 'px').css('top', popUpCal._pos[1] + 'px');
303 popUpCal._pos = null;
304 var fieldSettings = inst._get('fieldSettings');
305 $.extend(inst._settings, (fieldSettings ? fieldSettings(input) : {}));
306 popUpCal._showCalendar(inst);
309 /* Construct and display the calendar. */
310 _showCalendar: function(id) {
311 var inst = this._getInst(id);
312 popUpCal._updateCalendar(inst);
314 var speed = inst._get('speed');
315 inst._calendarDiv.show(speed, function() {
316 popUpCal._popUpShowing = true;
317 popUpCal._afterShow(inst);
320 popUpCal._popUpShowing = true;
321 popUpCal._afterShow(inst);
323 if (inst._input[0].type != 'hidden') {
324 inst._input[0].focus();
326 this._curInst = inst;
330 /* Generate the calendar content. */
331 _updateCalendar: function(inst) {
332 inst._calendarDiv.empty().append(inst._generateCalendar());
333 if (inst._input && inst._input != 'hidden') {
334 inst._input[0].focus();
338 /* Tidy up after displaying the calendar. */
339 _afterShow: function(inst) {
340 if ($.browser.msie) { // fix IE < 7 select problems
341 $('#calendar_cover').css({width: inst._calendarDiv[0].offsetWidth + 4,
342 height: inst._calendarDiv[0].offsetHeight + 4});
344 /*// re-position on screen if necessary
345 var calDiv = inst._calendarDiv[0];
346 var pos = popUpCal._findPos(inst._input[0]);
347 if ((calDiv.offsetLeft + calDiv.offsetWidth) >
348 (document.body.clientWidth + document.body.scrollLeft)) {
349 inst._calendarDiv.css('left', (pos[0] + inst._input[0].offsetWidth - calDiv.offsetWidth) + 'px');
351 if ((calDiv.offsetTop + calDiv.offsetHeight) >
352 (document.body.clientHeight + document.body.scrollTop)) {
353 inst._calendarDiv.css('top', (pos[1] - calDiv.offsetHeight) + 'px');
357 /* Hide the calendar from view.
358 @param id string/object - the ID of the current calendar instance,
359 or the instance itself
360 @param speed string - the speed at which to close the calendar
362 hideCalendar: function(id, speed) {
363 var inst = this._getInst(id);
364 if (popUpCal._popUpShowing) {
365 speed = (speed != null ? speed : inst._get('speed'));
366 inst._calendarDiv.hide(speed, function() {
367 popUpCal._tidyDialog(inst);
370 popUpCal._tidyDialog(inst);
372 popUpCal._popUpShowing = false;
373 popUpCal._lastInput = null;
374 inst._settings.prompt = null;
375 if (popUpCal._inDialog) {
376 popUpCal._dialogInput.css('position', 'absolute').
377 css('left', '0px').css('top', '-100px');
380 $('body').append(this._calendarDiv);
383 popUpCal._inDialog = false;
385 popUpCal._curInst = null;
388 /* Tidy up after a dialog display. */
389 _tidyDialog: function(inst) {
390 inst._calendarDiv.removeClass('calendar_dialog');
391 $('.calendar_prompt', inst._calendarDiv).remove();
394 /* Close calendar if clicked elsewhere. */
395 _checkExternalClick: function(event) {
396 if (!popUpCal._curInst) {
399 var target = $(event.target);
400 if( (target.parents("#calendar_div").length == 0)
401 && (target.attr('class') != 'calendar_trigger')
402 && popUpCal._popUpShowing
403 && !(popUpCal._inDialog && $.blockUI) )
405 popUpCal.hideCalendar(popUpCal._curInst, '');
409 /* Adjust one of the date sub-fields. */
410 _adjustDate: function(id, offset, period) {
411 var inst = this._getInst(id);
412 inst._adjustDate(offset, period);
413 this._updateCalendar(inst);
416 /* Action for current link. */
417 _gotoToday: function(id) {
418 var date = new Date();
419 var inst = this._getInst(id);
420 inst._selectedDay = date.getDate();
421 inst._selectedMonth = date.getMonth();
422 inst._selectedYear = date.getFullYear();
423 this._adjustDate(inst);
426 /* Action for selecting a new month/year. */
427 _selectMonthYear: function(id, select, period) {
428 var inst = this._getInst(id);
429 inst._selectingMonthYear = false;
430 inst[period == 'M' ? '_selectedMonth' : '_selectedYear'] =
431 select.options[select.selectedIndex].value - 0;
432 this._adjustDate(inst);
435 /* Restore input focus after not changing month/year. */
436 _clickMonthYear: function(id) {
437 var inst = this._getInst(id);
438 if (inst._input && inst._selectingMonthYear && !$.browser.msie) {
439 inst._input[0].focus();
441 inst._selectingMonthYear = !inst._selectingMonthYear;
444 /* Action for changing the first week day. */
445 _changeFirstDay: function(id, a) {
446 var inst = this._getInst(id);
447 var dayNames = inst._get('dayNames');
448 var value = a.firstChild.nodeValue;
449 for (var i = 0; i < 7; i++) {
450 if (dayNames[i] == value) {
451 inst._settings.firstDay = i;
455 this._updateCalendar(inst);
458 /* Action for selecting a day. */
459 _selectDay: function(id, td) {
460 var inst = this._getInst(id);
461 inst._selectedDay = $("a", td).html();
462 this._selectDate(id);
465 /* Erase the input field and hide the calendar. */
466 _clearDate: function(id) {
467 this._selectDate(id, '');
470 /* Update the input field with the selected date. */
471 _selectDate: function(id, dateStr) {
472 var inst = this._getInst(id);
473 dateStr = (dateStr != null ? dateStr : inst._formatDate());
475 inst._input.val(dateStr);
477 var onSelect = inst._get('onSelect');
479 onSelect(dateStr); // trigger custom callback
482 inst._input.trigger('change'); // fire the change event
485 this._updateCalendar(inst);
488 this.hideCalendar(inst, inst._get('speed'));
492 /* Set as customDate function to prevent selection of weekends.
493 @param date Date - the date to customise
494 @return [boolean, string] - is this date selectable?, what is its CSS class? */
495 noWeekends: function(date) {
496 var day = date.getDay();
497 return [(day > 0 && day < 6), ''];
500 /* Find an object's position on the screen. */
501 _findPos: function(obj) {
502 if (obj.type == 'hidden') {
503 obj = obj.nextSibling;
505 var curleft = curtop = 0;
506 if (obj.offsetParent) {
507 curleft = obj.offsetLeft;
508 curtop = obj.offsetTop;
509 while (obj = obj.offsetParent) {
510 var origcurleft = curleft;
511 curleft += obj.offsetLeft;
513 curleft = origcurleft;
515 curtop += obj.offsetTop;
518 return [curleft,curtop];
522 /* Individualised settings for calendars applied to one or more related inputs.
523 Instances are managed and manipulated through the PopUpCal manager. */
524 function PopUpCalInstance(settings, inline) {
525 this._id = popUpCal._register(this);
526 this._selectedDay = 0;
527 this._selectedMonth = 0; // 0-11
528 this._selectedYear = 0; // 4-digit year
529 this._input = null; // The attached input field
530 this._inline = inline; // True if showing inline, false if used in a popup
531 this._calendarDiv = (!inline ? popUpCal._calendarDiv :
532 $('<div id="calendar_div_' + this._id + '" class="calendar_inline"></div>'));
534 var date = new Date();
535 this._currentDay = date.getDate();
536 this._currentMonth = date.getMonth();
537 this._currentYear = date.getFullYear();
539 // customise the calendar object - uses manager defaults if not overridden
540 this._settings = $.extend({}, settings || {}); // clone
543 $.extend(PopUpCalInstance.prototype, {
544 /* Get a setting value, defaulting if necessary. */
545 _get: function(name) {
546 return (this._settings[name] != null ? this._settings[name] : popUpCal._defaults[name]);
549 /* Parse existing date and initialise calendar. */
550 _setDateFromField: function(input) {
551 this._input = $(input);
552 var dateFormat = this._get('dateFormat');
553 var currentDate = this._input.val().split(dateFormat.charAt(3));
554 if (currentDate.length == 3) {
555 this._currentDay = parseInt(currentDate[dateFormat.indexOf('D')], 10);
556 this._currentMonth = parseInt(currentDate[dateFormat.indexOf('M')], 10) - 1;
557 this._currentYear = parseInt(currentDate[dateFormat.indexOf('Y')], 10);
560 var date = new Date();
561 this._currentDay = date.getDate();
562 this._currentMonth = date.getMonth();
563 this._currentYear = date.getFullYear();
565 this._selectedDay = this._currentDay;
566 this._selectedMonth = this._currentMonth;
567 this._selectedYear = this._currentYear;
571 /* Set the date directly. */
572 _setDate: function(date) {
573 this._selectedDay = this._currentDay = date.getDate();
574 this._selectedMonth = this._currentMonth = date.getMonth();
575 this._selectedYear = this._currentYear = date.getFullYear();
579 /* Retrieve the date directly. */
580 _getDate: function() {
581 return new Date(this._currentYear, this._currentMonth, this._currentDay);
584 /* Generate the HTML for the current state of the calendar. */
585 _generateCalendar: function() {
586 var today = new Date();
587 today = new Date(today.getFullYear(), today.getMonth(), today.getDate()); // clear time
588 // build the calendar HTML
589 var controls = '<div class="calendar_control">' +
590 '<a class="calendar_clear" onclick="popUpCal._clearDate(' + this._id + ');">' +
591 this._get('clearText') + '</a>' +
592 '<a class="calendar_close" onclick="popUpCal.hideCalendar(' + this._id + ');">' +
593 this._get('closeText') + '</a></div>';
594 var prompt = this._get('prompt');
595 var closeAtTop = this._get('closeAtTop');
596 var hideIfNoPrevNext = this._get('hideIfNoPrevNext');
597 // controls and links
598 var html = (prompt ? '<div class="calendar_prompt">' + prompt + '</div>' : '') +
599 (closeAtTop && !this._inline ? controls : '') + '<div class="calendar_links">' +
600 (this._canAdjustMonth(-1) ? '<a class="calendar_prev" ' +
601 'onclick="popUpCal._adjustDate(' + this._id + ', -1, \'M\');">' + this._get('prevText') + '</a>' :
602 (hideIfNoPrevNext ? '' : '<label class="calendar_prev">' + this._get('prevText') + '</label>')) +
603 (this._isInRange(today) ? '<a class="calendar_current" ' +
604 'onclick="popUpCal._gotoToday(' + this._id + ');">' + this._get('currentText') + '</a>' : '') +
605 (this._canAdjustMonth(+1) ? '<a class="calendar_next" ' +
606 'onclick="popUpCal._adjustDate(' + this._id + ', +1, \'M\');">' + this._get('nextText') + '</a>' :
607 (hideIfNoPrevNext ? '' : '<label class="calendar_next">' + this._get('nextText') + '</label>')) +
608 '</div><div class="calendar_header">';
609 var minDate = this._get('minDate');
610 var maxDate = this._get('maxDate');
612 var monthNames = this._get('monthNames');
613 if (!this._get('changeMonth')) {
614 html += monthNames[this._selectedMonth] + ' ';
617 var inMinYear = (minDate && minDate.getFullYear() == this._selectedYear);
618 var inMaxYear = (maxDate && maxDate.getFullYear() == this._selectedYear);
619 html += '<select class="calendar_newMonth" ' +
620 'onchange="popUpCal._selectMonthYear(' + this._id + ', this, \'M\');" ' +
621 'onclick="popUpCal._clickMonthYear(' + this._id + ');">';
622 for (var month = 0; month < 12; month++) {
623 if ((!inMinYear || month >= minDate.getMonth()) &&
624 (!inMaxYear || month <= maxDate.getMonth())) {
625 html += '<option value="' + month + '"' +
626 (month == this._selectedMonth ? ' selected="selected"' : '') +
627 '>' + monthNames[month] + '</option>';
633 if (!this._get('changeYear')) {
634 html += this._selectedYear;
637 // determine range of years to display
638 var years = this._get('yearRange').split(':');
641 if (years.length != 2) {
642 year = this._selectedYear - 10;
643 endYear = this._selectedYear + 10;
645 else if (years[0].charAt(0) == '+' || years[0].charAt(0) == '-') {
646 year = this._selectedYear + parseInt(years[0], 10);
647 endYear = this._selectedYear + parseInt(years[1], 10);
650 year = parseInt(years[0], 10);
651 endYear = parseInt(years[1], 10);
653 year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
654 endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
655 html += '<select class="calendar_newYear" onchange="popUpCal._selectMonthYear(' +
656 this._id + ', this, \'Y\');" ' + 'onclick="popUpCal._clickMonthYear(' +
658 for (; year <= endYear; year++) {
659 html += '<option value="' + year + '"' +
660 (year == this._selectedYear ? ' selected="selected"' : '') +
661 '>' + year + '</option>';
665 html += '</div><table class="calendar" cellpadding="0" cellspacing="0"><thead>' +
666 '<tr class="calendar_titleRow">';
667 var firstDay = this._get('firstDay');
668 var changeFirstDay = this._get('changeFirstDay');
669 var dayNames = this._get('dayNames');
670 for (var dow = 0; dow < 7; dow++) { // days of the week
671 html += '<td>' + (!changeFirstDay ? '' : '<a onclick="popUpCal._changeFirstDay(' +
672 this._id + ', this);">') + dayNames[(dow + firstDay) % 7] +
673 (changeFirstDay ? '</a>' : '') + '</td>';
675 html += '</tr></thead><tbody>';
676 var daysInMonth = this._getDaysInMonth(this._selectedYear, this._selectedMonth);
677 this._selectedDay = Math.min(this._selectedDay, daysInMonth);
678 var leadDays = (this._getFirstDayOfMonth(this._selectedYear, this._selectedMonth) - firstDay + 7) % 7;
679 var currentDate = new Date(this._currentYear, this._currentMonth, this._currentDay);
680 var selectedDate = new Date(this._selectedYear, this._selectedMonth, this._selectedDay);
681 var printDate = new Date(this._selectedYear, this._selectedMonth, 1 - leadDays);
682 var numRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
683 var customDate = this._get('customDate');
684 var showOtherMonths = this._get('showOtherMonths');
685 for (var row = 0; row < numRows; row++) { // create calendar rows
686 html += '<tr class="calendar_daysRow">';
687 for (var dow = 0; dow < 7; dow++) { // create calendar days
688 var customSettings = (customDate ? customDate(printDate) : [true, '']);
689 var otherMonth = (printDate.getMonth() != this._selectedMonth);
690 var unselectable = otherMonth || !customSettings[0] ||
691 (minDate && printDate < minDate) || (maxDate && printDate > maxDate);
692 html += '<td class="calendar_daysCell' +
693 ((dow + firstDay + 6) % 7 >= 5 ? ' calendar_weekEndCell' : '') + // highlight weekends
694 (otherMonth ? ' calendar_otherMonth' : '') + // highlight days from other months
695 (printDate.getTime() == selectedDate.getTime() ? ' calendar_daysCellOver' : '') + // highlight selected day
696 (unselectable ? ' calendar_unselectable' : '') + // highlight unselectable days
697 (!otherMonth || showOtherMonths ? ' ' + customSettings[1] : '') + // highlight custom dates
698 (printDate.getTime() == currentDate.getTime() ? ' calendar_currentDay' : // highlight current day
699 (printDate.getTime() == today.getTime() ? ' calendar_today' : '')) + '"' + // highlight today (if different)
700 (unselectable ? '' : ' onmouseover="$(this).addClass(\'calendar_daysCellOver\');"' +
701 ' onmouseout="$(this).removeClass(\'calendar_daysCellOver\');"' +
702 ' onclick="popUpCal._selectDay(' + this._id + ', this);"') + '>' + // actions
703 (otherMonth ? (showOtherMonths ? printDate.getDate() : ' ') : // display for other months
704 (unselectable ? printDate.getDate() : '<a>' + printDate.getDate() + '</a>')) + '</td>'; // display for this month
705 printDate.setDate(printDate.getDate() + 1);
709 html += '</tbody></table>' + (!closeAtTop && !this._inline ? controls : '') +
710 '<div style="clear: both;"></div>' + (!$.browser.msie ? '' :
711 '<!--[if lte IE 6.5]><iframe src="javascript:false;" class="calendar_cover"></iframe><![endif]-->');
715 /* Adjust one of the date sub-fields. */
716 _adjustDate: function(offset, period) {
717 var date = new Date(this._selectedYear + (period == 'Y' ? offset : 0),
718 this._selectedMonth + (period == 'M' ? offset : 0),
719 this._selectedDay + (period == 'D' ? offset : 0));
720 // ensure it is within the bounds set
721 var minDate = this._get('minDate');
722 var maxDate = this._get('maxDate');
723 date = (minDate && date < minDate ? minDate : date);
724 date = (maxDate && date > maxDate ? maxDate : date);
725 this._selectedDay = date.getDate();
726 this._selectedMonth = date.getMonth();
727 this._selectedYear = date.getFullYear();
730 /* Find the number of days in a given month. */
731 _getDaysInMonth: function(year, month) {
732 return 32 - new Date(year, month, 32).getDate();
735 /* Find the day of the week of the first of a month. */
736 _getFirstDayOfMonth: function(year, month) {
737 return new Date(year, month, 1).getDay();
740 /* Determines if we should allow a "next/prev" month display change. */
741 _canAdjustMonth: function(offset) {
742 var date = new Date(this._selectedYear, this._selectedMonth + offset, 1);
744 date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
746 return this._isInRange(date);
749 /* Is the given date in the accepted range? */
750 _isInRange: function(date) {
751 var minDate = this._get('minDate');
752 var maxDate = this._get('maxDate');
753 return ((!minDate || date >= minDate) && (!maxDate || date <= maxDate));
756 /* Format the given date for display. */
757 _formatDate: function() {
758 var day = this._currentDay = this._selectedDay;
759 var month = this._currentMonth = this._selectedMonth;
760 var year = this._currentYear = this._selectedYear;
761 month++; // adjust javascript month
762 var dateFormat = this._get('dateFormat');
764 for (var i = 0; i < 3; i++) {
765 dateString += dateFormat.charAt(3) +
766 (dateFormat.charAt(i) == 'D' ? (day < 10 ? '0' : '') + day :
767 (dateFormat.charAt(i) == 'M' ? (month < 10 ? '0' : '') + month :
768 (dateFormat.charAt(i) == 'Y' ? year : '?')));
770 return dateString.substring(dateFormat.charAt(3) ? 1 : 0);
774 /* Attach the calendar to a jQuery selection.
775 @param settings object - the new settings to use for this calendar instance (anonymous)
776 @return jQuery object - for chaining further calls */
777 $.fn.calendar = function(settings) {
778 return this.each(function() {
779 // check for settings on the control itself - in namespace 'cal:'
780 var inlineSettings = null;
781 for (attrName in popUpCal._defaults) {
782 var attrValue = this.getAttribute('cal:' + attrName);
784 inlineSettings = inlineSettings || {};
786 inlineSettings[attrName] = eval(attrValue);
789 inlineSettings[attrName] = attrValue;
793 var nodeName = this.nodeName.toLowerCase();
794 if (nodeName == 'input') {
795 var instSettings = (inlineSettings ? $.extend($.extend({}, settings || {}),
796 inlineSettings || {}) : settings); // clone and customise
797 var inst = (inst && !inlineSettings ? inst :
798 new PopUpCalInstance(instSettings, false));
799 popUpCal._connectCalendar(this, inst);
801 else if (nodeName == 'div' || nodeName == 'span') {
802 var instSettings = $.extend($.extend({}, settings || {}),
803 inlineSettings || {}); // clone and customise
804 var inst = new PopUpCalInstance(instSettings, true);
805 popUpCal._inlineCalendar(this, inst);
810 /* Initialise the calendar. */
811 $(document).ready(function() {
812 popUpCal = new PopUpCal(); // singleton instance