/*********************************************************
 *	File:		  combocalendar.js
 *	Author:		MetaDesign
 *	Created:	March 7, 2007
 *
 *	Description:
 *	Javascript class for combo calendar control
 ********************************************************/

/*--------------------------------------------------------------
 * Class: ComboCalendar
 *
 * Javascript class for managing a calendar date with a
 * dynamically generated calendar table combined with a 
 * PulldownCalendar.  Validates the date against a minimum and 
 * maximum value.
 *------------------------------------------------------------*/
var ComboCalendar = Class.create();
ComboCalendar.prototype = {

  /*--------------------------------------------------------------
   * Method: initialize
   *
   * Establishes the PulldownCalendar object, minimum and maximum
   * dates.  Assigns an event handler for the PulldownCalendar.
   * Retrieves the current date in the PulldownCalendar and renders
   * calendar table.
   *
   * Parameters:
   * pPulldownCalendar    object    The PulldownCalendar object
   * pMinDateStr          string    String representation of minimum date
   * pMaxDateStr          string    String representation of maximum date
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  initialize: function(pPulldownCalendar, pMinDateStr, pMaxDateStr) {
    if (!pPulldownCalendar.valid) {
      this.valid = false;
      return;
    }
    
    this.valid = true;

    // Setup constants for CSS classes.
    this.CONTAINER_CLASS = "calendar";
    this.CONTROLS_CLASS = "controls";
    this.YEAR_MONTH_CLASS = "yearMonth";
    this.FIELD_CLASS = "dateField";
    this.FIELD_BODY_CLASS = "dateFieldBody";
    this.DAY_HEADER_CLASS = "dayHeader";
    this.DAY_DISPLAY_CLASS = "dayDisplay";
    this.DATE_ROW_CLASS = "dateRow";
    this.CLEAR_CLASS = "clear";
    
    // Number of calendar rows to display.
    this.NUM_ROWS = 6;
    
    // Grab reference to our pulldown and attach an event handler.
    this.pulldownCalendar = pPulldownCalendar;
    this.pulldownCalendar.ondatechange = this.pulldownDateChangeHandler.bind(this);
    
    // Set the minimum and maximum dates.
    this.minDate = new Date(pMinDateStr);
    this.maxDate = new Date(pMaxDateStr);

    // Initialize our selected date to the pulldown date
    // Set the display month to show our selected date
    this.date = this.pulldownCalendar.date;
    this.displayMonth = this.date;
    
    // Render the shell of the calendar table
    this.renderInit();
    
    // Render the current display month
    /*
    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.
    */    
    /*@cc_on
      @if (@_jscript_version < 5.7)
        setTimeout(function() {
          this.render();
        }.bind(this), 0);
      @else @*/
        this.render();
      /*@end
    @*/
  },
  
  /*--------------------------------------------------------------
   * Method: getElement
   *
   * Returns a reference to the DOM element containing the
   * ComboCalendar.
   *
   * Parameters:
   * None
   *
   * Return:
   * object   The DOM element containing the calendar
   *------------------------------------------------------------*/
  getElement: function() {
    return this.pulldownCalendar.getElement();
  },
  
  /*--------------------------------------------------------------
   * 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();
    // Display the month for the newly selected date.
    this.displayMonth = this.date;
    this.render();
    // Sync the PulldownCalendar
    this.pulldownCalendar.setDate(this.date);
  },
  
  /*--------------------------------------------------------------
   * Method: pulldownDateChangeHandler
   *
   * Event handler for a change in the PulldownCalendar date.
   *
   * Parameters:
   * pEvt                 event     The ondatechange event
   * pPulldownCalendar    object    The PulldownCalendar
   * pDate                date      The new date
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  pulldownDateChangeHandler: function(pEvt, pPulldownCalendar, pDate) {
    // Set the new selected date.
    this.date = pDate;
    // Validate the date.
    this.validateDate();
    // Display the month for the newly selected date.
    this.displayMonth = this.date;
    this.render();
    
    // Propagate the event
    if (this.ondatechange) {
      this.ondatechange(pEvt, this, this.date);
    }
  },
  
  /*--------------------------------------------------------------
   * Method: prevMonthHandler
   *
   * Event handler for previous month button click.
   *
   * Parameters:
   * pEvt    event     The onclick event
   * pBtn    object    The button that was clicked
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  prevMonthHandler: function(pEvt, pBtn) {
    // Display the previous month
    this.displayMonth = this.displayMonth.getPrevMonth();
    this.render();
  },
  
  /*--------------------------------------------------------------
   * Method: nextMonthHandler
   *
   * Event handler for next month button click.
   *
   * Parameters:
   * pEvt    event     The onclick event
   * pBtn    object    The button that was clicked
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  nextMonthHandler: function(pEvt, pBtn) {
    // Display the next month
    this.displayMonth = this.displayMonth.getNextMonth();
    this.render();
  },
  
  /*--------------------------------------------------------------
   * Method: dateClickHandler
   *
   * Event handler for CalendarCell click.
   *
   * Parameters:
   * pEvt     event     The onclick event
   * pCell    object    The CalendarCell
   * pDate    date      The new date
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  dateClickHandler: function(pEvt, pCell, pDate) {
    // Set the new selected date.
    this.date = pDate;
    // Validate the date.
    this.validateDate();
    // Display the month for the newly selected date.
    this.displayMonth = this.date;
    this.render();

    // Sync the PulldownCalendar
    this.pulldownCalendar.setDate(this.date);
    
    // Propagate the event
    if (this.ondatechange) {
      this.ondatechange(pEvt, this, this.date);
    }
  },

  /*--------------------------------------------------------------
   * Method: renderInit
   *
   * Renders the shell for the calendar table.
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  renderInit: function() {
    // Create the container for the calendar
    this.container = document.createElement("div");
    Element.addClassName(this.container, this.CONTAINER_CLASS);
    
    // Create the control area and add it to the container
    var controls = document.createElement("div");
    Element.addClassName(controls, this.CONTROLS_CLASS);
    this.container.appendChild(controls);
    
    // Create the month display and add it to the controls
    this.yearMonthDisplay = document.createElement("div");
    Element.addClassName(this.yearMonthDisplay, this.YEAR_MONTH_CLASS);
    controls.appendChild(this.yearMonthDisplay);
    
    // Create the previous month button, assign an event handler,
    // and add it to the controls
    this.prevMonthBtn = new CalendarButton("<img src='../images/icon_arrow_lt_hotel.gif' class='calimg'>");
    this.prevMonthBtn.onbuttonclick = this.prevMonthHandler.bind(this);
    controls.appendChild(this.prevMonthBtn.getElement());
    
    // Create the next month button, assign an event handler,
    // and add it to the controls
    this.nextMonthBtn = new CalendarButton("<img src='../images/icon_arrow_rt_hotel.gif' class='calimg'>");
    this.nextMonthBtn.onbuttonclick = this.nextMonthHandler.bind(this);
    controls.appendChild(this.nextMonthBtn.getElement());
    
    // The controls are floated, so clear the floats
    var clear = document.createElement("div");
    Element.addClassName(clear, this.CLEAR_CLASS);
    controls.appendChild(clear);
    
    // Create the field for the calendar table and add it to the container
    var calendarField = document.createElement("div");
    Element.addClassName(calendarField, this.FIELD_CLASS);
    this.container.appendChild(calendarField);
    
    // Create the calendar table and tbody
    var calendarFieldTable = document.createElement("table");
    Element.addClassName(calendarFieldTable, this.FIELD_BODY_CLASS);
    
    var calendarFieldBody = document.createElement("tbody");
    
    // Add the calendar table to the field
    calendarFieldTable.appendChild(calendarFieldBody);
    calendarField.appendChild(calendarFieldTable);
    
    // Create the days header
    var dayHeader = document.createElement("tr");
    Element.addClassName(dayHeader, this.DAY_HEADER_CLASS);
    
    // Add each day header
    var days = Date.getDayAbbreviations();
    for (var i = 0; i < days.length; i++) {
      var dayDisplay = document.createElement("td");
      Element.addClassName(dayDisplay, this.DAY_DISPLAY_CLASS);
      dayDisplay.innerHTML = days[i];
      dayHeader.appendChild(dayDisplay);
    }
    calendarFieldBody.appendChild(dayHeader);
    
    // Property to track the CalendarCells
    // Initialize the first dimension
    this.cells = new Array();
    
    // Create CalendarCell objects for each cell in the
    // calendar table
    for (var i = 0; i < this.NUM_ROWS; i++) {
      // Create a row for each week
      var dateRow = document.createElement("tr");
      Element.addClassName(dateRow, this.DATE_ROW_CLASS);
      
      // Initialize the second dimension
      this.cells[i] = new Array();
      
      // Iterate over the days in a week
      for (var j = 0; j < days.length; j++) {
        // Create a new CalendarCell with the day of the week initialized
        var calCell = new CalendarCell(j);
        // Bind the event handler
        calCell.oncellclick = this.dateClickHandler.bind(this);
        // Add the CalendarCell element to the table row
        dateRow.appendChild(calCell.getElement());
        // Save our CalendarCells so we can reference them later
        this.cells[i][j] = calCell;
      }
      // Add the week row to the calendar table
      calendarFieldBody.appendChild(dateRow);
    }
    
    // Add the container to the page
    this.pulldownCalendar.getElement().appendChild(this.container);
  },
  
  /*--------------------------------------------------------------
   * Method: render
   *
   * Renders the calendar data in the calendar table.
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  render: function() {
    // Set the month and year display
    this.yearMonthDisplay.innerHTML = this.displayMonth.getMonthName() + " " + this.displayMonth.getFullYear();
    
    // Disable the previous month button if we're at the minimum date month
    if (Date.isSameMonthYear(this.displayMonth, this.minDate)) {
      this.prevMonthBtn.disable();
    } else {
      this.prevMonthBtn.enable();
    }
    
    // Disable the next month button if we're at the maximum date month
    if (Date.isSameMonthYear(this.displayMonth, this.maxDate)) {
      this.nextMonthBtn.disable();
    } else {
      this.nextMonthBtn.enable();
    }
    
    // Variable to track the date we're currently rendering
    var dateCount = 1;
    // Determine the total number of dates to display in the month
    var daysInMonth = this.displayMonth.getDaysInMonth();
    
    // Date representation of the date we're currently rendering
    var currDate = new Date(this.displayMonth.getFullYear(), this.displayMonth.getMonth(), dateCount);
    
    // Loop through each week row in the calendar table
    for (var i = 0; i < this.NUM_ROWS; i++) {
      // Loop through each day cell in the calendar table
      for (var j = 0; j < this.cells[i].length; j++) {
        // Grab a handle to the CalendarCell for this week/day
        var currCell = this.cells[i][j];
        
        // If we're still tracking dates and the CalendarCell day matches the current date's day...
        if (currDate && (currCell.day == currDate.getDay())) {
          // Set the date for the CalendarCell
          currCell.setDate(currDate);
          
          // Disable the CalendarCell if we're below the minimum date or above the maximum date
          if ((currDate - this.minDate < 0) || (currDate - this.maxDate > 0)) {
            currCell.disable();
            // We shouldn't be selected if we're disabled
            currCell.deselect();
          } else {
            currCell.enable();
            // Select the CalendarCell if matches the selected date.
            if (currDate - this.date == 0) {
              currCell.select();
            } else {
              currCell.deselect();
            }
          }
          
          // Iterate the date count and check to see if we're past the end of the month
          if (++dateCount > daysInMonth) {
            // Stop rendering.  All remaining cells should be cleared
            currDate = null;
          } else {
            // Still tracking, update the current date
            currDate = new Date(this.displayMonth.getFullYear(), this.displayMonth.getMonth(), dateCount);
          }
        } else {
          // Clear out any CalendarCells before or after the dates of the month
          currCell.clearDate();
        }
      }
    }
  }
}

/*--------------------------------------------------------------
 * Class: CalendarButton
 *
 * Javascript class for a calendar button.
 *------------------------------------------------------------*/
