﻿
addLoadEvent(function() {
  $form.init();
});

var $form = {
    dateMin: -2208967200000, //minimum date value for sql server
    dateMax: 3453253200000, //maximum "
    lists: new Array(),  //array of expandable list objects
    hiddens: new Array(), //array of hideble sections

    //instantiate the form object
    init: function(savePrompt) {
        this.inputs.init();
        this.section.init()
        this.initMasks(document);
    },

    section: {
        elements: new Array(),
        anchors: new Array(),
        next: null,

        //add the form sections to the side nav bar if any exist
        init: function() {
            var divs = document.getElementsByTagName("div")
            for (var i = 0; i < divs.length; i++) {
                if (divs[i].className == "section") {
                    $form.section.elements.push(divs[i]);
                }
            }

            if ($("formnav") != null) {
                var anchors = $("formnav").getElementsByTagName("a");
                for (var i = 0; i < anchors.length; i++) {
                    $form.section.anchors.push(anchors[i]);
                }

                $form.section.setTo(this.elements[0].id);

            } else {
                if ($("saveContinue"))
                    $("saveContinue").remove();
            }
        },

        //changes the section when a nav link is clicked
        setTo: function(name) {
            if (name == null) { return false; }

            //hide all the section divs
            $form.section.next = null;
            for (var i = 0; i < $form.section.elements.length; i++) {
                $form.section.elements[i].style.display = "";
                $form.section.anchors[i].className = "";
                if ($form.section.elements[i - 1] == $(name)) {
                    $form.section.next = $form.section.elements[i].id;
                }
            }

            $(name).style.display = "block";
            $(name + "_link").className = "active";

            if ($("submit")) {
                if (this.next == null || wasSubmitted == true) {
                    $("submit").value = "Submit";
                    $("saveContinue").style.display = "none";
                } else {
                    $("submit").value = "Continue";
                    if ($("saveContinue") != null) {                  
                        $("saveContinue").style.display = "";
                    }
                }
            }

            return false;
        }
    },

    /**
    Basic form validation by checking for
    fields with class name set to required

  returns the first invalid field
    */
    basicValidation: function(f) {
        var field = null;
        for (var i = 0; i < f.length; i++) {
            var className = f[i].className;
            //check for empty or invalid required fields
            if (className.match(/required/)) {
                if (f[i].value == "" || (className.match(/email/) && !f[i].value.isEmail())) {
                    if (field == null) field = f[i];
                    this.markField(f[i], true);
                } else {
                    this.markField(f[i], false);
                }
            }
        }

        return field;
    },

    //Mark a form field to draw the user's attention
    markField: function(field, mark) {
        if (mark) {
            field.style.borderColor = "#C00";
            field.onmousedown = function() { this.style.borderColor = "" };
        } else {
            field.style.borderColor = "";
            field.onmousedown = null;
        }
    },

    /*
    Applies an input mask to a field being modified
    
    The check occurs before the form field is changed, so
    over-writing data already entered can mess up the mask.
    
    However, the onchange event can handle this. If the mask
    is not honored it will be forced after the user goes to
    another field
    */
    mask: function(e, field) {
        var evt = (e) ? e : window.event;
        var key = (evt.keyCode) ? evt.keyCode : evt.which;

        if (key != null) {
            if ($form.inputs.isUserFriendlyChar(key)) {
                return true;
            }
            var letter = $form.inputs.isLetter(e, field);
            var number = $form.inputs.isNumeric(e, field);
            var next = field.mask.charAt(field.value.length);

            while (next != "@" && next != "#" && next != "*") {
                field.value += next;
                next = field.mask.charAt(field.value.length);

                if (next == "") {
                    next = field.mask.charAt(field.mask.search(/[@#\*]/));
                    break;
                }
            }

            switch (next) {
                case "*": return letter || number;
                case "@": return letter;
                case "#": return number;
            }
        }

        return false;
    },

    /*
    After the value of a mask field has been changed, force the mask
    to be set rather than trust it just is.
    */
    setMask: function(field, mask) {
        field.mask = mask

        var mask = field.mask.substring(0, field.value.length)
        var exp = SanitizeRegex(mask);
        exp = exp.replace(/@/g, "[A-Za-z]");
        exp = exp.replace(/#/g, "[0-9]");
        exp = exp.replace(/\*/g, "[A-Za-z0-9]");

        if (new RegExp(exp).match(field.value)) {
            return;
        }
        // (###)###-#### 8505012630
        //do not use the variables from above
        //not a match, so rebuild the mask
        var value = field.value.replace(/[^A-Za-z0-9]/g, "");
        field.value = "";
        var i = 0, j = 0;
        while (j < field.mask.length && i < value.length) {
            var next = field.mask.charAt(j++);
            if (next == "@" || next == "#" || next == "*") {
                //check this field matches the given mask character
                //if it doesn't then skip over it
                var isMatch = false;
                if (next == "@") {
                    isMatch = value.charAt(i).match(/[A-Za-z]/) != null;
                } else if (next == "#") {
                    isMatch = value.charAt(i).match(/[0-9]/) != null;
                } else { //it's a *
                    isMatch = value.charAt(i).match(/[A-Za-z0-9]/) != null;
                }
                if (isMatch == true) {
                    field.value += value.charAt(i);
                } else {
                    j--;
                }
                i++;
            } else {
                field.value += next;
            }
        }
    },

    //Set the onchange listener for all mask fields in a given DOM context
    initMasks: function(document) {
        var inputs = document.getElementsByTagName("input");
        var i = 0;
        for (var i in inputs) {
            if (inputs[i].className && inputs[i].className.match("mask")) {
                inputs[i].onchange(inputs[i]);
            }
        }
    },

    /*
    Initializes a growing list in the form
    grabs the template node, stores its innerHTML then removes it
    from the document.
    */
    initList: function(template, token, maxlength, minlength, size, name) {
        //if some how this is a null template, just quit
        //this should never happen.
        if (template == null) return;

        var id = name.replace(/\./g, "_");
        var html = template.innerHTML;

        //remove the template from the document
        template.parentNode.removeChild(template);

        //create a greyed out non-functional item for presentation sake
        //construct the non-functional item inside 'frame'
        var frame = document.createElement("div");
        frame.innerHTML = html.replace(
        new RegExp(token, "g"), "t"
      ).trim();

        //Remove the 'x' button
        var closeBtn = frame.getElementsByTagName("a");
        for (var i = 0; i < closeBtn.length; i++) {
            if (closeBtn[i] && closeBtn[i].className == "close_x") {
                closeBtn[i].parentNode.removeChild(closeBtn[i]);
                break;
            }
        }

        //keep the disabled form fields from posting
        this.disableFields(frame);

        var greyout = document.createElement("div");
        greyout.className = "crossgrey";
        frame = frame.firstChild;
        frame.appendChild(greyout);
        frame.className = "listTemplate";

        frame.onclick = function() {
            $form.addItem(name);
        }

        //create an object to represent this list
        var list = this.lists[id] = this.lists[name] = {
            id: id,
            template: html,
            frame: frame,
            token: token,
            maxlength: maxlength != '' ? maxlength : 20,
            minlength: minlength != '' ? minlength : 0,
            nextid: size,   //item indices must be unique to prevent overlap
            length: size,   //number of items in the list
            addButton: $(id + "_add"),
            xButtons: new Array()
        };

        //nextid explained:
        //say someone adds 3 items to a list. That person deletes the
        //item in the middle of the list and adds another to the top.
        //if we use the length as the id a situation will arise where
        //two items have the same index.

        //make the list meet minimum size requirements by adding empty items
        while (list.length < minlength) {
            this.addItem(name)
        }

        $(id).appendChild(list.frame);
        //hide or show the buttons related to adding and removing list
        //items relative the max, min, and total # of items in the list
        this.listButtonState(list);
    },


    //add an item to a growing list
    addItem: function(name) {
        //get the object representing this list
        var list = this.lists[name];
        //if there's still room in the list add
        if (list.length < list.maxlength) {
            //create a new div, and stick the html in it after its id's/names
            //are changed using a regular expression.
            var newItem = document.createElement("div");
            newItem.innerHTML = list.template.replace(
      new RegExp(list.token, "g"), list.nextid
    ).trim();

            //make the mask inputs inside the new item work
            this.initMasks(newItem);

            //find the remove button
            var closeBtn = newItem.getElementsByTagName("a");
            for (var i = 0; i < closeBtn.length; i++) {
                if (closeBtn[i] && closeBtn[i].className == "close_x") {
                    list.xButtons[list.nextid] = closeBtn[i];
                    break;
                }
            }

            //attach it to the list of xbuttons    
            //append the first child of new item to the list
            //the first child is the HTML that was just manipulated
            $(list.id).appendChild(newItem.firstChild);
            list.length++; list.nextid++;

            //move the frame item to the end of the list
            var tmp = list.frame;
            list.frame = tmp.cloneNode(true);
            tmp.parentNode.removeChild(tmp);
            list.frame.onclick = function() {
                $form.addItem(name);
            };

            $(list.id).appendChild(list.frame);

        }

        this.listButtonState(list);
        return false;
    },


    //Remove an item from a growing list
    removeItem: function(id) {
        //the id of the list item
        var item = $(id);
        //get the list object by the parent of the item's ID
        var list = this.lists[item.parentNode.id];
        //check the list is capable of shrinking (this should always be true)
        if (list.length > list.minlength) {
            list.length--;
            item.parentNode.removeChild(item);
            list.addButton.style.display = "";
        }

        $form.listButtonState(list);
        return false;
    },

    listButtonState: function(list) {
        //hide all the remove buttons
        if (list.length <= list.minlength) {
            $form.toggleListButtons(list, "none");
        } else {
            $form.toggleListButtons(list, "");
        }

        //hide the add button
        if (list.addButton != null) {
            if (list.length >= list.maxlength) {
                list.addButton.style.display = "none";
            } else {
                list.addButton.style.display = "";
            }
        }

        //hide the frame
        if (list.frame != null) {
            if (list.length >= list.maxlength) {
                list.frame.style.display = "none";
            } else {
                list.frame.style.display = "";
            }
        }

    },

    toggleListButtons: function(list, action) {
        for (var i = 0; i < list.nextid; i++) {
            if (list.xButtons[i] != null) {
                list.xButtons[i].style.display = action;
            }
        }
    },

    /*
    sets a hidden block to the opposite of its current
    hidden status.
    
    id: the id of the hidden block
    checkbox: the checkbox being manipulated
    */
    toggleHidden: function(id, checkbox) {
        if ($form.hiddens[id].showing == true) {
            $form.hideHidden(id);
            return checkbox.checked == false;
        } else {
            $form.showHidden(id);
            return checkbox.checked == true;
        }
    },

    /*
    calls showfields or hidefields based on the value
    of show
    */
    setHidden: function(id, show) {
        if (show == true) { $form.showHidden(id); }
        else { $form.hideHidden(id); }
    },

    initHidden: function(id, showing) {
        addLoadEvent(function() {
            if ($(id) != null) {

                //check the object exists in the array already
                if ($form.hiddens[id] == null) {
                    //if it does create a new object for it, and hide if needed
                    hidden = $form.hiddens[id] = {
                        node: $(id).cloneNode(true),
                        showing: showing
                    };
                }
                if (showing != 'true') {
                    $form.setHidden(id, showing);
                }
            }
        });
    },

    /*
    Shows a hidden section and sets required
    to true for all input fields within
      
    id: id of the hidden section div
    */
    showHidden: function(id) {
        $form.hiddens[id].showing = true;
        $(id).parentNode.replaceChild($form.hiddens[id].node, $(id));
        $(id).style.display = "block";
    },

    /*
    Hides a hidden section and sets required
    to false for all input fields within
      
    id: id of the hidden section div
    ele: element reference if id cannot be provided
    */
    hideHidden: function(id, ele) {
        if ($form.hiddens[id].showing == true) {
            $form.hiddens[id].showing = false;
            $form.hiddens[id].node = $(id).cloneNode(true);
        }
        $(id).innerHTML = "";
    },

    //disable all form fields within a given element
    disableFields: function(ele) {
        var tags = ["textarea", "select", "input"];
        //get every input field in the hidden block
        for (var i = 0; i < tags.length; i++) {
            var fields = ele.getElementsByTagName(tags[i]);
            for (var j = 0; j < fields.length; j++) {
                fields[j].disabled = true;
                var className = fields[j].className;
                fields[j].className = "ignored"
            }
        }
    },

    //disable all fields for an entire form
    disableForm: function(form) {
        var fields = document.forms[form];
        for (var i = 0; i < fields.length; i++) {
            fields[i].disabled = true;
        }
    },


    //Object for controlling keyboard inputs
    inputs: {
        ctrlKey: false, //if the ctrl key is being held down ctrlKey will be true
        init: function() {
            //alter the key listeners for the page
            document.onkeydown = function(e, field) {
                var evt = (e) ? e : window.event;
                var key = (evt.keyCode) ? evt.keyCode : evt.which;

                if (key != null) {
                    if (parseInt(key, 10) == 17) {
                        $form.inputs.ctrlKey = true;
                    }
                }
            }

            document.onkeyup = function(e, field) {
                var evt = (e) ? e : window.event;
                var key = (evt.keyCode) ? evt.keyCode : evt.which;

                if (key != null) {
                    if (parseInt(key, 10) == 17) {
                        $form.inputs.ctrlKey = false;
                    }
                }
            }
        },


        /**
        checks the given key is a number.
        if isFloat is true, field must be the input box being checked
        isFloat allows a single decimal point to be added to
        the field anywhere but the start
        */
        isNumeric: function(e, field, isFloat) {
            var evt = (e) ? e : window.event;
            var key = (evt.keyCode) ? evt.keyCode : evt.which;

            if (key != null) {
                key = parseInt(key, 10);
                //check if this is a decimal in the right place
                if (isFloat && key == 110 &&
            field.value.indexOf(".") == -1 &&
            field.value.length > 0) {
                    return true;
                }
                //check for invalid keys
                else if ((key < 48 || key > 57) && (key < 96 || key > 105)) {
                    if (!$form.inputs.isUserFriendlyChar(key)) {
                        return false;
                    }
                }
                //check if shift is being pressed
                else {
                    if (evt.shiftKey)
                        return false;
                }
            }
            //everything else is passing
            return true;
        },

        forceNumeric: function(field, isFloat) {
            var str = field.value
            if (str.value == "") {
                return;
            }
            //remove all characters except #'s and .'s
            str = str.replace(/[^0-9\.]/g, "");
            //if this is a floating #, remove all but the last dot
            if (isFloat) {
                while (str.count("\\.") > 1) {
                    str = str.replace(/\./, "");
                }
                //other wise remove all dots
            } else {
                str = str.replace(/\./g, "");
            }
            field.value = str;
        },

        isLetter: function(e, field) {
            var evt = (e) ? e : window.event;
            var key = (evt.keyCode) ? evt.keyCode : evt.which;

            if (key != null) {
                key = parseInt(key, 10);
                //check for invalid keys
                if (key < 65 || key > 90) {
                    if (!$form.inputs.isUserFriendlyChar(key)) {
                        return false;
                    }
                }
            }
            //everything else is passing
            return true;
        },

        //checks the date field is being correctly populated
        isDate: function(e, field) {
            var evt = (e) ? e : window.event;
            var key = (evt.keyCode) ? evt.keyCode : evt.which;

            if (key != null) {
                key = parseInt(key, 10);

                //check if tab was pressed (so the calendar goes away)
                if (key == 9) {

                }

                var isNumber = $form.inputs.isNumeric(e, field, false);
                var isSlash = key == 111 || key == 191;
                //a slash was previously entered
                if (isSlash) {
                    if (field.value.indexOf("/") == field.value.length - 1) {
                        return false;
                    }
                    if (field.value.length == 0) {
                        return false;
                    }
                    if (field.value.count("/") >= 2) {
                        return false;
                    }
                }

                return isSlash || isNumber
            }

            return false;
        },

        /*
        Once a field has been entered into a date box
        the value is forced to valid format. If the date
        is FUBAR it is replaced with today's date.
        */
        forceDate: function(field) {
            if (field.value == "") {
                return;
            }

            var date = Date.parse(field.value);
            if (isNaN(date) || date < $form.dateMin || date > $form.dateMax) {
                date = new Date();
            } else {
                date = new Date(date);
            }

            var day = new String(date.getDate()).padL('0', 2);
            var month = new String(date.getMonth() + 1).padL('0', 2);
            var year = date.getFullYear();

            field.value = month + "/" + day + "/" + year;
        },

        isUserFriendlyChar: function(val) {
            if ($form.inputs.ctrlKey == true) {
                return true;
            }
            // Backspace, Tab, Enter, Insert, and Delete
            if (val == 8 || val == 9 || val == 13 || val == 45 || val == 46)
                return true;

            // Ctrl, Alt, CapsLock, Home, End, and Arrows
            if ((val > 16 && val < 21) || (val > 34 && val < 41))
                return true;

            // The rest
            return false;
        }

    }

}







////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////// Calendar Control //////////////////////////////////////////////////
///////////////////////////////////////// (don't touch) ////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////


Element.addMethods();

Element.addMethods({
  purgeChildren: function(element) { $A(element.childNodes).each(function(e) { $(e).remove(); }); },
  build: function(element, type, options, style) {
    var newElement = Element.buildAndAppend(type, options, style);
    element.appendChild(newElement);
    return newElement;
  }
});

Element.buildAndAppend = function(type, options, style) {
  var e = $(document.createElement(type));
  $H(options).each(function(pair) { e[pair.key] = pair.value });
  if (style) e.setStyle(style);
  return e;
};
nil = null;

Date.one_day = 24 * 60 * 60 * 1000;
Date.weekdays = $w("S M T W T F S");
Date.first_day_of_week = 0;
Date.months = $w("January February March April May June July August September October November December");
Date.padded2 = function(hour) { var padded2 = parseInt(hour, 10); if (hour < 10) padded2 = "0" + padded2; return padded2; }
Date.prototype.getPaddedMinutes = function() { return Date.padded2(this.getMinutes()); }
Date.prototype.getAMPMHour = function() { var hour = this.getHours(); return (hour == 0) ? 12 : (hour > 12 ? hour - 12 : hour) }
Date.prototype.getAMPM = function() { return (this.getHours() < 12) ? "AM" : "PM"; }
Date.prototype.stripTime = function() { return new Date(this.getFullYear(), this.getMonth(), this.getDate()); };
Date.prototype.daysDistance = function(compare_date) { return Math.round((compare_date - this) / Date.one_day); };
Date.prototype.toFormattedString = function(include_time) {
  var hour, str;
  str = Date.months[this.getMonth()] + " " + this.getDate() + ", " + this.getFullYear();

  if (include_time) { hour = this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() + " " + this.getAMPM() }
  return str;
}
Date.parseFormattedString = function(string) { return new Date(string); }
Math.floor_to_interval = function(n, i) { return Math.floor(n / i) * i; }
window.f_height = function() { return ([window.innerHeight ? window.innerHeight : null, document.documentElement ? document.documentElement.clientHeight : null, document.body ? document.body.clientHeight : null].select(function(x) { return x > 0 }).first() || 0); }
window.f_scrollTop = function() { return ([window.pageYOffset ? window.pageYOffset : null, document.documentElement ? document.documentElement.scrollTop : null, document.body ? document.body.scrollTop : null].select(function(x) { return x > 0 }).first() || 0); }

_translations = {
  "OK": "OK",
  "Now": "Now",
  "Today": "Today",
  "Clear": "Clear"
}
SelectBox = Class.create();
SelectBox.prototype = {
  initialize: function(parent_element, values, html_options, style_options) {
    this.element = $(parent_element).build("select", html_options, style_options);
    this.populate(values);
  },
  populate: function(values) {
    this.element.purgeChildren();
    var that = this; $A(values).each(function(pair) { if (typeof (pair) != "object") { pair = [pair, pair] }; that.element.build("option", { value: pair[1], innerHTML: pair[0] }) });
  },
  setValue: function(value) {
    var e = this.element;
    var matched = false;
    $R(0, e.options.length - 1).each(function(i) { if (e.options[i].value == value.toString()) { e.selectedIndex = i; matched = true; }; });
    return matched;
  },
  getValue: function() { return $F(this.element) }
}
CalendarDateSelect = Class.create();
CalendarDateSelect.prototype = {
  initialize: function(target_element, options) {
    this.target_element = $(target_element); // make sure it's an element, not a string
    if (!this.target_element) { alert("Target element " + target_element + " not found!"); return false; }
    if (this.target_element.tagName != "INPUT") this.target_element = this.target_element.down("INPUT")

    this.target_element.calendar_date_select = this;
    this.last_click_at = 0;
    // initialize the date control
    this.options = $H({
      embedded: false,
      popup: nil,
      time: false,
      buttons: true,
      clear_button: true,
      year_range: 10,
      close_on_click: nil,
      minute_interval: 5,
      popup_by: this.target_element,
      month_year: "dropdowns",
      onchange: this.target_element.onchange,
      valid_date_check: nil
    }).merge(options || {});
    this.use_time = this.options.get("time");
    this.parseDate();
    this.callback("before_show")
    this.initCalendarDiv();
    if (!this.options.get("embedded")) {
      this.positionCalendarDiv()
      // set the click handler to check if a user has clicked away from the document
      Event.observe(document, "mousedown", this.closeIfClickedOut_handler = this.closeIfClickedOut.bindAsEventListener(this));
      Event.observe(document, "keypress", this.keyPress_handler = this.keyPress.bindAsEventListener(this));
    }
    this.callback("after_show")
  },
  positionCalendarDiv: function() {
    var above = false;
    var c_pos = this.calendar_div.cumulativeOffset(), c_left = c_pos[0], c_top = c_pos[1], c_dim = this.calendar_div.getDimensions(), c_height = c_dim.height, c_width = c_dim.width;
    var w_top = window.f_scrollTop(), w_height = window.f_height();
    var e_dim = $(this.options.get("popup_by")).cumulativeOffset(), e_top = e_dim[1], e_left = e_dim[0], e_height = $(this.options.get("popup_by")).getDimensions().height, e_bottom = e_top + e_height;

    if (((e_bottom + c_height) > (w_top + w_height)) && (e_bottom - c_height > w_top)) above = true;
    var left_px = e_left.toString() + "px", top_px = (above ? (e_top - c_height) : (e_top + e_height)).toString() + "px";

    this.calendar_div.style.left = left_px; this.calendar_div.style.top = top_px;

    this.calendar_div.setStyle({ visibility: "" });

    // draw an iframe behind the calendar -- ugly hack to make IE 6 happy
    if (navigator.appName == "Microsoft Internet Explorer") this.iframe = $(document.body).build("iframe", { src: "javascript:false", className: "ie6_blocker" }, { left: left_px, top: top_px, height: c_height.toString() + "px", width: c_width.toString() + "px", border: "0px" })
  },
  initCalendarDiv: function() {
    if (this.options.get("embedded")) {
      var parent = this.target_element.parentNode;
      var style = {}
    } else {
      var parent = document.body
      var style = { position: "absolute", visibility: "hidden", left: 0, top: 0 }
    }
    this.calendar_div = $(parent).build('div', { className: "calendar_date_select" }, style);

    var that = this;
    // create the divs
    $w("top header body buttons footer bottom").each(function(name) {
      eval("var " + name + "_div = that." + name + "_div = that.calendar_div.build('div', { className: 'cds_" + name + "' }, { clear: 'left'} ); ");
    });

    this.initHeaderDiv();
    this.initButtonsDiv();
    this.initCalendarGrid();
    this.updateFooter("&#160;");

    this.refresh();
    this.setUseTime(this.use_time);
  },
  initHeaderDiv: function() {
    var header_div = this.header_div;
    this.close_button = header_div.build("a", { innerHTML: "x", href: "#", onclick: function() { this.close(); return false; } .bindAsEventListener(this), className: "close" });
    this.next_month_button = header_div.build("a", { innerHTML: "&gt;", href: "#", onclick: function() { this.navMonth(this.date.getMonth() + 1); return false; } .bindAsEventListener(this), className: "next" });
    this.prev_month_button = header_div.build("a", { innerHTML: "&lt;", href: "#", onclick: function() { this.navMonth(this.date.getMonth() - 1); return false; } .bindAsEventListener(this), className: "prev" });

    if (this.options.get("month_year") == "dropdowns") {
      this.month_select = new SelectBox(header_div, $R(0, 11).map(function(m) { return [Date.months[m], m] }), { className: "month", onchange: function() { this.navMonth(this.month_select.getValue()) } .bindAsEventListener(this) });
      this.year_select = new SelectBox(header_div, [], { className: "year", onchange: function() { this.navYear(this.year_select.getValue()) } .bindAsEventListener(this) });
      this.populateYearRange();
    } else {
      this.month_year_label = header_div.build("span")
    }
  },
  initCalendarGrid: function() {
    var body_div = this.body_div;
    this.calendar_day_grid = [];
    var days_table = body_div.build("table", { cellPadding: "0px", cellSpacing: "0px", width: "100%" })
    // make the weekdays!
    var weekdays_row = days_table.build("thead").build("tr");
    Date.weekdays.each(function(weekday) {
      weekdays_row.build("th", { innerHTML: weekday });
    });

    var days_tbody = days_table.build("tbody")
    // Make the days!
    var row_number = 0, weekday;
    for (var cell_index = 0; cell_index < 42; cell_index++) {
      weekday = (cell_index + Date.first_day_of_week) % 7;
      if (cell_index % 7 == 0) days_row = days_tbody.build("tr", { className: 'row_' + row_number++ });
      (this.calendar_day_grid[cell_index] = days_row.build("td", {
        calendar_date_select: this,
        onmouseover: function() { this.calendar_date_select.dayHover(this); },
        onmouseout: function() { this.calendar_date_select.dayHoverOut(this) },
        onclick: function() { this.calendar_date_select.updateSelectedDate(this, true); },
        className: (weekday == 0) || (weekday == 6) ? " weekend" : "" //clear the class
      },
        { cursor: "pointer" }
      )).build("div");
      this.calendar_day_grid[cell_index];
    }
  },
  initButtonsDiv: function() {
    var buttons_div = this.buttons_div;
    if (this.options.get("time")) {
      var blank_time = $A(this.options.get("time") == "mixed" ? [[" - ", ""]] : []);
      buttons_div.build("span", { innerHTML: "@", className: "at_sign" });

      var t = new Date();
      this.hour_select = new SelectBox(buttons_div,
        blank_time.concat($R(0, 23).map(function(x) { t.setHours(x); return $A([t.getAMPMHour() + " " + t.getAMPM(), x]) })),
        {
          calendar_date_select: this,
          onchange: function() { this.calendar_date_select.updateSelectedDate({ hour: this.value }); },
          className: "hour"
        }
      );
      buttons_div.build("span", { innerHTML: ":", className: "seperator" });
      var that = this;
      this.minute_select = new SelectBox(buttons_div,
        blank_time.concat($R(0, 59).select(function(x) { return (x % that.options.get('minute_interval') == 0) }).map(function(x) { return $A([Date.padded2(x), x]); })),
        {
          calendar_date_select: this,
          onchange: function() { this.calendar_date_select.updateSelectedDate({ minute: this.value }) },
          className: "minute"
        }
      );

    } else if (!this.options.get("buttons")) buttons_div.remove();

    if (this.options.get("buttons")) {
      buttons_div.build("span", { innerHTML: "&#160;" });
      if (this.options.get("time") == "mixed" || !this.options.get("time")) b = buttons_div.build("a", {
        innerHTML: _translations["Today"],
        href: "#",
        onclick: function() { this.today(false); return false; } .bindAsEventListener(this)
      });

      if (this.options.get("time") == "mixed") buttons_div.build("span", { innerHTML: "&#160;|&#160;", className: "button_seperator" })

      if (this.options.get("time")) b = buttons_div.build("a", {
        innerHTML: _translations["Now"],
        href: "#",
        onclick: function() { this.today(true); return false } .bindAsEventListener(this)
      });

      if (!this.options.get("embedded") && !this.closeOnClick()) {
        buttons_div.build("span", { innerHTML: "&#160;|&#160;", className: "button_seperator" })
        buttons_div.build("a", { innerHTML: _translations["OK"], href: "#", onclick: function() { this.close(); return false; } .bindAsEventListener(this) });
      }
      if (this.options.get('clear_button')) {
        buttons_div.build("span", { innerHTML: "&#160;|&#160;", className: "button_seperator" })
        buttons_div.build("a", { innerHTML: _translations["Clear"], href: "#", onclick: function() { this.clearDate(); if (!this.options.get("embedded")) this.close(); return false; } .bindAsEventListener(this) });
      }
    }
  },
  refresh: function() {
    this.refreshMonthYear();
    this.refreshCalendarGrid();

    this.setSelectedClass();
    this.updateFooter();
  },
  refreshCalendarGrid: function() {
    this.beginning_date = new Date(this.date).stripTime();
    this.beginning_date.setDate(1);
    this.beginning_date.setHours(12); // Prevent daylight savings time boundaries from showing a duplicate day
    var pre_days = this.beginning_date.getDay() // draw some days before the fact
    if (pre_days < 3) pre_days += 7;
    this.beginning_date.setDate(1 - pre_days + Date.first_day_of_week);

    var iterator = new Date(this.beginning_date);

    var today = new Date().stripTime();
    var this_month = this.date.getMonth();
    vdc = this.options.get("valid_date_check");
    for (var cell_index = 0; cell_index < 42; cell_index++) {
      day = iterator.getDate(); month = iterator.getMonth();
      cell = this.calendar_day_grid[cell_index];
      Element.remove(cell.childNodes[0]); div = cell.build("div", { innerHTML: day });
      if (month != this_month) div.className = "other";
      cell.day = day; cell.month = month; cell.year = iterator.getFullYear();
      if (vdc) { if (vdc(iterator.stripTime())) cell.removeClassName("disabled"); else cell.addClassName("disabled") };
      iterator.setDate(day + 1);
    }

    if (this.today_cell) this.today_cell.removeClassName("today");

    if ($R(0, 41).include(days_until = this.beginning_date.stripTime().daysDistance(today))) {
      this.today_cell = this.calendar_day_grid[days_until];
      this.today_cell.addClassName("today");
    }
  },
  refreshMonthYear: function() {
    var m = this.date.getMonth();
    var y = this.date.getFullYear();
    // set the month
    if (this.options.get("month_year") == "dropdowns") {
      this.month_select.setValue(m, false);

      var e = this.year_select.element;
      if (this.flexibleYearRange() && (!(this.year_select.setValue(y, false)) || e.selectedIndex <= 1 || e.selectedIndex >= e.options.length - 2)) this.populateYearRange();

      this.year_select.setValue(y);

    } else {
      this.month_year_label.update(Date.months[m] + " " + y.toString());
    }
  },
  populateYearRange: function() {
    this.year_select.populate(this.yearRange().toArray());
  },
  yearRange: function() {
    if (!this.flexibleYearRange())
      return $R(this.options.get("year_range")[0], this.options.get("year_range")[1]);

    var y = this.date.getFullYear();
    return $R(y - this.options.get("year_range"), y + this.options.get("year_range"));
  },
  flexibleYearRange: function() { return (typeof (this.options.get("year_range")) == "number"); },
  validYear: function(year) { if (this.flexibleYearRange()) { return true; } else { return this.yearRange().include(year); } },
  dayHover: function(element) {
    var hover_date = new Date(this.selected_date);
    hover_date.setYear(element.year); hover_date.setMonth(element.month); hover_date.setDate(element.day);
    this.updateFooter(hover_date.toFormattedString(this.use_time));
  },
  dayHoverOut: function(element) { this.updateFooter(); },
  clearSelectedClass: function() { if (this.selected_cell) this.selected_cell.removeClassName("selected"); },
  setSelectedClass: function() {
    if (!this.selection_made) return;
    this.clearSelectedClass()
    if ($R(0, 42).include(days_until = this.beginning_date.stripTime().daysDistance(this.selected_date.stripTime()))) {
      this.selected_cell = this.calendar_day_grid[days_until];
      this.selected_cell.addClassName("selected");
    }
  },
  reparse: function() { this.parseDate(); this.refresh(); },
  dateString: function() {
    return (this.selection_made) ? this.selected_date.toFormattedString(this.use_time) : "&#160;";
  },
  parseDate: function() {
    var value = $F(this.target_element).strip()
    this.selection_made = (value != "");
    this.date = value == "" ? NaN : Date.parseFormattedString(this.options.get("date") || value);
    if (isNaN(this.date) || this.date < -2208967200000 || this.date > 3453253200000) this.date = new Date();

    if (!this.validYear(this.date.getFullYear())) this.date.setYear((this.date.getFullYear() < this.yearRange().start) ? this.yearRange().start : this.yearRange().end);
    this.selected_date = new Date(this.date);
    this.use_time = /[0-9]:[0-9]{2}/.exec(value) ? true : false;
    this.date.setDate(1);
  },
  updateFooter: function(text) { if (!text) text = this.dateString(); this.footer_div.purgeChildren(); this.footer_div.build("span", { innerHTML: text }); },
  clearDate: function() {
    if ((this.target_element.disabled || this.target_element.readOnly) && this.options.get("popup") != "force") return false;
    var last_value = this.target_element.value;
    this.target_element.value = "";
    this.clearSelectedClass();
    this.updateFooter('&#160;');
    if (last_value != this.target_element.value) this.callback("onchange");
  },
  updateSelectedDate: function(partsOrElement, via_click) {
    var parts = $H(partsOrElement);
    if ((this.target_element.disabled || this.target_element.readOnly) && this.options.get("popup") != "force") return false;
    if (parts.get("day")) {
      var t_selected_date = this.selected_date, vdc = this.options.get("valid_date_check");
      for (var x = 0; x <= 3; x++) t_selected_date.setDate(parts.get("day"));
      t_selected_date.setYear(parts.get("year"));
      t_selected_date.setMonth(parts.get("month"));

      if (vdc && !vdc(t_selected_date.stripTime())) { return false; }
      this.selected_date = t_selected_date;
      this.selection_made = true;
    }

    if (!isNaN(parts.get("hour"))) this.selected_date.setHours(parts.get("hour"));
    if (!isNaN(parts.get("minute"))) this.selected_date.setMinutes(Math.floor_to_interval(parts.get("minute"), this.options.get("minute_interval")));
    if (parts.get("hour") === "" || parts.get("minute") === "")
      this.setUseTime(false);
    else if (!isNaN(parts.get("hour")) || !isNaN(parts.get("minute")))
      this.setUseTime(true);

    this.updateFooter();
    this.setSelectedClass();

    if (this.selection_made) this.updateValue();
    if (this.closeOnClick()) { this.close(); }
    if (via_click && !this.options.get("embedded")) {
      if ((new Date() - this.last_click_at) < 333) this.close();
      this.last_click_at = new Date();
    }
  },
  closeOnClick: function() {
    if (this.options.get("embedded")) return false;
    if (this.options.get("close_on_click") === nil)
      return (this.options.get("time")) ? false : true
    else
      return (this.options.get("close_on_click"))
  },
  navMonth: function(month) { (target_date = new Date(this.date)).setMonth(month); return (this.navTo(target_date)); },
  navYear: function(year) { (target_date = new Date(this.date)).setYear(year); return (this.navTo(target_date)); },
  navTo: function(date) {
    if (!this.validYear(date.getFullYear())) return false;
    this.date = date;
    this.date.setDate(1);
    this.refresh();
    this.callback("after_navigate", this.date);
    return true;
  },
  setUseTime: function(turn_on) {
    this.use_time = this.options.get("time") && (this.options.get("time") == "mixed" ? turn_on : true) // force use_time to true if time==true && time!="mixed"
    if (this.use_time && this.selected_date) { // only set hour/minute if a date is already selected
      var minute = Math.floor_to_interval(this.selected_date.getMinutes(), this.options.get("minute_interval"));
      var hour = this.selected_date.getHours();

      this.hour_select.setValue(hour);
      this.minute_select.setValue(minute)
    } else if (this.options.get("time") == "mixed") {
      this.hour_select.setValue(""); this.minute_select.setValue("");
    }
  },
  updateValue: function() {
    var last_value = this.target_element.value;
    this.target_element.value = this.dateString();
    if (last_value != this.target_element.value) this.callback("onchange");
  },
  today: function(now) {
    var d = new Date(); this.date = new Date();
    var o = $H({ day: d.getDate(), month: d.getMonth(), year: d.getFullYear(), hour: d.getHours(), minute: d.getMinutes() });
    if (!now) o = o.merge({ hour: "", minute: "" });
    this.updateSelectedDate(o, true);
    this.refresh();
  },
  close: function() {
    if (this.closed) return false;
    this.callback("before_close");
    this.target_element.calendar_date_select = nil;
    Event.stopObserving(document, "mousedown", this.closeIfClickedOut_handler);
    Event.stopObserving(document, "keypress", this.keyPress_handler);
    this.calendar_div.remove(); this.closed = true;
    if (this.iframe) this.iframe.remove();
    if (this.target_element.type != "hidden" && !this.target_element.disabled) this.target_element.focus();
    this.callback("after_close");
  },
  closeIfClickedOut: function(e) {
    if (!$(Event.element(e)).descendantOf(this.calendar_div)) this.close();
  },
  keyPress: function(e) {
    if (e.keyCode == Event.KEY_ESC) this.close();
  },
  callback: function(name, param) { if (this.options.get(name)) { this.options.get(name).bind(this.target_element)(param); } }
}
Date.prototype.toFormattedString = function(include_time) {
  str = Date.padded2(this.getMonth() + 1) + '/' + Date.padded2(this.getDate()) + '/' + this.getFullYear();

  if (include_time) { hour = this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() + " " + this.getAMPM() }
  return str;
}

Date.parseFormattedString = function(string) {
  // Test these with and without the time
  // 11/11/1111 12pm
  // 11/11/1111 1pm
  // 1/11/1111 10:10pm
  // 11/1/1111 01pm
  // 1/1/1111 01:11pm
  // 1/1/1111 1:11pm
  var regexp = "(([0-1]?[0-9])\/[0-3]?[0-9]\/[0-9]{4}) *([0-9]{1,2}(:[0-9]{2})? *(am|pm))?";
  var d = string.match(new RegExp(regexp, "i"));
  if (d == null) {
    return Date.parse(string); // Give javascript a chance to parse it.
  }

  mdy = d[1].split('/');
  hrs = 0;
  mts = 0;
  if (d[3] != null) {
    hrs = parseInt(d[3].split('')[0], 10);
    if (d[5].toLowerCase() == 'pm') { hrs += 12; } // Add 12 more to hrs
    mts = d[4].split(':')[1];
  }

  return new Date(mdy[2], parseInt(mdy[0], 10) - 1, mdy[1], hrs, mts, 0);
}
