/*!!
Epoch DHTML JavaScript Calendar - Version 2.0.2
English Edition
Primary JavaScript File
(c) 2006-2007 MeanFreePath
Free for NON-COMMERCIAL use - see website for details and updates
http://www.meanfreepath.com/javascript_calendar/index.html
!!*/

/**
* The main Epoch class.  All publicly-accessible methods and properties are called from this class
*/
function Epoch(name,mode,targetelement,multiselect) {
   var self = this; //workaround due to varying definitions of "this" in variable scopes. see http://www.meanfreepath.com/support/epoch/epoch.html#self for details
   //DEFINE PRIVATE METHODS
   //-----------------------------------------------------------------------------
   /**
   * Declares and initializes the calendar variables.  All the variables here can be safely changed
   * (within reason ;) by the developer
   */
   function calConfig() {
      self.versionNumber = '2.0.2';
      self.displayYearInitial = self.curDate.getFullYear(); //the initial year to display on load
      self.displayMonthInitial = self.curDate.getMonth(); //the initial month to display on load (0-11)
      self.displayYear = self.displayYearInitial;
      self.displayMonth = self.displayMonthInitial;
      self.minDate = new Date(2010,0,1);
      self.maxDate = new Date(2020,11,31);
      self.startDay = 0; // the day the week will 'start' on: 0(Sun) to 6(Sat)
      self.showWeeks = false; //whether the week numbers will be shown
      self.selCurMonthOnly = true; //allow user to only select dates in the currently displayed month
   }
   //-----------------------------------------------------------------------------
   /**
   * All language settings for Epoch are made here.
   * Check Date.dateFormat() for the Date object's language settings
   */
   function setLang() {
      self.daylist = new Array('S','M','T','W','T','F','S','S','M','T','W','T','F','S');
      self.months_sh = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
      self.monthup_title = 'Go to the next month';
      self.monthdn_title = 'Go to the previous month';
      self.clearbtn_caption = 'Clear';
      self.clearbtn_title = 'Clears any dates selected on the calendar';
      self.maxrange_caption = 'This is the maximum range';
      self.closebtn_caption = 'Close';
      self.closebtn_title = 'Close the calendar';
   }
   //-----------------------------------------------------------------------------
   /**
   * Initializes the standard Gregorian Calendar parameters
   */
   function setDays() {
      self.daynames = new Array();
      var j=0;
      for(var i=self.startDay;i<self.startDay + 7;i++) {
         self.daynames[j++] = self.daylist[i];
      }
      self.monthDayCount = new Array(31,((self.curDate.getFullYear() - 2000) % 4 ? 28 : 29),31,30,31,30,31,31,30,31,30,31);
   }
   //-----------------------------------------------------------------------------
   /**
   * Creates the full DOM implementation of the calendar
   */
   function createCalendar() {
      var tbody, tr, td;
      self.calendar = document.createElement('table');
      self.calendar.setAttribute('id',self.name+'_calendar');
      setClass(self.calendar,'calendar');
      self.calendar.style.display = 'none'; //default to invisible
      //to prevent IE from selecting text when clicking on the calendar
      addEventHandler(self.calendar,'selectstart', function() {return false;});
      addEventHandler(self.calendar,'drag', function() {return false;});
      tbody = document.createElement('tbody');

      //create the Main Calendar Heading
      tr = document.createElement('tr');
      td = document.createElement('td');
      td.appendChild(createMainHeading());
      tr.appendChild(td);
      tbody.appendChild(tr);

      //create the calendar Day Heading & the calendar Day Cells
      tr = document.createElement('tr');
      td = document.createElement('td');
      self.calendar.celltable = document.createElement('table');
      setClass(self.calendar.celltable,'cells');
      self.calendar.celltable.appendChild(createDayHeading());
      self.calendar.celltable.appendChild(createCalCells());
      td.appendChild(self.calendar.celltable);
      tr.appendChild(td);
      tbody.appendChild(tr);

      //create the calendar footer
      tr = document.createElement('tr');
      td = document.createElement('td');
      td.appendChild(createFooter());
      tr.appendChild(td);
      tbody.appendChild(tr);

      //add the tbody element to the main calendar table
      self.calendar.appendChild(tbody);

      //and add the onmouseover events to the calendar table
      addEventHandler(self.calendar,'mouseover',cal_onmouseover);
      addEventHandler(self.calendar,'mouseout',cal_onmouseout);
   }
   //-----------------------------------------------------------------------------
   /**
   * Creates the primary calendar heading, with months & years
   */
   function createMainHeading() {
      //create the containing <div> element
      var container = document.createElement('div');
      setClass(container,'mainheading');
      //create the child elements and other variables
      self.monthSelect = document.createElement('select');
      self.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(self.displayMonth == i) {
            opt.setAttribute('selected','selected');
         }
         opt.appendChild(document.createTextNode(self.months_sh[i]));
         self.monthSelect.appendChild(opt);
      }
      //and fill the year select box
      var yrMax = self.maxDate.getFullYear(), yrMin = self.minDate.getFullYear();
      for(i=yrMin;i<=yrMax;i++) {
         opt = document.createElement('option');
         opt.setAttribute('value',i);
         if(self.displayYear == i) {
            opt.setAttribute('selected','selected');
         }
         opt.appendChild(document.createTextNode(i));
         self.yearSelect.appendChild(opt);
      }
      //add the appropriate children for the month buttons
      monthUp.setAttribute('type','button');
      monthUp.setAttribute('value','>');
      monthUp.setAttribute('title',self.monthup_title);
      monthDn.setAttribute('type','button');
      monthDn.setAttribute('value','<');
      monthDn.setAttribute('title',self.monthdn_title);
      self.monthSelect.owner = self.yearSelect.owner = monthUp.owner = monthDn.owner = self;  //hack to allow us to access self calendar in the events (<fix>??)

      //assign the event handlers for the controls
      function selectonchange()  {
         if(self.goToMonth(self.yearSelect.value,self.monthSelect.value)) {
            self.displayMonth = self.monthSelect.value;
            self.displayYear = self.yearSelect.value;
         }
         else {
            self.monthSelect.value = self.displayMonth;
            self.yearSelect.value = self.displayYear;
         }
      }
      addEventHandler(monthUp,'click',function(){self.nextMonth();});
      addEventHandler(monthDn,'click',function(){self.prevMonth();});
      addEventHandler(self.monthSelect,'change',selectonchange);
      addEventHandler(self.yearSelect,'change',selectonchange);

      //and finally add the elements to the containing div
      container.appendChild(monthDn);
      container.appendChild(self.monthSelect);
      container.appendChild(self.yearSelect);
      container.appendChild(monthUp);
      return container;
   }
   //-----------------------------------------------------------------------------
   /**
   * Creates the footer of the calendar - goes under the calendar cells
   */
   function createFooter() {
      var container = document.createElement('div');
      var clearSelected = document.createElement('input');
      clearSelected.setAttribute('type','button');
      clearSelected.setAttribute('value',self.clearbtn_caption);
      clearSelected.setAttribute('title',self.clearbtn_title);
      clearSelected.owner = self;
      addEventHandler(clearSelected,'click',function() {self.resetSelections(false);});
      container.appendChild(clearSelected);
      if(self.mode == 'popup') {
         var closeBtn = document.createElement('input');
         closeBtn.setAttribute('type','button');
         closeBtn.setAttribute('value',self.closebtn_caption);
         closeBtn.setAttribute('title',self.closebtn_title);
         addEventHandler(closeBtn,'click',function(){self.hide();});
         setClass(closeBtn,'closeBtn');
         container.appendChild(closeBtn);
      }
      return container;
   }
   //-----------------------------------------------------------------------------
   /**
   * Creates the heading containing the day names
   */
   function createDayHeading() {
      //create the table element
      self.calHeading = document.createElement('thead');
      setClass(self.calHeading,'caldayheading');
      var tr = document.createElement('tr'), th;
      self.cols = new Array(false,false,false,false,false,false,false);

      //if we're showing the week headings, create an empty <td> for filler
      if(self.showWeeks) {
         th = document.createElement('th');
         setClass(th,'wkhead');
         tr.appendChild(th);
      }
      //populate the day titles
      for(var dow=0;dow<7;dow++) {
         th = document.createElement('th');
         th.appendChild(document.createTextNode(self.daynames[dow]));
         if(self.selectMultiple) { //if selectMultiple is true, assign the cell a CalHeading Object to handle all events
            th.headObj = new CalHeading(self,th,(dow + self.startDay < 7 ? dow + self.startDay : dow + self.startDay - 7));
         }
         tr.appendChild(th);
      }
      self.calHeading.appendChild(tr);
      return self.calHeading;
   }
   //-----------------------------------------------------------------------------
   /**
   * Creates the table containing the calendar day cells
   */
   function createCalCells() {
      self.rows = new Array(false,false,false,false,false,false);
      self.cells = new Array();
      var row = -1, totalCells = (self.showWeeks ? 48 : 42);
      var beginDate = new Date(self.displayYear,self.displayMonth,1);
      var endDate = new Date(self.displayYear,self.displayMonth,self.monthDayCount[self.displayMonth]);
      var sdt = new Date(beginDate);
      sdt.setDate(sdt.getDate() + (self.startDay - beginDate.getDay()) - (self.startDay - beginDate.getDay() > 0 ? 7 : 0) );
      //create the table element to hold the cells
      self.calCells = document.createElement('tbody');
      var tr,td;
      var cellIdx = 0, cell, week, dayval;

      for(var i=0;i<totalCells;i++) {
         if(self.showWeeks) { //if we are showing the week headings
            if(i % 8 == 0) {
               row++;
               week = sdt.getWeek(self.startDay);
               tr = document.createElement('tr');
               td = document.createElement('td');
               if(self.selectMultiple) { //if selectMultiple is enabled, create the associated weekObj objects
                  td.weekObj = new WeekHeading(self,td,week,row)
               }
               else {//otherwise just set the class of the td for consistent look
                  setClass(td,'wkhead');
               }
               td.appendChild(document.createTextNode(week));
               tr.appendChild(td);
               i++;
            }
         }
         else if(i % 7 == 0) { //otherwise, new row every 7 cells
            row++;
            week = sdt.getWeek(self.startDay);
            tr = document.createElement('tr');
         }
         //create the day cells
         dayval = sdt.getDate();
         td = document.createElement('td');
         td.appendChild(document.createTextNode(dayval));
         cell = new CalCell(self,td,sdt,row,week);//,'normal',sdt.getTime() >= self.minDate.getTime() && sdt.getTime() <= self.maxDate.getTime());
         self.cells[cellIdx] = cell;
         td.cellObj = cell;
         tr.appendChild(td);
         self.calCells.appendChild(tr);
         self.reDraw(cellIdx++); //and paint the cell according to its properties
         sdt.setDate(dayval + 1); //increment the date
      }
      return self.calCells;
   }
   //-----------------------------------------------------------------------------
   /**
   * Runs all the operations necessary to change the mode of the calendar
   * @param HTMLInputElement targetelement
   */
   function setMode(targetelement)  {
      if(self.mode == 'popup') { //set positioning to absolute for popup
         self.calendar.style.position = 'absolute';
      }
      //if a target element has been set, append the calendar to it
      if(targetelement) {
         switch(self.mode) {
            case 'flat':
               self.tgt = targetelement;
               self.tgt.appendChild(self.calendar);
               self.visible = true;
               break;
            case 'popup':
               self.calendar.style.position = 'absolute';
               document.body.appendChild(self.calendar);
               self.setTarget(targetelement,false);
               break;
         }
      }
      else { //otherwise, add the calendar to the document.body (useful if targetelement will not be defined until after the calendar is initialized)
         document.body.appendChild(self.calendar);
         self.visible = false;
      }
   }
   //-----------------------------------------------------------------------------
   /**
   * Removes the calendar table cells from the DOM (does not delete the cell objects associated with them)
   */
   function deleteCells() {
      self.calendar.celltable.removeChild(self.calendar.celltable.childNodes[1]); //remove the tbody element from the cell table
   }
   //-----------------------------------------------------------------------------
   /**
   * Sets the CSS class of the element, W3C & IE
   * @param HTMLElement element
   * @param string className
   */
   function setClass(element,className) {
      element.setAttribute('class',className);
      element.setAttribute('className',className); //<iehack>
   }
   /**
   * Updates a cell's data, including css class and selection properties
   * @param int cellindex
   */
   function setCellProperties(cellindex) {
      var cell = self.cells[cellindex];
      var date;
      idx = self.dateInArray(self.dates,cell.date);
      if(idx > -1) {
         date = self.dates[idx]; //reduce indirection
         cell.date.selected = date.selected || false;
         cell.date.type = date.type;
         cell.date.canSelect = date.canSelect;
         cell.setTitle(date.title);
         cell.setURL(date.href);
         cell.setHTML(date.cellHTML);
      }
      else {
         cell.date.selected = false; //if the cell's date isn't in the dates array, set it's selected value to false
      }
      //make all cells lying outside the min and max dates un-selectable
      if(cell.date.getTime() < self.minDate.getTime() || cell.date.getTime() > self.maxDate.getTime()) {
         cell.date.canSelect = false;
      }
      cell.setClass();
   }
   //-----------------------------------------------------------------------------
   function cal_onmouseover() {
      self.mousein = true;
   }
   //-----------------------------------------------------------------------------
   function cal_onmouseout()  {
      self.mousein = false;
   }
   //-----------------------------------------------------------------------------
   /**
    * Updates the calendar's selectedDates pointer array
    */
   function updateSelectedDates() {
      var idx = 0;
      self.selectedDates = new Array();
      for(i=0;i<self.dates.length;i++) {
         if(self.dates[i].selected) {
            self.selectedDates[idx++] = self.dates[i];
         }
      }
   }
   //PUBLIC METHODS
   //-----------------------------------------------------------------------------
   /**
   * Find a date in the given array, returning its index if found, -1 if not
   * @param array arr
   * @param Date searchVal
   * @param int startIndex
   * @return int
   */
   self.dateInArray = function(arr,searchVal,startIndex) {
      startIndex = (startIndex != null ? startIndex : 0); //default startIndex to 0, if not set
      for(var i=startIndex;i<arr.length;i++) {
         if(searchVal.getUeDay() == arr[i].getUeDay()) {
            return i;
         }
      }
      return -1;
   };
   //-----------------------------------------------------------------------------
   /**
   * Changes the target element of this calendar to another input.
   * Many thanks to Jake Olefsky - jake@olefsky.com
   * @param HTMLInputElement targetelement
   * @param bool focus
   */
   self.setTarget = function (targetelement, focus)
   {
      //if this is a popup calendar
      if(self.mode == 'popup') {
         //declare the event handlers for the target element
         function popupFocus() {
            self.show();
         }
         function popupBlur() {
            if(!self.mousein){
               self.hide();
            }
         }
         function popupKeyDown() {
            self.hide();
         }
         //unset old target element event handlers (if there is one yet)
         if(self.tgt) {
            removeEventHandler(self.tgt,'focus',popupFocus);
            removeEventHandler(self.tgt,'blur',popupBlur);
            removeEventHandler(self.tgt,'keydown',popupKeyDown);
         }
         //and set the new target element
         self.tgt = targetelement;
         //create a pointer to the INPUT's date object and init the new data array
         var dto = self.tgt.dateObj,pdateArr = new Array;
         //if a date is set for the target element
         if(dto) {
            if(self.tgt.value.length) { //load it into the calendar...
               pdateArr[0] = dto;
            }
            self.goToMonth(dto.getFullYear(),dto.getMonth()); //...and go to the target's month/year
         }
         self.selectDates(pdateArr,true,true,true);

         self.topOffset = self.tgt.offsetHeight; // the vertical distance (in pixels) to display the calendar from the Top of its input element
         self.leftOffset = 0;                // the horizontal distance (in pixels) to display the calendar from the Left of its input element
         self.updatePos(self.tgt);
         //and add the event handlers to the new element
         addEventHandler(self.tgt,'focus',popupFocus);
         addEventHandler(self.tgt,'blur',popupBlur);
         addEventHandler(self.tgt,'keydown',popupKeyDown);
         if(focus !== false) { //focus the target element immediately, unless otherwise specified
            popupFocus();
         }
      }
      else { //if this is a flat or inline calendar
         //if the target is already set, remove the calendar's DOM representation from it
         if(self.tgt) {
            self.tgt.removeChild(self.calendar);
         }
         //now, set the calendar's target to the new target element, and show the calendar
         self.tgt = targetelement;
         self.tgt.appendChild(self.calendar);
         self.show();
      }
   };
   //-----------------------------------------------------------------------------
   /**
   * Go to the next month.  if the month is December, go to January of the next year
   * Returns true if the month will be incremented
   * @return bool
   */
   self.nextMonth = function () {
      var month = self.displayMonth;
      var year = self.displayYear;
      //increment the month/year values, provided they're within the min/max ranges
      if(self.displayMonth < 11) { //i.e. if currently in the year
         month++;
      }
      else if(self.yearSelect.value < self.maxDate.getFullYear()) { //if not, increment the year as well
         month = 0;
         year++;
      }
      return self.goToMonth(year,month);
   };
   //-----------------------------------------------------------------------------
   /**
   * Go to the previous month - if the month is January, go to December of the previous year.
   * Returns true if the month will be decremented
   * @return bool
   */
   self.prevMonth = function () {
      var month = self.displayMonth;
      var year = self.displayYear;
      //increment the month/year values, provided they're within the min/max ranges
      if(self.displayMonth > 0) { //i.e. if currently in the year
         month--;
      }
      else { //if not, decrement the year as well
         month = 11;
         year--;
      }
      return self.goToMonth(year,month);
   };
   //-----------------------------------------------------------------------------
   /**
   * Sets the calendar to display the requested month/year, returning true if the
   * date is within the minimum and maximum allowed dates
   * @param int year
   * @param int month
   * @return bool
   */
   self.goToMonth = function (year,month) {
      var testdatemin = new Date(year, month, 31);
      var testdatemax = new Date(year, month, 1);
      if(testdatemin >= self.minDate && testdatemax <= self.maxDate) {
         self.monthSelect.value = self.displayMonth = month;
         self.yearSelect.value = self.displayYear = year;
         //recreate the calendar for the new month
         createCalCells();
         deleteCells();
         self.calendar.celltable.appendChild(self.calCells);
         return true;
      }
      else {
         //alert(self.maxrange_caption);
         return false;
      }
   };
   //-----------------------------------------------------------------------------
   /**
   * Moves the calendar's position to the target element's location (popup mode only)
   */
   self.updatePos = function (target) {
      if(self.mode == 'popup') {
         self.calendar.style.top = getTop(target) + self.topOffset + 'px';
         self.calendar.style.left = getLeft(target) + self.leftOffset + 'px';
      }
   };
   //-----------------------------------------------------------------------------
   /**
   * Displays the calendar
   */
   self.show = function () {
      self.updatePos(self.tgt); //update the calendar position, in case the page layout has changed since loading
      self.calendar.style.display = 'block'; //'table'; //<iehack> 'table' is the W3C-recommended spec, but IE isn't a fan of those
      self.visible = true;
   };
   //-----------------------------------------------------------------------------
   /**
   * Hides the calendar
   */
   self.hide = function () {
      self.calendar.style.display = 'none';
      self.visible = false;
   };
   //-----------------------------------------------------------------------------
   /**
   * Toggles (shows/hides) the calendar depending on its current state
   */
   self.toggle = function () {
      self.visible ? self.hide() : self.show();
   };
   //-----------------------------------------------------------------------------
   /**
   * Adds the array "dates" to the calendar's dates array, removing duplicate dates,
   * and redraws the calendar if redraw is true
   * @param array dates
   * @param bool redraw
   */
   self.addDates = function (dates,redraw) {
      var i;
      for(i=0;i<dates.length;i++) {
         if(self.dateInArray(self.dates,dates[i]) == -1) { //if the date isn't already in the array, add it!
            self.dates[self.dates.length] = dates[i];
         }
      }
      //now rebuild the selectedDates pointer array
      updateSelectedDates();
      if(redraw != false) { //redraw  the calendar if "redraw" is false or undefined
         self.reDraw();
      }
   };
   //-----------------------------------------------------------------------------
   /**
   * Removes the dates from the calendar's dates array and redraws the calendar
   * if redraw is true
   * @param array dates
   * @param bool redraw
   */
   self.removeDates = function (dates,redraw) {
      var idx;
      for(var i=0;i<dates.length;i++) {
         idx = self.dateInArray(self.dates,dates[i]);
         if(idx != -1) { //search for the dates in the dates array, removing them if the dates match
            self.dates.splice(idx,1);
         }
      }
      updateSelectedDates();
      if(redraw != false) { //redraw  the calendar if "redraw" is true or undefined
         self.reDraw();
      }
   };
   //-----------------------------------------------------------------------------
   /**
   * Selects or Deselects an array of dates
   * @param Array inpdates
   * @param bool selectVal
   * @param bool redraw
   * @param bool removeothers
   */
   self.selectDates = function (inpdates,selectVal,redraw,removeothers) {
      var i, idx;
      if(removeothers == true) {
         for(i=0;i<self.dates.length;i++) {
            self.dates[i].selected = false;
         }
      }
      for(i=0;i<inpdates.length;i++) {
         idx = self.dateInArray(self.dates,inpdates[i]);
         if(selectVal == true) {
            inpdates[i].selected = true;
            if(idx == -1) { //if the date does not exist in the calendar's dates array, add it
               self.dates[self.dates.length] = inpdates[i];
            }
            else { //if not, just select it
               self.dates[idx].selected = true;
            }
         }
         else { //if deselecting...
            if(idx > -1) { //if the date is found, deselect and/or remove it from the calendar's dates array
               self.dates[idx].selected = inpdates[i].selected = false;
               if(self.dates[idx].type == 'normal') { //remove 'normal' dates from the dates array, since they're useless unless selected
                  self.dates.splice(idx,1);
               }
            }
         }
      }
      //now rebuild the selectedDates pointer array
      updateSelectedDates();
      if(redraw != false) { //redraw the calendar if "redraw" is false or undefined
         self.reDraw();
      }
   };
   //-----------------------------------------------------------------------------
   /**
   * Adds the dates in dates as hidden inputs to the form "form".  inputname
   * is the name of each hidden element. "form" can either be a pointer to the form's
   * DOM element or its id string.
   * @param mixed form
   * @param string inputname
   */
   self.sendForm = function(form,inputname) {
      var inpname = inputname || 'epochdates', f, inp;
      f = (typeof(form) == 'string' ? document.getElementById(form) : form);
      if(!f) {
         alert('ERROR: Invalid form input');
         return false;
      }
      for(var i=0;i<self.dates.length;i++) {
         inp = document.createElement('input');
         inp.setAttribute('type','hidden');
         inp.setAttribute('name',inpname + '['+i+']');
         inp.setAttribute('value',encodeURIComponent(self.dates[i].dateFormat('Y-m-d')));  //default to the ISO date format
         f.appendChild(inp);
      }
      return true;
   };
   //-----------------------------------------------------------------------------
   /**
   * Erases the dates array and resets the calendar's selection variables to defaults.
   * If retMonth is true, the calendar will return to the initial default month/year
   * @param bool retMonth
   */
   self.resetSelections = function (retMonth) {
      var dateArray = new Array();
      var dt = self.dates;
      for(var i=0;i<dt.length;i++) {
         if(dt[i].selected) {
            dateArray[dateArray.length] = dt[i];
         }
      }
      self.selectDates(dateArray,false,false);
      self.rows = new Array(false,false,false,false,false,false,false);
      self.cols = new Array(false,false,false,false,false,false,false);
      if(self.mode == 'popup') { //hide the calendar and clear the input element if in popup mode
         // self.tgt.value = '';
         // self.hide();
      }
      retMonth == true ? self.goToMonth(self.displayYearInitial,self.displayMonthInitial) : self.reDraw();
   };
   //-----------------------------------------------------------------------------
   /**
   * Reapplies all the CSS classes for the calendar cells - usually called after changing their state
   * If index is specified, it will redraw that cell only.
   * @param int index
   */
   self.reDraw = function (index) {
      self.state = 1;
      var len = index ? index + 1 : self.cells.length;
      for(var i = index || 0;i<len;i++) {
         setCellProperties(i);
      }
      self.state = 2;
   };
   //-----------------------------------------------------------------------------
   /**
   * Returns the index of the cell whose date value matches "date", or -1 if not found
   * @param Date date
   * @return int
   */
   self.getCellIndex = function(date) {
      for(var i=0;i<self.cells.length;i++) {
         if(self.cells[i].date.getUeDay() == date.getUeDay()) {
            return i;
         }
      }
      return -1;
   };
   //-----------------------------------------------------------------------------
   //begin constructor code:

   //PUBLIC VARIABLES
   self.state = 0;
   self.name = name;
   self.curDate = new Date();
   self.mode = mode;
   self.selectMultiple = (multiselect == true); //'false' if not true or not set at all
   //the various calendar variables
   self.dates = new Array();
   self.selectedDates = new Array();

   self.calendar;
   self.calHeading;
   self.calCells;
   self.rows;
   self.cols;
   self.cells = new Array();
   //The controls
   self.monthSelect;
   self.yearSelect;
   self.mousein = false;

   //Initialize the calendar and its variables{
   calConfig();
   setLang();
   setDays();
   createCalendar(); //create the calendar DOM element and its children, and their related objects
   targetelement = typeof(targetelement) == 'string' ? document.getElementById(targetelement) : targetelement;
   setMode(targetelement);
   self.state = 2; //0: initializing, 1: redrawing, 2: finished!
   self.visible ? self.show() : self.hide();
}
//-----------------------------------------------------------------------------
/*****************************************************************************/
/**
* Object that contains the methods and properties for the calendar day headings
*/
function CalHeading(owner,tableCell,dayOfWeek) {
   //-----------------------------------------------------------------------------
   function DayHeadingonclick() {//selects/deselects the days for this object's day of week
      //reduce indirection:
      var sdates = owner.dates;
      var cells = owner.cells;
      var dateArray = new Array();
      owner.cols[dayOfWeek] = !owner.cols[dayOfWeek];
      for(var i=0;i<cells.length;i++) { //cycle through all the cells in the calendar, selecting all cells with the same dayOfWeek as this heading
         if(cells[i].dayOfWeek == dayOfWeek && cells[i].date.canSelect && (!owner.selCurMonthOnly || cells[i].date.getMonth() == owner.displayMonth && cells[i].date.getFullYear() == owner.displayYear)) { //if the cell's DoW matches, with other conditions
            dateArray[dateArray.length] = cells[i].date;
         }
      }
      owner.selectDates(dateArray,owner.cols[dayOfWeek],true);
   }
   //-----------------------------------------------------------------------------
   var self = this;
   self.dayOfWeek = dayOfWeek;
   addEventHandler(tableCell,'mouseup',DayHeadingonclick);
}
/*****************************************************************************/
/**
* Object that contains the methods and properties for the calendar week headings
*/
function WeekHeading(owner,tableCell,week,tableRow) {
   //-----------------------------------------------------------------------------
   function weekHeadingonclick() {
      //reduce indirection:
      var cells = owner.cells;
      var sdates = owner.dates;
      var dateArray = new Array();
      owner.rows[tableRow] = !owner.rows[tableRow];
      for(var i=0;i<cells.length;i++) {
         if(cells[i].tableRow == tableRow && cells[i].date.canSelect && (!owner.selCurMonthOnly || cells[i].date.getMonth() == owner.displayMonth && cells[i].date.getFullYear() == owner.displayYear)) { //if the cell's DoW matches, with other conditions)
            dateArray[dateArray.length] = cells[i].date;
         }
      }
      owner.selectDates(dateArray,owner.rows[tableRow],true);
   }
   //-----------------------------------------------------------------------------
   var self = this;
   self.week = week;
   tableCell.setAttribute('class','wkhead');
   tableCell.setAttribute('className','wkhead'); //<iehack>
   addEventHandler(tableCell,'mouseup',weekHeadingonclick);
}
/*****************************************************************************/
/**
* Object that holds all data & code related to a calendar cell
*/
/**
 * The CalCell constructor function
 * @param Epoch owner
 * @param HTMLTableCellElement tableCell
 * @param Date dateObj
 * @param int row
 * @param int week
 */
