This commit was generated by cvs2svn to compensate for changes in r4407,
[freeside.git] / httemplate / elements / calendar.js
1 /*  Copyright Mihai Bazon, 2002, 2003  |  http://dynarch.com/mishoo/
2  * ------------------------------------------------------------------
3  *
4  * The DHTML Calendar, version 0.9.6 "Keep cool but don't freeze"
5  *
6  * Details and latest version at:
7  * http://dynarch.com/mishoo/calendar.epl
8  *
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
11  */
12
13 // $Id: calendar.js,v 1.4 2004-09-22 11:04:41 ivan Exp $
14
15 /** The Calendar object constructor. */
16 Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) {
17         // member variables
18         this.activeDiv = null;
19         this.currentDateEl = null;
20         this.getDateStatus = null;
21         this.timeout = null;
22         this.onSelected = onSelected || null;
23         this.onClose = onClose || null;
24         this.dragging = false;
25         this.hidden = false;
26         this.minYear = 1970;
27         this.maxYear = 2050;
28         this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"];
29         this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"];
30         this.isPopup = true;
31         this.weekNumbers = true;
32         this.firstDayOfWeek = firstDayOfWeek; // 0 for Sunday, 1 for Monday, etc.
33         this.showsOtherMonths = false;
34         this.dateStr = dateStr;
35         this.ar_days = null;
36         this.showsTime = false;
37         this.time24 = true;
38         this.yearStep = 2;
39         // HTML elements
40         this.table = null;
41         this.element = null;
42         this.tbody = null;
43         this.firstdayname = null;
44         // Combo boxes
45         this.monthsCombo = null;
46         this.yearsCombo = null;
47         this.hilitedMonth = null;
48         this.activeMonth = null;
49         this.hilitedYear = null;
50         this.activeYear = null;
51         // Information
52         this.dateClicked = false;
53
54         // one-time initializations
55         if (typeof Calendar._SDN == "undefined") {
56                 // table of short day names
57                 if (typeof Calendar._SDN_len == "undefined")
58                         Calendar._SDN_len = 3;
59                 var ar = new Array();
60                 for (var i = 8; i > 0;) {
61                         ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len);
62                 }
63                 Calendar._SDN = ar;
64                 // table of short month names
65                 if (typeof Calendar._SMN_len == "undefined")
66                         Calendar._SMN_len = 3;
67                 ar = new Array();
68                 for (var i = 12; i > 0;) {
69                         ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len);
70                 }
71                 Calendar._SMN = ar;
72         }
73 };
74
75 // ** constants
76
77 /// "static", needed for event handlers.
78 Calendar._C = null;
79
80 /// detect a special case of "web browser"
81 Calendar.is_ie = ( /msie/i.test(navigator.userAgent) &&
82                    !/opera/i.test(navigator.userAgent) );
83
84 Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) );
85
86 /// detect Opera browser
87 Calendar.is_opera = /opera/i.test(navigator.userAgent);
88
89 /// detect KHTML-based browsers
90 Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);
91
92 // BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate
93 //        library, at some point.
94
95 Calendar.getAbsolutePos = function(el) {
96         var SL = 0, ST = 0;
97         var is_div = /^div$/i.test(el.tagName);
98         if (is_div && el.scrollLeft)
99                 SL = el.scrollLeft;
100         if (is_div && el.scrollTop)
101                 ST = el.scrollTop;
102         var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
103         if (el.offsetParent) {
104                 var tmp = this.getAbsolutePos(el.offsetParent);
105                 r.x += tmp.x;
106                 r.y += tmp.y;
107         }
108         return r;
109 };
110
111 Calendar.isRelated = function (el, evt) {
112         var related = evt.relatedTarget;
113         if (!related) {
114                 var type = evt.type;
115                 if (type == "mouseover") {
116                         related = evt.fromElement;
117                 } else if (type == "mouseout") {
118                         related = evt.toElement;
119                 }
120         }
121         while (related) {
122                 if (related == el) {
123                         return true;
124                 }
125                 related = related.parentNode;
126         }
127         return false;
128 };
129
130 Calendar.removeClass = function(el, className) {
131         if (!(el && el.className)) {
132                 return;
133         }
134         var cls = el.className.split(" ");
135         var ar = new Array();
136         for (var i = cls.length; i > 0;) {
137                 if (cls[--i] != className) {
138                         ar[ar.length] = cls[i];
139                 }
140         }
141         el.className = ar.join(" ");
142 };
143
144 Calendar.addClass = function(el, className) {
145         Calendar.removeClass(el, className);
146         el.className += " " + className;
147 };
148
149 Calendar.getElement = function(ev) {
150         if (Calendar.is_ie) {
151                 return window.event.srcElement;
152         } else {
153                 return ev.currentTarget;
154         }
155 };
156
157 Calendar.getTargetElement = function(ev) {
158         if (Calendar.is_ie) {
159                 return window.event.srcElement;
160         } else {
161                 return ev.target;
162         }
163 };
164
165 Calendar.stopEvent = function(ev) {
166         ev || (ev = window.event);
167         if (Calendar.is_ie) {
168                 ev.cancelBubble = true;
169                 ev.returnValue = false;
170         } else {
171                 ev.preventDefault();
172                 ev.stopPropagation();
173         }
174         return false;
175 };
176
177 Calendar.addEvent = function(el, evname, func) {
178         if (el.attachEvent) { // IE
179                 el.attachEvent("on" + evname, func);
180         } else if (el.addEventListener) { // Gecko / W3C
181                 el.addEventListener(evname, func, true);
182         } else {
183                 el["on" + evname] = func;
184         }
185 };
186
187 Calendar.removeEvent = function(el, evname, func) {
188         if (el.detachEvent) { // IE
189                 el.detachEvent("on" + evname, func);
190         } else if (el.removeEventListener) { // Gecko / W3C
191                 el.removeEventListener(evname, func, true);
192         } else {
193                 el["on" + evname] = null;
194         }
195 };
196
197 Calendar.createElement = function(type, parent) {
198         var el = null;
199         if (document.createElementNS) {
200                 // use the XHTML namespace; IE won't normally get here unless
201                 // _they_ "fix" the DOM2 implementation.
202                 el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
203         } else {
204                 el = document.createElement(type);
205         }
206         if (typeof parent != "undefined") {
207                 parent.appendChild(el);
208         }
209         return el;
210 };
211
212 // END: UTILITY FUNCTIONS
213
214 // BEGIN: CALENDAR STATIC FUNCTIONS
215
216 /** Internal -- adds a set of events to make some element behave like a button. */
217 Calendar._add_evs = function(el) {
218         with (Calendar) {
219                 addEvent(el, "mouseover", dayMouseOver);
220                 addEvent(el, "mousedown", dayMouseDown);
221                 addEvent(el, "mouseout", dayMouseOut);
222                 if (is_ie) {
223                         addEvent(el, "dblclick", dayMouseDblClick);
224                         el.setAttribute("unselectable", true);
225                 }
226         }
227 };
228
229 Calendar.findMonth = function(el) {
230         if (typeof el.month != "undefined") {
231                 return el;
232         } else if (typeof el.parentNode.month != "undefined") {
233                 return el.parentNode;
234         }
235         return null;
236 };
237
238 Calendar.findYear = function(el) {
239         if (typeof el.year != "undefined") {
240                 return el;
241         } else if (typeof el.parentNode.year != "undefined") {
242                 return el.parentNode;
243         }
244         return null;
245 };
246
247 Calendar.showMonthsCombo = function () {
248         var cal = Calendar._C;
249         if (!cal) {
250                 return false;
251         }
252         var cal = cal;
253         var cd = cal.activeDiv;
254         var mc = cal.monthsCombo;
255         if (cal.hilitedMonth) {
256                 Calendar.removeClass(cal.hilitedMonth, "hilite");
257         }
258         if (cal.activeMonth) {
259                 Calendar.removeClass(cal.activeMonth, "active");
260         }
261         var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];
262         Calendar.addClass(mon, "active");
263         cal.activeMonth = mon;
264         var s = mc.style;
265         s.display = "block";
266         if (cd.navtype < 0)
267                 s.left = cd.offsetLeft + "px";
268         else {
269                 var mcw = mc.offsetWidth;
270                 if (typeof mcw == "undefined")
271                         // Konqueror brain-dead techniques
272                         mcw = 50;
273                 s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px";
274         }
275         s.top = (cd.offsetTop + cd.offsetHeight) + "px";
276 };
277
278 Calendar.showYearsCombo = function (fwd) {
279         var cal = Calendar._C;
280         if (!cal) {
281                 return false;
282         }
283         var cal = cal;
284         var cd = cal.activeDiv;
285         var yc = cal.yearsCombo;
286         if (cal.hilitedYear) {
287                 Calendar.removeClass(cal.hilitedYear, "hilite");
288         }
289         if (cal.activeYear) {
290                 Calendar.removeClass(cal.activeYear, "active");
291         }
292         cal.activeYear = null;
293         var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
294         var yr = yc.firstChild;
295         var show = false;
296         for (var i = 12; i > 0; --i) {
297                 if (Y >= cal.minYear && Y <= cal.maxYear) {
298                         yr.firstChild.data = Y;
299                         yr.year = Y;
300                         yr.style.display = "block";
301                         show = true;
302                 } else {
303                         yr.style.display = "none";
304                 }
305                 yr = yr.nextSibling;
306                 Y += fwd ? cal.yearStep : -cal.yearStep;
307         }
308         if (show) {
309                 var s = yc.style;
310                 s.display = "block";
311                 if (cd.navtype < 0)
312                         s.left = cd.offsetLeft + "px";
313                 else {
314                         var ycw = yc.offsetWidth;
315                         if (typeof ycw == "undefined")
316                                 // Konqueror brain-dead techniques
317                                 ycw = 50;
318                         s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px";
319                 }
320                 s.top = (cd.offsetTop + cd.offsetHeight) + "px";
321         }
322 };
323
324 // event handlers
325
326 Calendar.tableMouseUp = function(ev) {
327         var cal = Calendar._C;
328         if (!cal) {
329                 return false;
330         }
331         if (cal.timeout) {
332                 clearTimeout(cal.timeout);
333         }
334         var el = cal.activeDiv;
335         if (!el) {
336                 return false;
337         }
338         var target = Calendar.getTargetElement(ev);
339         ev || (ev = window.event);
340         Calendar.removeClass(el, "active");
341         if (target == el || target.parentNode == el) {
342                 Calendar.cellClick(el, ev);
343         }
344         var mon = Calendar.findMonth(target);
345         var date = null;
346         if (mon) {
347                 date = new Date(cal.date);
348                 if (mon.month != date.getMonth()) {
349                         date.setMonth(mon.month);
350                         cal.setDate(date);
351                         cal.dateClicked = false;
352                         cal.callHandler();
353                 }
354         } else {
355                 var year = Calendar.findYear(target);
356                 if (year) {
357                         date = new Date(cal.date);
358                         if (year.year != date.getFullYear()) {
359                                 date.setFullYear(year.year);
360                                 cal.setDate(date);
361                                 cal.dateClicked = false;
362                                 cal.callHandler();
363                         }
364                 }
365         }
366         with (Calendar) {
367                 removeEvent(document, "mouseup", tableMouseUp);
368                 removeEvent(document, "mouseover", tableMouseOver);
369                 removeEvent(document, "mousemove", tableMouseOver);
370                 cal._hideCombos();
371                 _C = null;
372                 return stopEvent(ev);
373         }
374 };
375
376 Calendar.tableMouseOver = function (ev) {
377         var cal = Calendar._C;
378         if (!cal) {
379                 return;
380         }
381         var el = cal.activeDiv;
382         var target = Calendar.getTargetElement(ev);
383         if (target == el || target.parentNode == el) {
384                 Calendar.addClass(el, "hilite active");
385                 Calendar.addClass(el.parentNode, "rowhilite");
386         } else {
387                 if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2)))
388                         Calendar.removeClass(el, "active");
389                 Calendar.removeClass(el, "hilite");
390                 Calendar.removeClass(el.parentNode, "rowhilite");
391         }
392         ev || (ev = window.event);
393         if (el.navtype == 50 && target != el) {
394                 var pos = Calendar.getAbsolutePos(el);
395                 var w = el.offsetWidth;
396                 var x = ev.clientX;
397                 var dx;
398                 var decrease = true;
399                 if (x > pos.x + w) {
400                         dx = x - pos.x - w;
401                         decrease = false;
402                 } else
403                         dx = pos.x - x;
404
405                 if (dx < 0) dx = 0;
406                 var range = el._range;
407                 var current = el._current;
408                 var count = Math.floor(dx / 10) % range.length;
409                 for (var i = range.length; --i >= 0;)
410                         if (range[i] == current)
411                                 break;
412                 while (count-- > 0)
413                         if (decrease) {
414                                 if (--i < 0)
415                                         i = range.length - 1;
416                         } else if ( ++i >= range.length )
417                                 i = 0;
418                 var newval = range[i];
419                 el.firstChild.data = newval;
420
421                 cal.onUpdateTime();
422         }
423         var mon = Calendar.findMonth(target);
424         if (mon) {
425                 if (mon.month != cal.date.getMonth()) {
426                         if (cal.hilitedMonth) {
427                                 Calendar.removeClass(cal.hilitedMonth, "hilite");
428                         }
429                         Calendar.addClass(mon, "hilite");
430                         cal.hilitedMonth = mon;
431                 } else if (cal.hilitedMonth) {
432                         Calendar.removeClass(cal.hilitedMonth, "hilite");
433                 }
434         } else {
435                 if (cal.hilitedMonth) {
436                         Calendar.removeClass(cal.hilitedMonth, "hilite");
437                 }
438                 var year = Calendar.findYear(target);
439                 if (year) {
440                         if (year.year != cal.date.getFullYear()) {
441                                 if (cal.hilitedYear) {
442                                         Calendar.removeClass(cal.hilitedYear, "hilite");
443                                 }
444                                 Calendar.addClass(year, "hilite");
445                                 cal.hilitedYear = year;
446                         } else if (cal.hilitedYear) {
447                                 Calendar.removeClass(cal.hilitedYear, "hilite");
448                         }
449                 } else if (cal.hilitedYear) {
450                         Calendar.removeClass(cal.hilitedYear, "hilite");
451                 }
452         }
453         return Calendar.stopEvent(ev);
454 };
455
456 Calendar.tableMouseDown = function (ev) {
457         if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) {
458                 return Calendar.stopEvent(ev);
459         }
460 };
461
462 Calendar.calDragIt = function (ev) {
463         var cal = Calendar._C;
464         if (!(cal && cal.dragging)) {
465                 return false;
466         }
467         var posX;
468         var posY;
469         if (Calendar.is_ie) {
470                 posY = window.event.clientY + document.body.scrollTop;
471                 posX = window.event.clientX + document.body.scrollLeft;
472         } else {
473                 posX = ev.pageX;
474                 posY = ev.pageY;
475         }
476         cal.hideShowCovered();
477         var st = cal.element.style;
478         st.left = (posX - cal.xOffs) + "px";
479         st.top = (posY - cal.yOffs) + "px";
480         return Calendar.stopEvent(ev);
481 };
482
483 Calendar.calDragEnd = function (ev) {
484         var cal = Calendar._C;
485         if (!cal) {
486                 return false;
487         }
488         cal.dragging = false;
489         with (Calendar) {
490                 removeEvent(document, "mousemove", calDragIt);
491                 removeEvent(document, "mouseup", calDragEnd);
492                 tableMouseUp(ev);
493         }
494         cal.hideShowCovered();
495 };
496
497 Calendar.dayMouseDown = function(ev) {
498         var el = Calendar.getElement(ev);
499         if (el.disabled) {
500                 return false;
501         }
502         var cal = el.calendar;
503         cal.activeDiv = el;
504         Calendar._C = cal;
505         if (el.navtype != 300) with (Calendar) {
506                 if (el.navtype == 50) {
507                         el._current = el.firstChild.data;
508                         addEvent(document, "mousemove", tableMouseOver);
509                 } else
510                         addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver);
511                 addClass(el, "hilite active");
512                 addEvent(document, "mouseup", tableMouseUp);
513         } else if (cal.isPopup) {
514                 cal._dragStart(ev);
515         }
516         if (el.navtype == -1 || el.navtype == 1) {
517                 if (cal.timeout) clearTimeout(cal.timeout);
518                 cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250);
519         } else if (el.navtype == -2 || el.navtype == 2) {
520                 if (cal.timeout) clearTimeout(cal.timeout);
521                 cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250);
522         } else {
523                 cal.timeout = null;
524         }
525         return Calendar.stopEvent(ev);
526 };
527
528 Calendar.dayMouseDblClick = function(ev) {
529         Calendar.cellClick(Calendar.getElement(ev), ev || window.event);
530         if (Calendar.is_ie) {
531                 document.selection.empty();
532         }
533 };
534
535 Calendar.dayMouseOver = function(ev) {
536         var el = Calendar.getElement(ev);
537         if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) {
538                 return false;
539         }
540         if (el.ttip) {
541                 if (el.ttip.substr(0, 1) == "_") {
542                         el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
543                 }
544                 el.calendar.tooltips.firstChild.data = el.ttip;
545         }
546         if (el.navtype != 300) {
547                 Calendar.addClass(el, "hilite");
548                 if (el.caldate) {
549                         Calendar.addClass(el.parentNode, "rowhilite");
550                 }
551         }
552         return Calendar.stopEvent(ev);
553 };
554
555 Calendar.dayMouseOut = function(ev) {
556         with (Calendar) {
557                 var el = getElement(ev);
558                 if (isRelated(el, ev) || _C || el.disabled) {
559                         return false;
560                 }
561                 removeClass(el, "hilite");
562                 if (el.caldate) {
563                         removeClass(el.parentNode, "rowhilite");
564                 }
565                 el.calendar.tooltips.firstChild.data = _TT["SEL_DATE"];
566                 return stopEvent(ev);
567         }
568 };
569
570 /**
571  *  A generic "click" handler :) handles all types of buttons defined in this
572  *  calendar.
573  */
574 Calendar.cellClick = function(el, ev) {
575         var cal = el.calendar;
576         var closing = false;
577         var newdate = false;
578         var date = null;
579         if (typeof el.navtype == "undefined") {
580                 Calendar.removeClass(cal.currentDateEl, "selected");
581                 Calendar.addClass(el, "selected");
582                 closing = (cal.currentDateEl == el);
583                 if (!closing) {
584                         cal.currentDateEl = el;
585                 }
586                 cal.date = new Date(el.caldate);
587                 date = cal.date;
588                 newdate = true;
589                 // a date was clicked
590                 if (!(cal.dateClicked = !el.otherMonth))
591                         cal._init(cal.firstDayOfWeek, date);
592         } else {
593                 if (el.navtype == 200) {
594                         Calendar.removeClass(el, "hilite");
595                         cal.callCloseHandler();
596                         return;
597                 }
598                 date = (el.navtype == 0) ? new Date() : new Date(cal.date);
599                 // unless "today" was clicked, we assume no date was clicked so
600                 // the selected handler will know not to close the calenar when
601                 // in single-click mode.
602                 // cal.dateClicked = (el.navtype == 0);
603                 cal.dateClicked = false;
604                 var year = date.getFullYear();
605                 var mon = date.getMonth();
606                 function setMonth(m) {
607                         var day = date.getDate();
608                         var max = date.getMonthDays(m);
609                         if (day > max) {
610                                 date.setDate(max);
611                         }
612                         date.setMonth(m);
613                 };
614                 switch (el.navtype) {
615                     case 400:
616                         Calendar.removeClass(el, "hilite");
617                         var text = Calendar._TT["ABOUT"];
618                         if (typeof text != "undefined") {
619                                 text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : "";
620                         } else {
621                                 // FIXME: this should be removed as soon as lang files get updated!
622                                 text = "Help and about box text is not translated into this language.\n" +
623                                         "If you know this language and you feel generous please update\n" +
624                                         "the corresponding file in \"lang\" subdir to match calendar-en.js\n" +
625                                         "and send it back to <mishoo@infoiasi.ro> to get it into the distribution  ;-)\n\n" +
626                                         "Thank you!\n" +
627                                         "http://dynarch.com/mishoo/calendar.epl\n";
628                         }
629                         alert(text);
630                         return;
631                     case -2:
632                         if (year > cal.minYear) {
633                                 date.setFullYear(year - 1);
634                         }
635                         break;
636                     case -1:
637                         if (mon > 0) {
638                                 setMonth(mon - 1);
639                         } else if (year-- > cal.minYear) {
640                                 date.setFullYear(year);
641                                 setMonth(11);
642                         }
643                         break;
644                     case 1:
645                         if (mon < 11) {
646                                 setMonth(mon + 1);
647                         } else if (year < cal.maxYear) {
648                                 date.setFullYear(year + 1);
649                                 setMonth(0);
650                         }
651                         break;
652                     case 2:
653                         if (year < cal.maxYear) {
654                                 date.setFullYear(year + 1);
655                         }
656                         break;
657                     case 100:
658                         cal.setFirstDayOfWeek(el.fdow);
659                         return;
660                     case 50:
661                         var range = el._range;
662                         var current = el.firstChild.data;
663                         for (var i = range.length; --i >= 0;)
664                                 if (range[i] == current)
665                                         break;
666                         if (ev && ev.shiftKey) {
667                                 if (--i < 0)
668                                         i = range.length - 1;
669                         } else if ( ++i >= range.length )
670                                 i = 0;
671                         var newval = range[i];
672                         el.firstChild.data = newval;
673                         cal.onUpdateTime();
674                         return;
675                     case 0:
676                         // TODAY will bring us here
677                         if ((typeof cal.getDateStatus == "function") && cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) {
678                                 // remember, "date" was previously set to new
679                                 // Date() if TODAY was clicked; thus, it
680                                 // contains today date.
681                                 return false;
682                         }
683                         break;
684                 }
685                 if (!date.equalsTo(cal.date)) {
686                         cal.setDate(date);
687                         newdate = true;
688                 }
689         }
690         if (newdate) {
691                 cal.callHandler();
692         }
693         if (closing) {
694                 Calendar.removeClass(el, "hilite");
695                 cal.callCloseHandler();
696         }
697 };
698
699 // END: CALENDAR STATIC FUNCTIONS
700
701 // BEGIN: CALENDAR OBJECT FUNCTIONS
702
703 /**
704  *  This function creates the calendar inside the given parent.  If _par is
705  *  null than it creates a popup calendar inside the BODY element.  If _par is
706  *  an element, be it BODY, then it creates a non-popup calendar (still
707  *  hidden).  Some properties need to be set before calling this function.
708  */
709 Calendar.prototype.create = function (_par) {
710         var parent = null;
711         if (! _par) {
712                 // default parent is the document body, in which case we create
713                 // a popup calendar.
714                 parent = document.getElementsByTagName("body")[0];
715                 this.isPopup = true;
716         } else {
717                 parent = _par;
718                 this.isPopup = false;
719         }
720         this.date = this.dateStr ? new Date(this.dateStr) : new Date();
721
722         var table = Calendar.createElement("table");
723         this.table = table;
724         table.cellSpacing = 0;
725         table.cellPadding = 0;
726         table.calendar = this;
727         Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown);
728
729         var div = Calendar.createElement("div");
730         this.element = div;
731         div.className = "calendar";
732         if (this.isPopup) {
733                 div.style.position = "absolute";
734                 div.style.display = "none";
735         }
736         div.appendChild(table);
737
738         var thead = Calendar.createElement("thead", table);
739         var cell = null;
740         var row = null;
741
742         var cal = this;
743         var hh = function (text, cs, navtype) {
744                 cell = Calendar.createElement("td", row);
745                 cell.colSpan = cs;
746                 cell.className = "button";
747                 if (navtype != 0 && Math.abs(navtype) <= 2)
748                         cell.className += " nav";
749                 Calendar._add_evs(cell);
750                 cell.calendar = cal;
751                 cell.navtype = navtype;
752                 if (text.substr(0, 1) != "&") {
753                         cell.appendChild(document.createTextNode(text));
754                 }
755                 else {
756                         // FIXME: dirty hack for entities
757                         cell.innerHTML = text;
758                 }
759                 return cell;
760         };
761
762         row = Calendar.createElement("tr", thead);
763         var title_length = 6;
764         (this.isPopup) && --title_length;
765         (this.weekNumbers) && ++title_length;
766
767         hh("?", 1, 400).ttip = Calendar._TT["INFO"];
768         this.title = hh("", title_length, 300);
769         this.title.className = "title";
770         if (this.isPopup) {
771                 this.title.ttip = Calendar._TT["DRAG_TO_MOVE"];
772                 this.title.style.cursor = "move";
773                 hh("&#x00d7;", 1, 200).ttip = Calendar._TT["CLOSE"];
774         }
775
776         row = Calendar.createElement("tr", thead);
777         row.className = "headrow";
778
779         this._nav_py = hh("&#x00ab;", 1, -2);
780         this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
781
782         this._nav_pm = hh("&#x2039;", 1, -1);
783         this._nav_pm.ttip = Calendar._TT["PREV_MONTH"];
784
785         this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0);
786         this._nav_now.ttip = Calendar._TT["GO_TODAY"];
787
788         this._nav_nm = hh("&#x203a;", 1, 1);
789         this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
790
791         this._nav_ny = hh("&#x00bb;", 1, 2);
792         this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"];
793
794         // day names
795         row = Calendar.createElement("tr", thead);
796         row.className = "daynames";
797         if (this.weekNumbers) {
798                 cell = Calendar.createElement("td", row);
799                 cell.className = "name wn";
800                 cell.appendChild(document.createTextNode(Calendar._TT["WK"]));
801         }
802         for (var i = 7; i > 0; --i) {
803                 cell = Calendar.createElement("td", row);
804                 cell.appendChild(document.createTextNode(""));
805                 if (!i) {
806                         cell.navtype = 100;
807                         cell.calendar = this;
808                         Calendar._add_evs(cell);
809                 }
810         }
811         this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild;
812         this._displayWeekdays();
813
814         var tbody = Calendar.createElement("tbody", table);
815         this.tbody = tbody;
816
817         for (i = 6; i > 0; --i) {
818                 row = Calendar.createElement("tr", tbody);
819                 if (this.weekNumbers) {
820                         cell = Calendar.createElement("td", row);
821                         cell.appendChild(document.createTextNode(""));
822                 }
823                 for (var j = 7; j > 0; --j) {
824                         cell = Calendar.createElement("td", row);
825                         cell.appendChild(document.createTextNode(""));
826                         cell.calendar = this;
827                         Calendar._add_evs(cell);
828                 }
829         }
830
831         if (this.showsTime) {
832                 row = Calendar.createElement("tr", tbody);
833                 row.className = "time";
834
835                 cell = Calendar.createElement("td", row);
836                 cell.className = "time";
837                 cell.colSpan = 2;
838                 cell.innerHTML = Calendar._TT["TIME"] || "&nbsp;";
839
840                 cell = Calendar.createElement("td", row);
841                 cell.className = "time";
842                 cell.colSpan = this.weekNumbers ? 4 : 3;
843
844                 (function(){
845                         function makeTimePart(className, init, range_start, range_end) {
846                                 var part = Calendar.createElement("span", cell);
847                                 part.className = className;
848                                 part.appendChild(document.createTextNode(init));
849                                 part.calendar = cal;
850                                 part.ttip = Calendar._TT["TIME_PART"];
851                                 part.navtype = 50;
852                                 part._range = [];
853                                 if (typeof range_start != "number")
854                                         part._range = range_start;
855                                 else {
856                                         for (var i = range_start; i <= range_end; ++i) {
857                                                 var txt;
858                                                 if (i < 10 && range_end >= 10) txt = '0' + i;
859                                                 else txt = '' + i;
860                                                 part._range[part._range.length] = txt;
861                                         }
862                                 }
863                                 Calendar._add_evs(part);
864                                 return part;
865                         };
866                         var hrs = cal.date.getHours();
867                         var mins = cal.date.getMinutes();
868                         var t12 = !cal.time24;
869                         var pm = (hrs > 12);
870                         if (t12 && pm) hrs -= 12;
871                         var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
872                         var span = Calendar.createElement("span", cell);
873                         span.appendChild(document.createTextNode(":"));
874                         span.className = "colon";
875                         var M = makeTimePart("minute", mins, 0, 59);
876                         var AP = null;
877                         cell = Calendar.createElement("td", row);
878                         cell.className = "time";
879                         cell.colSpan = 2;
880                         if (t12)
881                                 AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]);
882                         else
883                                 cell.innerHTML = "&nbsp;";
884
885                         cal.onSetTime = function() {
886                                 var hrs = this.date.getHours();
887                                 var mins = this.date.getMinutes();
888                                 var pm = (hrs > 12);
889                                 if (pm && t12) hrs -= 12;
890                                 H.firstChild.data = (hrs < 10) ? ("0" + hrs) : hrs;
891                                 M.firstChild.data = (mins < 10) ? ("0" + mins) : mins;
892                                 if (t12)
893                                         AP.firstChild.data = pm ? "pm" : "am";
894                         };
895
896                         cal.onUpdateTime = function() {
897                                 var date = this.date;
898                                 var h = parseInt(H.firstChild.data, 10);
899                                 if (t12) {
900                                         if (/pm/i.test(AP.firstChild.data) && h < 12)
901                                                 h += 12;
902                                         else if (/am/i.test(AP.firstChild.data) && h == 12)
903                                                 h = 0;
904                                 }
905                                 var d = date.getDate();
906                                 var m = date.getMonth();
907                                 var y = date.getFullYear();
908                                 date.setHours(h);
909                                 date.setMinutes(parseInt(M.firstChild.data, 10));
910                                 date.setFullYear(y);
911                                 date.setMonth(m);
912                                 date.setDate(d);
913                                 this.dateClicked = false;
914                                 this.callHandler();
915                         };
916                 })();
917         } else {
918                 this.onSetTime = this.onUpdateTime = function() {};
919         }
920
921         var tfoot = Calendar.createElement("tfoot", table);
922
923         row = Calendar.createElement("tr", tfoot);
924         row.className = "footrow";
925
926         cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300);
927         cell.className = "ttip";
928         if (this.isPopup) {
929                 cell.ttip = Calendar._TT["DRAG_TO_MOVE"];
930                 cell.style.cursor = "move";
931         }
932         this.tooltips = cell;
933
934         div = Calendar.createElement("div", this.element);
935         this.monthsCombo = div;
936         div.className = "combo";
937         for (i = 0; i < Calendar._MN.length; ++i) {
938                 var mn = Calendar.createElement("div");
939                 mn.className = Calendar.is_ie ? "label-IEfix" : "label";
940                 mn.month = i;
941                 mn.appendChild(document.createTextNode(Calendar._SMN[i]));
942                 div.appendChild(mn);
943         }
944
945         div = Calendar.createElement("div", this.element);
946         this.yearsCombo = div;
947         div.className = "combo";
948         for (i = 12; i > 0; --i) {
949                 var yr = Calendar.createElement("div");
950                 yr.className = Calendar.is_ie ? "label-IEfix" : "label";
951                 yr.appendChild(document.createTextNode(""));
952                 div.appendChild(yr);
953         }
954
955         this._init(this.firstDayOfWeek, this.date);
956         parent.appendChild(this.element);
957 };
958
959 /** keyboard navigation, only for popup calendars */
960 Calendar._keyEvent = function(ev) {
961         if (!window.calendar) {
962                 return false;
963         }
964         (Calendar.is_ie) && (ev = window.event);
965         var cal = window.calendar;
966         var act = (Calendar.is_ie || ev.type == "keypress");
967         if (ev.ctrlKey) {
968                 switch (ev.keyCode) {
969                     case 37: // KEY left
970                         act && Calendar.cellClick(cal._nav_pm);
971                         break;
972                     case 38: // KEY up
973                         act && Calendar.cellClick(cal._nav_py);
974                         break;
975                     case 39: // KEY right
976                         act && Calendar.cellClick(cal._nav_nm);
977                         break;
978                     case 40: // KEY down
979                         act && Calendar.cellClick(cal._nav_ny);
980                         break;
981                     default:
982                         return false;
983                 }
984         } else switch (ev.keyCode) {
985             case 32: // KEY space (now)
986                 Calendar.cellClick(cal._nav_now);
987                 break;
988             case 27: // KEY esc
989                 act && cal.callCloseHandler();
990                 break;
991             case 37: // KEY left
992             case 38: // KEY up
993             case 39: // KEY right
994             case 40: // KEY down
995                 if (act) {
996                         var date = cal.date.getDate() - 1;
997                         var el = cal.currentDateEl;
998                         var ne = null;
999                         var prev = (ev.keyCode == 37) || (ev.keyCode == 38);
1000                         switch (ev.keyCode) {
1001                             case 37: // KEY left
1002                                 (--date >= 0) && (ne = cal.ar_days[date]);
1003                                 break;
1004                             case 38: // KEY up
1005                                 date -= 7;
1006                                 (date >= 0) && (ne = cal.ar_days[date]);
1007                                 break;
1008                             case 39: // KEY right
1009                                 (++date < cal.ar_days.length) && (ne = cal.ar_days[date]);
1010                                 break;
1011                             case 40: // KEY down
1012                                 date += 7;
1013                                 (date < cal.ar_days.length) && (ne = cal.ar_days[date]);
1014                                 break;
1015                         }
1016                         if (!ne) {
1017                                 if (prev) {
1018                                         Calendar.cellClick(cal._nav_pm);
1019                                 } else {
1020                                         Calendar.cellClick(cal._nav_nm);
1021                                 }
1022                                 date = (prev) ? cal.date.getMonthDays() : 1;
1023                                 el = cal.currentDateEl;
1024                                 ne = cal.ar_days[date - 1];
1025                         }
1026                         Calendar.removeClass(el, "selected");
1027                         Calendar.addClass(ne, "selected");
1028                         cal.date = new Date(ne.caldate);
1029                         cal.callHandler();
1030                         cal.currentDateEl = ne;
1031                 }
1032                 break;
1033             case 13: // KEY enter
1034                 if (act) {
1035                         cal.callHandler();
1036                         cal.hide();
1037                 }
1038                 break;
1039             default:
1040                 return false;
1041         }
1042         return Calendar.stopEvent(ev);
1043 };
1044
1045 /**
1046  *  (RE)Initializes the calendar to the given date and firstDayOfWeek
1047  */
1048 Calendar.prototype._init = function (firstDayOfWeek, date) {
1049         var today = new Date();
1050         this.table.style.visibility = "hidden";
1051         var year = date.getFullYear();
1052         if (year < this.minYear) {
1053                 year = this.minYear;
1054                 date.setFullYear(year);
1055         } else if (year > this.maxYear) {
1056                 year = this.maxYear;
1057                 date.setFullYear(year);
1058         }
1059         this.firstDayOfWeek = firstDayOfWeek;
1060         this.date = new Date(date);
1061         var month = date.getMonth();
1062         var mday = date.getDate();
1063         var no_days = date.getMonthDays();
1064
1065         // calendar voodoo for computing the first day that would actually be
1066         // displayed in the calendar, even if it's from the previous month.
1067         // WARNING: this is magic. ;-)
1068         date.setDate(1);
1069         var day1 = (date.getDay() - this.firstDayOfWeek) % 7;
1070         if (day1 < 0)
1071                 day1 += 7;
1072         date.setDate(-day1);
1073         date.setDate(date.getDate() + 1);
1074
1075         var row = this.tbody.firstChild;
1076         var MN = Calendar._SMN[month];
1077         var ar_days = new Array();
1078         var weekend = Calendar._TT["WEEKEND"];
1079         for (var i = 0; i < 6; ++i, row = row.nextSibling) {
1080                 var cell = row.firstChild;
1081                 if (this.weekNumbers) {
1082                         cell.className = "day wn";
1083                         cell.firstChild.data = date.getWeekNumber();
1084                         cell = cell.nextSibling;
1085                 }
1086                 row.className = "daysrow";
1087                 var hasdays = false;
1088                 for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(date.getDate() + 1)) {
1089                         var iday = date.getDate();
1090                         var wday = date.getDay();
1091                         cell.className = "day";
1092                         var current_month = (date.getMonth() == month);
1093                         if (!current_month) {
1094                                 if (this.showsOtherMonths) {
1095                                         cell.className += " othermonth";
1096                                         cell.otherMonth = true;
1097                                 } else {
1098                                         cell.className = "emptycell";
1099                                         cell.innerHTML = "&nbsp;";
1100                                         cell.disabled = true;
1101                                         continue;
1102                                 }
1103                         } else {
1104                                 cell.otherMonth = false;
1105                                 hasdays = true;
1106                         }
1107                         cell.disabled = false;
1108                         cell.firstChild.data = iday;
1109                         if (typeof this.getDateStatus == "function") {
1110                                 var status = this.getDateStatus(date, year, month, iday);
1111                                 if (status === true) {
1112                                         cell.className += " disabled";
1113                                         cell.disabled = true;
1114                                 } else {
1115                                         if (/disabled/i.test(status))
1116                                                 cell.disabled = true;
1117                                         cell.className += " " + status;
1118                                 }
1119                         }
1120                         if (!cell.disabled) {
1121                                 ar_days[ar_days.length] = cell;
1122                                 cell.caldate = new Date(date);
1123                                 cell.ttip = "_";
1124                                 if (current_month && iday == mday) {
1125                                         cell.className += " selected";
1126                                         this.currentDateEl = cell;
1127                                 }
1128                                 if (date.getFullYear() == today.getFullYear() &&
1129                                     date.getMonth() == today.getMonth() &&
1130                                     iday == today.getDate()) {
1131                                         cell.className += " today";
1132                                         cell.ttip += Calendar._TT["PART_TODAY"];
1133                                 }
1134                                 if (weekend.indexOf(wday.toString()) != -1) {
1135                                         cell.className += cell.otherMonth ? " oweekend" : " weekend";
1136                                 }
1137                         }
1138                 }
1139                 if (!(hasdays || this.showsOtherMonths))
1140                         row.className = "emptyrow";
1141         }
1142         this.ar_days = ar_days;
1143         this.title.firstChild.data = Calendar._MN[month] + ", " + year;
1144         this.onSetTime();
1145         this.table.style.visibility = "visible";
1146         // PROFILE
1147         // this.tooltips.firstChild.data = "Generated in " + ((new Date()) - today) + " ms";
1148 };
1149
1150 /**
1151  *  Calls _init function above for going to a certain date (but only if the
1152  *  date is different than the currently selected one).
1153  */
1154 Calendar.prototype.setDate = function (date) {
1155         if (!date.equalsTo(this.date)) {
1156                 this._init(this.firstDayOfWeek, date);
1157         }
1158 };
1159
1160 /**
1161  *  Refreshes the calendar.  Useful if the "disabledHandler" function is
1162  *  dynamic, meaning that the list of disabled date can change at runtime.
1163  *  Just * call this function if you think that the list of disabled dates
1164  *  should * change.
1165  */
1166 Calendar.prototype.refresh = function () {
1167         this._init(this.firstDayOfWeek, this.date);
1168 };
1169
1170 /** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */
1171 Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) {
1172         this._init(firstDayOfWeek, this.date);
1173         this._displayWeekdays();
1174 };
1175
1176 /**
1177  *  Allows customization of what dates are enabled.  The "unaryFunction"
1178  *  parameter must be a function object that receives the date (as a JS Date
1179  *  object) and returns a boolean value.  If the returned value is true then
1180  *  the passed date will be marked as disabled.
1181  */
1182 Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) {
1183         this.getDateStatus = unaryFunction;
1184 };
1185
1186 /** Customization of allowed year range for the calendar. */
1187 Calendar.prototype.setRange = function (a, z) {
1188         this.minYear = a;
1189         this.maxYear = z;
1190 };
1191
1192 /** Calls the first user handler (selectedHandler). */
1193 Calendar.prototype.callHandler = function () {
1194         if (this.onSelected) {
1195                 this.onSelected(this, this.date.print(this.dateFormat));
1196         }
1197 };
1198
1199 /** Calls the second user handler (closeHandler). */
1200 Calendar.prototype.callCloseHandler = function () {
1201         if (this.onClose) {
1202                 this.onClose(this);
1203         }
1204         this.hideShowCovered();
1205 };
1206
1207 /** Removes the calendar object from the DOM tree and destroys it. */
1208 Calendar.prototype.destroy = function () {
1209         var el = this.element.parentNode;
1210         el.removeChild(this.element);
1211         Calendar._C = null;
1212         window.calendar = null;
1213 };
1214
1215 /**
1216  *  Moves the calendar element to a different section in the DOM tree (changes
1217  *  its parent).
1218  */
1219 Calendar.prototype.reparent = function (new_parent) {
1220         var el = this.element;
1221         el.parentNode.removeChild(el);
1222         new_parent.appendChild(el);
1223 };
1224
1225 // This gets called when the user presses a mouse button anywhere in the
1226 // document, if the calendar is shown.  If the click was outside the open
1227 // calendar this function closes it.
1228 Calendar._checkCalendar = function(ev) {
1229         if (!window.calendar) {
1230                 return false;
1231         }
1232         var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
1233         for (; el != null && el != calendar.element; el = el.parentNode);
1234         if (el == null) {
1235                 // calls closeHandler which should hide the calendar.
1236                 window.calendar.callCloseHandler();
1237                 return Calendar.stopEvent(ev);
1238         }
1239 };
1240
1241 /** Shows the calendar. */
1242 Calendar.prototype.show = function () {
1243         var rows = this.table.getElementsByTagName("tr");
1244         for (var i = rows.length; i > 0;) {
1245                 var row = rows[--i];
1246                 Calendar.removeClass(row, "rowhilite");
1247                 var cells = row.getElementsByTagName("td");
1248                 for (var j = cells.length; j > 0;) {
1249                         var cell = cells[--j];
1250                         Calendar.removeClass(cell, "hilite");
1251                         Calendar.removeClass(cell, "active");
1252                 }
1253         }
1254         this.element.style.display = "block";
1255         this.hidden = false;
1256         if (this.isPopup) {
1257                 window.calendar = this;
1258                 Calendar.addEvent(document, "keydown", Calendar._keyEvent);
1259                 Calendar.addEvent(document, "keypress", Calendar._keyEvent);
1260                 Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
1261         }
1262         this.hideShowCovered();
1263 };
1264
1265 /**
1266  *  Hides the calendar.  Also removes any "hilite" from the class of any TD
1267  *  element.
1268  */
1269 Calendar.prototype.hide = function () {
1270         if (this.isPopup) {
1271                 Calendar.removeEvent(document, "keydown", Calendar._keyEvent);
1272                 Calendar.removeEvent(document, "keypress", Calendar._keyEvent);
1273                 Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
1274         }
1275         this.element.style.display = "none";
1276         this.hidden = true;
1277         this.hideShowCovered();
1278 };
1279
1280 /**
1281  *  Shows the calendar at a given absolute position (beware that, depending on
1282  *  the calendar element style -- position property -- this might be relative
1283  *  to the parent's containing rectangle).
1284  */
1285 Calendar.prototype.showAt = function (x, y) {
1286         var s = this.element.style;
1287         s.left = x + "px";
1288         s.top = y + "px";
1289         this.show();
1290 };
1291
1292 /** Shows the calendar near a given element. */
1293 Calendar.prototype.showAtElement = function (el, opts) {
1294         var self = this;
1295         var p = Calendar.getAbsolutePos(el);
1296         if (!opts || typeof opts != "string") {
1297                 this.showAt(p.x, p.y + el.offsetHeight);
1298                 return true;
1299         }
1300         function fixPosition(box) {
1301                 if (box.x < 0)
1302                         box.x = 0;
1303                 if (box.y < 0)
1304                         box.y = 0;
1305                 var cp = document.createElement("div");
1306                 var s = cp.style;
1307                 s.position = "absolute";
1308                 s.right = s.bottom = s.width = s.height = "0px";
1309                 document.body.appendChild(cp);
1310                 var br = Calendar.getAbsolutePos(cp);
1311                 document.body.removeChild(cp);
1312                 if (Calendar.is_ie) {
1313                         br.y += document.body.scrollTop;
1314                         br.x += document.body.scrollLeft;
1315                 } else {
1316                         br.y += window.scrollY;
1317                         br.x += window.scrollX;
1318                 }
1319                 var tmp = box.x + box.width - br.x;
1320                 if (tmp > 0) box.x -= tmp;
1321                 tmp = box.y + box.height - br.y;
1322                 if (tmp > 0) box.y -= tmp;
1323         };
1324         this.element.style.display = "block";
1325         Calendar.continuation_for_the_fucking_khtml_browser = function() {
1326                 var w = self.element.offsetWidth;
1327                 var h = self.element.offsetHeight;
1328                 self.element.style.display = "none";
1329                 var valign = opts.substr(0, 1);
1330                 var halign = "l";
1331                 if (opts.length > 1) {
1332                         halign = opts.substr(1, 1);
1333                 }
1334                 // vertical alignment
1335                 switch (valign) {
1336                     case "T": p.y -= h; break;
1337                     case "B": p.y += el.offsetHeight; break;
1338                     case "C": p.y += (el.offsetHeight - h) / 2; break;
1339                     case "t": p.y += el.offsetHeight - h; break;
1340                     case "b": break; // already there
1341                 }
1342                 // horizontal alignment
1343                 switch (halign) {
1344                     case "L": p.x -= w; break;
1345                     case "R": p.x += el.offsetWidth; break;
1346                     case "C": p.x += (el.offsetWidth - w) / 2; break;
1347                     case "r": p.x += el.offsetWidth - w; break;
1348                     case "l": break; // already there
1349                 }
1350                 p.width = w;
1351                 p.height = h + 40;
1352                 self.monthsCombo.style.display = "none";
1353                 fixPosition(p);
1354                 self.showAt(p.x, p.y);
1355         };
1356         if (Calendar.is_khtml)
1357                 setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
1358         else
1359                 Calendar.continuation_for_the_fucking_khtml_browser();
1360 };
1361
1362 /** Customizes the date format. */
1363 Calendar.prototype.setDateFormat = function (str) {
1364         this.dateFormat = str;
1365 };
1366
1367 /** Customizes the tooltip date format. */
1368 Calendar.prototype.setTtDateFormat = function (str) {
1369         this.ttDateFormat = str;
1370 };
1371
1372 /**
1373  *  Tries to identify the date represented in a string.  If successful it also
1374  *  calls this.setDate which moves the calendar to the given date.
1375  */
1376 Calendar.prototype.parseDate = function (str, fmt) {
1377         var y = 0;
1378         var m = -1;
1379         var d = 0;
1380         var a = str.split(/\W+/);
1381         if (!fmt) {
1382                 fmt = this.dateFormat;
1383         }
1384         var b = fmt.match(/%./g);
1385         var i = 0, j = 0;
1386         var hr = 0;
1387         var min = 0;
1388         for (i = 0; i < a.length; ++i) {
1389                 if (!a[i])
1390                         continue;
1391                 switch (b[i]) {
1392                     case "%d":
1393                     case "%e":
1394                         d = parseInt(a[i], 10);
1395                         break;
1396
1397                     case "%m":
1398                         m = parseInt(a[i], 10) - 1;
1399                         break;
1400
1401                     case "%Y":
1402                     case "%y":
1403                         y = parseInt(a[i], 10);
1404                         (y < 100) && (y += (y > 29) ? 1900 : 2000);
1405                         break;
1406
1407                     case "%b":
1408                     case "%B":
1409                         for (j = 0; j < 12; ++j) {
1410                                 if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; }
1411                         }
1412                         break;
1413
1414                     case "%H":
1415                     case "%I":
1416                     case "%k":
1417                     case "%l":
1418                         hr = parseInt(a[i], 10);
1419                         break;
1420
1421                     case "%P":
1422                     case "%p":
1423                         if (/pm/i.test(a[i]) && hr < 12)
1424                                 hr += 12;
1425                         break;
1426
1427                     case "%M":
1428                         min = parseInt(a[i], 10);
1429                         break;
1430                 }
1431         }
1432         if (y != 0 && m != -1 && d != 0) {
1433                 this.setDate(new Date(y, m, d, hr, min, 0));
1434                 return;
1435         }
1436         y = 0; m = -1; d = 0;
1437         for (i = 0; i < a.length; ++i) {
1438                 if (a[i].search(/[a-zA-Z]+/) != -1) {
1439                         var t = -1;
1440                         for (j = 0; j < 12; ++j) {
1441                                 if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
1442                         }
1443                         if (t != -1) {
1444                                 if (m != -1) {
1445                                         d = m+1;
1446                                 }
1447                                 m = t;
1448                         }
1449                 } else if (parseInt(a[i], 10) <= 12 && m == -1) {
1450                         m = a[i]-1;
1451                 } else if (parseInt(a[i], 10) > 31 && y == 0) {
1452                         y = parseInt(a[i], 10);
1453                         (y < 100) && (y += (y > 29) ? 1900 : 2000);
1454                 } else if (d == 0) {
1455                         d = a[i];
1456                 }
1457         }
1458         if (y == 0) {
1459                 var today = new Date();
1460                 y = today.getFullYear();
1461         }
1462         if (m != -1 && d != 0) {
1463                 this.setDate(new Date(y, m, d, hr, min, 0));
1464         }
1465 };
1466
1467 Calendar.prototype.hideShowCovered = function () {
1468         var self = this;
1469         Calendar.continuation_for_the_fucking_khtml_browser = function() {
1470                 function getVisib(obj){
1471                         var value = obj.style.visibility;
1472                         if (!value) {
1473                                 if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
1474                                         if (!Calendar.is_khtml)
1475                                                 value = document.defaultView.
1476                                                         getComputedStyle(obj, "").getPropertyValue("visibility");
1477                                         else
1478                                                 value = '';
1479                                 } else if (obj.currentStyle) { // IE
1480                                         value = obj.currentStyle.visibility;
1481                                 } else
1482                                         value = '';
1483                         }
1484                         return value;
1485                 };
1486
1487                 var tags = new Array("applet", "iframe", "select");
1488                 var el = self.element;
1489
1490                 var p = Calendar.getAbsolutePos(el);
1491                 var EX1 = p.x;
1492                 var EX2 = el.offsetWidth + EX1;
1493                 var EY1 = p.y;
1494                 var EY2 = el.offsetHeight + EY1;
1495
1496                 for (var k = tags.length; k > 0; ) {
1497                         var ar = document.getElementsByTagName(tags[--k]);
1498                         var cc = null;
1499
1500                         for (var i = ar.length; i > 0;) {
1501                                 cc = ar[--i];
1502
1503                                 p = Calendar.getAbsolutePos(cc);
1504                                 var CX1 = p.x;
1505                                 var CX2 = cc.offsetWidth + CX1;
1506                                 var CY1 = p.y;
1507                                 var CY2 = cc.offsetHeight + CY1;
1508
1509                                 if (self.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
1510                                         if (!cc.__msh_save_visibility) {
1511                                                 cc.__msh_save_visibility = getVisib(cc);
1512                                         }
1513                                         cc.style.visibility = cc.__msh_save_visibility;
1514                                 } else {
1515                                         if (!cc.__msh_save_visibility) {
1516                                                 cc.__msh_save_visibility = getVisib(cc);
1517                                         }
1518                                         cc.style.visibility = "hidden";
1519                                 }
1520                         }
1521                 }
1522         };
1523         if (Calendar.is_khtml)
1524                 setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
1525         else
1526                 Calendar.continuation_for_the_fucking_khtml_browser();
1527 };
1528
1529 /** Internal function; it displays the bar with the names of the weekday. */
1530 Calendar.prototype._displayWeekdays = function () {
1531         var fdow = this.firstDayOfWeek;
1532         var cell = this.firstdayname;
1533         var weekend = Calendar._TT["WEEKEND"];
1534         for (var i = 0; i < 7; ++i) {
1535                 cell.className = "day name";
1536                 var realday = (i + fdow) % 7;
1537                 if (i) {
1538                         cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]);
1539                         cell.navtype = 100;
1540                         cell.calendar = this;
1541                         cell.fdow = realday;
1542                         Calendar._add_evs(cell);
1543                 }
1544                 if (weekend.indexOf(realday.toString()) != -1) {
1545                         Calendar.addClass(cell, "weekend");
1546                 }
1547                 cell.firstChild.data = Calendar._SDN[(i + fdow) % 7];
1548                 cell = cell.nextSibling;
1549         }
1550 };
1551
1552 /** Internal function.  Hides all combo boxes that might be displayed. */
1553 Calendar.prototype._hideCombos = function () {
1554         this.monthsCombo.style.display = "none";
1555         this.yearsCombo.style.display = "none";
1556 };
1557
1558 /** Internal function.  Starts dragging the element. */
1559 Calendar.prototype._dragStart = function (ev) {
1560         if (this.dragging) {
1561                 return;
1562         }
1563         this.dragging = true;
1564         var posX;
1565         var posY;
1566         if (Calendar.is_ie) {
1567                 posY = window.event.clientY + document.body.scrollTop;
1568                 posX = window.event.clientX + document.body.scrollLeft;
1569         } else {
1570                 posY = ev.clientY + window.scrollY;
1571                 posX = ev.clientX + window.scrollX;
1572         }
1573         var st = this.element.style;
1574         this.xOffs = posX - parseInt(st.left);
1575         this.yOffs = posY - parseInt(st.top);
1576         with (Calendar) {
1577                 addEvent(document, "mousemove", calDragIt);
1578                 addEvent(document, "mouseup", calDragEnd);
1579         }
1580 };
1581
1582 // BEGIN: DATE OBJECT PATCHES
1583
1584 /** Adds the number of days array to the Date object. */
1585 Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
1586
1587 /** Constants used for time computations */
1588 Date.SECOND = 1000 /* milliseconds */;
1589 Date.MINUTE = 60 * Date.SECOND;
1590 Date.HOUR   = 60 * Date.MINUTE;
1591 Date.DAY    = 24 * Date.HOUR;
1592 Date.WEEK   =  7 * Date.DAY;
1593
1594 /** Returns the number of days in the current month */
1595 Date.prototype.getMonthDays = function(month) {
1596         var year = this.getFullYear();
1597         if (typeof month == "undefined") {
1598                 month = this.getMonth();
1599         }
1600         if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
1601                 return 29;
1602         } else {
1603                 return Date._MD[month];
1604         }
1605 };
1606
1607 /** Returns the number of day in the year. */
1608 Date.prototype.getDayOfYear = function() {
1609         var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
1610         var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
1611         var time = now - then;
1612         return Math.floor(time / Date.DAY);
1613 };
1614
1615 /** Returns the number of the week in year, as defined in ISO 8601. */
1616 Date.prototype.getWeekNumber = function() {
1617         var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
1618         var DoW = d.getDay();
1619         d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
1620         var ms = d.valueOf(); // GMT
1621         d.setMonth(0);
1622         d.setDate(4); // Thu in Week 1
1623         return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
1624 };
1625
1626 /** Checks dates equality (ignores time) */
1627 Date.prototype.equalsTo = function(date) {
1628         return ((this.getFullYear() == date.getFullYear()) &&
1629                 (this.getMonth() == date.getMonth()) &&
1630                 (this.getDate() == date.getDate()) &&
1631                 (this.getHours() == date.getHours()) &&
1632                 (this.getMinutes() == date.getMinutes()));
1633 };
1634
1635 /** Prints the date in a string according to the given format. */
1636 Date.prototype.print = function (str) {
1637         var m = this.getMonth();
1638         var d = this.getDate();
1639         var y = this.getFullYear();
1640         var wn = this.getWeekNumber();
1641         var w = this.getDay();
1642         var s = {};
1643         var hr = this.getHours();
1644         var pm = (hr >= 12);
1645         var ir = (pm) ? (hr - 12) : hr;
1646         var dy = this.getDayOfYear();
1647         if (ir == 0)
1648                 ir = 12;
1649         var min = this.getMinutes();
1650         var sec = this.getSeconds();
1651         s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N]
1652         s["%A"] = Calendar._DN[w]; // full weekday name
1653         s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N]
1654         s["%B"] = Calendar._MN[m]; // full month name
1655         // FIXME: %c : preferred date and time representation for the current locale
1656         s["%C"] = 1 + Math.floor(y / 100); // the century number
1657         s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
1658         s["%e"] = d; // the day of the month (range 1 to 31)
1659         // FIXME: %D : american date style: %m/%d/%y
1660         // FIXME: %E, %F, %G, %g, %h (man strftime)
1661         s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
1662         s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
1663         s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
1664         s["%k"] = hr;           // hour, range 0 to 23 (24h format)
1665         s["%l"] = ir;           // hour, range 1 to 12 (12h format)
1666         s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
1667         s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
1668         s["%n"] = "\n";         // a newline character
1669         s["%p"] = pm ? "PM" : "AM";
1670         s["%P"] = pm ? "pm" : "am";
1671         // FIXME: %r : the time in am/pm notation %I:%M:%S %p
1672         // FIXME: %R : the time in 24-hour notation %H:%M
1673         s["%s"] = Math.floor(this.getTime() / 1000);
1674         s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
1675         s["%t"] = "\t";         // a tab character
1676         // FIXME: %T : the time in 24-hour notation (%H:%M:%S)
1677         s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
1678         s["%u"] = w + 1;        // the day of the week (range 1 to 7, 1 = MON)
1679         s["%w"] = w;            // the day of the week (range 0 to 6, 0 = SUN)
1680         // FIXME: %x : preferred date representation for the current locale without the time
1681         // FIXME: %X : preferred time representation for the current locale without the date
1682         s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
1683         s["%Y"] = y;            // year with the century
1684         s["%%"] = "%";          // a literal '%' character
1685
1686         var re = /%./g;
1687         if (!Calendar.is_ie5)
1688                 return str.replace(re, function (par) { return s[par] || par; });
1689
1690         var a = str.match(re);
1691         for (var i = 0; i < a.length; i++) {
1692                 var tmp = s[a[i]];
1693                 if (tmp) {
1694                         re = new RegExp(a[i], 'g');
1695                         str = str.replace(re, tmp);
1696                 }
1697         }
1698
1699         return str;
1700 };
1701
1702 Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear;
1703 Date.prototype.setFullYear = function(y) {
1704         var d = new Date(this);
1705         d.__msh_oldSetFullYear(y);
1706         if (d.getMonth() != this.getMonth())
1707                 this.setDate(28);
1708         this.__msh_oldSetFullYear(y);
1709 };
1710
1711 // END: DATE OBJECT PATCHES
1712
1713
1714 // global object that remembers the calendar
1715 window.calendar = null;