/***************************************************************************** Copyright (C) 2006 Nick Baicoianu This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *****************************************************************************/ //constructor for the main Epoch class (ENGLISH VERSION) function Epoch(name,mode,targetelement,multiselect) { this.state = 0; this.name = name; this.iFrameName = this.name+'_frame'; this.curDate = new Date(); this.mode = mode; this.selectMultiple = (multiselect == true); //'false' is not true or not set at all //the various calendar variables //this.selectedDate = this.curDate; this.selectedDates = new Array(); this.calendar; this.calHeading; this.calCells; this.rows; this.cols; this.cells = new Array(); //The controls this.monthSelect; this.yearSelect; //standard initializations this.mousein = false; this.calConfig(); this.setDays(); this.displayYear = this.displayYearInitial; this.displayMonth = this.displayMonthInitial; this.hackIE = false; this.createCalendar(); //create the calendar DOM element and its children, and their related objects if(this.mode == 'popup' && targetelement && targetelement.type == 'text') //if the target element has been set to be an input text box { this.tgt = targetelement; this.calendar.style.position = 'absolute'; this.topOffset = this.tgt.offsetHeight; // the vertical distance (in pixels) to display the calendar from the Top of its input element this.leftOffset = 0; // the horizontal distance (in pixels) to display the calendar from the Left of its input element this.calendar.style.top = this.getTop(targetelement) + this.topOffset + 'px'; this.calendar.style.left = this.getLeft(targetelement) + this.leftOffset + 'px'; document.body.appendChild(this.calendar); this.tgt.calendar = this; this.tgt.onfocus = function () {this.calendar.show();}; //the calendar will popup when the input element is focused this.tgt.onblur = function () {if(!this.calendar.mousein){this.calendar.hide();}}; //the calendar will popup when the input element is focused } else { this.container = targetelement; this.container.appendChild(this.calendar); } if ( this.checkIE() ) { this.hackIE = true; this.prepUnderFrame(this.iFrameName); } this.state = 2; //0: initializing, 1: redrawing, 2: finished! this.visible ? this.show() : this.hide(); } //----------------------------------------------------------------------------- Epoch.prototype.calConfig = function () //PRIVATE: initialize calendar variables { //this.mode = 'flat'; //can be 'flat' or 'popup' this.displayYearInitial = this.curDate.getFullYear(); //the initial year to display on load this.displayMonthInitial = this.curDate.getMonth(); //the initial month to display on load (0-11) this.rangeYearLower = 1999; this.rangeYearUpper = this.displayYearInitial; this.minDate = new Date(this.rangeYearLower,0,1); this.maxDate = new Date(this.rangeYearUpper,0,1); this.startDay = 1; // the day the week will 'start' on: 0(Sun) to 6(Sat) this.showWeeks = false; //whether the week numbers will be shown this.selCurMonthOnly = false; //allow user to only select dates in the currently displayed month this.clearSelectedOnChange = true; //whether to clear all selected dates when changing months //flat mode-only settings: //this.selectMultiple = true; //whether the user can select multiple dates (flat mode only) switch(this.mode) //set the variables based on the calendar mode { case 'popup': //popup options this.visible = false; break; case 'flat': this.visible = true; break; } this.setLang(); }; //----------------------------------------------------------------------------- Epoch.prototype.setLang = function() //all language settings for Epoch are made here. Check Date.dateFormat() for the Date object's language settings { this.daylist = new Array('Вс','Пн','Вт','Ср','Чт','Пт','Сб','Вс','Пн','Вт','Ср','Чт','Пт','Сб'); /**/ this.months_sh = new Array('Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'); this.monthup_title = 'Переход к следующему месяцу'; this.monthdn_title = 'Переход к предыдущему месяцу'; this.clearbtn_caption = 'Очистить'; this.clearbtn_title = 'Очищает ранее выбранную дату'; this.maxrange_caption = 'Это граничная дата'; }; //----------------------------------------------------------------------------- Epoch.prototype.getTop = function (element) //PRIVATE: returns the absolute Top value of element, in pixels { var oNode = element; var iTop = 0; while(oNode.tagName != 'BODY') { iTop += oNode.offsetTop; oNode = oNode.offsetParent; } return iTop; }; //----------------------------------------------------------------------------- Epoch.prototype.getLeft = function (element) //PRIVATE: returns the absolute Left value of element, in pixels { var oNode = element; var iLeft = 0; while(oNode.tagName != 'BODY') { iLeft += oNode.offsetLeft; oNode = oNode.offsetParent; } return iLeft; }; //----------------------------------------------------------------------------- Epoch.prototype.show = function () //PUBLIC: displays the calendar { this.calendar.style.display = 'block'; this.visible = true; if (this.hackIE) this.setUnderFrame(this.iFrameName, this.calendar, true) }; //----------------------------------------------------------------------------- Epoch.prototype.hide = function () //PUBLIC: Hides the calendar { this.calendar.style.display = 'none'; this.visible = false; if (this.hackIE) this.setUnderFrame(this.iFrameName, this.calendar, false) }; //----------------------------------------------------------------------------- Epoch.prototype.toggle = function () //PUBLIC: Toggles (shows/hides) the calendar depending on its current state { if(this.visible) { this.hide(); } else { this.show(); } }; //----------------------------------------------------------------------------- Epoch.prototype.setDays = function () //PRIVATE: initializes the standard Gregorian Calendar parameters { this.daynames = new Array(); var j=0; for(var i=this.startDay; i< this.startDay + 7;i++) { this.daynames[j++] = this.daylist[i]; } this.monthDayCount = new Array(31,((this.curDate.getFullYear() - 2000) % 4 ? 28 : 29),31,30,31,30,31,31,30,31,30,31); }; //----------------------------------------------------------------------------- Epoch.prototype.setClass = function (element,className) //PRIVATE: sets the CSS class of the element, W3C & IE { element.setAttribute('class',className); element.setAttribute('className',className); // }; //----------------------------------------------------------------------------- Epoch.prototype.createCalendar = function () //PRIVATE: creates the full DOM implementation of the calendar { var tbody, tr, td; this.calendar = document.createElement('table'); this.calendar.setAttribute('id',this.name+'_calendar'); this.setClass(this.calendar,'calendar'); //to prevent IE from selecting text when clicking on the calendar this.calendar.onselectstart = function() {return false;}; this.calendar.ondrag = function() {return false;}; tbody = document.createElement('tbody'); //create the Main Calendar Heading tr = document.createElement('tr'); td = document.createElement('td'); td.appendChild(this.createMainHeading()); tr.appendChild(td); tbody.appendChild(tr); //create the calendar Day Heading tr = document.createElement('tr'); td = document.createElement('td'); td.appendChild(this.createDayHeading()); tr.appendChild(td); tbody.appendChild(tr); //create the calendar Day Cells tr = document.createElement('tr'); td = document.createElement('td'); td.setAttribute('id',this.name+'_cell_td'); this.calCellContainer = td; //used as a handle for manipulating the calendar cells as a whole td.appendChild(this.createCalCells()); tr.appendChild(td); tbody.appendChild(tr); //create the calendar footer tr = document.createElement('tr'); td = document.createElement('td'); td.appendChild(this.createFooter()); tr.appendChild(td); tbody.appendChild(tr); //add the tbody element to the main calendar table this.calendar.appendChild(tbody); //and add the onmouseover events to the calendar table this.calendar.owner = this; this.calendar.onmouseover = function() {this.owner.mousein = true;}; this.calendar.onmouseout = function() {this.owner.mousein = false;}; }; //----------------------------------------------------------------------------- Epoch.prototype.createMainHeading = function () //PRIVATE: Creates the primary calendar heading, with months & years { //create the containing
element var container = document.createElement('div'); container.setAttribute('id',this.name+'_mainheading'); this.setClass(container,'mainheading'); //create the child elements and other variables this.monthSelect = document.createElement('select'); this.yearSelect = document.createElement('select'); var monthDn = document.createElement('input'), monthUp = document.createElement('input'); var opt, i; //fill the month select box for(i=0;i<12;i++) { opt = document.createElement('option'); opt.setAttribute('value',i); if(this.state == 0 && this.displayMonth == i) { opt.setAttribute('selected','selected'); } opt.appendChild(document.createTextNode(this.months_sh[i])); this.monthSelect.appendChild(opt); } //and fill the year select box for(i=this.rangeYearLower;i<=this.rangeYearUpper;i++) { opt = document.createElement('option'); opt.setAttribute('value',i); if(this.state == 0 && this.displayYear == i) { opt.setAttribute('selected','selected'); } opt.appendChild(document.createTextNode(i)); this.yearSelect.appendChild(opt); } //add the appropriate children for the month buttons monthUp.setAttribute('type','button'); monthUp.setAttribute('value','>'); monthUp.setAttribute('title',this.monthup_title); monthUp.setAttribute('id',"monthInp"); monthDn.setAttribute('type','button'); monthDn.setAttribute('value','<'); monthDn.setAttribute('title',this.monthdn_title); monthDn.setAttribute('id',"monthInp"); this.monthSelect.owner = this.yearSelect.owner = monthUp.owner = monthDn.owner = this; //hack to allow us to access this calendar in the events (??) //assign the event handlers for the controls monthUp.onmouseup = function () {this.owner.nextMonth();}; monthDn.onmouseup = function () {this.owner.prevMonth();}; this.monthSelect.onchange = function() { this.owner.displayMonth = this.value; this.owner.displayYear = this.owner.yearSelect.value; this.owner.goToMonth(this.owner.displayYear,this.owner.displayMonth); }; this.yearSelect.onchange = function() { this.owner.displayMonth = this.owner.monthSelect.value; this.owner.displayYear = this.value; this.owner.goToMonth(this.owner.displayYear,this.owner.displayMonth); }; //and finally add the elements to the containing div container.appendChild(monthDn); container.appendChild(this.monthSelect); container.appendChild(this.yearSelect); container.appendChild(monthUp); return container; }; //----------------------------------------------------------------------------- Epoch.prototype.createFooter = function () //PRIVATE: creates the footer of the calendar - goes under the calendar cells { var container = document.createElement('div'); var clearSelected = document.createElement('input'); clearSelected.setAttribute('type','button'); clearSelected.setAttribute('value',this.clearbtn_caption); clearSelected.setAttribute('title',this.clearbtn_title); clearSelected.owner = this; clearSelected.onclick = function() { this.owner.resetSelections(false);}; container.appendChild(clearSelected); return container; }; //----------------------------------------------------------------------------- Epoch.prototype.resetSelections = function (returnToDefaultMonth) //PRIVATE: reset the calendar's selection variables to defaults { this.selectedDates = new Array(); this.rows = new Array(false,false,false,false,false,false,false); this.cols = new Array(false,false,false,false,false,false,false); if(this.tgt) //if there is a target element, clear it too { this.tgt.value = ''; if(this.mode == 'popup') {//hide the calendar if in popup mode this.hide(); } } if(returnToDefaultMonth == true) { this.goToMonth(this.displayYearInitial,this.displayMonthInitial); } else { this.reDraw(); } }; //----------------------------------------------------------------------------- Epoch.prototype.createDayHeading = function () //PRIVATE: creates the heading containing the day names { //create the table element this.calHeading = document.createElement('table'); this.calHeading.setAttribute('id',this.name+'_caldayheading'); this.setClass(this.calHeading,'caldayheading'); var tbody,tr,td; tbody = document.createElement('tbody'); tr = document.createElement('tr'); this.cols = new Array(false,false,false,false,false,false,false); //if we're showing the week headings, create an empty for filler if(this.showWeeks) { td = document.createElement('td'); td.setAttribute('class','wkhead'); td.setAttribute('className','wkhead'); // tr.appendChild(td); } //populate the day titles for(var dow=0;dow<7;dow++) { td = document.createElement('td'); td.appendChild(document.createTextNode(this.daynames[dow])); if(this.selectMultiple) { //if selectMultiple is true, assign the cell a CalHeading Object to handle all events td.headObj = new CalHeading(this,td,(dow + this.startDay < 7 ? dow + this.startDay : dow + this.startDay - 7)); } tr.appendChild(td); } tbody.appendChild(tr); this.calHeading.appendChild(tbody); return this.calHeading; }; //----------------------------------------------------------------------------- Epoch.prototype.createCalCells = function () //PRIVATE: creates the table containing the calendar day cells { this.rows = new Array(false,false,false,false,false,false); this.cells = new Array(); var row = -1, totalCells = (this.showWeeks ? 48 : 42); var beginDate = new Date(this.displayYear,this.displayMonth,1); var endDate = new Date(this.displayYear,this.displayMonth,this.monthDayCount[this.displayMonth]); var sdt = new Date(beginDate); sdt.setDate(sdt.getDate() + (this.startDay - beginDate.getDay()) - (this.startDay - beginDate.getDay() > 0 ? 7 : 0) ); //create the table element this.calCells = document.createElement('table'); this.calCells.setAttribute('id',this.name+'_calcells'); this.setClass(this.calCells,'calcells'); var tbody,tr,td; tbody = document.createElement('tbody'); for(var i=0;i } td.appendChild(document.createTextNode(sdt.getWeek())); tr.appendChild(td); i++; } } else if(i % 7 == 0) //otherwise, new row every 7 cells { row++; tr = document.createElement('tr'); } //create the day cells td = document.createElement('td'); td.appendChild(document.createTextNode(sdt.getDate()));// +' ' +sdt.getUeDay())); var cell = new CalCell(this,td,sdt,row); this.cells.push(cell); td.cellObj = cell; sdt.setDate(sdt.getDate() + 1); //increment the date tr.appendChild(td); tbody.appendChild(tr); } this.calCells.appendChild(tbody); this.reDraw(); return this.calCells; }; //----------------------------------------------------------------------------- Epoch.prototype.reDraw = function () //PRIVATE: reapplies all the CSS classes for the calendar cells, usually called after chaning their state { this.state = 1; var i,j; for(i=0;i 0) this.monthSelect.value--; else { if(this.yearSelect.value > this.rangeYearLower) { this.monthSelect.value = 11; this.yearSelect.value--; } else { alert(this.maxrange_caption); } } //assign the currently displaying month/year values this.displayMonth = this.monthSelect.value; this.displayYear = this.yearSelect.value; //and refresh the calendar for the new month/year this.deleteCells(); this.calCellContainer.appendChild(this.createCalCells()); }; //----------------------------------------------------------------------------- Epoch.prototype.addZero = function (vNumber) //PRIVATE: pads a 2 digit number with a leading zero { return ((vNumber < 10) ? '0' : '') + vNumber; }; //----------------------------------------------------------------------------- Epoch.prototype.addDates = function (dates,redraw) //PUBLIC: adds the array "dates" to the calendars selectedDates array (no duplicate dates) and redraws the calendar { var j,in_sd; for(var i=0;i -1 ? vYearLong : vYearShort); var vHour = this.addZero(vDate.getHours()); var vMinute = this.addZero(vDate.getMinutes()); var vSecond = this.addZero(vDate.getSeconds()); return vFormat.replace(/dd/g, vDay).replace(/mm/g, vMonth).replace(/y{1,4}/g, vYear).replace(/hh/g, vHour).replace(/nn/g, vMinute).replace(/ss/g, vSecond); }; //----------------------------------------------------------------------------- Epoch.prototype.updatePos = function (target) //PUBLIC: moves the calendar's position to target's location (popup mode only) { this.calendar.style.top = this.getTop(target) + this.topOffset + 'px' this.calendar.style.left = this.getLeft(target) + this.leftOffset + 'px' } //----------------------------------------------------------------------------- /*****************************************************************************/ function CalHeading(owner,tableCell,dow) { this.owner = owner; this.tableCell = tableCell; this.dayOfWeek = dow; //the event handlers this.tableCell.onclick = this.onclick; } //----------------------------------------------------------------------------- CalHeading.prototype.onclick = function () { //reduce indirection: var owner = this.headObj.owner; var sdates = owner.selectedDates; var cells = owner.cells; owner.cols[this.headObj.dayOfWeek] = !owner.cols[this.headObj.dayOfWeek]; for(var i=0;i //the event handlers this.tableCell.onclick = this.onclick; } //----------------------------------------------------------------------------- WeekHeading.prototype.onclick = function () { //reduce indirection: var owner = this.weekObj.owner; var cells = owner.cells; var sdates = owner.selectedDates; var i,j; owner.rows[this.weekObj.tableRow] = !owner.rows[this.weekObj.tableRow]; for(i=0;i { this.setAttribute('class',this.cellClass + ' hover'); this.setAttribute('className',this.cellClass + ' hover'); }; //----------------------------------------------------------------------------- CalCell.prototype.onmouseout = function () //replicate CSS :hover effect for non-supporting browsers { this.cellObj.setClass(); }; //----------------------------------------------------------------------------- CalCell.prototype.onclick = function () { //reduce indirection: var cell = this.cellObj; var owner = cell.owner; if(!owner.selCurMonthOnly || cell.date.getMonth() == owner.displayMonth && cell.date.getFullYear() == owner.displayYear) { if(owner.selectMultiple == true) //if we can select multiple cells simultaneously, add the currently selected cell's date to the selectedDates array { if(!cell.selected) //if this cell has been selected { if(owner.selectedDates.arrayIndex(cell.date) == -1) { owner.selectedDates.push(cell.date); } } else { var tmp = owner.selectedDates; // to reduce indirection //if the cell has been deselected, remove it from the owner calendar's selectedDates array for(var i=0;i 0 && this.date.getDay() < 6) { this.cellClass = 'wkday'; } else { this.cellClass = 'wkend'; } if(this.date.getFullYear() == this.owner.curDate.getFullYear() && this.date.getMonth() == this.owner.curDate.getMonth() && this.date.getDate() == this.owner.curDate.getDate()) { this.cellClass = this.cellClass + ' curdate'; } this.tableCell.setAttribute('class',this.cellClass); this.tableCell.setAttribute('className',this.cellClass); // }; /*****************************************************************************/ Date.prototype.getDayOfYear = function () //returns the day of the year for this date { return parseInt((this.getTime() - new Date(this.getFullYear(),0,1).getTime())/86400000 + 1); }; //----------------------------------------------------------------------------- Date.prototype.getWeek = function () //returns the day of the year for this date { return parseInt((this.getTime() - new Date(this.getFullYear(),0,1).getTime())/604800000 + 1); }; /*function getISOWeek() { var newYear = new Date(this.getFullYear(),0,1); var modDay = newYear.getDay(); if (modDay == 0) modDay=6; else modDay--; var daynum = ((Date.UTC(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0) - Date.UTC(this.getFullYear()),0,1,0,0,0)) /1000/60/60/24) + 1; if (modDay < 4 ) { var weeknum = Math.floor((daynum+modDay-1)/7)+1; } else { var weeknum = Math.floor((daynum+modDay-1)/7); if (weeknum == 0) { year--; var prevNewYear = new Date(this.getFullYear(),0,1); var prevmodDay = prevNewYear.getDay(); if (prevmodDay == 0) prevmodDay = 6; else prevmodDay--; if (prevmodDay < 4) weeknum = 53; else weeknum = 52; } } return + weeknum; }*/ //----------------------------------------------------------------------------- Date.prototype.getUeDay = function () //returns the number of DAYS since the UNIX Epoch - good for comparing the date portion { return parseInt(Math.floor((this.getTime() - this.getTimezoneOffset() * 60000)/86400000)); //must take into account the local timezone }; //----------------------------------------------------------------------------- Date.prototype.dateFormat = function(format) { if(!format) { // the default date format to use - can be customized to the current locale format = 'd.m.Y'; } LZ = function(x) {return(x < 0 || x > 9 ? '' : '0') + x}; var MONTH_NAMES = new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'); var DAY_NAMES = new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat'); format = format + ""; var result=""; var i_format=0; var c=""; var token=""; var y=this.getFullYear().toString(); var M=this.getMonth()+1; var d=this.getDate(); var E=this.getDay(); var H=this.getHours(); var m=this.getMinutes(); var s=this.getSeconds(); var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k; // Convert real this parts into formatted versions var value = new Object(); //if (y.length < 4) {y=''+(y-0+1900);} value['Y'] = y.toString(); value['y'] = y.substring(2); value['n'] = M; value['m'] = LZ(M); value['F'] = MONTH_NAMES[M-1]; value['M'] = MONTH_NAMES[M+11]; value['j'] = d; value['d'] = LZ(d); value['D'] = DAY_NAMES[E+7]; value['l'] = DAY_NAMES[E]; value['G'] = H; value['H'] = LZ(H); if (H==0) {value['g']=12;} else if (H>12){value['g']=H-12;} else {value['g']=H;} value['h']=LZ(value['g']); if (H > 11) {value['a']='pm'; value['A'] = 'PM';} else { value['a']='am'; value['A'] = 'AM';} value['i']=LZ(m); value['s']=LZ(s); //construct the result string while (i_format < format.length) { c=format.charAt(i_format); token=""; while ((format.charAt(i_format)==c) && (i_format < format.length)) { token += format.charAt(i_format++); } if (value[token] != null) { result=result + value[token]; } else { result=result + token; } } return result; }; /*****************************************************************************/ Array.prototype.arrayIndex = function(searchVal,startIndex) //similar to array.indexOf() - created to fix IE deficiencies { startIndex = (startIndex != null ? startIndex : 0); //default startIndex to 0, if not set for(var i=startIndex;i // check need of iframe under cal (for MSIE and