var CalendarButton = Class.create();
CalendarButton.prototype = {

  /*--------------------------------------------------------------
   * Method: initialize
   *
   * Creates the calendar button.  Assigns an event handler for the
   * button click.
   *
   * Parameters:
   * pName    string    Display name of the button
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  initialize: function(pName) {
    // Setup constants for CSS classes.
    this.DISABLED_CLASS = "buttonDisabled";
    this.BUTTON_CLASS = "button";
    this.LINK_CLASS = "buttonLink";
    
    // Create the button container
    this.button = document.createElement("div");
    Element.addClassName(this.button, this.BUTTON_CLASS);

    // Add a link to the button so we can have hover states.
    this.buttonLink = document.createElement("a");
    Element.addClassName(this.buttonLink, this.LINK_CLASS);

    // IE requires the href to be set to have hover and click
    // functionality
    this.buttonLink.href = "#";
    
    // Attach the event handler, set the name and add the link to the button
    this.buttonLink.onclick = this.buttonClickHandler.bindAsEventListener(this);
    this.buttonLink.innerHTML = pName;
    this.button.appendChild(this.buttonLink);
  },
  
  /*--------------------------------------------------------------
   * Method: getElement
   *
   * Returns a reference to the button DOM element.
   *
   * Parameters:
   * None
   *
   * Return:
   * object   The button DOM element
   *------------------------------------------------------------*/
  getElement: function() {
    return this.button;
  },
  
  /*--------------------------------------------------------------
   * Method: buttonClickHandler
   *
   * Event handler for a button click.
   *
   * Parameters:
   * pEvt   event   The onclick event
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  buttonClickHandler: function(pEvt) {
    if (this.enabled) {
      // Propagate the event
      if (this.onbuttonclick) {
        this.onbuttonclick(pEvt, this);
      }
    }
    // Cancel default click on href.
    Event.stop(pEvt);
  },
  
  /*--------------------------------------------------------------
   * Method: enable
   *
   * Enables the button
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  enable: function() {
    this.enabled = true;
    // Adjust the classname to show the button enabled
    Element.removeClassName(this.button, this.DISABLED_CLASS);
  },
  
  /*--------------------------------------------------------------
   * Method: disable
   *
   * Disables the button
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  disable: function() {
    this.enabled = false;
    // Adjust the classname to show the button disabled
    Element.addClassName(this.button, this.DISABLED_CLASS);
  }
}

/*--------------------------------------------------------------
 * Class: CalendarCell
 *
 * Javascript class for a calendar cell.
 *------------------------------------------------------------*/
var CalendarCell = Class.create();
CalendarCell.prototype = {

  /*--------------------------------------------------------------
   * Method: initialize
   *
   * Creates the calendar cell.  Assigns an event handler for the
   * cell click.
   *
   * Parameters:
   * pDay   integer   The day of the week the cell represents
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  initialize: function(pDay) {
    // Setup constants for CSS classes.
    this.DATE_CLASS = "date";
    this.SELECTED_DATE_CLASS = "dateSelected";
    this.TODAY_CLASS = "dateToday";
    this.DISABLED_CLASS = "dateDisabled";
    this.LINK_CLASS = "dateLink";

    // Save the day
    this.day = pDay;
    
    // Create the cell
    this.cell = document.createElement("td");

    // Add a link to the cell so we can have hover states.
    this.cellLink = document.createElement("a");
    Element.addClassName(this.cellLink, this.LINK_CLASS);
    
    // IE requires the href to be set to have hover and click
    // functionality
    this.cellLink.href = "#";

    // Attach the event handler, start with the cell cleared, and add the link to the cell
    this.cellLink.onclick = this.cellClickHandler.bindAsEventListener(this);
    this.cellLink.innerHTML = "&nbsp;";
    this.cell.appendChild(this.cellLink);

    // Start with the cell deselected
    this.deselect();
  },
  
  /*--------------------------------------------------------------
   * Method: getElement
   *
   * Returns a reference to the cell DOM element.
   *
   * Parameters:
   * None
   *
   * Return:
   * object   The cell DOM element
   *------------------------------------------------------------*/
  getElement: function() {
    return this.cell;
  },
  
  /*--------------------------------------------------------------
   * Method: cellClickHandler
   *
   * Event handler for a cell click.
   *
   * Parameters:
   * pEvt   event   The onclick event
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  cellClickHandler: function(pEvt) {
    if (this.enabled) {
      // Propagate the event
      if (this.oncellclick) {
        this.oncellclick(pEvt, this, this.date);
      }
    }
    // Cancel default click on href.
    Event.stop(pEvt);
  },
  
  /*--------------------------------------------------------------
   * Method: select
   *
   * Selects the cell
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  select: function() {
    this.selected = true;
    // Change to the selected classname
    Element.removeClassName(this.cell, this.DATE_CLASS);
    Element.addClassName(this.cell, this.SELECTED_DATE_CLASS);
  },
  
  /*--------------------------------------------------------------
   * Method: deselect
   *
   * Deselects the cell
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  deselect: function() {
    this.selected = false;
    // Change to the deselected classname
    Element.removeClassName(this.cell, this.SELECTED_DATE_CLASS);
    Element.addClassName(this.cell, this.DATE_CLASS);
  },
  
  /*--------------------------------------------------------------
   * Method: enable
   *
   * Enables the cell
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  enable: function() {
    this.enabled = true;
    // Adjust the classname to show the cell enabled
    Element.removeClassName(this.cell, this.DISABLED_CLASS);
  },
  
  /*--------------------------------------------------------------
   * Method: disable
   *
   * Disables the cell
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  disable: function() {
    this.enabled = false;
    // Adjust the classname to show the cell disabled
    Element.addClassName(this.cell, this.DISABLED_CLASS);
  },
  
  /*--------------------------------------------------------------
   * Method: setDate
   *
   * Sets the date for the cell
   *
   * Parameters:
   * pDate    date    The date to set
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  setDate: function(pDate) {
    this.date = pDate;
    // Display the proper date.
    this.cellLink.innerHTML = this.date.getDate();
    
    // If the date is today, adjust the classname to indicate so
    var now = new Date();
    // We just want the date portion, not the time.
    now = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    if (this.date - now == 0) {
      Element.addClassName(this.cell, this.TODAY_CLASS);
    } else {
      Element.removeClassName(this.cell, this.TODAY_CLASS);
    }
  },
  
  /*--------------------------------------------------------------
   * Method: clearDate
   *
   * Clears the cell to have no date
   *
   * Parameters:
   * None
   *
   * Return:
   * None
   *------------------------------------------------------------*/
  clearDate: function() {
    this.date = null;
    this.cellLink.innerHTML = "&nbsp;";
    this.disable();
    this.deselect();
    Element.removeClassName(this.cell, this.TODAY_CLASS);
  }
}