/*============================================================================== Rich Calendar 1.0 ================= Copyright (c) 2007-2008 Vyacheslav Smolin Author: ------- Vyacheslav Smolin (http://www.richarea.com, http://html2xhtml.richarea.com, re@richarea.com) About the script: ----------------- Rich Calendar is 100% JavaScript calendar script. No pop-up windows. Skinnable and multilingual. Multiple calendar instances on one page. Allows to embed calendar objects in html document or position them absolutely using flexible horizontal and vertical alignment options. Supports any date formats. Language dependant date formats. User-defined behaviour possible. Could be associated with an element (eg text field) to read/write date from/to. Pop-up mode (calendar closes on mouse click outside it). Requirements: ------------- Rich Calendar works in IE, Mozilla-based browsers such as Firefox, Opera 9+, and Safari 3.0. Usage: ------ Please see example.html. Demo: ----- http://www.richarea.com/demo/rich_calendar/ License: -------- Free. Copyright information must stay intact. We'd appreciate if you place a direct link to us somewhere on your site. ==============================================================================*/ // Rich Calendar RichCalendar = function(target_obj, show_time) { // value this.value = ''; // format this.format = '%Y-%m-%d'; // Week Day to start with (0 - Sunday, 1 - Monday, etc...) this.start_week_day = 1; // iframe object to show calendar object in this.iframe_obj = null; // path to calendar css and js files this.lib_path = 'rich_calendar/'; // DOM object to take/set date from/to this.target_obj = target_obj; // show time this.show_time = show_time; // function called when calendar value changes this.user_onchange_handler = null; // function called when data choice is cancelled this.user_onclose_handler = null; // function called when mouse clicked outside calendar with auto_close set // to true after it is closed this.user_onautoclose_handler = null; // default language this.default_lang = 'en'; // language this.language = 'en'; // current date this.date = new Date(); /* this.date.setFullYear(2008); this.date.setMonth(1); this.date.setDate(29); */ //this.date.setMonth(11); //this.date.setDate(31); // calendar skin name this.skin = ''; // calendar closes automatically on click outside it this.auto_close = true; // element which value is taken to initilize calendar and where calendar // returns date if user defined function to return date is not specified this.value_el = null; // specifies calendar positioning - absolute by default this.position = null; } RichCalendar.is_ie = /msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent); // Static functions RichCalendar.get_iframe_styles = function() { var i; var j; var styles = document.styleSheets; var sheets_num = styles.length; var style_text = ''; for (i=0; i ' + e.srcElement + ' => ' + e.target + ' => ' + window.event); //for (var i in e) alert(i + ' => ' + e[i]); var event = RichCalendar.get_event(e); var obj = RichCalendar.get_target_object(e); if (!obj) return; var cal = obj.calendar; var cur_year = cal.date.getFullYear(); var cur_month = cal.date.getMonth(); var cur_day = cal.date.getDate(); //alert(obj.rc_object_code); switch (obj.rc_object_code) { case 'day': // alert(obj.day_num); cal.date.setDate(obj.day_num); break; case 'prev_year': // determine number of days in prev year cal.date.setDate(1); cal.date.setFullYear(cur_year-1); var month_days = RichCalendar.get_month_days(cal.date); // prevent jumping to next month if (cur_day > month_days) { cal.date.setDate(month_days); } else { cal.date.setDate(cur_day); } cal.show_date(); break; case 'prev_month': // determine number of days in prev month cal.date.setDate(1); cal.date.setMonth(cur_month-1); var month_days = RichCalendar.get_month_days(cal.date); // prevent jumping to next month if (cur_day > month_days) { cal.date.setDate(month_days); } else { cal.date.setDate(cur_day); } cal.show_date(); break; case 'next_month': // determine number of days in prev month cal.date.setDate(1); cal.date.setMonth(cur_month+1); var month_days = RichCalendar.get_month_days(cal.date); // prevent jumping to next month if (cur_day > month_days) { cal.date.setDate(month_days); } else { cal.date.setDate(cur_day); } cal.show_date(); break; case 'next_year': // determine number of days in next year cal.date.setDate(1); cal.date.setFullYear(cur_year+1); var month_days = RichCalendar.get_month_days(cal.date); // prevent jumping to next month if (cur_day > month_days) { cal.date.setDate(month_days); } else { cal.date.setDate(cur_day); } cal.show_date(); break; case 'today': var today = new Date(); today.setHours(cal.date.getHours()); today.setMinutes(cal.date.getMinutes()); today.setSeconds(cal.date.getSeconds()); cal.date = today; cal.show_date(); break; case 'clear': // handle clear request if (cal.value_el) { cal.value_el.value = ''; } break; case 'close': // handle close request cal.onclose_handler(); break; case 'week_day': //alert(obj.innerHTML); cal.start_week_day = obj.week_day_num; cal.show_date(); break; default: break; } // handle close request if (obj.rc_object_code != 'week_day') { cal.onchange_handler(obj.rc_object_code); } // handle date change // hide all other auto closing calendars RichCalendar.hide_auto_close(cal); } // calendar onmouseover event handler RichCalendar.onmouseover = function(e) { //alert(e + ' => ' + e.srcElement + ' => ' + e.target + ' => ' + window.event); //for (var i in e) alert(i + ' => ' + e[i]); var event = RichCalendar.get_event(e); var obj = RichCalendar.get_target_object(e); if (!obj) return; var cal = obj.calendar; var cur_year = cal.date.getFullYear(); var cur_month = cal.date.getMonth(); var cur_day = cal.date.getDate(); switch (obj.rc_object_code) { case 'day': var date = new Date(cal.date); date.setDate(obj.day_num); cal.set_footer_text(cal.get_formatted_date(cal.text('footerDateFormat'), date)); // highlight day cell and its row RichCalendar.add_class(obj, "rc_highlight"); RichCalendar.add_class(obj.parentNode, "rc_highlight"); break; case 'clear': case 'today': case 'close': case 'prev_year': case 'prev_month': case 'next_month': case 'next_year': cal.set_footer_text(cal.text(obj.rc_object_code)); break; case 'week_day': if (obj.week_day_num != cal.start_week_day) { var day_names = cal.text("dayNames"); var name = day_names[obj.week_day_num]; var text = cal.text("make_first"); text = text.replace("%s", name); } else { var text = cal.text('footerDefaultText'); } cal.set_footer_text(text); break; default: cal.set_footer_text(cal.text('footerDefaultText')); break; } } // calendar onmouseout event handler RichCalendar.onmouseout = function(e) { //alert(e + ' => ' + e.srcElement + ' => ' + e.target + ' => ' + window.event); //for (var i in e) alert(i + ' => ' + e[i]); var event = RichCalendar.get_event(e); var obj = RichCalendar.get_target_object(e); if (!obj) return; var cal = obj.calendar; cal.set_footer_text(cal.text('footerDefaultText')); // un-highlight day cell and its row RichCalendar.remove_class(obj, "rc_highlight"); RichCalendar.remove_class(obj.parentNode, "rc_highlight"); } // document onmousedown event handler RichCalendar.document_onmousedown = function(e) { var event = RichCalendar.get_event(e); var obj = RichCalendar.get_target_object(e); if (!obj) return; var el = obj; var cal = null; while (el) { if (el.className && el.className.match(/^rc_iframe_body/) && el.tagName.toUpperCase() == 'BODY') { cal = el.calendar; break; } el = el.parentNode; } // close all not active calendars RichCalendar.hide_auto_close(cal); } // hide all calendars that should autoclose except cal and remove // them from RichCalendar.active_calendars RichCalendar.hide_auto_close = function(cal) { var active_cals = []; var i; for (i=0; i 11) month = null; } if (!month) { month = date.getMonth(); } if (month==1 && RichCalendar.is_leap_year(year)) { return 29; } else { //alert(month + ' -> ' + RichCalendar.month_days[month]); return RichCalendar.month_days[month]; } } // return true if year is a leap year RichCalendar.is_leap_year = function(year) { return (year%4==0 && year%100!=0 || year%400==0) ? true : false; } // return day of the year RichCalendar.get_day_of_year = function(date) { var now = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); var year_start = new Date(date.getFullYear(), 0, 0, 0, 0, 0); // milliseconds in day var day_in_msecs = 24*60*60*1000; return Math.floor((now - year_start) / day_in_msecs); } // add class to element RichCalendar.add_class = function(el, class_name) { RichCalendar.remove_class(el, class_name); el.className += " " + class_name; } // remove class from element RichCalendar.remove_class = function(el, class_name) { if (!el || !el.className) return var new_class_parts = []; var class_parts = String(el.className).split(" "); var i; for (i=0; i' + '' + '' + '' + '' + '' + ''; this.iframe_doc = this.iframe_obj.contentWindow.document; this.iframe_doc.open(); this.iframe_doc.write(iframe_content); this.iframe_doc.close(); RichCalendar.attach_event(this.iframe_doc, 'mousedown', RichCalendar.document_onmousedown); this.body_obj = this.iframe_doc.getElementById('rc_body'); this.body_obj.calendar = this; // main table this.table_obj = this.iframe_doc.createElement('TABLE'); this.table_obj.className = 'rc_table'; this.table_obj.setAttribute('id', 'rc_iframe_table'); this.table_obj.cellSpacing = 0; this.table_obj.cellPadding = 0; // store reference to the calendar this.table_obj.calendar = this; // header row this.head_tr = this.table_obj.insertRow(0); this.head_tr.className = 'rc_head_tr'; this.clear_td = this.head_tr.insertCell(0); this.clear_td.innerHTML = 'c'; this.clear_td.rc_object_code = 'clear'; this.clear_td.calendar = this; RichCalendar.attach_events(this.clear_td); //this.clear_td.className = 'rc_head_tr'; this.head_td = this.head_tr.insertCell(1); //this.head_td.className = 'rc_head_tr'; this.head_td.colSpan = 5; //this.head_td.innerHTML = 'asdf'; this.close_td = this.head_tr.insertCell(2); this.close_td.innerHTML = 'x'; this.close_td.rc_object_code = 'close'; this.close_td.calendar = this; RichCalendar.attach_events(this.close_td); //this.close_td.className = 'rc_head_tr'; // navigation row this.nav_tr = this.table_obj.insertRow(1); this.nav_tr.className = 'rc_nav_tr'; this.prev_year_td = this.nav_tr.insertCell(0); this.prev_year_td.innerHTML = '«'; this.prev_year_td.rc_object_code = 'prev_year'; this.prev_year_td.calendar = this; RichCalendar.attach_events(this.prev_year_td); this.prev_month_td = this.nav_tr.insertCell(1); this.prev_month_td.innerHTML = '‹'; this.prev_month_td.rc_object_code = 'prev_month'; this.prev_month_td.calendar = this; RichCalendar.attach_events(this.prev_month_td); this.today_td = this.nav_tr.insertCell(2); this.today_td.colSpan = 3; this.today_td.innerHTML = this.text('today'); this.today_td.rc_object_code = 'today'; this.today_td.calendar = this; RichCalendar.attach_events(this.today_td); this.next_month_td = this.nav_tr.insertCell(3); this.next_month_td.innerHTML = '›'; this.next_month_td.rc_object_code = 'next_month'; this.next_month_td.calendar = this; RichCalendar.attach_events(this.next_month_td); this.next_year_td = this.nav_tr.insertCell(4); this.next_year_td.innerHTML = '»'; this.next_year_td.rc_object_code = 'next_year'; this.next_year_td.calendar = this; RichCalendar.attach_events(this.next_year_td); // weekdays row this.wd_tr = this.table_obj.insertRow(2); this.wd_tr.className = 'rc_wd_tr'; var i; // var day_names = this.text('dayNamesShort'); for (i=0; i<7; i++) { // var wd = (i+this.start_week_day)%7; var td = this.wd_tr.insertCell(i); td.rc_object_code = 'week_day'; td.calendar = this; RichCalendar.attach_events(td); // td.innerHTML = day_names[wd]; // if (typeof(weekend_days[wd]) != "undefined") { // td.className = "rc_weekend_head"; // } } // calendar rows (initially create min number of rows necessary - 4) var rows_num = 4; var row_indx; var cell_indx; this.cal_tr = []; for (row_indx=0; row_indx " + position); var aligns = String(position).split("-"); if (aligns.length == 2) { var el_pos = RichCalendar.get_obj_pos(el); //alert(el_pos + ' => ' + el.offsetHeight); var x = el_pos[0]; var y = el_pos[1] + el.offsetHeight; // iframe border thikness var border_width = parseInt(this.iframe_obj.style.borderWidth); var cal_width = parseInt(this.iframe_obj.width) + 2*border_width; var cal_height = parseInt(this.iframe_obj.height) + 2*border_width; //alert('!: ' + cal_width + ' => ' + cal_height); // horizontal alignment switch (aligns[0]) { case "left": x -= cal_width; break; case "center": x += (el.offsetWidth - cal_width) / 2; break; case "right": x += el.offsetWidth; break; case "adj_right": x += el.offsetWidth - cal_width; break; default: break; } // vertical alignment switch (aligns[1]) { case "top": y -= el.offsetHeight + cal_height; break; case "center": y += (el.offsetHeight - cal_height) / 2 - el.offsetHeight; break; case "bottom": break; case "adj_bottom": y -= cal_height; break; default: break; } this.iframe_obj.style.left = x + 'px'; this.iframe_obj.style.top = y + 'px'; this.iframe_obj.style.visibility = 'visible'; } } // return true if calendar is relatively positioned RichCalendar.prototype.is_relative_position = function(position) { switch (position) { case "before": case "after": case "child": return true; default: return false; } } // creates an element in iframe RichCalendar.prototype.createElement = function(tagName, parent) { var el = this.iframe_doc.createElement(tagName); if (parent) { parent.appendChild(el); } return el; } // return text data desired RichCalendar.prototype.text = function(name, language) { if (typeof(language) == "undefined") { language = this.language; } if (typeof(RichCalendar.rc_lang_data[language]) != "undefined") { return typeof(RichCalendar.rc_lang_data[language][name]) != "undefined"?RichCalendar.rc_lang_data[language][name]:''; } return typeof(RichCalendar.rc_lang_data[this.default_language][name]) != "undefined"?RichCalendar.rc_lang_data[this.default_language][name]:''; } // show date in calendar RichCalendar.prototype.show_date = function() { // update week days row // numbers of weekend days var weekend_days = this.get_weekend_days(); var i; var day_names = this.text('dayNamesShort'); for (i=0; i<7; i++) { var wd = (i+this.start_week_day)%7; var td = this.wd_tr.cells[i]; td.innerHTML = day_names[wd]; if (typeof(weekend_days[wd]) != "undefined") { td.className = "rc_weekend_head"; } else { td.className = ""; } // td.rc_object_code = 'week_day'; // td.calendar = this; td.week_day_num = wd; // RichCalendar.attach_events(td); } var month_days = RichCalendar.get_month_days(this.date); // alert(month_days); // first day of the same month and year as this.date var date = new Date(this.date); date.setDate(1); var week_day = (date.getDay()+7-this.start_week_day)%7+1; // alert(week_day); // current data var cur_year = this.date.getFullYear(); var cur_month = this.date.getMonth(); var cur_day = this.date.getDate(); //alert(cur_year + ' => ' + cur_month + ' => ' + cur_day); // today var today = new Date(); var today_year = today.getFullYear(); var today_month = today.getMonth(); var today_day = today.getDate(); // var month_names = this.text('monthNames'); this.head_td.innerHTML = month_names[cur_month] + ', ' + cur_year; var row; var day; var days = 0; var last_row; for (row=0; row<6; row++) { // all days are shown => just check if need to remove unused rows if (days == month_days) { if (this.cal_tr[last_row+1]) { this.cal_tr[last_row+1].parentNode.removeChild(this.cal_tr[last_row+1]); this.cal_tr[row] = null; } continue; } for (day=0; day<7; day++) { if (!this.cal_tr[row]) { this.create_cal_row(row); } var cur_tr = this.cal_tr[row]; var cell = cur_tr.cells[day]; cell.className = ""; // should remove or IE attach the same event several times RichCalendar.detach_events(cell); if (row==0 && day+1 < week_day || days == month_days) { var td_text = ' '; // RichCalendar.detach_events(cell); } else { var day_num = days+1; var td_text = day_num; days++; cell.rc_object_code = 'day'; cell.day_num = day_num; cell.calendar = this; RichCalendar.attach_events(cell); // hilight current date if (cur_day == day_num) { RichCalendar.add_class(cell, "rc_current"); } // hilight today date if (day_num == today_day && cur_month == today_month && cur_year == today_year) { RichCalendar.add_class(cell, "rc_today"); } var wd = (day+this.start_week_day)%7; // hilight weekend days if (typeof(weekend_days[wd]) != "undefined") { RichCalendar.add_class(cell, "rc_weekend_day"); } else { RichCalendar.remove_class(cell, "rc_weekend_day"); } } cell.innerHTML = td_text; if (days == month_days) { last_row = row; } } } // set time if (this.show_time && this.hours_obj && this.mins_obj) { var hours = this.date.getHours(); if (hours < 10) hours = '0' + hours; var mins = this.date.getMinutes(); if (mins < 10) mins = '0' + mins; this.hours_obj.value = hours; this.mins_obj.value = mins; } // change size of the iframe to fit to its content /* var table_obj = this.iframe_doc.getElementById('rc_iframe_table'); this.iframe_obj.width = table_obj.offsetWidth; this.iframe_obj.height = table_obj.offsetHeight; */ var cal = this; window.setTimeout(function(){cal.fit_to_content()}, 1); // fix position (need to do this later then calendar is shown as // size of calendar could change in this.show(x, y) window.setTimeout(function(){cal.fix_position()}, 5); } // change size of the iframe to fit to its content RichCalendar.prototype.fit_to_content = function() { try { var table_obj = this.iframe_doc.getElementById('rc_iframe_table'); this.iframe_obj.width = table_obj.offsetWidth; this.iframe_obj.height = table_obj.offsetHeight; //alert(this.iframe_obj.width + ' => ' + this.iframe_obj.height); // sometimes IE return 0 values, so need to use another approach to // determine size of the calendar if (!parseInt(this.iframe_obj.width) || !parseInt(this.iframe_obj.height)) { this.size_div.innerHTML = this.body_obj.innerHTML; //alert(this.size_div.offsetWidth + ' => ' + this.size_div.offsetHeight); this.iframe_obj.width = this.size_div.offsetWidth; this.iframe_obj.height = this.size_div.offsetHeight; } }catch(e){} } // create calendar row RichCalendar.prototype.create_cal_row = function(index) { var row = this.table_obj.insertRow(3+index); row.className = 'rc_cal_tr'; var cell_indx; for (cell_indx=0; cell_indx<7; cell_indx++) { var td = row.insertCell(cell_indx); // td.innerHTML = index + '-' + cell_indx; } this.cal_tr[index] = row; return row; } // changes calendar layout RichCalendar.prototype.change_skin = function(skin) { if (!this.iframe_obj) return; var skin_suffix = RichCalendar.skin_suffix(skin); this.iframe_obj.className = 'rc_calendar' + skin_suffix; this.body_obj.className = 'rc_iframe_body' + skin_suffix; this.skin = skin; } // returns formatted date (chars recognized are alike used by PHP function date) RichCalendar.prototype.get_formatted_date = function(format, date) { if (!date) date = this.date; if (!format) format = this.get_date_format(); // set time if (this.show_time && this.hours_obj && this.mins_obj) { this.date.setHours(this.hours_obj.value); var mins = this.date.setMinutes(this.mins_obj.value); } var y = date.getFullYear(); var m = date.getMonth(); var d = date.getDate(); var wd = date.getDay(); var hr = date.getHours(); var mins = date.getMinutes(); var secs = date.getSeconds(); var month_names_short = this.text('monthNamesShort'); var month_names = this.text('monthNames'); var day_names_short = this.text('dayNamesShort'); var day_names = this.text('dayNames'); var am = hr < 12 ? true : false; var hr12 = hr > 12 ? hr - 12 : (hr == 0 ? 12 : hr); var f = []; f["%a"] = am?'am':'pm'; f["%A"] = am?'AM':'PM'; f["%d"] = d < 10 ? '0'+d : d; // day of the month, 2 digits with leading zeroes (01 to 31) f["%D"] = day_names_short[wd]; // day of the week, textual, short, eg "Fri" f["%F"] = month_names[m]; // month, textual, long; eg "January" f["%h"] = hr12 < 10 ? '0' + hr12 : hr12; // hour, 12-hour format (01 to 12) f["%H"] = hr < 10 ? '0' + hr : hr; // hour, 24-hour format (00 to 23) f["%g"] = hr12; // hour, 12-hour format without leading zeros (1 to 12) f["%G"] = hr; // hour, 24-hour format without leading zeros (0 to 23) f["%i"] = mins < 10 ? '0' + mins : mins; // minutes (00 to 59) f["%j"] = d; // day of the month without leading zeros (1 to 31) f["%l"] = day_names[wd]; // day of the week, textual, long, eg "Friday" f["%L"] = RichCalendar.is_leap_year(y)?1:0; // 1 if leap year, otherwise - 0 f["%m"] = m < 9 ? '0' + (m+1) : (m+1); //month (01 to 12) f["%n"] = m + 1; //month without leading zeros (1 to 12) f["%M"] = month_names_short[m]; // month, textual, short, eg "Jan" f["%s"] = secs < 10 ? '0' + secs : secs; // seconds (00 to 59) f["%t"] = RichCalendar.get_month_days(date); // number of days in the month (28 to 31) f["%w"] = wd; // day of the week, numeric (0, Sunday to 6, Saturday) f["%Y"] = y; // year, 4 digits, eg 2007 f["%y"] = String(y).substr(2, 2); // year, 2 digits, eg "07" f["%z"] = RichCalendar.get_day_of_year(date); // day of the year (1 to 366) var parts = String(format).match(/%./g); var i; var f_date = format; for (i=0; i // replace them with english month names for (j=0; j ' + f_p); for (i=0; i= 12) { hours -= 12; } else { if (/pm/i.test(p[i]) && hours < 12) { hours += 12; } } break; case '%d': case '%j': day = parseInt(Number(p[i])); break; case '%F': for (j=0; j= 12) { hours -= 12; } else { if (/pm/i.test(p[i]) && hours < 12) { hours += 12; } } break; case '%i': mins = parseInt(Number(p[i])); break; case '%m': case '%n': month = parseInt(Number(p[i]))-1; break; case '%M': for (j=0; j 29 ? 1900 : 2000); } break; default: break; } } if (isNaN(year) || year <= 0) year = today.getFullYear(); if (isNaN(month) || month < 0 || month > 11) month = today.getMonth(); if (isNaN(day) || day <= 0 || day > 31) day = today.getDate(); if (isNaN(hours) || hours < 0 || hours > 23) hours = today.getHours(); if (isNaN(mins) || mins < 0 || mins > 59) mins = today.getMinutes(); if (isNaN(seconds) || seconds < 0 || seconds > 59) seconds = today.getSeconds(); //alert(year + ' => ' + month + ' => ' + day + ' => ' + hours + ' => ' + mins + ' => ' + seconds); this.date = new Date(year, month, day, hours, mins, seconds); }