1 /* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
2 * ------------------------------------------------------------------
4 * The DHTML Calendar, version 0.9.5 "Your favorite time, bis"
6 * Details and latest version at:
7 * http://dynarch.com/mishoo/calendar.epl
9 * This script is distributed under the GNU Lesser General Public License.
10 * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
13 // $Id: calendar.js,v 1.3 2003-11-07 10:53:35 ivan Exp $
15 /** The Calendar object constructor. */
16 Calendar = function (mondayFirst, dateStr, onSelected, onClose) {
18 this.activeDiv = null;
19 this.currentDateEl = null;
20 this.getDateStatus = null;
22 this.onSelected = onSelected || null;
23 this.onClose = onClose || null;
24 this.dragging = false;
28 this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"];
29 this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"];
31 this.weekNumbers = true;
32 this.mondayFirst = mondayFirst;
33 this.dateStr = dateStr;
35 this.showsTime = false;
41 this.firstdayname = null;
43 this.monthsCombo = null;
44 this.yearsCombo = null;
45 this.hilitedMonth = null;
46 this.activeMonth = null;
47 this.hilitedYear = null;
48 this.activeYear = null;
50 this.dateClicked = false;
52 // one-time initializations
53 if (typeof Calendar._SDN == "undefined") {
54 // table of short day names
55 if (typeof Calendar._SDN_len == "undefined")
56 Calendar._SDN_len = 3;
58 for (var i = 8; i > 0;) {
59 ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len);
62 // table of short month names
63 if (typeof Calendar._SMN_len == "undefined")
64 Calendar._SMN_len = 3;
66 for (var i = 12; i > 0;) {
67 ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len);
75 /// "static", needed for event handlers.
78 /// detect a special case of "web browser"
79 Calendar.is_ie = ( /msie/i.test(navigator.userAgent) &&
80 !/opera/i.test(navigator.userAgent) );
82 /// detect Opera browser
83 Calendar.is_opera = /opera/i.test(navigator.userAgent);
85 /// detect KHTML-based browsers
86 Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);
88 // BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate
89 // library, at some point.
91 Calendar.getAbsolutePos = function(el) {
93 var is_div = /^div$/i.test(el.tagName);
94 if (is_div && el.scrollLeft)
96 if (is_div && el.scrollTop)
98 var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
99 if (el.offsetParent) {
100 var tmp = Calendar.getAbsolutePos(el.offsetParent);
107 Calendar.isRelated = function (el, evt) {
108 var related = evt.relatedTarget;
111 if (type == "mouseover") {
112 related = evt.fromElement;
113 } else if (type == "mouseout") {
114 related = evt.toElement;
121 related = related.parentNode;
126 Calendar.removeClass = function(el, className) {
127 if (!(el && el.className)) {
130 var cls = el.className.split(" ");
131 var ar = new Array();
132 for (var i = cls.length; i > 0;) {
133 if (cls[--i] != className) {
134 ar[ar.length] = cls[i];
137 el.className = ar.join(" ");
140 Calendar.addClass = function(el, className) {
141 Calendar.removeClass(el, className);
142 el.className += " " + className;
145 Calendar.getElement = function(ev) {
146 if (Calendar.is_ie) {
147 return window.event.srcElement;
149 return ev.currentTarget;
153 Calendar.getTargetElement = function(ev) {
154 if (Calendar.is_ie) {
155 return window.event.srcElement;
161 Calendar.stopEvent = function(ev) {
162 ev || (ev = window.event);
163 if (Calendar.is_ie) {
164 ev.cancelBubble = true;
165 ev.returnValue = false;
168 ev.stopPropagation();
173 Calendar.addEvent = function(el, evname, func) {
174 if (el.attachEvent) { // IE
175 el.attachEvent("on" + evname, func);
176 } else if (el.addEventListener) { // Gecko / W3C
177 el.addEventListener(evname, func, true);
179 el["on" + evname] = func;
183 Calendar.removeEvent = function(el, evname, func) {
184 if (el.detachEvent) { // IE
185 el.detachEvent("on" + evname, func);
186 } else if (el.removeEventListener) { // Gecko / W3C
187 el.removeEventListener(evname, func, true);
189 el["on" + evname] = null;
193 Calendar.createElement = function(type, parent) {
195 if (document.createElementNS) {
196 // use the XHTML namespace; IE won't normally get here unless
197 // _they_ "fix" the DOM2 implementation.
198 el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
200 el = document.createElement(type);
202 if (typeof parent != "undefined") {
203 parent.appendChild(el);
208 // END: UTILITY FUNCTIONS
210 // BEGIN: CALENDAR STATIC FUNCTIONS
212 /** Internal -- adds a set of events to make some element behave like a button. */
213 Calendar._add_evs = function(el) {
215 addEvent(el, "mouseover", dayMouseOver);
216 addEvent(el, "mousedown", dayMouseDown);
217 addEvent(el, "mouseout", dayMouseOut);
219 addEvent(el, "dblclick", dayMouseDblClick);
220 el.setAttribute("unselectable", true);
225 Calendar.findMonth = function(el) {
226 if (typeof el.month != "undefined") {
228 } else if (typeof el.parentNode.month != "undefined") {
229 return el.parentNode;
234 Calendar.findYear = function(el) {
235 if (typeof el.year != "undefined") {
237 } else if (typeof el.parentNode.year != "undefined") {
238 return el.parentNode;
243 Calendar.showMonthsCombo = function () {
244 var cal = Calendar._C;
249 var cd = cal.activeDiv;
250 var mc = cal.monthsCombo;
251 if (cal.hilitedMonth) {
252 Calendar.removeClass(cal.hilitedMonth, "hilite");
254 if (cal.activeMonth) {
255 Calendar.removeClass(cal.activeMonth, "active");
257 var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];
258 Calendar.addClass(mon, "active");
259 cal.activeMonth = mon;
263 s.left = cd.offsetLeft + "px";
265 s.left = (cd.offsetLeft + cd.offsetWidth - mc.offsetWidth) + "px";
266 s.top = (cd.offsetTop + cd.offsetHeight) + "px";
269 Calendar.showYearsCombo = function (fwd) {
270 var cal = Calendar._C;
275 var cd = cal.activeDiv;
276 var yc = cal.yearsCombo;
277 if (cal.hilitedYear) {
278 Calendar.removeClass(cal.hilitedYear, "hilite");
280 if (cal.activeYear) {
281 Calendar.removeClass(cal.activeYear, "active");
283 cal.activeYear = null;
284 var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
285 var yr = yc.firstChild;
287 for (var i = 12; i > 0; --i) {
288 if (Y >= cal.minYear && Y <= cal.maxYear) {
289 yr.firstChild.data = Y;
291 yr.style.display = "block";
294 yr.style.display = "none";
303 s.left = cd.offsetLeft + "px";
305 s.left = (cd.offsetLeft + cd.offsetWidth - yc.offsetWidth) + "px";
306 s.top = (cd.offsetTop + cd.offsetHeight) + "px";
312 Calendar.tableMouseUp = function(ev) {
313 var cal = Calendar._C;
318 clearTimeout(cal.timeout);
320 var el = cal.activeDiv;
324 var target = Calendar.getTargetElement(ev);
325 ev || (ev = window.event);
326 Calendar.removeClass(el, "active");
327 if (target == el || target.parentNode == el) {
328 Calendar.cellClick(el, ev);
330 var mon = Calendar.findMonth(target);
333 date = new Date(cal.date);
334 if (mon.month != date.getMonth()) {
335 date.setMonth(mon.month);
337 cal.dateClicked = false;
341 var year = Calendar.findYear(target);
343 date = new Date(cal.date);
344 if (year.year != date.getFullYear()) {
345 date.setFullYear(year.year);
347 cal.dateClicked = false;
353 removeEvent(document, "mouseup", tableMouseUp);
354 removeEvent(document, "mouseover", tableMouseOver);
355 removeEvent(document, "mousemove", tableMouseOver);
358 return stopEvent(ev);
362 Calendar.tableMouseOver = function (ev) {
363 var cal = Calendar._C;
367 var el = cal.activeDiv;
368 var target = Calendar.getTargetElement(ev);
369 if (target == el || target.parentNode == el) {
370 Calendar.addClass(el, "hilite active");
371 Calendar.addClass(el.parentNode, "rowhilite");
373 if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2)))
374 Calendar.removeClass(el, "active");
375 Calendar.removeClass(el, "hilite");
376 Calendar.removeClass(el.parentNode, "rowhilite");
378 ev || (ev = window.event);
379 if (el.navtype == 50 && target != el) {
380 var pos = Calendar.getAbsolutePos(el);
381 var w = el.offsetWidth;
392 var range = el._range;
393 var current = el._current;
394 var count = Math.floor(dx / 10) % range.length;
395 for (var i = range.length; --i >= 0;)
396 if (range[i] == current)
401 i = range.length - 1;
402 } else if (!(++i in range))
404 var newval = range[i];
405 el.firstChild.data = newval;
409 var mon = Calendar.findMonth(target);
411 if (mon.month != cal.date.getMonth()) {
412 if (cal.hilitedMonth) {
413 Calendar.removeClass(cal.hilitedMonth, "hilite");
415 Calendar.addClass(mon, "hilite");
416 cal.hilitedMonth = mon;
417 } else if (cal.hilitedMonth) {
418 Calendar.removeClass(cal.hilitedMonth, "hilite");
421 if (cal.hilitedMonth) {
422 Calendar.removeClass(cal.hilitedMonth, "hilite");
424 var year = Calendar.findYear(target);
426 if (year.year != cal.date.getFullYear()) {
427 if (cal.hilitedYear) {
428 Calendar.removeClass(cal.hilitedYear, "hilite");
430 Calendar.addClass(year, "hilite");
431 cal.hilitedYear = year;
432 } else if (cal.hilitedYear) {
433 Calendar.removeClass(cal.hilitedYear, "hilite");
435 } else if (cal.hilitedYear) {
436 Calendar.removeClass(cal.hilitedYear, "hilite");
439 return Calendar.stopEvent(ev);
442 Calendar.tableMouseDown = function (ev) {
443 if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) {
444 return Calendar.stopEvent(ev);
448 Calendar.calDragIt = function (ev) {
449 var cal = Calendar._C;
450 if (!(cal && cal.dragging)) {
455 if (Calendar.is_ie) {
456 posY = window.event.clientY + document.body.scrollTop;
457 posX = window.event.clientX + document.body.scrollLeft;
462 cal.hideShowCovered();
463 var st = cal.element.style;
464 st.left = (posX - cal.xOffs) + "px";
465 st.top = (posY - cal.yOffs) + "px";
466 return Calendar.stopEvent(ev);
469 Calendar.calDragEnd = function (ev) {
470 var cal = Calendar._C;
474 cal.dragging = false;
476 removeEvent(document, "mousemove", calDragIt);
477 removeEvent(document, "mouseover", stopEvent);
478 removeEvent(document, "mouseup", calDragEnd);
481 cal.hideShowCovered();
484 Calendar.dayMouseDown = function(ev) {
485 var el = Calendar.getElement(ev);
489 var cal = el.calendar;
492 if (el.navtype != 300) with (Calendar) {
493 if (el.navtype == 50)
494 el._current = el.firstChild.data;
495 addClass(el, "hilite active");
496 addEvent(document, "mouseover", tableMouseOver);
497 addEvent(document, "mousemove", tableMouseOver);
498 addEvent(document, "mouseup", tableMouseUp);
499 } else if (cal.isPopup) {
502 if (el.navtype == -1 || el.navtype == 1) {
503 if (cal.timeout) clearTimeout(cal.timeout);
504 cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250);
505 } else if (el.navtype == -2 || el.navtype == 2) {
506 if (cal.timeout) clearTimeout(cal.timeout);
507 cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250);
511 return Calendar.stopEvent(ev);
514 Calendar.dayMouseDblClick = function(ev) {
515 Calendar.cellClick(Calendar.getElement(ev), ev || window.event);
516 if (Calendar.is_ie) {
517 document.selection.empty();
521 Calendar.dayMouseOver = function(ev) {
522 var el = Calendar.getElement(ev);
523 if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) {
527 if (el.ttip.substr(0, 1) == "_") {
529 with (el.calendar.date) {
530 date = new Date(getFullYear(), getMonth(), el.caldate);
532 el.ttip = date.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
534 el.calendar.tooltips.firstChild.data = el.ttip;
536 if (el.navtype != 300) {
537 Calendar.addClass(el, "hilite");
539 Calendar.addClass(el.parentNode, "rowhilite");
542 return Calendar.stopEvent(ev);
545 Calendar.dayMouseOut = function(ev) {
547 var el = getElement(ev);
548 if (isRelated(el, ev) || _C || el.disabled) {
551 removeClass(el, "hilite");
553 removeClass(el.parentNode, "rowhilite");
555 el.calendar.tooltips.firstChild.data = _TT["SEL_DATE"];
556 return stopEvent(ev);
561 * A generic "click" handler :) handles all types of buttons defined in this
564 Calendar.cellClick = function(el, ev) {
565 var cal = el.calendar;
569 if (typeof el.navtype == "undefined") {
570 Calendar.removeClass(cal.currentDateEl, "selected");
571 Calendar.addClass(el, "selected");
572 closing = (cal.currentDateEl == el);
574 cal.currentDateEl = el;
576 cal.date.setDate(el.caldate);
579 // a date was clicked
580 cal.dateClicked = true;
582 if (el.navtype == 200) {
583 Calendar.removeClass(el, "hilite");
584 cal.callCloseHandler();
587 date = (el.navtype == 0) ? new Date() : new Date(cal.date);
588 // unless "today" was clicked, we assume no date was clicked so
589 // the selected handler will know not to close the calenar when
590 // in single-click mode.
591 // cal.dateClicked = (el.navtype == 0);
592 cal.dateClicked = false;
593 var year = date.getFullYear();
594 var mon = date.getMonth();
595 function setMonth(m) {
596 var day = date.getDate();
597 var max = date.getMonthDays(m);
603 switch (el.navtype) {
605 Calendar.removeClass(el, "hilite");
606 var text = Calendar._TT["ABOUT"];
607 if (typeof text != "undefined") {
608 text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : "";
610 // FIXME: this should be removed as soon as lang files get updated!
611 text = "Help and about box text is not translated into this language.\n" +
612 "If you know this language and you feel generous please update\n" +
613 "the corresponding file in \"lang\" subdir to match calendar-en.js\n" +
614 "and send it back to <mishoo@infoiasi.ro> to get it into the distribution ;-)\n\n" +
616 "http://dynarch.com/mishoo/calendar.epl\n";
621 if (year > cal.minYear) {
622 date.setFullYear(year - 1);
628 } else if (year-- > cal.minYear) {
629 date.setFullYear(year);
636 } else if (year < cal.maxYear) {
637 date.setFullYear(year + 1);
642 if (year < cal.maxYear) {
643 date.setFullYear(year + 1);
647 cal.setMondayFirst(!cal.mondayFirst);
650 var range = el._range;
651 var current = el.firstChild.data;
652 for (var i = range.length; --i >= 0;)
653 if (range[i] == current)
655 if (ev && ev.shiftKey) {
657 i = range.length - 1;
658 } else if (!(++i in range))
660 var newval = range[i];
661 el.firstChild.data = newval;
665 // TODAY will bring us here
666 if ((typeof cal.getDateStatus == "function") && cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) {
667 // remember, "date" was previously set to new
668 // Date() if TODAY was clicked; thus, it
669 // contains today date.
674 if (!date.equalsTo(cal.date)) {
683 Calendar.removeClass(el, "hilite");
684 cal.callCloseHandler();
688 // END: CALENDAR STATIC FUNCTIONS
690 // BEGIN: CALENDAR OBJECT FUNCTIONS
693 * This function creates the calendar inside the given parent. If _par is
694 * null than it creates a popup calendar inside the BODY element. If _par is
695 * an element, be it BODY, then it creates a non-popup calendar (still
696 * hidden). Some properties need to be set before calling this function.
698 Calendar.prototype.create = function (_par) {
701 // default parent is the document body, in which case we create
703 parent = document.getElementsByTagName("body")[0];
707 this.isPopup = false;
709 this.date = this.dateStr ? new Date(this.dateStr) : new Date();
711 var table = Calendar.createElement("table");
713 table.cellSpacing = 0;
714 table.cellPadding = 0;
715 table.calendar = this;
716 Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown);
718 var div = Calendar.createElement("div");
720 div.className = "calendar";
722 div.style.position = "absolute";
723 div.style.display = "none";
725 div.appendChild(table);
727 var thead = Calendar.createElement("thead", table);
732 var hh = function (text, cs, navtype) {
733 cell = Calendar.createElement("td", row);
735 cell.className = "button";
736 if (navtype != 0 && Math.abs(navtype) <= 2)
737 cell.className += " nav";
738 Calendar._add_evs(cell);
740 cell.navtype = navtype;
741 if (text.substr(0, 1) != "&") {
742 cell.appendChild(document.createTextNode(text));
745 // FIXME: dirty hack for entities
746 cell.innerHTML = text;
751 row = Calendar.createElement("tr", thead);
752 var title_length = 6;
753 (this.isPopup) && --title_length;
754 (this.weekNumbers) && ++title_length;
756 hh("?", 1, 400).ttip = Calendar._TT["INFO"];
757 this.title = hh("", title_length, 300);
758 this.title.className = "title";
760 this.title.ttip = Calendar._TT["DRAG_TO_MOVE"];
761 this.title.style.cursor = "move";
762 hh("×", 1, 200).ttip = Calendar._TT["CLOSE"];
765 row = Calendar.createElement("tr", thead);
766 row.className = "headrow";
768 this._nav_py = hh("«", 1, -2);
769 this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
771 this._nav_pm = hh("‹", 1, -1);
772 this._nav_pm.ttip = Calendar._TT["PREV_MONTH"];
774 this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0);
775 this._nav_now.ttip = Calendar._TT["GO_TODAY"];
777 this._nav_nm = hh("›", 1, 1);
778 this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
780 this._nav_ny = hh("»", 1, 2);
781 this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"];
784 row = Calendar.createElement("tr", thead);
785 row.className = "daynames";
786 if (this.weekNumbers) {
787 cell = Calendar.createElement("td", row);
788 cell.className = "name wn";
789 cell.appendChild(document.createTextNode(Calendar._TT["WK"]));
791 for (var i = 7; i > 0; --i) {
792 cell = Calendar.createElement("td", row);
793 cell.appendChild(document.createTextNode(""));
796 cell.calendar = this;
797 Calendar._add_evs(cell);
800 this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild;
801 this._displayWeekdays();
803 var tbody = Calendar.createElement("tbody", table);
806 for (i = 6; i > 0; --i) {
807 row = Calendar.createElement("tr", tbody);
808 if (this.weekNumbers) {
809 cell = Calendar.createElement("td", row);
810 cell.appendChild(document.createTextNode(""));
812 for (var j = 7; j > 0; --j) {
813 cell = Calendar.createElement("td", row);
814 cell.appendChild(document.createTextNode(""));
815 cell.calendar = this;
816 Calendar._add_evs(cell);
820 if (this.showsTime) {
821 row = Calendar.createElement("tr", tbody);
822 row.className = "time";
824 cell = Calendar.createElement("td", row);
825 cell.className = "time";
827 cell.innerHTML = " ";
829 cell = Calendar.createElement("td", row);
830 cell.className = "time";
831 cell.colSpan = this.weekNumbers ? 4 : 3;
834 function makeTimePart(className, init, range_start, range_end) {
835 var part = Calendar.createElement("span", cell);
836 part.className = className;
837 part.appendChild(document.createTextNode(init));
839 part.ttip = Calendar._TT["TIME_PART"];
842 if (typeof range_start != "number")
843 part._range = range_start;
845 for (var i = range_start; i <= range_end; ++i) {
847 if (i < 10 && range_end >= 10) txt = '0' + i;
849 part._range[part._range.length] = txt;
852 Calendar._add_evs(part);
855 var hrs = cal.date.getHours();
856 var mins = cal.date.getMinutes();
857 var t12 = !cal.time24;
859 if (t12 && pm) hrs -= 12;
860 var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
861 var span = Calendar.createElement("span", cell);
862 span.appendChild(document.createTextNode(":"));
863 span.className = "colon";
864 var M = makeTimePart("minute", mins, 0, 59);
866 cell = Calendar.createElement("td", row);
867 cell.className = "time";
870 AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]);
872 cell.innerHTML = " ";
874 cal.onSetTime = function() {
875 var hrs = this.date.getHours();
876 var mins = this.date.getMinutes();
878 if (pm && t12) hrs -= 12;
879 H.firstChild.data = (hrs < 10) ? ("0" + hrs) : hrs;
880 M.firstChild.data = (mins < 10) ? ("0" + mins) : mins;
882 AP.firstChild.data = pm ? "pm" : "am";
885 cal.onUpdateTime = function() {
886 var date = this.date;
887 var h = parseInt(H.firstChild.data, 10);
889 if (/pm/i.test(AP.firstChild.data) && h < 12)
891 else if (/am/i.test(AP.firstChild.data) && h == 12)
894 var d = date.getDate();
895 var m = date.getMonth();
896 var y = date.getFullYear();
898 date.setMinutes(parseInt(M.firstChild.data, 10));
902 this.dateClicked = false;
907 this.onSetTime = this.onUpdateTime = function() {};
910 var tfoot = Calendar.createElement("tfoot", table);
912 row = Calendar.createElement("tr", tfoot);
913 row.className = "footrow";
915 cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300);
916 cell.className = "ttip";
918 cell.ttip = Calendar._TT["DRAG_TO_MOVE"];
919 cell.style.cursor = "move";
921 this.tooltips = cell;
923 div = Calendar.createElement("div", this.element);
924 this.monthsCombo = div;
925 div.className = "combo";
926 for (i = 0; i < Calendar._MN.length; ++i) {
927 var mn = Calendar.createElement("div");
928 mn.className = Calendar.is_ie ? "label-IEfix" : "label";
930 mn.appendChild(document.createTextNode(Calendar._SMN[i]));
934 div = Calendar.createElement("div", this.element);
935 this.yearsCombo = div;
936 div.className = "combo";
937 for (i = 12; i > 0; --i) {
938 var yr = Calendar.createElement("div");
939 yr.className = Calendar.is_ie ? "label-IEfix" : "label";
940 yr.appendChild(document.createTextNode(""));
944 this._init(this.mondayFirst, this.date);
945 parent.appendChild(this.element);
948 /** keyboard navigation, only for popup calendars */
949 Calendar._keyEvent = function(ev) {
950 if (!window.calendar) {
953 (Calendar.is_ie) && (ev = window.event);
954 var cal = window.calendar;
955 var act = (Calendar.is_ie || ev.type == "keypress");
957 switch (ev.keyCode) {
959 act && Calendar.cellClick(cal._nav_pm);
962 act && Calendar.cellClick(cal._nav_py);
964 case 39: // KEY right
965 act && Calendar.cellClick(cal._nav_nm);
968 act && Calendar.cellClick(cal._nav_ny);
973 } else switch (ev.keyCode) {
974 case 32: // KEY space (now)
975 Calendar.cellClick(cal._nav_now);
982 case 39: // KEY right
985 var date = cal.date.getDate() - 1;
986 var el = cal.currentDateEl;
988 var prev = (ev.keyCode == 37) || (ev.keyCode == 38);
989 switch (ev.keyCode) {
991 (--date >= 0) && (ne = cal.ar_days[date]);
995 (date >= 0) && (ne = cal.ar_days[date]);
997 case 39: // KEY right
998 (++date < cal.ar_days.length) && (ne = cal.ar_days[date]);
1000 case 40: // KEY down
1002 (date < cal.ar_days.length) && (ne = cal.ar_days[date]);
1007 Calendar.cellClick(cal._nav_pm);
1009 Calendar.cellClick(cal._nav_nm);
1011 date = (prev) ? cal.date.getMonthDays() : 1;
1012 el = cal.currentDateEl;
1013 ne = cal.ar_days[date - 1];
1015 Calendar.removeClass(el, "selected");
1016 Calendar.addClass(ne, "selected");
1017 cal.date.setDate(ne.caldate);
1019 cal.currentDateEl = ne;
1022 case 13: // KEY enter
1031 return Calendar.stopEvent(ev);
1035 * (RE)Initializes the calendar to the given date and style (if mondayFirst is
1036 * true it makes Monday the first day of week, otherwise the weeks start on
1039 Calendar.prototype._init = function (mondayFirst, date) {
1040 var today = new Date();
1041 var year = date.getFullYear();
1042 if (year < this.minYear) {
1043 year = this.minYear;
1044 date.setFullYear(year);
1045 } else if (year > this.maxYear) {
1046 year = this.maxYear;
1047 date.setFullYear(year);
1049 this.mondayFirst = mondayFirst;
1050 this.date = new Date(date);
1051 var month = date.getMonth();
1052 var mday = date.getDate();
1053 var no_days = date.getMonthDays();
1055 var wday = date.getDay();
1056 var MON = mondayFirst ? 1 : 0;
1057 var SAT = mondayFirst ? 5 : 6;
1058 var SUN = mondayFirst ? 6 : 0;
1060 wday = (wday > 0) ? (wday - 1) : 6;
1063 var row = this.tbody.firstChild;
1064 var MN = Calendar._SMN[month];
1065 var hasToday = ((today.getFullYear() == year) && (today.getMonth() == month));
1066 var todayDate = today.getDate();
1067 var week_number = date.getWeekNumber();
1068 var ar_days = new Array();
1069 for (var i = 0; i < 6; ++i) {
1070 if (iday > no_days) {
1071 row.className = "emptyrow";
1072 row = row.nextSibling;
1075 var cell = row.firstChild;
1076 if (this.weekNumbers) {
1077 cell.className = "day wn";
1078 cell.firstChild.data = week_number;
1079 cell = cell.nextSibling;
1082 row.className = "daysrow";
1083 for (var j = 0; j < 7; ++j) {
1084 cell.className = "day";
1085 if ((!i && j < wday) || iday > no_days) {
1086 // cell.className = "emptycell";
1087 cell.innerHTML = " ";
1088 cell.disabled = true;
1089 cell = cell.nextSibling;
1092 cell.disabled = false;
1093 cell.firstChild.data = iday;
1094 if (typeof this.getDateStatus == "function") {
1096 var status = this.getDateStatus(date, year, month, iday);
1097 if (status === true) {
1098 cell.className += " disabled";
1099 cell.disabled = true;
1101 if (/disabled/i.test(status))
1102 cell.disabled = true;
1103 cell.className += " " + status;
1106 if (!cell.disabled) {
1107 ar_days[ar_days.length] = cell;
1108 cell.caldate = iday;
1111 cell.className += " selected";
1112 this.currentDateEl = cell;
1114 if (hasToday && (iday == todayDate)) {
1115 cell.className += " today";
1116 cell.ttip += Calendar._TT["PART_TODAY"];
1118 if (wday == SAT || wday == SUN) {
1119 cell.className += " weekend";
1123 ((++wday) ^ 7) || (wday = 0);
1124 cell = cell.nextSibling;
1126 row = row.nextSibling;
1128 this.ar_days = ar_days;
1129 this.title.firstChild.data = Calendar._MN[month] + ", " + year;
1132 // this.tooltips.firstChild.data = "Generated in " + ((new Date()) - today) + " ms";
1136 * Calls _init function above for going to a certain date (but only if the
1137 * date is different than the currently selected one).
1139 Calendar.prototype.setDate = function (date) {
1140 if (!date.equalsTo(this.date)) {
1141 this._init(this.mondayFirst, date);
1146 * Refreshes the calendar. Useful if the "disabledHandler" function is
1147 * dynamic, meaning that the list of disabled date can change at runtime.
1148 * Just * call this function if you think that the list of disabled dates
1151 Calendar.prototype.refresh = function () {
1152 this._init(this.mondayFirst, this.date);
1155 /** Modifies the "mondayFirst" parameter (EU/US style). */
1156 Calendar.prototype.setMondayFirst = function (mondayFirst) {
1157 this._init(mondayFirst, this.date);
1158 this._displayWeekdays();
1162 * Allows customization of what dates are enabled. The "unaryFunction"
1163 * parameter must be a function object that receives the date (as a JS Date
1164 * object) and returns a boolean value. If the returned value is true then
1165 * the passed date will be marked as disabled.
1167 Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) {
1168 this.getDateStatus = unaryFunction;
1171 /** Customization of allowed year range for the calendar. */
1172 Calendar.prototype.setRange = function (a, z) {
1177 /** Calls the first user handler (selectedHandler). */
1178 Calendar.prototype.callHandler = function () {
1179 if (this.onSelected) {
1180 this.onSelected(this, this.date.print(this.dateFormat));
1184 /** Calls the second user handler (closeHandler). */
1185 Calendar.prototype.callCloseHandler = function () {
1189 this.hideShowCovered();
1192 /** Removes the calendar object from the DOM tree and destroys it. */
1193 Calendar.prototype.destroy = function () {
1194 var el = this.element.parentNode;
1195 el.removeChild(this.element);
1197 window.calendar = null;
1201 * Moves the calendar element to a different section in the DOM tree (changes
1204 Calendar.prototype.reparent = function (new_parent) {
1205 var el = this.element;
1206 el.parentNode.removeChild(el);
1207 new_parent.appendChild(el);
1210 // This gets called when the user presses a mouse button anywhere in the
1211 // document, if the calendar is shown. If the click was outside the open
1212 // calendar this function closes it.
1213 Calendar._checkCalendar = function(ev) {
1214 if (!window.calendar) {
1217 var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
1218 for (; el != null && el != calendar.element; el = el.parentNode);
1220 // calls closeHandler which should hide the calendar.
1221 window.calendar.callCloseHandler();
1222 return Calendar.stopEvent(ev);
1226 /** Shows the calendar. */
1227 Calendar.prototype.show = function () {
1228 var rows = this.table.getElementsByTagName("tr");
1229 for (var i = rows.length; i > 0;) {
1230 var row = rows[--i];
1231 Calendar.removeClass(row, "rowhilite");
1232 var cells = row.getElementsByTagName("td");
1233 for (var j = cells.length; j > 0;) {
1234 var cell = cells[--j];
1235 Calendar.removeClass(cell, "hilite");
1236 Calendar.removeClass(cell, "active");
1239 this.element.style.display = "block";
1240 this.hidden = false;
1242 window.calendar = this;
1243 Calendar.addEvent(document, "keydown", Calendar._keyEvent);
1244 Calendar.addEvent(document, "keypress", Calendar._keyEvent);
1245 Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
1247 this.hideShowCovered();
1251 * Hides the calendar. Also removes any "hilite" from the class of any TD
1254 Calendar.prototype.hide = function () {
1256 Calendar.removeEvent(document, "keydown", Calendar._keyEvent);
1257 Calendar.removeEvent(document, "keypress", Calendar._keyEvent);
1258 Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
1260 this.element.style.display = "none";
1262 this.hideShowCovered();
1266 * Shows the calendar at a given absolute position (beware that, depending on
1267 * the calendar element style -- position property -- this might be relative
1268 * to the parent's containing rectangle).
1270 Calendar.prototype.showAt = function (x, y) {
1271 var s = this.element.style;
1277 /** Shows the calendar near a given element. */
1278 Calendar.prototype.showAtElement = function (el, opts) {
1280 var p = Calendar.getAbsolutePos(el);
1281 if (!opts || typeof opts != "string") {
1282 this.showAt(p.x, p.y + el.offsetHeight);
1285 this.element.style.display = "block";
1286 Calendar.continuation_for_the_fucking_khtml_browser = function() {
1287 var w = self.element.offsetWidth;
1288 var h = self.element.offsetHeight;
1289 self.element.style.display = "none";
1290 var valign = opts.substr(0, 1);
1292 if (opts.length > 1) {
1293 halign = opts.substr(1, 1);
1295 // vertical alignment
1297 case "T": p.y -= h; break;
1298 case "B": p.y += el.offsetHeight; break;
1299 case "C": p.y += (el.offsetHeight - h) / 2; break;
1300 case "t": p.y += el.offsetHeight - h; break;
1301 case "b": break; // already there
1303 // horizontal alignment
1305 case "L": p.x -= w; break;
1306 case "R": p.x += el.offsetWidth; break;
1307 case "C": p.x += (el.offsetWidth - w) / 2; break;
1308 case "r": p.x += el.offsetWidth - w; break;
1309 case "l": break; // already there
1311 self.showAt(p.x, p.y);
1313 if (Calendar.is_khtml)
1314 setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
1316 Calendar.continuation_for_the_fucking_khtml_browser();
1319 /** Customizes the date format. */
1320 Calendar.prototype.setDateFormat = function (str) {
1321 this.dateFormat = str;
1324 /** Customizes the tooltip date format. */
1325 Calendar.prototype.setTtDateFormat = function (str) {
1326 this.ttDateFormat = str;
1330 * Tries to identify the date represented in a string. If successful it also
1331 * calls this.setDate which moves the calendar to the given date.
1333 Calendar.prototype.parseDate = function (str, fmt) {
1337 var a = str.split(/\W+/);
1339 fmt = this.dateFormat;
1342 fmt.replace(/(%.)/g, function(str, par) {
1343 return b[b.length] = par;
1348 for (i = 0; i < a.length; ++i) {
1349 if (b[i] == "%a" || b[i] == "%A") {
1352 if (b[i] == "%d" || b[i] == "%e") {
1353 d = parseInt(a[i], 10);
1356 m = parseInt(a[i], 10) - 1;
1358 if (b[i] == "%Y" || b[i] == "%y") {
1359 y = parseInt(a[i], 10);
1360 (y < 100) && (y += (y > 29) ? 1900 : 2000);
1362 if (b[i] == "%b" || b[i] == "%B") {
1363 for (j = 0; j < 12; ++j) {
1364 if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; }
1366 } else if (/%[HIkl]/.test(b[i])) {
1367 hr = parseInt(a[i], 10);
1368 } else if (/%[pP]/.test(b[i])) {
1369 if (/pm/i.test(a[i]) && hr < 12)
1371 } else if (b[i] == "%M") {
1372 min = parseInt(a[i], 10);
1375 if (y != 0 && m != -1 && d != 0) {
1376 this.setDate(new Date(y, m, d, hr, min, 0));
1379 y = 0; m = -1; d = 0;
1380 for (i = 0; i < a.length; ++i) {
1381 if (a[i].search(/[a-zA-Z]+/) != -1) {
1383 for (j = 0; j < 12; ++j) {
1384 if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
1392 } else if (parseInt(a[i], 10) <= 12 && m == -1) {
1394 } else if (parseInt(a[i], 10) > 31 && y == 0) {
1395 y = parseInt(a[i], 10);
1396 (y < 100) && (y += (y > 29) ? 1900 : 2000);
1397 } else if (d == 0) {
1402 var today = new Date();
1403 y = today.getFullYear();
1405 if (m != -1 && d != 0) {
1406 this.setDate(new Date(y, m, d, hr, min, 0));
1410 Calendar.prototype.hideShowCovered = function () {
1412 Calendar.continuation_for_the_fucking_khtml_browser = function() {
1413 function getVisib(obj){
1414 var value = obj.style.visibility;
1416 if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
1417 if (!Calendar.is_khtml)
1418 value = document.defaultView.
1419 getComputedStyle(obj, "").getPropertyValue("visibility");
1422 } else if (obj.currentStyle) { // IE
1423 value = obj.currentStyle.visibility;
1430 var tags = new Array("applet", "iframe", "select");
1431 var el = self.element;
1433 var p = Calendar.getAbsolutePos(el);
1435 var EX2 = el.offsetWidth + EX1;
1437 var EY2 = el.offsetHeight + EY1;
1439 for (var k = tags.length; k > 0; ) {
1440 var ar = document.getElementsByTagName(tags[--k]);
1443 for (var i = ar.length; i > 0;) {
1446 p = Calendar.getAbsolutePos(cc);
1448 var CX2 = cc.offsetWidth + CX1;
1450 var CY2 = cc.offsetHeight + CY1;
1452 if (self.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
1453 if (!cc.__msh_save_visibility) {
1454 cc.__msh_save_visibility = getVisib(cc);
1456 cc.style.visibility = cc.__msh_save_visibility;
1458 if (!cc.__msh_save_visibility) {
1459 cc.__msh_save_visibility = getVisib(cc);
1461 cc.style.visibility = "hidden";
1466 if (Calendar.is_khtml)
1467 setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
1469 Calendar.continuation_for_the_fucking_khtml_browser();
1472 /** Internal function; it displays the bar with the names of the weekday. */
1473 Calendar.prototype._displayWeekdays = function () {
1474 var MON = this.mondayFirst ? 0 : 1;
1475 var SUN = this.mondayFirst ? 6 : 0;
1476 var SAT = this.mondayFirst ? 5 : 6;
1477 var cell = this.firstdayname;
1478 for (var i = 0; i < 7; ++i) {
1479 cell.className = "day name";
1481 cell.ttip = this.mondayFirst ? Calendar._TT["SUN_FIRST"] : Calendar._TT["MON_FIRST"];
1483 cell.calendar = this;
1484 Calendar._add_evs(cell);
1486 if (i == SUN || i == SAT) {
1487 Calendar.addClass(cell, "weekend");
1489 cell.firstChild.data = Calendar._SDN[i + 1 - MON];
1490 cell = cell.nextSibling;
1494 /** Internal function. Hides all combo boxes that might be displayed. */
1495 Calendar.prototype._hideCombos = function () {
1496 this.monthsCombo.style.display = "none";
1497 this.yearsCombo.style.display = "none";
1500 /** Internal function. Starts dragging the element. */
1501 Calendar.prototype._dragStart = function (ev) {
1502 if (this.dragging) {
1505 this.dragging = true;
1508 if (Calendar.is_ie) {
1509 posY = window.event.clientY + document.body.scrollTop;
1510 posX = window.event.clientX + document.body.scrollLeft;
1512 posY = ev.clientY + window.scrollY;
1513 posX = ev.clientX + window.scrollX;
1515 var st = this.element.style;
1516 this.xOffs = posX - parseInt(st.left);
1517 this.yOffs = posY - parseInt(st.top);
1519 addEvent(document, "mousemove", calDragIt);
1520 addEvent(document, "mouseover", stopEvent);
1521 addEvent(document, "mouseup", calDragEnd);
1525 // BEGIN: DATE OBJECT PATCHES
1527 /** Adds the number of days array to the Date object. */
1528 Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
1530 /** Constants used for time computations */
1531 Date.SECOND = 1000 /* milliseconds */;
1532 Date.MINUTE = 60 * Date.SECOND;
1533 Date.HOUR = 60 * Date.MINUTE;
1534 Date.DAY = 24 * Date.HOUR;
1535 Date.WEEK = 7 * Date.DAY;
1537 /** Returns the number of days in the current month */
1538 Date.prototype.getMonthDays = function(month) {
1539 var year = this.getFullYear();
1540 if (typeof month == "undefined") {
1541 month = this.getMonth();
1543 if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
1546 return Date._MD[month];
1550 /** Returns the number of day in the year. */
1551 Date.prototype.getDayOfYear = function() {
1552 var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
1553 var then = new Date(this.getFullYear(), 0, 1, 0, 0, 0);
1554 var time = now - then;
1555 return Math.floor(time / Date.DAY);
1558 /** Returns the number of the week in year, as defined in ISO 8601. */
1559 Date.prototype.getWeekNumber = function() {
1560 var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
1561 var then = new Date(this.getFullYear(), 0, 1, 0, 0, 0);
1562 var time = now - then;
1563 var day = then.getDay(); // 0 means Sunday
1564 if (day == 0) day = 7;
1565 (day > 4) && (day -= 4) || (day += 3);
1566 return Math.round(((time / Date.DAY) + day) / 7);
1569 /** Checks dates equality (ignores time) */
1570 Date.prototype.equalsTo = function(date) {
1571 return ((this.getFullYear() == date.getFullYear()) &&
1572 (this.getMonth() == date.getMonth()) &&
1573 (this.getDate() == date.getDate()) &&
1574 (this.getHours() == date.getHours()) &&
1575 (this.getMinutes() == date.getMinutes()));
1578 /** Prints the date in a string according to the given format. */
1579 Date.prototype.print = function (str) {
1580 var m = this.getMonth();
1581 var d = this.getDate();
1582 var y = this.getFullYear();
1583 var wn = this.getWeekNumber();
1584 var w = this.getDay();
1586 var hr = this.getHours();
1587 var pm = (hr >= 12);
1588 var ir = (pm) ? (hr - 12) : hr;
1589 var dy = this.getDayOfYear();
1592 var min = this.getMinutes();
1593 var sec = this.getSeconds();
1594 s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N]
1595 s["%A"] = Calendar._DN[w]; // full weekday name
1596 s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N]
1597 s["%B"] = Calendar._MN[m]; // full month name
1598 // FIXME: %c : preferred date and time representation for the current locale
1599 s["%C"] = 1 + Math.floor(y / 100); // the century number
1600 s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
1601 s["%e"] = d; // the day of the month (range 1 to 31)
1602 // FIXME: %D : american date style: %m/%d/%y
1603 // FIXME: %E, %F, %G, %g, %h (man strftime)
1604 s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
1605 s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
1606 s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
1607 s["%k"] = hr; // hour, range 0 to 23 (24h format)
1608 s["%l"] = ir; // hour, range 1 to 12 (12h format)
1609 s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
1610 s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
1611 s["%n"] = "\n"; // a newline character
1612 s["%p"] = pm ? "PM" : "AM";
1613 s["%P"] = pm ? "pm" : "am";
1614 // FIXME: %r : the time in am/pm notation %I:%M:%S %p
1615 // FIXME: %R : the time in 24-hour notation %H:%M
1616 s["%s"] = Math.floor(this.getTime() / 1000);
1617 s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
1618 s["%t"] = "\t"; // a tab character
1619 // FIXME: %T : the time in 24-hour notation (%H:%M:%S)
1620 s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
1621 s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
1622 s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
1623 // FIXME: %x : preferred date representation for the current locale without the time
1624 // FIXME: %X : preferred time representation for the current locale without the date
1625 s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
1626 s["%Y"] = y; // year with the century
1627 s["%%"] = "%"; // a literal '%' character
1628 var re = Date._msh_formatRegexp;
1629 if (typeof re == "undefined") {
1632 tmp += tmp ? ("|" + i) : i;
1633 Date._msh_formatRegexp = re = new RegExp("(" + tmp + ")", 'g');
1635 return str.replace(re, function(match, par) { return s[par]; });
1638 // END: DATE OBJECT PATCHES
1640 // global object that remembers the calendar
1641 window.calendar = null;