function CalCell(owner,tableCell,dateObj,row,week) {
   var self = this;
   //-----------------------------------------------------------------------------
   function calCellonclick() {
      if(self.date.canSelect) {
         if(owner.selectMultiple == true) { //if we can select multiple cells simultaneously, add the currently selected self's date to the dates array
            owner.selectDates(new Array(self.date),!self.date.selected,false);
            self.setClass(); //update the current cell's style to reflect the changes - a full redraw isn't necessary
         }
         else { //if we can only select one date at a time
            owner.selectDates(new Array(self.date),true,false,true);
            if(owner.mode == 'popup') { //update the target element's value and hide the calendar if in popup mode
               owner.tgt.value = self.date.dateFormat('Y-m-d'); //use the default date format defined in dateFormat
               owner.tgt.dateObj = new Date(self.date); //add a Date object to the target element for later reference
               owner.hide();
            }
            owner.reDraw(); //redraw all the calendar cells
         }
      }
   }
   //-----------------------------------------------------------------------------
   /**
   * Replicate the CSS :hover effect for non-supporting browsers <iehack>
   */
   function calCellonmouseover() {
      if(self.date.canSelect) {
         tableCell.setAttribute('class',self.cellClass + ' hover');
         tableCell.setAttribute('className',self.cellClass + ' hover');
      }
   }
   //-----------------------------------------------------------------------------
   /**
   * Replicate the CSS :hover effect for non-supporting browsers <iehack>
   */
   function calCellonmouseout() {
      self.setClass();
   }
   //-----------------------------------------------------------------------------
   /**
   * Sets the CSS class of the cell based on the specified criteria
   */
   self.setClass = function ()
   {
      if(self.date.canSelect !== false) {
         if(self.date.selected) {
            self.cellClass = 'cell_selected';
         }
         else if(owner.displayMonth != self.date.getMonth() ) {
            self.cellClass = 'notmnth';
         }
         else if(self.date.type == 'holiday') {
            self.cellClass = 'hlday';
         }
         else if(self.dayOfWeek > 0 && self.dayOfWeek < 6) {
            self.cellClass = 'wkday';
         }
         else {
            self.cellClass = 'wkend';
         }
      }
      else {
         self.cellClass = 'noselect';
      }
      //highlight the current date
      if(self.date.getUeDay() == owner.curDate.getUeDay()) {
         self.cellClass = self.cellClass + ' curdate';
      }
      tableCell.setAttribute('class',self.cellClass);
      tableCell.setAttribute('className',self.cellClass); //<iehack>
   };
   //-----------------------------------------------------------------------------
   /**
   * Sets the cell's hyperlink, if declared
   * @param string href
   * @param string type ('anchor' or 'js' - default 'anchor')
   */
   self.setURL = function(href,type) {
      if(href) {
         if(type == 'js') { //Make the WHOLE cell be a clickable link
            addEventHandler(self.tableCell,'mousedown',function(){window.location.href = href;});
         }
         else { //make only the date number of the cell a clickable link:
            var url = document.createElement('a');
            url.setAttribute('href',href);
            url.appendChild(document.createTextNode(self.date.getDate()));
            self.tableCell.replaceChild(url,self.tableCell.firstChild); //assumes the first child of the cell DOM node is the date text
         }
      }
   };
   //-----------------------------------------------------------------------------
   /**
   * Sets the title (i.e. tooltip) that appears when a user holds their mouse cursor over a cell
   * @param string titleStr
   */
   self.setTitle = function(titleStr) {
      if(titleStr && titleStr.length > 0) {
         self.title = titleStr;
         self.tableCell.setAttribute('title',titleStr);
      }
   };
   //-----------------------------------------------------------------------------
   /**
   * Sets the internal html of the cell, using a string containing html markup
   * @param string html
   */
   self.setHTML = function(html) {
      if(html && html.length > 0) {
         if(self.tableCell.childNodes[1]) {
            self.tableCell.childNodes[1].innerHTML = html;
         }
         else {
            var htmlCont = document.createElement('div');
            htmlCont.innerHTML = html;
            self.tableCell.appendChild(htmlCont);
         }
      }
   };
   //-----------------------------------------------------------------------------
   self.cellClass;         //the CSS class of the cell
   self.tableRow = row;
   self.tableCell = tableCell;
   self.date = new Date(dateObj);
   self.date.canSelect = true; //whether this cell can be selected or not - always true unless set otherwise externally
   self.date.type = 'normal';  //i.e. normal date, holiday, etc - always true unless set otherwise externally
   self.date.selected = false;   //whether the cell is selected (and is therefore stored in the owner's dates array)
   self.date.cellHTML = '';
   self.dayOfWeek = self.date.getDay();
   self.week = week;
   //assign the event handlers for the table cell element
   addEventHandler(tableCell,'click', calCellonclick);
   addEventHandler(tableCell,'mouseover', calCellonmouseover);
   addEventHandler(tableCell,'mouseout', calCellonmouseout);
   self.setClass();
}
/*****************************************************************************/
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);
};
//-----------------------------------------------------------------------------
/**
 * Returns the week number for this date.  dowOffset is the day of week the week
 * "starts" on for your locale - it can be from 0 to 6. If dowOffset is 1 (Monday),
 * the week returned is the ISO 8601 week number.
 * @param int dowOffset
 * @return int
 */
