/*********************************************************
 *	File:		  pulldowncalendar.js
 *	Author:		MetaDesign
 *	Created:	March 7, 2007
 *
 *	Description:
 *	Javascript class for pulldown calendar control
 ********************************************************/
 
/*--------------------------------------------------------------
 * Class: PulldownCalendar
 *
 * Javascript class for managing a calendar date with day, month,
 * and year pulldowns.  Validates the date against a minimum and
 * maximum value.  Changes pulldown values based on available
 * days and months.
 *------------------------------------------------------------*/
var PulldownCalendar = Class.create();
PulldownCalendar.prototype = {

  /*--------------------------------------------------------------
   * Method: initialize
   *
   * Establishes pulldown controls, minimum and maximum dates.
   * Assigns event handlers for the pulldowns.  Retrieves the
   * current date in the controls and validates it.
   *
   * Parameters:
   * pDayID       string    ID reference to day pulldown
   * pMonthID     string    ID reference to month pulldown
   * pYearID      string    ID reference to year pulldown
   * pMinDateStr  string    String representation of minimum date
   * pMaxDateStr  string    String representation of maximum date
   * pAllowEmpty  boolean   Whether the controls allow empty
   * pEmptyDayString    string   String to display for empty day pulldown
   * pEmptyMonthString  string   String to display for empty month pulldown
   * pEmptyYearString   string   String to display for empty year pulldown
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  initialize: function(pDayID, pMonthID, pYearID, pMinDateStr, pMaxDateStr, pAllowEmpty, pEmptyDayString, pEmptyMonthString, pEmptyYearString) {
    // Grab references to day, month, and year pulldowns.
    this.dayPulldown = $(pDayID);
    this.monthPulldown = $(pMonthID);
    this.yearPulldown = $(pYearID);
    
    // Make sure we have valid controls.
    if (!this.dayPulldown || !this.monthPulldown || !this.yearPulldown) {
      this.valid = false;
      return;
    }
    
    this.valid = true;
    
    // Set the minimum and maximum dates.
    this.minDate = new Date(pMinDateStr);
    this.maxDate = new Date(pMaxDateStr);
    
    // Set allow empty flag & string
    this.allowEmpty = pAllowEmpty;
    this.emptyDayString = pEmptyDayString;
    this.emptyMonthString = pEmptyMonthString;
    this.emptyYearString = pEmptyYearString;
    
    // Attach event handlers to each pulldown.
    this.dayPulldown.onchange = this.dayChangeHandler.bindAsEventListener(this);    
    this.monthPulldown.onchange = this.monthChangeHandler.bindAsEventListener(this);
    this.yearPulldown.onchange = this.yearChangeHandler.bindAsEventListener(this);
    
    // Save the date currently in the pulldowns and validate it.
    if (!this.allowEmpty)
    {
      this.determineDate();
    }
    
    // Reload the controls to represent the eligible days and months.
    /*
    IE 6 renders slowly.
    Use conditional compilation (http://www.javascriptkit.com/javatutors/conditionalcompile.shtml)
    to reload the controls asynchronously using setTimeout.
    The page will display without waiting for the controls to load.
    IE7 (JScript version 5.7) and other browsers render quickly, so reload controls as the page loads.
    */
    if (!this.allowEmpty)
    {
    /*@cc_on
      @if (@_jscript_version < 5.7)
        setTimeout(function() {
          this.reloadDays();
          this.reloadMonths();
        }.bind(this), 0);
      @else @*/
        this.reloadDays();
        this.reloadMonths();
      /*@end
    @*/
    }
  },
  
  /*--------------------------------------------------------------
   * Method: getElement
   *
   * Returns a reference to the DOM element containing the
   * PulldownCalendar.
   *
   * Parameters:
   * None
   *
   * Return:
   * object   The DOM element containing the calendar
   *------------------------------------------------------------*/
  getElement: function() {
    return this.dayPulldown.parentNode;
  },
  
  /*--------------------------------------------------------------
   * Method: determineDate
   *
   * Determines the date by inspecting the pulldown controls.
   * Validates the date and stores it locally.
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  determineDate: function() {
    // Determine the day, month, year in the pulldowns
    var day = this.dayPulldown.options[this.dayPulldown.selectedIndex].value;
    // Month value is 1 to 12.  Convert to zero index.
    var month = this.monthPulldown.options[this.monthPulldown.selectedIndex].value - 1;
    var year = this.yearPulldown.options[this.yearPulldown.selectedIndex].value;
    
    // Make sure we have a valid date.  For example, convert Nov 31 to Nov 30.
    var daysInMonth = (new Date(year, month, 1)).getDaysInMonth();
    day = Math.min(day, daysInMonth);
    
    // Save the date and validate it.
    this.date = new Date(year, month, day);
    if (day != 0 && month != 0 && year != 0) {        
        this.validateDate();
    }
  },
  
  /*--------------------------------------------------------------
   * Method: validateDate
   *
   * Ensure the date falls in between the minimum and maximum dates.
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  validateDate: function() {
    if (this.date - this.minDate < 0) {
      this.date = this.minDate;
    }
    
    if (this.maxDate - this.date < 0) {
      this.date = this.maxDate;
    }
  },
  
  /*--------------------------------------------------------------
   * Method: setDate
   *
   * Sets the date to pDate, validates it and reloads the controls
   * to reflect the new date.
   *
   * Parameters:
   * pDate    date    The date to set
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  setDate: function(pDate) {
    this.date = pDate;
    this.validateDate();
    
    this.reloadDays();
    this.reloadMonths();
    this.reloadYears();
  },
  
  /*--------------------------------------------------------------
   * Method: dayChangeHandler
   *
   * Event handler for a change in the day pulldown.
   *
   * Parameters:
   * pEvt   event   The onchange event
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  dayChangeHandler: function(pEvt) {
    // Get the new date.
    this.determineDate();

    // Propagate the event
    if (this.ondatechange) {
      this.ondatechange(pEvt, this, this.date);
    }
  },
  
  /*--------------------------------------------------------------
   * Method: monthChangeHandler
   *
   * Event handler for a change in the month pulldown.
   *
   * Parameters:
   * pEvt   event   The onchange event
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  monthChangeHandler: function(pEvt) {
    // Get the new date and reload the controls.
    this.determineDate();
    this.reloadDays();

    // Propagate the event
    if (this.ondatechange) {
      this.ondatechange(pEvt, this, this.date);
    }
  },
  
  /*--------------------------------------------------------------
   * Method: yearChangeHandler
   *
   * Event handler for a change in the year pulldown.
   *
   * Parameters:
   * pEvt   event   The onchange event
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  yearChangeHandler: function(pEvt) {
    // Get the new date and reload the controls.
    this.determineDate();
    this.reloadDays();
    this.reloadMonths();

    // Propagate the event
    if (this.ondatechange) {
      this.ondatechange(pEvt, this, this.date);
    }
  },
  
  /*--------------------------------------------------------------
   * Method: reloadDays
   *
   * Reloads the day pulldown.
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  reloadDays: function() {
    // Clear out the options and start from scratch
    this.clearOptions(this.dayPulldown);
    
    // Determine starting day based on minimum date
    var startDay = (Date.isSameMonthYear(this.date, this.minDate))
      ? this.minDate.getDate()
      : 1;

    // Determine ending day based on maximum date 
    var endDay = (Date.isSameMonthYear(this.date, this.maxDate))
      ? this.maxDate.getDate()
      : this.date.getDaysInMonth();

    // Add empty item
    if (this.allowEmpty)
    {
      var optionEmpty = document.createElement("option");
      optionEmpty.value = "";
      optionEmpty.innerHTML = this.emptyDayString;
      this.dayPulldown.appendChild(optionEmpty);
    }

    // Reload the day pulldown options
    for (var i = startDay; i <= endDay; i++) {
      var option = document.createElement("option");
      option.value = i;
      option.innerHTML = i;
      if (this.date.getDate() == i) {
        option.selected = true;
      }
      this.dayPulldown.appendChild(option);
    }
  },
  
  /*--------------------------------------------------------------
   * Method: reloadMonths
   *
   * Reloads the month pulldown.
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  reloadMonths: function() {
    // Clear out the options and start from scratch
    this.clearOptions(this.monthPulldown);
    
    // Determine starting month based on minimum date
    var startMonth = (this.date.getFullYear() == this.minDate.getFullYear())
      ? this.minDate.getMonth()
      : 0;
      
    // Determine ending month based on maximum date
    var endMonth = (this.date.getFullYear() == this.maxDate.getFullYear())
      ? this.maxDate.getMonth()
      : 11;
      
    // Add empty item
    if (this.allowEmpty)
    {
      var optionEmpty = document.createElement("option");
      optionEmpty.value = "";
      optionEmpty.innerHTML = this.emptyMonthString;
      this.monthPulldown.appendChild(optionEmpty);
    }

    // Reload the month pulldown options
    for (var i = startMonth; i <= endMonth; i++) {
      var option = document.createElement("option");
      option.value = i + 1;
      option.innerHTML = Date.getMonthAbbreviation(i);
      if (this.date.getMonth() == i) {
        option.selected = true;
      }
      this.monthPulldown.appendChild(option);
    }
  },
  
  /*--------------------------------------------------------------
   * Method: reloadYears
   *
   * Reloads the year pulldown.
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  reloadYears: function() {
    // Clear out the options and start from scratch
    this.clearOptions(this.yearPulldown);
    
    // Determine starting year based on minimum date
    var startYear = this.minDate.getFullYear();
    // Determine ending year based on maximum date
    var endYear = this.maxDate.getFullYear();

    // Add empty item
    if (this.allowEmpty)
    {
      var optionEmpty = document.createElement("option");
      optionEmpty.value = "";
      optionEmpty.innerHTML = this.emptyYearString;
      this.yearPulldown.appendChild(optionEmpty);
    }

    // Reload the year pulldown options
    for (var i = startYear; i <= endYear; i++) {
      var option = document.createElement("option");
      option.value = i;
      option.innerHTML = i;
      if (this.date.getFullYear() == i) {
        option.selected = true;
      }
      this.yearPulldown.appendChild(option);
    }
  },
  
  // TODO:  Enhancement - Change to reset only the ones that change to avoid flicker in IE
  /*--------------------------------------------------------------
   * Method: clearOptions
   *
   * Clears all options from a pulldown
   *
   * Parameters:
   * pPulldown    object    The pulldown to clear options
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  clearOptions: function(pPulldown) {
    while (pPulldown.firstChild) {
      pPulldown.removeChild(pPulldown.firstChild);
    }
  }
}
