var QIS
document.observe("dom:loaded", function() { QIS = new QISLibrary(); })

/**************************************************************************************************
   QISLibrary - collection of simplistic javascripts designed for onload inclusion into the website
   *  Required Javascript
      - prototype.js
      - src\scriptaculous.js
   *  Required CSS
      - QISLibrary.css
 **************************************************************************************************/
var QISLibrary = Class.create()
QISLibrary.prototype = {
   _images:   "../images/",
   _includes: "../includes/",
   initialize: function() {
      if (document.body) {
         this.Anchor();

         if ($("anchor")) {
//          this.CSS()
            var obj = this
            for (var i in obj) {
               if (typeof(obj[i]) == "object") {
                  if (obj[i].selector) { $$(obj[i].selector).each(function(n) { obj[i].init(n);}) }
               }
            }
         }
      }
   },

/**************************************************************************************************
   Anchor - This is an anchor element to append applicable items to
 **************************************************************************************************/
   Anchor: function() {
      if ($("anchor")) return
      $(document.body).insert({top:new Element("div", {"id":"anchor"}) })
   },

/**************************************************************************************************
   CSS - Append the QISLibrary.css <link> to the page
 **************************************************************************************************/
   CSS: function() {
      if ($("qislibrary_css")) return
      var n = new Element("link", {"id":"qislibrary_css","rel":"stylesheet","type":"text/css","href":QIS._includes+"QISLibrary.css?"+this.Random() })
      $("anchor").insert({after:n});
   },

/**************************************************************************************************
   Log - updates the Anchor object with specified text
 **************************************************************************************************/
   Log: function(s) {
      if (!$("anchor")) return
      $("anchor").insert(new Element("div").update(s))
   },

/**************************************************************************************************
   Random - Generates a unique Random number
 **************************************************************************************************/
   Random: function() {
      return (Math.floor(Math.random()*Math.pow(10,10))).toString(36);
   },

/**************************************************************************************************
   Int - returns a whole number or 0 if object does not exists/is not a number
 **************************************************************************************************/
   Int: function(object) {
      if (!$(object))        { return 0; }
      if (isNaN($F(object))) { return 0; }
      if ($F(object)=="")    { return 0; }
      return parseInt($F(object))
   },

/**************************************************************************************************
   Dbl - returns a decimal number or 0 if object does not exists/is not a number
 **************************************************************************************************/
   Dbl: function(object) {
      if (!$(object))        { return 0; }
      if (isNaN($F(object))) { return 0; }
      if ($F(object)=="")    { return 0; }
      return parseFloat($F(object))
   },

/**************************************************************************************************
   Email - Calls an .asp page to send an email when there is an error.
   *  Optional:
      - Includes\QISEmail.asp web page
 **************************************************************************************************/
   Email: function(page, source, response) {
      new Ajax.Request("..\\Includes\\QISEmail.asp?r="+QIS.Random(), {
         method: "post",
         asynchronous: true,
         evalJSON: true,
         parameters: {
             page    : page
            ,source  : source
            ,response: response.responseText
            ,status  : response.status+" - "+response.statusText
         },
         onSuccess: function(t) { },
         onFailure: function(t) { }
      })
   },

/**************************************************************************************************
   Alpha - Prevents the entry of any non-alpha character, the special attribute will allow
      additional specified characters to filter
   *  Optional:
      -  error div with name "{id}_error"
      -  css element "error"
 **************************************************************************************************/
   Alpha: {
      selector: "input.QISAlpha",
      init: function(object) {
         object = $(object)
         if (object.readAttribute("qis") == "loaded") return
         object.writeAttribute("qis", "loaded")
         object.observe("blur", function() { QIS.Alpha.Validate(object); })
         if (!object.readAttribute("special")) { object.writeAttribute("special", "") }
      },
      Validate: function(object) {
         object = $(object)
         object.value = QIS.Alpha.strip(object.value, object.readAttribute("special"))
         return true;
      },
      strip: function(s, ext) {
         var n = ""
         s = s.strip()
         $A(""+s).each(function(c) { n+=((ext+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ").include(c))?c:""; })
         return n
      }
   },

/**************************************************************************************************
   Numeric - Prevents the entry of any non-numeric character, and validates that the entered
      value is within specified min/max attributes
   *  Optional:
      -  error div with name "{id}_error"
      -  css element "error"
 **************************************************************************************************/
   Numeric: {
      selector: "input.QISNumeric",
      init: function(object) {
         object = $(object)
         if (object.readAttribute("qis") == "loaded") return
         object.writeAttribute("qis", "loaded")
//       object.observe("keypress", function(event) { return QIS.Numeric.Keypress(event) })
         object.observe("blur", function() { QIS.Numeric.Validate(object); })
         if (!object.readAttribute("percision")) { object.writeAttribute("percision", "0") }
      },
      Keypress: function(e) {
         var charCode = (e.keyCode)?e.keyCode:e.which;
         if (e.which) {
            if (charCode<32) return true;
            if (e.altKey || e.ctrlKey || e.shiftKey) { charCode = 999 }
            if (([45,46].indexOf(charCode)>-1) || (charCode>=48 && charCode<=57)) return true;
            e.preventDefault(true)
            return false;
         } else {
            if (charCode<48) return true;
            if (e.altKey || e.ctrlKey || e.shiftKey) { charCode = 999 }
            if (([109,110,189,190].indexOf(charCode)>-1) || (charCode>=48 && charCode<=57) || (charCode>=96 && charCode<=105)) return true;
            return false;
         }
         return true;
      },
      Validate: function(object) {
         object = $(object)
         object.value = QIS.Numeric.strip(object.value);
         var error = $(object.name+"_error")
         var min = parseFloat(QIS.Numeric.strip(object.readAttribute("min")))
         var max = parseFloat(QIS.Numeric.strip(object.readAttribute("max")))

         if (object.value=="") {
            if (error) { error.show().update("Entry required"); }
            return false;
         }
         if (isNaN(object.value)) {
            object.addClassName("error");
            if (error) { error.show().update("Must be numeric"); }
            return false;
         }

         var percision = object.readAttribute("percision")
         object.value = (parseInt(object.value * Math.pow(10,percision)) / Math.pow(10,percision)).toFixed(percision)

         if (parseFloat(object.value) < min) {
            object.addClassName("error");
            if (error) { error.show().update("Cannot be less than "+QIS.Numeric.format(min)); }
            return false;
         }
         if (parseFloat(object.value) > max) {
            object.addClassName("error");
            if (error) { error.show().update("Cannot be greater than "+QIS.Numeric.format(max)); }
            return false;
         }

         object.removeClassName("error");
         if (error) { error.hide().update(); }
         return true;
      },
      strip: function(s) {
         var n = ""
         $A(""+s).each(function(c) { n+=("1234567890.-".include(c))?c:""; })
         return n
      },
      format: function(s) {
         s = QIS.Numeric.strip(s);
         if (s < 10000) return s;
         var x = s.split('.');
         var rgx = /(\d+)(\d{3})/;
         while (rgx.test(x[0])) { x[0] = x[0].replace(rgx, "$1"+","+"$2"); }
         return x[0] + ((x.length>1)? "."+x[1]:"")
      }
   },

/**************************************************************************************************
   Default - Sets the value of an input box to the default attributes value and grays it.
   *  Required:
      - attribute "default"
 **************************************************************************************************/
   Default: {
      selector: "input.QISDefault",
      init: function(object) {
         object = $(object)
         if (object.readAttribute("qis") == "loaded") return
         object.writeAttribute("qis", "loaded")
         object.observe("focus", function(event) { QIS.Default.Focus(object); })
         object.observe("blur",  function(event) { QIS.Default.Blur(object);  })
         this.Blur(object)
      },
      Focus: function(object) {
         object = $(object)
         if (object.value == object.readAttribute("default")) { object.value = ""; object.select(); }
         if (object.value == "") { object.removeClassName("qis_default"); }
      },
      Blur: function(object) {
         object = $(object)
         if (object.value == "") { object.value = object.readAttribute("default"); }
         if (object.value == object.readAttribute("default")) { object.addClassName("qis_default"); }
      }
   },

/**************************************************************************************************
   Phone - validates and formats phone numbers
   *  Optional:
      -  error div with name "{id}_error"
      -  css element "error"
 **************************************************************************************************/
   Phone: {
      selector: "input.QISPhone",
      init: function(object) {
         object = $(object)
         if (object.readAttribute("qis") == "loaded") return
         object.writeAttribute("qis", "loaded")
         object.observe("change", function(event) { QIS.Phone.Validate(this) })
      },
      Validate: function(object) {
         object = $(object)
         var error = $(object.name+"_error")

         var number = "";
         object.removeClassName("error");
         $A(object.value).each(function(c) { number+=("1234567890".include(c))?c:""; })

         if (object.value=="") {
            object.addClassName("error");
            if (error) { error.show().update("Entry required"); }
            return false;
         }
         if (number.length != 10) {
            object.addClassName("error");
            if (error) { error.show().update("Must contain 10 numbers"); }
            return false;
         }

         object.writeAttribute("format", (object.readAttribute("format") || "simple"))
         switch (object.readAttribute("format")) {
            case "simple"   : object.value = "111-222-3333";   break;
            case "standard" : object.value = "(111) 222-3333"; break;
         }
         object.value = object.value.sub("111",number.substring(0,3)).sub("222",number.substring(3,6)).sub("3333",number.substring(6,10))
         if (error) { error.hide().update(); }
         return true;
      }
   },

/**************************************************************************************************
   Help - creates a popup help window
   *  Format
      <span class="QISHelp" id="help_[element id]" style="display:none">
         <span class="title">[Help Title]</span>
         [Help Text]
      </span>
 **************************************************************************************************/
   Help: {
      selector: "span.QISHelp",
      init: function(object) {
         object = $(object)
         object.hide()
         if (!$("help_div")) {
            var n = new Element("div", {"id":"help_div","class":"qis_help","style":"z-index:50;background:#eee;display:none;"})
            $("anchor").insert({after:n});
         }
      },
      Build: function() {
         var s=""
         s+="<iframe class='qis_help' id='help_ifr' src='javascript:false' scrolling='no' frameborder='0'></iframe>"
         s+="<table class='qis_help' id='help_table' cellspacing='0' cellpadding='0' border='0'>"
         s+=   "<tr id='help_handle'>"
         s+=      "<td class='corner'><img src='"+QIS._images+"Help_Left.gif' /></td>"
         s+=      "<td class='title' id='help_title' ></td>"
         s+=      "<td class='close' ><img src='"+QIS._images+"Help_Close.gif' onclick='QIS.Help.Hide()' /></td>"
         s+=      "<td class='corner'><img src='"+QIS._images+"Help_Right.gif' /></td>"
         s+=   "</tr>"
         s+=   "<tr>"
         s+=      "<td class='body' id='help_body' colspan='4' valign='top'>&nbsp;</td>"
         s+=   "</tr>"
         s+="</table>"
         $("help_div").update(s)
         new Draggable("help_div", {handle: "help_handle", endeffect:""})
      },
      Show: function(object) {
         object=$("help_"+object)
         if ($("help_div").empty()) { this.Build() }
         if (object.down("span.title")) {
            $("help_title").update(object.down("span.title").hide().innerHTML)
         }
         $("help_body").update(object.innerHTML)
         if ($("help_div").visible()) { return }

         var db = document.body
         var x = (db.scrollLeft + ((db.clientWidth  - $("help_div").getWidth())  / 2))
         var y = (db.scrollTop  + ((db.clientHeight - $("help_div").getHeight()) / 2))
         $("help_div").show().setStyle({left:x,top:y})

         var h = $("help_table").getHeight()
         var w = $("help_table").getWidth()
         $("help_ifr").setStyle({"height":h,"width":w})
      },
      Hide: function() {
         $("help_body").update("&nbsp;")
         $("help_div").hide()
      }
   },

/**************************************************************************************************
   Processing - creates a modal iframe that displays when a button is pressed.
   *  Required:
      - ifrProcessing.html
 **************************************************************************************************/
   Processing: {
      Build: function() {
         var n = new Element("iframe", {"id":"ifrProcessing","src":"../includes/ifrProcessing.html","scrolling":"no","frameborder":"1"})
         $("anchor").insert({after:n})
      },
      Show: function() {
         this.Build();
         var db = document.body;
         x = (db.clientWidth  - 250) / 2 + db.scrollLeft;
         y = (db.clientHeight - 100) / 2 + db.scrollTop;
         $('ifrProcessing').show().setStyle({position:'absolute',pixelLeft:x,pixelTop:y,width:'250px',height:'100px'})
      },
      Refresh: function() {
         if ($('ifrProcessing').readyState != 'complete') {
           setTimeout(QIS.Processing.Refresh, 10)
         } else {
            var i = $('ifrProcessing').$('imgProcessing')
            i.src = i.src
         }
      }
   },

/**************************************************************************************************
   TabPane - Converts proprly form HTML into a tabbed menu page
   *  Format
      <div class="QISTabPane">
         <div class="QISTabPage" style="display:none;">
            <div class="QISTabText">[Tab Name]</div>
            [Tab Text]
         </div>
      </div>
 **************************************************************************************************/
   TabPane: {
      selector: "div.QISTabPane",
      init: function(object) {
         object = $(object);
         if (object.readAttribute("qis") == "loaded") return
         object.writeAttribute("qis", "loaded")
         object.insert({top:new Element("div", {"class":"qis_tabs"}) });
         object.immediateDescendants().grep(new Selector(".QISTabPage")).each(function(n) {
            n.addClassName("qis_page")
            var tab = new Element("div", {"class":"qis_tab"})
               .insert(new Element("a", {"href":"#","onclick":"return false;"}).insert(n.down(".QISTabText")))
               .observe("click", function(e) { QIS.TabPane.setSelected(object, this, n); })
            n.siblings()[0].insert({bottom:tab})
         })
         QIS.TabPane.setSelected(object, object.firstDescendant().firstDescendant(), object.down(".QISTabPage"))
      },
      setSelected: function(object, tab, n) {
         if (n.visible()) return false;
         tab.siblings().invoke("removeClassName", "selected")
         tab.addClassName("selected")
         n.siblings().grep(new Selector(".QISTabPage")).invoke("hide")
         n.show()
      }
   },

/**************************************************************************************************
   Accordion - Converts an input box into a calendar widgit.  Also validations to min/max values if
      available
   *  Optional
      - "duration" attribute for time (in seconds) to complete
      - "maxHeight" force a specified height for the accordion menu
   *  Format
      <div class="QISAccordion"[ duration=".25"][ maxHeight="100"]>
         <div class="title">  [Title 1  ]</div>
         <div class="content">[Content 1]</div>
         <div class="title">  [Title 2  ]</div>
         <div class="content">[Content 2]</div>
      </div>
 **************************************************************************************************/
   Accordion: {
      selector: "div.QISAccordion",
      init: function(object) {
         object = $(object)
         if (object.readAttribute("qis") == "loaded") return
         object.writeAttribute("qis", "loaded")
         this.isAnimating  = false
         object.addClassName("qis_accordion")
         object.observe('click', QIS.Accordion.Animate.bindAsEventListener(this))
         object.select('.title')[0].addClassName("active")
      // set duration attribute
         var d = (object.readAttribute("duration") || .75)
         object.writeAttribute("duration", d)
      // set maxHeight attribute
         var h = (object.readAttribute("maxHeight") ||  QIS.Accordion.getMaxHeight(object))
         object.writeAttribute("maxHeight", h)
      // default hide/show
         object.select(".content").each(function(n) {
            if (n.previous(".title").hasClassName("active")) {
               n.setStyle({"height":h+"px"}).show()
            } else {
               n.setStyle({"height":"0px"}).hide()
            }
         })
      },
      getMaxHeight: function(object) {
         var contents = $(object).select(".content")
         var h = 0
         object.select(".content").each(function(n) {
            if (n.getHeight() > h) { h = n.getHeight() }
         })
         return h
      },
      Animate: function(e) {
         var object = e.element()
         if (this.isAnimating)              return;
         if (!object.hasClassName("title")) return;
         if (object.hasClassName("active")) return;

         var current = object.siblings().grep(new Selector(".active"))[0]
         object.next(".content").show()

         var h = object.up().readAttribute("maxHeight")
         var w = object.up().getWidth()
         var d = object.up().readAttribute("duration")

         var effects = new Array();
         effects.push(new Effect.Scale(object.next(".content"), 100, {
            sync        : true,
            scaleFrom   : 0,
            scaleContent: false,
            transition  : Effect.Transitions.sinoidal,
            scaleX      : false,
            scaleY      : true,
            scaleMode   : {
               originalHeight : h,
               originalWidth  : w
            }
         }))
         effects.push(new Effect.Scale(current.next(".content"), 0, {
            sync        : true,
            scaleFrom   : 100,
            scaleContent: false,
            transition  : Effect.Transitions.sinoidal,
            scaleX      : false,
            scaleY      : true
         }))

         new Effect.Parallel(effects, {
            duration    : d,
            fps         : 35,
            queue       : {
               position : 'end',
               scope    : 'accordion'
            },
            beforeStart : function() {
               current.removeClassName("active")
               object.addClassName("active")
               QIS.Accordion.isAnimating = true
            },
            afterFinish : function() {
               current.next('.content').hide()
               object.next('.content').setStyle({height: h+"px"})
               QIS.Accordion.isAnimating = false
            }
         })
      }
   },

/**************************************************************************************************
   Calendar - Converts an input box into a calendar widgit.  Also validations to min/max values if
      available
   *  Optional
      -  error div with name "{id}_error"
      -  css element "error"
 **************************************************************************************************/
   Date: {
      _months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
      _days:   [31,28,31,30,31,30,31,31,30,31,30,31],
      selector: "input.QISDate",
      init: function(object) {
         object = $(object)
         object.insert({after:new Element("span")
            .insert(new Element("select", {"id":object.id+"_m"}).observe("change", function() { QIS.Date.save(object.id); }))
            .insert(new Element("select", {"id":object.id+"_d"}).observe("change", function() { QIS.Date.save(object.id); }))
            .insert(new Element("select", {"id":object.id+"_y"}).observe("change", function() { QIS.Date.save(object.id); }))
         })

         $("anchor").insert({after:new Element("span").insert(object).hide() })

         $A(QIS.Date._months).each(function(s,i) {
            var n   = document.createElement("option")
            n.text  = s
            n.value = (i+1).toPaddedString(2)
            $(object.id+"_m").options.add(n);
         })
         for (var i=1; i<=31; i++) {
            var n   = document.createElement("option")
            n.text  = i
            n.value = i.toPaddedString(2)
            $(object.id+"_d").options.add(n);
         }
         for (var i=parseFloat((new Date()).getFullYear()); i>=1900; i--) {
            var n   = document.createElement("option")
            n.text  = i
            n.value = i
            $(object.id+"_y").options.add(n);
         }

         if (object.value!="") {
            var dte = new Date(object.value)
            $(object.id+"_y").value = dte.getYear()
            $(object.id+"_m").value = (dte.getMonth() + 1).toPaddedString(2)
            $(object.id+"_d").value = dte.getDate().toPaddedString(2)
         }
         QIS.Date.save(object.id)
      },
      save: function(xid) {
         QIS.Date.getDays(xid)
         $(xid).value = $F(xid+"_m")+"/"+$F(xid+"_d")+"/"+$F(xid+"_y")
      },
      getDays: function(xid) {
         var hold  = $F(xid+"_d")
         var month = parseFloat($F(xid+"_m"))
         var days  = QIS.Date._days[month - 1]
         if (month==2 && (new Date($F(xid+"_y"),1,29).getDate()==29)) { days = 29 }
         for (var i=$(xid+"_d").options.length-1; i>=0; i--) { $(xid+"_d").remove(i); }
         for (var i=1; i<=days; i++) {
            var n   = document.createElement("option")
            n.text  = i.toPaddedString(2)
            n.value = i.toPaddedString(2)
            $(xid+"_d").options.add(n)
         }
         $(xid+"_d").value = hold
      }
   },

/**************************************************************************************************
   Calendar - Converts an input box into a calendar widgit.  Also validations to min/max values if
      available
   *  Optional
      -  error div with name "{id}_error"
      -  css element "error"
 **************************************************************************************************/
   Calendar: {
      _months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
      _days:   ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
      _over:   false,
      _format: "mm/dd/yyyy",
      selector: "input.QISCalendar",
      init: function(object) {
         object = $(object)
         if (object.readAttribute("qis") == "loaded") return
         object.writeAttribute("qis", "loaded")
         this.Container();
         var n = new Element("img", {"id":"img_"+object.id,"src":QIS._images+"calendar.gif","style":"position:absolute;z-index:10;"})
         object.setStyle({width:"115px"}).insert({after:n})
         object.observe("blur",function() { QIS.Calendar.Validate(object.id); })
         $("img_"+object.id).observe("click",     function() { QIS.Calendar.Show(object.id) });
         $("img_"+object.id).observe("mouseover", function() { QIS.Calendar._over=true  });
         $("img_"+object.id).observe("mouseout",  function() { QIS.Calendar._over=false });
      },
      Container: function() {
         var n = new Element("div",    {"id":"divCalendar","style":"position:absolute;width:150px;height:183px;display:none;z-index:50;"})
            .insert(new Element("iframe", {"id":"calShim",    "style":"position:absolute;width:150px;height:183px;top:0px;left:0px;","scrolling":"no","frameborder":"0","src":"javascript:false"}))
            .insert(new Element("div",    {"id":"calBody",    "style":"position:absolute;width:150px;height:183px;top:0px;left:0px;"}))
         $("anchor").insert({after:n});
         $("divCalendar").observe("mouseover", function() { QIS.Calendar._over=true  });
         $("divCalendar").observe("mouseout",  function() { QIS.Calendar._over=false });
         Event.observe(document, "click", function() { if (!QIS.Calendar._over) { QIS.Calendar.Hide() } });
      },
      Build: function() {
         var today=new Date(); today=new Date(today.getFullYear(), today.getMonth(), today.getDate());
         var sel=this._selected, min=this._min, max=this._max;
         var yy=this._date.getFullYear(), mm=this._date.getMonth()

         var s=""
         s+="<table cellspacing='1' cellpadding='0' border='0' class='qis_calendar'>"
         s+=   "<tr>"
         s+=      "<td colspan='7'>"
         s+=         "<table cellspacing='0' cellpadding='0' border='0' class='qis_calendar'>"
         s+=            "<tr class='title'>"
         s+=               "<td align='center' onClick='QIS.Calendar.moveYear(-1);'  title='Previous Year' >&#171;</td>"
         s+=               "<td align='center' onClick='QIS.Calendar.moveMonth(-1);' title='Previous Month'>&#139;</td>"
         s+=               "<td align='center' width='100%'>"+this._months[mm].substring(0,3)+"&nbsp;"+yy+"</td>"
         s+=               "<td align='center' onClick='QIS.Calendar.moveMonth(+1);' title='Next Month'>&#155;</td>"
         s+=               "<td align='center' onClick='QIS.Calendar.moveYear(+1);'  title='Next Year' >&#187;</td>"
         s+=            "</tr>"
         s+=         "</table>"
         s+=      "</td>"
         s+=   "</tr>"
         s+=   "<tr class='header'>"
         for (var i=0; i<7; i++) {
            s+=   "<td>"+this._days[i].charAt(0)+"</td>"
         }
         s+=   "</tr>"
         var idx=1
         var fd=this.firstDay()
         var ld=this.lastDay()
         for (var i=0; i<6; i++) {
            s+="<tr class='body'>"
            for (var j=0; j<7; j++) {
               var date = new Date(yy,mm,idx)
               switch (true) {
                  case (i*7+j<fd || idx>ld):
                     s+="<td>&nbsp;</td>"
                     break
                  case (!(today<date) && !(today>date) && !(sel<date) && !(sel>date)):
                     s+="<td class='today selected' onClick='QIS.Calendar.Select(this);'>"+(idx++)+"</td>"
                     break
                  case (!(today<date) && !(today>date)):
                     s+="<td class='today' onClick='QIS.Calendar.Select(this);'>"+(idx++)+"</td>"
                     break
                  case (!(sel<date) && !(sel>date)):
                     s+="<td class='selected' onClick='QIS.Calendar.Select(this);'>"+(idx++)+"</td>"
                     break
                  case (date < min || date > max):
                     s+="<td class='invalid'>"+(idx++)+"</td>"
                     break
                  default:
                     s+="<td onClick='QIS.Calendar.Select(this);'>"+(idx++)+"</td>"
                     break
               }
            }
            s+="</tr>"
         }
         s+=   "<tr class='footer'>"
         s+=      "<td colspan='7' align='center'>"
         s+=         "<span onClick='QIS.Calendar.Hide();'>Close</span>"
         s+=      "</td>"
         s+=   "</tr>"
         s+="</table>"
         $("calBody").update(s);
      },
      Select: function(object) {
         var yy=this._date.getFullYear(), mm=this._date.getMonth()+1, dd=object.innerHTML
         var d=this._format
         d = d.replace(/yyyy/i, yy)
         d = d.replace(/mm/i, ((mm<10)?"0"+mm:mm))
         d = d.replace(/dd/i, ((dd<10)?"0"+dd:dd))
         this._textbox.value=d;
         this.Hide()
      },
      Show: function(target, min, max) {
         if ($("divCalendar").visible() && this._textbox == $(target)) { this.Hide(); return; }
         this._textbox = $(target)
         var date=new Date()
         mm=(date.getMonth()+1).toPaddedString(2);
         dd=(date.getDate()).toPaddedString(2);
         yy=date.getFullYear();
         this._selected=this._date=(this.genDate(this._textbox.value) || this.genDate(mm+"/"+dd+"/"+yy))
         this._min=this.genDate(this._textbox.readAttribute("min") || "01/01/1800")
         this._max=this.genDate(this._textbox.readAttribute("max") || "01/01/2200")
         this.Build()
         var osTop  = this._textbox.getHeight()
         Position.clone(this._textbox, "divCalendar", {setWidth:false, setHeight:false, offsetTop:osTop})
         $("divCalendar").hide()
         try { Effect.SlideDown('divCalendar', { duration: .1 }); }
         catch (e) { $("divCalendar").show() }
      },
      Hide: function() {
         if (!$("divCalendar").visible()) { return; }
         try { if (Effect) Effect.SlideUp('divCalendar', { duration: .1 }); }
         catch (e) { $("divCalendar").hide() }
         this.Validate(this._textbox)
         this._textbox.focus()
      },
      genDate: function(s) {
         if (s!="" && this.reFormat().test(s)) {
            var yIdx = this._format.search(/yyyy/i);
            var mIdx = this._format.search(/mm/i);
            var dIdx = this._format.search(/dd/i);
            var yy=s.substring(yIdx,yIdx+4)-0;
            var mm=s.substring(mIdx,mIdx+2)-1;
            var dd=s.substring(dIdx,dIdx+2)-0;
            return new Date(yy,mm,dd);
         }
         return false
      },
      moveYear: function(i) {
         var yy=this._date.getFullYear()+i, mm=this._date.getMonth()
         this._date=new Date(yy,mm,1);
         this.Build();
      },
      moveMonth: function(i) {
         var yy=this._date.getFullYear(), mm=this._date.getMonth()+i
         if (mm<00) { mm=11; yy--; }
         if (mm>11) { mm=00; yy++; }
         this._date=new Date(yy,mm,1);
         this.Build();
      },
      firstDay: function() {
         var yy=this._date.getFullYear(), mm=this._date.getMonth();
         var fd=new Date(yy,mm,1);
         return (fd.getDay() != 0)? fd.getDay() : 7;
      },
      lastDay: function() {
         var yy=this._date.getFullYear(), mm=this._date.getMonth();
         for (var i=31; i>=28; i--) {
            var nd=new Date(yy,mm,i);
            if (mm == nd.getMonth()) { return i; }
         }
      },
      reFormat: function() {
         var re = this._format;
         re = re.replace(/\//g,'\\\/');
         re = re.replace(/dd/i,'\\d\\d');
         re = re.replace(/mm/i,'\\d\\d');
         re = re.replace(/yyyy/i,'\\d\\d\\d\\d');
         return new RegExp(re)
      },
      Validate: function(target) {
         object = $(target)
         var error = $(object.name+"_error")

         if (object.value == "") {
            if (error) { error.show().update("Entry required"); }
            return false;
         }

         var i=0, s="", a=new Array()
         $A(object.value+"|").each( function(n) {
            if (!'1234567890'.include(n)) {
               if (s!="") { a[i++]=s; s=""; }
            } else {
               s+=""+n;
            }
         })

         if (a.length == 0) {
            object.addClassName("error");
            if (error) { error.show().update("Invalid date"); }
            return false;
         } else if (a.length == 3) {
            var mm=parseFloat(a[0])
            var dd=parseFloat(a[1])
            var yy=parseFloat(a[2])
         } else if (a[0].length == 6 || a[0].length == 8) {
            var mm=parseFloat(a[0].toString().substring(0,2))
            var dd=parseFloat(a[0].toString().substring(2,4))
            var yy=parseFloat(a[0].toString().substring(4,4+a[0].length))
         } else {
            object.addClassName("error");
            if (error) { error.show().update("Invalid date"); }
            return false;
         }

         mm=mm.toPaddedString(2);
         dd=dd.toPaddedString(2);
         yy=(yy<50 )?2000+yy:yy
         yy=(yy<100)?1900+yy:yy
         var date = new Date(yy,mm-1,dd)
         if (yy!=date.getFullYear() || mm!=date.getMonth()+1 || dd!=date.getDate()) {
            object.addClassName("error");
            if (error) { error.show().update("Invalid date"); }
            return false;
         }
         if ((date.getFullYear()-(new Date().getFullYear())).abs() > 200) {
            object.addClassName("error");
            if (error) { error.show().update("Invalid date"); }
            return false;
         }

         if (object.readAttribute("min")) {
            if (date < this.genDate(object.readAttribute("min"))) {
               object.addClassName("error");
               if (error) { error.show().update("Date cannot be before "+object.readAttribute("min")); }
               return false;
            }
         }
         if (object.readAttribute("max")) {
            if (date > this.genDate(object.readAttribute("max"))) {
               object.addClassName("error");
               if (error) { error.show().update("Date cannot be after "+object.readAttribute("max")); }
               return false;
            }
         }

         object.removeClassName("error").value = mm+"/"+dd+"/"+yy
         if (error) { error.hide().update(); }
         return true;
      }
   }
}

var QIS = new QISLibrary();