Date.prototype.getWeek = function (dowOffset) {
   dowOffset = typeof(dowOffset) == 'int' ? dowOffset : 0; //default dowOffset to zero
   var newYear = new Date(this.getFullYear(),0,1);
   var day = newYear.getDay() - dowOffset; //the day of week the year begins on
   day = (day >= 0 ? day : day + 7);
   var weeknum, daynum = Math.floor((this.getTime() - newYear.getTime() - (this.getTimezoneOffset()-newYear.getTimezoneOffset())*60000)/86400000) + 1;
   //if the year starts before the middle of a week
   if(day < 4) {
      weeknum = Math.floor((daynum+day-1)/7) + 1;
      if(weeknum > 52) {
         nYear = new Date(this.getFullYear() + 1,0,1);
         nday = nYear.getDay() - dowOffset;
         nday = nday >= 0 ? nday : nday + 7;
         weeknum = nday < 4 ? 1 : 53; //if the next year starts before the middle of the week, it is week #1 of that year
      }
   }
   else {
      weeknum = Math.floor((daynum+day-1)/7);
   }
   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 = 'm/d/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');
   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();
   value = {
      Y: y.toString(),
      y: y.substring(2),
      n: M,
      m: LZ(M),
      F: MONTH_NAMES[M-1],
      M: MONTH_NAMES[M+11],
      j: d,
      d: LZ(d),
      D: DAY_NAMES[E+7],
      l: DAY_NAMES[E],
      G: H,
      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;
};
/*****************************************************************************/
//-----------------------------------------------------------------------------
function addEventHandler(element, type, func) { //unfortunate hack to deal with Internet Explorer's horrible DOM event model <iehack>
   if(element.addEventListener) {
      element.addEventListener(type,func,false);
   }
   else if (element.attachEvent) {
      element.attachEvent('on'+type,func);
   }
}
//-----------------------------------------------------------------------------
function removeEventHandler(element, type, func) { //unfortunate hack to deal with Internet Explorer's horrible DOM event model <iehack>
   if(element.removeEventListener) {
      element.removeEventListener(type,func,false);
   }
   else if (element.attachEvent) {
      element.detachEvent('on'+type,func);
   }
}
//-----------------------------------------------------------------------------
function getTop(element) {//returns the absolute Top value of element, in pixels
   var oNode = element;
   var iTop = 0;

   while(oNode.tagName != 'HTML') {
      iTop += oNode.offsetTop || 0;
      if(oNode.offsetParent) { //i.e. the parent element is not hidden
         oNode = oNode.offsetParent;
      }
      else {
         break;
      }
   }
   return iTop;
}
//-----------------------------------------------------------------------------
function getLeft(element) { //returns the absolute Left value of element, in pixels
   var oNode = element;
   var iLeft = 0;
   while(oNode.tagName != 'HTML') {
      iLeft += oNode.offsetLeft || 0;
      if(oNode.offsetParent) { //i.e. the parent element is not hidden
         oNode = oNode.offsetParent;
      }
      else {
         break;
      }
   }
   return iLeft;
}
//-----------------------------------------------------------------------------

